From 6a0a1528a9fd90bfcdd1c9283db4c717bc2d6975 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <abdullah_ahmed@brown.edu>
Date: Tue, 16 Apr 2019 18:12:46 -0400
Subject: basic linking and unlinking working

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

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

diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index a92cbd263..d81679ef3 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -18,6 +18,7 @@ import {
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { FieldViewProps } from "../views/nodes/FieldView";
 import { throwStatement } from "babel-types";
+const { openPrompt, TextField } = require("./ProsemirrorCopy/prompt.js");
 
 const SVG = "http://www.w3.org/2000/svg";
 
@@ -123,6 +124,9 @@ export class TooltipTextMenu {
         this.tooltip.appendChild(dd_fontStyle.render(this.view).dom);
         this.tooltip.appendChild(this.fontSizeIndicator);
         this.tooltip.appendChild(dd_fontSize.render(this.view).dom);
+
+        this.tooltip.appendChild(this.createLink().render(this.view).dom);
+
         dd_fontStyle.render(this.view).dom.nodeValue = "TEST";
         console.log(dd_fontStyle.render(this.view).dom.nodeValue);
     }
@@ -173,6 +177,48 @@ export class TooltipTextMenu {
             }
         });
     }
+
+    createLink() {
+        let markType = schema.marks.link;
+        return new MenuItem({
+            title: "Add or remove link",
+            label: "Add or remove link",
+            execEvent: "",
+            icon: icons.link,
+            css: "color:white;",
+            class: "menuicon",
+            enable(state) { return !state.selection.empty },
+            run: (state, dispatch, view) => {
+                // to remove link
+                if (this.markActive(state, markType)) {
+                    toggleMark(markType)(state, dispatch);
+                    return true;
+                }
+                // to create link
+                openPrompt({
+                    title: "Create a link",
+                    fields: {
+                        href: new TextField({
+                            label: "Link target",
+                            required: true
+                        }),
+                        title: new TextField({ label: "Title" })
+                    },
+                    callback(attrs: any) {
+                        toggleMark(markType, attrs)(view.state, view.dispatch);
+                        view.focus();
+                    }
+                });
+            }
+        });
+    }
+
+    markActive = function (state: EditorState<any>, type: MarkType<Schema<string, string>>) {
+        let { from, $from, to, empty } = state.selection
+        if (empty) return type.isInSet(state.storedMarks || $from.marks())
+        else return state.doc.rangeHasMark(from, to, type)
+    }
+
     // Helper function to create menu icons
     icon(text: string, name: string) {
         let span = document.createElement("span");
-- 
cgit v1.2.3-70-g09d2


From 8eefe79b19d0e8b8295ea4c967ec3caedf96123d Mon Sep 17 00:00:00 2001
From: Sam Wilkins <abdullah_ahmed@brown.edu>
Date: Mon, 22 Apr 2019 19:56:52 -0400
Subject: preliminary linking navigation

---
 src/client/util/ProsemirrorCopy/prompt.js | 21 ++++++++++++----
 src/client/util/TooltipTextMenu.tsx       | 42 ++++++++++++++++++++++++++++---
 2 files changed, 55 insertions(+), 8 deletions(-)

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

diff --git a/src/client/util/ProsemirrorCopy/prompt.js b/src/client/util/ProsemirrorCopy/prompt.js
index 35dd7b872..b9068195f 100644
--- a/src/client/util/ProsemirrorCopy/prompt.js
+++ b/src/client/util/ProsemirrorCopy/prompt.js
@@ -3,6 +3,9 @@ const prefix = "ProseMirror-prompt"
 export function openPrompt(options) {
     let wrapper = document.body.appendChild(document.createElement("div"))
     wrapper.className = prefix
+    wrapper.style.zIndex = 1000;
+    wrapper.style.width = 250;
+    wrapper.style.textAlign = "center";
 
     let mouseOutside = e => { if (!wrapper.contains(e.target)) close() }
     setTimeout(() => window.addEventListener("mousedown", mouseOutside), 50)
@@ -25,19 +28,24 @@ export function openPrompt(options) {
     cancelButton.addEventListener("click", close)
 
     let form = wrapper.appendChild(document.createElement("form"))
-    if (options.title) form.appendChild(document.createElement("h5")).textContent = options.title
+    let title = document.createElement("h5")
+    title.style.marginBottom = 15
+    title.style.marginTop = 10
+    if (options.title) form.appendChild(title).textContent = options.title
     domFields.forEach(field => {
         form.appendChild(document.createElement("div")).appendChild(field)
     })
-    let buttons = form.appendChild(document.createElement("div"))
-    buttons.className = prefix + "-buttons"
+    let b = document.createElement("div");
+    b.style.marginTop = 15;
+    let buttons = form.appendChild(b)
+    // buttons.className = prefix + "-buttons"
     buttons.appendChild(submitButton)
     buttons.appendChild(document.createTextNode(" "))
     buttons.appendChild(cancelButton)
 
     let box = wrapper.getBoundingClientRect()
-    wrapper.style.top = ((window.innerHeight - box.height) / 2) + "px"
-    wrapper.style.left = ((window.innerWidth - box.width) / 2) + "px"
+    wrapper.style.top = options.flyout_top + "px"
+    wrapper.style.left = options.flyout_left + "px"
 
     let submit = () => {
         let params = getValues(options.fields, domFields)
@@ -145,6 +153,9 @@ export class TextField extends Field {
         input.placeholder = this.options.label
         input.value = this.options.value || ""
         input.autocomplete = "off"
+        input.style.marginBottom = 4
+        input.style.border = "1px solid black"
+        input.style.padding = "4px 4px"
         return input
     }
 }
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index d81679ef3..c861083ce 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -6,18 +6,20 @@ 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 {
-    faListUl,
+    faListUl, faGrinTongueSquint,
 } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { FieldViewProps } from "../views/nodes/FieldView";
 import { throwStatement } from "babel-types";
+import { KeyStore } from "../../fields/KeyStore";
+import { NumberField } from "../../fields/NumberField";
 const { openPrompt, TextField } = require("./ProsemirrorCopy/prompt.js");
 
 const SVG = "http://www.w3.org/2000/svg";
@@ -35,6 +37,7 @@ export class TooltipTextMenu {
     private fontSizeToNum: Map<MarkType, number>;
     private fontStylesToName: Map<MarkType, string>;
     private fontSizeIndicator: HTMLSpanElement = document.createElement("span");
+    private link: HTMLAnchorElement;
 
     constructor(view: EditorView, editorProps: FieldViewProps) {
         this.view = view;
@@ -96,6 +99,15 @@ export class TooltipTextMenu {
 
         this.addFontDropdowns();
 
+        let target = "https://www.google.com";
+
+        this.link = document.createElement("a");
+        this.link.href = target;
+        this.link.textContent = target;
+        this.link.target = "_blank";
+        this.link.style.color = "white";
+        this.tooltip.appendChild(this.link);
+
         this.update(view, undefined);
     }
 
@@ -207,7 +219,9 @@ export class TooltipTextMenu {
                     callback(attrs: any) {
                         toggleMark(markType, attrs)(view.state, view.dispatch);
                         view.focus();
-                    }
+                    },
+                    flyout_top: 0,
+                    flyout_left: 0
                 });
             }
         });
@@ -266,6 +280,20 @@ export class TooltipTextMenu {
         };
     }
 
+    getMarksInSelection(state: EditorState<any>, targets: MarkType<any>[]) {
+        let found: Mark<any>[] = [];
+        let { from, to } = state.selection as TextSelection;
+        state.doc.nodesBetween(from, to, (node) => {
+            let marks = node.marks;
+            if (marks) {
+                marks.forEach(m => {
+                    if (targets.includes(m.type)) found.push(m);
+                });
+            }
+        });
+        return found;
+    }
+
     //updates the tooltip menu when the selection changes
     update(view: EditorView, lastState: EditorState | undefined) {
         let state = view.state;
@@ -279,6 +307,14 @@ export class TooltipTextMenu {
             return;
         }
 
+        let linksInSelection = this.activeMarksOnSelection([schema.marks.link]);
+        if (linksInSelection.length > 0) {
+            let attributes = this.getMarksInSelection(this.view.state, [schema.marks.link])[0].attrs;
+            this.link.href = attributes.href;
+            this.link.textContent = attributes.title;
+            this.link.style.visibility = "visible"
+        } else this.link.style.visibility = "hidden"
+
         // Otherwise, reposition it and update its content
         this.tooltip.style.display = "";
         let { from, to } = state.selection;
-- 
cgit v1.2.3-70-g09d2


From ece19e7f0fefdfff81e4599953bfb02429456963 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <abdullah_ahmed@brown.edu>
Date: Mon, 22 Apr 2019 20:13:47 -0400
Subject: clean up for rich text linking

---
 src/client/util/TooltipTextMenu.tsx | 4 ----
 1 file changed, 4 deletions(-)

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

diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index c861083ce..824ba463e 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -99,11 +99,7 @@ export class TooltipTextMenu {
 
         this.addFontDropdowns();
 
-        let target = "https://www.google.com";
-
         this.link = document.createElement("a");
-        this.link.href = target;
-        this.link.textContent = target;
         this.link.target = "_blank";
         this.link.style.color = "white";
         this.tooltip.appendChild(this.link);
-- 
cgit v1.2.3-70-g09d2


From 47ecf8d30f4aa5e25a659fc7f3c0c1487420150e Mon Sep 17 00:00:00 2001
From: Sam Wilkins <abdullah_ahmed@brown.edu>
Date: Thu, 9 May 2019 20:59:10 -0400
Subject: merge with master, but haven't reconciled internal and external
 linking

---
 package.json                                       |    5 +-
 src/Utils.ts                                       |   40 +-
 src/client/DocServer.ts                            |  130 +
 src/client/documents/Documents.ts                  |  286 +-
 src/client/goldenLayout.js                         | 5359 ++++++++++++++++++++
 .../northstar/core/brusher/IBaseBrushable.ts       |    4 +-
 src/client/northstar/core/filter/FilterModel.ts    |   17 +-
 .../northstar/core/filter/IBaseFilterConsumer.ts   |    4 +-
 .../northstar/core/filter/ValueComparision.ts      |    6 +-
 src/client/northstar/dash-fields/HistogramField.ts |   95 +-
 src/client/northstar/dash-nodes/HistogramBox.scss  |    6 +-
 src/client/northstar/dash-nodes/HistogramBox.tsx   |  104 +-
 .../dash-nodes/HistogramLabelPrimitives.tsx        |    8 +-
 .../northstar/operations/HistogramOperation.ts     |   52 +-
 src/client/util/DocumentManager.ts                 |   66 +-
 src/client/util/DragManager.ts                     |  123 +-
 src/client/util/ProsemirrorExampleTransfer.ts      |    1 -
 src/client/util/RichTextSchema.tsx                 |   87 +-
 src/client/util/Scripting.ts                       |   34 +-
 src/client/util/SelectionManager.ts                |   30 +-
 src/client/util/SerializationHelper.ts             |  130 +
 src/client/util/TooltipTextMenu.tsx                |  189 +-
 src/client/util/UndoManager.ts                     |   12 +-
 src/client/views/.DS_Store                         |  Bin 8196 -> 6148 bytes
 src/client/views/DocComponent.tsx                  |   14 +
 src/client/views/DocumentDecorations.scss          |  128 +-
 src/client/views/DocumentDecorations.tsx           |  390 +-
 src/client/views/EditableView.tsx                  |    2 +-
 src/client/views/InkingCanvas.tsx                  |   47 +-
 src/client/views/InkingControl.tsx                 |    7 +-
 src/client/views/InkingStroke.scss                 |    3 +
 src/client/views/InkingStroke.tsx                  |   10 +-
 src/client/views/Main.scss                         |   48 +-
 src/client/views/Main.tsx                          |  190 +-
 src/client/views/MainOverlayTextBox.scss           |    1 +
 src/client/views/MainOverlayTextBox.tsx            |   63 +-
 src/client/views/PresentationView.scss             |   68 +
 src/client/views/PresentationView.tsx              |  190 +
 src/client/views/PreviewCursor.tsx                 |   36 +-
 src/client/views/TemplateMenu.tsx                  |   91 +
 src/client/views/Templates.tsx                     |   84 +
 .../views/collections/CollectionBaseView.tsx       |  115 +-
 .../views/collections/CollectionDockingView.tsx    |  224 +-
 .../views/collections/CollectionPDFView.scss       |   26 +-
 src/client/views/collections/CollectionPDFView.tsx |   49 +-
 .../views/collections/CollectionSchemaView.scss    |   12 +-
 .../views/collections/CollectionSchemaView.tsx     |  184 +-
 src/client/views/collections/CollectionSubView.tsx |  363 +-
 .../views/collections/CollectionTreeView.scss      |   56 +-
 .../views/collections/CollectionTreeView.tsx       |  186 +-
 .../views/collections/CollectionVideoView.tsx      |   88 +-
 src/client/views/collections/CollectionView.tsx    |   15 +-
 .../CollectionFreeFormLinkView.scss                |   10 +-
 .../CollectionFreeFormLinkView.tsx                 |   51 +-
 .../CollectionFreeFormLinksView.tsx                |  117 +-
 .../CollectionFreeFormRemoteCursors.tsx            |   65 +-
 .../collectionFreeForm/CollectionFreeFormView.scss |  151 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  242 +-
 .../collectionFreeForm/MarqueeView.scss            |    2 +-
 .../collections/collectionFreeForm/MarqueeView.tsx |  258 +-
 src/client/views/globalCssVariables.scss           |    6 +-
 src/client/views/globalCssVariables.scss.d.ts      |    4 +-
 src/client/views/nodes/AudioBox.tsx                |   27 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  260 +-
 src/client/views/nodes/DocumentContentsView.tsx    |   76 +-
 src/client/views/nodes/DocumentView.scss           |   17 +-
 src/client/views/nodes/DocumentView.tsx            |  365 +-
 src/client/views/nodes/FieldView.tsx               |   84 +-
 src/client/views/nodes/FormattedTextBox.scss       |   18 +-
 src/client/views/nodes/FormattedTextBox.tsx        |  266 +-
 src/client/views/nodes/IconBox.scss                |   22 +
 src/client/views/nodes/IconBox.tsx                 |   69 +
 src/client/views/nodes/ImageBox.scss               |    6 +
 src/client/views/nodes/ImageBox.tsx                |   78 +-
 src/client/views/nodes/KeyValueBox.scss            |    1 +
 src/client/views/nodes/KeyValueBox.tsx             |   37 +-
 src/client/views/nodes/KeyValuePair.scss           |    1 +
 src/client/views/nodes/KeyValuePair.tsx            |   62 +-
 src/client/views/nodes/LinkBox.scss                |   23 +-
 src/client/views/nodes/LinkBox.tsx                 |   84 +-
 src/client/views/nodes/LinkEditor.scss             |    3 +-
 src/client/views/nodes/LinkEditor.tsx              |   17 +-
 src/client/views/nodes/LinkMenu.tsx                |   27 +-
 src/client/views/nodes/PDFBox.scss                 |   18 +
 src/client/views/nodes/PDFBox.tsx                  |  302 +-
 src/client/views/nodes/Sticky.tsx                  |   83 -
 src/client/views/nodes/VideoBox.tsx                |   80 +-
 src/client/views/nodes/WebBox.scss                 |   17 +-
 src/client/views/nodes/WebBox.tsx                  |   38 +-
 src/debug/Test.tsx                                 |   87 +-
 src/debug/Viewer.tsx                               |  368 +-
 src/fields/AudioField.ts                           |   31 -
 src/fields/BasicField.ts                           |   59 -
 src/fields/BooleanField.ts                         |   25 -
 src/fields/Document.ts                             |  430 --
 src/fields/DocumentReference.ts                    |   57 -
 src/fields/Field.ts                                |   69 -
 src/fields/FieldUpdatedArgs.ts                     |   27 -
 src/fields/HtmlField.ts                            |   25 -
 src/fields/ImageField.ts                           |   29 -
 src/fields/InkField.ts                             |   53 -
 src/fields/Key.ts                                  |   50 -
 src/fields/KeyStore.ts                             |   65 -
 src/fields/ListField.ts                            |  196 -
 src/fields/NumberField.ts                          |   25 -
 src/fields/PDFField.ts                             |   36 -
 src/fields/RichTextField.ts                        |   26 -
 src/fields/ScriptField.ts                          |  174 +-
 src/fields/TextField.ts                            |   25 -
 src/fields/TupleField.ts                           |   59 -
 src/fields/VideoField.ts                           |   30 -
 src/fields/WebField.ts                             |   30 -
 src/mobile/ImageUpload.tsx                         |   34 +-
 src/new_fields/CursorField.ts                      |   55 +
 src/new_fields/DateField.ts                        |   18 +
 src/new_fields/Doc.ts                              |  228 +
 src/new_fields/HtmlField.ts                        |   18 +
 src/new_fields/IconField.ts                        |   18 +
 src/new_fields/InkField.ts                         |   43 +
 src/new_fields/List.ts                             |  279 +
 src/new_fields/ObjectField.ts                      |   18 +
 src/new_fields/Proxy.ts                            |   65 +
 src/new_fields/RefField.ts                         |   18 +
 src/new_fields/RichTextField.ts                    |   18 +
 src/new_fields/Schema.ts                           |   82 +
 src/new_fields/Types.ts                            |   88 +
 src/new_fields/URLField.ts                         |   34 +
 src/new_fields/util.ts                             |  104 +
 src/server/Message.ts                              |   17 +-
 src/server/ServerUtil.ts                           |   60 -
 .../authentication/controllers/WorkspacesMenu.css  |    3 -
 .../authentication/controllers/WorkspacesMenu.tsx  |   89 -
 .../authentication/models/current_user_utils.ts    |   37 +-
 src/server/database.ts                             |   58 +-
 src/server/index.ts                                |   31 +-
 test/test.ts                                       |  166 +-
 136 files changed, 11361 insertions(+), 4661 deletions(-)
 create mode 100644 src/client/DocServer.ts
 create mode 100644 src/client/goldenLayout.js
 create mode 100644 src/client/util/SerializationHelper.ts
 create mode 100644 src/client/views/DocComponent.tsx
 create mode 100644 src/client/views/InkingStroke.scss
 create mode 100644 src/client/views/PresentationView.scss
 create mode 100644 src/client/views/PresentationView.tsx
 create mode 100644 src/client/views/TemplateMenu.tsx
 create mode 100644 src/client/views/Templates.tsx
 create mode 100644 src/client/views/nodes/IconBox.scss
 create mode 100644 src/client/views/nodes/IconBox.tsx
 delete mode 100644 src/client/views/nodes/Sticky.tsx
 delete mode 100644 src/fields/AudioField.ts
 delete mode 100644 src/fields/BasicField.ts
 delete mode 100644 src/fields/BooleanField.ts
 delete mode 100644 src/fields/Document.ts
 delete mode 100644 src/fields/DocumentReference.ts
 delete mode 100644 src/fields/Field.ts
 delete mode 100644 src/fields/FieldUpdatedArgs.ts
 delete mode 100644 src/fields/HtmlField.ts
 delete mode 100644 src/fields/ImageField.ts
 delete mode 100644 src/fields/InkField.ts
 delete mode 100644 src/fields/Key.ts
 delete mode 100644 src/fields/KeyStore.ts
 delete mode 100644 src/fields/ListField.ts
 delete mode 100644 src/fields/NumberField.ts
 delete mode 100644 src/fields/PDFField.ts
 delete mode 100644 src/fields/RichTextField.ts
 delete mode 100644 src/fields/TextField.ts
 delete mode 100644 src/fields/TupleField.ts
 delete mode 100644 src/fields/VideoField.ts
 delete mode 100644 src/fields/WebField.ts
 create mode 100644 src/new_fields/CursorField.ts
 create mode 100644 src/new_fields/DateField.ts
 create mode 100644 src/new_fields/Doc.ts
 create mode 100644 src/new_fields/HtmlField.ts
 create mode 100644 src/new_fields/IconField.ts
 create mode 100644 src/new_fields/InkField.ts
 create mode 100644 src/new_fields/List.ts
 create mode 100644 src/new_fields/ObjectField.ts
 create mode 100644 src/new_fields/Proxy.ts
 create mode 100644 src/new_fields/RefField.ts
 create mode 100644 src/new_fields/RichTextField.ts
 create mode 100644 src/new_fields/Schema.ts
 create mode 100644 src/new_fields/Types.ts
 create mode 100644 src/new_fields/URLField.ts
 create mode 100644 src/new_fields/util.ts
 delete mode 100644 src/server/ServerUtil.ts
 delete mode 100644 src/server/authentication/controllers/WorkspacesMenu.css
 delete mode 100644 src/server/authentication/controllers/WorkspacesMenu.tsx

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

diff --git a/package.json b/package.json
index 2463afa74..943451c3b 100644
--- a/package.json
+++ b/package.json
@@ -34,7 +34,7 @@
         "webpack": "^4.29.6",
         "webpack-cli": "^3.2.3",
         "webpack-dev-middleware": "^3.6.1",
-        "webpack-dev-server": "^3.2.1",
+        "webpack-dev-server": "^3.3.1",
         "webpack-hot-middleware": "^2.24.3"
     },
     "dependencies": {
@@ -125,7 +125,7 @@
         "mobx-react-devtools": "^6.1.1",
         "mongodb": "^3.1.13",
         "mongoose": "^5.4.18",
-        "node-sass": "^4.11.0",
+        "node-sass": "^4.12.0",
         "nodemailer": "^5.1.1",
         "nodemon": "^1.18.10",
         "normalize.css": "^8.0.1",
@@ -163,6 +163,7 @@
         "react-table": "^6.9.2",
         "request": "^2.88.0",
         "request-promise": "^4.2.4",
+        "serializr": "^1.5.1",
         "socket.io": "^2.2.0",
         "socket.io-client": "^2.2.0",
         "typescript-collections": "^1.3.2",
diff --git a/src/Utils.ts b/src/Utils.ts
index dec6245ef..d4b6f5377 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -1,11 +1,12 @@
 import v4 = require('uuid/v4');
 import v5 = require("uuid/v5");
 import { Socket } from 'socket.io';
-import { Message, Types, Transferable } from './server/Message';
-import { Document } from './fields/Document';
+import { Message } from './server/Message';
 
 export class Utils {
 
+    public static DRAG_THRESHOLD = 4;
+
     public static GenerateGuid(): string {
         return v4();
     }
@@ -87,13 +88,20 @@ export class Utils {
     }
 }
 
-export function OmitKeys(obj: any, keys: any, addKeyFunc?: (dup: any) => void) {
+export function OmitKeys(obj: any, keys: string[], addKeyFunc?: (dup: any) => void): { omit: any, extract: any } {
+    const omit: any = { ...obj };
+    const extract: any = {};
+    keys.forEach(key => {
+        extract[key] = omit[key];
+        delete omit[key];
+    });
+    addKeyFunc && addKeyFunc(omit);
+    return { omit, extract };
+}
+
+export function WithKeys(obj: any, keys: string[], addKeyFunc?: (dup: any) => void) {
     var dup: any = {};
-    for (var key in obj) {
-        if (keys.indexOf(key) === -1) {
-            dup[key] = obj[key];
-        }
-    }
+    keys.forEach(key => dup[key] = obj[key]);
     addKeyFunc && addKeyFunc(dup);
     return dup;
 }
@@ -108,6 +116,18 @@ export function returnZero() { return 0; }
 
 export function emptyFunction() { }
 
-export function emptyDocFunction(doc: Document) { }
+export type Without<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
+
+export type Predicate<K, V> = (entry: [K, V]) => boolean;
 
-export type Without<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
\ No newline at end of file
+export function deepCopy<K, V>(source: Map<K, V>, predicate?: Predicate<K, V>) {
+    let deepCopy = new Map<K, V>();
+    let entries = source.entries(), next = entries.next();
+    while (!next.done) {
+        let entry = next.value;
+        if (!predicate || predicate(entry)) {
+            deepCopy.set(entry[0], entry[1]);
+        }
+    }
+    return deepCopy;
+}
\ No newline at end of file
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
new file mode 100644
index 000000000..a288d394a
--- /dev/null
+++ b/src/client/DocServer.ts
@@ -0,0 +1,130 @@
+import * as OpenSocket from 'socket.io-client';
+import { MessageStore } from "./../server/Message";
+import { Opt } from '../new_fields/Doc';
+import { Utils } from '../Utils';
+import { SerializationHelper } from './util/SerializationHelper';
+import { RefField, HandleUpdate, Id } from '../new_fields/RefField';
+
+export namespace DocServer {
+    const _cache: { [id: string]: RefField | Promise<Opt<RefField>> } = {};
+    const _socket = OpenSocket(`${window.location.protocol}//${window.location.hostname}:4321`);
+    const GUID: string = Utils.GenerateGuid();
+
+    export function prepend(extension: string): string {
+        return window.location.origin + extension;
+    }
+
+    export function DeleteDatabase() {
+        Utils.Emit(_socket, MessageStore.DeleteAll, {});
+    }
+
+    export async function GetRefField(id: string): Promise<Opt<RefField>> {
+        let cached = _cache[id];
+        if (cached === undefined) {
+            const prom = Utils.EmitCallback(_socket, MessageStore.GetRefField, id).then(fieldJson => {
+                const field = SerializationHelper.Deserialize(fieldJson);
+                if (_cache[id] !== undefined && !(_cache[id] instanceof Promise)) {
+                    id;
+                }
+                if (field !== undefined) {
+                    _cache[id] = field;
+                } else {
+                    delete _cache[id];
+                }
+                return field;
+            });
+            _cache[id] = prom;
+            return prom;
+        } else if (cached instanceof Promise) {
+            return cached;
+        } else {
+            return cached;
+        }
+    }
+
+    export async function GetRefFields(ids: string[]): Promise<{ [id: string]: Opt<RefField> }> {
+        const requestedIds: string[] = [];
+        const waitingIds: string[] = [];
+        const promises: Promise<Opt<RefField>>[] = [];
+        const map: { [id: string]: Opt<RefField> } = {};
+        for (const id of ids) {
+            const cached = _cache[id];
+            if (cached === undefined) {
+                requestedIds.push(id);
+            } else if (cached instanceof Promise) {
+                promises.push(cached);
+                waitingIds.push(id);
+            } else {
+                map[id] = cached;
+            }
+        }
+        const prom = Utils.EmitCallback(_socket, MessageStore.GetRefFields, requestedIds).then(fields => {
+            const fieldMap: { [id: string]: RefField } = {};
+            for (const field of fields) {
+                if (field !== undefined) {
+                    fieldMap[field.id] = SerializationHelper.Deserialize(field);
+                }
+            }
+            return fieldMap;
+        });
+        requestedIds.forEach(id => _cache[id] = prom.then(fields => fields[id]));
+        const fields = await prom;
+        requestedIds.forEach(id => {
+            const field = fields[id];
+            if (field !== undefined) {
+                _cache[id] = field;
+            } else {
+                delete _cache[id];
+            }
+            map[id] = field;
+        });
+        const otherFields = await Promise.all(promises);
+        waitingIds.forEach((id, index) => map[id] = otherFields[index]);
+        return map;
+    }
+
+    export function UpdateField(id: string, diff: any) {
+        if (id === updatingId) {
+            return;
+        }
+        Utils.Emit(_socket, MessageStore.UpdateField, { id, diff });
+    }
+
+    export function CreateField(field: RefField) {
+        _cache[field[Id]] = field;
+        const initialState = SerializationHelper.Serialize(field);
+        Utils.Emit(_socket, MessageStore.CreateField, initialState);
+    }
+
+    let updatingId: string | undefined;
+    function respondToUpdate(diff: any) {
+        const id = diff.id;
+        if (id === undefined) {
+            return;
+        }
+        const field = _cache[id];
+        const update = (f: Opt<RefField>) => {
+            if (f === undefined) {
+                return;
+            }
+            const handler = f[HandleUpdate];
+            if (handler) {
+                updatingId = id;
+                handler.call(f, diff.diff);
+                updatingId = undefined;
+            }
+        };
+        if (field instanceof Promise) {
+            field.then(update);
+        } else {
+            update(field);
+        }
+    }
+
+    function connected() {
+        _socket.emit(MessageStore.Bar.Message, GUID);
+    }
+
+    Utils.AddServerHandler(_socket, MessageStore.Foo, connected);
+    Utils.AddServerHandler(_socket, MessageStore.UpdateField, respondToUpdate);
+}
\ No newline at end of file
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 4febfa7eb..63ba01b6a 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1,20 +1,6 @@
-import { AudioField } from "../../fields/AudioField";
-import { Document } from "../../fields/Document";
-import { Field, Opt } from "../../fields/Field";
-import { HtmlField } from "../../fields/HtmlField";
-import { ImageField } from "../../fields/ImageField";
-import { InkField, StrokeData } from "../../fields/InkField";
-import { Key } from "../../fields/Key";
-import { KeyStore } from "../../fields/KeyStore";
-import { ListField } from "../../fields/ListField";
-import { PDFField } from "../../fields/PDFField";
-import { TextField } from "../../fields/TextField";
-import { VideoField } from "../../fields/VideoField";
-import { WebField } from "../../fields/WebField";
 import { HistogramField } from "../northstar/dash-fields/HistogramField";
 import { HistogramBox } from "../northstar/dash-nodes/HistogramBox";
 import { HistogramOperation } from "../northstar/operations/HistogramOperation";
-import { Server } from "../Server";
 import { CollectionPDFView } from "../views/collections/CollectionPDFView";
 import { CollectionVideoView } from "../views/collections/CollectionVideoView";
 import { CollectionView } from "../views/collections/CollectionView";
@@ -32,37 +18,62 @@ import { action } from "mobx";
 import { ColumnAttributeModel } from "../northstar/core/attribute/AttributeModel";
 import { AttributeTransformationModel } from "../northstar/core/attribute/AttributeTransformationModel";
 import { AggregateFunction } from "../northstar/model/idea/idea";
+import { Template } from "../views/Templates";
+import { MINIMIZED_ICON_SIZE } from "../views/globalCssVariables.scss";
+import { IconBox } from "../views/nodes/IconBox";
+import { Field, Doc, Opt } from "../../new_fields/Doc";
+import { OmitKeys } from "../../Utils";
+import { ImageField, VideoField, AudioField, PdfField, WebField } from "../../new_fields/URLField";
+import { HtmlField } from "../../new_fields/HtmlField";
+import { List } from "../../new_fields/List";
+import { Cast } from "../../new_fields/Types";
+import { IconField } from "../../new_fields/IconField";
+import { listSpec } from "../../new_fields/Schema";
+import { DocServer } from "../DocServer";
+import { StrokeData, InkField } from "../../new_fields/InkField";
+import { dropActionType } from "../util/DragManager";
+import { DateField } from "../../new_fields/DateField";
 
 export interface DocumentOptions {
     x?: number;
     y?: number;
-    ink?: Map<string, StrokeData>;
+    ink?: InkField;
     width?: number;
     height?: number;
     nativeWidth?: number;
     nativeHeight?: number;
     title?: string;
-    panx?: number;
-    pany?: number;
+    panX?: number;
+    panY?: number;
     page?: number;
     scale?: number;
+    baseLayout?: string;
     layout?: string;
-    layoutKeys?: Key[];
+    templates?: List<string>;
     viewType?: number;
     backgroundColor?: string;
-    copyDraggedItems?: boolean;
+    dropAction?: dropActionType;
+    backgroundLayout?: string;
+    curPage?: number;
+    documentText?: string;
+    borderRounding?: number;
+    schemaColumns?: List<string>;
+    dockingConfig?: string;
+    // [key: string]: Opt<Field>;
 }
+const delegateKeys = ["x", "y", "width", "height", "panX", "panY"];
 
-export namespace Documents {
-    let textProto: Document;
-    let histoProto: Document;
-    let imageProto: Document;
-    let webProto: Document;
-    let collProto: Document;
-    let kvpProto: Document;
-    let videoProto: Document;
-    let audioProto: Document;
-    let pdfProto: Document;
+export namespace Docs {
+    let textProto: Doc;
+    let histoProto: Doc;
+    let imageProto: Doc;
+    let webProto: Doc;
+    let collProto: Doc;
+    let kvpProto: Doc;
+    let videoProto: Doc;
+    let audioProto: Doc;
+    let pdfProto: Doc;
+    let iconProto: Doc;
     const textProtoId = "textProto";
     const histoProtoId = "histoProto";
     const pdfProtoId = "pdfProto";
@@ -72,117 +83,103 @@ export namespace Documents {
     const kvpProtoId = "kvpProto";
     const videoProtoId = "videoProto";
     const audioProtoId = "audioProto";
+    const iconProtoId = "iconProto";
 
     export function initProtos(): Promise<void> {
-        return Server.GetFields([textProtoId, histoProtoId, collProtoId, pdfProtoId, imageProtoId, videoProtoId, audioProtoId, webProtoId, kvpProtoId]).then(fields => {
-            textProto = fields[textProtoId] as Document || CreateTextPrototype();
-            histoProto = fields[histoProtoId] as Document || CreateHistogramPrototype();
-            collProto = fields[collProtoId] as Document || CreateCollectionPrototype();
-            imageProto = fields[imageProtoId] as Document || CreateImagePrototype();
-            webProto = fields[webProtoId] as Document || CreateWebPrototype();
-            kvpProto = fields[kvpProtoId] as Document || CreateKVPPrototype();
-            videoProto = fields[videoProtoId] as Document || CreateVideoPrototype();
-            audioProto = fields[audioProtoId] as Document || CreateAudioPrototype();
-            pdfProto = fields[pdfProtoId] as Document || CreatePdfPrototype();
+        return DocServer.GetRefFields([textProtoId, histoProtoId, collProtoId, imageProtoId, webProtoId, kvpProtoId, videoProtoId, audioProtoId, pdfProtoId, iconProtoId]).then(fields => {
+            textProto = fields[textProtoId] as Doc || CreateTextPrototype();
+            histoProto = fields[histoProtoId] as Doc || CreateHistogramPrototype();
+            collProto = fields[collProtoId] as Doc || CreateCollectionPrototype();
+            imageProto = fields[imageProtoId] as Doc || CreateImagePrototype();
+            webProto = fields[webProtoId] as Doc || CreateWebPrototype();
+            kvpProto = fields[kvpProtoId] as Doc || CreateKVPPrototype();
+            videoProto = fields[videoProtoId] as Doc || CreateVideoPrototype();
+            audioProto = fields[audioProtoId] as Doc || CreateAudioPrototype();
+            pdfProto = fields[pdfProtoId] as Doc || CreatePdfPrototype();
+            iconProto = fields[iconProtoId] as Doc || CreateIconPrototype();
         });
     }
-    function assignOptions(doc: Document, options: DocumentOptions): Document {
-        if (options.nativeWidth !== undefined) { doc.SetNumber(KeyStore.NativeWidth, options.nativeWidth); }
-        if (options.nativeHeight !== undefined) { doc.SetNumber(KeyStore.NativeHeight, options.nativeHeight); }
-        if (options.title !== undefined) { doc.SetText(KeyStore.Title, options.title); }
-        if (options.page !== undefined) { doc.SetNumber(KeyStore.Page, options.page); }
-        if (options.scale !== undefined) { doc.SetNumber(KeyStore.Scale, options.scale); }
-        if (options.viewType !== undefined) { doc.SetNumber(KeyStore.ViewType, options.viewType); }
-        if (options.backgroundColor !== undefined) { doc.SetText(KeyStore.BackgroundColor, options.backgroundColor); }
-        if (options.ink !== undefined) { doc.Set(KeyStore.Ink, new InkField(options.ink)); }
-        if (options.layout !== undefined) { doc.SetText(KeyStore.Layout, options.layout); }
-        if (options.layoutKeys !== undefined) { doc.Set(KeyStore.LayoutKeys, new ListField(options.layoutKeys)); }
-        if (options.copyDraggedItems !== undefined) { doc.SetBoolean(KeyStore.CopyDraggedItems, options.copyDraggedItems); }
-        return doc;
-    }
 
-    function assignToDelegate(doc: Document, options: DocumentOptions): Document {
-        if (options.x !== undefined) { doc.SetNumber(KeyStore.X, options.x); }
-        if (options.y !== undefined) { doc.SetNumber(KeyStore.Y, options.y); }
-        if (options.width !== undefined) { doc.SetNumber(KeyStore.Width, options.width); }
-        if (options.height !== undefined) { doc.SetNumber(KeyStore.Height, options.height); }
-        if (options.panx !== undefined) { doc.SetNumber(KeyStore.PanX, options.panx); }
-        if (options.pany !== undefined) { doc.SetNumber(KeyStore.PanY, options.pany); }
-        return doc;
+    function setupPrototypeOptions(protoId: string, title: string, layout: string, options: DocumentOptions): Doc {
+        return Doc.assign(new Doc(protoId, true), { ...options, title: title, layout: layout, baseLayout: layout });
     }
-
-    function setupPrototypeOptions(protoId: string, title: string, layout: string, options: DocumentOptions): Document {
-        return assignOptions(new Document(protoId), { ...options, title: title, layout: layout });
+    function SetInstanceOptions<U extends Field>(doc: Doc, options: DocumentOptions, value: U) {
+        const deleg = Doc.MakeDelegate(doc);
+        deleg.data = value;
+        return Doc.assign(deleg, options);
     }
-    function SetInstanceOptions<T, U extends Field & { Data: T }>(doc: Document, options: DocumentOptions, value: [T, { new(): U }] | Document, id?: string) {
-        var deleg = doc.MakeDelegate(id);
-        if (value instanceof Document) {
-            deleg.Set(KeyStore.Data, value);
-        }
-        else {
-            deleg.SetData(KeyStore.Data, value[0], value[1]);
-        }
-        return assignOptions(deleg, options);
+    function SetDelegateOptions<U extends Field>(doc: Doc, options: DocumentOptions) {
+        const deleg = Doc.MakeDelegate(doc);
+        return Doc.assign(deleg, options);
     }
 
-    function CreateImagePrototype(): Document {
-        let imageProto = setupPrototypeOptions(imageProtoId, "IMAGE_PROTO", CollectionView.LayoutString("AnnotationsKey"),
-            { x: 0, y: 0, nativeWidth: 600, width: 300, layoutKeys: [KeyStore.Data, KeyStore.Annotations, KeyStore.Caption] });
-        imageProto.SetText(KeyStore.BackgroundLayout, ImageBox.LayoutString());
-        imageProto.SetNumber(KeyStore.CurPage, 0);
+    function CreateImagePrototype(): Doc {
+        let imageProto = setupPrototypeOptions(imageProtoId, "IMAGE_PROTO", CollectionView.LayoutString("annotations"),
+            { x: 0, y: 0, nativeWidth: 600, width: 300, backgroundLayout: ImageBox.LayoutString(), curPage: 0 });
         return imageProto;
     }
 
-    function CreateHistogramPrototype(): Document {
-        let histoProto = setupPrototypeOptions(histoProtoId, "HISTO PROTO", CollectionView.LayoutString("AnnotationsKey"),
-            { x: 0, y: 0, width: 300, height: 300, backgroundColor: "black", layoutKeys: [KeyStore.Data, KeyStore.Annotations, KeyStore.Caption] });
-        histoProto.SetText(KeyStore.BackgroundLayout, HistogramBox.LayoutString());
+    function CreateHistogramPrototype(): Doc {
+        let histoProto = setupPrototypeOptions(histoProtoId, "HISTO PROTO", CollectionView.LayoutString("annotations"),
+            { x: 0, y: 0, width: 300, height: 300, backgroundColor: "black", backgroundLayout: HistogramBox.LayoutString() });
         return histoProto;
     }
-    function CreateTextPrototype(): Document {
+    function CreateIconPrototype(): Doc {
+        let iconProto = setupPrototypeOptions(iconProtoId, "ICON_PROTO", IconBox.LayoutString(),
+            { x: 0, y: 0, width: Number(MINIMIZED_ICON_SIZE), height: Number(MINIMIZED_ICON_SIZE) });
+        return iconProto;
+    }
+    function CreateTextPrototype(): Doc {
         let textProto = setupPrototypeOptions(textProtoId, "TEXT_PROTO", FormattedTextBox.LayoutString(),
-            { x: 0, y: 0, width: 300, height: 150, layoutKeys: [KeyStore.Data] });
+            { x: 0, y: 0, width: 300, height: 150, backgroundColor: "#f1efeb" });
         return textProto;
     }
-    function CreatePdfPrototype(): Document {
-        let pdfProto = setupPrototypeOptions(pdfProtoId, "PDF_PROTO", CollectionPDFView.LayoutString("AnnotationsKey"),
-            { x: 0, y: 0, nativeWidth: 1200, width: 300, layoutKeys: [KeyStore.Data, KeyStore.Annotations] });
-        pdfProto.SetNumber(KeyStore.CurPage, 1);
-        pdfProto.SetText(KeyStore.BackgroundLayout, PDFBox.LayoutString());
+    function CreatePdfPrototype(): Doc {
+        let pdfProto = setupPrototypeOptions(pdfProtoId, "PDF_PROTO", CollectionPDFView.LayoutString("annotations"),
+            { x: 0, y: 0, nativeWidth: 1200, width: 300, backgroundLayout: PDFBox.LayoutString(), curPage: 1 });
         return pdfProto;
     }
-    function CreateWebPrototype(): Document {
+    function CreateWebPrototype(): Doc {
         let webProto = setupPrototypeOptions(webProtoId, "WEB_PROTO", WebBox.LayoutString(),
-            { x: 0, y: 0, width: 300, height: 300, layoutKeys: [KeyStore.Data] });
+            { x: 0, y: 0, width: 300, height: 300 });
         return webProto;
     }
-    function CreateCollectionPrototype(): Document {
-        let collProto = setupPrototypeOptions(collProtoId, "COLLECTION_PROTO", CollectionView.LayoutString("DataKey"),
-            { panx: 0, pany: 0, scale: 1, width: 500, height: 500, layoutKeys: [KeyStore.Data] });
+    function CreateCollectionPrototype(): Doc {
+        let collProto = setupPrototypeOptions(collProtoId, "COLLECTION_PROTO", CollectionView.LayoutString("data"),
+            { panX: 0, panY: 0, scale: 1, width: 500, height: 500 });
         return collProto;
     }
 
-    function CreateKVPPrototype(): Document {
+    function CreateKVPPrototype(): Doc {
         let kvpProto = setupPrototypeOptions(kvpProtoId, "KVP_PROTO", KeyValueBox.LayoutString(),
-            { x: 0, y: 0, width: 300, height: 150, layoutKeys: [KeyStore.Data] });
+            { x: 0, y: 0, width: 300, height: 150 });
         return kvpProto;
     }
-    function CreateVideoPrototype(): Document {
-        let videoProto = setupPrototypeOptions(videoProtoId, "VIDEO_PROTO", CollectionVideoView.LayoutString("AnnotationsKey"),
-            { x: 0, y: 0, nativeWidth: 600, width: 300, layoutKeys: [KeyStore.Data, KeyStore.Annotations, KeyStore.Caption] });
-        videoProto.SetNumber(KeyStore.CurPage, 0);
-        videoProto.SetText(KeyStore.BackgroundLayout, VideoBox.LayoutString());
+    function CreateVideoPrototype(): Doc {
+        let videoProto = setupPrototypeOptions(videoProtoId, "VIDEO_PROTO", CollectionVideoView.LayoutString("annotations"),
+            { x: 0, y: 0, nativeWidth: 600, width: 300, backgroundLayout: VideoBox.LayoutString(), curPage: 0 });
         return videoProto;
     }
-    function CreateAudioPrototype(): Document {
+    function CreateAudioPrototype(): Doc {
         let audioProto = setupPrototypeOptions(audioProtoId, "AUDIO_PROTO", AudioBox.LayoutString(),
-            { x: 0, y: 0, width: 300, height: 150, layoutKeys: [KeyStore.Data] });
+            { x: 0, y: 0, width: 300, height: 150 });
         return audioProto;
     }
 
+    function CreateInstance(proto: Doc, data: Field, options: DocumentOptions) {
+        const { omit: protoProps, extract: delegateProps } = OmitKeys(options, delegateKeys);
+        if (!("author" in protoProps)) {
+            protoProps.author = CurrentUserUtils.email;
+        }
+        if (!("creationDate" in protoProps)) {
+            protoProps.creationDate = new DateField;
+        }
+
+        return SetDelegateOptions(SetInstanceOptions(proto, protoProps, data), delegateProps);
+    }
 
     export function ImageDocument(url: string, options: DocumentOptions = {}) {
-        return assignToDelegate(SetInstanceOptions(imageProto, options, [new URL(url), ImageField]).MakeDelegate(), { ...options, layoutKeys: [KeyStore.Data, KeyStore.Annotations, KeyStore.Caption] });
+        return CreateInstance(imageProto, new ImageField(new URL(url)), options);
         // let doc = SetInstanceOptions(GetImagePrototype(), { ...options, layoutKeys: [KeyStore.Data, KeyStore.Annotations, KeyStore.Caption] },
         //     [new URL(url), ImageField]);
         // doc.SetText(KeyStore.Caption, "my caption...");
@@ -191,76 +188,83 @@ export namespace Documents {
         // return doc;
     }
     export function VideoDocument(url: string, options: DocumentOptions = {}) {
-        return assignToDelegate(SetInstanceOptions(videoProto, options, [new URL(url), VideoField]), options);
+        return CreateInstance(videoProto, new VideoField(new URL(url)), options);
     }
     export function AudioDocument(url: string, options: DocumentOptions = {}) {
-        return assignToDelegate(SetInstanceOptions(audioProto, options, [new URL(url), AudioField]), options);
+        return CreateInstance(audioProto, new AudioField(new URL(url)), options);
     }
 
-    export function HistogramDocument(histoOp: HistogramOperation, options: DocumentOptions = {}, id?: string, delegId?: string) {
-        return assignToDelegate(SetInstanceOptions(histoProto, options, [histoOp, HistogramField], id).MakeDelegate(delegId), options);
+    export function HistogramDocument(histoOp: HistogramOperation, options: DocumentOptions = {}) {
+        return CreateInstance(histoProto, new HistogramField(histoOp), options);
     }
     export function TextDocument(options: DocumentOptions = {}) {
-        return assignToDelegate(SetInstanceOptions(textProto, options, ["", TextField]).MakeDelegate(), options);
+        return CreateInstance(textProto, "", options);
+    }
+    export function IconDocument(icon: string, options: DocumentOptions = {}) {
+        return CreateInstance(iconProto, new IconField(icon), options);
     }
     export function PdfDocument(url: string, options: DocumentOptions = {}) {
-        return assignToDelegate(SetInstanceOptions(pdfProto, options, [new URL(url), PDFField]).MakeDelegate(), options);
+        return CreateInstance(pdfProto, new PdfField(new URL(url)), options);
     }
     export async function DBDocument(url: string, options: DocumentOptions = {}) {
         let schemaName = options.title ? options.title : "-no schema-";
         let ctlog = await Gateway.Instance.GetSchema(url, schemaName);
         if (ctlog && ctlog.schemas) {
             let schema = ctlog.schemas[0];
-            let schemaDoc = Documents.TreeDocument([], { ...options, nativeWidth: undefined, nativeHeight: undefined, width: 150, height: 100, title: schema.displayName! });
-            let schemaDocuments = schemaDoc.GetList(KeyStore.Data, [] as Document[]);
+            let schemaDoc = Docs.TreeDocument([], { ...options, nativeWidth: undefined, nativeHeight: undefined, width: 150, height: 100, title: schema.displayName! });
+            let schemaDocuments = Cast(schemaDoc.data, listSpec(Doc));
+            if (!schemaDocuments) {
+                return;
+            }
+            const docs = schemaDocuments;
             CurrentUserUtils.GetAllNorthstarColumnAttributes(schema).map(attr => {
-                Server.GetField(attr.displayName! + ".alias", action((field: Opt<Field>) => {
-                    if (field instanceof Document) {
-                        schemaDocuments.push(field);
+                DocServer.GetRefField(attr.displayName! + ".alias").then(action((field: Opt<Field>) => {
+                    if (field instanceof Doc) {
+                        docs.push(field);
                     } else {
                         var atmod = new ColumnAttributeModel(attr);
                         let histoOp = new HistogramOperation(schema.displayName!,
                             new AttributeTransformationModel(atmod, AggregateFunction.None),
                             new AttributeTransformationModel(atmod, AggregateFunction.Count),
                             new AttributeTransformationModel(atmod, AggregateFunction.Count));
-                        schemaDocuments.push(Documents.HistogramDocument(histoOp, { width: 200, height: 200, title: attr.displayName! }, undefined, attr.displayName! + ".alias"));
+                        docs.push(Docs.HistogramDocument(histoOp, { width: 200, height: 200, title: attr.displayName! }));
                     }
                 }));
             });
             return schemaDoc;
         }
-        return Documents.TreeDocument([], { width: 50, height: 100, title: schemaName });
+        return Docs.TreeDocument([], { width: 50, height: 100, title: schemaName });
     }
     export function WebDocument(url: string, options: DocumentOptions = {}) {
-        return assignToDelegate(SetInstanceOptions(webProto, options, [new URL(url), WebField]).MakeDelegate(), options);
+        return CreateInstance(webProto, new WebField(new URL(url)), options);
     }
     export function HtmlDocument(html: string, options: DocumentOptions = {}) {
-        return assignToDelegate(SetInstanceOptions(webProto, options, [html, HtmlField]).MakeDelegate(), options);
+        return CreateInstance(webProto, new HtmlField(html), options);
     }
-    export function KVPDocument(document: Document, options: DocumentOptions = {}, id?: string) {
-        return assignToDelegate(SetInstanceOptions(kvpProto, options, document, id), options);
+    export function KVPDocument(document: Doc, options: DocumentOptions = {}) {
+        return CreateInstance(kvpProto, document, options);
     }
-    export function FreeformDocument(documents: Array<Document>, options: DocumentOptions, id?: string, makePrototype: boolean = true) {
+    export function FreeformDocument(documents: Array<Doc>, options: DocumentOptions, makePrototype: boolean = true) {
         if (!makePrototype) {
-            return SetInstanceOptions(collProto, { ...options, viewType: CollectionViewType.Freeform }, [documents, ListField], id);
+            return SetInstanceOptions(collProto, { ...options, viewType: CollectionViewType.Freeform }, new List(documents));
         }
-        return assignToDelegate(SetInstanceOptions(collProto, { ...options, viewType: CollectionViewType.Freeform }, [documents, ListField], id).MakeDelegate(), options);
+        return CreateInstance(collProto, new List(documents), { schemaColumns: new List(["title"]), ...options, viewType: CollectionViewType.Freeform });
     }
-    export function SchemaDocument(documents: Array<Document>, options: DocumentOptions, id?: string) {
-        return assignToDelegate(SetInstanceOptions(collProto, { ...options, viewType: CollectionViewType.Schema }, [documents, ListField], id), options);
+    export function SchemaDocument(documents: Array<Doc>, options: DocumentOptions) {
+        return CreateInstance(collProto, new List(documents), { schemaColumns: new List(["title"]), ...options, viewType: CollectionViewType.Schema });
     }
-    export function TreeDocument(documents: Array<Document>, options: DocumentOptions, id?: string) {
-        return assignToDelegate(SetInstanceOptions(collProto, { ...options, viewType: CollectionViewType.Tree }, [documents, ListField], id), options);
+    export function TreeDocument(documents: Array<Doc>, options: DocumentOptions) {
+        return CreateInstance(collProto, new List(documents), { schemaColumns: new List(["title"]), ...options, viewType: CollectionViewType.Tree });
     }
-    export function DockDocument(config: string, options: DocumentOptions, id?: string) {
-        return assignToDelegate(SetInstanceOptions(collProto, { ...options, viewType: CollectionViewType.Docking }, [config, TextField], id), options);
+    export function DockDocument(documents: Array<Doc>, config: string, options: DocumentOptions) {
+        return CreateInstance(collProto, new List(documents), { ...options, viewType: CollectionViewType.Docking, dockingConfig: config });
     }
 
-    export function CaptionDocument(doc: Document) {
-        const captionDoc = doc.CreateAlias();
-        captionDoc.SetText(KeyStore.OverlayLayout, FixedCaption());
-        captionDoc.SetNumber(KeyStore.Width, doc.GetNumber(KeyStore.Width, 0));
-        captionDoc.SetNumber(KeyStore.Height, doc.GetNumber(KeyStore.Height, 0));
+    export function CaptionDocument(doc: Doc) {
+        const captionDoc = Doc.MakeAlias(doc);
+        captionDoc.overlayLayout = FixedCaption();
+        captionDoc.width = Cast(doc.width, "number", 0);
+        captionDoc.height = Cast(doc.height, "number", 0);
         return captionDoc;
     }
 
@@ -271,14 +275,14 @@ export namespace Documents {
             + ImageBox.LayoutString() +
             `</div>
             <div style="position:relative; height:15%; text-align:center; ">`
-            + FormattedTextBox.LayoutString("CaptionKey") +
+            + FormattedTextBox.LayoutString("caption") +
             `</div> 
         </div>`;
     }
-    export function FixedCaption(fieldName: string = "Caption") {
+    export function FixedCaption(fieldName: string = "caption") {
         return `<div style="position:absolute; height:30px; bottom:0; width:100%">
             <div style="position:absolute; width:100%; height:100%; text-align:center;bottom:0;">`
-            + FormattedTextBox.LayoutString(fieldName + "Key") +
+            + FormattedTextBox.LayoutString(fieldName) +
             `</div> 
         </div>`;
     }
@@ -290,7 +294,7 @@ export namespace Documents {
         {layout}
     </div>
     <div style="height:(100% + 25px); width:100%; position:absolute">
-        <FormattedTextBox doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={"CaptionKey"} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} isTopMost={isTopMost}/>
+        <FormattedTextBox doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={"caption"} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} isTopMost={isTopMost}/>
     </div>
 </div>       
         `);
@@ -302,7 +306,7 @@ export namespace Documents {
             {layout}
         </div>
         <div style="height:25px; width:100%; position:absolute">
-            <FormattedTextBox doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={"CaptionKey"} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} isTopMost={isTopMost}/>
+            <FormattedTextBox doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={"caption"} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} isTopMost={isTopMost}/>
         </div>
     </div>       
             `);
@@ -325,7 +329,7 @@ export namespace Documents {
             {layout}
         </div>
         <div style="height:15%; width:100%; position:absolute">
-            <FormattedTextBox doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={"CaptionKey"} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} isTopMost={isTopMost}/>
+            <FormattedTextBox doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={"caption"} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} isTopMost={isTopMost}/>
         </div>
     </div>       
             `);
diff --git a/src/client/goldenLayout.js b/src/client/goldenLayout.js
new file mode 100644
index 000000000..56a71f1ac
--- /dev/null
+++ b/src/client/goldenLayout.js
@@ -0,0 +1,5359 @@
+(function ($) {
+    var lm = { "config": {}, "container": {}, "controls": {}, "errors": {}, "items": {}, "utils": {} };
+    lm.utils.F = function () {
+    };
+
+    lm.utils.extend = function (subClass, superClass) {
+        subClass.prototype = lm.utils.createObject(superClass.prototype);
+        subClass.prototype.contructor = subClass;
+    };
+
+    lm.utils.createObject = function (prototype) {
+        if (typeof Object.create === 'function') {
+            return Object.create(prototype);
+        } else {
+            lm.utils.F.prototype = prototype;
+            return new lm.utils.F();
+        }
+    };
+
+    lm.utils.objectKeys = function (object) {
+        var keys, key;
+
+        if (typeof Object.keys === 'function') {
+            return Object.keys(object);
+        } else {
+            keys = [];
+            for (key in object) {
+                keys.push(key);
+            }
+            return keys;
+        }
+    };
+
+    lm.utils.getHashValue = function (key) {
+        var matches = location.hash.match(new RegExp(key + '=([^&]*)'));
+        return matches ? matches[1] : null;
+    };
+
+    lm.utils.getQueryStringParam = function (param) {
+        if (window.location.hash) {
+            return lm.utils.getHashValue(param);
+        } else if (!window.location.search) {
+            return null;
+        }
+
+        var keyValuePairs = window.location.search.substr(1).split('&'),
+            params = {},
+            pair,
+            i;
+
+        for (i = 0; i < keyValuePairs.length; i++) {
+            pair = keyValuePairs[i].split('=');
+            params[pair[0]] = pair[1];
+        }
+
+        return params[param] || null;
+    };
+
+    lm.utils.copy = function (target, source) {
+        for (var key in source) {
+            target[key] = source[key];
+        }
+        return target;
+    };
+
+	/**
+	 * This is based on Paul Irish's shim, but looks quite odd in comparison. Why?
+	 * Because
+	 * a) it shouldn't affect the global requestAnimationFrame function
+	 * b) it shouldn't pass on the time that has passed
+	 *
+	 * @param   {Function} fn
+	 *
+	 * @returns {void}
+	 */
+    lm.utils.animFrame = function (fn) {
+        return (window.requestAnimationFrame ||
+            window.webkitRequestAnimationFrame ||
+            window.mozRequestAnimationFrame ||
+            function (callback) {
+                window.setTimeout(callback, 1000 / 60);
+            })(function () {
+                fn();
+            });
+    };
+
+    lm.utils.indexOf = function (needle, haystack) {
+        if (!(haystack instanceof Array)) {
+            throw new Error('Haystack is not an Array');
+        }
+
+        if (haystack.indexOf) {
+            return haystack.indexOf(needle);
+        } else {
+            for (var i = 0; i < haystack.length; i++) {
+                if (haystack[i] === needle) {
+                    return i;
+                }
+            }
+            return -1;
+        }
+    };
+
+    if (typeof /./ != 'function' && typeof Int8Array != 'object') {
+        lm.utils.isFunction = function (obj) {
+            return typeof obj == 'function' || false;
+        };
+    } else {
+        lm.utils.isFunction = function (obj) {
+            return toString.call(obj) === '[object Function]';
+        };
+    }
+
+    lm.utils.fnBind = function (fn, context, boundArgs) {
+
+        if (Function.prototype.bind !== undefined) {
+            return Function.prototype.bind.apply(fn, [context].concat(boundArgs || []));
+        }
+
+        var bound = function () {
+
+            // Join the already applied arguments to the now called ones (after converting to an array again).
+            var args = (boundArgs || []).concat(Array.prototype.slice.call(arguments, 0));
+
+            // If not being called as a constructor
+            if (!(this instanceof bound)) {
+                // return the result of the function called bound to target and partially applied.
+                return fn.apply(context, args);
+            }
+            // If being called as a constructor, apply the function bound to self.
+            fn.apply(this, args);
+        };
+        // Attach the prototype of the function to our newly created function.
+        bound.prototype = fn.prototype;
+        return bound;
+    };
+
+    lm.utils.removeFromArray = function (item, array) {
+        var index = lm.utils.indexOf(item, array);
+
+        if (index === -1) {
+            throw new Error('Can\'t remove item from array. Item is not in the array');
+        }
+
+        array.splice(index, 1);
+    };
+
+    lm.utils.now = function () {
+        if (typeof Date.now === 'function') {
+            return Date.now();
+        } else {
+            return (new Date()).getTime();
+        }
+    };
+
+    lm.utils.getUniqueId = function () {
+        return (Math.random() * 1000000000000000)
+            .toString(36)
+            .replace('.', '');
+    };
+
+	/**
+	 * A basic XSS filter. It is ultimately up to the
+	 * implementing developer to make sure their particular
+	 * applications and usecases are save from cross site scripting attacks
+	 *
+	 * @param   {String} input
+	 * @param    {Boolean} keepTags
+	 *
+	 * @returns {String} filtered input
+	 */
+    lm.utils.filterXss = function (input, keepTags) {
+
+        var output = input
+            .replace(/javascript/gi, 'j&#97;vascript')
+            .replace(/expression/gi, 'expr&#101;ssion')
+            .replace(/onload/gi, 'onlo&#97;d')
+            .replace(/script/gi, '&#115;cript')
+            .replace(/onerror/gi, 'on&#101;rror');
+
+        if (keepTags === true) {
+            return output;
+        } else {
+            return output
+                .replace(/>/g, '&gt;')
+                .replace(/</g, '&lt;');
+        }
+    };
+
+	/**
+	 * Removes html tags from a string
+	 *
+	 * @param   {String} input
+	 *
+	 * @returns {String} input without tags
+	 */
+    lm.utils.stripTags = function (input) {
+        return $.trim(input.replace(/(<([^>]+)>)/ig, ''));
+    };
+	/**
+	 * A generic and very fast EventEmitter
+	 * implementation. On top of emitting the
+	 * actual event it emits an
+	 *
+	 * lm.utils.EventEmitter.ALL_EVENT
+	 *
+	 * event for every event triggered. This allows
+	 * to hook into it and proxy events forwards
+	 *
+	 * @constructor
+	 */
+    lm.utils.EventEmitter = function () {
+        this._mSubscriptions = {};
+        this._mSubscriptions[lm.utils.EventEmitter.ALL_EVENT] = [];
+
+		/**
+		 * Listen for events
+		 *
+		 * @param   {String} sEvent    The name of the event to listen to
+		 * @param   {Function} fCallback The callback to execute when the event occurs
+		 * @param   {[Object]} oContext The value of the this pointer within the callback function
+		 *
+		 * @returns {void}
+		 */
+        this.on = function (sEvent, fCallback, oContext) {
+            if (!lm.utils.isFunction(fCallback)) {
+                throw new Error('Tried to listen to event ' + sEvent + ' with non-function callback ' + fCallback);
+            }
+
+            if (!this._mSubscriptions[sEvent]) {
+                this._mSubscriptions[sEvent] = [];
+            }
+
+            this._mSubscriptions[sEvent].push({ fn: fCallback, ctx: oContext });
+        };
+
+		/**
+		 * Emit an event and notify listeners
+		 *
+		 * @param   {String} sEvent The name of the event
+		 * @param    {Mixed}  various additional arguments that will be passed to the listener
+		 *
+		 * @returns {void}
+		 */
+        this.emit = function (sEvent) {
+            var i, ctx, args;
+
+            args = Array.prototype.slice.call(arguments, 1);
+
+            var subs = this._mSubscriptions[sEvent];
+
+            if (subs) {
+                subs = subs.slice();
+                for (i = 0; i < subs.length; i++) {
+                    ctx = subs[i].ctx || {};
+                    subs[i].fn.apply(ctx, args);
+                }
+            }
+
+            args.unshift(sEvent);
+
+            var allEventSubs = this._mSubscriptions[lm.utils.EventEmitter.ALL_EVENT].slice()
+
+            for (i = 0; i < allEventSubs.length; i++) {
+                ctx = allEventSubs[i].ctx || {};
+                allEventSubs[i].fn.apply(ctx, args);
+            }
+        };
+
+		/**
+		 * Removes a listener for an event, or all listeners if no callback and context is provided.
+		 *
+		 * @param   {String} sEvent    The name of the event
+		 * @param   {Function} fCallback The previously registered callback method (optional)
+		 * @param   {Object} oContext  The previously registered context (optional)
+		 *
+		 * @returns {void}
+		 */
+        this.unbind = function (sEvent, fCallback, oContext) {
+            if (!this._mSubscriptions[sEvent]) {
+                throw new Error('No subscribtions to unsubscribe for event ' + sEvent);
+            }
+
+            var i, bUnbound = false;
+
+            for (i = 0; i < this._mSubscriptions[sEvent].length; i++) {
+                if
+                    (
+                    (!fCallback || this._mSubscriptions[sEvent][i].fn === fCallback) &&
+                    (!oContext || oContext === this._mSubscriptions[sEvent][i].ctx)
+                ) {
+                    this._mSubscriptions[sEvent].splice(i, 1);
+                    bUnbound = true;
+                }
+            }
+
+            if (bUnbound === false) {
+                throw new Error('Nothing to unbind for ' + sEvent);
+            }
+        };
+
+		/**
+		 * Alias for unbind
+		 */
+        this.off = this.unbind;
+
+		/**
+		 * Alias for emit
+		 */
+        this.trigger = this.emit;
+    };
+
+	/**
+	 * The name of the event that's triggered for every other event
+	 *
+	 * usage
+	 *
+	 * myEmitter.on( lm.utils.EventEmitter.ALL_EVENT, function( eventName, argsArray ){
+	 * 	//do stuff
+	 * });
+	 *
+	 * @type {String}
+	 */
+    lm.utils.EventEmitter.ALL_EVENT = '__all';
+    lm.utils.DragListener = function (eElement, nButtonCode) {
+        lm.utils.EventEmitter.call(this);
+
+        this._eElement = $(eElement);
+        this._oDocument = $(document);
+        this._eBody = $(document.body);
+        this._nButtonCode = nButtonCode || 0;
+
+		/**
+		 * The delay after which to start the drag in milliseconds
+		 */
+        this._nDelay = 200;
+
+		/**
+		 * The distance the mouse needs to be moved to qualify as a drag
+		 */
+        this._nDistance = 10;//TODO - works better with delay only
+
+        this._nX = 0;
+        this._nY = 0;
+
+        this._nOriginalX = 0;
+        this._nOriginalY = 0;
+
+        this._bDragging = false;
+
+        this._fMove = lm.utils.fnBind(this.onMouseMove, this);
+        this._fUp = lm.utils.fnBind(this.onMouseUp, this);
+        this._fDown = lm.utils.fnBind(this.onMouseDown, this);
+
+
+        this._eElement.on('mousedown touchstart', this._fDown);
+    };
+
+    lm.utils.DragListener.timeout = null;
+
+    lm.utils.copy(lm.utils.DragListener.prototype, {
+        destroy: function () {
+            this._eElement.unbind('mousedown touchstart', this._fDown);
+            this._oDocument.unbind('mouseup touchend', this._fUp);
+            this._eElement = null;
+            this._oDocument = null;
+            this._eBody = null;
+        },
+
+        onMouseDown: function (oEvent) {
+            oEvent.preventDefault();
+
+            if (oEvent.button == 0 || oEvent.type === "touchstart") {
+                var coordinates = this._getCoordinates(oEvent);
+
+                this._nOriginalX = coordinates.x;
+                this._nOriginalY = coordinates.y;
+
+                this._oDocument.on('mousemove touchmove', this._fMove);
+                this._oDocument.one('mouseup touchend', this._fUp);
+
+                this._timeout = setTimeout(lm.utils.fnBind(this._startDrag, this), this._nDelay);
+            }
+        },
+
+        onMouseMove: function (oEvent) {
+            if (this._timeout != null) {
+                oEvent.preventDefault();
+
+                var coordinates = this._getCoordinates(oEvent);
+
+                this._nX = coordinates.x - this._nOriginalX;
+                this._nY = coordinates.y - this._nOriginalY;
+
+                if (this._bDragging === false) {
+                    if (
+                        Math.abs(this._nX) > this._nDistance ||
+                        Math.abs(this._nY) > this._nDistance
+                    ) {
+                        clearTimeout(this._timeout);
+                        this._startDrag();
+                    }
+                }
+
+                if (this._bDragging) {
+                    this.emit('drag', this._nX, this._nY, oEvent);
+                }
+            }
+        },
+
+        onMouseUp: function (oEvent) {
+            if (this._timeout != null) {
+                clearTimeout(this._timeout);
+                this._eBody.removeClass('lm_dragging');
+                this._eElement.removeClass('lm_dragging');
+                this._oDocument.find('iframe').css('pointer-events', '');
+                this._oDocument.unbind('mousemove touchmove', this._fMove);
+                this._oDocument.unbind('mouseup touchend', this._fUp);
+
+                if (this._bDragging === true) {
+                    this._bDragging = false;
+                    this.emit('dragStop', oEvent, this._nOriginalX + this._nX);
+                }
+            }
+        },
+
+        _startDrag: function () {
+            this._bDragging = true;
+            this._eBody.addClass('lm_dragging');
+            this._eElement.addClass('lm_dragging');
+            this._oDocument.find('iframe').css('pointer-events', 'none');
+            this.emit('dragStart', this._nOriginalX, this._nOriginalY);
+        },
+
+        _getCoordinates: function (event) {
+            event = event.originalEvent && event.originalEvent.touches ? event.originalEvent.touches[0] : event;
+            return {
+                x: event.pageX,
+                y: event.pageY
+            };
+        }
+    });
+	/**
+	 * The main class that will be exposed as GoldenLayout.
+	 *
+	 * @public
+	 * @constructor
+	 * @param {GoldenLayout config} config
+	 * @param {[DOM element container]} container Can be a jQuery selector string or a Dom element. Defaults to body
+	 *
+	 * @returns {VOID}
+	 */
+    lm.LayoutManager = function (config, container) {
+
+        if (!$ || typeof $.noConflict !== 'function') {
+            var errorMsg = 'jQuery is missing as dependency for GoldenLayout. ';
+            errorMsg += 'Please either expose $ on GoldenLayout\'s scope (e.g. window) or add "jquery" to ';
+            errorMsg += 'your paths when using RequireJS/AMD';
+            throw new Error(errorMsg);
+        }
+        lm.utils.EventEmitter.call(this);
+
+        this.isInitialised = false;
+        this._isFullPage = false;
+        this._resizeTimeoutId = null;
+        this._components = { 'lm-react-component': lm.utils.ReactComponentHandler };
+        this._itemAreas = [];
+        this._resizeFunction = lm.utils.fnBind(this._onResize, this);
+        this._unloadFunction = lm.utils.fnBind(this._onUnload, this);
+        this._maximisedItem = null;
+        this._maximisePlaceholder = $('<div class="lm_maximise_place"></div>');
+        this._creationTimeoutPassed = false;
+        this._subWindowsCreated = false;
+        this._dragSources = [];
+        this._updatingColumnsResponsive = false;
+        this._firstLoad = true;
+
+        this.width = null;
+        this.height = null;
+        this.root = null;
+        this.openPopouts = [];
+        this.selectedItem = null;
+        this.isSubWindow = false;
+        this.eventHub = new lm.utils.EventHub(this);
+        this.config = this._createConfig(config);
+        this.container = container;
+        this.dropTargetIndicator = null;
+        this.transitionIndicator = null;
+        this.tabDropPlaceholder = $('<div class="lm_drop_tab_placeholder"></div>');
+
+        if (this.isSubWindow === true) {
+            $('body').css('visibility', 'hidden');
+        }
+
+        this._typeToItem = {
+            'column': lm.utils.fnBind(lm.items.RowOrColumn, this, [true]),
+            'row': lm.utils.fnBind(lm.items.RowOrColumn, this, [false]),
+            'stack': lm.items.Stack,
+            'component': lm.items.Component
+        };
+    };
+
+	/**
+	 * Hook that allows to access private classes
+	 */
+    lm.LayoutManager.__lm = lm;
+
+	/**
+	 * Takes a GoldenLayout configuration object and
+	 * replaces its keys and values recursively with
+	 * one letter codes
+	 *
+	 * @static
+	 * @public
+	 * @param   {Object} config A GoldenLayout config object
+	 *
+	 * @returns {Object} minified config
+	 */
+    lm.LayoutManager.minifyConfig = function (config) {
+        return (new lm.utils.ConfigMinifier()).minifyConfig(config);
+    };
+
+	/**
+	 * Takes a configuration Object that was previously minified
+	 * using minifyConfig and returns its original version
+	 *
+	 * @static
+	 * @public
+	 * @param   {Object} minifiedConfig
+	 *
+	 * @returns {Object} the original configuration
+	 */
+    lm.LayoutManager.unminifyConfig = function (config) {
+        return (new lm.utils.ConfigMinifier()).unminifyConfig(config);
+    };
+
+    lm.utils.copy(lm.LayoutManager.prototype, {
+
+		/**
+		 * Register a component with the layout manager. If a configuration node
+		 * of type component is reached it will look up componentName and create the
+		 * associated component
+		 *
+		 *  {
+		 *		type: "component",
+		 *		componentName: "EquityNewsFeed",
+		 *		componentState: { "feedTopic": "us-bluechips" }
+		 *  }
+		 *
+		 * @public
+		 * @param   {String} name
+		 * @param   {Function} constructor
+		 *
+		 * @returns {void}
+		 */
+        registerComponent: function (name, constructor) {
+            if (typeof constructor !== 'function') {
+                throw new Error('Please register a constructor function');
+            }
+
+            if (this._components[name] !== undefined) {
+                throw new Error('Component ' + name + ' is already registered');
+            }
+
+            this._components[name] = constructor;
+        },
+
+		/**
+		 * Creates a layout configuration object based on the the current state
+		 *
+		 * @public
+		 * @returns {Object} GoldenLayout configuration
+		 */
+        toConfig: function (root) {
+            var config, next, i;
+
+            if (this.isInitialised === false) {
+                throw new Error('Can\'t create config, layout not yet initialised');
+            }
+
+            if (root && !(root instanceof lm.items.AbstractContentItem)) {
+                throw new Error('Root must be a ContentItem');
+            }
+
+			/*
+			 * settings & labels
+			 */
+            config = {
+                settings: lm.utils.copy({}, this.config.settings),
+                dimensions: lm.utils.copy({}, this.config.dimensions),
+                labels: lm.utils.copy({}, this.config.labels)
+            };
+
+			/*
+			 * Content
+			 */
+            config.content = [];
+            next = function (configNode, item) {
+                var key, i;
+
+                for (key in item.config) {
+                    if (key !== 'content') {
+                        configNode[key] = item.config[key];
+                    }
+                }
+
+                if (item.contentItems.length) {
+                    configNode.content = [];
+
+                    for (i = 0; i < item.contentItems.length; i++) {
+                        configNode.content[i] = {};
+                        next(configNode.content[i], item.contentItems[i]);
+                    }
+                }
+            };
+
+            if (root) {
+                next(config, { contentItems: [root] });
+            } else {
+                next(config, this.root);
+            }
+
+			/*
+			 * Retrieve config for subwindows
+			 */
+            this._$reconcilePopoutWindows();
+            config.openPopouts = [];
+            for (i = 0; i < this.openPopouts.length; i++) {
+                config.openPopouts.push(this.openPopouts[i].toConfig());
+            }
+
+			/*
+			 * Add maximised item
+			 */
+            config.maximisedItemId = this._maximisedItem ? '__glMaximised' : null;
+            return config;
+        },
+
+		/**
+		 * Returns a previously registered component
+		 *
+		 * @public
+		 * @param   {String} name The name used
+		 *
+		 * @returns {Function}
+		 */
+        getComponent: function (name) {
+            if (this._components[name] === undefined) {
+                throw new lm.errors.ConfigurationError('Unknown component "' + name + '"');
+            }
+
+            return this._components[name];
+        },
+
+		/**
+		 * Creates the actual layout. Must be called after all initial components
+		 * are registered. Recurses through the configuration and sets up
+		 * the item tree.
+		 *
+		 * If called before the document is ready it adds itself as a listener
+		 * to the document.ready event
+		 *
+		 * @public
+		 *
+		 * @returns {void}
+		 */
+        init: function () {
+
+			/**
+			 * Create the popout windows straight away. If popouts are blocked
+			 * an error is thrown on the same 'thread' rather than a timeout and can
+			 * be caught. This also prevents any further initilisation from taking place.
+			 */
+            if (this._subWindowsCreated === false) {
+                this._createSubWindows();
+                this._subWindowsCreated = true;
+            }
+
+
+			/**
+			 * If the document isn't ready yet, wait for it.
+			 */
+            if (document.readyState === 'loading' || document.body === null) {
+                $(document).ready(lm.utils.fnBind(this.init, this));
+                return;
+            }
+
+			/**
+			 * If this is a subwindow, wait a few milliseconds for the original
+			 * page's js calls to be executed, then replace the bodies content
+			 * with GoldenLayout
+			 */
+            if (this.isSubWindow === true && this._creationTimeoutPassed === false) {
+                setTimeout(lm.utils.fnBind(this.init, this), 7);
+                this._creationTimeoutPassed = true;
+                return;
+            }
+
+            if (this.isSubWindow === true) {
+                this._adjustToWindowMode();
+            }
+
+            this._setContainer();
+            this.dropTargetIndicator = new lm.controls.DropTargetIndicator(this.container);
+            this.transitionIndicator = new lm.controls.TransitionIndicator();
+            this.updateSize();
+            this._create(this.config);
+            this._bindEvents();
+            this.isInitialised = true;
+            this._adjustColumnsResponsive();
+            this.emit('initialised');
+        },
+
+		/**
+		 * Updates the layout managers size
+		 *
+		 * @public
+		 * @param   {[int]} width  height in pixels
+		 * @param   {[int]} height width in pixels
+		 *
+		 * @returns {void}
+		 */
+        updateSize: function (width, height) {
+            if (arguments.length === 2) {
+                this.width = width;
+                this.height = height;
+            } else {
+                this.width = this.container.width();
+                this.height = this.container.height();
+            }
+
+            if (this.isInitialised === true) {
+                this.root.callDownwards('setSize', [this.width, this.height]);
+
+                if (this._maximisedItem) {
+                    this._maximisedItem.element.width(this.container.width());
+                    this._maximisedItem.element.height(this.container.height());
+                    this._maximisedItem.callDownwards('setSize');
+                }
+
+                this._adjustColumnsResponsive();
+            }
+        },
+
+		/**
+		 * Destroys the LayoutManager instance itself as well as every ContentItem
+		 * within it. After this is called nothing should be left of the LayoutManager.
+		 *
+		 * @public
+		 * @returns {void}
+		 */
+        destroy: function () {
+            if (this.isInitialised === false) {
+                return;
+            }
+            this._onUnload();
+            $(window).off('resize', this._resizeFunction);
+            $(window).off('unload beforeunload', this._unloadFunction);
+            this.root.callDownwards('_$destroy', [], true);
+            this.root.contentItems = [];
+            this.tabDropPlaceholder.remove();
+            this.dropTargetIndicator.destroy();
+            this.transitionIndicator.destroy();
+            this.eventHub.destroy();
+
+            this._dragSources.forEach(function (dragSource) {
+                dragSource._dragListener.destroy();
+                dragSource._element = null;
+                dragSource._itemConfig = null;
+                dragSource._dragListener = null;
+            });
+            this._dragSources = [];
+        },
+
+		/**
+		 * Recursively creates new item tree structures based on a provided
+		 * ItemConfiguration object
+		 *
+		 * @public
+		 * @param   {Object} config ItemConfig
+		 * @param   {[ContentItem]} parent The item the newly created item should be a child of
+		 *
+		 * @returns {lm.items.ContentItem}
+		 */
+        createContentItem: function (config, parent) {
+            var typeErrorMsg, contentItem;
+
+            if (typeof config.type !== 'string') {
+                throw new lm.errors.ConfigurationError('Missing parameter \'type\'', config);
+            }
+
+            if (config.type === 'react-component') {
+                config.type = 'component';
+                config.componentName = 'lm-react-component';
+            }
+
+            if (!this._typeToItem[config.type]) {
+                typeErrorMsg = 'Unknown type \'' + config.type + '\'. ' +
+                    'Valid types are ' + lm.utils.objectKeys(this._typeToItem).join(',');
+
+                throw new lm.errors.ConfigurationError(typeErrorMsg);
+            }
+
+
+			/**
+			 * We add an additional stack around every component that's not within a stack anyways.
+			 */
+            if (
+                // If this is a component
+                config.type === 'component' &&
+
+                // and it's not already within a stack
+                !(parent instanceof lm.items.Stack) &&
+
+                // and we have a parent
+                !!parent &&
+
+                // and it's not the topmost item in a new window
+                !(this.isSubWindow === true && parent instanceof lm.items.Root)
+            ) {
+                config = {
+                    type: 'stack',
+                    width: config.width,
+                    height: config.height,
+                    content: [config]
+                };
+            }
+
+            contentItem = new this._typeToItem[config.type](this, config, parent);
+            return contentItem;
+        },
+
+		/**
+		 * Creates a popout window with the specified content and dimensions
+		 *
+		 * @param   {Object|lm.itemsAbstractContentItem} configOrContentItem
+		 * @param   {[Object]} dimensions A map with width, height, left and top
+		 * @param    {[String]} parentId the id of the element this item will be appended to
+		 *                             when popIn is called
+		 * @param    {[Number]} indexInParent The position of this item within its parent element
+	
+		 * @returns {lm.controls.BrowserPopout}
+		 */
+        createPopout: function (configOrContentItem, dimensions, parentId, indexInParent) {
+            var config = configOrContentItem,
+                isItem = configOrContentItem instanceof lm.items.AbstractContentItem,
+                self = this,
+                windowLeft,
+                windowTop,
+                offset,
+                parent,
+                child,
+                browserPopout;
+
+            parentId = parentId || null;
+
+            if (isItem) {
+                config = this.toConfig(configOrContentItem).content;
+                parentId = lm.utils.getUniqueId();
+
+				/**
+				 * If the item is the only component within a stack or for some
+				 * other reason the only child of its parent the parent will be destroyed
+				 * when the child is removed.
+				 *
+				 * In order to support this we move up the tree until we find something
+				 * that will remain after the item is being popped out
+				 */
+                parent = configOrContentItem.parent;
+                child = configOrContentItem;
+                while (parent.contentItems.length === 1 && !parent.isRoot) {
+                    parent = parent.parent;
+                    child = child.parent;
+                }
+
+                parent.addId(parentId);
+                if (isNaN(indexInParent)) {
+                    indexInParent = lm.utils.indexOf(child, parent.contentItems);
+                }
+            } else {
+                if (!(config instanceof Array)) {
+                    config = [config];
+                }
+            }
+
+
+            if (!dimensions && isItem) {
+                windowLeft = window.screenX || window.screenLeft;
+                windowTop = window.screenY || window.screenTop;
+                offset = configOrContentItem.element.offset();
+
+                dimensions = {
+                    left: windowLeft + offset.left,
+                    top: windowTop + offset.top,
+                    width: configOrContentItem.element.width(),
+                    height: configOrContentItem.element.height()
+                };
+            }
+
+            if (!dimensions && !isItem) {
+                dimensions = {
+                    left: window.screenX || window.screenLeft + 20,
+                    top: window.screenY || window.screenTop + 20,
+                    width: 500,
+                    height: 309
+                };
+            }
+
+            if (isItem) {
+                configOrContentItem.remove();
+            }
+
+            browserPopout = new lm.controls.BrowserPopout(config, dimensions, parentId, indexInParent, this);
+
+            browserPopout.on('initialised', function () {
+                self.emit('windowOpened', browserPopout);
+            });
+
+            browserPopout.on('closed', function () {
+                self._$reconcilePopoutWindows();
+            });
+
+            this.openPopouts.push(browserPopout);
+
+            return browserPopout;
+        },
+
+		/**
+		 * Attaches DragListener to any given DOM element
+		 * and turns it into a way of creating new ContentItems
+		 * by 'dragging' the DOM element into the layout
+		 *
+		 * @param   {jQuery DOM element} element
+		 * @param   {Object|Function} itemConfig for the new item to be created, or a function which will provide it
+		 *
+		 * @returns {void}
+		 */
+        createDragSource: function (element, itemConfig) {
+            this.config.settings.constrainDragToContainer = false;
+            var dragSource = new lm.controls.DragSource($(element), itemConfig, this);
+            this._dragSources.push(dragSource);
+
+            return dragSource;
+        },
+
+		/**
+		 * Programmatically selects an item. This deselects
+		 * the currently selected item, selects the specified item
+		 * and emits a selectionChanged event
+		 *
+		 * @param   {lm.item.AbstractContentItem} item#
+		 * @param   {[Boolean]} _$silent Wheather to notify the item of its selection
+		 * @event    selectionChanged
+		 *
+		 * @returns {VOID}
+		 */
+        selectItem: function (item, _$silent) {
+
+            if (this.config.settings.selectionEnabled !== true) {
+                throw new Error('Please set selectionEnabled to true to use this feature');
+            }
+
+            if (item === this.selectedItem) {
+                return;
+            }
+
+            if (this.selectedItem !== null) {
+                this.selectedItem.deselect();
+            }
+
+            if (item && _$silent !== true) {
+                item.select();
+            }
+
+            this.selectedItem = item;
+
+            this.emit('selectionChanged', item);
+        },
+
+		/*************************
+		 * PACKAGE PRIVATE
+		 *************************/
+        _$maximiseItem: function (contentItem) {
+            if (this._maximisedItem !== null) {
+                this._$minimiseItem(this._maximisedItem);
+            }
+            this._maximisedItem = contentItem;
+            this._maximisedItem.addId('__glMaximised');
+            contentItem.element.addClass('lm_maximised');
+            contentItem.element.after(this._maximisePlaceholder);
+            this.root.element.prepend(contentItem.element);
+            contentItem.element.width(this.container.width());
+            contentItem.element.height(this.container.height());
+            contentItem.callDownwards('setSize');
+            this._maximisedItem.emit('maximised');
+            this.emit('stateChanged');
+        },
+
+        _$minimiseItem: function (contentItem) {
+            contentItem.element.removeClass('lm_maximised');
+            contentItem.removeId('__glMaximised');
+            this._maximisePlaceholder.after(contentItem.element);
+            this._maximisePlaceholder.remove();
+            contentItem.parent.callDownwards('setSize');
+            this._maximisedItem = null;
+            contentItem.emit('minimised');
+            this.emit('stateChanged');
+        },
+
+		/**
+		 * This method is used to get around sandboxed iframe restrictions.
+		 * If 'allow-top-navigation' is not specified in the iframe's 'sandbox' attribute
+		 * (as is the case with codepens) the parent window is forbidden from calling certain
+		 * methods on the child, such as window.close() or setting document.location.href.
+		 *
+		 * This prevented GoldenLayout popouts from popping in in codepens. The fix is to call
+		 * _$closeWindow on the child window's gl instance which (after a timeout to disconnect
+		 * the invoking method from the close call) closes itself.
+		 *
+		 * @packagePrivate
+		 *
+		 * @returns {void}
+		 */
+        _$closeWindow: function () {
+            window.setTimeout(function () {
+                window.close();
+            }, 1);
+        },
+
+        _$getArea: function (x, y) {
+            var i, area, smallestSurface = Infinity, mathingArea = null;
+
+            for (i = 0; i < this._itemAreas.length; i++) {
+                area = this._itemAreas[i];
+
+                if (
+                    x > area.x1 &&
+                    x < area.x2 &&
+                    y > area.y1 &&
+                    y < area.y2 &&
+                    smallestSurface > area.surface
+                ) {
+                    smallestSurface = area.surface;
+                    mathingArea = area;
+                }
+            }
+
+            return mathingArea;
+        },
+
+        _$createRootItemAreas: function () {
+            var areaSize = 50;
+            var sides = { y2: 0, x2: 0, y1: 'y2', x1: 'x2' };
+            for (var side in sides) {
+                var area = this.root._$getArea();
+                area.side = side;
+                if (sides[side])
+                    area[side] = area[sides[side]] - areaSize;
+                else
+                    area[side] = areaSize;
+                area.surface = (area.x2 - area.x1) * (area.y2 - area.y1);
+                this._itemAreas.push(area);
+            }
+        },
+
+        _$calculateItemAreas: function () {
+            var i, area, allContentItems = this._getAllContentItems();
+            this._itemAreas = [];
+
+			/**
+			 * If the last item is dragged out, highlight the entire container size to
+			 * allow to re-drop it. allContentItems[ 0 ] === this.root at this point
+			 *
+			 * Don't include root into the possible drop areas though otherwise since it
+			 * will used for every gap in the layout, e.g. splitters
+			 */
+            if (allContentItems.length === 1) {
+                this._itemAreas.push(this.root._$getArea());
+                return;
+            }
+            this._$createRootItemAreas();
+
+            for (i = 0; i < allContentItems.length; i++) {
+
+                if (!(allContentItems[i].isStack)) {
+                    continue;
+                }
+
+                area = allContentItems[i]._$getArea();
+
+                if (area === null) {
+                    continue;
+                } else if (area instanceof Array) {
+                    this._itemAreas = this._itemAreas.concat(area);
+                } else {
+                    this._itemAreas.push(area);
+                    var header = {};
+                    lm.utils.copy(header, area);
+                    lm.utils.copy(header, area.contentItem._contentAreaDimensions.header.highlightArea);
+                    header.surface = (header.x2 - header.x1) * (header.y2 - header.y1);
+                    this._itemAreas.push(header);
+                }
+            }
+        },
+
+		/**
+		 * Takes a contentItem or a configuration and optionally a parent
+		 * item and returns an initialised instance of the contentItem.
+		 * If the contentItem is a function, it is first called
+		 *
+		 * @packagePrivate
+		 *
+		 * @param   {lm.items.AbtractContentItem|Object|Function} contentItemOrConfig
+		 * @param   {lm.items.AbtractContentItem} parent Only necessary when passing in config
+		 *
+		 * @returns {lm.items.AbtractContentItem}
+		 */
+        _$normalizeContentItem: function (contentItemOrConfig, parent) {
+            if (!contentItemOrConfig) {
+                throw new Error('No content item defined');
+            }
+
+            if (lm.utils.isFunction(contentItemOrConfig)) {
+                contentItemOrConfig = contentItemOrConfig();
+            }
+
+            if (contentItemOrConfig instanceof lm.items.AbstractContentItem) {
+                return contentItemOrConfig;
+            }
+
+            if ($.isPlainObject(contentItemOrConfig) && contentItemOrConfig.type) {
+                var newContentItem = this.createContentItem(contentItemOrConfig, parent);
+                newContentItem.callDownwards('_$init');
+                return newContentItem;
+            } else {
+                throw new Error('Invalid contentItem');
+            }
+        },
+
+		/**
+		 * Iterates through the array of open popout windows and removes the ones
+		 * that are effectively closed. This is necessary due to the lack of reliably
+		 * listening for window.close / unload events in a cross browser compatible fashion.
+		 *
+		 * @packagePrivate
+		 *
+		 * @returns {void}
+		 */
+        _$reconcilePopoutWindows: function () {
+            var openPopouts = [], i;
+
+            for (i = 0; i < this.openPopouts.length; i++) {
+                if (this.openPopouts[i].getWindow().closed === false) {
+                    openPopouts.push(this.openPopouts[i]);
+                } else {
+                    this.emit('windowClosed', this.openPopouts[i]);
+                }
+            }
+
+            if (this.openPopouts.length !== openPopouts.length) {
+                this.emit('stateChanged');
+                this.openPopouts = openPopouts;
+            }
+
+        },
+
+		/***************************
+		 * PRIVATE
+		 ***************************/
+		/**
+		 * Returns a flattened array of all content items,
+		 * regardles of level or type
+		 *
+		 * @private
+		 *
+		 * @returns {void}
+		 */
+        _getAllContentItems: function () {
+            var allContentItems = [];
+
+            var addChildren = function (contentItem) {
+                allContentItems.push(contentItem);
+
+                if (contentItem.contentItems instanceof Array) {
+                    for (var i = 0; i < contentItem.contentItems.length; i++) {
+                        addChildren(contentItem.contentItems[i]);
+                    }
+                }
+            };
+
+            addChildren(this.root);
+
+            return allContentItems;
+        },
+
+		/**
+		 * Binds to DOM/BOM events on init
+		 *
+		 * @private
+		 *
+		 * @returns {void}
+		 */
+        _bindEvents: function () {
+            if (this._isFullPage) {
+                $(window).resize(this._resizeFunction);
+            }
+            $(window).on('unload beforeunload', this._unloadFunction);
+        },
+
+		/**
+		 * Debounces resize events
+		 *
+		 * @private
+		 *
+		 * @returns {void}
+		 */
+        _onResize: function () {
+            clearTimeout(this._resizeTimeoutId);
+            this._resizeTimeoutId = setTimeout(lm.utils.fnBind(this.updateSize, this), 100);
+        },
+
+		/**
+		 * Extends the default config with the user specific settings and applies
+		 * derivations. Please note that there's a seperate method (AbstractContentItem._extendItemNode)
+		 * that deals with the extension of item configs
+		 *
+		 * @param   {Object} config
+		 * @static
+		 * @returns {Object} config
+		 */
+        _createConfig: function (config) {
+            var windowConfigKey = lm.utils.getQueryStringParam('gl-window');
+
+            if (windowConfigKey) {
+                this.isSubWindow = true;
+                config = localStorage.getItem(windowConfigKey);
+                config = JSON.parse(config);
+                config = (new lm.utils.ConfigMinifier()).unminifyConfig(config);
+                localStorage.removeItem(windowConfigKey);
+            }
+
+            config = $.extend(true, {}, lm.config.defaultConfig, config);
+
+            var nextNode = function (node) {
+                for (var key in node) {
+                    if (key !== 'props' && typeof node[key] === 'object') {
+                        nextNode(node[key]);
+                    }
+                    else if (key === 'type' && node[key] === 'react-component') {
+                        node.type = 'component';
+                        node.componentName = 'lm-react-component';
+                    }
+                }
+            }
+
+            nextNode(config);
+
+            if (config.settings.hasHeaders === false) {
+                config.dimensions.headerHeight = 0;
+            }
+
+            return config;
+        },
+
+		/**
+		 * This is executed when GoldenLayout detects that it is run
+		 * within a previously opened popout window.
+		 *
+		 * @private
+		 *
+		 * @returns {void}
+		 */
+        _adjustToWindowMode: function () {
+            var popInButton = $('<div class="lm_popin" title="' + this.config.labels.popin + '">' +
+                '<div class="lm_icon"></div>' +
+                '<div class="lm_bg"></div>' +
+                '</div>');
+
+            popInButton.click(lm.utils.fnBind(function () {
+                this.emit('popIn');
+            }, this));
+
+            document.title = lm.utils.stripTags(this.config.content[0].title);
+
+            $('head').append($('body link, body style, template, .gl_keep'));
+
+            this.container = $('body')
+                .html('')
+                .css('visibility', 'visible')
+                .append(popInButton);
+
+			/*
+			 * This seems a bit pointless, but actually causes a reflow/re-evaluation getting around
+			 * slickgrid's "Cannot find stylesheet." bug in chrome
+			 */
+            var x = document.body.offsetHeight; // jshint ignore:line
+
+			/*
+			 * Expose this instance on the window object
+			 * to allow the opening window to interact with
+			 * it
+			 */
+            window.__glInstance = this;
+        },
+
+		/**
+		 * Creates Subwindows (if there are any). Throws an error
+		 * if popouts are blocked.
+		 *
+		 * @returns {void}
+		 */
+        _createSubWindows: function () {
+            var i, popout;
+
+            for (i = 0; i < this.config.openPopouts.length; i++) {
+                popout = this.config.openPopouts[i];
+
+                this.createPopout(
+                    popout.content,
+                    popout.dimensions,
+                    popout.parentId,
+                    popout.indexInParent
+                );
+            }
+        },
+
+		/**
+		 * Determines what element the layout will be created in
+		 *
+		 * @private
+		 *
+		 * @returns {void}
+		 */
+        _setContainer: function () {
+            var container = $(this.container || 'body');
+
+            if (container.length === 0) {
+                throw new Error('GoldenLayout container not found');
+            }
+
+            if (container.length > 1) {
+                throw new Error('GoldenLayout more than one container element specified');
+            }
+
+            if (container[0] === document.body) {
+                this._isFullPage = true;
+
+                $('html, body').css({
+                    height: '100%',
+                    margin: 0,
+                    padding: 0,
+                    overflow: 'hidden'
+                });
+            }
+
+            this.container = container;
+        },
+
+		/**
+		 * Kicks of the initial, recursive creation chain
+		 *
+		 * @param   {Object} config GoldenLayout Config
+		 *
+		 * @returns {void}
+		 */
+        _create: function (config) {
+            var errorMsg;
+
+            if (!(config.content instanceof Array)) {
+                if (config.content === undefined) {
+                    errorMsg = 'Missing setting \'content\' on top level of configuration';
+                } else {
+                    errorMsg = 'Configuration parameter \'content\' must be an array';
+                }
+
+                throw new lm.errors.ConfigurationError(errorMsg, config);
+            }
+
+            if (config.content.length > 1) {
+                errorMsg = 'Top level content can\'t contain more then one element.';
+                throw new lm.errors.ConfigurationError(errorMsg, config);
+            }
+
+            this.root = new lm.items.Root(this, { content: config.content }, this.container);
+            this.root.callDownwards('_$init');
+
+            if (config.maximisedItemId === '__glMaximised') {
+                this.root.getItemsById(config.maximisedItemId)[0].toggleMaximise();
+            }
+        },
+
+		/**
+		 * Called when the window is closed or the user navigates away
+		 * from the page
+		 *
+		 * @returns {void}
+		 */
+        _onUnload: function () {
+            if (this.config.settings.closePopoutsOnUnload === true) {
+                for (var i = 0; i < this.openPopouts.length; i++) {
+                    this.openPopouts[i].close();
+                }
+            }
+        },
+
+		/**
+		 * Adjusts the number of columns to be lower to fit the screen and still maintain minItemWidth.
+		 *
+		 * @returns {void}
+		 */
+        _adjustColumnsResponsive: function () {
+
+            // If there is no min width set, or not content items, do nothing.
+            if (!this._useResponsiveLayout() || this._updatingColumnsResponsive || !this.config.dimensions || !this.config.dimensions.minItemWidth || this.root.contentItems.length === 0 || !this.root.contentItems[0].isRow) {
+                this._firstLoad = false;
+                return;
+            }
+
+            this._firstLoad = false;
+
+            // If there is only one column, do nothing.
+            var columnCount = this.root.contentItems[0].contentItems.length;
+            if (columnCount <= 1) {
+                return;
+            }
+
+            // If they all still fit, do nothing.
+            var minItemWidth = this.config.dimensions.minItemWidth;
+            var totalMinWidth = columnCount * minItemWidth;
+            if (totalMinWidth <= this.width) {
+                return;
+            }
+
+            // Prevent updates while it is already happening.
+            this._updatingColumnsResponsive = true;
+
+            // Figure out how many columns to stack, and put them all in the first stack container.
+            var finalColumnCount = Math.max(Math.floor(this.width / minItemWidth), 1);
+            var stackColumnCount = columnCount - finalColumnCount;
+
+            var rootContentItem = this.root.contentItems[0];
+            var firstStackContainer = this._findAllStackContainers()[0];
+            for (var i = 0; i < stackColumnCount; i++) {
+                // Stack from right.
+                var column = rootContentItem.contentItems[rootContentItem.contentItems.length - 1];
+                this._addChildContentItemsToContainer(firstStackContainer, column);
+            }
+
+            this._updatingColumnsResponsive = false;
+        },
+
+		/**
+		 * Determines if responsive layout should be used.
+		 *
+		 * @returns {bool} - True if responsive layout should be used; otherwise false.
+		 */
+        _useResponsiveLayout: function () {
+            return this.config.settings && (this.config.settings.responsiveMode == 'always' || (this.config.settings.responsiveMode == 'onload' && this._firstLoad));
+        },
+
+		/**
+		 * Adds all children of a node to another container recursively.
+		 * @param {object} container - Container to add child content items to.
+		 * @param {object} node - Node to search for content items.
+		 * @returns {void}
+		 */
+        _addChildContentItemsToContainer: function (container, node) {
+            if (node.type === 'stack') {
+                node.contentItems.forEach(function (item) {
+                    container.addChild(item);
+                    node.removeChild(item, true);
+                });
+            }
+            else {
+                node.contentItems.forEach(lm.utils.fnBind(function (item) {
+                    this._addChildContentItemsToContainer(container, item);
+                }, this));
+            }
+        },
+
+		/**
+		 * Finds all the stack containers.
+		 * @returns {array} - The found stack containers.
+		 */
+        _findAllStackContainers: function () {
+            var stackContainers = [];
+            this._findAllStackContainersRecursive(stackContainers, this.root);
+
+            return stackContainers;
+        },
+
+		/**
+		 * Finds all the stack containers.
+		 *
+		 * @param {array} - Set of containers to populate.
+		 * @param {object} - Current node to process.
+		 *
+		 * @returns {void}
+		 */
+        _findAllStackContainersRecursive: function (stackContainers, node) {
+            node.contentItems.forEach(lm.utils.fnBind(function (item) {
+                if (item.type == 'stack') {
+                    stackContainers.push(item);
+                }
+                else if (!item.isComponent) {
+                    this._findAllStackContainersRecursive(stackContainers, item);
+                }
+            }, this));
+        }
+    });
+
+	/**
+	 * Expose the Layoutmanager as the single entrypoint using UMD
+	 */
+    (function () {
+        /* global define */
+        if (typeof define === 'function' && define.amd) {
+            define(['jquery'], function (jquery) {
+                $ = jquery;
+                return lm.LayoutManager;
+            }); // jshint ignore:line
+        } else if (typeof exports === 'object') {
+            module.exports = lm.LayoutManager;
+        } else {
+            window.GoldenLayout = lm.LayoutManager;
+        }
+    })();
+
+    lm.config.itemDefaultConfig = {
+        isClosable: true,
+        reorderEnabled: true,
+        title: ''
+    };
+    lm.config.defaultConfig = {
+        openPopouts: [],
+        settings: {
+            hasHeaders: true,
+            constrainDragToContainer: true,
+            reorderEnabled: true,
+            selectionEnabled: false,
+            popoutWholeStack: false,
+            blockedPopoutsThrowError: true,
+            closePopoutsOnUnload: true,
+            showPopoutIcon: true,
+            showMaximiseIcon: true,
+            showCloseIcon: true,
+            responsiveMode: 'onload', // Can be onload, always, or none.
+            tabOverlapAllowance: 0, // maximum pixel overlap per tab
+            reorderOnTabMenuClick: true,
+            tabControlOffset: 10
+        },
+        dimensions: {
+            borderWidth: 5,
+            borderGrabWidth: 15,
+            minItemHeight: 10,
+            minItemWidth: 10,
+            headerHeight: 20,
+            dragProxyWidth: 300,
+            dragProxyHeight: 200
+        },
+        labels: {
+            close: 'close',
+            maximise: 'maximise',
+            minimise: 'minimise',
+            popout: 'open in new window',
+            popin: 'pop in',
+            tabDropdown: 'additional tabs'
+        }
+    };
+
+    lm.container.ItemContainer = function (config, parent, layoutManager) {
+        lm.utils.EventEmitter.call(this);
+
+        this.width = null;
+        this.height = null;
+        this.title = config.componentName;
+        this.parent = parent;
+        this.layoutManager = layoutManager;
+        this.isHidden = false;
+
+        this._config = config;
+        this._element = $([
+            '<div class="lm_item_container">',
+            '<div class="lm_content"></div>',
+            '</div>'
+        ].join(''));
+
+        this._contentElement = this._element.find('.lm_content');
+    };
+
+    lm.utils.copy(lm.container.ItemContainer.prototype, {
+
+		/**
+		 * Get the inner DOM element the container's content
+		 * is intended to live in
+		 *
+		 * @returns {DOM element}
+		 */
+        getElement: function () {
+            return this._contentElement;
+        },
+
+		/**
+		 * Hide the container. Notifies the containers content first
+		 * and then hides the DOM node. If the container is already hidden
+		 * this should have no effect
+		 *
+		 * @returns {void}
+		 */
+        hide: function () {
+            this.emit('hide');
+            this.isHidden = true;
+            this._element.hide();
+        },
+
+		/**
+		 * Shows a previously hidden container. Notifies the
+		 * containers content first and then shows the DOM element.
+		 * If the container is already visible this has no effect.
+		 *
+		 * @returns {void}
+		 */
+        show: function () {
+            this.emit('show');
+            this.isHidden = false;
+            this._element.show();
+            // call shown only if the container has a valid size
+            if (this.height != 0 || this.width != 0) {
+                this.emit('shown');
+            }
+        },
+
+		/**
+		 * Set the size from within the container. Traverses up
+		 * the item tree until it finds a row or column element
+		 * and resizes its items accordingly.
+		 *
+		 * If this container isn't a descendant of a row or column
+		 * it returns false
+		 * @todo  Rework!!!
+		 * @param {Number} width  The new width in pixel
+		 * @param {Number} height The new height in pixel
+		 *
+		 * @returns {Boolean} resizeSuccesful
+		 */
+        setSize: function (width, height) {
+            var rowOrColumn = this.parent,
+                rowOrColumnChild = this,
+                totalPixel,
+                percentage,
+                direction,
+                newSize,
+                delta,
+                i;
+
+            while (!rowOrColumn.isColumn && !rowOrColumn.isRow) {
+                rowOrColumnChild = rowOrColumn;
+                rowOrColumn = rowOrColumn.parent;
+
+
+				/**
+				 * No row or column has been found
+				 */
+                if (rowOrColumn.isRoot) {
+                    return false;
+                }
+            }
+
+            direction = rowOrColumn.isColumn ? "height" : "width";
+            newSize = direction === "height" ? height : width;
+
+            totalPixel = this[direction] * (1 / (rowOrColumnChild.config[direction] / 100));
+            percentage = (newSize / totalPixel) * 100;
+            delta = (rowOrColumnChild.config[direction] - percentage) / (rowOrColumn.contentItems.length - 1);
+
+            for (i = 0; i < rowOrColumn.contentItems.length; i++) {
+                if (rowOrColumn.contentItems[i] === rowOrColumnChild) {
+                    rowOrColumn.contentItems[i].config[direction] = percentage;
+                } else {
+                    rowOrColumn.contentItems[i].config[direction] += delta;
+                }
+            }
+
+            rowOrColumn.callDownwards('setSize');
+
+            return true;
+        },
+
+		/**
+		 * Closes the container if it is closable. Can be called by
+		 * both the component within at as well as the contentItem containing
+		 * it. Emits a close event before the container itself is closed.
+		 *
+		 * @returns {void}
+		 */
+        close: function () {
+            if (this._config.isClosable) {
+                this.emit('close');
+                this.parent.close();
+            }
+        },
+
+		/**
+		 * Returns the current state object
+		 *
+		 * @returns {Object} state
+		 */
+        getState: function () {
+            return this._config.componentState;
+        },
+
+		/**
+		 * Merges the provided state into the current one
+		 *
+		 * @param   {Object} state
+		 *
+		 * @returns {void}
+		 */
+        extendState: function (state) {
+            this.setState($.extend(true, this.getState(), state));
+        },
+
+		/**
+		 * Notifies the layout manager of a stateupdate
+		 *
+		 * @param {serialisable} state
+		 */
+        setState: function (state) {
+            this._config.componentState = state;
+            this.parent.emitBubblingEvent('stateChanged');
+        },
+
+		/**
+		 * Set's the components title
+		 *
+		 * @param {String} title
+		 */
+        setTitle: function (title) {
+            this.parent.setTitle(title);
+        },
+
+		/**
+		 * Set's the containers size. Called by the container's component.
+		 * To set the size programmatically from within the container please
+		 * use the public setSize method
+		 *
+		 * @param {[Int]} width  in px
+		 * @param {[Int]} height in px
+		 *
+		 * @returns {void}
+		 */
+        _$setSize: function (width, height) {
+            if (width !== this.width || height !== this.height) {
+                this.width = width;
+                this.height = height;
+                var cl = this._contentElement[0];
+                var hdelta = cl.offsetWidth - cl.clientWidth;
+                var vdelta = cl.offsetHeight - cl.clientHeight;
+                this._contentElement.width(this.width - hdelta)
+                    .height(this.height - vdelta);
+                this.emit('resize');
+            }
+        }
+    });
+
+	/**
+	 * Pops a content item out into a new browser window.
+	 * This is achieved by
+	 *
+	 *    - Creating a new configuration with the content item as root element
+	 *    - Serializing and minifying the configuration
+	 *    - Opening the current window's URL with the configuration as a GET parameter
+	 *    - GoldenLayout when opened in the new window will look for the GET parameter
+	 *      and use it instead of the provided configuration
+	 *
+	 * @param {Object} config GoldenLayout item config
+	 * @param {Object} dimensions A map with width, height, top and left
+	 * @param {String} parentId The id of the element the item will be appended to on popIn
+	 * @param {Number} indexInParent The position of this element within its parent
+	 * @param {lm.LayoutManager} layoutManager
+	 */
+    lm.controls.BrowserPopout = function (config, dimensions, parentId, indexInParent, layoutManager) {
+        lm.utils.EventEmitter.call(this);
+        this.isInitialised = false;
+
+        this._config = config;
+        this._dimensions = dimensions;
+        this._parentId = parentId;
+        this._indexInParent = indexInParent;
+        this._layoutManager = layoutManager;
+        this._popoutWindow = null;
+        this._id = null;
+        this._createWindow();
+    };
+
+    lm.utils.copy(lm.controls.BrowserPopout.prototype, {
+
+        toConfig: function () {
+            if (this.isInitialised === false) {
+                throw new Error('Can\'t create config, layout not yet initialised');
+                return;
+            }
+            return {
+                dimensions: {
+                    width: this.getGlInstance().width,
+                    height: this.getGlInstance().height,
+                    left: this._popoutWindow.screenX || this._popoutWindow.screenLeft,
+                    top: this._popoutWindow.screenY || this._popoutWindow.screenTop
+                },
+                content: this.getGlInstance().toConfig().content,
+                parentId: this._parentId,
+                indexInParent: this._indexInParent
+            };
+        },
+
+        getGlInstance: function () {
+            return this._popoutWindow.__glInstance;
+        },
+
+        getWindow: function () {
+            return this._popoutWindow;
+        },
+
+        close: function () {
+            if (this.getGlInstance()) {
+                this.getGlInstance()._$closeWindow();
+            } else {
+                try {
+                    this.getWindow().close();
+                } catch (e) {
+                }
+            }
+        },
+
+		/**
+		 * Returns the popped out item to its original position. If the original
+		 * parent isn't available anymore it falls back to the layout's topmost element
+		 */
+        popIn: function () {
+            var childConfig,
+                parentItem,
+                index = this._indexInParent;
+
+            if (this._parentId) {
+
+				/*
+				 * The $.extend call seems a bit pointless, but it's crucial to
+				 * copy the config returned by this.getGlInstance().toConfig()
+				 * onto a new object. Internet Explorer keeps the references
+				 * to objects on the child window, resulting in the following error
+				 * once the child window is closed:
+				 *
+				 * The callee (server [not server application]) is not available and disappeared
+				 */
+                childConfig = $.extend(true, {}, this.getGlInstance().toConfig()).content[0];
+                parentItem = this._layoutManager.root.getItemsById(this._parentId)[0];
+
+				/*
+				 * Fallback if parentItem is not available. Either add it to the topmost
+				 * item or make it the topmost item if the layout is empty
+				 */
+                if (!parentItem) {
+                    if (this._layoutManager.root.contentItems.length > 0) {
+                        parentItem = this._layoutManager.root.contentItems[0];
+                    } else {
+                        parentItem = this._layoutManager.root;
+                    }
+                    index = 0;
+                }
+            }
+
+            parentItem.addChild(childConfig, this._indexInParent);
+            this.close();
+        },
+
+		/**
+		 * Creates the URL and window parameter
+		 * and opens a new window
+		 *
+		 * @private
+		 *
+		 * @returns {void}
+		 */
+        _createWindow: function () {
+            var checkReadyInterval,
+                url = this._createUrl(),
+
+				/**
+				 * Bogus title to prevent re-usage of existing window with the
+				 * same title. The actual title will be set by the new window's
+				 * GoldenLayout instance if it detects that it is in subWindowMode
+				 */
+                title = Math.floor(Math.random() * 1000000).toString(36),
+
+				/**
+				 * The options as used in the window.open string
+				 */
+                options = this._serializeWindowOptions({
+                    width: this._dimensions.width,
+                    height: this._dimensions.height,
+                    innerWidth: this._dimensions.width,
+                    innerHeight: this._dimensions.height,
+                    menubar: 'no',
+                    toolbar: 'no',
+                    location: 'no',
+                    personalbar: 'no',
+                    resizable: 'yes',
+                    scrollbars: 'no',
+                    status: 'no'
+                });
+
+            this._popoutWindow = window.open(url, title, options);
+
+            if (!this._popoutWindow) {
+                if (this._layoutManager.config.settings.blockedPopoutsThrowError === true) {
+                    var error = new Error('Popout blocked');
+                    error.type = 'popoutBlocked';
+                    throw error;
+                } else {
+                    return;
+                }
+            }
+
+            $(this._popoutWindow)
+                .on('load', lm.utils.fnBind(this._positionWindow, this))
+                .on('unload beforeunload', lm.utils.fnBind(this._onClose, this));
+
+			/**
+			 * Polling the childwindow to find out if GoldenLayout has been initialised
+			 * doesn't seem optimal, but the alternatives - adding a callback to the parent
+			 * window or raising an event on the window object - both would introduce knowledge
+			 * about the parent to the child window which we'd rather avoid
+			 */
+            checkReadyInterval = setInterval(lm.utils.fnBind(function () {
+                if (this._popoutWindow.__glInstance && this._popoutWindow.__glInstance.isInitialised) {
+                    this._onInitialised();
+                    clearInterval(checkReadyInterval);
+                }
+            }, this), 10);
+        },
+
+		/**
+		 * Serialises a map of key:values to a window options string
+		 *
+		 * @param   {Object} windowOptions
+		 *
+		 * @returns {String} serialised window options
+		 */
+        _serializeWindowOptions: function (windowOptions) {
+            var windowOptionsString = [], key;
+
+            for (key in windowOptions) {
+                windowOptionsString.push(key + '=' + windowOptions[key]);
+            }
+
+            return windowOptionsString.join(',');
+        },
+
+		/**
+		 * Creates the URL for the new window, including the
+		 * config GET parameter
+		 *
+		 * @returns {String} URL
+		 */
+        _createUrl: function () {
+            var config = { content: this._config },
+                storageKey = 'gl-window-config-' + lm.utils.getUniqueId(),
+                urlParts;
+
+            config = (new lm.utils.ConfigMinifier()).minifyConfig(config);
+
+            try {
+                localStorage.setItem(storageKey, JSON.stringify(config));
+            } catch (e) {
+                throw new Error('Error while writing to localStorage ' + e.toString());
+            }
+
+            urlParts = document.location.href.split('?');
+
+            // URL doesn't contain GET-parameters
+            if (urlParts.length === 1) {
+                return urlParts[0] + '?gl-window=' + storageKey;
+
+                // URL contains GET-parameters
+            } else {
+                return document.location.href + '&gl-window=' + storageKey;
+            }
+        },
+
+		/**
+		 * Move the newly created window roughly to
+		 * where the component used to be.
+		 *
+		 * @private
+		 *
+		 * @returns {void}
+		 */
+        _positionWindow: function () {
+            this._popoutWindow.moveTo(this._dimensions.left, this._dimensions.top);
+            this._popoutWindow.focus();
+        },
+
+		/**
+		 * Callback when the new window is opened and the GoldenLayout instance
+		 * within it is initialised
+		 *
+		 * @returns {void}
+		 */
+        _onInitialised: function () {
+            this.isInitialised = true;
+            this.getGlInstance().on('popIn', this.popIn, this);
+            this.emit('initialised');
+        },
+
+		/**
+		 * Invoked 50ms after the window unload event
+		 *
+		 * @private
+		 *
+		 * @returns {void}
+		 */
+        _onClose: function () {
+            setTimeout(lm.utils.fnBind(this.emit, this, ['closed']), 50);
+        }
+    });
+	/**
+	 * This class creates a temporary container
+	 * for the component whilst it is being dragged
+	 * and handles drag events
+	 *
+	 * @constructor
+	 * @private
+	 *
+	 * @param {Number} x              The initial x position
+	 * @param {Number} y              The initial y position
+	 * @param {lm.utils.DragListener} dragListener
+	 * @param {lm.LayoutManager} layoutManager
+	 * @param {lm.item.AbstractContentItem} contentItem
+	 * @param {lm.item.AbstractContentItem} originalParent
+	 */
+    lm.controls.DragProxy = function (x, y, dragListener, layoutManager, contentItem, originalParent) {
+
+        lm.utils.EventEmitter.call(this);
+
+        this._dragListener = dragListener;
+        this._layoutManager = layoutManager;
+        this._contentItem = contentItem;
+        this._originalParent = originalParent;
+
+        this._area = null;
+        this._lastValidArea = null;
+
+        this._dragListener.on('drag', this._onDrag, this);
+        this._dragListener.on('dragStop', this._onDrop, this);
+
+        this.element = $(lm.controls.DragProxy._template);
+        if (originalParent && originalParent._side) {
+            this._sided = originalParent._sided;
+            this.element.addClass('lm_' + originalParent._side);
+            if (['right', 'bottom'].indexOf(originalParent._side) >= 0)
+                this.element.find('.lm_content').after(this.element.find('.lm_header'));
+        }
+        this.element.css({ left: x, top: y });
+        this.element.find('.lm_tab').attr('title', lm.utils.stripTags(this._contentItem.config.title));
+        this.element.find('.lm_title').html(this._contentItem.config.title);
+        this.childElementContainer = this.element.find('.lm_content');
+        this.childElementContainer.append(contentItem.element);
+
+        this._updateTree();
+        this._layoutManager._$calculateItemAreas();
+        this._setDimensions();
+
+        $(document.body).append(this.element);
+
+        var offset = this._layoutManager.container.offset();
+
+        this._minX = offset.left;
+        this._minY = offset.top;
+        this._maxX = this._layoutManager.container.width() + this._minX;
+        this._maxY = this._layoutManager.container.height() + this._minY;
+        this._width = this.element.width();
+        this._height = this.element.height();
+
+        this._setDropPosition(x, y);
+    };
+
+    lm.controls.DragProxy._template = '<div class="lm_dragProxy">' +
+        '<div class="lm_header">' +
+        '<ul class="lm_tabs">' +
+        '<li class="lm_tab lm_active"><i class="lm_left"></i>' +
+        '<span class="lm_title"></span>' +
+        '<i class="lm_right"></i></li>' +
+        '</ul>' +
+        '</div>' +
+        '<div class="lm_content"></div>' +
+        '</div>';
+
+    lm.utils.copy(lm.controls.DragProxy.prototype, {
+
+		/**
+		 * Callback on every mouseMove event during a drag. Determines if the drag is
+		 * still within the valid drag area and calls the layoutManager to highlight the
+		 * current drop area
+		 *
+		 * @param   {Number} offsetX The difference from the original x position in px
+		 * @param   {Number} offsetY The difference from the original y position in px
+		 * @param   {jQuery DOM event} event
+		 *
+		 * @private
+		 *
+		 * @returns {void}
+		 */
+        _onDrag: function (offsetX, offsetY, event) {
+
+            event = event.originalEvent && event.originalEvent.touches ? event.originalEvent.touches[0] : event;
+
+            var x = event.pageX,
+                y = event.pageY,
+                isWithinContainer = x > this._minX && x < this._maxX && y > this._minY && y < this._maxY;
+
+            if (!isWithinContainer && this._layoutManager.config.settings.constrainDragToContainer === true) {
+                return;
+            }
+
+            this._setDropPosition(x, y);
+        },
+
+		/**
+		 * Sets the target position, highlighting the appropriate area
+		 *
+		 * @param   {Number} x The x position in px
+		 * @param   {Number} y The y position in px
+		 *
+		 * @private
+		 *
+		 * @returns {void}
+		 */
+        _setDropPosition: function (x, y) {
+            this.element.css({ left: x, top: y });
+            this._area = this._layoutManager._$getArea(x, y);
+
+            if (this._area !== null) {
+                this._lastValidArea = this._area;
+                this._area.contentItem._$highlightDropZone(x, y, this._area);
+            }
+        },
+
+		/**
+		 * Callback when the drag has finished. Determines the drop area
+		 * and adds the child to it
+		 *
+		 * @private
+		 *
+		 * @returns {void}
+		 */
+        _onDrop: function () {
+            this._layoutManager.dropTargetIndicator.hide();
+
+			/*
+			 * Valid drop area found
+			 */
+            if (this._area !== null) {
+                this._area.contentItem._$onDrop(this._contentItem, this._area);
+
+				/**
+				 * No valid drop area available at present, but one has been found before.
+				 * Use it
+				 */
+            } else if (this._lastValidArea !== null) {
+                this._lastValidArea.contentItem._$onDrop(this._contentItem, this._lastValidArea);
+
+				/**
+				 * No valid drop area found during the duration of the drag. Return
+				 * content item to its original position if a original parent is provided.
+				 * (Which is not the case if the drag had been initiated by createDragSource)
+				 */
+            } else if (this._originalParent) {
+                this._originalParent.addChild(this._contentItem);
+
+				/**
+				 * The drag didn't ultimately end up with adding the content item to
+				 * any container. In order to ensure clean up happens, destroy the
+				 * content item.
+				 */
+            } else {
+                this._contentItem._$destroy();
+            }
+
+            this.element.remove();
+
+            this._layoutManager.emit('itemDropped', this._contentItem);
+        },
+
+		/**
+		 * Removes the item from its original position within the tree
+		 *
+		 * @private
+		 *
+		 * @returns {void}
+		 */
+        _updateTree: function () {
+
+			/**
+			 * parent is null if the drag had been initiated by a external drag source
+			 */
+            if (this._contentItem.parent) {
+                this._contentItem.parent.removeChild(this._contentItem, true);
+            }
+
+            this._contentItem._$setParent(this);
+        },
+
+		/**
+		 * Updates the Drag Proxie's dimensions
+		 *
+		 * @private
+		 *
+		 * @returns {void}
+		 */
+        _setDimensions: function () {
+            var dimensions = this._layoutManager.config.dimensions,
+                width = dimensions.dragProxyWidth,
+                height = dimensions.dragProxyHeight;
+
+            this.element.width(width);
+            this.element.height(height);
+            width -= (this._sided ? dimensions.headerHeight : 0);
+            height -= (!this._sided ? dimensions.headerHeight : 0);
+            this.childElementContainer.width(width);
+            this.childElementContainer.height(height);
+            this._contentItem.element.width(width);
+            this._contentItem.element.height(height);
+            this._contentItem.callDownwards('_$show');
+            this._contentItem.callDownwards('setSize');
+        }
+    });
+
+	/**
+	 * Allows for any DOM item to create a component on drag
+	 * start tobe dragged into the Layout
+	 *
+	 * @param {jQuery element} element
+	 * @param {Object} itemConfig the configuration for the contentItem that will be created
+	 * @param {LayoutManager} layoutManager
+	 *
+	 * @constructor
+	 */
+    lm.controls.DragSource = function (element, itemConfig, layoutManager) {
+        this._element = element;
+        this._itemConfig = itemConfig;
+        this._layoutManager = layoutManager;
+        this._dragListener = null;
+
+        this._createDragListener();
+    };
+
+    lm.utils.copy(lm.controls.DragSource.prototype, {
+
+		/**
+		 * Called initially and after every drag
+		 *
+		 * @returns {void}
+		 */
+        _createDragListener: function () {
+            if (this._dragListener !== null) {
+                this._dragListener.destroy();
+            }
+
+            this._dragListener = new lm.utils.DragListener(this._element);
+            this._dragListener.on('dragStart', this._onDragStart, this);
+            this._dragListener.on('dragStop', this._createDragListener, this);
+        },
+
+		/**
+		 * Callback for the DragListener's dragStart event
+		 *
+		 * @param   {int} x the x position of the mouse on dragStart
+		 * @param   {int} y the x position of the mouse on dragStart
+		 *
+		 * @returns {void}
+		 */
+        _onDragStart: function (x, y) {
+            var itemConfig = this._itemConfig;
+            if (lm.utils.isFunction(itemConfig)) {
+                itemConfig = itemConfig();
+            }
+            var contentItem = this._layoutManager._$normalizeContentItem($.extend(true, {}, itemConfig)),
+                dragProxy = new lm.controls.DragProxy(x, y, this._dragListener, this._layoutManager, contentItem, null);
+
+            this._layoutManager.transitionIndicator.transitionElements(this._element, dragProxy.element);
+        }
+    });
+
+    lm.controls.DropTargetIndicator = function () {
+        this.element = $(lm.controls.DropTargetIndicator._template);
+        $(document.body).append(this.element);
+    };
+
+    lm.controls.DropTargetIndicator._template = '<div class="lm_dropTargetIndicator"><div class="lm_inner"></div></div>';
+
+    lm.utils.copy(lm.controls.DropTargetIndicator.prototype, {
+        destroy: function () {
+            this.element.remove();
+        },
+
+        highlight: function (x1, y1, x2, y2) {
+            this.highlightArea({ x1: x1, y1: y1, x2: x2, y2: y2 });
+        },
+
+        highlightArea: function (area) {
+            this.element.css({
+                left: area.x1,
+                top: area.y1,
+                width: area.x2 - area.x1,
+                height: area.y2 - area.y1
+            }).show();
+        },
+
+        hide: function () {
+            this.element.hide();
+        }
+    });
+	/**
+	 * This class represents a header above a Stack ContentItem.
+	 *
+	 * @param {lm.LayoutManager} layoutManager
+	 * @param {lm.item.AbstractContentItem} parent
+	 */
+    lm.controls.Header = function (layoutManager, parent) {
+        lm.utils.EventEmitter.call(this);
+
+        this.layoutManager = layoutManager;
+        this.element = $(lm.controls.Header._template);
+
+        if (this.layoutManager.config.settings.selectionEnabled === true) {
+            this.element.addClass('lm_selectable');
+            this.element.on('click touchstart', lm.utils.fnBind(this._onHeaderClick, this));
+        }
+
+        this.tabsContainer = this.element.find('.lm_tabs');
+        this.tabDropdownContainer = this.element.find('.lm_tabdropdown_list');
+        this.tabDropdownContainer.hide();
+        this.controlsContainer = this.element.find('.lm_controls');
+        this.parent = parent;
+        this.parent.on('resize', this._updateTabSizes, this);
+        this.tabs = [];
+        this.activeContentItem = null;
+        this.closeButton = null;
+        this.tabDropdownButton = null;
+        this.hideAdditionalTabsDropdown = lm.utils.fnBind(this._hideAdditionalTabsDropdown, this);
+        $(document).mouseup(this.hideAdditionalTabsDropdown);
+
+        this._lastVisibleTabIndex = -1;
+        this._tabControlOffset = this.layoutManager.config.settings.tabControlOffset;
+        this._createControls();
+    };
+
+    lm.controls.Header._template = [
+        '<div class="lm_header">',
+        '<ul class="lm_tabs"></ul>',
+        '<ul class="lm_controls"></ul>',
+        '<ul class="lm_tabdropdown_list"></ul>',
+        '</div>'
+    ].join('');
+
+    lm.utils.copy(lm.controls.Header.prototype, {
+
+		/**
+		 * Creates a new tab and associates it with a contentItem
+		 *
+		 * @param    {lm.item.AbstractContentItem} contentItem
+		 * @param    {Integer} index The position of the tab
+		 *
+		 * @returns {void}
+		 */
+        createTab: function (contentItem, index) {
+            var tab, i;
+
+            //If there's already a tab relating to the
+            //content item, don't do anything
+            for (i = 0; i < this.tabs.length; i++) {
+                if (this.tabs[i].contentItem === contentItem) {
+                    return;
+                }
+            }
+
+            tab = new lm.controls.Tab(this, contentItem);
+
+            if (this.tabs.length === 0) {
+                this.tabs.push(tab);
+                this.tabsContainer.append(tab.element);
+                return;
+            }
+
+            if (index === undefined) {
+                index = this.tabs.length;
+            }
+
+            if (index > 0) {
+                this.tabs[index - 1].element.after(tab.element);
+            } else {
+                this.tabs[0].element.before(tab.element);
+            }
+
+            this.tabs.splice(index, 0, tab);
+            this._updateTabSizes();
+        },
+
+		/**
+		 * Finds a tab based on the contentItem its associated with and removes it.
+		 *
+		 * @param    {lm.item.AbstractContentItem} contentItem
+		 *
+		 * @returns {void}
+		 */
+        removeTab: function (contentItem) {
+            for (var i = 0; i < this.tabs.length; i++) {
+                if (this.tabs[i].contentItem === contentItem) {
+                    this.tabs[i]._$destroy();
+                    this.tabs.splice(i, 1);
+                    return;
+                }
+            }
+
+            throw new Error('contentItem is not controlled by this header');
+        },
+
+		/**
+		 * The programmatical equivalent of clicking a Tab.
+		 *
+		 * @param {lm.item.AbstractContentItem} contentItem
+		 */
+        setActiveContentItem: function (contentItem) {
+            var i, j, isActive, activeTab;
+
+            for (i = 0; i < this.tabs.length; i++) {
+                isActive = this.tabs[i].contentItem === contentItem;
+                this.tabs[i].setActive(isActive);
+                if (isActive === true) {
+                    this.activeContentItem = contentItem;
+                    this.parent.config.activeItemIndex = i;
+                }
+            }
+
+            if (this.layoutManager.config.settings.reorderOnTabMenuClick) {
+				/**
+				 * If the tab selected was in the dropdown, move everything down one to make way for this one to be the first.
+				 * This will make sure the most used tabs stay visible.
+				 */
+                if (this._lastVisibleTabIndex !== -1 && this.parent.config.activeItemIndex > this._lastVisibleTabIndex) {
+                    activeTab = this.tabs[this.parent.config.activeItemIndex];
+                    for (j = this.parent.config.activeItemIndex; j > 0; j--) {
+                        this.tabs[j] = this.tabs[j - 1];
+                    }
+                    this.tabs[0] = activeTab;
+                    this.parent.config.activeItemIndex = 0;
+                }
+            }
+
+            this._updateTabSizes();
+            this.parent.emitBubblingEvent('stateChanged');
+        },
+
+		/**
+		 * Programmatically operate with header position.
+		 *
+		 * @param {string} position one of ('top','left','right','bottom') to set or empty to get it.
+		 *
+		 * @returns {string} previous header position
+		 */
+        position: function (position) {
+            var previous = this.parent._header.show;
+            if (previous && !this.parent._side)
+                previous = 'top';
+            if (position !== undefined && this.parent._header.show != position) {
+                this.parent._header.show = position;
+                this.parent._setupHeaderPosition();
+            }
+            return previous;
+        },
+
+		/**
+		 * Programmatically set closability.
+		 *
+		 * @package private
+		 * @param {Boolean} isClosable Whether to enable/disable closability.
+		 *
+		 * @returns {Boolean} Whether the action was successful
+		 */
+        _$setClosable: function (isClosable) {
+            if (this.closeButton && this._isClosable()) {
+                this.closeButton.element[isClosable ? "show" : "hide"]();
+                return true;
+            }
+
+            return false;
+        },
+
+		/**
+		 * Destroys the entire header
+		 *
+		 * @package private
+		 *
+		 * @returns {void}
+		 */
+        _$destroy: function () {
+            this.emit('destroy', this);
+
+            for (var i = 0; i < this.tabs.length; i++) {
+                this.tabs[i]._$destroy();
+            }
+            $(document).off('mouseup', this.hideAdditionalTabsDropdown);
+            this.element.remove();
+        },
+
+		/**
+		 * get settings from header
+		 *
+		 * @returns {string} when exists
+		 */
+        _getHeaderSetting: function (name) {
+            if (name in this.parent._header)
+                return this.parent._header[name];
+        },
+		/**
+		 * Creates the popout, maximise and close buttons in the header's top right corner
+		 *
+		 * @returns {void}
+		 */
+        _createControls: function () {
+            var closeStack,
+                popout,
+                label,
+                maximiseLabel,
+                minimiseLabel,
+                maximise,
+                maximiseButton,
+                tabDropdownLabel,
+                showTabDropdown;
+
+			/**
+			 * Dropdown to show additional tabs.
+			 */
+            showTabDropdown = lm.utils.fnBind(this._showAdditionalTabsDropdown, this);
+            tabDropdownLabel = this.layoutManager.config.labels.tabDropdown;
+            this.tabDropdownButton = new lm.controls.HeaderButton(this, tabDropdownLabel, 'lm_tabdropdown', showTabDropdown);
+            this.tabDropdownButton.element.hide();
+
+			/**
+			 * Popout control to launch component in new window.
+			 */
+            if (this._getHeaderSetting('popout')) {
+                popout = lm.utils.fnBind(this._onPopoutClick, this);
+                label = this._getHeaderSetting('popout');
+                new lm.controls.HeaderButton(this, label, 'lm_popout', popout);
+            }
+
+			/**
+			 * Maximise control - set the component to the full size of the layout
+			 */
+            if (this._getHeaderSetting('maximise')) {
+                maximise = lm.utils.fnBind(this.parent.toggleMaximise, this.parent);
+                maximiseLabel = this._getHeaderSetting('maximise');
+                minimiseLabel = this._getHeaderSetting('minimise');
+                maximiseButton = new lm.controls.HeaderButton(this, maximiseLabel, 'lm_maximise', maximise);
+
+                this.parent.on('maximised', function () {
+                    maximiseButton.element.attr('title', minimiseLabel);
+                });
+
+                this.parent.on('minimised', function () {
+                    maximiseButton.element.attr('title', maximiseLabel);
+                });
+            }
+
+			/**
+			 * Close button
+			 */
+            if (this._isClosable()) {
+                closeStack = lm.utils.fnBind(this.parent.remove, this.parent);
+                label = this._getHeaderSetting('close');
+                this.closeButton = new lm.controls.HeaderButton(this, label, 'lm_close', closeStack);
+            }
+        },
+
+		/**
+		 * Shows drop down for additional tabs when there are too many to display.
+		 *
+		 * @returns {void}
+		 */
+        _showAdditionalTabsDropdown: function () {
+            this.tabDropdownContainer.show();
+        },
+
+		/**
+		 * Hides drop down for additional tabs when there are too many to display.
+		 *
+		 * @returns {void}
+		 */
+        _hideAdditionalTabsDropdown: function (e) {
+            this.tabDropdownContainer.hide();
+        },
+
+		/**
+		 * Checks whether the header is closable based on the parent config and
+		 * the global config.
+		 *
+		 * @returns {Boolean} Whether the header is closable.
+		 */
+        _isClosable: function () {
+            return this.parent.config.isClosable && this.layoutManager.config.settings.showCloseIcon;
+        },
+
+        _onPopoutClick: function () {
+            if (this.layoutManager.config.settings.popoutWholeStack === true) {
+                this.parent.popout();
+            } else {
+                this.activeContentItem.popout();
+            }
+        },
+
+
+		/**
+		 * Invoked when the header's background is clicked (not it's tabs or controls)
+		 *
+		 * @param    {jQuery DOM event} event
+		 *
+		 * @returns {void}
+		 */
+        _onHeaderClick: function (event) {
+            if (event.target === this.element[0]) {
+                this.parent.select();
+            }
+        },
+
+		/**
+		 * Pushes the tabs to the tab dropdown if the available space is not sufficient
+		 *
+		 * @returns {void}
+		 */
+        _updateTabSizes: function (showTabMenu) {
+            if (this.tabs.length === 0) {
+                return;
+            }
+
+            //Show the menu based on function argument
+            this.tabDropdownButton.element.toggle(showTabMenu === true);
+
+            var size = function (val) {
+                return val ? 'width' : 'height';
+            };
+            this.element.css(size(!this.parent._sided), '');
+            this.element[size(this.parent._sided)](this.layoutManager.config.dimensions.headerHeight);
+            var availableWidth = this.element.outerWidth() - this.controlsContainer.outerWidth() - this._tabControlOffset,
+                cumulativeTabWidth = 0,
+                visibleTabWidth = 0,
+                tabElement,
+                i,
+                j,
+                marginLeft,
+                overlap = 0,
+                tabWidth,
+                tabOverlapAllowance = this.layoutManager.config.settings.tabOverlapAllowance,
+                tabOverlapAllowanceExceeded = false,
+                activeIndex = (this.activeContentItem ? this.tabs.indexOf(this.activeContentItem.tab) : 0),
+                activeTab = this.tabs[activeIndex];
+            if (this.parent._sided)
+                availableWidth = this.element.outerHeight() - this.controlsContainer.outerHeight() - this._tabControlOffset;
+            this._lastVisibleTabIndex = -1;
+
+            for (i = 0; i < this.tabs.length; i++) {
+                tabElement = this.tabs[i].element;
+
+                //Put the tab in the tabContainer so its true width can be checked
+                this.tabsContainer.append(tabElement);
+                tabWidth = tabElement.outerWidth() + parseInt(tabElement.css('margin-right'), 10);
+
+                cumulativeTabWidth += tabWidth;
+
+                //Include the active tab's width if it isn't already
+                //This is to ensure there is room to show the active tab
+                if (activeIndex <= i) {
+                    visibleTabWidth = cumulativeTabWidth;
+                } else {
+                    visibleTabWidth = cumulativeTabWidth + activeTab.element.outerWidth() + parseInt(activeTab.element.css('margin-right'), 10);
+                }
+
+                // If the tabs won't fit, check the overlap allowance.
+                if (visibleTabWidth > availableWidth) {
+
+                    //Once allowance is exceeded, all remaining tabs go to menu.
+                    if (!tabOverlapAllowanceExceeded) {
+
+                        //No overlap for first tab or active tab
+                        //Overlap spreads among non-active, non-first tabs
+                        if (activeIndex > 0 && activeIndex <= i) {
+                            overlap = (visibleTabWidth - availableWidth) / (i - 1);
+                        } else {
+                            overlap = (visibleTabWidth - availableWidth) / i;
+                        }
+
+                        //Check overlap against allowance.
+                        if (overlap < tabOverlapAllowance) {
+                            for (j = 0; j <= i; j++) {
+                                marginLeft = (j !== activeIndex && j !== 0) ? '-' + overlap + 'px' : '';
+                                this.tabs[j].element.css({ 'z-index': i - j, 'margin-left': marginLeft });
+                            }
+                            this._lastVisibleTabIndex = i;
+                            this.tabsContainer.append(tabElement);
+                        } else {
+                            tabOverlapAllowanceExceeded = true;
+                        }
+
+                    } else if (i === activeIndex) {
+                        //Active tab should show even if allowance exceeded. (We left room.)
+                        tabElement.css({ 'z-index': 'auto', 'margin-left': '' });
+                        this.tabsContainer.append(tabElement);
+                    }
+
+                    if (tabOverlapAllowanceExceeded && i !== activeIndex) {
+                        if (showTabMenu) {
+                            //Tab menu already shown, so we just add to it.
+                            tabElement.css({ 'z-index': 'auto', 'margin-left': '' });
+                            this.tabDropdownContainer.append(tabElement);
+                        } else {
+                            //We now know the tab menu must be shown, so we have to recalculate everything.
+                            this._updateTabSizes(true);
+                            return;
+                        }
+                    }
+
+                }
+                else {
+                    this._lastVisibleTabIndex = i;
+                    tabElement.css({ 'z-index': 'auto', 'margin-left': '' });
+                    this.tabsContainer.append(tabElement);
+                }
+            }
+
+        }
+    });
+
+
+    lm.controls.HeaderButton = function (header, label, cssClass, action) {
+        this._header = header;
+        this.element = $('<li class="' + cssClass + '" title="' + label + '"></li>');
+        this._header.on('destroy', this._$destroy, this);
+        this._action = action;
+        this.element.on('click touchstart', this._action);
+        this._header.controlsContainer.append(this.element);
+    };
+
+    lm.utils.copy(lm.controls.HeaderButton.prototype, {
+        _$destroy: function () {
+            this.element.off();
+            this.element.remove();
+        }
+    });
+    lm.controls.Splitter = function (isVertical, size, grabSize) {
+        this._isVertical = isVertical;
+        this._size = size;
+        this._grabSize = grabSize < size ? size : grabSize;
+
+        this.element = this._createElement();
+        this._dragListener = new lm.utils.DragListener(this.element);
+    };
+
+    lm.utils.copy(lm.controls.Splitter.prototype, {
+        on: function (event, callback, context) {
+            this._dragListener.on(event, callback, context);
+        },
+
+        _$destroy: function () {
+            this.element.remove();
+        },
+
+        _createElement: function () {
+            var dragHandle = $('<div class="lm_drag_handle"></div>');
+            var element = $('<div class="lm_splitter"></div>');
+            element.append(dragHandle);
+
+            var handleExcessSize = this._grabSize - this._size;
+            var handleExcessPos = handleExcessSize / 2;
+
+            if (this._isVertical) {
+                dragHandle.css('top', -handleExcessPos);
+                dragHandle.css('height', this._size + handleExcessSize);
+                element.addClass('lm_vertical');
+                element['height'](this._size);
+            } else {
+                dragHandle.css('left', -handleExcessPos);
+                dragHandle.css('width', this._size + handleExcessSize);
+                element.addClass('lm_horizontal');
+                element['width'](this._size);
+            }
+
+            return element;
+        }
+    });
+
+	/**
+	 * Represents an individual tab within a Stack's header
+	 *
+	 * @param {lm.controls.Header} header
+	 * @param {lm.items.AbstractContentItem} contentItem
+	 *
+	 * @constructor
+	 */
+    lm.controls.Tab = function (header, contentItem) {
+        this.header = header;
+        this.contentItem = contentItem;
+        this.element = $(lm.controls.Tab._template);
+        this.titleElement = this.element.find('.lm_title');
+        this.closeElement = this.element.find('.lm_close_tab');
+        this.closeElement[contentItem.config.isClosable ? 'show' : 'hide']();
+        this.isActive = false;
+
+        this.setTitle(contentItem.config.title);
+        this.contentItem.on('titleChanged', this.setTitle, this);
+
+        this._layoutManager = this.contentItem.layoutManager;
+
+        if (
+            this._layoutManager.config.settings.reorderEnabled === true &&
+            contentItem.config.reorderEnabled === true
+        ) {
+            this._dragListener = new lm.utils.DragListener(this.element);
+            this._dragListener.on('dragStart', this._onDragStart, this);
+            this.contentItem.on('destroy', this._dragListener.destroy, this._dragListener);
+        }
+
+        this._onTabClickFn = lm.utils.fnBind(this._onTabClick, this);
+        this._onCloseClickFn = lm.utils.fnBind(this._onCloseClick, this);
+
+        this.element.on('mousedown touchstart', this._onTabClickFn);
+
+        if (this.contentItem.config.isClosable) {
+            this.closeElement.on('click touchstart', this._onCloseClickFn);
+            this.closeElement.on('mousedown', this._onCloseMousedown);
+        } else {
+            this.closeElement.remove();
+        }
+
+        this.contentItem.tab = this;
+        this.contentItem.emit('tab', this);
+        this.contentItem.layoutManager.emit('tabCreated', this);
+
+        if (this.contentItem.isComponent) {
+            this.contentItem.container.tab = this;
+            this.contentItem.container.emit('tab', this);
+        }
+    };
+
+	/**
+	 * The tab's html template
+	 *
+	 * @type {String}
+	 */
+    lm.controls.Tab._template = '<li class="lm_tab"><i class="lm_left"></i>' +
+        '<span class="lm_title"></span><div class="lm_close_tab"></div>' +
+        '<i class="lm_right"></i></li>';
+
+    lm.utils.copy(lm.controls.Tab.prototype, {
+
+		/**
+		 * Sets the tab's title to the provided string and sets
+		 * its title attribute to a pure text representation (without
+		 * html tags) of the same string.
+		 *
+		 * @public
+		 * @param {String} title can contain html
+		 */
+        setTitle: function (title) {
+            this.element.attr('title', lm.utils.stripTags(title));
+            this.titleElement.html(title);
+        },
+
+		/**
+		 * Sets this tab's active state. To programmatically
+		 * switch tabs, use header.setActiveContentItem( item ) instead.
+		 *
+		 * @public
+		 * @param {Boolean} isActive
+		 */
+        setActive: function (isActive) {
+            if (isActive === this.isActive) {
+                return;
+            }
+            this.isActive = isActive;
+
+            if (isActive) {
+                this.element.addClass('lm_active');
+            } else {
+                this.element.removeClass('lm_active');
+            }
+        },
+
+		/**
+		 * Destroys the tab
+		 *
+		 * @private
+		 * @returns {void}
+		 */
+        _$destroy: function () {
+            this.element.off('mousedown touchstart', this._onTabClickFn);
+            this.closeElement.off('click touchstart', this._onCloseClickFn);
+            if (this._dragListener) {
+                this.contentItem.off('destroy', this._dragListener.destroy, this._dragListener);
+                this._dragListener.off('dragStart', this._onDragStart);
+                this._dragListener = null;
+            }
+            this.element.remove();
+        },
+
+		/**
+		 * Callback for the DragListener
+		 *
+		 * @param   {Number} x The tabs absolute x position
+		 * @param   {Number} y The tabs absolute y position
+		 *
+		 * @private
+		 * @returns {void}
+		 */
+        _onDragStart: function (x, y) {
+            if (this.contentItem.parent.isMaximised === true) {
+                this.contentItem.parent.toggleMaximise();
+            }
+            new lm.controls.DragProxy(
+                x,
+                y,
+                this._dragListener,
+                this._layoutManager,
+                this.contentItem,
+                this.header.parent
+            );
+        },
+
+		/**
+		 * Callback when the tab is clicked
+		 *
+		 * @param {jQuery DOM event} event
+		 *
+		 * @private
+		 * @returns {void}
+		 */
+        _onTabClick: function (event) {
+            // left mouse button or tap
+            if (event.button === 0 || event.type === 'touchstart') {
+                var activeContentItem = this.header.parent.getActiveContentItem();
+                if (this.contentItem !== activeContentItem) {
+                    this.header.parent.setActiveContentItem(this.contentItem);
+                }
+
+                // middle mouse button
+            } else if (event.button === 1 && this.contentItem.config.isClosable) {
+                this._onCloseClick(event);
+            }
+        },
+
+		/**
+		 * Callback when the tab's close button is
+		 * clicked
+		 *
+		 * @param   {jQuery DOM event} event
+		 *
+		 * @private
+		 * @returns {void}
+		 */
+        _onCloseClick: function (event) {
+            event.stopPropagation();
+            this.header.parent.removeChild(this.contentItem);
+        },
+
+
+		/**
+		 * Callback to capture tab close button mousedown
+		 * to prevent tab from activating.
+		 *
+		 * @param (jQuery DOM event) event
+		 *
+		 * @private
+		 * @returns {void}
+		 */
+        _onCloseMousedown: function (event) {
+            event.stopPropagation();
+        }
+    });
+
+    lm.controls.TransitionIndicator = function () {
+        this._element = $('<div class="lm_transition_indicator"></div>');
+        $(document.body).append(this._element);
+
+        this._toElement = null;
+        this._fromDimensions = null;
+        this._totalAnimationDuration = 200;
+        this._animationStartTime = null;
+    };
+
+    lm.utils.copy(lm.controls.TransitionIndicator.prototype, {
+        destroy: function () {
+            this._element.remove();
+        },
+
+        transitionElements: function (fromElement, toElement) {
+			/**
+			 * TODO - This is not quite as cool as expected. Review.
+			 */
+            return;
+            this._toElement = toElement;
+            this._animationStartTime = lm.utils.now();
+            this._fromDimensions = this._measure(fromElement);
+            this._fromDimensions.opacity = 0.8;
+            this._element.show().css(this._fromDimensions);
+            lm.utils.animFrame(lm.utils.fnBind(this._nextAnimationFrame, this));
+        },
+
+        _nextAnimationFrame: function () {
+            var toDimensions = this._measure(this._toElement),
+                animationProgress = (lm.utils.now() - this._animationStartTime) / this._totalAnimationDuration,
+                currentFrameStyles = {},
+                cssProperty;
+
+            if (animationProgress >= 1) {
+                this._element.hide();
+                return;
+            }
+
+            toDimensions.opacity = 0;
+
+            for (cssProperty in this._fromDimensions) {
+                currentFrameStyles[cssProperty] = this._fromDimensions[cssProperty] +
+                    (toDimensions[cssProperty] - this._fromDimensions[cssProperty]) *
+                    animationProgress;
+            }
+
+            this._element.css(currentFrameStyles);
+            lm.utils.animFrame(lm.utils.fnBind(this._nextAnimationFrame, this));
+        },
+
+        _measure: function (element) {
+            var offset = element.offset();
+
+            return {
+                left: offset.left,
+                top: offset.top,
+                width: element.outerWidth(),
+                height: element.outerHeight()
+            };
+        }
+    });
+    lm.errors.ConfigurationError = function (message, node) {
+        Error.call(this);
+
+        this.name = 'Configuration Error';
+        this.message = message;
+        this.node = node;
+    };
+
+    lm.errors.ConfigurationError.prototype = new Error();
+
+	/**
+	 * This is the baseclass that all content items inherit from.
+	 * Most methods provide a subset of what the sub-classes do.
+	 *
+	 * It also provides a number of functions for tree traversal
+	 *
+	 * @param {lm.LayoutManager} layoutManager
+	 * @param {item node configuration} config
+	 * @param {lm.item} parent
+	 *
+	 * @event stateChanged
+	 * @event beforeItemDestroyed
+	 * @event itemDestroyed
+	 * @event itemCreated
+	 * @event componentCreated
+	 * @event rowCreated
+	 * @event columnCreated
+	 * @event stackCreated
+	 *
+	 * @constructor
+	 */
+    lm.items.AbstractContentItem = function (layoutManager, config, parent) {
+        lm.utils.EventEmitter.call(this);
+
+        this.config = this._extendItemNode(config);
+        this.type = config.type;
+        this.contentItems = [];
+        this.parent = parent;
+
+        this.isInitialised = false;
+        this.isMaximised = false;
+        this.isRoot = false;
+        this.isRow = false;
+        this.isColumn = false;
+        this.isStack = false;
+        this.isComponent = false;
+
+        this.layoutManager = layoutManager;
+        this._pendingEventPropagations = {};
+        this._throttledEvents = ['stateChanged'];
+
+        this.on(lm.utils.EventEmitter.ALL_EVENT, this._propagateEvent, this);
+
+        if (config.content) {
+            this._createContentItems(config);
+        }
+    };
+
+    lm.utils.copy(lm.items.AbstractContentItem.prototype, {
+
+		/**
+		 * Set the size of the component and its children, called recursively
+		 *
+		 * @abstract
+		 * @returns void
+		 */
+        setSize: function () {
+            throw new Error('Abstract Method');
+        },
+
+		/**
+		 * Calls a method recursively downwards on the tree
+		 *
+		 * @param   {String} functionName      the name of the function to be called
+		 * @param   {[Array]}functionArguments optional arguments that are passed to every function
+		 * @param   {[bool]} bottomUp          Call methods from bottom to top, defaults to false
+		 * @param   {[bool]} skipSelf          Don't invoke the method on the class that calls it, defaults to false
+		 *
+		 * @returns {void}
+		 */
+        callDownwards: function (functionName, functionArguments, bottomUp, skipSelf) {
+            var i;
+
+            if (bottomUp !== true && skipSelf !== true) {
+                this[functionName].apply(this, functionArguments || []);
+            }
+            for (i = 0; i < this.contentItems.length; i++) {
+                this.contentItems[i].callDownwards(functionName, functionArguments, bottomUp);
+            }
+            if (bottomUp === true && skipSelf !== true) {
+                this[functionName].apply(this, functionArguments || []);
+            }
+        },
+
+		/**
+		 * Removes a child node (and its children) from the tree
+		 *
+		 * @param   {lm.items.ContentItem} contentItem
+		 *
+		 * @returns {void}
+		 */
+        removeChild: function (contentItem, keepChild) {
+
+			/*
+			 * Get the position of the item that's to be removed within all content items this node contains
+			 */
+            var index = lm.utils.indexOf(contentItem, this.contentItems);
+
+			/*
+			 * Make sure the content item to be removed is actually a child of this item
+			 */
+            if (index === -1) {
+                throw new Error('Can\'t remove child item. Unknown content item');
+            }
+
+			/**
+			 * Call ._$destroy on the content item. This also calls ._$destroy on all its children
+			 */
+            if (keepChild !== true) {
+                this.contentItems[index]._$destroy();
+            }
+
+			/**
+			 * Remove the content item from this nodes array of children
+			 */
+            this.contentItems.splice(index, 1);
+
+			/**
+			 * Remove the item from the configuration
+			 */
+            this.config.content.splice(index, 1);
+
+			/**
+			 * If this node still contains other content items, adjust their size
+			 */
+            if (this.contentItems.length > 0) {
+                this.callDownwards('setSize');
+
+				/**
+				 * If this was the last content item, remove this node as well
+				 */
+            } else if (!(this instanceof lm.items.Root) && this.config.isClosable === true) {
+                this.parent.removeChild(this);
+            }
+        },
+
+		/**
+		 * Sets up the tree structure for the newly added child
+		 * The responsibility for the actual DOM manipulations lies
+		 * with the concrete item
+		 *
+		 * @param {lm.items.AbstractContentItem} contentItem
+		 * @param {[Int]} index If omitted item will be appended
+		 */
+        addChild: function (contentItem, index) {
+            if (index === undefined) {
+                index = this.contentItems.length;
+            }
+
+            this.contentItems.splice(index, 0, contentItem);
+
+            if (this.config.content === undefined) {
+                this.config.content = [];
+            }
+
+            this.config.content.splice(index, 0, contentItem.config);
+            contentItem.parent = this;
+
+            if (contentItem.parent.isInitialised === true && contentItem.isInitialised === false) {
+                contentItem._$init();
+            }
+        },
+
+		/**
+		 * Replaces oldChild with newChild. This used to use jQuery.replaceWith... which for
+		 * some reason removes all event listeners, so isn't really an option.
+		 *
+		 * @param   {lm.item.AbstractContentItem} oldChild
+		 * @param   {lm.item.AbstractContentItem} newChild
+		 *
+		 * @returns {void}
+		 */
+        replaceChild: function (oldChild, newChild, _$destroyOldChild) {
+
+            newChild = this.layoutManager._$normalizeContentItem(newChild);
+
+            var index = lm.utils.indexOf(oldChild, this.contentItems),
+                parentNode = oldChild.element[0].parentNode;
+
+            if (index === -1) {
+                throw new Error('Can\'t replace child. oldChild is not child of this');
+            }
+
+            parentNode.replaceChild(newChild.element[0], oldChild.element[0]);
+
+			/*
+			 * Optionally destroy the old content item
+			 */
+            if (_$destroyOldChild === true) {
+                oldChild.parent = null;
+                oldChild._$destroy();
+            }
+
+			/*
+			 * Wire the new contentItem into the tree
+			 */
+            this.contentItems[index] = newChild;
+            newChild.parent = this;
+
+			/*
+			 * Update tab reference
+			 */
+            if (this.isStack) {
+                this.header.tabs[index].contentItem = newChild;
+            }
+
+            //TODO This doesn't update the config... refactor to leave item nodes untouched after creation
+            if (newChild.parent.isInitialised === true && newChild.isInitialised === false) {
+                newChild._$init();
+            }
+
+            this.callDownwards('setSize');
+        },
+
+		/**
+		 * Convenience method.
+		 * Shorthand for this.parent.removeChild( this )
+		 *
+		 * @returns {void}
+		 */
+        remove: function () {
+            this.parent.removeChild(this);
+        },
+
+		/**
+		 * Removes the component from the layout and creates a new
+		 * browser window with the component and its children inside
+		 *
+		 * @returns {lm.controls.BrowserPopout}
+		 */
+        popout: function () {
+            var browserPopout = this.layoutManager.createPopout(this);
+            this.emitBubblingEvent('stateChanged');
+            return browserPopout;
+        },
+
+		/**
+		 * Maximises the Item or minimises it if it is already maximised
+		 *
+		 * @returns {void}
+		 */
+        toggleMaximise: function (e) {
+            e && e.preventDefault();
+            if (this.isMaximised === true) {
+                this.layoutManager._$minimiseItem(this);
+            } else {
+                this.layoutManager._$maximiseItem(this);
+            }
+
+            this.isMaximised = !this.isMaximised;
+            this.emitBubblingEvent('stateChanged');
+        },
+
+		/**
+		 * Selects the item if it is not already selected
+		 *
+		 * @returns {void}
+		 */
+        select: function () {
+            if (this.layoutManager.selectedItem !== this) {
+                this.layoutManager.selectItem(this, true);
+                this.element.addClass('lm_selected');
+            }
+        },
+
+		/**
+		 * De-selects the item if it is selected
+		 *
+		 * @returns {void}
+		 */
+        deselect: function () {
+            if (this.layoutManager.selectedItem === this) {
+                this.layoutManager.selectedItem = null;
+                this.element.removeClass('lm_selected');
+            }
+        },
+
+		/**
+		 * Set this component's title
+		 *
+		 * @public
+		 * @param {String} title
+		 *
+		 * @returns {void}
+		 */
+        setTitle: function (title) {
+            this.config.title = title;
+            this.emit('titleChanged', title);
+            this.emit('stateChanged');
+        },
+
+		/**
+		 * Checks whether a provided id is present
+		 *
+		 * @public
+		 * @param   {String}  id
+		 *
+		 * @returns {Boolean} isPresent
+		 */
+        hasId: function (id) {
+            if (!this.config.id) {
+                return false;
+            } else if (typeof this.config.id === 'string') {
+                return this.config.id === id;
+            } else if (this.config.id instanceof Array) {
+                return lm.utils.indexOf(id, this.config.id) !== -1;
+            }
+        },
+
+		/**
+		 * Adds an id. Adds it as a string if the component doesn't
+		 * have an id yet or creates/uses an array
+		 *
+		 * @public
+		 * @param {String} id
+		 *
+		 * @returns {void}
+		 */
+        addId: function (id) {
+            if (this.hasId(id)) {
+                return;
+            }
+
+            if (!this.config.id) {
+                this.config.id = id;
+            } else if (typeof this.config.id === 'string') {
+                this.config.id = [this.config.id, id];
+            } else if (this.config.id instanceof Array) {
+                this.config.id.push(id);
+            }
+        },
+
+		/**
+		 * Removes an existing id. Throws an error
+		 * if the id is not present
+		 *
+		 * @public
+		 * @param   {String} id
+		 *
+		 * @returns {void}
+		 */
+        removeId: function (id) {
+            if (!this.hasId(id)) {
+                throw new Error('Id not found');
+            }
+
+            if (typeof this.config.id === 'string') {
+                delete this.config.id;
+            } else if (this.config.id instanceof Array) {
+                var index = lm.utils.indexOf(id, this.config.id);
+                this.config.id.splice(index, 1);
+            }
+        },
+
+		/****************************************
+		 * SELECTOR
+		 ****************************************/
+        getItemsByFilter: function (filter) {
+            var result = [],
+                next = function (contentItem) {
+                    for (var i = 0; i < contentItem.contentItems.length; i++) {
+
+                        if (filter(contentItem.contentItems[i]) === true) {
+                            result.push(contentItem.contentItems[i]);
+                        }
+
+                        next(contentItem.contentItems[i]);
+                    }
+                };
+
+            next(this);
+            return result;
+        },
+
+        getItemsById: function (id) {
+            return this.getItemsByFilter(function (item) {
+                if (item.config.id instanceof Array) {
+                    return lm.utils.indexOf(id, item.config.id) !== -1;
+                } else {
+                    return item.config.id === id;
+                }
+            });
+        },
+
+        getItemsByType: function (type) {
+            return this._$getItemsByProperty('type', type);
+        },
+
+        getComponentsByName: function (componentName) {
+            var components = this._$getItemsByProperty('componentName', componentName),
+                instances = [],
+                i;
+
+            for (i = 0; i < components.length; i++) {
+                instances.push(components[i].instance);
+            }
+
+            return instances;
+        },
+
+		/****************************************
+		 * PACKAGE PRIVATE
+		 ****************************************/
+        _$getItemsByProperty: function (key, value) {
+            return this.getItemsByFilter(function (item) {
+                return item[key] === value;
+            });
+        },
+
+        _$setParent: function (parent) {
+            this.parent = parent;
+        },
+
+        _$highlightDropZone: function (x, y, area) {
+            this.layoutManager.dropTargetIndicator.highlightArea(area);
+        },
+
+        _$onDrop: function (contentItem) {
+            this.addChild(contentItem);
+        },
+
+        _$hide: function () {
+            this._callOnActiveComponents('hide');
+            this.element.hide();
+            this.layoutManager.updateSize();
+        },
+
+        _$show: function () {
+            this._callOnActiveComponents('show');
+            this.element.show();
+            this.layoutManager.updateSize();
+        },
+
+        _callOnActiveComponents: function (methodName) {
+            var stacks = this.getItemsByType('stack'),
+                activeContentItem,
+                i;
+
+            for (i = 0; i < stacks.length; i++) {
+                activeContentItem = stacks[i].getActiveContentItem();
+
+                if (activeContentItem && activeContentItem.isComponent) {
+                    activeContentItem.container[methodName]();
+                }
+            }
+        },
+
+		/**
+		 * Destroys this item ands its children
+		 *
+		 * @returns {void}
+		 */
+        _$destroy: function () {
+            this.emitBubblingEvent('beforeItemDestroyed');
+            this.callDownwards('_$destroy', [], true, true);
+            this.element.remove();
+            this.emitBubblingEvent('itemDestroyed');
+        },
+
+		/**
+		 * Returns the area the component currently occupies in the format
+		 *
+		 * {
+		 *		x1: int
+		 *		xy: int
+		 *		y1: int
+		 *		y2: int
+		 *		contentItem: contentItem
+		 * }
+		 */
+        _$getArea: function (element) {
+            element = element || this.element;
+
+            var offset = element.offset(),
+                width = element.width(),
+                height = element.height();
+
+            return {
+                x1: offset.left,
+                y1: offset.top,
+                x2: offset.left + width,
+                y2: offset.top + height,
+                surface: width * height,
+                contentItem: this
+            };
+        },
+
+		/**
+		 * The tree of content items is created in two steps: First all content items are instantiated,
+		 * then init is called recursively from top to bottem. This is the basic init function,
+		 * it can be used, extended or overwritten by the content items
+		 *
+		 * Its behaviour depends on the content item
+		 *
+		 * @package private
+		 *
+		 * @returns {void}
+		 */
+        _$init: function () {
+            var i;
+            this.setSize();
+
+            for (i = 0; i < this.contentItems.length; i++) {
+                this.childElementContainer.append(this.contentItems[i].element);
+            }
+
+            this.isInitialised = true;
+            this.emitBubblingEvent('itemCreated');
+            this.emitBubblingEvent(this.type + 'Created');
+        },
+
+		/**
+		 * Emit an event that bubbles up the item tree.
+		 *
+		 * @param   {String} name The name of the event
+		 *
+		 * @returns {void}
+		 */
+        emitBubblingEvent: function (name) {
+            var event = new lm.utils.BubblingEvent(name, this);
+            this.emit(name, event);
+        },
+
+		/**
+		 * Private method, creates all content items for this node at initialisation time
+		 * PLEASE NOTE, please see addChild for adding contentItems add runtime
+		 * @private
+		 * @param   {configuration item node} config
+		 *
+		 * @returns {void}
+		 */
+        _createContentItems: function (config) {
+            var oContentItem, i;
+
+            if (!(config.content instanceof Array)) {
+                throw new lm.errors.ConfigurationError('content must be an Array', config);
+            }
+
+            for (i = 0; i < config.content.length; i++) {
+                oContentItem = this.layoutManager.createContentItem(config.content[i], this);
+                this.contentItems.push(oContentItem);
+            }
+        },
+
+		/**
+		 * Extends an item configuration node with default settings
+		 * @private
+		 * @param   {configuration item node} config
+		 *
+		 * @returns {configuration item node} extended config
+		 */
+        _extendItemNode: function (config) {
+
+            for (var key in lm.config.itemDefaultConfig) {
+                if (config[key] === undefined) {
+                    config[key] = lm.config.itemDefaultConfig[key];
+                }
+            }
+
+            return config;
+        },
+
+		/**
+		 * Called for every event on the item tree. Decides whether the event is a bubbling
+		 * event and propagates it to its parent
+		 *
+		 * @param    {String} name the name of the event
+		 * @param   {lm.utils.BubblingEvent} event
+		 *
+		 * @returns {void}
+		 */
+        _propagateEvent: function (name, event) {
+            if (event instanceof lm.utils.BubblingEvent &&
+                event.isPropagationStopped === false &&
+                this.isInitialised === true) {
+
+				/**
+				 * In some cases (e.g. if an element is created from a DragSource) it
+				 * doesn't have a parent and is not below root. If that's the case
+				 * propagate the bubbling event from the top level of the substree directly
+				 * to the layoutManager
+				 */
+                if (this.isRoot === false && this.parent) {
+                    this.parent.emit.apply(this.parent, Array.prototype.slice.call(arguments, 0));
+                } else {
+                    this._scheduleEventPropagationToLayoutManager(name, event);
+                }
+            }
+        },
+
+		/**
+		 * All raw events bubble up to the root element. Some events that
+		 * are propagated to - and emitted by - the layoutManager however are
+		 * only string-based, batched and sanitized to make them more usable
+		 *
+		 * @param {String} name the name of the event
+		 *
+		 * @private
+		 * @returns {void}
+		 */
+        _scheduleEventPropagationToLayoutManager: function (name, event) {
+            if (lm.utils.indexOf(name, this._throttledEvents) === -1) {
+                this.layoutManager.emit(name, event.origin);
+            } else {
+                if (this._pendingEventPropagations[name] !== true) {
+                    this._pendingEventPropagations[name] = true;
+                    lm.utils.animFrame(lm.utils.fnBind(this._propagateEventToLayoutManager, this, [name, event]));
+                }
+            }
+
+        },
+
+		/**
+		 * Callback for events scheduled by _scheduleEventPropagationToLayoutManager
+		 *
+		 * @param {String} name the name of the event
+		 *
+		 * @private
+		 * @returns {void}
+		 */
+        _propagateEventToLayoutManager: function (name, event) {
+            this._pendingEventPropagations[name] = false;
+            this.layoutManager.emit(name, event);
+        }
+    });
+
+	/**
+	 * @param {[type]} layoutManager [description]
+	 * @param {[type]} config      [description]
+	 * @param {[type]} parent        [description]
+	 */
+    lm.items.Component = function (layoutManager, config, parent) {
+        lm.items.AbstractContentItem.call(this, layoutManager, config, parent);
+
+        var ComponentConstructor = layoutManager.getComponent(this.config.componentName),
+            componentConfig = $.extend(true, {}, this.config.componentState || {});
+
+        componentConfig.componentName = this.config.componentName;
+        this.componentName = this.config.componentName;
+
+        if (this.config.title === '') {
+            this.config.title = this.config.componentName;
+        }
+
+        this.isComponent = true;
+        this.container = new lm.container.ItemContainer(this.config, this, layoutManager);
+        this.instance = new ComponentConstructor(this.container, componentConfig);
+        this.element = this.container._element;
+    };
+
+    lm.utils.extend(lm.items.Component, lm.items.AbstractContentItem);
+
+    lm.utils.copy(lm.items.Component.prototype, {
+
+        close: function () {
+            this.parent.removeChild(this);
+        },
+
+        setSize: function () {
+            if (this.element.is(':visible')) {
+                // Do not update size of hidden components to prevent unwanted reflows
+                this.container._$setSize(this.element.width(), this.element.height());
+            }
+        },
+
+        _$init: function () {
+            lm.items.AbstractContentItem.prototype._$init.call(this);
+            this.container.emit('open');
+        },
+
+        _$hide: function () {
+            this.container.hide();
+            lm.items.AbstractContentItem.prototype._$hide.call(this);
+        },
+
+        _$show: function () {
+            this.container.show();
+            lm.items.AbstractContentItem.prototype._$show.call(this);
+        },
+
+        _$shown: function () {
+            this.container.shown();
+            lm.items.AbstractContentItem.prototype._$shown.call(this);
+        },
+
+        _$destroy: function () {
+            this.container.emit('destroy', this);
+            lm.items.AbstractContentItem.prototype._$destroy.call(this);
+        },
+
+		/**
+		 * Dragging onto a component directly is not an option
+		 *
+		 * @returns null
+		 */
+        _$getArea: function () {
+            return null;
+        }
+    });
+
+    lm.items.Root = function (layoutManager, config, containerElement) {
+        lm.items.AbstractContentItem.call(this, layoutManager, config, null);
+        this.isRoot = true;
+        this.type = 'root';
+        this.element = $('<div class="lm_goldenlayout lm_item lm_root"></div>');
+        this.childElementContainer = this.element;
+        this._containerElement = containerElement;
+        this._containerElement.append(this.element);
+    };
+
+    lm.utils.extend(lm.items.Root, lm.items.AbstractContentItem);
+
+    lm.utils.copy(lm.items.Root.prototype, {
+        addChild: function (contentItem) {
+            if (this.contentItems.length > 0) {
+                throw new Error('Root node can only have a single child');
+            }
+
+            contentItem = this.layoutManager._$normalizeContentItem(contentItem, this);
+            this.childElementContainer.append(contentItem.element);
+            lm.items.AbstractContentItem.prototype.addChild.call(this, contentItem);
+
+            this.callDownwards('setSize');
+            this.emitBubblingEvent('stateChanged');
+        },
+
+        setSize: function (width, height) {
+            width = (typeof width === 'undefined') ? this._containerElement.width() : width;
+            height = (typeof height === 'undefined') ? this._containerElement.height() : height;
+
+            this.element.width(width);
+            this.element.height(height);
+
+			/*
+			 * Root can be empty
+			 */
+            if (this.contentItems[0]) {
+                this.contentItems[0].element.width(width);
+                this.contentItems[0].element.height(height);
+            }
+        },
+        _$highlightDropZone: function (x, y, area) {
+            this.layoutManager.tabDropPlaceholder.remove();
+            lm.items.AbstractContentItem.prototype._$highlightDropZone.apply(this, arguments);
+        },
+
+        _$onDrop: function (contentItem, area) {
+            var stack;
+
+            if (contentItem.isComponent) {
+                stack = this.layoutManager.createContentItem({
+                    type: 'stack',
+                    header: contentItem.config.header || {}
+                }, this);
+                stack._$init();
+                stack.addChild(contentItem);
+                contentItem = stack;
+            }
+
+            if (!this.contentItems.length) {
+                this.addChild(contentItem);
+            } else {
+                var type = area.side[0] == 'x' ? 'row' : 'column';
+                var dimension = area.side[0] == 'x' ? 'width' : 'height';
+                var insertBefore = area.side[1] == '2';
+                var column = this.contentItems[0];
+                if (!column instanceof lm.items.RowOrColumn || column.type != type) {
+                    var rowOrColumn = this.layoutManager.createContentItem({ type: type }, this);
+                    this.replaceChild(column, rowOrColumn);
+                    rowOrColumn.addChild(contentItem, insertBefore ? 0 : undefined, true);
+                    rowOrColumn.addChild(column, insertBefore ? undefined : 0, true);
+                    column.config[dimension] = 50;
+                    contentItem.config[dimension] = 50;
+                    rowOrColumn.callDownwards('setSize');
+                } else {
+                    var sibbling = column.contentItems[insertBefore ? 0 : column.contentItems.length - 1]
+                    column.addChild(contentItem, insertBefore ? 0 : undefined, true);
+                    sibbling.config[dimension] *= 0.5;
+                    contentItem.config[dimension] = sibbling.config[dimension];
+                    column.callDownwards('setSize');
+                }
+            }
+        }
+    });
+
+
+
+    lm.items.RowOrColumn = function (isColumn, layoutManager, config, parent) {
+        lm.items.AbstractContentItem.call(this, layoutManager, config, parent);
+
+        this.isRow = !isColumn;
+        this.isColumn = isColumn;
+
+        this.element = $('<div class="lm_item lm_' + (isColumn ? 'column' : 'row') + '"></div>');
+        this.childElementContainer = this.element;
+        this._splitterSize = layoutManager.config.dimensions.borderWidth;
+        this._splitterGrabSize = layoutManager.config.dimensions.borderGrabWidth;
+        this._isColumn = isColumn;
+        this._dimension = isColumn ? 'height' : 'width';
+        this._splitter = [];
+        this._splitterPosition = null;
+        this._splitterMinPosition = null;
+        this._splitterMaxPosition = null;
+    };
+
+    lm.utils.extend(lm.items.RowOrColumn, lm.items.AbstractContentItem);
+
+    lm.utils.copy(lm.items.RowOrColumn.prototype, {
+
+		/**
+		 * Add a new contentItem to the Row or Column
+		 *
+		 * @param {lm.item.AbstractContentItem} contentItem
+		 * @param {[int]} index The position of the new item within the Row or Column.
+		 *                      If no index is provided the item will be added to the end
+		 * @param {[bool]} _$suspendResize If true the items won't be resized. This will leave the item in
+		 *                                 an inconsistent state and is only intended to be used if multiple
+		 *                                 children need to be added in one go and resize is called afterwards
+		 *
+		 * @returns {void}
+		 */
+        addChild: function (contentItem, index, _$suspendResize) {
+
+            var newItemSize, itemSize, i, splitterElement;
+
+            contentItem = this.layoutManager._$normalizeContentItem(contentItem, this);
+
+            if (index === undefined) {
+                index = this.contentItems.length;
+            }
+
+            if (this.contentItems.length > 0) {
+                splitterElement = this._createSplitter(Math.max(0, index - 1)).element;
+
+                if (index > 0) {
+                    this.contentItems[index - 1].element.after(splitterElement);
+                    splitterElement.after(contentItem.element);
+                } else {
+                    this.contentItems[0].element.before(splitterElement);
+                    splitterElement.before(contentItem.element);
+                }
+            } else {
+                this.childElementContainer.append(contentItem.element);
+            }
+
+            lm.items.AbstractContentItem.prototype.addChild.call(this, contentItem, index);
+
+            let fixedItemSize = 0;
+            let variableItemCount = 0;
+            for (i = 0; i < this.contentItems.length; i++) {
+                if (this.contentItems[i].config.fixed)
+                    fixedItemSize += this.contentItems[i].config[this._dimension];
+                else variableItemCount++;
+            }
+
+            newItemSize = (1 / variableItemCount) * (100 - fixedItemSize);
+
+            if (_$suspendResize === true) {
+                this.emitBubblingEvent('stateChanged');
+                return;
+            }
+
+            for (i = 0; i < this.contentItems.length; i++) {
+                if (this.contentItems[i].config.fixed)
+                    ;
+                else if (this.contentItems[i] === contentItem) {
+                    contentItem.config[this._dimension] = newItemSize;
+                } else {
+                    itemSize = this.contentItems[i].config[this._dimension] *= (100 - newItemSize - fixedItemSize) / (100 - fixedItemSize);
+                    this.contentItems[i].config[this._dimension] = itemSize;
+                }
+            }
+
+            this.callDownwards('setSize');
+            this.emitBubblingEvent('stateChanged');
+
+        },
+
+		/**
+		 * Removes a child of this element
+		 *
+		 * @param   {lm.items.AbstractContentItem} contentItem
+		 * @param   {boolean} keepChild   If true the child will be removed, but not destroyed
+		 *
+		 * @returns {void}
+		 */
+        removeChild: function (contentItem, keepChild) {
+            var removedItemSize = contentItem.config[this._dimension],
+                index = lm.utils.indexOf(contentItem, this.contentItems),
+                splitterIndex = Math.max(index - 1, 0),
+                i,
+                childItem;
+
+            if (index === -1) {
+                throw new Error('Can\'t remove child. ContentItem is not child of this Row or Column');
+            }
+
+			/**
+			 * Remove the splitter before the item or after if the item happens
+			 * to be the first in the row/column
+			 */
+            if (this._splitter[splitterIndex]) {
+                this._splitter[splitterIndex]._$destroy();
+                this._splitter.splice(splitterIndex, 1);
+            }
+
+            let fixedItemSize = 0;
+            for (i = 0; i < this.contentItems.length; i++) {
+                if (this.contentItems[i].config.fixed)
+                    fixedItemSize += this.contentItems[i].config[this._dimension];
+            }
+			/**
+			 * Allocate the space that the removed item occupied to the remaining items
+			 */
+            for (i = 0; i < this.contentItems.length; i++) {
+                if (this.contentItems[i].config.fixed)
+                    ;
+                else if (this.contentItems[i] !== contentItem) {
+                    this.contentItems[i].config[this._dimension] *= (100 - fixedItemSize) / (100 - removedItemSize - fixedItemSize);
+                }
+            }
+
+            lm.items.AbstractContentItem.prototype.removeChild.call(this, contentItem, keepChild);
+
+            if (this.contentItems.length === 1 && this.config.isClosable === true) {
+                childItem = this.contentItems[0];
+                this.contentItems = [];
+                this.parent.replaceChild(this, childItem, true);
+            } else {
+                this.callDownwards('setSize');
+                this.emitBubblingEvent('stateChanged');
+            }
+        },
+
+		/**
+		 * Replaces a child of this Row or Column with another contentItem
+		 *
+		 * @param   {lm.items.AbstractContentItem} oldChild
+		 * @param   {lm.items.AbstractContentItem} newChild
+		 *
+		 * @returns {void}
+		 */
+        replaceChild: function (oldChild, newChild) {
+            var size = oldChild.config[this._dimension];
+            lm.items.AbstractContentItem.prototype.replaceChild.call(this, oldChild, newChild);
+            newChild.config[this._dimension] = size;
+            this.callDownwards('setSize');
+            this.emitBubblingEvent('stateChanged');
+        },
+
+		/**
+		 * Called whenever the dimensions of this item or one of its parents change
+		 *
+		 * @returns {void}
+		 */
+        setSize: function () {
+            if (this.contentItems.length > 0) {
+                this._calculateRelativeSizes();
+                this._setAbsoluteSizes();
+            }
+            this.emitBubblingEvent('stateChanged');
+            this.emit('resize');
+        },
+
+		/**
+		 * Invoked recursively by the layout manager. AbstractContentItem.init appends
+		 * the contentItem's DOM elements to the container, RowOrColumn init adds splitters
+		 * in between them
+		 *
+		 * @package private
+		 * @override AbstractContentItem._$init
+		 * @returns {void}
+		 */
+        _$init: function () {
+            if (this.isInitialised === true) return;
+
+            var i;
+
+            lm.items.AbstractContentItem.prototype._$init.call(this);
+
+            for (i = 0; i < this.contentItems.length - 1; i++) {
+                this.contentItems[i].element.after(this._createSplitter(i).element);
+            }
+        },
+
+		/**
+		 * Turns the relative sizes calculated by _calculateRelativeSizes into
+		 * absolute pixel values and applies them to the children's DOM elements
+		 *
+		 * Assigns additional pixels to counteract Math.floor
+		 *
+		 * @private
+		 * @returns {void}
+		 */
+        _setAbsoluteSizes: function () {
+            var i,
+                sizeData = this._calculateAbsoluteSizes();
+
+            for (i = 0; i < this.contentItems.length; i++) {
+                if (sizeData.additionalPixel - i > 0) {
+                    sizeData.itemSizes[i]++;
+                }
+
+                if (this._isColumn) {
+                    this.contentItems[i].element.width(sizeData.totalWidth);
+                    this.contentItems[i].element.height(sizeData.itemSizes[i]);
+                } else {
+                    this.contentItems[i].element.width(sizeData.itemSizes[i]);
+                    this.contentItems[i].element.height(sizeData.totalHeight);
+                }
+            }
+        },
+
+		/**
+		 * Calculates the absolute sizes of all of the children of this Item.
+		 * @returns {object} - Set with absolute sizes and additional pixels.
+		 */
+        _calculateAbsoluteSizes: function () {
+            var i,
+                totalSplitterSize = (this.contentItems.length - 1) * this._splitterSize,
+                totalWidth = this.element.width(),
+                totalHeight = this.element.height(),
+                totalAssigned = 0,
+                additionalPixel,
+                itemSize,
+                itemSizes = [];
+
+            if (this._isColumn) {
+                totalHeight -= totalSplitterSize;
+            } else {
+                totalWidth -= totalSplitterSize;
+            }
+
+            for (i = 0; i < this.contentItems.length; i++) {
+                if (this._isColumn) {
+                    itemSize = Math.floor(totalHeight * (this.contentItems[i].config.height / 100));
+                } else {
+                    itemSize = Math.floor(totalWidth * (this.contentItems[i].config.width / 100));
+                }
+
+                totalAssigned += itemSize;
+                itemSizes.push(itemSize);
+            }
+
+            additionalPixel = Math.floor((this._isColumn ? totalHeight : totalWidth) - totalAssigned);
+
+            return {
+                itemSizes: itemSizes,
+                additionalPixel: additionalPixel,
+                totalWidth: totalWidth,
+                totalHeight: totalHeight
+            };
+        },
+
+		/**
+		 * Calculates the relative sizes of all children of this Item. The logic
+		 * is as follows:
+		 *
+		 * - Add up the total size of all items that have a configured size
+		 *
+		 * - If the total == 100 (check for floating point errors)
+		 *        Excellent, job done
+		 *
+		 * - If the total is > 100,
+		 *        set the size of items without set dimensions to 1/3 and add this to the total
+		 *        set the size off all items so that the total is hundred relative to their original size
+		 *
+		 * - If the total is < 100
+		 *        If there are items without set dimensions, distribute the remainder to 100 evenly between them
+		 *        If there are no items without set dimensions, increase all items sizes relative to
+		 *        their original size so that they add up to 100
+		 *
+		 * @private
+		 * @returns {void}
+		 */
+        _calculateRelativeSizes: function () {
+
+            var i,
+                total = 0,
+                itemsWithoutSetDimension = [],
+                dimension = this._isColumn ? 'height' : 'width';
+
+            for (i = 0; i < this.contentItems.length; i++) {
+                if (this.contentItems[i].config[dimension] !== undefined) {
+                    total += this.contentItems[i].config[dimension];
+                } else {
+                    itemsWithoutSetDimension.push(this.contentItems[i]);
+                }
+            }
+
+			/**
+			 * Everything adds up to hundred, all good :-)
+			 */
+            if (Math.round(total) === 100) {
+                this._respectMinItemWidth();
+                return;
+            }
+
+			/**
+			 * Allocate the remaining size to the items without a set dimension
+			 */
+            if (Math.round(total) < 100 && itemsWithoutSetDimension.length > 0) {
+                for (i = 0; i < itemsWithoutSetDimension.length; i++) {
+                    itemsWithoutSetDimension[i].config[dimension] = (100 - total) / itemsWithoutSetDimension.length;
+                }
+                this._respectMinItemWidth();
+                return;
+            }
+
+			/**
+			 * If the total is > 100, but there are also items without a set dimension left, assing 50
+			 * as their dimension and add it to the total
+			 *
+			 * This will be reset in the next step
+			 */
+            if (Math.round(total) > 100) {
+                for (i = 0; i < itemsWithoutSetDimension.length; i++) {
+                    itemsWithoutSetDimension[i].config[dimension] = 50;
+                    total += 50;
+                }
+            }
+
+			/**
+			 * Set every items size relative to 100 relative to its size to total
+			 */
+            for (i = 0; i < this.contentItems.length; i++) {
+                this.contentItems[i].config[dimension] = (this.contentItems[i].config[dimension] / total) * 100;
+            }
+
+            this._respectMinItemWidth();
+        },
+
+		/**
+		 * Adjusts the column widths to respect the dimensions minItemWidth if set.
+		 * @returns {}
+		 */
+        _respectMinItemWidth: function () {
+            var minItemWidth = this.layoutManager.config.dimensions ? (this.layoutManager.config.dimensions.minItemWidth || 0) : 0,
+                sizeData = null,
+                entriesOverMin = [],
+                totalOverMin = 0,
+                totalUnderMin = 0,
+                remainingWidth = 0,
+                itemSize = 0,
+                contentItem = null,
+                reducePercent,
+                reducedWidth,
+                allEntries = [],
+                entry;
+
+            if (this._isColumn || !minItemWidth || this.contentItems.length <= 1) {
+                return;
+            }
+
+            sizeData = this._calculateAbsoluteSizes();
+
+			/**
+			 * Figure out how much we are under the min item size total and how much room we have to use.
+			 */
+            for (var i = 0; i < this.contentItems.length; i++) {
+
+                contentItem = this.contentItems[i];
+                itemSize = sizeData.itemSizes[i];
+
+                if (itemSize < minItemWidth) {
+                    totalUnderMin += minItemWidth - itemSize;
+                    entry = { width: minItemWidth };
+
+                }
+                else {
+                    totalOverMin += itemSize - minItemWidth;
+                    entry = { width: itemSize };
+                    entriesOverMin.push(entry);
+                }
+
+                allEntries.push(entry);
+            }
+
+			/**
+			 * If there is nothing under min, or there is not enough over to make up the difference, do nothing.
+			 */
+            if (totalUnderMin === 0 || totalUnderMin > totalOverMin) {
+                return;
+            }
+
+			/**
+			 * Evenly reduce all columns that are over the min item width to make up the difference.
+			 */
+            reducePercent = totalUnderMin / totalOverMin;
+            remainingWidth = totalUnderMin;
+            for (i = 0; i < entriesOverMin.length; i++) {
+                entry = entriesOverMin[i];
+                reducedWidth = Math.round((entry.width - minItemWidth) * reducePercent);
+                remainingWidth -= reducedWidth;
+                entry.width -= reducedWidth;
+            }
+
+			/**
+			 * Take anything remaining from the last item.
+			 */
+            if (remainingWidth !== 0) {
+                allEntries[allEntries.length - 1].width -= remainingWidth;
+            }
+
+			/**
+			 * Set every items size relative to 100 relative to its size to total
+			 */
+            for (i = 0; i < this.contentItems.length; i++) {
+                this.contentItems[i].config.width = (allEntries[i].width / sizeData.totalWidth) * 100;
+            }
+        },
+
+		/**
+		 * Instantiates a new lm.controls.Splitter, binds events to it and adds
+		 * it to the array of splitters at the position specified as the index argument
+		 *
+		 * What it doesn't do though is append the splitter to the DOM
+		 *
+		 * @param   {Int} index The position of the splitter
+		 *
+		 * @returns {lm.controls.Splitter}
+		 */
+        _createSplitter: function (index) {
+            var splitter;
+            splitter = new lm.controls.Splitter(this._isColumn, this._splitterSize, this._splitterGrabSize);
+            splitter.on('drag', lm.utils.fnBind(this._onSplitterDrag, this, [splitter]), this);
+            splitter.on('dragStop', lm.utils.fnBind(this._onSplitterDragStop, this, [splitter]), this);
+            splitter.on('dragStart', lm.utils.fnBind(this._onSplitterDragStart, this, [splitter]), this);
+            this._splitter.splice(index, 0, splitter);
+            return splitter;
+        },
+
+		/**
+		 * Locates the instance of lm.controls.Splitter in the array of
+		 * registered splitters and returns a map containing the contentItem
+		 * before and after the splitters, both of which are affected if the
+		 * splitter is moved
+		 *
+		 * @param   {lm.controls.Splitter} splitter
+		 *
+		 * @returns {Object} A map of contentItems that the splitter affects
+		 */
+        _getItemsForSplitter: function (splitter) {
+            var index = lm.utils.indexOf(splitter, this._splitter);
+
+            return {
+                before: this.contentItems[index],
+                after: this.contentItems[index + 1]
+            };
+        },
+
+		/**
+		 * Gets the minimum dimensions for the given item configuration array
+		 * @param item
+		 * @private
+		 */
+        _getMinimumDimensions: function (arr) {
+            var minWidth = 0, minHeight = 0;
+
+            for (var i = 0; i < arr.length; ++i) {
+                minWidth = Math.max(arr[i].minWidth || 0, minWidth);
+                minHeight = Math.max(arr[i].minHeight || 0, minHeight);
+            }
+
+            return { horizontal: minWidth, vertical: minHeight };
+        },
+
+		/**
+		 * Invoked when a splitter's dragListener fires dragStart. Calculates the splitters
+		 * movement area once (so that it doesn't need calculating on every mousemove event)
+		 *
+		 * @param   {lm.controls.Splitter} splitter
+		 *
+		 * @returns {void}
+		 */
+        _onSplitterDragStart: function (splitter) {
+            var items = this._getItemsForSplitter(splitter),
+                minSize = this.layoutManager.config.dimensions[this._isColumn ? 'minItemHeight' : 'minItemWidth'];
+
+            var beforeMinDim = this._getMinimumDimensions(items.before.config.content);
+            var beforeMinSize = this._isColumn ? beforeMinDim.vertical : beforeMinDim.horizontal;
+
+            var afterMinDim = this._getMinimumDimensions(items.after.config.content);
+            var afterMinSize = this._isColumn ? afterMinDim.vertical : afterMinDim.horizontal;
+
+            this._splitterPosition = 0;
+            this._splitterMinPosition = -1 * (items.before.element[this._dimension]() - (beforeMinSize || minSize));
+            this._splitterMaxPosition = items.after.element[this._dimension]() - (afterMinSize || minSize);
+        },
+
+		/**
+		 * Invoked when a splitter's DragListener fires drag. Updates the splitters DOM position,
+		 * but not the sizes of the elements the splitter controls in order to minimize resize events
+		 *
+		 * @param   {lm.controls.Splitter} splitter
+		 * @param   {Int} offsetX  Relative pixel values to the splitters original position. Can be negative
+		 * @param   {Int} offsetY  Relative pixel values to the splitters original position. Can be negative
+		 *
+		 * @returns {void}
+		 */
+        _onSplitterDrag: function (splitter, offsetX, offsetY) {
+            var offset = this._isColumn ? offsetY : offsetX;
+
+            if (offset > this._splitterMinPosition && offset < this._splitterMaxPosition) {
+                this._splitterPosition = offset;
+                splitter.element.css(this._isColumn ? 'top' : 'left', offset);
+            }
+        },
+
+		/**
+		 * Invoked when a splitter's DragListener fires dragStop. Resets the splitters DOM position,
+		 * and applies the new sizes to the elements before and after the splitter and their children
+		 * on the next animation frame
+		 *
+		 * @param   {lm.controls.Splitter} splitter
+		 *
+		 * @returns {void}
+		 */
+        _onSplitterDragStop: function (splitter) {
+
+            var items = this._getItemsForSplitter(splitter),
+                sizeBefore = items.before.element[this._dimension](),
+                sizeAfter = items.after.element[this._dimension](),
+                splitterPositionInRange = (this._splitterPosition + sizeBefore) / (sizeBefore + sizeAfter),
+                totalRelativeSize = items.before.config[this._dimension] + items.after.config[this._dimension];
+
+            items.before.config[this._dimension] = splitterPositionInRange * totalRelativeSize;
+            items.after.config[this._dimension] = (1 - splitterPositionInRange) * totalRelativeSize;
+
+            splitter.element.css({
+                'top': 0,
+                'left': 0
+            });
+
+            lm.utils.animFrame(lm.utils.fnBind(this.callDownwards, this, ['setSize']));
+        }
+    });
+
+    lm.items.Stack = function (layoutManager, config, parent) {
+        lm.items.AbstractContentItem.call(this, layoutManager, config, parent);
+
+        this.element = $('<div class="lm_item lm_stack"></div>');
+        this._activeContentItem = null;
+        var cfg = layoutManager.config;
+        this._header = { // defaults' reconstruction from old configuration style
+            show: cfg.settings.hasHeaders === true && config.hasHeaders !== false,
+            popout: cfg.settings.showPopoutIcon && cfg.labels.popout,
+            maximise: cfg.settings.showMaximiseIcon && cfg.labels.maximise,
+            close: cfg.settings.showCloseIcon && cfg.labels.close,
+            minimise: cfg.labels.minimise,
+        };
+        if (cfg.header) // load simplified version of header configuration (https://github.com/deepstreamIO/golden-layout/pull/245)
+            lm.utils.copy(this._header, cfg.header);
+        if (config.header) // load from stack
+            lm.utils.copy(this._header, config.header);
+        if (config.content && config.content[0] && config.content[0].header) // load from component if stack omitted
+            lm.utils.copy(this._header, config.content[0].header);
+
+        this._dropZones = {};
+        this._dropSegment = null;
+        this._contentAreaDimensions = null;
+        this._dropIndex = null;
+
+        this.isStack = true;
+
+        this.childElementContainer = $('<div class="lm_items"></div>');
+        this.header = new lm.controls.Header(layoutManager, this);
+
+        this.element.append(this.header.element);
+        this.element.append(this.childElementContainer);
+        this._setupHeaderPosition();
+        this._$validateClosability();
+    };
+
+    lm.utils.extend(lm.items.Stack, lm.items.AbstractContentItem);
+
+    lm.utils.copy(lm.items.Stack.prototype, {
+
+        setSize: function () {
+            var i,
+                headerSize = this._header.show ? this.layoutManager.config.dimensions.headerHeight : 0,
+                contentWidth = this.element.width() - (this._sided ? headerSize : 0),
+                contentHeight = this.element.height() - (!this._sided ? headerSize : 0);
+
+            this.childElementContainer.width(contentWidth);
+            this.childElementContainer.height(contentHeight);
+
+            for (i = 0; i < this.contentItems.length; i++) {
+                this.contentItems[i].element.width(contentWidth).height(contentHeight);
+            }
+            this.emit('resize');
+            this.emitBubblingEvent('stateChanged');
+        },
+
+        _$init: function () {
+            var i, initialItem;
+
+            if (this.isInitialised === true) return;
+
+            lm.items.AbstractContentItem.prototype._$init.call(this);
+
+            for (i = 0; i < this.contentItems.length; i++) {
+                this.header.createTab(this.contentItems[i]);
+                this.contentItems[i]._$hide();
+            }
+
+            if (this.contentItems.length > 0) {
+                initialItem = this.contentItems[this.config.activeItemIndex || 0];
+
+                if (!initialItem) {
+                    throw new Error('Configured activeItemIndex out of bounds');
+                }
+
+                this.setActiveContentItem(initialItem);
+            }
+        },
+
+        setActiveContentItem: function (contentItem) {
+            if (lm.utils.indexOf(contentItem, this.contentItems) === -1) {
+                throw new Error('contentItem is not a child of this stack');
+            }
+
+            if (this._activeContentItem !== null) {
+                this._activeContentItem._$hide();
+            }
+
+            this._activeContentItem = contentItem;
+            this.header.setActiveContentItem(contentItem);
+            contentItem._$show();
+            this.emit('activeContentItemChanged', contentItem);
+            this.layoutManager.emit('activeContentItemChanged', contentItem);
+            this.emitBubblingEvent('stateChanged');
+        },
+
+        getActiveContentItem: function () {
+            return this.header.activeContentItem;
+        },
+
+        addChild: function (contentItem, index) {
+            contentItem = this.layoutManager._$normalizeContentItem(contentItem, this);
+            lm.items.AbstractContentItem.prototype.addChild.call(this, contentItem, index);
+            this.childElementContainer.append(contentItem.element);
+            this.header.createTab(contentItem, index);
+            this.setActiveContentItem(contentItem);
+            this.callDownwards('setSize');
+            this._$validateClosability();
+            this.emitBubblingEvent('stateChanged');
+        },
+
+        removeChild: function (contentItem, keepChild) {
+            var index = lm.utils.indexOf(contentItem, this.contentItems);
+            lm.items.AbstractContentItem.prototype.removeChild.call(this, contentItem, keepChild);
+            this.header.removeTab(contentItem);
+            if (this.header.activeContentItem === contentItem) {
+                if (this.contentItems.length > 0) {
+                    this.setActiveContentItem(this.contentItems[Math.max(index - 1, 0)]);
+                } else {
+                    this._activeContentItem = null;
+                }
+            }
+
+            this._$validateClosability();
+            this.emitBubblingEvent('stateChanged');
+        },
+
+		/**
+		 * Validates that the stack is still closable or not. If a stack is able
+		 * to close, but has a non closable component added to it, the stack is no
+		 * longer closable until all components are closable.
+		 *
+		 * @returns {void}
+		 */
+        _$validateClosability: function () {
+            var contentItem,
+                isClosable,
+                len,
+                i;
+
+            isClosable = this.header._isClosable();
+
+            for (i = 0, len = this.contentItems.length; i < len; i++) {
+                if (!isClosable) {
+                    break;
+                }
+
+                isClosable = this.contentItems[i].config.isClosable;
+            }
+
+            this.header._$setClosable(isClosable);
+        },
+
+        _$destroy: function () {
+            lm.items.AbstractContentItem.prototype._$destroy.call(this);
+            this.header._$destroy();
+        },
+
+
+		/**
+		 * Ok, this one is going to be the tricky one: The user has dropped {contentItem} onto this stack.
+		 *
+		 * It was dropped on either the stacks header or the top, right, bottom or left bit of the content area
+		 * (which one of those is stored in this._dropSegment). Now, if the user has dropped on the header the case
+		 * is relatively clear: We add the item to the existing stack... job done (might be good to have
+		 * tab reordering at some point, but lets not sweat it right now)
+		 *
+		 * If the item was dropped on the content part things are a bit more complicated. If it was dropped on either the
+		 * top or bottom region we need to create a new column and place the items accordingly.
+		 * Unless, of course if the stack is already within a column... in which case we want
+		 * to add the newly created item to the existing column...
+		 * either prepend or append it, depending on wether its top or bottom.
+		 *
+		 * Same thing for rows and left / right drop segments... so in total there are 9 things that can potentially happen
+		 * (left, top, right, bottom) * is child of the right parent (row, column) + header drop
+		 *
+		 * @param    {lm.item} contentItem
+		 *
+		 * @returns {void}
+		 */
+        _$onDrop: function (contentItem) {
+
+			/*
+			 * The item was dropped on the header area. Just add it as a child of this stack and
+			 * get the hell out of this logic
+			 */
+            if (this._dropSegment === 'header') {
+                this._resetHeaderDropZone();
+                this.addChild(contentItem, this._dropIndex);
+                return;
+            }
+
+			/*
+			 * The stack is empty. Let's just add the element.
+			 */
+            if (this._dropSegment === 'body') {
+                this.addChild(contentItem);
+                return;
+            }
+
+			/*
+			 * The item was dropped on the top-, left-, bottom- or right- part of the content. Let's
+			 * aggregate some conditions to make the if statements later on more readable
+			 */
+            var isVertical = this._dropSegment === 'top' || this._dropSegment === 'bottom',
+                isHorizontal = this._dropSegment === 'left' || this._dropSegment === 'right',
+                insertBefore = this._dropSegment === 'top' || this._dropSegment === 'left',
+                hasCorrectParent = (isVertical && this.parent.isColumn) || (isHorizontal && this.parent.isRow),
+                type = isVertical ? 'column' : 'row',
+                dimension = isVertical ? 'height' : 'width',
+                index,
+                stack,
+                rowOrColumn;
+
+			/*
+			 * The content item can be either a component or a stack. If it is a component, wrap it into a stack
+			 */
+            if (contentItem.isComponent) {
+                stack = this.layoutManager.createContentItem({
+                    type: 'stack',
+                    header: contentItem.config.header || {}
+                }, this);
+                stack._$init();
+                stack.addChild(contentItem);
+                contentItem = stack;
+            }
+
+			/*
+			 * If the item is dropped on top or bottom of a column or left and right of a row, it's already
+			 * layd out in the correct way. Just add it as a child
+			 */
+            if (hasCorrectParent) {
+                index = lm.utils.indexOf(this, this.parent.contentItems);
+                this.parent.addChild(contentItem, insertBefore ? index : index + 1, true);
+                this.config[dimension] *= 0.5;
+                contentItem.config[dimension] = this.config[dimension];
+                this.parent.callDownwards('setSize');
+				/*
+				 * This handles items that are dropped on top or bottom of a row or left / right of a column. We need
+				 * to create the appropriate contentItem for them to live in
+				 */
+            } else {
+                type = isVertical ? 'column' : 'row';
+                rowOrColumn = this.layoutManager.createContentItem({ type: type }, this);
+                this.parent.replaceChild(this, rowOrColumn);
+
+                rowOrColumn.addChild(contentItem, insertBefore ? 0 : undefined, true);
+                rowOrColumn.addChild(this, insertBefore ? undefined : 0, true);
+
+                this.config[dimension] = 50;
+                contentItem.config[dimension] = 50;
+                rowOrColumn.callDownwards('setSize');
+            }
+        },
+
+		/**
+		 * If the user hovers above the header part of the stack, indicate drop positions for tabs.
+		 * otherwise indicate which segment of the body the dragged item would be dropped on
+		 *
+		 * @param    {Int} x Absolute Screen X
+		 * @param    {Int} y Absolute Screen Y
+		 *
+		 * @returns {void}
+		 */
+        _$highlightDropZone: function (x, y) {
+            var segment, area;
+
+            for (segment in this._contentAreaDimensions) {
+                area = this._contentAreaDimensions[segment].hoverArea;
+
+                if (area.x1 < x && area.x2 > x && area.y1 < y && area.y2 > y) {
+
+                    if (segment === 'header') {
+                        this._dropSegment = 'header';
+                        this._highlightHeaderDropZone(this._sided ? y : x);
+                    } else {
+                        this._resetHeaderDropZone();
+                        this._highlightBodyDropZone(segment);
+                    }
+
+                    return;
+                }
+            }
+        },
+
+        _$getArea: function () {
+            if (this.element.is(':visible') === false) {
+                return null;
+            }
+
+            var getArea = lm.items.AbstractContentItem.prototype._$getArea,
+                headerArea = getArea.call(this, this.header.element),
+                contentArea = getArea.call(this, this.childElementContainer),
+                contentWidth = contentArea.x2 - contentArea.x1,
+                contentHeight = contentArea.y2 - contentArea.y1;
+
+            this._contentAreaDimensions = {
+                header: {
+                    hoverArea: {
+                        x1: headerArea.x1,
+                        y1: headerArea.y1,
+                        x2: headerArea.x2,
+                        y2: headerArea.y2
+                    },
+                    highlightArea: {
+                        x1: headerArea.x1,
+                        y1: headerArea.y1,
+                        x2: headerArea.x2,
+                        y2: headerArea.y2
+                    }
+                }
+            };
+
+			/**
+			 * If this Stack is a parent to rows, columns or other stacks only its
+			 * header is a valid dropzone.
+			 */
+            if (this._activeContentItem && this._activeContentItem.isComponent === false) {
+                return headerArea;
+            }
+
+			/**
+			 * Highlight the entire body if the stack is empty
+			 */
+            if (this.contentItems.length === 0) {
+
+                this._contentAreaDimensions.body = {
+                    hoverArea: {
+                        x1: contentArea.x1,
+                        y1: contentArea.y1,
+                        x2: contentArea.x2,
+                        y2: contentArea.y2
+                    },
+                    highlightArea: {
+                        x1: contentArea.x1,
+                        y1: contentArea.y1,
+                        x2: contentArea.x2,
+                        y2: contentArea.y2
+                    }
+                };
+
+                return getArea.call(this, this.element);
+            }
+
+            this._contentAreaDimensions.left = {
+                hoverArea: {
+                    x1: contentArea.x1,
+                    y1: contentArea.y1,
+                    x2: contentArea.x1 + contentWidth * 0.25,
+                    y2: contentArea.y2
+                },
+                highlightArea: {
+                    x1: contentArea.x1,
+                    y1: contentArea.y1,
+                    x2: contentArea.x1 + contentWidth * 0.5,
+                    y2: contentArea.y2
+                }
+            };
+
+            this._contentAreaDimensions.top = {
+                hoverArea: {
+                    x1: contentArea.x1 + contentWidth * 0.25,
+                    y1: contentArea.y1,
+                    x2: contentArea.x1 + contentWidth * 0.75,
+                    y2: contentArea.y1 + contentHeight * 0.5
+                },
+                highlightArea: {
+                    x1: contentArea.x1,
+                    y1: contentArea.y1,
+                    x2: contentArea.x2,
+                    y2: contentArea.y1 + contentHeight * 0.5
+                }
+            };
+
+            this._contentAreaDimensions.right = {
+                hoverArea: {
+                    x1: contentArea.x1 + contentWidth * 0.75,
+                    y1: contentArea.y1,
+                    x2: contentArea.x2,
+                    y2: contentArea.y2
+                },
+                highlightArea: {
+                    x1: contentArea.x1 + contentWidth * 0.5,
+                    y1: contentArea.y1,
+                    x2: contentArea.x2,
+                    y2: contentArea.y2
+                }
+            };
+
+            this._contentAreaDimensions.bottom = {
+                hoverArea: {
+                    x1: contentArea.x1 + contentWidth * 0.25,
+                    y1: contentArea.y1 + contentHeight * 0.5,
+                    x2: contentArea.x1 + contentWidth * 0.75,
+                    y2: contentArea.y2
+                },
+                highlightArea: {
+                    x1: contentArea.x1,
+                    y1: contentArea.y1 + contentHeight * 0.5,
+                    x2: contentArea.x2,
+                    y2: contentArea.y2
+                }
+            };
+
+            return getArea.call(this, this.element);
+        },
+
+        _highlightHeaderDropZone: function (x) {
+            var i,
+                tabElement,
+                tabsLength = this.header.tabs.length,
+                isAboveTab = false,
+                tabTop,
+                tabLeft,
+                offset,
+                placeHolderLeft,
+                headerOffset,
+                tabWidth,
+                halfX;
+
+            // Empty stack
+            if (tabsLength === 0) {
+                headerOffset = this.header.element.offset();
+
+                this.layoutManager.dropTargetIndicator.highlightArea({
+                    x1: headerOffset.left,
+                    x2: headerOffset.left + 100,
+                    y1: headerOffset.top + this.header.element.height() - 20,
+                    y2: headerOffset.top + this.header.element.height()
+                });
+
+                return;
+            }
+
+            for (i = 0; i < tabsLength; i++) {
+                tabElement = this.header.tabs[i].element;
+                offset = tabElement.offset();
+                if (this._sided) {
+                    tabLeft = offset.top;
+                    tabTop = offset.left;
+                    tabWidth = tabElement.height();
+                } else {
+                    tabLeft = offset.left;
+                    tabTop = offset.top;
+                    tabWidth = tabElement.width();
+                }
+
+                if (x > tabLeft && x < tabLeft + tabWidth) {
+                    isAboveTab = true;
+                    break;
+                }
+            }
+
+            if (isAboveTab === false && x < tabLeft) {
+                return;
+            }
+
+            halfX = tabLeft + tabWidth / 2;
+
+            if (x < halfX) {
+                this._dropIndex = i;
+                tabElement.before(this.layoutManager.tabDropPlaceholder);
+            } else {
+                this._dropIndex = Math.min(i + 1, tabsLength);
+                tabElement.after(this.layoutManager.tabDropPlaceholder);
+            }
+
+
+            if (this._sided) {
+                placeHolderTop = this.layoutManager.tabDropPlaceholder.offset().top;
+                this.layoutManager.dropTargetIndicator.highlightArea({
+                    x1: tabTop,
+                    x2: tabTop + tabElement.innerHeight(),
+                    y1: placeHolderTop,
+                    y2: placeHolderTop + this.layoutManager.tabDropPlaceholder.width()
+                });
+                return;
+            }
+            placeHolderLeft = this.layoutManager.tabDropPlaceholder.offset().left;
+
+            this.layoutManager.dropTargetIndicator.highlightArea({
+                x1: placeHolderLeft,
+                x2: placeHolderLeft + this.layoutManager.tabDropPlaceholder.width(),
+                y1: tabTop,
+                y2: tabTop + tabElement.innerHeight()
+            });
+        },
+
+        _resetHeaderDropZone: function () {
+            this.layoutManager.tabDropPlaceholder.remove();
+        },
+
+        _setupHeaderPosition: function () {
+            var side = ['right', 'left', 'bottom'].indexOf(this._header.show) >= 0 && this._header.show;
+            this.header.element.toggle(!!this._header.show);
+            this._side = side;
+            this._sided = ['right', 'left'].indexOf(this._side) >= 0;
+            this.element.removeClass('lm_left lm_right lm_bottom');
+            if (this._side)
+                this.element.addClass('lm_' + this._side);
+            if (this.element.find('.lm_header').length && this.childElementContainer) {
+                var headerPosition = ['right', 'bottom'].indexOf(this._side) >= 0 ? 'before' : 'after';
+                this.header.element[headerPosition](this.childElementContainer);
+                this.callDownwards('setSize');
+            }
+        },
+
+        _highlightBodyDropZone: function (segment) {
+            var highlightArea = this._contentAreaDimensions[segment].highlightArea;
+            this.layoutManager.dropTargetIndicator.highlightArea(highlightArea);
+            this._dropSegment = segment;
+        }
+    });
+
+    lm.utils.BubblingEvent = function (name, origin) {
+        this.name = name;
+        this.origin = origin;
+        this.isPropagationStopped = false;
+    };
+
+    lm.utils.BubblingEvent.prototype.stopPropagation = function () {
+        this.isPropagationStopped = true;
+    };
+	/**
+	 * Minifies and unminifies configs by replacing frequent keys
+	 * and values with one letter substitutes. Config options must
+	 * retain array position/index, add new options at the end.
+	 *
+	 * @constructor
+	 */
+    lm.utils.ConfigMinifier = function () {
+        this._keys = [
+            'settings',
+            'hasHeaders',
+            'constrainDragToContainer',
+            'selectionEnabled',
+            'dimensions',
+            'borderWidth',
+            'minItemHeight',
+            'minItemWidth',
+            'headerHeight',
+            'dragProxyWidth',
+            'dragProxyHeight',
+            'labels',
+            'close',
+            'maximise',
+            'minimise',
+            'popout',
+            'content',
+            'componentName',
+            'componentState',
+            'id',
+            'width',
+            'type',
+            'height',
+            'isClosable',
+            'title',
+            'popoutWholeStack',
+            'openPopouts',
+            'parentId',
+            'activeItemIndex',
+            'reorderEnabled',
+            'borderGrabWidth',
+
+
+
+
+            //Maximum 36 entries, do not cross this line!
+        ];
+        if (this._keys.length > 36) {
+            throw new Error('Too many keys in config minifier map');
+        }
+
+        this._values = [
+            true,
+            false,
+            'row',
+            'column',
+            'stack',
+            'component',
+            'close',
+            'maximise',
+            'minimise',
+            'open in new window'
+        ];
+    };
+
+    lm.utils.copy(lm.utils.ConfigMinifier.prototype, {
+
+		/**
+		 * Takes a GoldenLayout configuration object and
+		 * replaces its keys and values recursively with
+		 * one letter counterparts
+		 *
+		 * @param   {Object} config A GoldenLayout config object
+		 *
+		 * @returns {Object} minified config
+		 */
+        minifyConfig: function (config) {
+            var min = {};
+            this._nextLevel(config, min, '_min');
+            return min;
+        },
+
+		/**
+		 * Takes a configuration Object that was previously minified
+		 * using minifyConfig and returns its original version
+		 *
+		 * @param   {Object} minifiedConfig
+		 *
+		 * @returns {Object} the original configuration
+		 */
+        unminifyConfig: function (minifiedConfig) {
+            var orig = {};
+            this._nextLevel(minifiedConfig, orig, '_max');
+            return orig;
+        },
+
+		/**
+		 * Recursive function, called for every level of the config structure
+		 *
+		 * @param   {Array|Object} orig
+		 * @param   {Array|Object} min
+		 * @param    {String} translationFn
+		 *
+		 * @returns {void}
+		 */
+        _nextLevel: function (from, to, translationFn) {
+            var key, minKey;
+
+            for (key in from) {
+
+				/**
+				 * For in returns array indices as keys, so let's cast them to numbers
+				 */
+                if (from instanceof Array) key = parseInt(key, 10);
+
+				/**
+				 * In case something has extended Object prototypes
+				 */
+                if (!from.hasOwnProperty(key)) continue;
+
+				/**
+				 * Translate the key to a one letter substitute
+				 */
+                minKey = this[translationFn](key, this._keys);
+
+				/**
+				 * For Arrays and Objects, create a new Array/Object
+				 * on the minified object and recurse into it
+				 */
+                if (typeof from[key] === 'object') {
+                    to[minKey] = from[key] instanceof Array ? [] : {};
+                    this._nextLevel(from[key], to[minKey], translationFn);
+
+					/**
+					 * For primitive values (Strings, Numbers, Boolean etc.)
+					 * minify the value
+					 */
+                } else {
+                    to[minKey] = this[translationFn](from[key], this._values);
+                }
+            }
+        },
+
+		/**
+		 * Minifies value based on a dictionary
+		 *
+		 * @param   {String|Boolean} value
+		 * @param   {Array<String|Boolean>} dictionary
+		 *
+		 * @returns {String} The minified version
+		 */
+        _min: function (value, dictionary) {
+			/**
+			 * If a value actually is a single character, prefix it
+			 * with ___ to avoid mistaking it for a minification code
+			 */
+            if (typeof value === 'string' && value.length === 1) {
+                return '___' + value;
+            }
+
+            var index = lm.utils.indexOf(value, dictionary);
+
+			/**
+			 * value not found in the dictionary, return it unmodified
+			 */
+            if (index === -1) {
+                return value;
+
+				/**
+				 * value found in dictionary, return its base36 counterpart
+				 */
+            } else {
+                return index.toString(36);
+            }
+        },
+
+        _max: function (value, dictionary) {
+			/**
+			 * value is a single character. Assume that it's a translation
+			 * and return the original value from the dictionary
+			 */
+            if (typeof value === 'string' && value.length === 1) {
+                return dictionary[parseInt(value, 36)];
+            }
+
+			/**
+			 * value originally was a single character and was prefixed with ___
+			 * to avoid mistaking it for a translation. Remove the prefix
+			 * and return the original character
+			 */
+            if (typeof value === 'string' && value.substr(0, 3) === '___') {
+                return value[3];
+            }
+			/**
+			 * value was not minified
+			 */
+            return value;
+        }
+    });
+
+	/**
+	 * An EventEmitter singleton that propagates events
+	 * across multiple windows. This is a little bit trickier since
+	 * windows are allowed to open childWindows in their own right
+	 *
+	 * This means that we deal with a tree of windows. Hence the rules for event propagation are:
+	 *
+	 * - Propagate events from this layout to both parents and children
+	 * - Propagate events from parent to this and children
+	 * - Propagate events from children to the other children (but not the emitting one) and the parent
+	 *
+	 * @constructor
+	 *
+	 * @param {lm.LayoutManager} layoutManager
+	 */
+    lm.utils.EventHub = function (layoutManager) {
+        lm.utils.EventEmitter.call(this);
+        this._layoutManager = layoutManager;
+        this._dontPropagateToParent = null;
+        this._childEventSource = null;
+        this.on(lm.utils.EventEmitter.ALL_EVENT, lm.utils.fnBind(this._onEventFromThis, this));
+        this._boundOnEventFromChild = lm.utils.fnBind(this._onEventFromChild, this);
+        $(window).on('gl_child_event', this._boundOnEventFromChild);
+    };
+
+	/**
+	 * Called on every event emitted on this eventHub, regardles of origin.
+	 *
+	 * @private
+	 *
+	 * @param {Mixed}
+	 *
+	 * @returns {void}
+	 */
+    lm.utils.EventHub.prototype._onEventFromThis = function () {
+        var args = Array.prototype.slice.call(arguments);
+
+        if (this._layoutManager.isSubWindow && args[0] !== this._dontPropagateToParent) {
+            this._propagateToParent(args);
+        }
+        this._propagateToChildren(args);
+
+        //Reset
+        this._dontPropagateToParent = null;
+        this._childEventSource = null;
+    };
+
+	/**
+	 * Called by the parent layout.
+	 *
+	 * @param   {Array} args Event name + arguments
+	 *
+	 * @returns {void}
+	 */
+    lm.utils.EventHub.prototype._$onEventFromParent = function (args) {
+        this._dontPropagateToParent = args[0];
+        this.emit.apply(this, args);
+    };
+
+	/**
+	 * Callback for child events raised on the window
+	 *
+	 * @param   {DOMEvent} event
+	 * @private
+	 *
+	 * @returns {void}
+	 */
+    lm.utils.EventHub.prototype._onEventFromChild = function (event) {
+        this._childEventSource = event.originalEvent.__gl;
+        this.emit.apply(this, event.originalEvent.__glArgs);
+    };
+
+	/**
+	 * Propagates the event to the parent by emitting
+	 * it on the parent's DOM window
+	 *
+	 * @param   {Array} args Event name + arguments
+	 * @private
+	 *
+	 * @returns {void}
+	 */
+    lm.utils.EventHub.prototype._propagateToParent = function (args) {
+        var event,
+            eventName = 'gl_child_event';
+
+        if (document.createEvent) {
+            event = window.opener.document.createEvent('HTMLEvents');
+            event.initEvent(eventName, true, true);
+        } else {
+            event = window.opener.document.createEventObject();
+            event.eventType = eventName;
+        }
+
+        event.eventName = eventName;
+        event.__glArgs = args;
+        event.__gl = this._layoutManager;
+
+        if (document.createEvent) {
+            window.opener.dispatchEvent(event);
+        } else {
+            window.opener.fireEvent('on' + event.eventType, event);
+        }
+    };
+
+	/**
+	 * Propagate events to children
+	 *
+	 * @param   {Array} args Event name + arguments
+	 * @private
+	 *
+	 * @returns {void}
+	 */
+    lm.utils.EventHub.prototype._propagateToChildren = function (args) {
+        var childGl, i;
+
+        for (i = 0; i < this._layoutManager.openPopouts.length; i++) {
+            childGl = this._layoutManager.openPopouts[i].getGlInstance();
+
+            if (childGl && childGl !== this._childEventSource) {
+                childGl.eventHub._$onEventFromParent(args);
+            }
+        }
+    };
+
+
+	/**
+	 * Destroys the EventHub
+	 *
+	 * @public
+	 * @returns {void}
+	 */
+
+    lm.utils.EventHub.prototype.destroy = function () {
+        $(window).off('gl_child_event', this._boundOnEventFromChild);
+    };
+	/**
+	 * A specialised GoldenLayout component that binds GoldenLayout container
+	 * lifecycle events to react components
+	 *
+	 * @constructor
+	 *
+	 * @param {lm.container.ItemContainer} container
+	 * @param {Object} state state is not required for react components
+	 */
+    lm.utils.ReactComponentHandler = function (container, state) {
+        this._reactComponent = null;
+        this._originalComponentWillUpdate = null;
+        this._container = container;
+        this._initialState = state;
+        this._reactClass = this._getReactClass();
+        this._container.on('open', this._render, this);
+        this._container.on('destroy', this._destroy, this);
+    };
+
+    lm.utils.copy(lm.utils.ReactComponentHandler.prototype, {
+
+		/**
+		 * Creates the react class and component and hydrates it with
+		 * the initial state - if one is present
+		 *
+		 * By default, react's getInitialState will be used
+		 *
+		 * @private
+		 * @returns {void}
+		 */
+        _render: function () {
+            this._reactComponent = ReactDOM.render(this._getReactComponent(), this._container.getElement()[0]);
+            this._originalComponentWillUpdate = this._reactComponent.componentWillUpdate || function () {
+            };
+            this._reactComponent.componentWillUpdate = this._onUpdate.bind(this);
+            if (this._container.getState()) {
+                this._reactComponent.setState(this._container.getState());
+            }
+        },
+
+		/**
+		 * Removes the component from the DOM and thus invokes React's unmount lifecycle
+		 *
+		 * @private
+		 * @returns {void}
+		 */
+        _destroy: function () {
+            ReactDOM.unmountComponentAtNode(this._container.getElement()[0]);
+            this._container.off('open', this._render, this);
+            this._container.off('destroy', this._destroy, this);
+        },
+
+		/**
+		 * Hooks into React's state management and applies the componentstate
+		 * to GoldenLayout
+		 *
+		 * @private
+		 * @returns {void}
+		 */
+        _onUpdate: function (nextProps, nextState) {
+            this._container.setState(nextState);
+            this._originalComponentWillUpdate.call(this._reactComponent, nextProps, nextState);
+        },
+
+		/**
+		 * Retrieves the react class from GoldenLayout's registry
+		 *
+		 * @private
+		 * @returns {React.Class}
+		 */
+        _getReactClass: function () {
+            var componentName = this._container._config.component;
+            var reactClass;
+
+            if (!componentName) {
+                throw new Error('No react component name. type: react-component needs a field `component`');
+            }
+
+            reactClass = this._container.layoutManager.getComponent(componentName);
+
+            if (!reactClass) {
+                throw new Error('React component "' + componentName + '" not found. ' +
+                    'Please register all components with GoldenLayout using `registerComponent(name, component)`');
+            }
+
+            return reactClass;
+        },
+
+		/**
+		 * Copies and extends the properties array and returns the React element
+		 *
+		 * @private
+		 * @returns {React.Element}
+		 */
+        _getReactComponent: function () {
+            var defaultProps = {
+                glEventHub: this._container.layoutManager.eventHub,
+                glContainer: this._container,
+            };
+            var props = $.extend(defaultProps, this._container._config.props);
+            return React.createElement(this._reactClass, props);
+        }
+    });
+})(window.$);
\ No newline at end of file
diff --git a/src/client/northstar/core/brusher/IBaseBrushable.ts b/src/client/northstar/core/brusher/IBaseBrushable.ts
index c46db4d22..87f4ba413 100644
--- a/src/client/northstar/core/brusher/IBaseBrushable.ts
+++ b/src/client/northstar/core/brusher/IBaseBrushable.ts
@@ -1,9 +1,9 @@
 import { PIXIPoint } from '../../utils/MathUtil';
 import { IEquatable } from '../../utils/IEquatable';
-import { Document } from '../../../../fields/Document';
+import { Doc } from '../../../../new_fields/Doc';
 
 export interface IBaseBrushable<T> extends IEquatable {
-    BrusherModels: Array<Document>;
+    BrusherModels: Array<Doc>;
     BrushColors: Array<number>;
     Position: PIXIPoint;
     Size: PIXIPoint;
diff --git a/src/client/northstar/core/filter/FilterModel.ts b/src/client/northstar/core/filter/FilterModel.ts
index e2ba3f652..6ab96b33d 100644
--- a/src/client/northstar/core/filter/FilterModel.ts
+++ b/src/client/northstar/core/filter/FilterModel.ts
@@ -2,10 +2,9 @@ import { ValueComparison } from "./ValueComparision";
 import { Utils } from "../../utils/Utils";
 import { IBaseFilterProvider } from "./IBaseFilterProvider";
 import { FilterOperand } from "./FilterOperand";
-import { KeyStore } from "../../../../fields/KeyStore";
-import { FieldWaiting } from "../../../../fields/Field";
-import { Document } from "../../../../fields/Document";
 import { HistogramField } from "../../dash-fields/HistogramField";
+import { Cast, FieldValue } from "../../../../new_fields/Types";
+import { Doc } from "../../../../new_fields/Doc";
 
 export class FilterModel {
     public ValueComparisons: ValueComparison[];
@@ -52,12 +51,12 @@ export class FilterModel {
             let children = new Array<string>();
             let linkedGraphNodes = baseOperation.Links;
             linkedGraphNodes.map(linkVm => {
-                let filterDoc = linkVm.Get(KeyStore.LinkedFromDocs);
-                if (filterDoc && filterDoc !== FieldWaiting && filterDoc instanceof Document) {
-                    let filterHistogram = filterDoc.GetT(KeyStore.Data, HistogramField);
-                    if (filterHistogram && filterHistogram !== FieldWaiting) {
-                        if (!visitedFilterProviders.has(filterHistogram.Data)) {
-                            let child = FilterModel.GetFilterModelsRecursive(filterHistogram.Data, visitedFilterProviders, filterModels, false);
+                let filterDoc = FieldValue(Cast(linkVm.linkedFrom, Doc));
+                if (filterDoc) {
+                    let filterHistogram = Cast(filterDoc.data, HistogramField);
+                    if (filterHistogram) {
+                        if (!visitedFilterProviders.has(filterHistogram.HistoOp)) {
+                            let child = FilterModel.GetFilterModelsRecursive(filterHistogram.HistoOp, visitedFilterProviders, filterModels, false);
                             if (child !== "") {
                                 // if (linkVm.IsInverted) {
                                 //     child = "! " + child;
diff --git a/src/client/northstar/core/filter/IBaseFilterConsumer.ts b/src/client/northstar/core/filter/IBaseFilterConsumer.ts
index 59d7adf4c..e7549d113 100644
--- a/src/client/northstar/core/filter/IBaseFilterConsumer.ts
+++ b/src/client/northstar/core/filter/IBaseFilterConsumer.ts
@@ -1,10 +1,10 @@
 import { FilterOperand } from '../filter/FilterOperand';
 import { IEquatable } from '../../utils/IEquatable';
-import { Document } from "../../../../fields/Document";
+import { Doc } from '../../../../new_fields/Doc';
 
 export interface IBaseFilterConsumer extends IEquatable {
     FilterOperand: FilterOperand;
-    Links: Document[];
+    Links: Doc[];
 }
 
 export function instanceOfIBaseFilterConsumer(object: any): object is IBaseFilterConsumer {
diff --git a/src/client/northstar/core/filter/ValueComparision.ts b/src/client/northstar/core/filter/ValueComparision.ts
index 80b1242a9..65687a82b 100644
--- a/src/client/northstar/core/filter/ValueComparision.ts
+++ b/src/client/northstar/core/filter/ValueComparision.ts
@@ -62,13 +62,13 @@ export class ValueComparison {
         var rawName = this.attributeModel.CodeName;
         switch (this.Predicate) {
             case Predicate.STARTS_WITH:
-                ret += rawName + " !== null && " + rawName + ".StartsWith(" + val + ") ";
+                ret += rawName + " != null && " + rawName + ".StartsWith(" + val + ") ";
                 return ret;
             case Predicate.ENDS_WITH:
-                ret += rawName + " !== null && " + rawName + ".EndsWith(" + val + ") ";
+                ret += rawName + " != null && " + rawName + ".EndsWith(" + val + ") ";
                 return ret;
             case Predicate.CONTAINS:
-                ret += rawName + " !== null && " + rawName + ".Contains(" + val + ") ";
+                ret += rawName + " != null && " + rawName + ".Contains(" + val + ") ";
                 return ret;
             default:
                 ret += rawName + " " + op + " " + val + " ";
diff --git a/src/client/northstar/dash-fields/HistogramField.ts b/src/client/northstar/dash-fields/HistogramField.ts
index c699691a4..f01f08487 100644
--- a/src/client/northstar/dash-fields/HistogramField.ts
+++ b/src/client/northstar/dash-fields/HistogramField.ts
@@ -1,64 +1,55 @@
-import { action } from "mobx";
+import { observable } from "mobx";
+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 { BasicField } from "../../../fields/BasicField";
-import { Field, FieldId } from "../../../fields/Field";
+import { ObjectField, Copy } from "../../../new_fields/ObjectField";
 import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
-import { Types } from "../../../server/Message";
 import { OmitKeys } from "../../../Utils";
-
-
-export class HistogramField extends BasicField<HistogramOperation> {
-    constructor(data?: HistogramOperation, id?: FieldId, save: boolean = true) {
-        super(data ? data : HistogramOperation.Empty, save, id);
-    }
-
-    toString(): string {
-        return JSON.stringify(OmitKeys(this.Data, ['Links', 'BrushLinks', 'Result', 'BrushColors', 'FilterModels', 'FilterOperand']));
-    }
-
-    Copy(): Field {
-        return new HistogramField(this.Data.Copy());
+import { Deserializable } from "../../util/SerializationHelper";
+
+function serialize(field: HistogramField) {
+    return OmitKeys(field.HistoOp, ['Links', 'BrushLinks', 'Result', 'BrushColors', 'FilterModels', 'FilterOperand']).omit;
+}
+
+function deserialize(jp: any) {
+    let X: AttributeTransformationModel | undefined;
+    let Y: AttributeTransformationModel | undefined;
+    let V: AttributeTransformationModel | undefined;
+
+    let schema = CurrentUserUtils.GetNorthstarSchema(jp.SchemaName);
+    if (schema) {
+        CurrentUserUtils.GetAllNorthstarColumnAttributes(schema).map(attr => {
+            if (attr.displayName === jp.X.AttributeModel.Attribute.DisplayName) {
+                X = new AttributeTransformationModel(new ColumnAttributeModel(attr), jp.X.AggregateFunction);
+            }
+            if (attr.displayName === jp.Y.AttributeModel.Attribute.DisplayName) {
+                Y = new AttributeTransformationModel(new ColumnAttributeModel(attr), jp.Y.AggregateFunction);
+            }
+            if (attr.displayName === jp.V.AttributeModel.Attribute.DisplayName) {
+                V = new AttributeTransformationModel(new ColumnAttributeModel(attr), jp.V.AggregateFunction);
+            }
+        });
+        if (X && Y && V) {
+            return new HistogramField(new HistogramOperation(jp.SchemaName, X, Y, V, jp.Normalization));
+        }
     }
-
-    ToScriptString(): string {
-        return `new HistogramField("${this.Data}")`;
+    return new HistogramField(HistogramOperation.Empty);
+}
+
+@Deserializable("histogramField")
+export class HistogramField extends ObjectField {
+    @serializable(custom(serialize, deserialize)) @observable public readonly HistoOp: HistogramOperation;
+    constructor(data?: HistogramOperation) {
+        super();
+        this.HistoOp = data ? data : HistogramOperation.Empty;
     }
 
-
-    ToJson() {
-        return {
-            type: Types.HistogramOp,
-            data: this.toString(),
-            id: this.Id
-        };
+    toString(): string {
+        return JSON.stringify(OmitKeys(this.HistoOp, ['Links', 'BrushLinks', 'Result', 'BrushColors', 'FilterModels', 'FilterOperand']).omit);
     }
 
-    @action
-    static FromJson(id: string, data: any): HistogramField {
-        let jp = JSON.parse(data);
-        let X: AttributeTransformationModel | undefined;
-        let Y: AttributeTransformationModel | undefined;
-        let V: AttributeTransformationModel | undefined;
-
-        let schema = CurrentUserUtils.GetNorthstarSchema(jp.SchemaName);
-        if (schema) {
-            CurrentUserUtils.GetAllNorthstarColumnAttributes(schema).map(attr => {
-                if (attr.displayName === jp.X.AttributeModel.Attribute.DisplayName) {
-                    X = new AttributeTransformationModel(new ColumnAttributeModel(attr), jp.X.AggregateFunction);
-                }
-                if (attr.displayName === jp.Y.AttributeModel.Attribute.DisplayName) {
-                    Y = new AttributeTransformationModel(new ColumnAttributeModel(attr), jp.Y.AggregateFunction);
-                }
-                if (attr.displayName === jp.V.AttributeModel.Attribute.DisplayName) {
-                    V = new AttributeTransformationModel(new ColumnAttributeModel(attr), jp.V.AggregateFunction);
-                }
-            });
-            if (X && Y && V) {
-                return new HistogramField(new HistogramOperation(jp.SchemaName, X, Y, V, jp.Normalization), id, false);
-            }
-        }
-        return new HistogramField(HistogramOperation.Empty, id, false);
+    [Copy]() {
+        return new HistogramField(this.HistoOp.Copy());
     }
 }
\ No newline at end of file
diff --git a/src/client/northstar/dash-nodes/HistogramBox.scss b/src/client/northstar/dash-nodes/HistogramBox.scss
index e899cf15e..06d781263 100644
--- a/src/client/northstar/dash-nodes/HistogramBox.scss
+++ b/src/client/northstar/dash-nodes/HistogramBox.scss
@@ -1,12 +1,12 @@
 .histogrambox-container {
     padding: 0vw;
     position: absolute;
-    top: 0;
-    left:0;
+    top: -50%;
+    left:-50%;
     text-align: center;
     width: 100%;
     height: 100%;
-    background: black;
+    background: black; 
   }
   .histogrambox-xaxislabel {
     position:absolute;
diff --git a/src/client/northstar/dash-nodes/HistogramBox.tsx b/src/client/northstar/dash-nodes/HistogramBox.tsx
index 0e84ace50..5e7b867b3 100644
--- a/src/client/northstar/dash-nodes/HistogramBox.tsx
+++ b/src/client/northstar/dash-nodes/HistogramBox.tsx
@@ -1,10 +1,6 @@
 import React = require("react");
 import { action, computed, observable, reaction, runInAction, trace } from "mobx";
 import { observer } from "mobx-react";
-import Measure from "react-measure";
-import { FieldWaiting, Opt } from "../../../fields/Field";
-import { Document } from "../../../fields/Document";
-import { KeyStore } from "../../../fields/KeyStore";
 import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
 import { ChartType, VisualBinRange } from '../../northstar/model/binRanges/VisualBinRange';
 import { VisualBinRangeHelper } from "../../northstar/model/binRanges/VisualBinRangeHelper";
@@ -21,18 +17,20 @@ import "./HistogramBox.scss";
 import { HistogramBoxPrimitives } from './HistogramBoxPrimitives';
 import { HistogramLabelPrimitives } from "./HistogramLabelPrimitives";
 import { StyleConstants } from "../utils/StyleContants";
+import { NumCast, Cast } from "../../../new_fields/Types";
+import { listSpec } from "../../../new_fields/Schema";
+import { Doc } from "../../../new_fields/Doc";
+import { Id } from "../../../new_fields/RefField";
 
 
 @observer
 export class HistogramBox extends React.Component<FieldViewProps> {
-    public static LayoutString(fieldStr: string = "DataKey") { return FieldView.LayoutString(HistogramBox, fieldStr); }
+    public static LayoutString(fieldStr: string = "data") { return FieldView.LayoutString(HistogramBox, fieldStr); }
     private _dropXRef = React.createRef<HTMLDivElement>();
     private _dropYRef = React.createRef<HTMLDivElement>();
     private _dropXDisposer?: DragManager.DragDropDisposer;
     private _dropYDisposer?: DragManager.DragDropDisposer;
 
-    @observable public PanelWidth: number = 100;
-    @observable public PanelHeight: number = 100;
     @observable public HistoOp: HistogramOperation = HistogramOperation.Empty;
     @observable public VisualBinRanges: VisualBinRange[] = [];
     @observable public ValueRange: number[] = [];
@@ -50,9 +48,9 @@ export class HistogramBox extends React.Component<FieldViewProps> {
     @action
     dropX = (e: Event, de: DragManager.DropEvent) => {
         if (de.data instanceof DragManager.DocumentDragData) {
-            let h = de.data.draggedDocuments[0].GetT(KeyStore.Data, HistogramField);
-            if (h && h !== FieldWaiting) {
-                this.HistoOp.X = h.Data.X;
+            let h = Cast(de.data.draggedDocuments[0].data, HistogramField);
+            if (h) {
+                this.HistoOp.X = h.HistoOp.X;
             }
             e.stopPropagation();
             e.preventDefault();
@@ -61,9 +59,9 @@ export class HistogramBox extends React.Component<FieldViewProps> {
     @action
     dropY = (e: Event, de: DragManager.DropEvent) => {
         if (de.data instanceof DragManager.DocumentDragData) {
-            let h = de.data.draggedDocuments[0].GetT(KeyStore.Data, HistogramField);
-            if (h && h !== FieldWaiting) {
-                this.HistoOp.Y = h.Data.X;
+            let h = Cast(de.data.draggedDocuments[0].data, HistogramField);
+            if (h) {
+                this.HistoOp.Y = h.HistoOp.X;
             }
             e.stopPropagation();
             e.preventDefault();
@@ -88,7 +86,7 @@ export class HistogramBox extends React.Component<FieldViewProps> {
         }
         reaction(() => CurrentUserUtils.NorthstarDBCatalog, (catalog?: Catalog) => this.activateHistogramOperation(catalog), { fireImmediately: true });
         reaction(() => [this.VisualBinRanges && this.VisualBinRanges.slice()], () => this.SizeConverter.SetVisualBinRanges(this.VisualBinRanges));
-        reaction(() => [this.PanelHeight, this.PanelWidth], () => this.SizeConverter.SetIsSmall(this.PanelWidth < 40 && this.PanelHeight < 40));
+        reaction(() => [this.props.PanelWidth(), this.props.PanelHeight()], (size: number[]) => this.SizeConverter.SetIsSmall(size[0] < 40 && size[1] < 40));
         reaction(() => this.HistogramResult ? this.HistogramResult.binRanges : undefined,
             (binRanges: BinRange[] | undefined) => {
                 if (binRanges) {
@@ -113,59 +111,63 @@ export class HistogramBox extends React.Component<FieldViewProps> {
         }
     }
 
-    activateHistogramOperation(catalog?: Catalog) {
+    async activateHistogramOperation(catalog?: Catalog) {
         if (catalog) {
-            this.props.Document.GetTAsync(this.props.fieldKey, HistogramField).then((histoOp: Opt<HistogramField>) => runInAction(() => {
-                this.HistoOp = histoOp ? histoOp.Data : HistogramOperation.Empty;
+            let histoOp = await Cast(this.props.Document[this.props.fieldKey], HistogramField);
+            runInAction(() => {
+                this.HistoOp = histoOp ? histoOp.HistoOp : HistogramOperation.Empty;
                 if (this.HistoOp !== HistogramOperation.Empty) {
-                    reaction(() => this.props.Document.GetList(KeyStore.LinkedFromDocs, [] as Document[]), (docs) => this.HistoOp.Links.splice(0, this.HistoOp.Links.length, ...docs), { fireImmediately: true });
-                    reaction(() => this.props.Document.GetList(KeyStore.BrushingDocs, []).length,
+                    reaction(() => Cast(this.props.Document.linkedFromDocs, listSpec(Doc), []).filter(d => d).map(d => d as Doc), (docs) => this.HistoOp.Links.splice(0, this.HistoOp.Links.length, ...docs), { fireImmediately: true });
+                    reaction(() => Cast(this.props.Document.brushingDocs, listSpec(Doc), []).length,
                         () => {
-                            let brushingDocs = this.props.Document.GetList(KeyStore.BrushingDocs, [] as Document[]);
-                            let proto = this.props.Document.GetPrototype() as Document;
-                            this.HistoOp.BrushLinks.splice(0, this.HistoOp.BrushLinks.length, ...brushingDocs.map((brush, i) => {
-                                brush.SetNumber(KeyStore.BackgroundColor, StyleConstants.BRUSH_COLORS[i % StyleConstants.BRUSH_COLORS.length]);
-                                let brushed = brush.GetList(KeyStore.BrushingDocs, [] as Document[]);
-                                return { l: brush, b: brushed[0].Id === proto.Id ? brushed[1] : brushed[0] };
-                            }));
+                            let brushingDocs = Cast(this.props.Document.brushingDocs, listSpec(Doc), []).filter(d => d).map(d => d as Doc);
+                            const proto = this.props.Document.proto;
+                            if (proto) {
+                                this.HistoOp.BrushLinks.splice(0, this.HistoOp.BrushLinks.length, ...brushingDocs.map((brush, i) => {
+                                    brush.backgroundColor = StyleConstants.BRUSH_COLORS[i % StyleConstants.BRUSH_COLORS.length];
+                                    let brushed = Cast(brush.brushingDocs, listSpec(Doc), []).filter(d => d).map(d => d as Doc);
+                                    return { l: brush, b: brushed[0][Id] === proto[Id] ? brushed[1] : brushed[0] };
+                                }));
+                            }
                         }, { fireImmediately: true });
                     reaction(() => this.createOperationParamsCache, () => this.HistoOp.Update(), { fireImmediately: true });
                 }
-            }));
+            });
         }
     }
+
+    @action
+    private onScrollWheel = (e: React.WheelEvent) => {
+        this.HistoOp.DrillDown(e.deltaY > 0);
+        e.stopPropagation();
+    }
+
     render() {
         let labelY = this.HistoOp && this.HistoOp.Y ? this.HistoOp.Y.PresentedName : "<...>";
         let labelX = this.HistoOp && this.HistoOp.X ? this.HistoOp.X.PresentedName : "<...>";
-        var h = this.props.isTopMost ? this.PanelHeight : this.props.Document.GetNumber(KeyStore.Height, 0);
-        var w = this.props.isTopMost ? this.PanelWidth : this.props.Document.GetNumber(KeyStore.Width, 0);
         let loff = this.SizeConverter.LeftOffset;
         let toff = this.SizeConverter.TopOffset;
         let roff = this.SizeConverter.RightOffset;
         let boff = this.SizeConverter.BottomOffset;
         return (
-            <Measure onResize={(r: any) => runInAction(() => { this.PanelWidth = r.entry.width; this.PanelHeight = r.entry.height; })}>
-                {({ measureRef }) =>
-                    <div className="histogrambox-container" ref={measureRef} style={{ transform: `translate(-50%, -50%)` }}>
-                        <div className="histogrambox-yaxislabel" onPointerDown={this.yLabelPointerDown} ref={this._dropYRef} >
-                            <span className="histogrambox-yaxislabel-text">
-                                {labelY}
-                            </span>
-                        </div>
-                        <div className="histogrambox-primitives" style={{
-                            transform: `translate(${loff + 25}px, ${toff}px)`,
-                            width: `calc(100% - ${loff + roff + 25}px)`,
-                            height: `calc(100% - ${toff + boff}px)`,
-                        }}>
-                            <HistogramLabelPrimitives HistoBox={this} />
-                            <HistogramBoxPrimitives HistoBox={this} />
-                        </div>
-                        <div className="histogrambox-xaxislabel" onPointerDown={this.xLabelPointerDown} ref={this._dropXRef} >
-                            {labelX}
-                        </div>
-                    </div>
-                }
-            </Measure>
+            <div className="histogrambox-container" onWheel={this.onScrollWheel}>
+                <div className="histogrambox-yaxislabel" onPointerDown={this.yLabelPointerDown} ref={this._dropYRef} >
+                    <span className="histogrambox-yaxislabel-text">
+                        {labelY}
+                    </span>
+                </div>
+                <div className="histogrambox-primitives" style={{
+                    transform: `translate(${loff + 25}px, ${toff}px)`,
+                    width: `calc(100% - ${loff + roff + 25}px)`,
+                    height: `calc(100% - ${toff + boff}px)`,
+                }}>
+                    <HistogramLabelPrimitives HistoBox={this} />
+                    <HistogramBoxPrimitives HistoBox={this} />
+                </div>
+                <div className="histogrambox-xaxislabel" onPointerDown={this.xLabelPointerDown} ref={this._dropXRef} >
+                    {labelX}
+                </div>
+            </div>
         );
     }
 }
diff --git a/src/client/northstar/dash-nodes/HistogramLabelPrimitives.tsx b/src/client/northstar/dash-nodes/HistogramLabelPrimitives.tsx
index 5785fe838..62aebd3c6 100644
--- a/src/client/northstar/dash-nodes/HistogramLabelPrimitives.tsx
+++ b/src/client/northstar/dash-nodes/HistogramLabelPrimitives.tsx
@@ -12,7 +12,7 @@ import { HistogramPrimitivesProps } from "./HistogramBoxPrimitives";
 @observer
 export class HistogramLabelPrimitives extends React.Component<HistogramPrimitivesProps> {
     componentDidMount() {
-        reaction(() => [this.props.HistoBox.PanelWidth, this.props.HistoBox.SizeConverter.LeftOffset, this.props.HistoBox.VisualBinRanges.length],
+        reaction(() => [this.props.HistoBox.props.PanelWidth(), this.props.HistoBox.SizeConverter.LeftOffset, this.props.HistoBox.VisualBinRanges.length],
             (fields) => HistogramLabelPrimitives.computeLabelAngle(fields[0], fields[1], this.props.HistoBox), { fireImmediately: true });
     }
 
@@ -35,7 +35,7 @@ export class HistogramLabelPrimitives extends React.Component<HistogramPrimitive
         if (!vb.length || !sc.Initialized) {
             return (null);
         }
-        let dim = (axis === 0 ? this.props.HistoBox.PanelWidth : this.props.HistoBox.PanelHeight) / ((axis === 0 && vb[axis] instanceof NominalVisualBinRange) ?
+        let dim = (axis === 0 ? this.props.HistoBox.props.PanelWidth() : this.props.HistoBox.props.PanelHeight()) / ((axis === 0 && vb[axis] instanceof NominalVisualBinRange) ?
             (12 + 5) : //  (<number>FontStyles.AxisLabel.fontSize + 5)));
             sc.MaxLabelSizes[axis].coords[axis] + 5);
 
@@ -49,12 +49,12 @@ export class HistogramLabelPrimitives extends React.Component<HistogramPrimitive
                 let yStart = (axis === 1 ? r.yFrom - textHeight / 2 : r.yFrom);
 
                 if (axis === 0 && vb[axis] instanceof NominalVisualBinRange) {
-                    let space = (r.xTo - r.xFrom) / sc.RenderDimension * this.props.HistoBox.PanelWidth;
+                    let space = (r.xTo - r.xFrom) / sc.RenderDimension * this.props.HistoBox.props.PanelWidth();
                     xStart += Math.max(textWidth / 2, (1 - textWidth / space) * textWidth / 2) - textHeight / 2;
                 }
 
                 let xPercent = axis === 1 ? `${xStart}px` : `${xStart / sc.RenderDimension * 100}%`;
-                let yPercent = axis === 0 ? `${this.props.HistoBox.PanelHeight - sc.BottomOffset - textHeight}px` : `${yStart / sc.RenderDimension * 100}%`;
+                let yPercent = axis === 0 ? `${this.props.HistoBox.props.PanelHeight() - sc.BottomOffset - textHeight}px` : `${yStart / sc.RenderDimension * 100}%`;
 
                 prims.push(
                     <div className="histogramLabelPrimitives-placer" key={DashUtils.GenerateGuid()} style={{ transform: `translate(${xPercent}, ${yPercent})` }}>
diff --git a/src/client/northstar/operations/HistogramOperation.ts b/src/client/northstar/operations/HistogramOperation.ts
index 760106023..78b206bdc 100644
--- a/src/client/northstar/operations/HistogramOperation.ts
+++ b/src/client/northstar/operations/HistogramOperation.ts
@@ -1,7 +1,4 @@
 import { action, computed, observable, trace } from "mobx";
-import { Document } from "../../../fields/Document";
-import { FieldWaiting } from "../../../fields/Field";
-import { KeyStore } from "../../../fields/KeyStore";
 import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
 import { ColumnAttributeModel } from "../core/attribute/AttributeModel";
 import { AttributeTransformationModel } from "../core/attribute/AttributeTransformationModel";
@@ -16,14 +13,16 @@ import { AggregateFunction, AggregateParameters, Attribute, AverageAggregatePara
 import { ModelHelpers } from "../model/ModelHelpers";
 import { ArrayUtil } from "../utils/ArrayUtil";
 import { BaseOperation } from "./BaseOperation";
+import { Doc } from "../../../new_fields/Doc";
+import { Cast, NumCast } from "../../../new_fields/Types";
 
 export class HistogramOperation extends BaseOperation implements IBaseFilterConsumer, IBaseFilterProvider {
     public static Empty = new HistogramOperation("-empty schema-", new AttributeTransformationModel(new ColumnAttributeModel(new Attribute())), new AttributeTransformationModel(new ColumnAttributeModel(new Attribute())), new AttributeTransformationModel(new ColumnAttributeModel(new Attribute())));
     @observable public FilterOperand: FilterOperand = FilterOperand.AND;
-    @observable public Links: Document[] = [];
-    @observable public BrushLinks: { l: Document, b: Document }[] = [];
+    @observable public Links: Doc[] = [];
+    @observable public BrushLinks: { l: Doc, b: Doc }[] = [];
     @observable public BrushColors: number[] = [];
-    @observable public FilterModels: FilterModel[] = [];
+    @observable public BarFilterModels: FilterModel[] = [];
 
     @observable public Normalization: number = -1;
     @observable public X: AttributeTransformationModel;
@@ -50,17 +49,24 @@ export class HistogramOperation extends BaseOperation implements IBaseFilterCons
         throw new Error("Method not implemented.");
     }
 
+
+    @computed public get FilterModels() {
+        return this.BarFilterModels;
+    }
     @action
     public AddFilterModels(filterModels: FilterModel[]): void {
-        filterModels.filter(f => f !== null).forEach(fm => this.FilterModels.push(fm));
+        filterModels.filter(f => f !== null).forEach(fm => this.BarFilterModels.push(fm));
     }
     @action
     public RemoveFilterModels(filterModels: FilterModel[]): void {
-        ArrayUtil.RemoveMany(this.FilterModels, filterModels);
+        ArrayUtil.RemoveMany(this.BarFilterModels, filterModels);
     }
 
     @computed
     public get FilterString(): string {
+        if (this.OverridingFilters.length > 0) {
+            return "(" + this.OverridingFilters.filter(fm => fm !== null).map(fm => fm.ToPythonString()).join(" || ") + ")";
+        }
         let filterModels: FilterModel[] = [];
         return FilterModel.GetFilterModelsRecursive(this, new Set<IBaseFilterProvider>(), filterModels, true);
     }
@@ -70,15 +76,37 @@ export class HistogramOperation extends BaseOperation implements IBaseFilterCons
         trace();
         let brushes: string[] = [];
         this.BrushLinks.map(brushLink => {
-            let brushHistogram = brushLink.b.GetT(KeyStore.Data, HistogramField);
-            if (brushHistogram && brushHistogram !== FieldWaiting) {
+            let brushHistogram = Cast(brushLink.b.data, HistogramField);
+            if (brushHistogram) {
                 let filterModels: FilterModel[] = [];
-                brushes.push(FilterModel.GetFilterModelsRecursive(brushHistogram.Data, new Set<IBaseFilterProvider>(), filterModels, false));
+                brushes.push(FilterModel.GetFilterModelsRecursive(brushHistogram.HistoOp, new Set<IBaseFilterProvider>(), filterModels, false));
             }
         });
         return brushes;
     }
 
+    _stackedFilters: (FilterModel[])[] = [];
+    @action
+    public DrillDown(up: boolean) {
+        if (!up) {
+            if (!this.BarFilterModels.length) {
+                return;
+            }
+            this._stackedFilters.push(this.BarFilterModels.map(f => f));
+            this.OverridingFilters.length = 0;
+            this.OverridingFilters.push(...this._stackedFilters[this._stackedFilters.length - 1]);
+            this.BarFilterModels.map(fm => fm).map(fm => this.RemoveFilterModels([fm]));
+            //this.updateHistogram();
+        } else {
+            this.OverridingFilters.length = 0;
+            if (this._stackedFilters.length) {
+                this.OverridingFilters.push(...this._stackedFilters.pop()!);
+            }
+            // else 
+            //     this.updateHistogram();
+        }
+    }
+
     private getAggregateParameters(histoX: AttributeTransformationModel, histoY: AttributeTransformationModel, histoValue: AttributeTransformationModel) {
         let allAttributes = new Array<AttributeTransformationModel>(histoX, histoY, histoValue);
         allAttributes = ArrayUtil.Distinct(allAttributes.filter(a => a.AggregateFunction !== AggregateFunction.None));
@@ -120,7 +148,7 @@ export class HistogramOperation extends BaseOperation implements IBaseFilterCons
 
     @action
     public async Update(): Promise<void> {
-        this.BrushColors = this.BrushLinks.map(e => e.l.GetNumber(KeyStore.BackgroundColor, 0));
+        this.BrushColors = this.BrushLinks.map(e => NumCast(e.l.backgroundColor));
         return super.Update();
     }
 }
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 3b5a5b470..779b07ce5 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -1,9 +1,9 @@
 import { computed, observable } from 'mobx';
-import { Document } from "../../fields/Document";
-import { FieldWaiting } from '../../fields/Field';
-import { KeyStore } from '../../fields/KeyStore';
-import { ListField } from '../../fields/ListField';
 import { DocumentView } from '../views/nodes/DocumentView';
+import { Doc } from '../../new_fields/Doc';
+import { FieldValue, Cast, BoolCast } from '../../new_fields/Types';
+import { listSpec } from '../../new_fields/Schema';
+import { SelectionManager } from './SelectionManager';
 
 
 export class DocumentManager {
@@ -25,28 +25,29 @@ export class DocumentManager {
         // this.DocumentViews = new Array<DocumentView>();
     }
 
-    public getDocumentView(toFind: Document): DocumentView | null {
+    public getDocumentView(toFind: Doc): DocumentView | null {
 
-        let toReturn: DocumentView | null;
-        toReturn = null;
+        let toReturn: DocumentView | null = null;
 
         //gets document view that is in a freeform canvas collection
         DocumentManager.Instance.DocumentViews.map(view => {
-            let doc = view.props.Document;
-
-            if (doc === toFind) {
+            if (view.props.Document === toFind) {
                 toReturn = view;
                 return;
             }
-            let docSrc = doc.GetT(KeyStore.Prototype, Document);
-            if (docSrc && docSrc !== FieldWaiting && Object.is(docSrc, toFind)) {
-                toReturn = view;
-            }
         });
+        if (!toReturn) {
+            DocumentManager.Instance.DocumentViews.map(view => {
+                let doc = view.props.Document.proto;
+                if (doc && Object.is(doc, toFind)) {
+                    toReturn = view;
+                }
+            });
+        }
 
         return toReturn;
     }
-    public getDocumentViews(toFind: Document): DocumentView[] {
+    public getDocumentViews(toFind: Doc): DocumentView[] {
 
         let toReturn: DocumentView[] = [];
 
@@ -58,8 +59,8 @@ export class DocumentManager {
             if (doc === toFind) {
                 toReturn.push(view);
             } else {
-                let docSrc = doc.GetT(KeyStore.Prototype, Document);
-                if (docSrc && docSrc !== FieldWaiting && Object.is(docSrc, toFind)) {
+                let docSrc = FieldValue(doc.proto);
+                if (docSrc && Object.is(docSrc, toFind)) {
                     toReturn.push(view);
                 }
             }
@@ -70,21 +71,34 @@ export class DocumentManager {
 
     @computed
     public get LinkedDocumentViews() {
-        return DocumentManager.Instance.DocumentViews.reduce((pairs, dv) => {
-            let linksList = dv.props.Document.GetT(KeyStore.LinkedToDocs, ListField);
-            if (linksList && linksList !== FieldWaiting && linksList.Data.length) {
-                pairs.push(...linksList.Data.reduce((pairs, link) => {
-                    if (link instanceof Document) {
-                        let linkToDoc = link.GetT(KeyStore.LinkedToDocs, Document);
-                        if (linkToDoc && linkToDoc !== FieldWaiting) {
+        return DocumentManager.Instance.DocumentViews.filter(dv => dv.isSelected() || BoolCast(dv.props.Document.libraryBrush, false)).reduce((pairs, dv) => {
+            let linksList = Cast(dv.props.Document.linkedToDocs, listSpec(Doc), []).filter(d => d).map(d => d as Doc);
+            if (linksList && linksList.length) {
+                pairs.push(...linksList.reduce((pairs, link) => {
+                    if (link) {
+                        let linkToDoc = FieldValue(Cast(link.linkedTo, Doc));
+                        if (linkToDoc) {
                             DocumentManager.Instance.getDocumentViews(linkToDoc).map(docView1 =>
                                 pairs.push({ a: dv, b: docView1, l: link }));
                         }
                     }
                     return pairs;
-                }, [] as { a: DocumentView, b: DocumentView, l: Document }[]));
+                }, [] as { a: DocumentView, b: DocumentView, l: Doc }[]));
+            }
+            linksList = Cast(dv.props.Document.linkedFromDocs, listSpec(Doc), []).filter(d => d).map(d => d as Doc);
+            if (linksList && linksList.length) {
+                pairs.push(...linksList.reduce((pairs, link) => {
+                    if (link) {
+                        let linkFromDoc = FieldValue(Cast(link.linkedFrom, Doc));
+                        if (linkFromDoc) {
+                            DocumentManager.Instance.getDocumentViews(linkFromDoc).map(docView1 =>
+                                pairs.push({ a: dv, b: docView1, l: link }));
+                        }
+                    }
+                    return pairs;
+                }, pairs));
             }
             return pairs;
-        }, [] as { a: DocumentView, b: DocumentView, l: Document }[]);
+        }, [] as { a: DocumentView, b: DocumentView, l: Doc }[]);
     }
 }
\ No newline at end of file
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 4bd654e15..266679c16 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -1,14 +1,12 @@
-import { action } from "mobx";
-import { Document } from "../../fields/Document";
-import { FieldWaiting } from "../../fields/Field";
-import { KeyStore } from "../../fields/KeyStore";
+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 { DocumentDecorations } from "../views/DocumentDecorations";
 import * as globalCssVariables from "../views/globalCssVariables.scss";
-import { MainOverlayTextBox } from "../views/MainOverlayTextBox";
 
-export function SetupDrag(_reference: React.RefObject<HTMLDivElement>, docFunc: () => Document, moveFunc?: DragManager.MoveFunction, copyOnDrop: boolean = false) {
+export type dropActionType = "alias" | "copy" | undefined;
+export function SetupDrag(_reference: React.RefObject<HTMLDivElement>, docFunc: () => Doc, moveFunc?: DragManager.MoveFunction, dropAction?: dropActionType) {
     let onRowMove = action((e: PointerEvent): void => {
         e.stopPropagation();
         e.preventDefault();
@@ -16,7 +14,7 @@ export function SetupDrag(_reference: React.RefObject<HTMLDivElement>, docFunc:
         document.removeEventListener("pointermove", onRowMove);
         document.removeEventListener('pointerup', onRowUp);
         var dragData = new DragManager.DocumentDragData([docFunc()]);
-        dragData.copyOnDrop = copyOnDrop;
+        dragData.dropAction = dropAction;
         dragData.moveDocument = moveFunc;
         DragManager.StartDocumentDrag([_reference.current!], dragData, e.x, e.y);
     });
@@ -40,19 +38,21 @@ export function SetupDrag(_reference: React.RefObject<HTMLDivElement>, docFunc:
     return onItemDown;
 }
 
-export async function DragLinksAsDocuments(dragEle: HTMLElement, x: number, y: number, sourceDoc: Document) {
-    let srcTarg = sourceDoc.GetT(KeyStore.Prototype, Document);
-    let draggedDocs = (srcTarg && srcTarg !== FieldWaiting) ?
-        srcTarg.GetList(KeyStore.LinkedToDocs, [] as Document[]).map(linkDoc =>
-            (linkDoc.GetT(KeyStore.LinkedToDocs, Document)) as Document) : [];
-    let draggedFromDocs = (srcTarg && srcTarg !== FieldWaiting) ?
-        srcTarg.GetList(KeyStore.LinkedFromDocs, [] as Document[]).map(linkDoc =>
-            (linkDoc.GetT(KeyStore.LinkedFromDocs, Document)) as Document) : [];
+export async function DragLinksAsDocuments(dragEle: HTMLElement, x: number, y: number, sourceDoc: Doc) {
+    let srcTarg = sourceDoc.proto;
+    let draggedDocs: Doc[] = [];
+    let draggedFromDocs: Doc[] = []
+    if (srcTarg) {
+        let linkToDocs = await DocListCast(srcTarg.linkedToDocs);
+        let linkFromDocs = await DocListCast(srcTarg.linkedFromDocs);
+        if (linkToDocs) draggedDocs = linkToDocs.map(linkDoc => Cast(linkDoc.linkedTo, Doc) as Doc);
+        if (linkFromDocs) draggedFromDocs = linkFromDocs.map(linkDoc => Cast(linkDoc.linkedFrom, Doc) as Doc);
+    }
     draggedDocs.push(...draggedFromDocs);
     if (draggedDocs.length) {
-        let moddrag = [] as Document[];
+        let moddrag: Doc[] = [];
         for (const draggedDoc of draggedDocs) {
-            let doc = await draggedDoc.GetTAsync(KeyStore.AnnotationOn, Document);
+            let doc = await Cast(draggedDoc.annotationOn, Doc);
             if (doc) moddrag.push(doc);
         }
         let dragData = new DragManager.DocumentDragData(moddrag.length ? moddrag : draggedDocs);
@@ -134,35 +134,46 @@ export namespace DragManager {
         };
     }
 
-    export type MoveFunction = (document: Document, targetCollection: Document, addDocument: (document: Document) => boolean) => boolean;
+    export type MoveFunction = (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
     export class DocumentDragData {
-        constructor(dragDoc: Document[]) {
+        constructor(dragDoc: Doc[]) {
             this.draggedDocuments = dragDoc;
             this.droppedDocuments = dragDoc;
             this.xOffset = 0;
             this.yOffset = 0;
         }
-        draggedDocuments: Document[];
-        droppedDocuments: Document[];
+        draggedDocuments: Doc[];
+        droppedDocuments: Doc[];
         xOffset: number;
         yOffset: number;
-        aliasOnDrop?: boolean;
-        copyOnDrop?: boolean;
+        dropAction: dropActionType;
+        userDropAction: dropActionType;
         moveDocument?: MoveFunction;
         [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.aliasOnDrop ? dragData.draggedDocuments.map(d => d.CreateAlias()) : dragData.copyOnDrop ? dragData.draggedDocuments.map(d => d.Copy(true) as Document) : dragData.draggedDocuments));
+            (dropData: { [id: string]: any }) =>
+                (dropData.droppedDocuments = dragData.userDropAction === "alias" || (!dragData.userDropAction && dragData.dropAction === "alias") ?
+                    dragData.draggedDocuments.map(d => Doc.MakeAlias(d)) :
+                    dragData.userDropAction === "copy" || (!dragData.userDropAction && dragData.dropAction === "copy") ?
+                        dragData.draggedDocuments.map(d => Doc.MakeCopy(d, true)) :
+                        dragData.draggedDocuments));
     }
 
     export class LinkDragData {
-        constructor(linkSourceDoc: Document) {
+        constructor(linkSourceDoc: Doc, blacklist: Doc[] = []) {
             this.linkSourceDocument = linkSourceDoc;
+            this.blacklist = blacklist;
         }
-        droppedDocuments: Document[] = [];
-        linkSourceDocument: Document;
+        droppedDocuments: Doc[] = [];
+        linkSourceDocument: Doc;
+        blacklist: Doc[];
+        dontClearTextBox?: boolean;
         [id: string]: any;
     }
 
@@ -170,20 +181,22 @@ export namespace DragManager {
         StartDrag([ele], dragData, downX, downY, options);
     }
 
+    export let AbortDrag: () => void = emptyFunction;
+
     function StartDrag(eles: HTMLElement[], dragData: { [id: string]: any }, downX: number, downY: number, options?: DragOptions, finishDrag?: (dropData: { [id: string]: any }) => void) {
         if (!dragDiv) {
             dragDiv = document.createElement("div");
             dragDiv.className = "dragManager-dragDiv";
+            dragDiv.style.pointerEvents = "none";
             DragManager.Root().appendChild(dragDiv);
         }
-        MainOverlayTextBox.Instance.SetTextDoc();
 
         let scaleXs: number[] = [];
         let scaleYs: number[] = [];
         let xs: number[] = [];
         let ys: number[] = [];
 
-        const docs: Document[] =
+        const docs: Doc[] =
             dragData instanceof DocumentDragData ? dragData.draggedDocuments : [];
         let dragElements = eles.map(ele => {
             const w = ele.offsetWidth,
@@ -204,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})`;
@@ -247,10 +261,10 @@ export namespace DragManager {
             e.stopPropagation();
             e.preventDefault();
             if (dragData instanceof DocumentDragData) {
-                dragData.aliasOnDrop = e.ctrlKey || e.altKey;
+                dragData.userDropAction = e.ctrlKey || e.altKey ? "alias" : undefined;
             }
             if (e.shiftKey) {
-                abortDrag();
+                AbortDrag();
                 CollectionDockingView.Instance.StartOtherDrag(docs, {
                     pageX: e.pageX,
                     pageY: e.pageY,
@@ -269,31 +283,47 @@ export namespace DragManager {
             );
         };
 
-        const abortDrag = () => {
+        let hideDragElements = () => {
+            dragElements.map(dragElement => dragElement.parentNode === dragDiv && dragDiv.removeChild(dragElement));
+            eles.map(ele => (ele.hidden = false));
+        };
+        let endDrag = () => {
             document.removeEventListener("pointermove", moveHandler, true);
             document.removeEventListener("pointerup", upHandler);
-            dragElements.map(dragElement => dragDiv.removeChild(dragElement));
-            eles.map(ele => (ele.hidden = false));
+            if (options) {
+                options.handlers.dragComplete({});
+            }
+        };
+
+        AbortDrag = () => {
+            hideDragElements();
+            endDrag();
         };
         const upHandler = (e: PointerEvent) => {
-            abortDrag();
-            FinishDrag(eles, e, dragData, options, finishDrag);
+            hideDragElements();
+            dispatchDrag(eles, e, dragData, options, finishDrag);
+            endDrag();
         };
         document.addEventListener("pointermove", moveHandler, true);
         document.addEventListener("pointerup", upHandler);
     }
 
-    function FinishDrag(dragEles: HTMLElement[], e: PointerEvent, dragData: { [index: string]: any }, options?: DragOptions, finishDrag?: (dragData: { [index: string]: any }) => void) {
+    function dispatchDrag(dragEles: HTMLElement[], e: PointerEvent, dragData: { [index: string]: any }, options?: DragOptions, finishDrag?: (dragData: { [index: string]: any }) => void) {
         let removed = dragEles.map(dragEle => {
-            let parent = dragEle.parentElement;
-            if (parent) parent.removeChild(dragEle);
-            return [dragEle, parent];
+            // let parent = dragEle.parentElement;
+            // if (parent) parent.removeChild(dragEle);
+            let ret = [dragEle, dragEle.style.width, dragEle.style.height];
+            dragEle.style.width = "0";
+            dragEle.style.height = "0";
+            return ret;
         });
         const target = document.elementFromPoint(e.x, e.y);
         removed.map(r => {
-            let dragEle = r[0];
-            let parent = r[1];
-            if (parent && dragEle) parent.appendChild(dragEle);
+            let dragEle = r[0] as HTMLElement;
+            dragEle.style.width = r[1] as string;
+            dragEle.style.height = r[2] as string;
+            // let parent = r[1];
+            // if (parent && dragEle) parent.appendChild(dragEle);
         });
         if (target) {
             if (finishDrag) finishDrag(dragData);
@@ -308,11 +338,6 @@ export namespace DragManager {
                     }
                 })
             );
-
-            if (options) {
-                options.handlers.dragComplete({});
-            }
         }
-        DocumentDecorations.Instance.Hidden = false;
     }
 }
diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts
index d2665b0fc..964720b06 100644
--- a/src/client/util/ProsemirrorExampleTransfer.ts
+++ b/src/client/util/ProsemirrorExampleTransfer.ts
@@ -7,7 +7,6 @@ import { wrapInList, splitListItem, liftListItem, sinkListItem } from "prosemirr
 import { undo, redo } from "prosemirror-history";
 import { undoInputRule } from "prosemirror-inputrules";
 import { Transaction, EditorState } from "prosemirror-state";
-import { MenuItem } from "prosemirror-menu";
 
 const mac = typeof navigator !== "undefined" ? /Mac/.test(navigator.platform) : false;
 
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 9ef71e305..c0e6f7899 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -84,6 +84,7 @@ export const nodes: { [index: string]: NodeSpec } = {
         inline: true,
         attrs: {
             src: {},
+            width: { default: "100px" },
             alt: { default: null },
             title: { default: null }
         },
@@ -94,11 +95,16 @@ export const nodes: { [index: string]: NodeSpec } = {
                 return {
                     src: dom.getAttribute("src"),
                     title: dom.getAttribute("title"),
-                    alt: dom.getAttribute("alt")
-                };
+                    alt: dom.getAttribute("alt"),
+                    width: Math.min(100, Number(dom.getAttribute("width"))),
+                }
             }
         }],
-        toDOM(node: any) { return ["img", node.attrs]; }
+        // 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 }]
+        }
     },
 
     // :: NodeSpec A hard line break, represented in the DOM as `<br>`.
@@ -290,6 +296,13 @@ export const marks: { [index: string]: MarkSpec } = {
         }]
     },
 
+    p14: {
+        parseDOM: [{ style: 'font-size: 14px;' }],
+        toDOM: () => ['span', {
+            style: 'font-size: 14px;'
+        }]
+    },
+
     p16: {
         parseDOM: [{ style: 'font-size: 16px;' }],
         toDOM: () => ['span', {
@@ -325,7 +338,75 @@ export const marks: { [index: string]: MarkSpec } = {
         }]
     },
 };
+function getFontSize(element: any) {
+    return parseFloat((getComputedStyle(element) as any).fontSize);
+}
+
+export class ImageResizeView {
+    _handle: HTMLElement;
+    _img: HTMLElement;
+    _outer: HTMLElement;
+    constructor(node: any, view: any, getPos: any) {
+        this._handle = document.createElement("span");
+        this._img = document.createElement("img");
+        this._outer = document.createElement("span");
+        this._outer.style.position = "relative";
+        this._outer.style.width = node.attrs.width;
+        this._outer.style.display = "inline-block";
+        this._outer.style.overflow = "hidden";
+
+        this._img.setAttribute("src", node.attrs.src);
+        this._img.style.width = "100%";
+        this._handle.style.position = "absolute";
+        this._handle.style.width = "20px";
+        this._handle.style.height = "20px";
+        this._handle.style.backgroundColor = "blue";
+        this._handle.style.borderRadius = "15px";
+        this._handle.style.display = "none";
+        this._handle.style.bottom = "-10px";
+        this._handle.style.right = "-10px";
+        let self = this;
+        this._handle.onpointerdown = function (e: any) {
+            e.preventDefault();
+            e.stopPropagation();
+            const startX = e.pageX;
+            const startWidth = parseFloat(node.attrs.width);
+            const onpointermove = (e: any) => {
+                const currentX = e.pageX;
+                const diffInPx = currentX - startX;
+                self._outer.style.width = `${startWidth + diffInPx}`;
+            }
+
+            const onpointerup = () => {
+                document.removeEventListener("pointermove", onpointermove);
+                document.removeEventListener("pointerup", onpointerup);
+                view.dispatch(
+                    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)
+        }
 
+        this._outer.appendChild(this._handle);
+        this._outer.appendChild(this._img);
+        (this as any).dom = this._outer;
+    }
+
+    selectNode() {
+        this._img.classList.add("ProseMirror-selectednode");
+
+        this._handle.style.display = "";
+    }
+
+    deselectNode() {
+        this._img.classList.remove("ProseMirror-selectednode");
+
+        this._handle.style.display = "none";
+    }
+}
 // :: Schema
 // This schema rougly corresponds to the document schema used by
 // [CommonMark](http://commonmark.org/), minus the list elements,
diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
index c67cc067a..e45f61c11 100644
--- a/src/client/util/Scripting.ts
+++ b/src/client/util/Scripting.ts
@@ -1,13 +1,5 @@
 // import * as ts from "typescript"
 let ts = (window as any).ts;
-import { Opt, Field } from "../../fields/Field";
-import { Document } from "../../fields/Document";
-import { NumberField } from "../../fields/NumberField";
-import { ImageField } from "../../fields/ImageField";
-import { TextField } from "../../fields/TextField";
-import { RichTextField } from "../../fields/RichTextField";
-import { KeyStore } from "../../fields/KeyStore";
-import { ListField } from "../../fields/ListField";
 // // @ts-ignore
 // import * as typescriptlib from '!!raw-loader!../../../node_modules/typescript/lib/lib.d.ts'
 // // @ts-ignore
@@ -15,8 +7,11 @@ import { ListField } from "../../fields/ListField";
 
 // @ts-ignore
 import * as typescriptlib from '!!raw-loader!./type_decls.d';
-import { Documents } from "../documents/Documents";
-import { Key } from "../../fields/Key";
+import { Docs } from "../documents/Documents";
+import { Doc, Field } from '../../new_fields/Doc';
+import { ImageField, PdfField, VideoField, AudioField } from '../../new_fields/URLField';
+import { List } from '../../new_fields/List';
+import { RichTextField } from '../../new_fields/RichTextField';
 
 export interface ScriptSucccess {
     success: true;
@@ -50,9 +45,9 @@ function Run(script: string | undefined, customParams: string[], diagnostics: an
         return { compiled: false, errors: diagnostics };
     }
 
-    let fieldTypes = [Document, NumberField, TextField, ImageField, RichTextField, ListField, Key];
-    let paramNames = ["KeyStore", "Documents", ...fieldTypes.map(fn => fn.name)];
-    let params: any[] = [KeyStore, Documents, ...fieldTypes];
+    let fieldTypes = [Doc, ImageField, PdfField, VideoField, AudioField, List, RichTextField];
+    let paramNames = ["Docs", ...fieldTypes.map(fn => fn.name)];
+    let params: any[] = [Docs, ...fieldTypes];
     let compiledFunction = new Function(...paramNames, `return ${script}`);
     let { capturedVariables = {} } = options;
     let run = (args: { [name: string]: any } = {}): ScriptResult => {
@@ -171,17 +166,4 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp
     let diagnostics = ts.getPreEmitDiagnostics(program).concat(testResult.diagnostics);
 
     return Run(outputText, paramNames, diagnostics, script, options);
-}
-
-export function OrLiteralType(returnType: string): string {
-    return `${returnType} | string | number`;
-}
-
-export function ToField(data: any): Opt<Field> {
-    if (typeof data === "string") {
-        return new TextField(data);
-    } else if (typeof data === "number") {
-        return new NumberField(data);
-    }
-    return undefined;
 }
\ No newline at end of file
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index b15a93d9f..8c92c2023 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -1,8 +1,8 @@
 import { observable, action } from "mobx";
+import { Doc } from "../../new_fields/Doc";
 import { DocumentView } from "../views/nodes/DocumentView";
-import { Document } from "../../fields/Document";
-import { Main } from "../views/Main";
-import { MainOverlayTextBox } from "../views/MainOverlayTextBox";
+import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
+import { NumCast } from "../../new_fields/Types";
 
 export namespace SelectionManager {
     class Manager {
@@ -26,7 +26,7 @@ export namespace SelectionManager {
         DeselectAll(): void {
             manager.SelectedDocuments.map(dv => dv.props.whenActiveChanged(false));
             manager.SelectedDocuments = [];
-            MainOverlayTextBox.Instance.SetTextDoc();
+            FormattedTextBox.InputBoxOverlay = undefined;
         }
         @action
         ReselectAll() {
@@ -36,7 +36,7 @@ export namespace SelectionManager {
         }
         @action
         ReselectAll2(sdocs: DocumentView[]) {
-            sdocs.map(s => SelectionManager.SelectDoc(s, false));
+            sdocs.map(s => SelectionManager.SelectDoc(s, true));
         }
     }
 
@@ -50,7 +50,7 @@ export namespace SelectionManager {
         return manager.SelectedDocuments.indexOf(doc) !== -1;
     }
 
-    export function DeselectAll(except?: Document): void {
+    export function DeselectAll(except?: Doc): void {
         let found: DocumentView | undefined = undefined;
         if (except) {
             for (const view of manager.SelectedDocuments) {
@@ -64,9 +64,25 @@ export namespace SelectionManager {
 
     export function ReselectAll() {
         let sdocs = manager.ReselectAll();
-        manager.ReselectAll2(sdocs);
+        setTimeout(() => manager.ReselectAll2(sdocs), 0);
     }
     export function SelectedDocuments(): Array<DocumentView> {
         return manager.SelectedDocuments;
     }
+    export function ViewsSortedVertically(): DocumentView[] {
+        let sorted = SelectionManager.SelectedDocuments().slice().sort((doc1, doc2) => {
+            if (NumCast(doc1.props.Document.x) > NumCast(doc2.props.Document.x)) return 1;
+            if (NumCast(doc1.props.Document.x) < NumCast(doc2.props.Document.x)) return -1;
+            return 0;
+        });
+        return sorted;
+    }
+    export function ViewsSortedHorizontally(): DocumentView[] {
+        let sorted = SelectionManager.SelectedDocuments().slice().sort((doc1, doc2) => {
+            if (NumCast(doc1.props.Document.y) > NumCast(doc2.props.Document.y)) return 1;
+            if (NumCast(doc1.props.Document.y) < NumCast(doc2.props.Document.y)) return -1;
+            return 0;
+        });
+        return sorted;
+    }
 }
diff --git a/src/client/util/SerializationHelper.ts b/src/client/util/SerializationHelper.ts
new file mode 100644
index 000000000..7ded85e43
--- /dev/null
+++ b/src/client/util/SerializationHelper.ts
@@ -0,0 +1,130 @@
+import { PropSchema, serialize, deserialize, custom, setDefaultModelSchema, getDefaultModelSchema, primitive, SKIP } from "serializr";
+import { Field } from "../../new_fields/Doc";
+
+export namespace SerializationHelper {
+    let serializing: number = 0;
+    export function IsSerializing() {
+        return serializing > 0;
+    }
+
+    export function Serialize(obj: Field): any {
+        if (obj === undefined || obj === null) {
+            return undefined;
+        }
+
+        if (typeof obj !== 'object') {
+            return obj;
+        }
+
+        serializing += 1;
+        if (!(obj.constructor.name in reverseMap)) {
+            throw Error(`type '${obj.constructor.name}' not registered. Make sure you register it using a @Deserializable decorator`);
+        }
+
+        const json = serialize(obj);
+        json.__type = reverseMap[obj.constructor.name];
+        serializing -= 1;
+        return json;
+    }
+
+    export function Deserialize(obj: any): any {
+        if (obj === undefined || obj === null) {
+            return undefined;
+        }
+
+        if (typeof obj !== 'object') {
+            return obj;
+        }
+
+        serializing += 1;
+        if (!obj.__type) {
+            throw Error("No property 'type' found in JSON.");
+        }
+
+        if (!(obj.__type in serializationTypes)) {
+            throw Error(`type '${obj.__type}' not registered. Make sure you register it using a @Deserializable decorator`);
+        }
+
+        const value = deserialize(serializationTypes[obj.__type], obj);
+        serializing -= 1;
+        return value;
+    }
+}
+
+let serializationTypes: { [name: string]: any } = {};
+let reverseMap: { [ctor: string]: string } = {};
+
+export interface DeserializableOpts {
+    (constructor: { new(...args: any[]): any }): void;
+    withFields(fields: string[]): Function;
+}
+
+export function Deserializable(name: string): DeserializableOpts;
+export function Deserializable(constructor: { new(...args: any[]): any }): void;
+export function Deserializable(constructor: { new(...args: any[]): any } | string): DeserializableOpts | void {
+    function addToMap(name: string, ctor: { new(...args: any[]): any }) {
+        const schema = getDefaultModelSchema(ctor) as any;
+        if (schema.targetClass !== ctor) {
+            const newSchema = { ...schema, factory: () => new ctor() };
+            setDefaultModelSchema(ctor, newSchema);
+        }
+        if (!(name in serializationTypes)) {
+            serializationTypes[name] = ctor;
+            reverseMap[ctor.name] = name;
+        } else {
+            throw new Error(`Name ${name} has already been registered as deserializable`);
+        }
+    }
+    if (typeof constructor === "string") {
+        return Object.assign((ctor: { new(...args: any[]): any }) => {
+            addToMap(constructor, ctor);
+        }, { withFields: Deserializable.withFields });
+    }
+    addToMap(constructor.name, constructor);
+}
+
+export namespace Deserializable {
+    export function withFields(fields: string[]) {
+        return function (constructor: { new(...fields: any[]): any }) {
+            Deserializable(constructor);
+            let schema = getDefaultModelSchema(constructor);
+            if (schema) {
+                schema.factory = context => {
+                    const args = fields.map(key => context.json[key]);
+                    return new constructor(...args);
+                };
+                // TODO A modified version of this would let us not reassign fields that we're passing into the constructor later on in deserializing
+                // fields.forEach(field => {
+                //     if (field in schema.props) {
+                //         let propSchema = schema.props[field];
+                //         if (propSchema === false) {
+                //             return;
+                //         } else if (propSchema === true) {
+                //             propSchema = primitive();
+                //         }
+                //         schema.props[field] = custom(propSchema.serializer,
+                //             () => {
+                //                 return SKIP;
+                //             });
+                //     }
+                // });
+            } else {
+                schema = {
+                    props: {},
+                    factory: context => {
+                        const args = fields.map(key => context.json[key]);
+                        return new constructor(...args);
+                    }
+                };
+                setDefaultModelSchema(constructor, schema);
+            }
+        };
+    }
+}
+
+export function autoObject(): PropSchema {
+    return custom(
+        (s) => SerializationHelper.Serialize(s),
+        (s) => SerializationHelper.Deserialize(s)
+    );
+}
\ No newline at end of file
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 7a6f90dd0..18ec5732b 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -11,36 +11,52 @@ 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 { wrapInList, bulletList, liftListItem, listItem, } from 'prosemirror-schema-list';
+import { liftTarget, RemoveMarkStep, AddMarkStep } from 'prosemirror-transform';
 import {
     faListUl, faGrinTongueSquint,
 } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { FieldViewProps } from "../views/nodes/FieldView";
 import { throwStatement } from "babel-types";
-import { KeyStore } from "../../fields/KeyStore";
-import { NumberField } from "../../fields/NumberField";
 const { openPrompt, TextField } = require("./ProsemirrorCopy/prompt.js");
+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";
 
 //appears above a selection of text in a RichTextBox to give user options such as Bold, Italics, etc.
 export class TooltipTextMenu {
 
-    private tooltip: HTMLElement;
+    public tooltip: HTMLElement;
     private num_icons = 0;
     private view: EditorView;
     private fontStyles: MarkType[];
     private fontSizes: MarkType[];
+    private listTypes: NodeType[];
     private editorProps: FieldViewProps;
     private state: EditorState;
     private fontSizeToNum: Map<MarkType, number>;
     private fontStylesToName: Map<MarkType, string>;
+    private listTypeToIcon: Map<NodeType, string>;
     private fontSizeIndicator: HTMLSpanElement = document.createElement("span");
     private link: HTMLAnchorElement;
+
     //dropdown doms
     private fontSizeDom?: Node;
     private fontStyleDom?: Node;
+    private listTypeBtnDom?: Node;
+
+    private linkEditor?: HTMLDivElement;
+    private linkText?: HTMLDivElement;
+    private linkDrag?: HTMLImageElement;
 
     constructor(view: EditorView, editorProps: FieldViewProps) {
         this.view = view;
@@ -62,8 +78,9 @@ export class TooltipTextMenu {
             { command: toggleMark(schema.marks.strikethrough), dom: this.icon("S", "strikethrough") },
             { command: toggleMark(schema.marks.superscript), dom: this.icon("s", "superscript") },
             { command: toggleMark(schema.marks.subscript), dom: this.icon("s", "subscript") },
-            { command: wrapInList(schema.nodes.bullet_list), dom: this.icon(":", "bullets") },
-            { command: lift, dom: this.icon("<", "lift") },
+            // { command: wrapInList(schema.nodes.bullet_list), dom: this.icon(":", "bullets") },
+            // { command: wrapInList(schema.nodes.ordered_list), dom: this.icon("1)", "bullets") },
+            // { command: lift, dom: this.icon("<", "lift") },
         ];
         //add menu items
         items.forEach(({ dom, command }) => {
@@ -93,6 +110,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);
@@ -100,7 +118,11 @@ export class TooltipTextMenu {
         this.fontSizeToNum.set(schema.marks.p72, 72);
         this.fontSizes = Array.from(this.fontSizeToNum.keys());
 
-        //this.addFontDropdowns();
+        //list types
+        this.listTypeToIcon = new Map();
+        this.listTypeToIcon.set(schema.nodes.bullet_list, ":");
+        this.listTypeToIcon.set(schema.nodes.ordered_list, "1)");
+        this.listTypes = Array.from(this.listTypeToIcon.keys());
 
         this.link = document.createElement("a");
         this.link.target = "_blank";
@@ -118,7 +140,7 @@ export class TooltipTextMenu {
         //font SIZES
         let fontSizeBtns: MenuItem[] = [];
         this.fontSizeToNum.forEach((number, mark) => {
-            fontSizeBtns.push(this.dropdownBtn(String(number), "width: 50px;", mark, this.view, this.changeToMarkInGroup, this.fontSizes));
+            fontSizeBtns.push(this.dropdownMarkBtn(String(number), "width: 50px;", mark, this.view, this.changeToMarkInGroup, this.fontSizes));
         });
 
         if (this.fontSizeDom) { this.tooltip.removeChild(this.fontSizeDom); }
@@ -137,7 +159,7 @@ export class TooltipTextMenu {
         //font STYLES
         let fontBtns: MenuItem[] = [];
         this.fontStylesToName.forEach((name, mark) => {
-            fontBtns.push(this.dropdownBtn(name, "font-family: " + name + ", sans-serif; width: 125px;", mark, this.view, this.changeToMarkInGroup, this.fontStyles));
+            fontBtns.push(this.dropdownMarkBtn(name, "font-family: " + name + ", sans-serif; width: 125px;", mark, this.view, this.changeToMarkInGroup, this.fontStyles));
         });
 
         if (this.fontStyleDom) { this.tooltip.removeChild(this.fontStyleDom); }
@@ -149,6 +171,119 @@ 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.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";
+            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();
+                }
+            }
+            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.linkDrag!, 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.linkDrag);
+            this.linkEditor.appendChild(this.linkText);
+            this.linkEditor.appendChild(linkBtn);
+            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
+        if (listTypeBtn) { this.tooltip.removeChild(listTypeBtn); }
+
+        //Make a dropdown of all list types
+        let toAdd: MenuItem[] = [];
+        this.listTypeToIcon.forEach((icon, type) => {
+            toAdd.push(this.dropdownNodeBtn(icon, "width: 40px;", type, this.view, this.listTypes, this.changeToNodeType));
+        });
+        //option to remove the list formatting
+        toAdd.push(this.dropdownNodeBtn("X", "width: 40px;", undefined, this.view, this.listTypes, this.changeToNodeType));
+
+        listTypeBtn = (new Dropdown(toAdd, {
+            label: label,
+            css: "color:white; width: 40px;"
+        }) as MenuItem).render(this.view).dom;
+
+        //add this new button and return it
+        this.tooltip.appendChild(listTypeBtn);
+        return listTypeBtn;
+    }
+
     //for a specific grouping of marks (passed in), remove all and apply the passed-in one to the selected text
     changeToMarkInGroup(markType: MarkType, view: EditorView, fontMarks: MarkType[]) {
         let { empty, $cursor, ranges } = view.state.selection as TextSelection;
@@ -180,9 +315,18 @@ export class TooltipTextMenu {
         return toggleMark(markType)(view.state, view.dispatch, view);
     }
 
-    //makes a button for the drop down
+    //remove all node typeand apply the passed-in one to the selected text
+    changeToNodeType(nodeType: NodeType | undefined, view: EditorView, allNodes: NodeType[]) {
+        //remove old
+        liftListItem(schema.nodes.list_item)(view.state, view.dispatch);
+        if (nodeType) { //add new
+            wrapInList(nodeType)(view.state, view.dispatch);
+        }
+    }
+
+    //makes a button for the drop down FOR MARKS
     //css is the style you want applied to the button
-    dropdownBtn(label: string, css: string, markType: MarkType, view: EditorView, changeToMarkInGroup: (markType: MarkType<any>, view: EditorView, groupMarks: MarkType[]) => any, groupMarks: MarkType[]) {
+    dropdownMarkBtn(label: string, css: string, markType: MarkType, view: EditorView, changeToMarkInGroup: (markType: MarkType<any>, view: EditorView, groupMarks: MarkType[]) => any, groupMarks: MarkType[]) {
         return new MenuItem({
             title: "",
             label: label,
@@ -233,6 +377,22 @@ export class TooltipTextMenu {
         });
     }
 
+    //makes a button for the drop down FOR NODE TYPES
+    //css is the style you want applied to the button
+    dropdownNodeBtn(label: string, css: string, nodeType: NodeType | undefined, view: EditorView, groupNodes: NodeType[], changeToNodeInGroup: (nodeType: NodeType<any> | undefined, view: EditorView, groupNodes: NodeType[]) => any) {
+        return new MenuItem({
+            title: "",
+            label: label,
+            execEvent: "",
+            class: "menuicon",
+            css: css,
+            enable(state) { return true; },
+            run() {
+                changeToNodeInGroup(nodeType, view, groupNodes);
+            }
+        });
+    }
+
     markActive = function (state: EditorState<any>, type: MarkType<Schema<string, string>>) {
         let { from, $from, to, empty } = state.selection
         if (empty) return type.isInSet(state.storedMarks || $from.marks())
@@ -337,6 +497,9 @@ export class TooltipTextMenu {
         this.tooltip.style.width = 225 + "px";
         this.tooltip.style.bottom = (box.bottom - start.top) * this.editorProps.ScreenToLocalTransform().Scale + "px";
 
+        //UPDATE LIST ITEM DROPDOWN
+        this.listTypeBtnDom = this.updateListItemDropdown(":", this.listTypeBtnDom!);
+
         //UPDATE FONT STYLE DROPDOWN
         let activeStyles = this.activeMarksOnSelection(this.fontStyles);
         if (activeStyles.length === 1) {
@@ -361,9 +524,11 @@ export class TooltipTextMenu {
         } else { //multiple font sizes selected
             this.updateFontSizeDropdown("Various");
         }
+
+        this.updateLinkMenu();
     }
 
-    //finds all active marks on selection
+    //finds all active marks on selection in given group
     activeMarksOnSelection(markGroup: MarkType[]) {
         //current selection
         let { empty, $cursor, ranges } = this.view.state.selection as TextSelection;
diff --git a/src/client/util/UndoManager.ts b/src/client/util/UndoManager.ts
index 27aed4bac..c0ed015bd 100644
--- a/src/client/util/UndoManager.ts
+++ b/src/client/util/UndoManager.ts
@@ -1,7 +1,6 @@
-import { observable, action } from "mobx";
+import { observable, action, runInAction } from "mobx";
 import 'source-map-support/register';
 import { Without } from "../../Utils";
-import { string } from "prop-types";
 
 function getBatchName(target: any, key: string | symbol): string {
     let keyName = key.toString();
@@ -94,6 +93,10 @@ export namespace UndoManager {
         return redoStack.length > 0;
     }
 
+    export function PrintBatches(): void {
+        GetOpenBatches().forEach(batch => console.log(batch.batchName));
+    }
+
     let openBatches: Batch[] = [];
     export function GetOpenBatches(): Without<Batch, 'end'>[] {
         return openBatches;
@@ -140,10 +143,11 @@ export namespace UndoManager {
         }
     });
 
-    export function RunInBatch(fn: () => void, batchName: string) {
+    //TODO Make this return the return value
+    export function RunInBatch<T>(fn: () => T, batchName: string) {
         let batch = StartBatch(batchName);
         try {
-            fn();
+            return runInAction(fn);
         } finally {
             batch.end();
         }
diff --git a/src/client/views/.DS_Store b/src/client/views/.DS_Store
index 0964d5ff3..5008ddfcf 100644
Binary files a/src/client/views/.DS_Store and b/src/client/views/.DS_Store differ
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
new file mode 100644
index 000000000..d6562492f
--- /dev/null
+++ b/src/client/views/DocComponent.tsx
@@ -0,0 +1,14 @@
+import * as React from 'react';
+import { Doc } from '../../new_fields/Doc';
+import { computed } from 'mobx';
+
+export function DocComponent<P extends { Document: Doc }, T>(schemaCtor: (doc: Doc) => T) {
+    class Component extends React.Component<P> {
+        //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then
+        @computed
+        get Document(): T {
+            return schemaCtor(this.props.Document);
+        }
+    }
+    return Component;
+}
\ No newline at end of file
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index c1a949639..6a2e33836 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -1,20 +1,22 @@
 @import "globalCssVariables";
 
+$linkGap : 3px;
 .documentDecorations {
     position: absolute;
 }
-#documentDecorations-container {
+
+.documentDecorations-container {
+    z-index: $docDecorations-zindex;
     position: absolute;
     top: 0;
-    left:0;
+    left: 0;
     display: grid;
-    z-index: $docDecorations-zindex;
     grid-template-rows: 20px 8px 1fr 8px;
-    grid-template-columns: 8px 8px 1fr 8px 8px;
+    grid-template-columns: 8px 16px 1fr 8px 8px;
     pointer-events: none;
 
     #documentDecorations-centerCont {
-        grid-column:3;
+        grid-column: 3;
         background: none;
     }
 
@@ -39,8 +41,8 @@
     #documentDecorations-bottomRightResizer,
     #documentDecorations-topRightResizer,
     #documentDecorations-rightResizer {
-        grid-column-start:5;
-        grid-column-end:7;
+        grid-column-start: 5;
+        grid-column-end: 7;
     }
 
     #documentDecorations-topLeftResizer,
@@ -63,16 +65,17 @@
         cursor: ew-resize;
     }
     .title{
-        width:100%;
         background: lightblue;
-        grid-column-start:3;
+        grid-column-start: 3;
         grid-column-end: 4;
         pointer-events: auto;
+        overflow: hidden;
     }
 }
 
+
 .documentDecorations-closeButton {
-    background:$alt-accent;
+    background: $alt-accent;
     opacity: 0.8;
     grid-column-start: 4;
     grid-column-end: 6;
@@ -80,15 +83,22 @@
     text-align: center;
     cursor: pointer;
 }
+
 .documentDecorations-minimizeButton {
-    background:$alt-accent;
+    background: $alt-accent;
     opacity: 0.8;
     grid-column-start: 1;
     grid-column-end: 3;
     pointer-events: all;
     text-align: center;
     cursor: pointer;
+    position: absolute;
+    left: 0px;
+    top: 0px;
+    width: $MINIMIZED_ICON_SIZE;
+    height: $MINIMIZED_ICON_SIZE;
 }
+
 .documentDecorations-background {
     background: lightblue;
     position: absolute;
@@ -96,8 +106,8 @@
 }
 
 .linkFlyout {
-    grid-column: 1/4;
-    margin-left: 25px;
+    grid-column: 2/4;
+    margin-top: $linkGap;
 }
 
 .linkButton-empty:hover {
@@ -112,35 +122,34 @@
     cursor: pointer;
 }
 
+.link-button-container {
+    grid-column: 1/4;
+    width: auto;
+    height: auto;
+    display: flex;
+    flex-direction: row;
+}
+
 .linkButton-linker {
-    position:absolute;
-    bottom:0px;
-    left: 0px;
+    margin-left: 5px;
+    margin-top: $linkGap;
     height: 20px;
     width: 20px;
-    margin-top: 10px;
-    margin-right: 5px;
+    text-align: center;
     border-radius: 50%;
-    opacity: 0.9;
     pointer-events: auto;
     color: $dark-color;
     border: $dark-color 1px solid;
-    text-transform: uppercase;
-    letter-spacing: 2px;
-    font-size: 75%;
-    transition: transform 0.2s;
-    text-align: center;
-    display: flex;
-    justify-content: center;
-    align-items: center;
 }
-.linkButton-tray {
-    grid-column: 1/4;
+
+.linkButton-linker:hover {
+    cursor: pointer;
+    transform: scale(1.05);
 }
-.linkButton-empty {
+
+.linkButton-empty, .linkButton-nonempty {
     height: 20px;
     width: 20px;
-    margin-top: 10px;
     border-radius: 50%;
     opacity: 0.9;
     pointer-events: auto;
@@ -154,17 +163,19 @@
     display: flex;
     justify-content: center;
     align-items: center;
+
+    &:hover {
+        background: $main-accent;
+        transform: scale(1.05);
+        cursor: pointer;
+    }
 }
 
-.linkButton-nonempty {
-    height: 20px;
-    width: 20px;
-    margin-top: 10px;
-    border-radius: 50%;
-    opacity: 0.9;
+.templating-menu {
+    position: absolute;
+    bottom: 0;
+    left: 50px;
     pointer-events: auto;
-    background-color: $dark-color;
-    color: $light-color;
     text-transform: uppercase;
     letter-spacing: 2px;
     font-size: 75%;
@@ -173,4 +184,43 @@
     display: flex;
     justify-content: center;
     align-items: center;
+}
+
+.fa-icon-link {
+    margin-top: 3px;
+}
+.templating-button {
+    width: 20px;
+    height: 20px;
+    border-radius: 50%;
+    opacity: 0.9;
+    font-size:14;
+    background-color: $dark-color;
+    color: $light-color;
+    text-align: center;
+    cursor: pointer;
+
+    &:hover {
+        background: $main-accent;
+        transform: scale(1.05);
+    }
+}
+
+#template-list {
+    position: absolute;
+    top: 0;
+    left: 30px;
+    width: max-content;
+    font-family: $sans-serif;
+    font-size: 12px;
+    background-color: $light-color-secondary;
+    padding: 2px 12px;
+    list-style: none;
+    .templateToggle {
+        text-align: left;
+    }
+
+    input {
+        margin-right: 10px;
+    }
 }
\ No newline at end of file
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 16fac0694..4786b4de6 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -1,94 +1,126 @@
-import { action, computed, observable } from "mobx";
+import { action, computed, observable, runInAction, untracked, reaction } from "mobx";
 import { observer } from "mobx-react";
-import { Key } from "../../fields/Key";
-import { KeyStore } from "../../fields/KeyStore";
-import { ListField } from "../../fields/ListField";
-import { NumberField } from "../../fields/NumberField";
-import { TextField } from "../../fields/TextField";
-import { emptyFunction } from "../../Utils";
+import { emptyFunction, Utils } from "../../Utils";
 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 } from "./nodes/DocumentView";
+import { DocumentView, PositionDocument } from "./nodes/DocumentView";
 import { LinkMenu } from "./nodes/LinkMenu";
+import { TemplateMenu } from "./TemplateMenu";
 import React = require("react");
+import { Template, Templates } from "./Templates";
+import { CompileScript } from "../util/Scripting";
+import { IconBox } from "./nodes/IconBox";
+import { Cast, FieldValue, NumCast, StrCast } from "../../new_fields/Types";
+import { Doc, FieldResult } from "../../new_fields/Doc";
+import { listSpec } from "../../new_fields/Schema";
+import { Docs } from "../documents/Documents";
+import { List } from "../../new_fields/List";
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
+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 { CollectionView } from "./collections/CollectionView";
+import { DocumentManager } from "../util/DocumentManager";
+import { FormattedTextBox } from "./nodes/FormattedTextBox";
+import { FieldView } from "./nodes/FieldView";
+
+library.add(faLink);
 
 @observer
 export class DocumentDecorations extends React.Component<{}, { value: string }> {
     static Instance: DocumentDecorations;
-    private _resizer = "";
     private _isPointerDown = false;
+    private _resizing = "";
     private keyinput: React.RefObject<HTMLInputElement>;
-    private _documents: DocumentView[] = SelectionManager.SelectedDocuments();
     private _resizeBorderWidth = 16;
-    private _linkBoxHeight = 30;
+    private _linkBoxHeight = 20 + 3; // link button height + margin
     private _titleHeight = 20;
     private _linkButton = React.createRef<HTMLDivElement>();
     private _linkerButton = React.createRef<HTMLDivElement>();
-    //@observable private _title: string = this._documents[0].props.Document.Title;
-    @observable private _title: string = this._documents.length > 0 ? this._documents[0].props.Document.Title : "";
-    @observable private _fieldKey: Key = KeyStore.Title;
+    private _downX = 0;
+    private _downY = 0;
+    private _iconDoc?: Doc = undefined;
+    @observable private _minimizedX = 0;
+    @observable private _minimizedY = 0;
+    @observable private _title: string = "";
+    @observable private _edtingTitle = false;
+    @observable private _fieldKey = "title";
     @observable private _hidden = false;
     @observable private _opacity = 1;
-    @observable private _dragging = false;
-
+    @observable private _removeIcon = false;
+    @observable public Interacting = false;
 
     constructor(props: Readonly<{}>) {
         super(props);
         DocumentDecorations.Instance = this;
-        this.handleChange = this.handleChange.bind(this);
         this.keyinput = React.createRef();
+        reaction(() => SelectionManager.SelectedDocuments().slice(), docs => this._edtingTitle = false);
     }
 
-    @action
-    handleChange = (event: any) => {
-        this._title = event.target.value;
-    }
-
-    @action
-    enterPressed = (e: any) => {
+    @action titleChanged = (event: any) => { this._title = event.target.value; };
+    @action titleBlur = () => { this._edtingTitle = false; };
+    @action titleEntered = (e: any) => {
         var key = e.keyCode || e.which;
         // enter pressed
         if (key === 13) {
             var text = e.target.value;
             if (text[0] === '#') {
-                let command = text.slice(1, text.length);
-                this._fieldKey = new Key(command);
-                // if (command === "Title" || command === "title") {
-                //     this._fieldKey = KeyStore.Title;
-                // }
-                // else if (command === "Width" || command === "width") {
-                //     this._fieldKey = KeyStore.Width;
-                // }
-                this._title = "changed";
-                // TODO: Change field with switch statement
+                this._fieldKey = text.slice(1, text.length);
+                this._title = this.selectionTitle;
             }
             else {
-                if (this._documents.length > 0) {
-                    let field = this._documents[0].props.Document.Get(this._fieldKey);
-                    if (field instanceof TextField) {
-                        this._documents.forEach(d =>
-                            d.props.Document.Set(this._fieldKey, new TextField(this._title)));
+                if (SelectionManager.SelectedDocuments().length > 0) {
+                    let field = SelectionManager.SelectedDocuments()[0].props.Document[this._fieldKey];
+                    if (typeof field === "number") {
+                        SelectionManager.SelectedDocuments().forEach(d => {
+                            let doc = d.props.Document.proto ? d.props.Document.proto : d.props.Document;
+                            doc[this._fieldKey] = +this._title;
+                        });
+                    } else {
+                        SelectionManager.SelectedDocuments().forEach(d => {
+                            let doc = d.props.Document.proto ? d.props.Document.proto : d.props.Document;
+                            doc[this._fieldKey] = this._title;
+                        });
                     }
-                    else if (field instanceof NumberField) {
-                        this._documents.forEach(d =>
-                            d.props.Document.Set(this._fieldKey, new NumberField(+this._title)));
-                    }
-                    this._title = "changed";
                 }
             }
             e.target.blur();
         }
     }
+    @action onTitleDown = (e: React.PointerEvent): void => {
+        this._downX = e.clientX;
+        this._downY = e.clientY;
+        e.stopPropagation();
+        this.onBackgroundDown(e);
+        document.removeEventListener("pointermove", this.onTitleMove);
+        document.removeEventListener("pointerup", this.onTitleUp);
+        document.addEventListener("pointermove", this.onTitleMove);
+        document.addEventListener("pointerup", this.onTitleUp);
+    }
+    @action onTitleMove = (e: PointerEvent): void => {
+        if (Math.abs(e.clientX - this._downX) > 4 || Math.abs(e.clientY - this._downY) > 4) {
+            this.Interacting = true;
+        }
+        if (this.Interacting) this.onBackgroundMove(e);
+        e.stopPropagation();
+    }
+    @action onTitleUp = (e: PointerEvent): void => {
+        if (Math.abs(e.clientX - this._downX) < 4 || Math.abs(e.clientY - this._downY) < 4) {
+            this._title = this.selectionTitle;
+            this._edtingTitle = true;
+        }
+        document.removeEventListener("pointermove", this.onTitleMove);
+        document.removeEventListener("pointerup", this.onTitleUp);
+        this.onBackgroundUp(e);
+    }
 
     @computed
     get Bounds(): { x: number, y: number, b: number, r: number } {
-        this._documents = SelectionManager.SelectedDocuments();
         return SelectionManager.SelectedDocuments().reduce((bounds, documentView) => {
             if (documentView.props.isTopMost) {
                 return bounds;
@@ -103,46 +135,38 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         }, { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE });
     }
 
-
-    @computed
-    public get Hidden() { return this._hidden; }
-    public set Hidden(value: boolean) { this._hidden = value; }
-
-    _lastDrag: number[] = [0, 0];
     onBackgroundDown = (e: React.PointerEvent): void => {
         document.removeEventListener("pointermove", this.onBackgroundMove);
-        document.addEventListener("pointermove", this.onBackgroundMove);
         document.removeEventListener("pointerup", this.onBackgroundUp);
+        document.addEventListener("pointermove", this.onBackgroundMove);
         document.addEventListener("pointerup", this.onBackgroundUp);
-        this._lastDrag = [e.clientX, e.clientY];
         e.stopPropagation();
-        if (e.currentTarget.localName !== "input") {
-            e.preventDefault();
-        }
+        e.preventDefault();
     }
 
     @action
     onBackgroundMove = (e: PointerEvent): void => {
         let dragDocView = SelectionManager.SelectedDocuments()[0];
-        const [left, top] = dragDocView.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
+        const [left, top] = dragDocView.props.ScreenToLocalTransform().scale(dragDocView.props.ContentScaling()).inverse().transformPoint(0, 0);
+        const [xoff, yoff] = dragDocView.props.ScreenToLocalTransform().scale(dragDocView.props.ContentScaling()).transformDirection(e.x - left, e.y - top);
         let dragData = new DragManager.DocumentDragData(SelectionManager.SelectedDocuments().map(dv => dv.props.Document));
-        dragData.aliasOnDrop = false;
-        dragData.xOffset = e.x - left;
-        dragData.yOffset = e.y - top;
-        let move = SelectionManager.SelectedDocuments()[0].props.moveDocument;
-        dragData.moveDocument = move;
-        this._dragging = true;
+        dragData.xOffset = xoff;
+        dragData.yOffset = yoff;
+        dragData.moveDocument = SelectionManager.SelectedDocuments()[0].props.moveDocument;
+        this.Interacting = true;
+        this._hidden = true;
         document.removeEventListener("pointermove", this.onBackgroundMove);
         document.removeEventListener("pointerup", this.onBackgroundUp);
+        document.removeEventListener("pointermove", this.onTitleMove);
+        document.removeEventListener("pointerup", this.onTitleUp);
         DragManager.StartDocumentDrag(SelectionManager.SelectedDocuments().map(docView => docView.ContentDiv!), dragData, e.x, e.y, {
-            handlers: {
-                dragComplete: action(() => this._dragging = false),
-            },
+            handlers: { dragComplete: action(() => this._hidden = this.Interacting = false) },
             hideSource: true
         });
         e.stopPropagation();
     }
 
+    @action
     onBackgroundUp = (e: PointerEvent): void => {
         document.removeEventListener("pointermove", this.onBackgroundMove);
         document.removeEventListener("pointerup", this.onBackgroundUp);
@@ -175,32 +199,121 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
             document.removeEventListener("pointerup", this.onCloseUp);
         }
     }
+    @action
     onMinimizeDown = (e: React.PointerEvent): void => {
         e.stopPropagation();
+        this._iconDoc = undefined;
         if (e.button === 0) {
+            this._downX = e.pageX;
+            this._downY = e.pageY;
+            this._removeIcon = false;
+            let selDoc = SelectionManager.SelectedDocuments()[0];
+            let selDocPos = selDoc.props.ScreenToLocalTransform().scale(selDoc.props.ContentScaling()).inverse().transformPoint(0, 0);
+            this._minimizedX = selDocPos[0] + 12;
+            this._minimizedY = selDocPos[1] + 12;
             document.removeEventListener("pointermove", this.onMinimizeMove);
             document.addEventListener("pointermove", this.onMinimizeMove);
             document.removeEventListener("pointerup", this.onMinimizeUp);
             document.addEventListener("pointerup", this.onMinimizeUp);
         }
     }
+
+    @action
     onMinimizeMove = (e: PointerEvent): void => {
         e.stopPropagation();
+        if (Math.abs(e.pageX - this._downX) > Utils.DRAG_THRESHOLD ||
+            Math.abs(e.pageY - this._downY) > Utils.DRAG_THRESHOLD) {
+            let selDoc = SelectionManager.SelectedDocuments()[0];
+            let selDocPos = selDoc.props.ScreenToLocalTransform().scale(selDoc.props.ContentScaling()).inverse().transformPoint(0, 0);
+            let snapped = Math.abs(e.pageX - selDocPos[0]) < 20 && Math.abs(e.pageY - selDocPos[1]) < 20;
+            this._minimizedX = snapped ? selDocPos[0] + 4 : e.clientX;
+            this._minimizedY = snapped ? selDocPos[1] - 18 : e.clientY;
+            let selectedDocs = SelectionManager.SelectedDocuments().map(sd => sd);
+
+            if (selectedDocs.length > 1) {
+                this._iconDoc = this._iconDoc ? this._iconDoc : this.createIcon(SelectionManager.SelectedDocuments(), CollectionView.LayoutString());
+                this.moveIconDoc(this._iconDoc);
+            } else {
+                this.getIconDoc(selectedDocs[0]).then(icon => icon && this.moveIconDoc(this._iconDoc = icon));
+            }
+            this._removeIcon = snapped;
+        }
     }
+    @action
     onMinimizeUp = (e: PointerEvent): void => {
         e.stopPropagation();
         if (e.button === 0) {
-            SelectionManager.SelectedDocuments().map(dv => dv.minimize());
             document.removeEventListener("pointermove", this.onMinimizeMove);
             document.removeEventListener("pointerup", this.onMinimizeUp);
+            let selectedDocs = SelectionManager.SelectedDocuments().map(sd => sd);
+            if (this._iconDoc && selectedDocs.length === 1 && this._removeIcon) {
+                selectedDocs[0].props.removeDocument && selectedDocs[0].props.removeDocument(this._iconDoc);
+            }
+            if (!this._removeIcon) {
+                if (selectedDocs.length === 1)
+                    this.getIconDoc(selectedDocs[0]).then(icon => selectedDocs[0].props.toggleMinimized());
+                else {
+                    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));
+                    }
+                }
+            }
+            this._removeIcon = false;
         }
+        runInAction(() => this._minimizedX = this._minimizedY = 0);
     }
 
+    @action createIcon = (selected: DocumentView[], layoutString: string): Doc => {
+        let doc = selected[0].props.Document;
+        let iconDoc = Docs.IconDocument(layoutString);
+        iconDoc.isButton = true;
+        iconDoc.title = selected.length > 1 ? "ICONset" : "ICON" + StrCast(doc.title);
+        iconDoc.labelField = this._fieldKey;
+        iconDoc[this._fieldKey] = selected.length > 1 ? "collection" : undefined;
+        iconDoc.isMinimized = false;
+        iconDoc.width = Number(MINIMIZED_ICON_SIZE);
+        iconDoc.height = Number(MINIMIZED_ICON_SIZE);
+        iconDoc.x = NumCast(doc.x);
+        iconDoc.y = NumCast(doc.y) - 24;
+        iconDoc.maximizedDocs = new List<Doc>(selected.map(s => s.props.Document));
+        doc.minimizedDoc = iconDoc;
+        selected[0].props.addDocument && selected[0].props.addDocument(iconDoc, false);
+        return iconDoc;
+    }
+    @action
+    public getIconDoc = async (docView: DocumentView): Promise<Doc | undefined> => {
+        let doc = docView.props.Document;
+        let iconDoc: Doc | undefined = await Cast(doc.minimizedDoc, Doc);
+
+        if (!iconDoc || !DocumentManager.Instance.getDocumentView(iconDoc)) {
+            const layout = StrCast(doc.backgroundLayout, StrCast(doc.layout, FieldView.LayoutString(DocumentView)));
+            iconDoc = this.createIcon([docView], layout);
+        }
+        return iconDoc;
+    }
+    moveIconDoc(iconDoc: Doc) {
+        let selView = SelectionManager.SelectedDocuments()[0];
+        let zoom = NumCast(selView.props.Document.zoomBasis, 1);
+        let where = (selView.props.ScreenToLocalTransform()).scale(selView.props.ContentScaling()).scale(1 / zoom).
+            transformPoint(this._minimizedX - 12, this._minimizedY - 12);
+        iconDoc.x = where[0] + NumCast(selView.props.Document.x);
+        iconDoc.y = where[1] + NumCast(selView.props.Document.y);
+    }
+
+    @action
     onPointerDown = (e: React.PointerEvent): void => {
         e.stopPropagation();
         if (e.button === 0) {
             this._isPointerDown = true;
-            this._resizer = e.currentTarget.id;
+            this._resizing = e.currentTarget.id;
+            this.Interacting = true;
             document.removeEventListener("pointermove", this.onPointerMove);
             document.addEventListener("pointermove", this.onPointerMove);
             document.removeEventListener("pointerup", this.onPointerUp);
@@ -221,11 +334,15 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         e.stopPropagation();
     }
 
+    @action
     onLinkerButtonMoved = (e: PointerEvent): void => {
         if (this._linkerButton.current !== null) {
             document.removeEventListener("pointermove", this.onLinkerButtonMoved);
             document.removeEventListener("pointerup", this.onLinkerButtonUp);
-            let dragData = new DragManager.LinkDragData(SelectionManager.SelectedDocuments()[0].props.Document);
+            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),
@@ -269,7 +386,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
 
         let dX = 0, dY = 0, dW = 0, dH = 0;
 
-        switch (this._resizer) {
+        switch (this._resizing) {
             case "":
                 break;
             case "documentDecorations-topLeftResizer":
@@ -308,39 +425,45 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 break;
         }
 
-        MainOverlayTextBox.Instance.SetTextDoc();
+        runInAction(() => FormattedTextBox.InputBoxOverlay = undefined);
         SelectionManager.SelectedDocuments().forEach(element => {
             const rect = element.ContentDiv ? element.ContentDiv.getBoundingClientRect() : new DOMRect();
 
             if (rect.width !== 0) {
-                let doc = element.props.Document;
-                let width = doc.GetNumber(KeyStore.Width, 0);
-                let nwidth = doc.GetNumber(KeyStore.NativeWidth, 0);
-                let nheight = doc.GetNumber(KeyStore.NativeHeight, 0);
-                let height = doc.GetNumber(KeyStore.Height, nwidth ? nheight / nwidth * width : 0);
-                let x = doc.GetOrCreate(KeyStore.X, NumberField);
-                let y = doc.GetOrCreate(KeyStore.Y, NumberField);
+                let doc = PositionDocument(element.props.Document);
+                let width = FieldValue(doc.width, 0);
+                let nwidth = FieldValue(doc.nativeWidth, 0);
+                let nheight = FieldValue(doc.nativeHeight, 0);
+                let height = FieldValue(doc.height, nwidth ? nheight / nwidth * width : 0);
+                let x = FieldValue(doc.x, 0);
+                let y = FieldValue(doc.y, 0);
                 let scale = width / rect.width;
                 let actualdW = Math.max(width + (dW * scale), 20);
                 let actualdH = Math.max(height + (dH * scale), 20);
-                x.Data += dX * (actualdW - width);
-                y.Data += dY * (actualdH - height);
-                var nativeWidth = doc.GetNumber(KeyStore.NativeWidth, 0);
-                var nativeHeight = doc.GetNumber(KeyStore.NativeHeight, 0);
+                x += dX * (actualdW - width);
+                y += dY * (actualdH - height);
+                doc.x = x;
+                doc.y = y;
+                var nativeWidth = FieldValue(doc.nativeWidth, 0);
+                var nativeHeight = FieldValue(doc.nativeHeight, 0);
                 if (nativeWidth > 0 && nativeHeight > 0) {
                     if (Math.abs(dW) > Math.abs(dH)) {
                         actualdH = nativeHeight / nativeWidth * actualdW;
                     }
                     else actualdW = nativeWidth / nativeHeight * actualdH;
                 }
-                doc.SetNumber(KeyStore.Width, actualdW);
-                doc.SetNumber(KeyStore.Height, actualdH);
+                doc.width = actualdW;
+                doc.height = actualdH;
             }
         });
     }
 
+    @action
     onPointerUp = (e: PointerEvent): void => {
         e.stopPropagation();
+        this._resizing = "";
+        this.Interacting = false;
+        SelectionManager.ReselectAll();
         if (e.button === 0) {
             e.preventDefault();
             this._isPointerDown = false;
@@ -349,17 +472,20 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         }
     }
 
-    getValue = (): string => {
-        if (this._title === "changed" && this._documents.length > 0) {
-            let field = this._documents[0].props.Document.Get(this._fieldKey);
-            if (field instanceof TextField) {
-                return (field).GetValue();
+    @computed
+    get selectionTitle(): string {
+        if (SelectionManager.SelectedDocuments().length === 1) {
+            let field = SelectionManager.SelectedDocuments()[0].props.Document[this._fieldKey];
+            if (typeof field === "string") {
+                return field;
             }
-            else if (field instanceof NumberField) {
-                return (field).GetValue().toString();
+            else if (typeof field === "number") {
+                return field.toString();
             }
+        } else if (SelectionManager.SelectedDocuments().length > 1) {
+            return "-multiple-";
         }
-        return this._title;
+        return "-unset-";
     }
 
     changeFlyoutContent = (): void => {
@@ -368,53 +494,81 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
     // buttonOnPointerUp = (e: React.PointerEvent): void => {
     //     e.stopPropagation();
     // }
+
     render() {
         var bounds = this.Bounds;
-        if (bounds.x === Number.MAX_VALUE) {
-            return (null);
-        }
-        // console.log(this._documents.length)
-        // let test = this._documents[0].props.Document.Title;
-        if (this.Hidden) {
-            return (null);
-        }
-        if (isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) {
-            console.log("DocumentDecorations: Bounds Error");
+        let seldoc = SelectionManager.SelectedDocuments().length ? SelectionManager.SelectedDocuments()[0] : undefined;
+        if (bounds.x === Number.MAX_VALUE || !seldoc || this._hidden || isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) {
             return (null);
         }
+        let minimizeIcon = (
+            <div className="documentDecorations-minimizeButton" onPointerDown={this.onMinimizeDown}>
+                {SelectionManager.SelectedDocuments().length === 1 ? IconBox.DocumentIcon(StrCast(SelectionManager.SelectedDocuments()[0].props.Document.layout, "...")) : "..."}
+            </div>);
 
         let linkButton = null;
         if (SelectionManager.SelectedDocuments().length > 0) {
             let selFirst = SelectionManager.SelectedDocuments()[0];
-            let linkToSize = selFirst.props.Document.GetData(KeyStore.LinkedToDocs, ListField, []).length;
-            let linkFromSize = selFirst.props.Document.GetData(KeyStore.LinkedFromDocs, ListField, []).length;
+            let linkToSize = Cast(selFirst.props.Document.linkedToDocs, listSpec(Doc), []).length;
+            let linkFromSize = Cast(selFirst.props.Document.linkedFromDocs, listSpec(Doc), []).length;
             let linkCount = linkToSize + linkFromSize;
             linkButton = (<Flyout
                 anchorPoint={anchorPoints.RIGHT_TOP}
                 content={<LinkMenu docView={selFirst}
                     changeFlyout={this.changeFlyoutContent} />}>
-                <div className={"linkButton-" + (selFirst.props.Document.GetData(KeyStore.LinkedToDocs, ListField, []).length ? "nonempty" : "empty")} onPointerDown={this.onLinkButtonDown} >{linkCount}</div>
-            </Flyout>);
+                <div className={"linkButton-" + (linkCount ? "nonempty" : "empty")} onPointerDown={this.onLinkButtonDown} >{linkCount}</div>
+            </Flyout >);
         }
+
+        let templates: Map<Template, boolean> = new Map();
+        Array.from(Object.values(Templates.TemplateList)).map(template => {
+            let sorted = SelectionManager.ViewsSortedVertically().slice().sort((doc1, doc2) => {
+                if (NumCast(doc1.props.Document.x) > NumCast(doc2.props.Document.x)) return 1;
+                if (NumCast(doc1.props.Document.x) < NumCast(doc2.props.Document.x)) return -1;
+                return 0;
+            });
+            let docTemps = sorted.reduce((res: string[], doc: DocumentView, i) => {
+                let temps = doc.props.Document.templates;
+                if (temps instanceof List) {
+                    temps.map(temp => {
+                        if (temp !== Templates.Bullet.Layout || i === 0) {
+                            res.push(temp);
+                        }
+                    })
+                }
+                return res
+            }, [] as string[]);
+            let checked = false;
+            docTemps.forEach(temp => {
+                if (template.Layout === temp) {
+                    checked = true;
+                }
+            });
+            templates.set(template, checked);
+        });
+
         return (<div className="documentDecorations">
             <div className="documentDecorations-background" style={{
                 width: (bounds.r - bounds.x + this._resizeBorderWidth) + "px",
                 height: (bounds.b - bounds.y + this._resizeBorderWidth) + "px",
                 left: bounds.x - this._resizeBorderWidth / 2,
                 top: bounds.y - this._resizeBorderWidth / 2,
-                pointerEvents: this._dragging ? "none" : "all",
+                pointerEvents: this.Interacting ? "none" : "all",
                 zIndex: SelectionManager.SelectedDocuments().length > 1 ? 1000 : 0,
             }} onPointerDown={this.onBackgroundDown} onContextMenu={(e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); }} >
             </div>
-            <div id="documentDecorations-container" style={{
+            <div className="documentDecorations-container" style={{
                 width: (bounds.r - bounds.x + this._resizeBorderWidth) + "px",
                 height: (bounds.b - bounds.y + this._resizeBorderWidth + this._linkBoxHeight + this._titleHeight) + "px",
                 left: bounds.x - this._resizeBorderWidth / 2,
                 top: bounds.y - this._resizeBorderWidth / 2 - this._titleHeight,
                 opacity: this._opacity
             }}>
-                <div className="documentDecorations-minimizeButton" onPointerDown={this.onMinimizeDown}>...</div>
-                <input ref={this.keyinput} className="title" type="text" name="dynbox" value={this.getValue()} onChange={this.handleChange} onPointerDown={this.onBackgroundDown} onKeyPress={this.enterPressed} />
+                {minimizeIcon}
+
+                {this._edtingTitle ?
+                    <input ref={this.keyinput} className="title" type="text" name="dynbox" value={this._title} onBlur={this.titleBlur} onChange={this.titleChanged} onKeyPress={this.titleEntered} /> :
+                    <div className="title" onPointerDown={this.onTitleDown} ><span>{`${this.selectionTitle}`}</span></div>}
                 <div className="documentDecorations-closeButton" onPointerDown={this.onCloseDown}>X</div>
                 <div id="documentDecorations-topLeftResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
                 <div id="documentDecorations-topResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
@@ -425,9 +579,17 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 <div id="documentDecorations-bottomLeftResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
                 <div id="documentDecorations-bottomResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
                 <div id="documentDecorations-bottomRightResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
-
-                <div title="View Links" className="linkFlyout" ref={this._linkButton}> {linkButton}  </div>
-                <div className="linkButton-linker" ref={this._linkerButton} onPointerDown={this.onLinkerButtonDown}>∞</div>
+                <div className="link-button-container">
+                    <div className="linkButtonWrapper">
+                        <div title="View Links" className="linkFlyout" ref={this._linkButton}> {linkButton}  </div>
+                    </div>
+                    <div className="linkButtonWrapper">
+                        <div title="Drag Link" className="linkButton-linker" ref={this._linkerButton} onPointerDown={this.onLinkerButtonDown}>
+                            <FontAwesomeIcon className="fa-icon-link" icon="link" size="sm" />
+                        </div>
+                    </div>
+                    <TemplateMenu docs={SelectionManager.ViewsSortedVertically()} templates={templates} />
+                </div>
             </div >
         </div>
         );
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index 2f17c6c51..73467eb9d 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -60,7 +60,7 @@ export class EditableView extends React.Component<EditableProps> {
             return (
                 <div className="editableView-container-editing" style={{ display: this.props.display, height: "auto", maxHeight: `${this.props.height}` }}
                     onClick={action(() => this.editing = true)} >
-                    {this.props.contents}
+                    <span>{this.props.contents}</span>
                 </div>
             );
         }
diff --git a/src/client/views/InkingCanvas.tsx b/src/client/views/InkingCanvas.tsx
index 47ee8eb85..1c0d13545 100644
--- a/src/client/views/InkingCanvas.tsx
+++ b/src/client/views/InkingCanvas.tsx
@@ -1,9 +1,5 @@
 import { action, computed, trace, observable, runInAction } from "mobx";
 import { observer } from "mobx-react";
-import { Document } from "../../fields/Document";
-import { FieldWaiting } from "../../fields/Field";
-import { InkField, InkTool, StrokeData, StrokeMap } from "../../fields/InkField";
-import { KeyStore } from "../../fields/KeyStore";
 import { Utils } from "../../Utils";
 import { Transform } from "../util/Transform";
 import "./InkingCanvas.scss";
@@ -11,10 +7,13 @@ import { InkingControl } from "./InkingControl";
 import { InkingStroke } from "./InkingStroke";
 import React = require("react");
 import { undoBatch, UndoManager } from "../util/UndoManager";
+import { StrokeData, InkField, InkTool } from "../../new_fields/InkField";
+import { Doc } from "../../new_fields/Doc";
+import { Cast, PromiseValue, NumCast } from "../../new_fields/Types";
 
 interface InkCanvasProps {
     getScreenTransform: () => Transform;
-    Document: Document;
+    Document: Doc;
     children: () => JSX.Element[];
 }
 
@@ -23,7 +22,7 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
     maxCanvasDim = 8192 / 2; // 1/2 of the maximum canvas dimension for Chrome
     @observable inkMidX: number = 0;
     @observable inkMidY: number = 0;
-    private previousState?: StrokeMap;
+    private previousState?: Map<string, StrokeData>;
     private _currentStrokeId: string = "";
     public static IntersectStrokeRect(stroke: StrokeData, selRect: { left: number, top: number, width: number, height: number }): boolean {
         return stroke.pathData.reduce((inside: boolean, val) => inside ||
@@ -33,9 +32,9 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
     }
 
     componentDidMount() {
-        this.props.Document.GetTAsync(KeyStore.Ink, InkField, ink => runInAction(() => {
+        PromiseValue(Cast(this.props.Document.ink, InkField)).then(ink => runInAction(() => {
             if (ink) {
-                let bounds = Array.from(ink.Data).reduce(([mix, max, miy, may], [id, strokeData]) =>
+                let bounds = Array.from(ink.inkData).reduce(([mix, max, miy, may], [id, strokeData]) =>
                     strokeData.pathData.reduce(([mix, max, miy, may], p) =>
                         [Math.min(mix, p.x), Math.max(max, p.x), Math.min(miy, p.y), Math.max(may, p.y)],
                         [mix, max, miy, may]),
@@ -47,13 +46,13 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
     }
 
     @computed
-    get inkData(): StrokeMap {
-        let map = this.props.Document.GetT(KeyStore.Ink, InkField);
-        return !map || map === FieldWaiting ? new Map : new Map(map.Data);
+    get inkData(): Map<string, StrokeData> {
+        let map = Cast(this.props.Document.ink, InkField);
+        return !map ? new Map : new Map(map.inkData);
     }
 
-    set inkData(value: StrokeMap) {
-        this.props.Document.SetDataOnPrototype(KeyStore.Ink, value, InkField);
+    set inkData(value: Map<string, StrokeData>) {
+        Doc.SetOnPrototype(this.props.Document, "ink", new InkField(value));
     }
 
     @action
@@ -78,7 +77,7 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
                 color: InkingControl.Instance.selectedColor,
                 width: InkingControl.Instance.selectedWidth,
                 tool: InkingControl.Instance.selectedTool,
-                page: this.props.Document.GetNumber(KeyStore.CurPage, -1)
+                page: NumCast(this.props.Document.curPage, -1)
             });
             this.inkData = data;
         }
@@ -137,24 +136,28 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
 
     @computed
     get drawnPaths() {
-        let curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1);
+        let curPage = NumCast(this.props.Document.curPage, -1);
         let paths = Array.from(this.inkData).reduce((paths, [id, strokeData]) => {
             if (strokeData.page === -1 || strokeData.page === curPage) {
-                paths.push(<InkingStroke key={id} id={id} line={strokeData.pathData}
+                paths.push(<InkingStroke key={id} id={id}
+                    line={strokeData.pathData}
+                    count={strokeData.pathData.length}
                     offsetX={this.maxCanvasDim - this.inkMidX}
                     offsetY={this.maxCanvasDim - this.inkMidY}
-                    color={strokeData.color} width={strokeData.width}
-                    tool={strokeData.tool} deleteCallback={this.removeLine} />);
+                    color={strokeData.color}
+                    width={strokeData.width}
+                    tool={strokeData.tool}
+                    deleteCallback={this.removeLine} />);
             }
             return paths;
         }, [] as JSX.Element[]);
-        return [<svg className={`inkingCanvas-paths-markers`} key="Markers"
+        return [<svg className={`inkingCanvas-paths-ink`} key="Pens"
             style={{ left: `${this.inkMidX - this.maxCanvasDim}px`, top: `${this.inkMidY - this.maxCanvasDim}px` }} >
-            {paths.filter(path => path.props.tool === InkTool.Highlighter)}
+            {paths.filter(path => path.props.tool !== InkTool.Highlighter)}
         </svg>,
-        <svg className={`inkingCanvas-paths-ink`} key="Pens"
+        <svg className={`inkingCanvas-paths-markers`} key="Markers"
             style={{ left: `${this.inkMidX - this.maxCanvasDim}px`, top: `${this.inkMidY - this.maxCanvasDim}px` }}>
-            {paths.filter(path => path.props.tool !== InkTool.Highlighter)}
+            {paths.filter(path => path.props.tool === InkTool.Highlighter)}
         </svg>];
     }
 
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index 9a68f0671..4b3dbd4e0 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -2,15 +2,14 @@ import { observable, action, computed } from "mobx";
 
 import { CirclePicker, ColorResult } from 'react-color';
 import React = require("react");
-import { InkTool } from "../../fields/InkField";
 import { observer } from "mobx-react";
 import "./InkingControl.scss";
 import { library } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { faPen, faHighlighter, faEraser, faBan } from '@fortawesome/free-solid-svg-icons';
 import { SelectionManager } from "../util/SelectionManager";
-import { KeyStore } from "../../fields/KeyStore";
-import { TextField } from "../../fields/TextField";
+import { InkTool } from "../../new_fields/InkField";
+import { Doc } from "../../new_fields/Doc";
 
 library.add(faPen, faHighlighter, faEraser, faBan);
 
@@ -39,7 +38,7 @@ export class InkingControl extends React.Component {
         if (SelectionManager.SelectedDocuments().length === 1) {
             var sdoc = SelectionManager.SelectedDocuments()[0];
             if (sdoc.props.ContainingCollectionView) {
-                sdoc.props.Document.SetDataOnPrototype(KeyStore.BackgroundColor, color.hex, TextField);
+                Doc.SetOnPrototype(sdoc.props.Document, "backgroundColor", color.hex);
             }
         }
     }
diff --git a/src/client/views/InkingStroke.scss b/src/client/views/InkingStroke.scss
new file mode 100644
index 000000000..cdbfdcff3
--- /dev/null
+++ b/src/client/views/InkingStroke.scss
@@ -0,0 +1,3 @@
+.inkingStroke-marker {
+    mix-blend-mode: multiply
+}
\ No newline at end of file
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 0f05da22c..37b1f5899 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -1,14 +1,16 @@
 import { observer } from "mobx-react";
-import { observable } from "mobx";
+import { observable, trace } from "mobx";
 import { InkingControl } from "./InkingControl";
-import { InkTool } from "../../fields/InkField";
 import React = require("react");
+import { InkTool } from "../../new_fields/InkField";
+import "./InkingStroke.scss";
 
 
 interface StrokeProps {
     offsetX: number;
     offsetY: number;
     id: string;
+    count: number;
     line: Array<{ x: number, y: number }>;
     color: string;
     width: string;
@@ -48,10 +50,12 @@ export class InkingStroke extends React.Component<StrokeProps> {
     render() {
         let pathStyle = this.createStyle();
         let pathData = this.parseData(this.props.line);
+        let pathlength = this.props.count; // bcz: this is needed to force reactions to the line data changes
+        let marker = this.props.tool === InkTool.Highlighter ? "-marker" : "";
 
         let pointerEvents: any = InkingControl.Instance.selectedTool === InkTool.Eraser ? "all" : "none";
         return (
-            <path d={pathData} style={{ ...pathStyle, pointerEvents: pointerEvents }} strokeLinejoin="round" strokeLinecap="round"
+            <path className={`inkingStroke${marker}`} d={pathData} style={{ ...pathStyle, pointerEvents: pointerEvents }} strokeLinejoin="round" strokeLinecap="round"
                 onPointerOver={this.deleteStroke} onPointerDown={this.deleteStroke} />
         );
     }
diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss
index 4373534b2..5c5c252e9 100644
--- a/src/client/views/Main.scss
+++ b/src/client/views/Main.scss
@@ -1,5 +1,6 @@
 @import "globalCssVariables";
 @import "nodeModuleOverrides";
+
 html,
 body {
     width: 100%;
@@ -7,9 +8,13 @@ body {
     overflow: auto;
     font-family: $sans-serif;
     margin: 0;
-    position:absolute;
+    position: absolute;
     top: 0;
-    left:0;
+    left: 0;
+}
+
+div {
+    user-select: none;
 }
 
 #dash-title {
@@ -42,7 +47,9 @@ h1 {
 }
 
 .jsx-parser {
-    width:100%
+    width:100%;
+    pointer-events: none;
+    border-radius: inherit;
 }
 
 p {
@@ -53,7 +60,7 @@ p {
 ::-webkit-scrollbar {
     -webkit-appearance: none;
     height: 5px;
-    width: 5px;
+    width: 10px;
 }
 
 ::-webkit-scrollbar-thumb {
@@ -114,6 +121,7 @@ button:hover {
     position: absolute;
     bottom: 62px;
     left: 24px;
+
     .toolbar-button {
         display: block;
         margin-bottom: 10px;
@@ -123,8 +131,9 @@ button:hover {
 // add nodes menu. Note that the + button is actually an input label, not an actual button.
 #add-nodes-menu {
     position: absolute;
-    bottom: 24px;
+    bottom: 22px;
     left: 24px;
+
     label {
         background: $dark-color;
         color: $light-color;
@@ -137,44 +146,52 @@ button:hover {
         cursor: pointer;
         transition: transform 0.2s;
     }
+
     label p {
         padding-left: 10.5px;
-        padding-top: 3px;
     }
+
     label:hover {
         background: $main-accent;
         transform: scale(1.15);
     }
+
     input {
         display: none;
     }
+
     input:not(:checked)~#add-options-content {
         display: none;
     }
+
     input:checked~label {
         transform: rotate(45deg);
         transition: transform 0.5s;
         cursor: pointer;
     }
 }
+
 #root {
     overflow: visible;
 }
+
 #main-div {
-    width:100%;
-    height:100%;
-    position:absolute;
+    width: 100%;
+    height: 100%;
+    position: absolute;
     top: 0;
-    left:0;
+    left: 0;
     overflow: scroll;
+    z-index: 1;
 }
 #mainContent-div {
-    width:100%;
-    height:100%;
-    position:absolute;
+    width: 100%;
+    height: 100%;
+    position: absolute;
     top: 0;
-    left:0;
+    left: 0;
 }
+
 #add-options-content {
     display: table;
     opacity: 1;
@@ -188,7 +205,8 @@ button:hover {
 
 ul#add-options-list {
     list-style: none;
-    padding: 0;
+    padding: 5 0 0 0;
+
     li {
         display: inline-block;
         padding: 0;
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 5cae4fdaf..617580332 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -8,17 +8,10 @@ import * as React from 'react';
 import * as ReactDOM from 'react-dom';
 import Measure from 'react-measure';
 import * as request from 'request';
-import { Document } from '../../fields/Document';
-import { Field, FieldWaiting, Opt, FIELD_WAITING } from '../../fields/Field';
-import { KeyStore } from '../../fields/KeyStore';
-import { ListField } from '../../fields/ListField';
-import { WorkspacesMenu } from '../../server/authentication/controllers/WorkspacesMenu';
 import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils';
-import { MessageStore } from '../../server/Message';
 import { RouteStore } from '../../server/RouteStore';
-import { ServerUtils } from '../../server/ServerUtil';
-import { emptyDocFunction, emptyFunction, returnTrue, Utils, returnOne } from '../../Utils';
-import { Documents } from '../documents/Documents';
+import { emptyFunction, returnTrue, Utils, returnOne, returnZero } from '../../Utils';
+import { Docs } from '../documents/Documents';
 import { ColumnAttributeModel } from '../northstar/core/attribute/AttributeModel';
 import { AttributeTransformationModel } from '../northstar/core/attribute/AttributeTransformationModel';
 import { Gateway, NorthstarSettings } from '../northstar/manager/Gateway';
@@ -26,10 +19,10 @@ import { AggregateFunction, Catalog } from '../northstar/model/idea/idea';
 import '../northstar/model/ModelExtensions';
 import { HistogramOperation } from '../northstar/operations/HistogramOperation';
 import '../northstar/utils/Extensions';
-import { Server } from '../Server';
-import { SetupDrag } from '../util/DragManager';
+import { SetupDrag, DragManager } from '../util/DragManager';
 import { Transform } from '../util/Transform';
 import { UndoManager } from '../util/UndoManager';
+import { PresentationView } from './PresentationView';
 import { CollectionDockingView } from './collections/CollectionDockingView';
 import { ContextMenu } from './ContextMenu';
 import { DocumentDecorations } from './DocumentDecorations';
@@ -38,6 +31,12 @@ import "./Main.scss";
 import { MainOverlayTextBox } from './MainOverlayTextBox';
 import { DocumentView } from './nodes/DocumentView';
 import { PreviewCursor } from './PreviewCursor';
+import { SelectionManager } from '../util/SelectionManager';
+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';
 
 
 @observer
@@ -47,11 +46,13 @@ export class Main extends React.Component {
     @observable public pwidth: number = 0;
     @observable public pheight: number = 0;
 
-    @computed private get mainContainer(): Document | undefined | FIELD_WAITING {
-        return CurrentUserUtils.UserDocument.GetT(KeyStore.ActiveWorkspace, Document);
+    @computed private get mainContainer(): Opt<Doc> {
+        return FieldValue(Cast(CurrentUserUtils.UserDocument.activeWorkspace, Doc));
     }
-    private set mainContainer(doc: Document | undefined | FIELD_WAITING) {
-        doc && CurrentUserUtils.UserDocument.Set(KeyStore.ActiveWorkspace, doc);
+    private set mainContainer(doc: Opt<Doc>) {
+        if (doc) {
+            CurrentUserUtils.UserDocument.activeWorkspace = doc;
+        }
     }
 
     constructor(props: Readonly<{}>) {
@@ -84,11 +85,11 @@ export class Main extends React.Component {
         this.initEventListeners();
         this.initAuthenticationRouters();
 
-        try {
-            this.initializeNorthstar();
-        } catch (e) {
+        // try {
+        //     this.initializeNorthstar();
+        // } catch (e) {
 
-        }
+        // }
     }
 
     componentDidMount() { window.onpopstate = this.onHistory; }
@@ -98,9 +99,8 @@ export class Main extends React.Component {
     onHistory = () => {
         if (window.location.pathname !== RouteStore.home) {
             let pathname = window.location.pathname.split("/");
-            CurrentUserUtils.MainDocId = pathname[pathname.length - 1];
-            Server.GetField(CurrentUserUtils.MainDocId, action((field: Opt<Field>) => {
-                if (field instanceof Document) {
+            DocServer.GetRefField(pathname[pathname.length - 1]).then(action((field: Opt<Field>) => {
+                if (field instanceof Doc) {
                     this.openWorkspace(field, true);
                 }
             }));
@@ -111,6 +111,12 @@ export class Main extends React.Component {
         // window.addEventListener("pointermove", (e) => this.reportLocation(e))
         window.addEventListener("drop", (e) => e.preventDefault(), false); // drop event handler
         window.addEventListener("dragover", (e) => e.preventDefault(), false); // drag event handler
+        window.addEventListener("keydown", (e) => {
+            if (e.key === "Escape") {
+                DragManager.AbortDrag();
+                SelectionManager.DeselectAll();
+            }
+        }, 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)) {
@@ -119,56 +125,55 @@ export class Main extends React.Component {
         }), true);
     }
 
-    initAuthenticationRouters = () => {
+    initAuthenticationRouters = async () => {
         // Load the user's active workspace, or create a new one if initial session after signup
         if (!CurrentUserUtils.MainDocId) {
-            CurrentUserUtils.UserDocument.GetTAsync(KeyStore.ActiveWorkspace, Document).then(doc => {
-                if (doc) {
-                    CurrentUserUtils.MainDocId = doc.Id;
-                    this.openWorkspace(doc);
-                } else {
-                    this.createNewWorkspace();
-                }
-            });
+            const doc = await Cast(CurrentUserUtils.UserDocument.activeWorkspace, Doc);
+            if (doc) {
+                this.openWorkspace(doc);
+            } else {
+                this.createNewWorkspace();
+            }
         } else {
-            Server.GetField(CurrentUserUtils.MainDocId).then(field =>
-                field instanceof Document ? this.openWorkspace(field) :
+            DocServer.GetRefField(CurrentUserUtils.MainDocId).then(field =>
+                field instanceof Doc ? this.openWorkspace(field) :
                     this.createNewWorkspace(CurrentUserUtils.MainDocId));
         }
     }
 
     @action
-    createNewWorkspace = (id?: string): void => {
-        CurrentUserUtils.UserDocument.GetTAsync<ListField<Document>>(KeyStore.Workspaces, ListField).then(action((list: Opt<ListField<Document>>) => {
-            if (list) {
-                let freeformDoc = Documents.FreeformDocument([], { x: 0, y: 400, title: "mini collection" });
-                var dockingLayout = { content: [{ type: 'row', content: [CollectionDockingView.makeDocumentConfig(freeformDoc)] }] };
-                let mainDoc = Documents.DockDocument(JSON.stringify(dockingLayout), { title: `Main Container ${list.Data.length + 1}` }, id);
-                list.Data.push(mainDoc);
-                CurrentUserUtils.MainDocId = mainDoc.Id;
-                // bcz: strangely, we need a timeout to prevent exceptions/issues initializing GoldenLayout (the rendering engine for Main Container)
-                setTimeout(() => {
-                    this.openWorkspace(mainDoc);
-                    let pendingDocument = Documents.SchemaDocument([], { title: "New Mobile Uploads" });
-                    mainDoc.Set(KeyStore.OptionalRightCollection, pendingDocument);
-                }, 0);
-            }
-        }));
+    createNewWorkspace = async (id?: string) => {
+        const list = Cast(CurrentUserUtils.UserDocument.data, listSpec(Doc));
+        if (list) {
+            let freeformDoc = Docs.FreeformDocument([], { x: 0, y: 400, title: `WS collection ${list.length + 1}` });
+            var dockingLayout = { content: [{ type: 'row', content: [CollectionDockingView.makeDocumentConfig(CurrentUserUtils.UserDocument, 150), CollectionDockingView.makeDocumentConfig(freeformDoc, 600)] }] };
+            let mainDoc = Docs.DockDocument([CurrentUserUtils.UserDocument, freeformDoc], JSON.stringify(dockingLayout), { title: `Workspace ${list.length + 1}` });
+            list.push(mainDoc);
+            // bcz: strangely, we need a timeout to prevent exceptions/issues initializing GoldenLayout (the rendering engine for Main Container)
+            setTimeout(() => {
+                this.openWorkspace(mainDoc);
+                let pendingDocument = Docs.SchemaDocument([], { title: "New Mobile Uploads" });
+                mainDoc.optionalRightCollection = pendingDocument;
+            }, 0);
+        }
     }
 
     @action
-    openWorkspace = (doc: Document, fromHistory = false): void => {
+    openWorkspace = async (doc: Doc, fromHistory = false) => {
+        CurrentUserUtils.MainDocId = doc[Id];
         this.mainContainer = doc;
-        fromHistory || window.history.pushState(null, doc.Title, "/doc/" + doc.Id);
-        CurrentUserUtils.UserDocument.GetTAsync(KeyStore.OptionalRightCollection, Document).then(col =>
-            // if there is a pending doc, and it has new data, show it (syip: we use a timeout to prevent collection docking view from being uninitialized)
-            setTimeout(() =>
-                col && col.GetTAsync<ListField<Document>>(KeyStore.Data, ListField, (f: Opt<ListField<Document>>) =>
-                    f && f.Data.length > 0 && CollectionDockingView.Instance.AddRightSplit(col))
-                , 100)
-        );
+        fromHistory || window.history.pushState(null, StrCast(doc.title), "/doc/" + doc[Id]);
+        const col = await Cast(CurrentUserUtils.UserDocument.optionalRightCollection, Doc);
+        // if there is a pending doc, and it has new data, show it (syip: we use a timeout to prevent collection docking view from being uninitialized)
+        setTimeout(async () => {
+            if (col) {
+                const l = Cast(col.data, listSpec(Doc));
+                if (l && l.length > 0) {
+                    CollectionDockingView.Instance.AddRightSplit(col);
+                }
+            }
+        }, 100);
     }
-
     @computed
     get mainContent() {
         let pwidthFunc = () => this.pwidth;
@@ -180,6 +185,7 @@ export class Main extends React.Component {
                 <div ref={measureRef} id="mainContent-div">
                     {!mainCont ? (null) :
                         <DocumentView Document={mainCont}
+                            toggleMinimized={emptyFunction}
                             addDocument={undefined}
                             removeDocument={undefined}
                             ScreenToLocalTransform={Transform.Identity}
@@ -188,35 +194,38 @@ export class Main extends React.Component {
                             PanelHeight={pheightFunc}
                             isTopMost={true}
                             selectOnLoad={false}
-                            focus={emptyDocFunction}
+                            focus={emptyFunction}
                             parentActive={returnTrue}
                             whenActiveChanged={emptyFunction}
+                            bringToFront={emptyFunction}
                             ContainingCollectionView={undefined} />}
+                    <PresentationView key="presentation" />
                 </div>
             }
         </Measure>;
     }
 
     /* for the expandable add nodes menu. Not included with the miscbuttons because once it expands it expands the whole div with it, making canvas interactions limited. */
-    @computed
-    get nodesMenu() {
+    nodesMenu() {
+
         let imgurl = "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg";
         let pdfurl = "http://www.adobe.com/support/products/enterprise/knowledgecenter/media/c27211_sample_explain.pdf";
         let weburl = "https://cs.brown.edu/courses/cs166/";
         let audiourl = "http://techslides.com/demos/samples/sample.mp3";
         let videourl = "http://techslides.com/demos/sample-videos/small.mp4";
 
-        let addTextNode = action(() => Documents.TextDocument({ width: 200, height: 200, title: "a text note" }));
-        let addColNode = action(() => Documents.FreeformDocument([], { width: 200, height: 200, title: "a freeform collection" }));
-        let addSchemaNode = action(() => Documents.SchemaDocument([], { width: 200, height: 200, title: "a schema collection" }));
-        let addTreeNode = action(() => Documents.TreeDocument(this._northstarSchemas, { width: 250, height: 400, title: "northstar schemas", copyDraggedItems: true }));
-        let addVideoNode = action(() => Documents.VideoDocument(videourl, { width: 200, title: "video node" }));
-        let addPDFNode = action(() => Documents.PdfDocument(pdfurl, { width: 200, height: 200, title: "a pdf doc" }));
-        let addImageNode = action(() => Documents.ImageDocument(imgurl, { width: 200, title: "an image of a cat" }));
-        let addWebNode = action(() => Documents.WebDocument(weburl, { width: 200, height: 200, title: "a sample web page" }));
-        let addAudioNode = action(() => Documents.AudioDocument(audiourl, { width: 200, height: 200, title: "audio node" }));
+        let addTextNode = action(() => Docs.TextDocument({ borderRounding: -1, width: 200, height: 200, title: "a text note" }));
+        let addColNode = action(() => Docs.FreeformDocument([], { width: 200, height: 200, title: "a freeform collection" }));
+        let addSchemaNode = action(() => Docs.SchemaDocument([], { width: 200, height: 200, title: "a schema collection" }));
+        let addTreeNode = action(() => Docs.TreeDocument([CurrentUserUtils.UserDocument], { width: 250, height: 400, title: "Library:" + CurrentUserUtils.email, dropAction: "alias" }));
+        // let addTreeNode = action(() => Docs.TreeDocument(this._northstarSchemas, { width: 250, height: 400, title: "northstar schemas", dropAction: "copy"  }));
+        let addVideoNode = action(() => Docs.VideoDocument(videourl, { width: 200, title: "video node" }));
+        let addPDFNode = action(() => Docs.PdfDocument(pdfurl, { width: 200, height: 200, title: "a pdf doc" }));
+        let addImageNode = action(() => Docs.ImageDocument(imgurl, { width: 200, title: "an image of a cat" }));
+        let addWebNode = action(() => Docs.WebDocument(weburl, { width: 200, height: 200, title: "a sample web page" }));
+        let addAudioNode = action(() => Docs.AudioDocument(audiourl, { width: 200, height: 200, title: "audio node" }));
 
-        let btns: [React.RefObject<HTMLDivElement>, IconName, string, () => Document][] = [
+        let btns: [React.RefObject<HTMLDivElement>, IconName, string, () => Doc][] = [
             [React.createRef<HTMLDivElement>(), "font", "Add Textbox", addTextNode],
             [React.createRef<HTMLDivElement>(), "image", "Add Image", addImageNode],
             [React.createRef<HTMLDivElement>(), "file-pdf", "Add PDF", addPDFNode],
@@ -248,36 +257,20 @@ export class Main extends React.Component {
     /* @TODO this should really be moved into a moveable toolbar component, but for now let's put it here to meet the deadline */
     @computed
     get miscButtons() {
-        let workspacesRef = React.createRef<HTMLDivElement>();
         let logoutRef = React.createRef<HTMLDivElement>();
-        let toggleWorkspaces = () => runInAction(() => this._workspacesShown = !this._workspacesShown);
 
-        let clearDatabase = action(() => Utils.Emit(Server.Socket, MessageStore.DeleteAll, {}));
         return [
-            <button className="clear-db-button" key="clear-db" onClick={clearDatabase}>Clear Database</button>,
+            <button className="clear-db-button" key="clear-db" onClick={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>
                 <button className="toolbar-button round-button" title="Ink" onClick={() => InkingControl.Instance.toggleDisplay()}><FontAwesomeIcon icon="pen-nib" size="sm" /></button>
             </div >,
-            <div className="main-buttonDiv" key="workspaces" style={{ top: '34px', left: '2px', position: 'absolute' }} ref={workspacesRef}>
-                <button onClick={toggleWorkspaces}>Workspaces</button></div>,
             <div className="main-buttonDiv" key="logout" style={{ top: '34px', right: '1px', position: 'absolute' }} ref={logoutRef}>
-                <button onClick={() => request.get(ServerUtils.prepend(RouteStore.logout), emptyFunction)}>Log Out</button></div>
+                <button onClick={() => request.get(DocServer.prepend(RouteStore.logout), emptyFunction)}>Log Out</button></div>
         ];
     }
 
-    @computed
-    get workspaceMenu() {
-        let areWorkspacesShown = () => this._workspacesShown;
-        let toggleWorkspaces = () => runInAction(() => this._workspacesShown = !this._workspacesShown);
-        let workspaces = CurrentUserUtils.UserDocument.GetT<ListField<Document>>(KeyStore.Workspaces, ListField);
-        return (!workspaces || workspaces === FieldWaiting || this.mainContainer === FieldWaiting) ? (null) :
-            <WorkspacesMenu active={this.mainContainer} open={this.openWorkspace}
-                new={this.createNewWorkspace} allWorkspaces={workspaces.Data}
-                isShown={areWorkspacesShown} toggle={toggleWorkspaces} />;
-    }
-
     render() {
         return (
             <div id="main-div">
@@ -285,9 +278,8 @@ export class Main extends React.Component {
                 {this.mainContent}
                 <PreviewCursor />
                 <ContextMenu />
-                {this.nodesMenu}
+                {this.nodesMenu()}
                 {this.miscButtons}
-                {this.workspaceMenu}
                 <InkingControl />
                 <MainOverlayTextBox />
             </div>
@@ -295,17 +287,17 @@ export class Main extends React.Component {
     }
 
     // --------------- Northstar hooks ------------- /
-    private _northstarSchemas: Document[] = [];
+    private _northstarSchemas: Doc[] = [];
 
     @action SetNorthstarCatalog(ctlog: Catalog) {
         CurrentUserUtils.NorthstarDBCatalog = ctlog;
         if (ctlog && ctlog.schemas) {
             ctlog.schemas.map(schema => {
-                let schemaDocuments: Document[] = [];
+                let schemaDocuments: Doc[] = [];
                 let attributesToBecomeDocs = CurrentUserUtils.GetAllNorthstarColumnAttributes(schema);
                 Promise.all(attributesToBecomeDocs.reduce((promises, attr) => {
-                    promises.push(Server.GetField(attr.displayName! + ".alias").then(action((field: Opt<Field>) => {
-                        if (field instanceof Document) {
+                    promises.push(DocServer.GetRefField(attr.displayName! + ".alias").then(action((field: Opt<Field>) => {
+                        if (field instanceof Doc) {
                             schemaDocuments.push(field);
                         } else {
                             var atmod = new ColumnAttributeModel(attr);
@@ -313,12 +305,12 @@ export class Main extends React.Component {
                                 new AttributeTransformationModel(atmod, AggregateFunction.None),
                                 new AttributeTransformationModel(atmod, AggregateFunction.Count),
                                 new AttributeTransformationModel(atmod, AggregateFunction.Count));
-                            schemaDocuments.push(Documents.HistogramDocument(histoOp, { width: 200, height: 200, title: attr.displayName! }, undefined, attr.displayName! + ".alias"));
+                            schemaDocuments.push(Docs.HistogramDocument(histoOp, { width: 200, height: 200, title: attr.displayName! }));
                         }
                     })));
                     return promises;
                 }, [] as Promise<void>[])).finally(() =>
-                    this._northstarSchemas.push(Documents.TreeDocument(schemaDocuments, { width: 50, height: 100, title: schema.displayName! })));
+                    this._northstarSchemas.push(Docs.TreeDocument(schemaDocuments, { width: 50, height: 100, title: schema.displayName! })));
             });
         }
     }
@@ -330,7 +322,7 @@ export class Main extends React.Component {
 }
 
 (async () => {
-    await Documents.initProtos();
+    await Docs.initProtos();
     await CurrentUserUtils.loadCurrentUser();
     ReactDOM.render(<Main />, document.getElementById('root'));
 })();
diff --git a/src/client/views/MainOverlayTextBox.scss b/src/client/views/MainOverlayTextBox.scss
index 697d68c8c..f6a746e63 100644
--- a/src/client/views/MainOverlayTextBox.scss
+++ b/src/client/views/MainOverlayTextBox.scss
@@ -7,6 +7,7 @@
     overflow: visible;
     top: 0;
     left: 0;
+    pointer-events: none;   
     z-index: $mainTextInput-zindex;
     .formattedTextBox-cont {
         background-color: rgba(248, 6, 6, 0.001);
diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx
index 8cb01117c..91f626737 100644
--- a/src/client/views/MainOverlayTextBox.tsx
+++ b/src/client/views/MainOverlayTextBox.tsx
@@ -1,15 +1,10 @@
-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 { Document } from '../../fields/Document';
-import { Key } from '../../fields/Key';
-import { KeyStore } from '../../fields/KeyStore';
-import { emptyDocFunction, emptyFunction, returnTrue } from '../../Utils';
-import '../northstar/model/ModelExtensions';
-import '../northstar/utils/Extensions';
+import { emptyFunction, returnTrue, returnZero } from '../../Utils';
 import { DragManager } from '../util/DragManager';
 import { Transform } from '../util/Transform';
+import "normalize.css";
 import "./MainOverlayTextBox.scss";
 import { FormattedTextBox } from './nodes/FormattedTextBox';
 
@@ -19,11 +14,8 @@ interface MainOverlayTextBoxProps {
 @observer
 export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps> {
     public static Instance: MainOverlayTextBox;
-    @observable public TextDoc?: Document = undefined;
-    public TextScroll: number = 0;
-    private _textRect: any;
-    private _textXf: Transform = Transform.Identity();
-    private _textFieldKey: Key = KeyStore.Data;
+    @observable _textXf: () => Transform = () => Transform.Identity();
+    private _textFieldKey: string = "data";
     private _textColor: string | null = null;
     private _textTargetDiv: HTMLDivElement | undefined;
     private _textProxyDiv: React.RefObject<HTMLDivElement>;
@@ -32,31 +24,32 @@ 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?: Document, textFieldKey?: Key, 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._textXf = tx ? tx : () => Transform.Identity();
         this._textTargetDiv = div;
         if (div) {
+            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._textRect = div.getBoundingClientRect();
-            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;
         }
     }
 
@@ -66,14 +59,13 @@ 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!]);
-            const [left, top] = this._textXf
-                .inverse()
-                .transformPoint(0, 0);
+            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;
             DragManager.StartDocumentDrag([this._textTargetDiv!], dragData, e.clientX, e.clientY, {
@@ -90,18 +82,15 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
     }
 
     render() {
-        if (this.TextDoc) {
-            let x: number = this._textRect.x;
-            let y: number = this._textRect.y;
-            let w: number = this._textRect.width;
-            let h: number = this._textRect.height;
-            let t = this._textXf.transformPoint(0, 0);
-            let s = this._textXf.transformPoint(1, 0);
-            s[0] = Math.sqrt((s[0] - t[0]) * (s[0] - t[0]) + (s[1] - t[1]) * (s[1] - t[1]));
-            return <div className="mainOverlayTextBox-textInput" style={{ pointerEvents: "none", transform: `translate(${x}px, ${y}px) scale(${1 / s[0]},${1 / s[0]})`, width: "auto", height: "auto" }} >
-                <div className="mainOverlayTextBox-textInput" onPointerDown={this.textBoxDown} ref={this._textProxyDiv} onScroll={this.textScroll} style={{ pointerEvents: "none", transform: `scale(${1}, ${1})`, width: `${w * s[0]}px`, height: `${h * s[0]}px` }}>
-                    <FormattedTextBox fieldKey={this._textFieldKey} isOverlay={true} Document={this.TextDoc} isSelected={returnTrue} select={emptyFunction} isTopMost={true}
-                        selectOnLoad={true} ContainingCollectionView={undefined} whenActiveChanged={emptyFunction} active={returnTrue} ScreenToLocalTransform={() => this._textXf} focus={emptyDocFunction} />
+        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={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>
             </ div>;
         }
diff --git a/src/client/views/PresentationView.scss b/src/client/views/PresentationView.scss
new file mode 100644
index 000000000..7c5677f0d
--- /dev/null
+++ b/src/client/views/PresentationView.scss
@@ -0,0 +1,68 @@
+.presentationView-cont {
+    position: absolute;
+    background: white;
+    z-index: 1;
+    box-shadow: #AAAAAA .2vw .2vw .4vw;
+    right: 0;
+    top:0;
+    bottom:0;
+}
+
+.presentationView-item {
+    width: 220px;
+    height: 40px;
+    vertical-align: center;
+    padding-top: 15px;
+    -webkit-touch-callout: none;
+    -webkit-user-select: none;
+    -khtml-user-select: none;
+    -moz-user-select: none;
+    -ms-user-select: none;
+    user-select: none;
+    transition: all .1s;
+}
+
+.presentationView-item:hover {
+    transition: all .1s;
+    background: #AAAAAA
+}
+
+.presentationView-heading {
+     margin-top: 0px;
+    height: 40px;
+    background: lightseagreen;
+    padding: 30px;
+}
+.presentationView-title {
+   padding-top: 3px; 
+   padding-bottom: 3px; 
+   font-size: 25px;
+   float:left;
+}
+.presentation-icon{
+    float: right;
+    display: inline;
+    width: 10px;
+    margin-top: 7px;
+}
+.presentationView-header {
+    padding-top: 1px; 
+    padding-bottom: 1px; 
+    font-size: 15px;
+    float:left;
+ }
+
+ .presentation-next{
+     float: right;
+ }
+ .presentation-back{
+     float: left;
+ }
+ .presentation-next:hover{
+    transition: all .1s;
+    background: #AAAAAA
+}
+.presentation-back:hover{
+    transition: all .1s;
+    background: #AAAAAA
+}
\ No newline at end of file
diff --git a/src/client/views/PresentationView.tsx b/src/client/views/PresentationView.tsx
new file mode 100644
index 000000000..94867b813
--- /dev/null
+++ b/src/client/views/PresentationView.tsx
@@ -0,0 +1,190 @@
+import { observer } from "mobx-react";
+import React = require("react")
+import { observable, action, runInAction, reaction } from "mobx";
+import "./PresentationView.scss"
+import "./Main.tsx";
+import { DocumentManager } from "../util/DocumentManager";
+import { Utils } from "../../Utils";
+import { Doc } from "../../new_fields/Doc";
+import { listSpec } from "../../new_fields/Schema";
+import { Cast, NumCast, FieldValue, PromiseValue } from "../../new_fields/Types";
+import { Id } from "../../new_fields/RefField";
+import { List } from "../../new_fields/List";
+import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils";
+
+export interface PresViewProps {
+    //Document: Doc;
+}
+
+@observer
+/**
+ * Component that takes in a document prop and a boolean whether it's collapsed or not.
+ */
+class PresentationViewItem extends React.Component<PresViewProps> {
+
+    @observable Document: Doc;
+    constructor(props: PresViewProps) {
+        super(props);
+        this.Document = FieldValue(Cast(FieldValue(Cast(CurrentUserUtils.UserDocument.activeWorkspace, Doc))!.presentationView, Doc))!;
+    }
+    //look at CollectionFreeformView.focusDocument(d)
+    @action
+    openDoc = (doc: Doc) => {
+        let docView = DocumentManager.Instance.getDocumentView(doc);
+        if (docView) {
+            docView.props.focus(docView.props.Document);
+        }
+    }
+
+    /**
+  * Removes a document from the presentation view
+  **/
+    @action
+    public RemoveDoc(doc: Doc) {
+        const value = Cast(this.Document.data, listSpec(Doc), []);
+        let index = -1;
+        for (let i = 0; i < value.length; i++) {
+            if (value[i][Id] === doc[Id]) {
+                index = i;
+                break;
+            }
+        }
+        if (index !== -1) {
+            value.splice(index, 1);
+        }
+    }
+
+    /**
+     * Renders a single child document. It will just append a list element.
+     * @param document The document to render.
+     */
+    renderChild(document: Doc) {
+        let title = document.title;
+
+        //to get currently selected presentation doc
+        let selected = NumCast(this.Document.selectedDoc, 0);
+
+        // finally, if it's a normal document, then render it as such.
+        const children = Cast(this.Document.data, listSpec(Doc));
+        const styles: any = {};
+        if (children && children[selected] === document) {
+            //this doc is selected
+            styles.background = "gray";
+        }
+        return (
+            <li className="presentationView-item" style={styles} key={Utils.GenerateGuid()}>
+                <div className="presentationView-header" onClick={() => this.openDoc(document)}>{title}</div>
+                <div className="presentation-icon" onClick={() => this.RemoveDoc(document)}>X</div>
+            </li>
+        );
+
+    }
+
+    render() {
+        const children = Cast(this.Document.data, listSpec(Doc), []);
+
+        return (
+            <div>
+                {children.map(value => this.renderChild(value))}
+            </div>
+        );
+    }
+}
+
+
+@observer
+export class PresentationView extends React.Component<PresViewProps>  {
+    public static Instance: PresentationView;
+
+    //observable means render is re-called every time variable is changed
+    @observable
+    collapsed: boolean = false;
+    closePresentation = action(() => this.Document!.width = 0);
+    next = () => {
+        const current = NumCast(this.Document!.selectedDoc);
+        const allDocs = FieldValue(Cast(this.Document!.data, listSpec(Doc)));
+        if (allDocs && current < allDocs.length + 1) {
+            //can move forwards
+            this.Document!.selectedDoc = current + 1;
+            const doc = allDocs[current + 1];
+            let docView = DocumentManager.Instance.getDocumentView(doc);
+            if (docView) {
+                docView.props.focus(docView.props.Document);
+            }
+        }
+
+    }
+    back = () => {
+        const current = NumCast(this.Document!.selectedDoc);
+        const allDocs = FieldValue(Cast(this.Document!.data, listSpec(Doc)));
+        if (allDocs && current - 1 >= 0) {
+            //can move forwards
+            this.Document!.selectedDoc = current - 1;
+            const doc = allDocs[current - 1];
+            let docView = DocumentManager.Instance.getDocumentView(doc);
+            if (docView) {
+                docView.props.focus(docView.props.Document);
+            }
+        }
+    }
+
+    private ref = React.createRef<HTMLDivElement>();
+
+    @observable Document?: Doc;
+    //initilize class variables
+    constructor(props: PresViewProps) {
+        super(props);
+        let self = this;
+        reaction(() =>
+            CurrentUserUtils.UserDocument.activeWorkspace,
+            (activeW) => {
+                if (activeW && activeW instanceof Doc) {
+                    PromiseValue(Cast(activeW.presentationView, Doc)).
+                        then(pv => runInAction(() =>
+                            self.Document = pv ? pv : (activeW.presentationView = new Doc())))
+                }
+            },
+            { fireImmediately: true });
+        PresentationView.Instance = this;
+    }
+
+    /**
+     * Adds a document to the presentation view
+     **/
+    @action
+    public PinDoc(doc: Doc) {
+        //add this new doc to props.Document
+        const data = Cast(this.Document!.data, listSpec(Doc));
+        if (data) {
+            data.push(doc);
+        } else {
+            this.Document!.data = new List([doc]);
+        }
+
+        this.Document!.width = 300;
+    }
+
+    render() {
+        if (!this.Document)
+            return (null);
+        let titleStr = this.Document.Title;
+        let width = NumCast(this.Document.width);
+
+        //TODO: next and back should be icons
+        return (
+            <div className="presentationView-cont" style={{ width: width, overflow: "hidden" }}>
+                <div className="presentationView-heading">
+                    <div className="presentationView-title">{titleStr}</div>
+                    <div className='presentation-icon' onClick={this.closePresentation}>X</div></div>
+                <div>
+                    <div className="presentation-back" onClick={this.back}>back</div>
+                    <div className="presentation-next" onClick={this.next}>next</div>
+
+                </div>
+                <ul>
+                    <PresentationViewItem />
+                </ul>
+            </div>
+        );
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index ff8434681..78024a58c 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -7,30 +7,48 @@ import "./PreviewCursor.scss";
 @observer
 export class PreviewCursor extends React.Component<{}> {
     private _prompt = React.createRef<HTMLDivElement>();
+    static _onKeyPress?: (e: KeyboardEvent) => void;
+    @observable static _clickPoint = [0, 0];
+    @observable public static Visible = false;
     //when focus is lost, this will remove the preview cursor
     @action onBlur = (): void => {
         PreviewCursor.Visible = false;
-        PreviewCursor.hide();
     }
 
-    @observable static clickPoint = [0, 0];
-    @observable public static Visible = false;
-    @observable public static hide = () => { };
+    constructor(props: any) {
+        super(props);
+        document.addEventListener("keydown", this.onKeyPress);
+    }
+
+    @action
+    onKeyPress = (e: KeyboardEvent) => {
+        // Mixing events between React and Native is finicky.  In FormattedTextBox, we set the
+        // DASHFormattedTextBoxHandled flag when a text box consumes a key press so that we can ignore
+        // the keyPress here.
+        //if not these keys, make a textbox if preview cursor is active!
+        if (e.key.startsWith("F") && !e.key.endsWith("F")) {
+        } else if (e.key !== "Escape" && e.key !== "Alt" && e.key !== "Shift" && e.key !== "Meta" && e.key !== "Control" && !e.defaultPrevented && !(e as any).DASHFormattedTextBoxHandled) {
+            if ((!e.ctrlKey && !e.metaKey) || e.key === "v" || e.key === "q") {
+                PreviewCursor.Visible && PreviewCursor._onKeyPress && PreviewCursor._onKeyPress(e);
+                PreviewCursor.Visible = false;
+            }
+        }
+    }
     @action
-    public static Show(hide: any, x: number, y: number) {
-        this.clickPoint = [x, y];
-        this.hide = hide;
+    public static Show(x: number, y: number, onKeyPress: (e: KeyboardEvent) => void) {
+        this._clickPoint = [x, y];
+        this._onKeyPress = onKeyPress;
         setTimeout(action(() => this.Visible = true), (1));
     }
     render() {
-        if (!PreviewCursor.clickPoint) {
+        if (!PreviewCursor._clickPoint) {
             return (null);
         }
         if (PreviewCursor.Visible && this._prompt.current) {
             this._prompt.current.focus();
         }
         return <div className="previewCursor" id="previewCursor" onBlur={this.onBlur} tabIndex={0} ref={this._prompt}
-            style={{ transform: `translate(${PreviewCursor.clickPoint[0]}px, ${PreviewCursor.clickPoint[1]}px)`, opacity: PreviewCursor.Visible ? 1 : 0 }}>
+            style={{ transform: `translate(${PreviewCursor._clickPoint[0]}px, ${PreviewCursor._clickPoint[1]}px)`, opacity: PreviewCursor.Visible ? 1 : 0 }}>
             I
         </div >;
     }
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
new file mode 100644
index 000000000..e2b3bd07a
--- /dev/null
+++ b/src/client/views/TemplateMenu.tsx
@@ -0,0 +1,91 @@
+import { observable, computed, action, trace } from "mobx";
+import React = require("react");
+import { observer } from "mobx-react";
+import './DocumentDecorations.scss';
+import { Template } from "./Templates";
+import { DocumentView } from "./nodes/DocumentView";
+import { List } from "../../new_fields/List";
+import { Doc } from "../../new_fields/Doc";
+import { NumCast } from "../../new_fields/Types";
+const higflyout = require("@hig/flyout");
+export const { anchorPoints } = higflyout;
+export const Flyout = higflyout.default;
+
+@observer
+class TemplateToggle extends React.Component<{ template: Template, checked: boolean, toggle: (event: React.ChangeEvent<HTMLInputElement>, template: Template) => void }> {
+    render() {
+        if (this.props.template) {
+            return (
+                <li className="templateToggle">
+                    <input type="checkbox" checked={this.props.checked} onChange={(event) => this.props.toggle(event, this.props.template)} />
+                    {this.props.template.Name}
+                </li>
+            );
+        } else {
+            return (null);
+        }
+    }
+}
+
+export interface TemplateMenuProps {
+    docs: DocumentView[];
+    templates: Map<Template, boolean>;
+}
+
+@observer
+export class TemplateMenu extends React.Component<TemplateMenuProps> {
+    @observable private _hidden: boolean = true;
+
+    constructor(props: TemplateMenuProps) {
+        super(props);
+        console.log("");
+    }
+
+    @action
+    toggleTemplate = (event: React.ChangeEvent<HTMLInputElement>, template: Template): void => {
+        if (event.target.checked) {
+            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));
+            } else {
+                this.props.docs.map(d => d.addTemplate(template));
+            }
+            this.props.templates.set(template, true);
+        } else {
+            if (template.Name == "Bullet") {
+                let topDocView = this.props.docs[0];
+                topDocView.removeTemplate(template);
+                topDocView.props.Document.subBulletDocs = undefined;
+            } else {
+                this.props.docs.map(d => d.removeTemplate(template));
+            }
+            this.props.templates.set(template, false);
+        }
+    }
+
+    @action
+    componentWillReceiveProps(nextProps: TemplateMenuProps) {
+        // this._templates = nextProps.templates;
+    }
+
+    @action
+    toggleTemplateActivity = (): void => {
+        this._hidden = !this._hidden;
+    }
+
+    render() {
+        let templateMenu: Array<JSX.Element> = [];
+        this.props.templates.forEach((checked, template) =>
+            templateMenu.push(<TemplateToggle key={template.Name} template={template} checked={checked} toggle={this.toggleTemplate} />));
+
+        return (
+            <div className="templating-menu" >
+                <div className="templating-button" onClick={() => this.toggleTemplateActivity()}>+</div>
+                <ul id="template-list" style={{ display: this._hidden ? "none" : "block" }}>
+                    {templateMenu}
+                </ul>
+            </div>
+        );
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/Templates.tsx b/src/client/views/Templates.tsx
new file mode 100644
index 000000000..02f9aa510
--- /dev/null
+++ b/src/client/views/Templates.tsx
@@ -0,0 +1,84 @@
+import React = require("react");
+
+export enum TemplatePosition {
+    InnerTop,
+    InnerBottom,
+    InnerRight,
+    InnerLeft,
+    OutterTop,
+    OutterBottom,
+    OutterRight,
+    OutterLeft,
+}
+
+export class Template {
+    constructor(name: string, position: TemplatePosition, layout: string) {
+        this._name = name;
+        this._position = position;
+        this._layout = layout;
+    }
+
+    private _name: string;
+    private _position: TemplatePosition;
+    private _layout: string;
+
+    get Name(): string {
+        return this._name;
+    }
+
+    get Position(): TemplatePosition {
+        return this._position;
+    }
+
+    get Layout(): string {
+        return this._layout;
+    }
+}
+
+export namespace Templates {
+    // export const BasicLayout = new Template("Basic layout", "{layout}");
+
+    export const OuterCaption = new Template("Outer caption", TemplatePosition.OutterBottom,
+        `<div id="screenSpace" style="margin-top: 100%; font-size:14px; background:yellow; width:100%; position:absolute"><FormattedTextBox {...props} fieldKey={"caption"} /></div>`
+    );
+
+    export const InnerCaption = new Template("Inner caption", TemplatePosition.InnerBottom,
+        `<div><div style="margin:auto; height:calc(100% - 50px); width:100%;">{layout}</div><div style="height:50px; width:100%; position:absolute"><FormattedTextBox {...props} fieldKey={"caption"}/></div></div>`
+    );
+
+    export const SideCaption = new Template("Side caption", TemplatePosition.OutterRight,
+        `<div><div style="margin:auto; height:100%; width:100%;">{layout}</div><div style="height:100%; width:300px; position:absolute; top: 0; right: -300px;"><FormattedTextBox {...props} fieldKey={"caption"}/></div> </div>`
+    );
+
+    export const TitleOverlay = new Template("TitleOverlay", TemplatePosition.InnerTop,
+        `<div><div style="height:100%; width:100%;position:absolute;">{layout}</div>
+        <div style="height:25px; width:100%; position:absolute; top: 0; background-color: rgba(0, 0, 0, .4); color: white; ">
+        <span style="text-align:center;width:100%;font-size:20px;position:absolute;overflow:hidden;white-space:nowrap;text-overflow:ellipsis">{props.Document.title}</span>
+        </div></div>`
+    );
+    export const Title = new Template("Title", TemplatePosition.InnerTop,
+        `<div><div style="height:calc(100% - 25px); margin-top: 25px; width:100%;position:absolute;">{layout}</div>
+        <div style="height:25px; width:100%; position:absolute; top: 0; background-color: rgba(0, 0, 0, .4); color: white; ">
+            <span style="text-align:center;width:100%;font-size:20px;position:absolute;overflow:hidden;white-space:nowrap;text-overflow:ellipsis">{props.Document.title}</span>
+        </div></div>`
+    );
+
+    export const Bullet = new Template("Bullet", TemplatePosition.InnerTop,
+        `<div><div style="height:100%; width:100%;position:absolute;">{layout}</div>
+        <div id="isBullet" style="height:15px; width:15px; margin-left:-16px; pointer-events:all; position:absolute; top: 0; background-color: rgba(0, 0, 0, .4); color: white;">
+        <img  id="isBullet"  src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOEAAADhCAMAAAAJbSJIAAAAZlBMVEX///8AAABmZmb7+/tYWFhgYGBFRUVSUlL4+Pg/Pz9jY2N5eXmcnJyioqKBgYFzc3NtbW1LS0s3NzfW1taWlpaOjo6IiIgvLy9WVlampqZcXFw5OTlvb28mJiYxMTHe3t7l5eUjIyMY8kIZAAAD2UlEQVR4nO2d61biMBRGW1FBEVHxfp15/5ecOVa5lHxtArmck/Xtn1BotjtNoXQtm4YQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEFIrX6UHEA1gsmrneceRjHm7cj28attKFOf/TRyKIliH4vzbZE+xE2zbZYkxRWX5Y9JT/BW0X3G+NtlR3Ahar7jcMtlS3Ba0XXG+Y7JW3BW0XHHZM/lR7AvaVewL/ijuC1pV3Bf8VnQJ2lR0CYriq/Nxg4puwfa1aZ7dz9yUHnEgN26NZ3luWkPFd7fEtHsWVDwpO+YgTgYKCuYn6tAU7TBecaygcGpZEQie7m5luKJPQQFUvCwx5iAuvQoK4KShvSIoOHVtCz7dnOUecxBn7kG/urc2eCz6T9EOcxXDCgpAUetyAwoOCBqrGF5QMKR4mCA8L+pTBIJwkRl95eifJjPHTDYTFQ8vePyrs3BsBfXLzfFHkvKKMY4j1ctNnCmmuGKslfCQT0RZiPdFVmnFmOcy36sDWYn7DU9hxdifRkKuEGQh/pWW0K/QiUlxtUxVxTTXyhQtN6kuI6mpmO5qpxJFIBjl1yMVimmvV4PfrnIq3iYsKICTRj7F9L84gIq38fYwCCj4HnMfRY/FPL8ZFayYo6BQbLlJeZrYpVDFXAUFcMtKWkUgmOhmnwKKOQsK4NaxdIp5CwqZj8X8gv27jNecJ9nZuXtnie/SzjhRQcHkt6Fnq1imoAAUY1csVVDIUrFcQSGDIhC8jriLQZIrli0oXKdVLF1QSFqxfEEBVLyI8NYXCgoKySaqhinakajimxrBRBX1FBQSVNRyDP4SXVGbYHRFfYJN8xhTESwyj5HHHEjEihoLCqDiXfAb3aksKESqCAoqEIxUUW9BAS03E+93mOhcZDYcXVF3QeHBPcI3v4qo4EPiUQcBKr75vHaiv6AAKt6NV0SCqgoKqOKYovpFZgOo+DmsOHkyUlA4ZKKamaIdQPEJK5oqKKCKM7D9zFZBIayiuYICWm5cFWef7o3vs486CP8VdQIEVRcU7sFE7VecgSmqvKDgVxEJqi8ogIof2xVnH2YLCuMT1fAU7RirOPtrXHCsovmCwlDFCgoKWNH4IrMBTdQ/NUzRjiu3CeCq9HAPAVSspaDgX9FkQcG3ollB34qGBf0UTQv6KBoXHFc0LzimWIFg0ywGBBelBxcHXLGKggKqWElBwV2xIkF3xaoEXYqVCe4rVifYV3wpPZwULOouKLzUXVBY1F1QeKm7oLCoXVAqVi7YNM7/F0YIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCG+/ANh4i1CHdc63QAAAABJRU5ErkJggg==" 
+         width="15px" height="15px"/>
+         </div>
+        </div>`
+    );
+
+    export const TemplateList: Template[] = [Title, TitleOverlay, OuterCaption, InnerCaption, SideCaption, Bullet];
+
+    export function sortTemplates(a: Template, b: Template) {
+        if (a.Position < b.Position) { return -1; }
+        if (a.Position > b.Position) { return 1; }
+        return 0;
+    }
+
+}
+
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index a8b061b04..2b1f7bb37 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -1,13 +1,14 @@
 import { action, computed } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { Document } from '../../../fields/Document';
-import { Field, FieldValue, FieldWaiting } from '../../../fields/Field';
-import { KeyStore } from '../../../fields/KeyStore';
-import { ListField } from '../../../fields/ListField';
-import { NumberField } from '../../../fields/NumberField';
 import { ContextMenu } from '../ContextMenu';
 import { FieldViewProps } from '../nodes/FieldView';
+import { Cast, FieldValue, PromiseValue, NumCast } from '../../../new_fields/Types';
+import { Doc, FieldResult, Opt } 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';
 
 export enum CollectionViewType {
     Invalid,
@@ -18,9 +19,9 @@ export enum CollectionViewType {
 }
 
 export interface CollectionRenderProps {
-    addDocument: (document: Document, allowDuplicates?: boolean) => boolean;
-    removeDocument: (document: Document) => boolean;
-    moveDocument: (document: Document, targetCollection: Document, addDocument: (document: Document) => boolean) => boolean;
+    addDocument: (document: Doc, allowDuplicates?: boolean) => boolean;
+    removeDocument: (document: Doc) => boolean;
+    moveDocument: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
     active: () => boolean;
     whenActiveChanged: (isActive: boolean) => void;
 }
@@ -37,11 +38,9 @@ export interface CollectionViewProps extends FieldViewProps {
 export class CollectionBaseView extends React.Component<CollectionViewProps> {
     get collectionViewType(): CollectionViewType | undefined {
         let Document = this.props.Document;
-        let viewField = Document.GetT(KeyStore.ViewType, NumberField);
-        if (viewField === FieldWaiting) {
-            return undefined;
-        } else if (viewField) {
-            return viewField.Data;
+        let viewField = Cast(Document.viewType, "number");
+        if (viewField !== undefined) {
+            return viewField;
         } else {
             return CollectionViewType.Invalid;
         }
@@ -60,100 +59,77 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
         this.props.whenActiveChanged(isActive);
     }
 
-    createsCycle(documentToAdd: Document, containerDocument: Document): boolean {
-        if (!(documentToAdd instanceof Document)) {
+    createsCycle(documentToAdd: Doc, containerDocument: Doc): boolean {
+        if (!(documentToAdd instanceof Doc)) {
             return false;
         }
-        let data = documentToAdd.GetList(KeyStore.Data, [] as Document[]);
+        let data = Cast(documentToAdd.data, listSpec(Doc), []).filter(d => d).map(d => d as Doc);
         for (const doc of data.filter(d => d instanceof Document)) {
             if (this.createsCycle(doc, containerDocument)) {
                 return true;
             }
         }
-        let annots = documentToAdd.GetList(KeyStore.Annotations, [] as Document[]);
+        let annots = Cast(documentToAdd.annotations, listSpec(Doc), []).filter(d => d).map(d => d as Doc);
         for (const annot of annots) {
             if (this.createsCycle(annot, containerDocument)) {
                 return true;
             }
         }
-        for (let containerProto: FieldValue<Document> = containerDocument; containerProto && containerProto !== FieldWaiting; containerProto = containerProto.GetPrototype()) {
-            if (containerProto.Id === documentToAdd.Id) {
+        for (let containerProto: Opt<Doc> = containerDocument; containerProto !== undefined; containerProto = FieldValue(containerProto.proto)) {
+            if (containerProto[Id] === documentToAdd[Id]) {
                 return true;
             }
         }
         return false;
     }
-    @computed get isAnnotationOverlay() { return this.props.fieldKey && this.props.fieldKey.Id === KeyStore.Annotations.Id; } // bcz: ? Why do we need to compare Id's?
+    @computed get isAnnotationOverlay() { return this.props.fieldKey && this.props.fieldKey === "annotations"; }
 
     @action.bound
-    addDocument(doc: Document, allowDuplicates: boolean = false): boolean {
+    addDocument(doc: Doc, allowDuplicates: boolean = false): boolean {
         let props = this.props;
-        var curPage = props.Document.GetNumber(KeyStore.CurPage, -1);
-        doc.SetOnPrototype(KeyStore.Page, new NumberField(curPage));
-        if (this.isAnnotationOverlay) {
-            doc.SetNumber(KeyStore.Zoom, this.props.Document.GetNumber(KeyStore.Scale, 1));
-        }
+        var curPage = Cast(props.Document.curPage, "number", -1);
+        Doc.SetOnPrototype(doc, "page", curPage);
         if (curPage >= 0) {
-            doc.SetOnPrototype(KeyStore.AnnotationOn, props.Document);
+            Doc.SetOnPrototype(doc, "annotationOn", props.Document);
         }
-        if (props.Document.Get(props.fieldKey) instanceof Field) {
+        if (!this.createsCycle(doc, props.Document)) {
             //TODO This won't create the field if it doesn't already exist
-            const value = props.Document.GetData(props.fieldKey, ListField, new Array<Document>());
-            if (!this.createsCycle(doc, props.Document)) {
-                if (!value.some(v => v.Id === doc.Id) || allowDuplicates) {
+            const value = Cast(props.Document[props.fieldKey], listSpec(Doc));
+            let alreadyAdded = true;
+            if (value !== undefined) {
+                if (allowDuplicates || !value.some(v => v instanceof Doc && v[Id] === doc[Id])) {
+                    alreadyAdded = false;
                     value.push(doc);
                 }
+            } else {
+                alreadyAdded = false;
+                Doc.SetOnPrototype(this.props.Document, this.props.fieldKey, new List([doc]));
             }
-            else {
-                return false;
-            }
-        } else {
-            let proto = props.Document.GetPrototype();
-            if (!proto || proto === FieldWaiting || !this.createsCycle(proto, doc)) {
-                const field = new ListField([doc]);
-                // const script = CompileScript(`
-                //     if(added) {
-                //         console.log("added " + field.Title + " " + doc.Title);
-                //     } else {
-                //         console.log("removed " + field.Title + " " + doc.Title);
-                //     }
-                // `, {
-                //         addReturn: false,
-                //         params: {
-                //             field: Document.name,
-                //             added: "boolean"
-                //         },
-                //         capturedVariables: {
-                //             doc: this.props.Document
-                //         }
-                //     });
-                // if (script.compiled) {
-                //     field.addScript(new ScriptField(script));
-                // }
-                props.Document.SetOnPrototype(props.fieldKey, field);
-            }
-            else {
-                return false;
+            // set the ZoomBasis only if hasn't already been set -- bcz: maybe set/resetting the ZoomBasis should be a parameter to addDocument?
+            if (!alreadyAdded && (this.collectionViewType === CollectionViewType.Freeform || this.collectionViewType === CollectionViewType.Invalid)) {
+                let zoom = NumCast(this.props.Document.scale, 1);
+                Doc.SetOnPrototype(doc, "zoomBasis", zoom);
             }
         }
         return true;
     }
 
     @action.bound
-    removeDocument(doc: Document): boolean {
+    removeDocument(doc: Doc): boolean {
         const props = this.props;
         //TODO This won't create the field if it doesn't already exist
-        const value = props.Document.GetData(props.fieldKey, ListField, new Array<Document>());
+        const value = Cast(props.Document[props.fieldKey], listSpec(Doc), []);
         let index = -1;
         for (let i = 0; i < value.length; i++) {
-            if (value[i].Id === doc.Id) {
+            let v = value[i];
+            if (v instanceof Doc && v[Id] === doc[Id]) {
                 index = i;
                 break;
             }
         }
-        doc.GetTAsync(KeyStore.AnnotationOn, Document).then((annotationOn) => {
+        PromiseValue(Cast(doc.annotationOn, Doc)).then((annotationOn) => {
             if (annotationOn === props.Document) {
-                doc.Set(KeyStore.AnnotationOn, undefined, true);
+                doc.annotationOn = undefined;
             }
         });
 
@@ -168,11 +144,12 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
     }
 
     @action.bound
-    moveDocument(doc: Document, targetCollection: Document, addDocument: (doc: Document) => boolean): boolean {
+    moveDocument(doc: Doc, targetCollection: Doc, addDocument: (doc: Doc) => boolean): boolean {
         if (this.props.Document === targetCollection) {
             return true;
         }
         if (this.removeDocument(doc)) {
+            SelectionManager.DeselectAll();
             return addDocument(doc);
         }
         return false;
@@ -188,7 +165,9 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
         };
         const viewtype = this.collectionViewType;
         return (
-            <div className={this.props.className || "collectionView-cont"} onContextMenu={this.props.onContextMenu} ref={this.props.contentRef}>
+            <div className={this.props.className || "collectionView-cont"}
+                style={{ borderRadius: "inherit", pointerEvents: "all" }}
+                onContextMenu={this.props.onContextMenu} ref={this.props.contentRef}>
                 {viewtype !== undefined ? this.props.children(viewtype, props) : (null)}
             </div>
         );
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 39e27b601..159815ea5 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -1,36 +1,35 @@
-import * as GoldenLayout from "golden-layout";
 import 'golden-layout/src/css/goldenlayout-base.css';
 import 'golden-layout/src/css/goldenlayout-dark-theme.css';
-import { action, observable, reaction, trace } from "mobx";
+import { action, observable, reaction } from "mobx";
 import { observer } from "mobx-react";
 import * as ReactDOM from 'react-dom';
-import { Document } from "../../../fields/Document";
-import { KeyStore } from "../../../fields/KeyStore";
 import Measure from "react-measure";
-import { FieldId, Opt, Field, FieldWaiting } from "../../../fields/Field";
-import { Utils, returnTrue, emptyFunction, emptyDocFunction, returnOne } from "../../../Utils";
-import { Server } from "../../Server";
-import { undoBatch } from "../../util/UndoManager";
+import * as GoldenLayout from "../../../client/goldenLayout";
+import { Doc, Field, Opt } from "../../../new_fields/Doc";
+import { FieldId, Id } from "../../../new_fields/RefField";
+import { listSpec } from "../../../new_fields/Schema";
+import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
+import { emptyFunction, returnTrue, Utils } from "../../../Utils";
+import { DocServer } from "../../DocServer";
+import { DragLinksAsDocuments, DragManager } from "../../util/DragManager";
+import { Transform } from '../../util/Transform';
+import { undoBatch, UndoManager } from "../../util/UndoManager";
 import { DocumentView } from "../nodes/DocumentView";
 import "./CollectionDockingView.scss";
-import React = require("react");
 import { SubCollectionViewProps } from "./CollectionSubView";
-import { ServerUtils } from "../../../server/ServerUtil";
-import { DragManager, DragLinksAsDocuments } from "../../util/DragManager";
-import { TextField } from "../../../fields/TextField";
-import { ListField } from "../../../fields/ListField";
-import { thisExpression } from "babel-types";
+import React = require("react");
 
 @observer
 export class CollectionDockingView extends React.Component<SubCollectionViewProps> {
     public static Instance: CollectionDockingView;
-    public static makeDocumentConfig(document: Document) {
+    public static makeDocumentConfig(document: Doc, width?: number) {
         return {
             type: 'react-component',
             component: 'DocumentFrameRenderer',
-            title: document.Title,
+            title: document.title,
+            width: width,
             props: {
-                documentId: document.Id,
+                documentId: document[Id],
                 //collectionDockingView: CollectionDockingView.Instance
             }
         };
@@ -38,7 +37,6 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
 
     private _goldenLayout: any = null;
     private _containerRef = React.createRef<HTMLDivElement>();
-    private _fullScreen: any = null;
     private _flush: boolean = false;
     private _ignoreStateChange = "";
 
@@ -48,14 +46,18 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
         (window as any).React = React;
         (window as any).ReactDOM = ReactDOM;
     }
-    public StartOtherDrag(dragDocs: Document[], e: any) {
+    hack: boolean = false;
+    undohack: any = null;
+    public StartOtherDrag(dragDocs: Doc[], e: any) {
+        this.hack = true;
+        this.undohack = UndoManager.StartBatch("goldenDrag");
         dragDocs.map(dragDoc =>
             this.AddRightSplit(dragDoc, true).contentItems[0].tab._dragListener.
                 onMouseDown({ pageX: e.pageX, pageY: e.pageY, preventDefault: emptyFunction, button: 0 }));
     }
 
     @action
-    public OpenFullScreen(document: Document) {
+    public OpenFullScreen(document: Doc) {
         let newItemStackConfig = {
             type: 'stack',
             content: [CollectionDockingView.makeDocumentConfig(document)]
@@ -64,26 +66,52 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
         this._goldenLayout.root.contentItems[0].addChild(docconfig);
         docconfig.callDownwards('_$init');
         this._goldenLayout._$maximiseItem(docconfig);
-        this._fullScreen = docconfig;
         this._ignoreStateChange = JSON.stringify(this._goldenLayout.toConfig());
         this.stateChanged();
     }
+
+    @undoBatch
     @action
-    public CloseFullScreen() {
-        if (this._fullScreen) {
-            this._goldenLayout._$minimiseItem(this._fullScreen);
-            this._goldenLayout.root.contentItems[0].removeChild(this._fullScreen);
-            this._fullScreen = null;
-            this._ignoreStateChange = JSON.stringify(this._goldenLayout.toConfig());
-            this.stateChanged();
+    public CloseRightSplit(document: Doc) {
+        if (this._goldenLayout.root.contentItems[0].isRow) {
+            this._goldenLayout.root.contentItems[0].contentItems.map((child: any, i: number) => {
+                if (child.contentItems.length === 1 && child.contentItems[0].config.component === "DocumentFrameRenderer" &&
+                    child.contentItems[0].config.props.documentId == document[Id]) {
+                    child.contentItems[0].remove();
+                    this.layoutChanged(document);
+                    this.stateChanged();
+                } else
+                    child.contentItems.map((tab: any, j: number) => {
+                        if (tab.config.component === "DocumentFrameRenderer" && tab.config.props.documentId === document[Id]) {
+                            child.contentItems[j].remove();
+                            child.config.activeItemIndex = Math.max(child.contentItems.length - 1, 0);
+                            let docs = Cast(this.props.Document.data, listSpec(Doc));
+                            docs && docs.indexOf(document) !== -1 && docs.splice(docs.indexOf(document), 1);
+                            this.stateChanged();
+                        }
+                    });
+            })
         }
     }
 
+    @action
+    layoutChanged(removed?: Doc) {
+        this._goldenLayout.root.callDownwards('setSize', [this._goldenLayout.width, this._goldenLayout.height]);
+        this._goldenLayout.emit('sbcreteChanged');
+        this._ignoreStateChange = JSON.stringify(this._goldenLayout.toConfig());
+        if (removed) CollectionDockingView.Instance._removedDocs.push(removed);
+        this.stateChanged();
+    }
+
     //
     //  Creates a vertical split on the right side of the docking view, and then adds the Document to that split
     //
     @action
-    public AddRightSplit(document: Document, minimize: boolean = false) {
+    public AddRightSplit(document: Doc, minimize: boolean = false) {
+        let docs = Cast(this.props.Document.data, listSpec(Doc));
+        if (docs) {
+            docs.push(document);
+        }
         let newItemStackConfig = {
             type: 'stack',
             content: [CollectionDockingView.makeDocumentConfig(document)]
@@ -106,20 +134,18 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
             newContentItem.config.width = 50;
         }
         if (minimize) {
-            newContentItem.config.width = 10;
-            newContentItem.config.height = 10;
+            // bcz: this makes the drag image show up better, but it also messes with fixed layout sizes
+            // newContentItem.config.width = 10;
+            // newContentItem.config.height = 10;
         }
         newContentItem.callDownwards('_$init');
-        this._goldenLayout.root.callDownwards('setSize', [this._goldenLayout.width, this._goldenLayout.height]);
-        this._goldenLayout.emit('stateChanged');
-        this._ignoreStateChange = JSON.stringify(this._goldenLayout.toConfig());
-        this.stateChanged();
+        this.layoutChanged();
 
         return newContentItem;
     }
 
     setupGoldenLayout() {
-        var config = this.props.Document.GetText(KeyStore.Data, "");
+        var config = StrCast(this.props.Document.dockingConfig);
         if (config) {
             if (!this._goldenLayout) {
                 this._goldenLayout = new GoldenLayout(JSON.parse(config));
@@ -154,7 +180,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
     componentDidMount: () => void = () => {
         if (this._containerRef.current) {
             reaction(
-                () => this.props.Document.GetText(KeyStore.Data, ""),
+                () => StrCast(this.props.Document.dockingConfig),
                 () => {
                     if (!this._goldenLayout || this._ignoreStateChange !== JSON.stringify(this._goldenLayout.toConfig())) {
                         setTimeout(() => this.setupGoldenLayout(), 1);
@@ -202,8 +228,8 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
             let y = e.clientY;
             let docid = (e.target as any).DashDocId;
             let tab = (e.target as any).parentElement as HTMLElement;
-            Server.GetField(docid, action(async (sourceDoc: Opt<Field>) =>
-                (sourceDoc instanceof Document) && DragLinksAsDocuments(tab, x, y, sourceDoc)));
+            DocServer.GetRefField(docid).then(action(async (sourceDoc: Opt<Field>) =>
+                (sourceDoc instanceof Doc) && DragLinksAsDocuments(tab, x, y, sourceDoc)));
         } else
             if ((className === "lm_title" || className === "lm_tab lm_active") && !e.shiftKey) {
                 e.stopPropagation();
@@ -212,12 +238,12 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                 let y = e.clientY;
                 let docid = (e.target as any).DashDocId;
                 let tab = (e.target as any).parentElement as HTMLElement;
-                Server.GetField(docid, action((f: Opt<Field>) => {
-                    if (f instanceof Document) {
+                DocServer.GetRefField(docid).then(action((f: Opt<Field>) => {
+                    if (f instanceof Doc) {
                         DragManager.StartDocumentDrag([tab], new DragManager.DocumentDragData([f]), x, y,
                             {
                                 handlers: {
-                                    dragComplete: action(emptyFunction),
+                                    dragComplete: emptyFunction,
                                 },
                                 hideSource: false
                             });
@@ -234,8 +260,18 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
 
     @undoBatch
     stateChanged = () => {
+        let docs = Cast(CollectionDockingView.Instance.props.Document.data, listSpec(Doc));
+        CollectionDockingView.Instance._removedDocs.map(theDoc =>
+            docs && docs.indexOf(theDoc) !== -1 &&
+            docs.splice(docs.indexOf(theDoc), 1));
+        CollectionDockingView.Instance._removedDocs.length = 0;
         var json = JSON.stringify(this._goldenLayout.toConfig());
-        this.props.Document.SetText(KeyStore.Data, json);
+        this.props.Document.dockingConfig = json;
+        if (this.undohack && !this.hack) {
+            this.undohack.end();
+            this.undohack = undefined;
+        }
+        this.hack = false;
     }
 
     itemDropped = () => {
@@ -249,40 +285,42 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
         return template.content.firstChild;
     }
 
-    tabCreated = (tab: any) => {
+    tabCreated = async (tab: any) => {
         if (tab.hasOwnProperty("contentItem") && tab.contentItem.config.type !== "stack") {
-            Server.GetField(tab.contentItem.config.props.documentId, action((f: Opt<Field>) => {
-                if (f !== undefined && f instanceof Document) {
-                    f.GetTAsync(KeyStore.Title, TextField, (tfield) => {
-                        if (tfield !== undefined) {
-                            tab.titleElement[0].textContent = f.Title;
-                        }
-                    });
-                    f.GetTAsync(KeyStore.LinkedFromDocs, ListField).then(lf =>
-                        f.GetTAsync(KeyStore.LinkedToDocs, ListField).then(lt => {
-                            let count = (lf ? lf.Data.length : 0) + (lt ? lt.Data.length : 0);
-                            let counter: any = this.htmlToElement(`<div class="messageCounter">${count}</div>`);
-                            tab.element.append(counter);
-                            counter.DashDocId = tab.contentItem.config.props.documentId;
-                            tab.reactionDisposer = reaction(() => [f.GetT(KeyStore.LinkedFromDocs, ListField), f.GetT(KeyStore.LinkedToDocs, ListField)],
-                                (lists) => {
-                                    let count = (lists.length > 0 && lists[0] && lists[0]!.Data ? lists[0]!.Data.length : 0) +
-                                        (lists.length > 1 && lists[1] && lists[1]!.Data ? lists[1]!.Data.length : 0);
-                                    counter.innerHTML = count;
-                                });
-                        }));
+            if (tab.contentItem.config.fixed) {
+                tab.contentItem.parent.config.fixed = true;
+            }
+            DocServer.GetRefField(tab.contentItem.config.props.documentId).then(async doc => {
+                if (doc instanceof Doc) {
+                    let counter: any = this.htmlToElement(`<div class="messageCounter">0</div>`);
+                    tab.element.append(counter);
+                    counter.DashDocId = tab.contentItem.config.props.documentId;
+                    tab.reactionDisposer = reaction(() => [doc.linkedFromDocs, doc.LinkedToDocs, doc.title],
+                        () => {
+                            const lf = Cast(doc.linkedFromDocs, listSpec(Doc), []);
+                            const lt = Cast(doc.linkedToDocs, listSpec(Doc), []);
+                            let count = (lf ? lf.length : 0) + (lt ? lt.length : 0);
+                            counter.innerHTML = count;
+                            tab.titleElement[0].textContent = doc.title;
+                        }, { fireImmediately: true });
                     tab.titleElement[0].DashDocId = tab.contentItem.config.props.documentId;
                 }
-            }));
+            });
         }
         tab.closeElement.off('click') //unbind the current click handler
-            .click(function () {
+            .click(async function () {
                 if (tab.reactionDisposer) {
                     tab.reactionDisposer();
                 }
+                let doc = await DocServer.GetRefField(tab.contentItem.config.props.documentId);
+                if (doc instanceof Doc) {
+                    let theDoc = doc;
+                    CollectionDockingView.Instance._removedDocs.push(theDoc);
+                }
                 tab.contentItem.remove();
             });
     }
+    _removedDocs: Doc[] = [];
 
     stackCreated = (stack: any) => {
         //stack.header.controlsContainer.find('.lm_popout').hide();
@@ -291,13 +329,21 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
             .click(action(function () {
                 //if (confirm('really close this?')) {
                 stack.remove();
+                stack.contentItems.map(async (contentItem: any) => {
+                    let doc = await DocServer.GetRefField(contentItem.config.props.documentId);
+                    if (doc instanceof Doc) {
+                        let theDoc = doc;
+                        CollectionDockingView.Instance._removedDocs.push(theDoc);
+                    }
+                });
                 //}
             }));
         stack.header.controlsContainer.find('.lm_popout') //get the close icon
             .off('click') //unbind the current click handler
             .click(action(function () {
-                var url = ServerUtils.prepend("/doc/" + stack.contentItems[0].tab.contentItem.config.props.documentId);
-                let win = window.open(url, stack.contentItems[0].tab.title, "width=300,height=400");
+                stack.config.fixed = !stack.config.fixed;
+                // var url = DocServer.prepend("/doc/" + stack.contentItems[0].tab.contentItem.config.props.documentId);
+                // let win = window.open(url, stack.contentItems[0].tab.title, "width=300,height=400");
             }));
     }
 
@@ -307,6 +353,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                 onPointerDown={this.onPointerDown} onPointerUp={this.onPointerUp} ref={this._containerRef} />
         );
     }
+
 }
 
 interface DockedFrameProps {
@@ -315,38 +362,45 @@ interface DockedFrameProps {
 }
 @observer
 export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
-
     _mainCont = React.createRef<HTMLDivElement>();
     @observable private _panelWidth = 0;
     @observable private _panelHeight = 0;
-    @observable private _document: Opt<Document>;
+    @observable private _document: Opt<Doc>;
 
     constructor(props: any) {
         super(props);
-        Server.GetField(this.props.documentId, action((f: Opt<Field>) => this._document = f as Document));
+        DocServer.GetRefField(this.props.documentId).then(action((f: Opt<Field>) => this._document = f as Doc));
     }
 
-    nativeWidth = () => this._document!.GetNumber(KeyStore.NativeWidth, this._panelWidth);
-    nativeHeight = () => this._document!.GetNumber(KeyStore.NativeHeight, this._panelHeight);
+    nativeWidth = () => NumCast(this._document!.nativeWidth, this._panelWidth);
+    nativeHeight = () => NumCast(this._document!.nativeHeight, this._panelHeight);
     contentScaling = () => {
-        let wscale = this._panelWidth / (this.nativeWidth() ? this.nativeWidth() : this._panelWidth);
-        if (wscale * this.nativeHeight() > this._panelHeight)
-            return this._panelHeight / (this.nativeHeight() ? this.nativeHeight() : this._panelHeight);
-        return wscale;
+        const nativeH = this.nativeHeight();
+        const nativeW = this.nativeWidth();
+        let wscale = this._panelWidth / nativeW;
+        return wscale * nativeH > this._panelHeight ? this._panelHeight / nativeH : wscale;
     }
 
     ScreenToLocalTransform = () => {
-        let { scale, translateX, translateY } = Utils.GetScreenTransform(this._mainCont.current!.children[0].firstChild as HTMLElement);
-        scale = Utils.GetScreenTransform(this._mainCont.current!).scale;
-        return CollectionDockingView.Instance.props.ScreenToLocalTransform().translate(-translateX, -translateY).scale(scale / this.contentScaling());
+        if (this._mainCont.current && this._mainCont.current.children) {
+            let { scale, translateX, translateY } = Utils.GetScreenTransform(this._mainCont.current.children[0].firstChild as HTMLElement);
+            scale = Utils.GetScreenTransform(this._mainCont.current).scale;
+            return CollectionDockingView.Instance.props.ScreenToLocalTransform().translate(-translateX, -translateY).scale(scale / this.contentScaling());
+        }
+        return Transform.Identity();
     }
     get previewPanelCenteringOffset() { return (this._panelWidth - this.nativeWidth() * this.contentScaling()) / 2; }
 
     get content() {
+        if (!this._document) {
+            return (null);
+        }
         return (
             <div className="collectionDockingView-content" ref={this._mainCont}
                 style={{ transform: `translate(${this.previewPanelCenteringOffset}px, 0px)` }}>
-                <DocumentView key={this._document!.Id} Document={this._document!}
+                <DocumentView key={this._document[Id]} Document={this._document}
+                    toggleMinimized={emptyFunction}
+                    bringToFront={emptyFunction}
                     addDocument={undefined}
                     removeDocument={undefined}
                     ContentScaling={this.contentScaling}
@@ -357,15 +411,17 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
                     selectOnLoad={false}
                     parentActive={returnTrue}
                     whenActiveChanged={emptyFunction}
-                    focus={emptyDocFunction}
+                    focus={emptyFunction}
+                    bringToFront={emptyFunction}
                     ContainingCollectionView={undefined} />
-            </div>);
+            </div >);
     }
 
     render() {
+        let theContent = this.content;
         return !this._document ? (null) :
             <Measure onResize={action((r: any) => { this._panelWidth = r.entry.width; this._panelHeight = r.entry.height; })}>
-                {({ measureRef }) => <div ref={measureRef}>  {this.content} </div>}
+                {({ measureRef }) => <div ref={measureRef}>  {theContent} </div>}
             </Measure>;
     }
 }
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionPDFView.scss b/src/client/views/collections/CollectionPDFView.scss
index 0eca3f1cd..f6fb79582 100644
--- a/src/client/views/collections/CollectionPDFView.scss
+++ b/src/client/views/collections/CollectionPDFView.scss
@@ -1,20 +1,39 @@
 .collectionPdfView-buttonTray {
-    top : 25px;
+    top : 15px;
     left : 20px;
     position: relative; 
     transform-origin: left top;
     position: absolute;
 }
+.collectionPdfView-thumb {
+    width:25px;
+    height:25px;
+    transform-origin: left top;
+    position: absolute;
+    background: darkgray;
+}
+.collectionPdfView-slider {
+    width:25px;
+    height:25px;
+    transform-origin: left top;
+    position: absolute;
+    background: lightgray;
+}
 .collectionPdfView-cont{
     width: 100%;
     height: 100%;
     position: absolute; 
     top: 0;
     left:0;
-    
+}
+.collectionPdfView-cont-dragging {
+    span {
+        user-select: none;
+    }
 }
 .collectionPdfView-backward {
     color : white;
+    font-size: 24px;
     top :0px;
     left : 0px;
     position: absolute;
@@ -22,8 +41,9 @@
 }
 .collectionPdfView-forward {
     color : white;
+    font-size: 24px;
     top :0px;
-    left : 35px;
+    left : 45px;
     position: absolute;
     background-color: rgba(50, 50, 50, 0.2);
 }
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionPDFView.tsx b/src/client/views/collections/CollectionPDFView.tsx
index 229bc4059..b3762206a 100644
--- a/src/client/views/collections/CollectionPDFView.tsx
+++ b/src/client/views/collections/CollectionPDFView.tsx
@@ -1,6 +1,5 @@
-import { action } from "mobx";
+import { action, observable } from "mobx";
 import { observer } from "mobx-react";
-import { KeyStore } from "../../../fields/KeyStore";
 import { ContextMenu } from "../ContextMenu";
 import "./CollectionPDFView.scss";
 import React = require("react");
@@ -8,32 +7,60 @@ import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormV
 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";
 
 
 @observer
 export class CollectionPDFView extends React.Component<FieldViewProps> {
 
-    public static LayoutString(fieldKey: string = "DataKey") {
+    public static LayoutString(fieldKey: string = "data") {
         return FieldView.LayoutString(CollectionPDFView, fieldKey);
     }
+    @observable _inThumb = false;
 
-    private get curPage() { return this.props.Document.GetNumber(KeyStore.CurPage, -1); }
-    private get numPages() { return this.props.Document.GetNumber(KeyStore.NumPages, 0); }
-    @action onPageBack = () => this.curPage > 1 ? this.props.Document.SetNumber(KeyStore.CurPage, this.curPage - 1) : -1;
-    @action onPageForward = () => this.curPage < this.numPages ? this.props.Document.SetNumber(KeyStore.CurPage, this.curPage + 1) : -1;
+    private set curPage(value: number) { this.props.Document.curPage = value; }
+    private get curPage() { return NumCast(this.props.Document.curPage, -1); }
+    private get numPages() { return NumCast(this.props.Document.numPages); }
+    @action onPageBack = () => this.curPage > 1 ? (this.props.Document.curPage = this.curPage - 1) : -1;
+    @action onPageForward = () => this.curPage < this.numPages ? (this.props.Document.curPage = this.curPage + 1) : -1;
 
+    @action
+    onThumbDown = (e: React.PointerEvent) => {
+        document.addEventListener("pointermove", this.onThumbMove, false);
+        document.addEventListener("pointerup", this.onThumbUp, false);
+        e.stopPropagation();
+        this._inThumb = true;
+    }
+    @action
+    onThumbMove = (e: PointerEvent) => {
+        let pso = (e.clientY - (e as any).target.parentElement.getBoundingClientRect().top) / (e as any).target.parentElement.getBoundingClientRect().height;
+        this.curPage = Math.trunc(Math.min(this.numPages, pso * this.numPages + 1));
+        e.stopPropagation();
+    }
+    @action
+    onThumbUp = (e: PointerEvent) => {
+        this._inThumb = false;
+        document.removeEventListener("pointermove", this.onThumbMove);
+        document.removeEventListener("pointerup", this.onThumbUp);
+    }
+    nativeWidth = () => NumCast(this.props.Document.nativeWidth);
+    nativeHeight = () => NumCast(this.props.Document.nativeHeight);
     private get uIButtons() {
-        let scaling = Math.min(1.8, this.props.ScreenToLocalTransform().Scale);
+        let ratio = (this.curPage - 1) / this.numPages * 100;
         return (
-            <div className="collectionPdfView-buttonTray" key="tray" style={{ transform: `scale(${scaling}, ${scaling})` }}>
+            <div className="collectionPdfView-buttonTray" key="tray" style={{ height: "100%" }}>
                 <button className="collectionPdfView-backward" onClick={this.onPageBack}>{"<"}</button>
                 <button className="collectionPdfView-forward" onClick={this.onPageForward}>{">"}</button>
+                <div className="collectionPdfView-slider" onPointerDown={this.onThumbDown} style={{ top: 60, left: -20, width: 50, height: `calc(100% - 80px)` }} >
+                    <div className="collectionPdfView-thumb" onPointerDown={this.onThumbDown} style={{ top: `${ratio}%`, width: 50, height: 50 }} />
+                </div>
             </div>
         );
     }
 
     onContextMenu = (e: React.MouseEvent): void => {
-        if (!e.isPropagationStopped() && this.props.Document.Id !== "mainDoc") { // 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() && this.props.Document[Id] !== "mainDoc") { // 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: "PDFOptions", event: emptyFunction });
         }
     }
@@ -50,7 +77,7 @@ export class CollectionPDFView extends React.Component<FieldViewProps> {
 
     render() {
         return (
-            <CollectionBaseView {...this.props} className="collectionPdfView-cont" onContextMenu={this.onContextMenu}>
+            <CollectionBaseView {...this.props} className={`collectionPdfView-cont${this._inThumb ? "-dragging" : ""}`} onContextMenu={this.onContextMenu}>
                 {this.subView}
             </CollectionBaseView>
         );
diff --git a/src/client/views/collections/CollectionSchemaView.scss b/src/client/views/collections/CollectionSchemaView.scss
index bced0d5d4..cfdb3ab22 100644
--- a/src/client/views/collections/CollectionSchemaView.scss
+++ b/src/client/views/collections/CollectionSchemaView.scss
@@ -1,6 +1,7 @@
 @import "../globalCssVariables";
 
 
+
 .collectionSchemaView-container {
     border-width: $COLLECTION_BORDER_WIDTH;
     border-color : $intermediate-color;
@@ -10,6 +11,10 @@
     position: absolute;
     width: 100%;
     height: 100%;
+
+    .collectionSchemaView-cellContents {
+        height: $MAX_ROW_HEIGHT;
+    }
     
     .collectionSchemaView-previewRegion {
         position: relative;
@@ -104,7 +109,7 @@
         }
         .rt-tr-group {
             direction: ltr;
-            max-height: 44px;
+            max-height: $MAX_ROW_HEIGHT;
         }
         .rt-td {
             border-width: 1px;
@@ -136,7 +141,7 @@
     }
     .ReactTable .rt-th,
     .ReactTable .rt-td {
-        max-height: 44;
+        max-height: $MAX_ROW_HEIGHT;
         padding: 3px 7px;
         font-size: 13px;
         text-align: center;
@@ -153,9 +158,6 @@
     }
     .documentView-node:first-child {
         background: $light-color;
-        .imageBox-cont img {
-            object-fit: contain;
-        }
     }
 }
 //options menu styling
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 08c9740d0..ae949b2ed 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -2,19 +2,14 @@ import React = require("react");
 import { library } from '@fortawesome/fontawesome-svg-core';
 import { faCog, faPlus } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable, untracked } from "mobx";
+import { action, computed, observable, untracked, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import ReactTable, { CellInfo, ComponentPropsGetterR, ReactTableDefaults } from "react-table";
+import { MAX_ROW_HEIGHT } from '../../views/globalCssVariables.scss';
 import "react-table/react-table.css";
-import { Document } from "../../../fields/Document";
-import { Field, Opt } from "../../../fields/Field";
-import { Key } from "../../../fields/Key";
-import { KeyStore } from "../../../fields/KeyStore";
-import { ListField } from "../../../fields/ListField";
-import { emptyDocFunction, emptyFunction, returnFalse } from "../../../Utils";
-import { Server } from "../../Server";
+import { emptyFunction, returnFalse, returnZero } from "../../../Utils";
 import { SetupDrag } from "../../util/DragManager";
-import { CompileScript, ToField } from "../../util/Scripting";
+import { CompileScript } from "../../util/Scripting";
 import { Transform } from "../../util/Transform";
 import { COLLECTION_BORDER_WIDTH } from "../../views/globalCssVariables.scss";
 import { anchorPoints, Flyout } from "../DocumentDecorations";
@@ -24,44 +19,47 @@ import { DocumentView } from "../nodes/DocumentView";
 import { FieldView, FieldViewProps } from "../nodes/FieldView";
 import "./CollectionSchemaView.scss";
 import { CollectionSubView } from "./CollectionSubView";
+import { Opt, Field, Doc, DocListCast } from "../../../new_fields/Doc";
+import { Cast, FieldValue, NumCast } from "../../../new_fields/Types";
+import { listSpec } from "../../../new_fields/Schema";
+import { List } from "../../../new_fields/List";
+import { Id } from "../../../new_fields/RefField";
 
 
 // bcz: need to add drag and drop of rows and columns.  This seems like it might work for rows: https://codesandbox.io/s/l94mn1q657
 
 
 @observer
-class KeyToggle extends React.Component<{ keyId: string, checked: boolean, toggle: (key: Key) => void }> {
-    @observable key: Key | undefined;
-
+class KeyToggle extends React.Component<{ keyName: string, checked: boolean, toggle: (key: string) => void }> {
     constructor(props: any) {
         super(props);
-        Server.GetField(this.props.keyId, action((field: Opt<Field>) => field instanceof Key && (this.key = field)));
     }
 
     render() {
-        return !this.key ? (null) :
-            (<div key={this.key.Id}>
-                <input type="checkbox" checked={this.props.checked} onChange={() => this.key && this.props.toggle(this.key)} />
-                {this.key.Name}
-            </div>);
+        return (
+            <div key={this.props.keyName}>
+                <input type="checkbox" checked={this.props.checked} onChange={() => this.props.toggle(this.props.keyName)} />
+                {this.props.keyName}
+            </div>
+        );
     }
 }
 
 @observer
-export class CollectionSchemaView extends CollectionSubView {
+export class CollectionSchemaView extends CollectionSubView(doc => doc) {
     private _mainCont?: HTMLDivElement;
     private _startSplitPercent = 0;
     private DIVIDER_WIDTH = 4;
 
-    @observable _columns: Array<Key> = [KeyStore.Title, KeyStore.Data, KeyStore.Author];
+    @observable _columns: Array<string> = ["title", "data", "author"];
     @observable _selectedIndex = 0;
     @observable _columnsPercentage = 0;
-    @observable _keys: Key[] = [];
+    @observable _keys: string[] = [];
     @observable _newKeyName: string = "";
 
-    @computed get splitPercentage() { return this.props.Document.GetNumber(KeyStore.SchemaSplitPercentage, 0); }
-    @computed get columns() { return this.props.Document.GetList(KeyStore.ColumnsKey, [] as Key[]); }
-    @computed get borderWidth() { return COLLECTION_BORDER_WIDTH; }
+    @computed get splitPercentage() { return NumCast(this.props.Document.schemaSplitPercentage); }
+    @computed get columns() { return Cast(this.props.Document.schemaColumns, listSpec("string"), []); }
+    @computed get borderWidth() { return Number(COLLECTION_BORDER_WIDTH); }
 
     renderCell = (rowProps: CellInfo) => {
         let props: FieldViewProps = {
@@ -73,43 +71,38 @@ export class CollectionSchemaView extends CollectionSubView {
             isTopMost: false,
             selectOnLoad: false,
             ScreenToLocalTransform: Transform.Identity,
-            focus: emptyDocFunction,
+            focus: emptyFunction,
             active: returnFalse,
             whenActiveChanged: emptyFunction,
+            PanelHeight: returnZero,
+            PanelWidth: returnZero,
         };
         let contents = (
             <FieldView {...props} />
         );
         let reference = React.createRef<HTMLDivElement>();
         let onItemDown = SetupDrag(reference, () => props.Document, this.props.moveDocument);
-        let applyToDoc = (doc: Document, run: (args?: { [name: string]: any }) => any) => {
+        let applyToDoc = (doc: Doc, run: (args?: { [name: string]: any }) => any) => {
             const res = run({ this: doc });
             if (!res.success) return false;
             const field = res.result;
-            if (field instanceof Field) {
-                doc.Set(props.fieldKey, field);
-                return true;
-            } else {
-                let dataField = ToField(field);
-                if (dataField) {
-                    doc.Set(props.fieldKey, dataField);
-                    return true;
-                }
-            }
-            return false;
+            doc[props.fieldKey] = field;
+            return true;
         };
         return (
-            <div className="collectionSchemaView-cellContents" onPointerDown={onItemDown} style={{ height: "56px" }} key={props.Document.Id} ref={reference}>
+            <div className="collectionSchemaView-cellContents" onPointerDown={onItemDown} key={props.Document[Id]} ref={reference}>
                 <EditableView
                     display={"inline"}
                     contents={contents}
-                    height={56}
+                    height={Number(MAX_ROW_HEIGHT)}
                     GetValue={() => {
-                        let field = props.Document.Get(props.fieldKey);
-                        if (field && field instanceof Field) {
-                            return field.ToScriptString();
+                        let field = props.Document[props.fieldKey];
+                        if (field) {
+                            //TODO Types
+                            // return field.ToScriptString();
+                            return String(field);
                         }
-                        return field || "";
+                        return "";
                     }}
                     SetValue={(value: string) => {
                         let script = CompileScript(value, { addReturn: true, params: { this: Document.name } });
@@ -118,21 +111,18 @@ export class CollectionSchemaView extends CollectionSubView {
                         }
                         return applyToDoc(props.Document, script.run);
                     }}
-                    OnFillDown={(value: string) => {
+                    OnFillDown={async (value: string) => {
                         let script = CompileScript(value, { addReturn: true, params: { this: Document.name } });
                         if (!script.compiled) {
                             return;
                         }
                         const run = script.run;
                         //TODO This should be able to be refactored to compile the script once
-                        this.props.Document.GetTAsync<ListField<Document>>(this.props.fieldKey, ListField).then((val) => {
-                            if (val) {
-                                val.Data.forEach(doc => applyToDoc(doc, run));
-                            }
-                        });
+                        const val = await DocListCast(this.props.Document[this.props.fieldKey])
+                        val && val.forEach(doc => applyToDoc(doc, run));
                     }}>
                 </EditableView>
-            </div>
+            </div >
         );
     }
 
@@ -163,36 +153,37 @@ export class CollectionSchemaView extends CollectionSubView {
     }
 
     @action
-    toggleKey = (key: Key) => {
-        this.props.Document.GetOrCreateAsync<ListField<Key>>(KeyStore.ColumnsKey, ListField,
-            (field) => {
-                const index = field.Data.indexOf(key);
-                if (index === -1) {
-                    this.columns.push(key);
-                } else {
-                    this.columns.splice(index, 1);
-                }
-
-            });
+    toggleKey = (key: string) => {
+        let list = Cast(this.props.Document.schemaColumns, listSpec("string"));
+        if (list === undefined) {
+            this.props.Document.schemaColumns = list = new List<string>([key]);
+        } else {
+            const index = list.indexOf(key);
+            if (index === -1) {
+                list.push(key);
+            } else {
+                list.splice(index, 1);
+            }
+        }
     }
 
     //toggles preview side-panel of schema
     @action
     toggleExpander = (event: React.ChangeEvent<HTMLInputElement>) => {
-        this.props.Document.SetNumber(KeyStore.SchemaSplitPercentage, this.splitPercentage === 0 ? 33 : 0);
+        this.props.Document.schemaSplitPercentage = this.splitPercentage === 0 ? 33 : 0;
     }
 
     @action
     onDividerMove = (e: PointerEvent): void => {
         let nativeWidth = this._mainCont!.getBoundingClientRect();
-        this.props.Document.SetNumber(KeyStore.SchemaSplitPercentage, Math.max(0, 100 - Math.round((e.clientX - nativeWidth.left) / nativeWidth.width * 100)));
+        this.props.Document.schemaSplitPercentage = Math.max(0, 100 - Math.round((e.clientX - nativeWidth.left) / nativeWidth.width * 100));
     }
     @action
     onDividerUp = (e: PointerEvent): void => {
         document.removeEventListener("pointermove", this.onDividerMove);
         document.removeEventListener('pointerup', this.onDividerUp);
         if (this._startSplitPercent === this.splitPercentage) {
-            this.props.Document.SetNumber(KeyStore.SchemaSplitPercentage, this.splitPercentage === 0 ? 33 : 0);
+            this.props.Document.schemaSplitPercentage = this.splitPercentage === 0 ? 33 : 0;
         }
     }
     onDividerDown = (e: React.PointerEvent) => {
@@ -205,8 +196,7 @@ export class CollectionSchemaView extends CollectionSubView {
 
     onPointerDown = (e: React.PointerEvent): void => {
         if (e.button === 0 && !e.altKey && !e.ctrlKey && !e.metaKey) {
-            if (this.props.isSelected())
-                e.stopPropagation();
+            if (this.props.isSelected()) e.stopPropagation();
             else e.preventDefault();
         }
     }
@@ -219,7 +209,7 @@ export class CollectionSchemaView extends CollectionSubView {
 
     @action
     addColumn = () => {
-        this.columns.push(new Key(this._newKeyName));
+        this.columns.push(this._newKeyName);
         this._newKeyName = "";
     }
 
@@ -234,21 +224,22 @@ export class CollectionSchemaView extends CollectionSubView {
         this.previewScript = e.currentTarget.value;
     }
 
-    get previewDocument(): Document | undefined {
-        const children = this.props.Document.GetList(this.props.fieldKey, [] as Document[]);
-        const selected = children.length > this._selectedIndex ? children[this._selectedIndex] : undefined;
-        return selected ? (this.previewScript ? selected.Get(new Key(this.previewScript)) as Document : selected) : undefined;
+    get previewDocument(): Doc | undefined {
+        const children = Cast(this.props.Document[this.props.fieldKey], listSpec(Doc), []);
+        const selected = children.length > this._selectedIndex ? FieldValue(children[this._selectedIndex]) : undefined;
+        return selected ? (this.previewScript ? FieldValue(Cast(selected[this.previewScript], Doc)) : selected) : undefined;
     }
-    get tableWidth() { return this.props.PanelWidth() * (1 - this.splitPercentage / 100); }
-    get previewRegionHeight() { return this.props.PanelHeight(); }
-    get previewRegionWidth() { return this.props.PanelWidth() * this.splitPercentage / 100 - this.DIVIDER_WIDTH; }
+    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; }
+    get previewRegionWidth() { return (this.props.PanelWidth() - 2 * this.borderWidth - this.DIVIDER_WIDTH) * this.splitPercentage / 100; }
 
-    private previewDocNativeWidth = () => this.previewDocument!.GetNumber(KeyStore.NativeWidth, this.previewRegionWidth);
-    private previewDocNativeHeight = () => this.previewDocument!.GetNumber(KeyStore.NativeHeight, this.previewRegionHeight);
+    private previewDocNativeWidth = () => Cast(this.previewDocument!.nativeWidth, "number", this.previewRegionWidth);
+    private previewDocNativeHeight = () => Cast(this.previewDocument!.nativeHeight, "number", this.previewRegionHeight);
     private previewContentScaling = () => {
         let wscale = this.previewRegionWidth / (this.previewDocNativeWidth() ? this.previewDocNativeWidth() : this.previewRegionWidth);
-        if (wscale * this.previewDocNativeHeight() > this.previewRegionHeight)
+        if (wscale * this.previewDocNativeHeight() > this.previewRegionHeight) {
             return this.previewRegionHeight / (this.previewDocNativeHeight() ? this.previewDocNativeHeight() : this.previewRegionHeight);
+        }
         return wscale;
     }
     private previewPanelWidth = () => this.previewDocNativeWidth() * this.previewContentScaling();
@@ -256,23 +247,26 @@ export class CollectionSchemaView extends CollectionSubView {
     get previewPanelCenteringOffset() { return (this.previewRegionWidth - this.previewDocNativeWidth() * this.previewContentScaling()) / 2; }
     getPreviewTransform = (): Transform => this.props.ScreenToLocalTransform().translate(
         - this.borderWidth - this.DIVIDER_WIDTH - this.tableWidth - this.previewPanelCenteringOffset,
-        - this.borderWidth).scale(1 / this.previewContentScaling());
+        - this.borderWidth).scale(1 / this.previewContentScaling())
 
     @computed
     get previewPanel() {
         // let doc = CompileScript(this.previewScript, { this: selected }, true)();
-        return !this.previewDocument ? (null) : (
-            <div className="collectionSchemaView-previewRegion" style={{ width: `calc(${this.props.Document.GetNumber(KeyStore.SchemaSplitPercentage, 0)}% - ${this.DIVIDER_WIDTH}px)` }}>
+        const previewDoc = this.previewDocument;
+        return !previewDoc ? (null) : (
+            <div className="collectionSchemaView-previewRegion" style={{ width: `${this.previewRegionWidth}px` }}>
                 <div className="collectionSchemaView-previewDoc" style={{ transform: `translate(${this.previewPanelCenteringOffset}px, 0px)` }}>
-                    <DocumentView Document={this.previewDocument} isTopMost={false} selectOnLoad={false}
+                    <DocumentView Document={previewDoc} isTopMost={false} selectOnLoad={false}
+                        toggleMinimized={emptyFunction}
                         addDocument={this.props.addDocument} removeDocument={this.props.removeDocument}
                         ScreenToLocalTransform={this.getPreviewTransform}
                         ContentScaling={this.previewContentScaling}
                         PanelWidth={this.previewPanelWidth} PanelHeight={this.previewPanelHeight}
                         ContainingCollectionView={this.props.CollectionView}
-                        focus={emptyDocFunction}
+                        focus={emptyFunction}
                         parentActive={this.props.active}
                         whenActiveChanged={this.props.whenActiveChanged}
+                        bringToFront={emptyFunction}
                     />
                 </div>
                 <input className="collectionSchemaView-input" value={this.previewScript} onChange={this.onPreviewScriptChange}
@@ -282,24 +276,25 @@ export class CollectionSchemaView extends CollectionSubView {
     }
 
     get documentKeysCheckList() {
-        const docs = this.props.Document.GetList(this.props.fieldKey, [] as Document[]);
-        let keys: { [id: string]: boolean } = {};
+        const docs = Cast(this.props.Document[this.props.fieldKey], listSpec(Doc), []).filter(d => d).map(d => d as Doc);
+        let keys: { [key: string]: boolean } = {};
         // bcz: ugh.  this is untracked since otherwise a large collection of documents will blast the server for all their fields.
         //  then as each document's fields come back, we update the documents _proxies.  Each time we do this, the whole schema will be
         //  invalidated and re-rendered.   This workaround will inquire all of the document fields before the options button is clicked.
         //  then by the time the options button is clicked, all of the fields should be in place.  If a new field is added while this menu
         //  is displayed (unlikely) it won't show up until something else changes.
-        untracked(() => docs.map(doc => doc.GetAllPrototypes().map(proto => proto._proxies.forEach((val: any, key: string) => keys[key] = false))));
+        //TODO Types
+        untracked(() => docs.map(doc => Doc.GetAllPrototypes(doc).map(proto => Object.keys(proto).forEach(key => keys[key] = false))));
 
-        this.columns.forEach(key => keys[key.Id] = true);
+        this.columns.forEach(key => keys[key] = true);
         return Array.from(Object.keys(keys)).map(item =>
-            (<KeyToggle checked={keys[item]} key={item} keyId={item} toggle={this.toggleKey} />));
+            (<KeyToggle checked={keys[item]} key={item} keyName={item} toggle={this.toggleKey} />));
     }
 
     get tableOptionsPanel() {
         return !this.props.active() ? (null) :
             (<Flyout
-                anchorPoint={anchorPoints.LEFT_TOP}
+                anchorPoint={anchorPoints.RIGHT_TOP}
                 content={<div>
                     <div id="schema-options-header"><h5><b>Options</b></h5></div>
                     <div id="options-flyout-div">
@@ -327,19 +322,16 @@ export class CollectionSchemaView extends CollectionSubView {
     render() {
         library.add(faCog);
         library.add(faPlus);
-        if (!this.previewDocument)
-            return (null);
-
-        const children = this.props.Document.GetList(this.props.fieldKey, [] as Document[]);
+        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}>
-                <div className="collectionSchemaView-tableContainer" style={{ width: `calc(100% - ${this.splitPercentage}%)` }}>
+                <div className="collectionSchemaView-tableContainer" style={{ width: `${this.tableWidth}px` }}>
                     <ReactTable data={children} page={0} pageSize={children.length} showPagination={false}
                         columns={this.columns.map(col => ({
-                            Header: col.Name,
-                            accessor: (doc: Document) => [doc, col],
-                            id: col.Id
+                            Header: col,
+                            accessor: (doc: Doc) => [doc, col],
+                            id: col
                         }))}
                         column={{ ...ReactTableDefaults.column, Cell: this.renderCell, }}
                         getTrProps={this.getTrProps}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 5c3b2e586..0b08e150a 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -1,28 +1,28 @@
 import { action, runInAction } from "mobx";
-import { Document } from "../../../fields/Document";
-import { ListField } from "../../../fields/ListField";
 import React = require("react");
-import { KeyStore } from "../../../fields/KeyStore";
-import { FieldWaiting, Opt } from "../../../fields/Field";
 import { undoBatch, UndoManager } from "../../util/UndoManager";
 import { DragManager } from "../../util/DragManager";
-import { Documents, DocumentOptions } from "../../documents/Documents";
+import { Docs, DocumentOptions } from "../../documents/Documents";
 import { RouteStore } from "../../../server/RouteStore";
-import { TupleField } from "../../../fields/TupleField";
 import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
-import { NumberField } from "../../../fields/NumberField";
-import { ServerUtils } from "../../../server/ServerUtil";
-import { Server } from "../../Server";
 import { FieldViewProps } from "../nodes/FieldView";
 import * as rp from 'request-promise';
 import { CollectionView } from "./CollectionView";
 import { CollectionPDFView } from "./CollectionPDFView";
 import { CollectionVideoView } from "./CollectionVideoView";
+import { Doc, Opt, FieldResult } from "../../../new_fields/Doc";
+import { DocComponent } from "../DocComponent";
+import { listSpec } from "../../../new_fields/Schema";
+import { Cast, PromiseValue, FieldValue, ListSpec } from "../../../new_fields/Types";
+import { List } from "../../../new_fields/List";
+import { DocServer } from "../../DocServer";
+import { ObjectField } from "../../../new_fields/ObjectField";
+import CursorField, { CursorPosition, CursorMetadata } from "../../../new_fields/CursorField";
 
 export interface CollectionViewProps extends FieldViewProps {
-    addDocument: (document: Document, allowDuplicates?: boolean) => boolean;
-    removeDocument: (document: Document) => boolean;
-    moveDocument: (document: Document, targetCollection: Document, addDocument: (document: Document) => boolean) => boolean;
+    addDocument: (document: Doc, allowDuplicates?: boolean) => boolean;
+    removeDocument: (document: Doc) => boolean;
+    moveDocument: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
     PanelWidth: () => number;
     PanelHeight: () => number;
 }
@@ -31,200 +31,195 @@ export interface SubCollectionViewProps extends CollectionViewProps {
     CollectionView: CollectionView | CollectionPDFView | CollectionVideoView;
 }
 
-export type CursorEntry = TupleField<[string, string], [number, number]>;
-
-export class CollectionSubView extends React.Component<SubCollectionViewProps> {
-    private dropDisposer?: DragManager.DragDropDisposer;
-    protected createDropTarget = (ele: HTMLDivElement) => {
-        if (this.dropDisposer) {
-            this.dropDisposer();
+export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
+    class CollectionSubView extends DocComponent<SubCollectionViewProps, T>(schemaCtor) {
+        private dropDisposer?: DragManager.DragDropDisposer;
+        protected createDropTarget = (ele: HTMLDivElement) => {
+            if (this.dropDisposer) {
+                this.dropDisposer();
+            }
+            if (ele) {
+                this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } });
+            }
         }
-        if (ele) {
-            this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } });
+        protected CreateDropTarget(ele: HTMLDivElement) {
+            this.createDropTarget(ele);
+        }
+
+        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)).map(doc => doc as Doc);
         }
-    }
-    protected CreateDropTarget(ele: HTMLDivElement) {
-        this.createDropTarget(ele);
-    }
 
-    @action
-    protected setCursorPosition(position: [number, number]) {
-        let ind;
-        let doc = this.props.Document;
-        let id = CurrentUserUtils.id;
-        let email = CurrentUserUtils.email;
-        if (id && email) {
-            let textInfo: [string, string] = [id, email];
-            doc.GetTAsync(KeyStore.Prototype, Document).then(proto => {
+        @action
+        protected async setCursorPosition(position: [number, number]) {
+            let ind;
+            let doc = this.props.Document;
+            let id = CurrentUserUtils.id;
+            let email = CurrentUserUtils.email;
+            let pos = { x: position[0], y: position[1] };
+            if (id && email) {
+                const proto = await doc.proto;
                 if (!proto) {
                     return;
                 }
-                proto.GetOrCreateAsync<ListField<CursorEntry>>(KeyStore.Cursors, ListField, action((field: ListField<CursorEntry>) => {
-                    let cursors = field.Data;
-                    if (cursors.length > 0 && (ind = cursors.findIndex(entry => entry.Data[0][0] === id)) > -1) {
-                        cursors[ind].Data[1] = position;
-                    } else {
-                        let entry = new TupleField<[string, string], [number, number]>([textInfo, position]);
-                        cursors.push(entry);
-                    }
-                }));
-            });
+                let cursors = Cast(proto.cursors, listSpec(CursorField));
+                if (!cursors) {
+                    proto.cursors = cursors = new List<CursorField>();
+                }
+                if (cursors.length > 0 && (ind = cursors.findIndex(entry => entry.data.metadata.id === id)) > -1) {
+                    cursors[ind].setPosition(pos);
+                } else {
+                    let entry = new CursorField({ metadata: { id: id, identifier: email }, position: pos });
+                    cursors.push(entry);
+                }
+            }
         }
-    }
 
-    @undoBatch
-    @action
-    protected drop(e: Event, de: DragManager.DropEvent): boolean {
-        if (de.data instanceof DragManager.DocumentDragData) {
-            if (de.data.aliasOnDrop || de.data.copyOnDrop) {
-                [KeyStore.Width, KeyStore.Height, KeyStore.CurPage].map(key =>
-                    de.data.draggedDocuments.map((draggedDocument: Document, i: number) =>
-                        draggedDocument.GetTAsync(key, NumberField, (f: Opt<NumberField>) => f ? de.data.droppedDocuments[i].SetNumber(key, f.Data) : null)));
-            }
-            let added = false;
-            if (de.data.aliasOnDrop || de.data.copyOnDrop) {
-                added = de.data.droppedDocuments.reduce((added: boolean, d) => {
-                    let moved = this.props.addDocument(d);
-                    return moved || added;
-                }, false);
-            } else if (de.data.moveDocument) {
-                const move = de.data.moveDocument;
-                added = de.data.droppedDocuments.reduce((added: boolean, d) => {
-                    let moved = move(d, this.props.Document, this.props.addDocument);
-                    return moved || added;
-                }, false);
-            } else {
-                added = de.data.droppedDocuments.reduce((added: boolean, d) => {
-                    let moved = this.props.addDocument(d);
-                    return moved || added;
-                }, false);
+        @undoBatch
+        @action
+        protected drop(e: Event, de: DragManager.DropEvent): boolean {
+            if (de.data instanceof DragManager.DocumentDragData) {
+                if (de.data.dropAction || de.data.userDropAction) {
+                    ["width", "height", "curPage"].map(key =>
+                        de.data.draggedDocuments.map((draggedDocument: Doc, i: number) =>
+                            PromiseValue(Cast(draggedDocument[key], "number")).then(f => f && (de.data.droppedDocuments[i][key] = f))));
+                }
+                let added = false;
+                if (de.data.dropAction || de.data.userDropAction) {
+                    added = de.data.droppedDocuments.reduce((added: boolean, d) => {
+                        let moved = this.props.addDocument(d);
+                        return moved || added;
+                    }, false);
+                } else if (de.data.moveDocument) {
+                    const move = de.data.moveDocument;
+                    added = de.data.droppedDocuments.reduce((added: boolean, d) => {
+                        let moved = move(d, this.props.Document, this.props.addDocument);
+                        return moved || added;
+                    }, false);
+                } else {
+                    added = de.data.droppedDocuments.reduce((added: boolean, d) => {
+                        let moved = this.props.addDocument(d);
+                        return moved || added;
+                    }, false);
+                }
+                e.stopPropagation();
+                return added;
             }
-            e.stopPropagation();
-            return added;
+            return false;
         }
-        return false;
-    }
 
-    protected async getDocumentFromType(type: string, path: string, options: DocumentOptions): Promise<Opt<Document>> {
-        let ctor: ((path: string, options: DocumentOptions) => (Document | Promise<Document | undefined>)) | undefined = undefined;
-        if (type.indexOf("image") !== -1) {
-            ctor = Documents.ImageDocument;
-        }
-        if (type.indexOf("video") !== -1) {
-            ctor = Documents.VideoDocument;
-        }
-        if (type.indexOf("audio") !== -1) {
-            ctor = Documents.AudioDocument;
-        }
-        if (type.indexOf("pdf") !== -1) {
-            ctor = Documents.PdfDocument;
-            options.nativeWidth = 1200;
-        }
-        if (type.indexOf("excel") !== -1) {
-            ctor = Documents.DBDocument;
-            options.copyDraggedItems = true;
-        }
-        if (type.indexOf("html") !== -1) {
-            if (path.includes('localhost')) {
-                let s = path.split('/');
-                let id = s[s.length - 1];
-                Server.GetField(id).then(field => {
-                    if (field instanceof Document) {
-                        let alias = field.CreateAlias();
-                        alias.SetNumber(KeyStore.X, options.x || 0);
-                        alias.SetNumber(KeyStore.Y, options.y || 0);
-                        alias.SetNumber(KeyStore.Width, options.width || 300);
-                        alias.SetNumber(KeyStore.Height, options.height || options.width || 300);
-                        this.props.addDocument(alias, false);
-                    }
-                });
-                return undefined;
+        protected async getDocumentFromType(type: string, path: string, options: DocumentOptions): Promise<Opt<Doc>> {
+            let ctor: ((path: string, options: DocumentOptions) => (Doc | Promise<Doc | undefined>)) | undefined = undefined;
+            if (type.indexOf("image") !== -1) {
+                ctor = Docs.ImageDocument;
+            }
+            if (type.indexOf("video") !== -1) {
+                ctor = Docs.VideoDocument;
+            }
+            if (type.indexOf("audio") !== -1) {
+                ctor = Docs.AudioDocument;
             }
-            ctor = Documents.WebDocument;
-            options = { height: options.width, ...options, title: path };
+            if (type.indexOf("pdf") !== -1) {
+                ctor = Docs.PdfDocument;
+                options.nativeWidth = 1200;
+            }
+            if (type.indexOf("excel") !== -1) {
+                ctor = Docs.DBDocument;
+                options.dropAction = "copy";
+            }
+            if (type.indexOf("html") !== -1) {
+                if (path.includes('localhost')) {
+                    let s = path.split('/');
+                    let id = s[s.length - 1];
+                    DocServer.GetRefField(id).then(field => {
+                        if (field instanceof Doc) {
+                            let alias = Doc.MakeAlias(field);
+                            alias.x = options.x || 0;
+                            alias.y = options.y || 0;
+                            alias.width = options.width || 300;
+                            alias.height = options.height || options.width || 300;
+                            this.props.addDocument(alias, false);
+                        }
+                    });
+                    return undefined;
+                }
+                ctor = Docs.WebDocument;
+                options = { height: options.width, ...options, title: path, nativeWidth: undefined };
+            }
+            return ctor ? ctor(path, options) : undefined;
         }
-        return ctor ? ctor(path, options) : undefined;
-    }
 
-    @undoBatch
-    @action
-    protected onDrop(e: React.DragEvent, options: DocumentOptions): void {
-        let html = e.dataTransfer.getData("text/html");
-        let text = e.dataTransfer.getData("text/plain");
+        @undoBatch
+        @action
+        protected onDrop(e: React.DragEvent, options: DocumentOptions): void {
+            let html = e.dataTransfer.getData("text/html");
+            let text = e.dataTransfer.getData("text/plain");
 
-        if (text && text.startsWith("<div")) {
-            return;
-        }
-        e.stopPropagation();
-        e.preventDefault();
-
-        if (html && html.indexOf("<img") !== 0 && !html.startsWith("<a")) {
-            console.log("not good");
-            let htmlDoc = Documents.HtmlDocument(html, { ...options, width: 300, height: 300 });
-            htmlDoc.SetText(KeyStore.DocumentText, text);
-            this.props.addDocument(htmlDoc, false);
-            return;
-        }
+            if (text && text.startsWith("<div")) {
+                return;
+            }
+            e.stopPropagation();
+            e.preventDefault();
 
-        let batch = UndoManager.StartBatch("collection view drop");
-        let promises: Promise<void>[] = [];
-        // tslint:disable-next-line:prefer-for-of
-        for (let i = 0; i < e.dataTransfer.items.length; i++) {
-            const upload = window.location.origin + RouteStore.upload;
-            let item = e.dataTransfer.items[i];
-            if (item.kind === "string" && item.type.indexOf("uri") !== -1) {
-                let str: string;
-                let prom = new Promise<string>(resolve => e.dataTransfer.items[i].getAsString(resolve))
-                    .then(action((s: string) => rp.head(ServerUtils.prepend(RouteStore.corsProxy + "/" + (str = s)))))
-                    .then(result => {
-                        let type = result.headers["content-type"];
-                        if (type) {
-                            this.getDocumentFromType(type, str, { ...options, width: 300, nativeWidth: 300 })
-                                .then(doc => doc && this.props.addDocument(doc, false));
-                        }
-                    });
-                promises.push(prom);
+            if (html && html.indexOf("<img") !== 0 && !html.startsWith("<a")) {
+                let htmlDoc = Docs.HtmlDocument(html, { ...options, width: 300, height: 300, documentText: text });
+                this.props.addDocument(htmlDoc, false);
+                return;
             }
-            let type = item.type;
-            if (item.kind === "file") {
-                let file = item.getAsFile();
-                let formData = new FormData();
 
-                if (file) {
-                    formData.append('file', file);
-                }
-                let dropFileName = file ? file.name : "-empty-";
-
-                let prom = fetch(upload, {
-                    method: 'POST',
-                    body: formData
-                }).then(async (res: Response) => {
-                    (await res.json()).map(action((file: any) => {
-                        let path = window.location.origin + file;
-                        let docPromise = this.getDocumentFromType(type, path, { ...options, nativeWidth: 600, width: 300, title: dropFileName });
-
-                        docPromise.then(action((doc?: Document) => {
-                            let docs = this.props.Document.GetT(KeyStore.Data, ListField);
-                            if (docs !== FieldWaiting) {
-                                if (!docs) {
-                                    docs = new ListField<Document>();
-                                    this.props.Document.Set(KeyStore.Data, docs);
-                                }
-                                if (doc) {
-                                    docs.Data.push(doc);
-                                }
+            let batch = UndoManager.StartBatch("collection view drop");
+            let promises: Promise<void>[] = [];
+            // tslint:disable-next-line:prefer-for-of
+            for (let i = 0; i < e.dataTransfer.items.length; i++) {
+                const upload = window.location.origin + RouteStore.upload;
+                let item = e.dataTransfer.items[i];
+                if (item.kind === "string" && item.type.indexOf("uri") !== -1) {
+                    let str: string;
+                    let prom = new Promise<string>(resolve => e.dataTransfer.items[i].getAsString(resolve))
+                        .then(action((s: string) => rp.head(DocServer.prepend(RouteStore.corsProxy + "/" + (str = s)))))
+                        .then(result => {
+                            let type = result["content-type"];
+                            if (type) {
+                                this.getDocumentFromType(type, str, { ...options, width: 300, nativeWidth: 300 })
+                                    .then(doc => doc && this.props.addDocument(doc, false));
                             }
+                        });
+                    promises.push(prom);
+                }
+                let type = item.type;
+                if (item.kind === "file") {
+                    let file = item.getAsFile();
+                    let formData = new FormData();
+
+                    if (file) {
+                        formData.append('file', file);
+                    }
+                    let dropFileName = file ? file.name : "-empty-";
+
+                    let prom = fetch(upload, {
+                        method: 'POST',
+                        body: formData
+                    }).then(async (res: Response) => {
+                        (await res.json()).map(action((file: any) => {
+                            let path = window.location.origin + file;
+                            let docPromise = this.getDocumentFromType(type, path, { ...options, nativeWidth: 600, width: 300, title: dropFileName });
+
+                            docPromise.then(doc => doc && this.props.addDocument(doc));
                         }));
-                    }));
-                });
-                promises.push(prom);
+                    });
+                    promises.push(prom);
+                }
             }
-        }
 
-        if (promises.length) {
-            Promise.all(promises).finally(() => batch.end());
-        } else {
-            batch.end();
+            if (promises.length) {
+                Promise.all(promises).finally(() => batch.end());
+            } else {
+                batch.end();
+            }
         }
     }
+    return CollectionSubView;
 }
+
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index 8ecc5b67b..411d67ff7 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -23,37 +23,37 @@
         margin: 5px 0;
     }
 
-    .collection-child {
-        margin-top: 10px;
-        margin-bottom: 10px;
-    }
 
     .no-indent {
         padding-left: 0;
     }
 
     .bullet {
-        width: 1.5em;
-        display: inline-block;
+        float:left;
+        position: relative;
+        width: 15px;
+        display: block;
         color: $intermediate-color;
-    }
-
-    .coll-title {
-        font-size: 24px;
-        margin-bottom: 20px;
+        margin-top: 3px;
+        transform: scale(1.3,1.3);
     }
 
     .docContainer {
-        display: inline-table;
+        margin-left: 10px;
+        display: block;
+       // width:100%;//width: max-content;
     }
-
     .docContainer:hover {
-        .delete-button {
-            display: inline;
-            // width: auto;
+        .treeViewItem-openRight {
+            display:inline;
         }
     }
 
+
+    .editableView-container {
+        font-weight: bold;
+    }
+
     .delete-button {
         color: $intermediate-color;
         // float: right;
@@ -61,4 +61,28 @@
         // margin-top: 3px;
         display: inline;
     }
+    .treeViewItem-openRight {
+        margin-left: 5px;
+        display:none;
+    }
+    .docContainer:hover {
+        .delete-button {
+            display: inline;
+            // width: auto;
+        }
+    }
+
+    .coll-title {
+        width:max-content;
+        display: block;
+        font-size: 24px;
+    }
+    .collection-child {
+        margin-top: 10px;
+        margin-bottom: 10px;
+    }
+    .collectionTreeView-keyHeader {
+        font-style: italic;
+        font-size: 8pt;
+    }
 }
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 51a02fc25..33787f06b 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -1,27 +1,33 @@
 import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
-import { faCaretDown, faCaretRight, faTrashAlt } from '@fortawesome/free-solid-svg-icons';
+import { faCaretDown, faCaretRight, faTrashAlt, faAngleRight } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, observable, trace } from "mobx";
 import { observer } from "mobx-react";
-import { Document } from "../../../fields/Document";
-import { FieldWaiting } from "../../../fields/Field";
-import { KeyStore } from "../../../fields/KeyStore";
-import { ListField } from "../../../fields/ListField";
-import { SetupDrag, DragManager } from "../../util/DragManager";
+import { DragManager, SetupDrag, dropActionType } from "../../util/DragManager";
 import { EditableView } from "../EditableView";
-import "./CollectionTreeView.scss";
-import { CollectionView } from "./CollectionView";
-import * as globalCssVariables from "../../views/globalCssVariables.scss";
 import { CollectionSubView } from "./CollectionSubView";
+import "./CollectionTreeView.scss";
 import React = require("react");
-import { props } from 'bluebird';
+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 { ContextMenu } from '../ContextMenu';
+import { undoBatch } from '../../util/UndoManager';
+import { Main } from '../Main';
+import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
+import { CollectionDockingView } from './CollectionDockingView';
+import { DocumentManager } from '../../util/DocumentManager';
+import { Utils } from '../../../Utils';
+import { List } from '../../../new_fields/List';
+import { indexOf } from 'typescript-collections/dist/lib/arrays';
 
 
 export interface TreeViewProps {
-    document: Document;
-    deleteDoc: (doc: Document) => void;
+    document: Doc;
+    deleteDoc: (doc: Doc) => void;
     moveDocument: DragManager.MoveFunction;
-    copyOnDrag: boolean;
+    dropAction: "alias" | "copy" | undefined;
 }
 
 export enum BulletType {
@@ -31,6 +37,7 @@ export enum BulletType {
 }
 
 library.add(faTrashAlt);
+library.add(faAngleRight);
 library.add(faCaretDown);
 library.add(faCaretRight);
 
@@ -42,13 +49,29 @@ class TreeView extends React.Component<TreeViewProps> {
 
     @observable _collapsed: boolean = true;
 
-    delete = () => this.props.deleteDoc(this.props.document);
+    @undoBatch delete = () => this.props.deleteDoc(this.props.document);
+
+    @undoBatch openRight = async () => {
+        if (this.props.document.dockingConfig) {
+            Main.Instance.openWorkspace(this.props.document);
+        } else {
+            CollectionDockingView.Instance.AddRightSplit(this.props.document);
+        }
+    };
+
+    get children() {
+        return Cast(this.props.document.data, listSpec(Doc), []); // bcz: needed?    .filter(doc => FieldValue(doc));
+    }
+
+    onPointerDown = (e: React.PointerEvent) => {
+        e.stopPropagation();
+    }
 
     @action
-    remove = (document: Document) => {
-        var children = this.props.document.GetT<ListField<Document>>(KeyStore.Data, ListField);
-        if (children && children !== FieldWaiting) {
-            children.Data.splice(children.Data.indexOf(document), 1);
+    remove = (document: Document, key: string) => {
+        let children = Cast(this.props.document[key], listSpec(Doc), []);
+        if (children) {
+            children.splice(children.indexOf(document), 1);
         }
     }
 
@@ -58,7 +81,7 @@ class TreeView extends React.Component<TreeViewProps> {
             return true;
         }
         //TODO This should check if it was removed
-        this.remove(document);
+        this.remove(document, "data");
         return addDoc(document);
     }
 
@@ -77,83 +100,136 @@ class TreeView extends React.Component<TreeViewProps> {
      */
     renderTitle() {
         let reference = React.createRef<HTMLDivElement>();
-        let onItemDown = SetupDrag(reference, () => this.props.document, this.props.moveDocument, this.props.copyOnDrag);
+        let onItemDown = SetupDrag(reference, () => this.props.document, this.props.moveDocument, this.props.dropAction);
         let editableView = (titleString: string) =>
             (<EditableView
                 display={"inline"}
                 contents={titleString}
                 height={36}
-                GetValue={() => this.props.document.Title}
+                GetValue={() => StrCast(this.props.document.title)}
                 SetValue={(value: string) => {
-                    this.props.document.SetText(KeyStore.Title, value);
+                    let target = this.props.document.proto ? this.props.document.proto : this.props.document;
+                    target.title = value;
                     return true;
                 }}
             />);
+        let dataDocs = 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" />
+            </div>);
         return (
-            <div className="docContainer" ref={reference} onPointerDown={onItemDown}>
-                {editableView(this.props.document.Title)}
-                <div className="delete-button" onClick={this.delete}><FontAwesomeIcon icon="trash-alt" size="xs" /></div>
+            <div className="docContainer" ref={reference} onPointerDown={onItemDown}
+                style={{ background: BoolCast(this.props.document.libraryBrush, false) ? "#06121212" : "0" }}
+                onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}>
+                {editableView(StrCast(this.props.document.title))}
+                {openRight}
+                {/* {<div className="delete-button" onClick={this.delete}><FontAwesomeIcon icon="trash-alt" size="xs" /></div>} */}
             </div >);
     }
 
+    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
+            ContextMenu.Instance.addItem({ description: "Open as Workspace", event: undoBatch(() => Main.Instance.openWorkspace(this.props.document)) });
+            ContextMenu.Instance.addItem({ description: "Open Right", event: () => CollectionDockingView.Instance.AddRightSplit(this.props.document) });
+            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", event: undoBatch(() => {
+                    this.props.deleteDoc(this.props.document);
+                })
+            });
+            ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
+            e.stopPropagation();
+        }
+    }
+
+    onPointerEnter = (e: React.PointerEvent): void => { this.props.document.libraryBrush = true; };
+    onPointerLeave = (e: React.PointerEvent): void => { this.props.document.libraryBrush = false; };
+
     render() {
         let bulletType = BulletType.List;
-        let childElements: JSX.Element | undefined = undefined;
-        var children = this.props.document.GetT<ListField<Document>>(KeyStore.Data, ListField);
-        if (children && children !== FieldWaiting) { // add children for a collection
-            if (!this._collapsed) {
-                bulletType = BulletType.Collapsible;
-                childElements = <ul>
-                    {children.Data.map(value => <TreeView key={value.Id} document={value} deleteDoc={this.remove} moveDocument={this.move} copyOnDrag={this.props.copyOnDrag} />)}
-                </ul >;
-            }
-            else bulletType = BulletType.Collapsed;
+        let contentElement: (JSX.Element | null)[] = [];
+        let keys = Array.from(Object.keys(this.props.document));
+        if (this.props.document.proto instanceof Doc) {
+            keys.push(...Array.from(Object.keys(this.props.document.proto)));
         }
-        return <div className="treeViewItem-container" >
+        keys.map(key => {
+            let docList = Cast(this.props.document[key], listSpec(Doc));
+            if (docList instanceof List && docList.length && docList[0] instanceof Doc) {
+                if (!this._collapsed) {
+                    bulletType = BulletType.Collapsible;
+                    contentElement.push(<ul key={key + "more"}>
+                        {(key === "data") ? (null) :
+                            <span className="collectionTreeView-keyHeader" key={key}>{key}</span>}
+                        {TreeView.GetChildElements(docList, key !== "data", (doc: Doc) => this.remove(doc, key), this.move, this.props.dropAction)}
+                    </ul >);
+                } else
+                    bulletType = BulletType.Collapsed;
+            }
+        });
+        return <div className="treeViewItem-container"
+            onContextMenu={this.onWorkspaceContextMenu}>
             <li className="collection-child">
                 {this.renderBullet(bulletType)}
                 {this.renderTitle()}
-                {childElements ? childElements : (null)}
+                {contentElement}
             </li>
         </div>;
     }
+    public static GetChildElements(docs: (Doc | Promise<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} />);
+    }
 }
 
 @observer
-export class CollectionTreeView extends CollectionSubView {
-
+export class CollectionTreeView extends CollectionSubView(Document) {
     @action
     remove = (document: Document) => {
-        var children = this.props.Document.GetT<ListField<Document>>(KeyStore.Data, ListField);
-        if (children && children !== FieldWaiting) {
-            children.Data.splice(children.Data.indexOf(document), 1);
+        let children = Cast(this.props.Document.data, listSpec(Doc), []);
+        if (children) {
+            children.splice(children.indexOf(document), 1);
+        }
+    }
+    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
+            ContextMenu.Instance.addItem({ description: "Create Workspace", event: undoBatch(() => Main.Instance.createNewWorkspace()) });
+        }
+        if (!ContextMenu.Instance.getItems().some(item => item.description === "Delete")) {
+            ContextMenu.Instance.addItem({ description: "Delete", event: undoBatch(() => this.remove(this.props.Document)) });
         }
     }
-
     render() {
-        let children = this.props.Document.GetT<ListField<Document>>(KeyStore.Data, ListField);
-        let copyOnDrag = this.props.Document.GetBoolean(KeyStore.CopyDraggedItems, false);
-        let childrenElement = !children || children === FieldWaiting ? (null) :
-            (children.Data.map(value =>
-                <TreeView document={value} key={value.Id} deleteDoc={this.remove} moveDocument={this.props.moveDocument} copyOnDrag={copyOnDrag} />)
-            );
+        const children = this.children;
+        let dropAction = StrCast(this.props.Document.dropAction, "alias") as dropActionType;
+        if (!children) {
+            return (null);
+        }
+        let childElements = TreeView.GetChildElements(children, false, this.remove, this.props.moveDocument, dropAction);
 
         return (
-            <div id="body" className="collectionTreeView-dropTarget" onWheel={(e: React.WheelEvent) => e.stopPropagation()} onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createDropTarget}>
+            <div id="body" className="collectionTreeView-dropTarget"
+                style={{ borderRadius: "inherit" }}
+                onContextMenu={this.onContextMenu}
+                onWheel={(e: React.WheelEvent) => e.stopPropagation()}
+                onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createDropTarget}>
                 <div className="coll-title">
                     <EditableView
-                        contents={this.props.Document.Title}
+                        contents={this.props.Document.title}
                         display={"inline"}
                         height={72}
-                        GetValue={() => this.props.Document.Title}
+                        GetValue={() => StrCast(this.props.Document.title)}
                         SetValue={(value: string) => {
-                            this.props.Document.SetText(KeyStore.Title, value);
+                            let target = this.props.Document.proto ? this.props.Document.proto : this.props.Document;
+                            target.title = value;
                             return true;
                         }} />
                 </div>
-                <hr />
                 <ul className="no-indent">
-                    {childrenElement}
+                    {childElements}
                 </ul>
             </div >
         );
diff --git a/src/client/views/collections/CollectionVideoView.tsx b/src/client/views/collections/CollectionVideoView.tsx
index 29fb342c6..cb3fd1ba4 100644
--- a/src/client/views/collections/CollectionVideoView.tsx
+++ b/src/client/views/collections/CollectionVideoView.tsx
@@ -1,6 +1,5 @@
 import { action, observable, trace } from "mobx";
 import { observer } from "mobx-react";
-import { KeyStore } from "../../../fields/KeyStore";
 import { ContextMenu } from "../ContextMenu";
 import { CollectionViewType, CollectionBaseView, CollectionRenderProps } from "./CollectionBaseView";
 import React = require("react");
@@ -8,17 +7,18 @@ 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 { VideoBox } from "../nodes/VideoBox";
 
 
 @observer
 export class CollectionVideoView extends React.Component<FieldViewProps> {
-    private _intervalTimer: any = undefined;
-    private _player: HTMLVideoElement | undefined = undefined;
+    private _videoBox: VideoBox | undefined = undefined;
+    @observable _playTimer?: NodeJS.Timeout = undefined;
 
     @observable _currentTimecode: number = 0;
-    @observable _isPlaying: boolean = false;
 
-    public static LayoutString(fieldKey: string = "DataKey") {
+    public static LayoutString(fieldKey: string = "data") {
         return FieldView.LayoutString(CollectionVideoView, fieldKey);
     }
     private get uIButtons() {
@@ -29,7 +29,7 @@ export class CollectionVideoView extends React.Component<FieldViewProps> {
                 <span style={{ fontSize: 8 }}>{" " + Math.round((this._currentTimecode - Math.trunc(this._currentTimecode)) * 100)}</span>
             </div>,
             <div className="collectionVideoView-play" key="play" onPointerDown={this.onPlayDown} style={{ transform: `scale(${scaling}, ${scaling})` }}>
-                {this._isPlaying ? "\"" : ">"}
+                {this._playTimer ? "\"" : ">"}
             </div>,
             <div className="collectionVideoView-full" key="full" onPointerDown={this.onFullDown} style={{ transform: `scale(${scaling}, ${scaling})` }}>
                 F
@@ -38,53 +38,36 @@ export class CollectionVideoView extends React.Component<FieldViewProps> {
     }
 
     @action
-    mainCont = (ele: HTMLDivElement | null) => {
-        if (ele) {
-            this._player = ele.getElementsByTagName("video")[0];
-            if (this.props.Document.GetNumber(KeyStore.CurPage, -1) >= 0) {
-                this._currentTimecode = this.props.Document.GetNumber(KeyStore.CurPage, -1);
-            }
+    updateTimecode = () => {
+        if (this._videoBox && this._videoBox.player) {
+            this._currentTimecode = this._videoBox.player.currentTime;
+            this.props.Document.curPage = Math.round(this._currentTimecode);
         }
     }
 
-    componentDidMount() {
-        this._intervalTimer = setInterval(this.updateTimecode, 1000);
-    }
+    componentDidMount() { this.updateTimecode(); }
 
-    componentWillUnmount() {
-        clearInterval(this._intervalTimer);
-    }
-
-    @action
-    updateTimecode = () => {
-        if (this._player) {
-            if ((this._player as any).AHackBecauseSomethingResetsTheVideoToZero !== -1) {
-                this._player.currentTime = (this._player as any).AHackBecauseSomethingResetsTheVideoToZero;
-                (this._player as any).AHackBecauseSomethingResetsTheVideoToZero = -1;
-            } else {
-                this._currentTimecode = this._player.currentTime;
-                this.props.Document.SetNumber(KeyStore.CurPage, Math.round(this._currentTimecode));
-            }
-        }
-    }
+    componentWillUnmount() { if (this._playTimer) clearInterval(this._playTimer); }
 
     @action
     onPlayDown = () => {
-        if (this._player) {
-            if (this._player.paused) {
-                this._player.play();
-                this._isPlaying = true;
+        if (this._videoBox && this._videoBox.player) {
+            if (this._videoBox.player.paused) {
+                this._videoBox.player.play();
+                if (!this._playTimer) this._playTimer = setInterval(this.updateTimecode, 1000);
             } else {
-                this._player.pause();
-                this._isPlaying = false;
+                this._videoBox.player.pause();
+                if (this._playTimer) clearInterval(this._playTimer);
+                this._playTimer = undefined;
+
             }
         }
     }
 
     @action
     onFullDown = (e: React.PointerEvent) => {
-        if (this._player) {
-            this._player.requestFullscreen();
+        if (this._videoBox && this._videoBox.player) {
+            this._videoBox.player.requestFullscreen();
             e.stopPropagation();
             e.preventDefault();
         }
@@ -92,33 +75,34 @@ export class CollectionVideoView extends React.Component<FieldViewProps> {
 
     @action
     onResetDown = () => {
-        if (this._player) {
-            this._player.pause();
-            this._player.currentTime = 0;
+        if (this._videoBox && this._videoBox.player) {
+            this._videoBox.player.pause();
+            this._videoBox.player.currentTime = 0;
+            if (this._playTimer) clearInterval(this._playTimer);
+            this._playTimer = undefined;
+            this.updateTimecode();
         }
-
     }
 
     onContextMenu = (e: React.MouseEvent): void => {
-        if (!e.isPropagationStopped() && this.props.Document.Id !== "mainDoc") { // 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() && this.props.Document[Id] !== "mainDoc") { // 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: "VideoOptions", event: emptyFunction });
         }
     }
 
+    setVideoBox = (player: VideoBox) => { this._videoBox = player; };
+
     private subView = (_type: CollectionViewType, renderProps: CollectionRenderProps) => {
         let props = { ...this.props, ...renderProps };
-        return (
-            <>
-                <CollectionFreeFormView {...props} CollectionView={this} />
-                {this.props.isSelected() ? this.uIButtons : (null)}
-            </>
-        );
+        return (<>
+            <CollectionFreeFormView {...props} setVideoBox={this.setVideoBox} CollectionView={this} />
+            {this.props.isSelected() ? this.uIButtons : (null)}
+        </>);
     }
 
     render() {
-        trace();
         return (
-            <CollectionBaseView {...this.props} className="collectionVideoView-cont" contentRef={this.mainCont} onContextMenu={this.onContextMenu}>
+            <CollectionBaseView {...this.props} className="collectionVideoView-cont" onContextMenu={this.onContextMenu}>
                 {this.subView}
             </CollectionBaseView>);
     }
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 675e720e2..8c1442d38 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -7,14 +7,15 @@ import { CollectionDockingView } from './CollectionDockingView';
 import { CollectionTreeView } from './CollectionTreeView';
 import { ContextMenu } from '../ContextMenu';
 import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
-import { KeyStore } from '../../../fields/KeyStore';
 import { observer } from 'mobx-react';
 import { undoBatch } from '../../util/UndoManager';
 import { trace } from 'mobx';
+import { Id } from '../../../new_fields/RefField';
+import { Main } from '../Main';
 
 @observer
 export class CollectionView extends React.Component<FieldViewProps> {
-    public static LayoutString(fieldStr: string = "DataKey") { return FieldView.LayoutString(CollectionView, fieldStr); }
+    public static LayoutString(fieldStr: string = "data") { return FieldView.LayoutString(CollectionView, fieldStr); }
 
     private SubView = (type: CollectionViewType, renderProps: CollectionRenderProps) => {
         let props = { ...this.props, ...renderProps };
@@ -29,13 +30,13 @@ export class CollectionView extends React.Component<FieldViewProps> {
         return (null);
     }
 
-    get isAnnotationOverlay() { return this.props.fieldKey && this.props.fieldKey.Id === KeyStore.Annotations.Id; } // bcz: ? Why do we need to compare Id's?
+    get isAnnotationOverlay() { return this.props.fieldKey && this.props.fieldKey === "annotations"; } // bcz: ? Why do we need to compare Id's?
 
     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.SetNumber(KeyStore.ViewType, CollectionViewType.Freeform)) });
-            ContextMenu.Instance.addItem({ description: "Schema", event: undoBatch(() => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Schema)) });
-            ContextMenu.Instance.addItem({ description: "Treeview", event: undoBatch(() => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Tree)) });
+        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) });
+            ContextMenu.Instance.addItem({ description: "Schema", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Schema) });
+            ContextMenu.Instance.addItem({ description: "Treeview", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Tree) });
         }
     }
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss
index 3b2f79be1..737ffba7d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss
@@ -1,6 +1,12 @@
 .collectionfreeformlinkview-linkLine {
     stroke: black;
-    stroke-width: 3;
     transform: translate(10000px,10000px);
+    opacity: 0.5;
     pointer-events: all;
-}
\ No newline at end of file
+}
+.collectionfreeformlinkview-linkCircle {
+    stroke: rgb(0,0,0);
+    opacity: 0.5;
+    transform: translate(10000px,10000px);
+    pointer-events: all;
+}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index 8868f7df0..63d2f7642 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -1,37 +1,58 @@
 import { observer } from "mobx-react";
-import { Document } from "../../../../fields/Document";
-import { KeyStore } from "../../../../fields/KeyStore";
 import { Utils } from "../../../../Utils";
 import "./CollectionFreeFormLinkView.scss";
 import React = require("react");
 import v5 = require("uuid/v5");
+import { StrCast, NumCast, BoolCast } from "../../../../new_fields/Types";
+import { Doc, WidthSym, HeightSym } from "../../../../new_fields/Doc";
+import { InkingControl } from "../../InkingControl";
 
 export interface CollectionFreeFormLinkViewProps {
-    A: Document;
-    B: Document;
-    LinkDocs: Document[];
+    A: Doc;
+    B: Doc;
+    LinkDocs: Doc[];
+    addDocument: (document: Doc, allowDuplicates?: boolean) => boolean;
+    removeDocument: (document: Doc) => boolean;
 }
 
 @observer
 export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFormLinkViewProps> {
 
     onPointerDown = (e: React.PointerEvent) => {
-        this.props.LinkDocs.map(l =>
-            console.log("Link:" + l.Title));
+        if (e.button === 0 && !InkingControl.Instance.selectedTool) {
+            let a = this.props.A;
+            let b = this.props.B;
+            let x1 = NumCast(a.x) + (BoolCast(a.isMinimized, false) ? 5 : a[WidthSym]() / 2);
+            let y1 = NumCast(a.y) + (BoolCast(a.isMinimized, false) ? 5 : a[HeightSym]() / 2);
+            let x2 = NumCast(b.x) + (BoolCast(b.isMinimized, false) ? 5 : b[WidthSym]() / 2);
+            let y2 = NumCast(b.y) + (BoolCast(b.isMinimized, false) ? 5 : b[HeightSym]() / 2);
+            this.props.LinkDocs.map(l => {
+                let width = l[WidthSym]();
+                l.x = (x1 + x2) / 2 - width / 2;
+                l.y = (y1 + y2) / 2 + 10;
+                if (!this.props.removeDocument(l)) this.props.addDocument(l, false);
+            });
+            e.stopPropagation();
+            e.preventDefault();
+        }
     }
     render() {
         let l = this.props.LinkDocs;
         let a = this.props.A;
         let b = this.props.B;
-        let x1 = a.GetNumber(KeyStore.X, 0) + (a.GetBoolean(KeyStore.Minimized, false) ? 5 : a.Width() / 2);
-        let y1 = a.GetNumber(KeyStore.Y, 0) + (a.GetBoolean(KeyStore.Minimized, false) ? 5 : a.Height() / 2);
-        let x2 = b.GetNumber(KeyStore.X, 0) + (b.GetBoolean(KeyStore.Minimized, false) ? 5 : b.Width() / 2);
-        let y2 = b.GetNumber(KeyStore.Y, 0) + (b.GetBoolean(KeyStore.Minimized, false) ? 5 : b.Height() / 2);
+        let x1 = NumCast(a.x) + (BoolCast(a.isMinimized, false) ? 5 : NumCast(a.width) / 2);
+        let y1 = NumCast(a.y) + (BoolCast(a.isMinimized, false) ? 5 : NumCast(a.height) / 2);
+        let x2 = NumCast(b.x) + (BoolCast(b.isMinimized, false) ? 5 : NumCast(b.width) / 2);
+        let y2 = NumCast(b.y) + (BoolCast(b.isMinimized, false) ? 5 : NumCast(b.height) / 2);
         return (
-            <line key={Utils.GenerateGuid()} className="collectionfreeformlinkview-linkLine" onPointerDown={this.onPointerDown}
-                style={{ strokeWidth: `${l.length * 5}` }}
-                x1={`${x1}`} y1={`${y1}`}
-                x2={`${x2}`} y2={`${y2}`} />
+            <>
+                <line key={Utils.GenerateGuid()} className="collectionfreeformlinkview-linkLine"
+                    style={{ strokeWidth: `${l.length / 2}` }}
+                    x1={`${x1}`} y1={`${y1}`}
+                    x2={`${x2}`} y2={`${y2}`} />
+                <circle key={Utils.GenerateGuid()} className="collectionfreeformlinkview-linkCircle"
+                    cx={(x1 + x2) / 2} cy={(y1 + y2) / 2} r={5} onPointerDown={this.onPointerDown} />
+            </>
         );
     }
 }
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index cd74d3a84..1d4584cfe 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -1,8 +1,5 @@
-import { computed, IReactionDisposer, reaction } from "mobx";
+import { computed, IReactionDisposer, reaction, trace } from "mobx";
 import { observer } from "mobx-react";
-import { Document } from "../../../../fields/Document";
-import { KeyStore } from "../../../../fields/KeyStore";
-import { ListField } from "../../../../fields/ListField";
 import { Utils } from "../../../../Utils";
 import { DocumentManager } from "../../../util/DocumentManager";
 import { DocumentView } from "../../nodes/DocumentView";
@@ -10,57 +7,68 @@ import { CollectionViewProps } from "../CollectionSubView";
 import "./CollectionFreeFormLinksView.scss";
 import { CollectionFreeFormLinkView } from "./CollectionFreeFormLinkView";
 import React = require("react");
+import { Doc, 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";
 
 @observer
 export class CollectionFreeFormLinksView extends React.Component<CollectionViewProps> {
 
     _brushReactionDisposer?: IReactionDisposer;
     componentDidMount() {
-        this._brushReactionDisposer = reaction(() => this.props.Document.GetList(this.props.fieldKey, [] as Document[]).map(doc => doc.GetNumber(KeyStore.X, 0)),
+        this._brushReactionDisposer = reaction(
             () => {
-                let views = this.props.Document.GetList(this.props.fieldKey, [] as Document[]);
-                for (let i = 0; i < views.length; i++) {
-                    for (let j = 0; j < views.length; j++) {
-                        let srcDoc = views[j];
-                        let dstDoc = views[i];
-                        let x1 = srcDoc.GetNumber(KeyStore.X, 0);
-                        let x1w = srcDoc.GetNumber(KeyStore.Width, -1);
-                        let x2 = dstDoc.GetNumber(KeyStore.X, 0);
-                        let x2w = dstDoc.GetNumber(KeyStore.Width, -1);
-                        if (x1w < 0 || x2w < 0 || i === j) {
-                            continue;
-                        }
+                let doclist = Cast(this.props.Document[this.props.fieldKey], listSpec(Doc), []);
+                return { doclist: doclist ? doclist : [], xs: doclist instanceof List ? doclist.map(d => d instanceof Doc && d.x) : [] };
+            },
+            async () => {
+                let doclist = await DocListCast(this.props.Document[this.props.fieldKey]);
+                let views = doclist ? doclist.filter(doc => StrCast(doc.backgroundLayout).indexOf("istogram") !== -1) : [];
+                views.forEach((dstDoc, i) => {
+                    views.forEach((srcDoc, j) => {
                         let dstTarg = dstDoc;
                         let srcTarg = srcDoc;
-                        let findBrush = (field: ListField<Document>) => field.Data.findIndex(brush => {
-                            let bdocs = brush ? brush.GetList(KeyStore.BrushingDocs, [] as Document[]) : [];
-                            return (bdocs.length && ((bdocs[0] === dstTarg && bdocs[1] === srcTarg)) ? true : false);
-                        });
-                        let brushAction = (field: ListField<Document>) => {
-                            let found = findBrush(field);
-                            if (found !== -1) {
-                                console.log("REMOVE BRUSH " + srcTarg.Title + " " + dstTarg.Title);
-                                field.Data.splice(found, 1);
-                            }
-                        };
-                        if (Math.abs(x1 + x1w - x2) < 20) {
-                            let linkDoc: Document = new Document();
-                            linkDoc.SetText(KeyStore.Title, "Histogram Brush");
-                            linkDoc.SetText(KeyStore.LinkDescription, "Brush between " + srcTarg.Title + " and " + dstTarg.Title);
-                            linkDoc.SetData(KeyStore.BrushingDocs, [dstTarg, srcTarg], ListField);
-
-                            brushAction = (field: ListField<Document>) => {
-                                if (findBrush(field) === -1) {
-                                    console.log("ADD BRUSH " + srcTarg.Title + " " + dstTarg.Title);
-                                    (findBrush(field) === -1) && field.Data.push(linkDoc);
+                        let x1 = NumCast(srcDoc.x);
+                        let x2 = NumCast(dstDoc.x);
+                        let x1w = NumCast(srcDoc.width, -1);
+                        let x2w = NumCast(dstDoc.width, -1);
+                        if (x1w < 0 || x2w < 0 || i === j) { }
+                        else {
+                            let findBrush = (field: (Doc | Promise<Doc>)[]) => field.findIndex(brush => {
+                                let bdocs = brush instanceof Doc ? Cast(brush.brushingDocs, listSpec(Doc), []) : undefined;
+                                return bdocs && bdocs.length && ((bdocs[0] === dstTarg && bdocs[1] === srcTarg)) ? true : false;
+                            });
+                            let brushAction = (field: (Doc | Promise<Doc>)[]) => {
+                                let found = findBrush(field);
+                                if (found !== -1) {
+                                    console.log("REMOVE BRUSH " + srcTarg.title + " " + dstTarg.title);
+                                    field.splice(found, 1);
                                 }
                             };
-                        }
-                        dstTarg.GetOrCreateAsync(KeyStore.BrushingDocs, ListField, brushAction);
-                        srcTarg.GetOrCreateAsync(KeyStore.BrushingDocs, ListField, brushAction);
+                            if (Math.abs(x1 + x1w - x2) < 20) {
+                                let linkDoc: Doc = new Doc();
+                                linkDoc.title = "Histogram Brush";
+                                linkDoc.linkDescription = "Brush between " + StrCast(srcTarg.title) + " and " + StrCast(dstTarg.Title);
+                                linkDoc.brushingDocs = new List([dstTarg, srcTarg]);
 
-                    }
-                }
+                                brushAction = (field: (Doc | Promise<Doc>)[]) => {
+                                    if (findBrush(field) === -1) {
+                                        console.log("ADD BRUSH " + srcTarg.title + " " + dstTarg.title);
+                                        field.push(linkDoc);
+                                    }
+                                };
+                            }
+                            let dstBrushDocs = Cast(dstTarg.brushingDocs, listSpec(Doc), []);
+                            let srcBrushDocs = Cast(srcTarg.brushingDocs, listSpec(Doc), []);
+                            if (dstBrushDocs === undefined) dstTarg.brushingDocs = dstBrushDocs = new List<Doc>();
+                            else brushAction(dstBrushDocs);
+                            if (srcBrushDocs === undefined) srcTarg.brushingDocs = srcBrushDocs = new List<Doc>();
+                            else brushAction(srcBrushDocs);
+                        }
+                    })
+                })
             });
     }
     componentWillUnmount() {
@@ -70,9 +78,17 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP
     }
     documentAnchors(view: DocumentView) {
         let equalViews = [view];
-        let containerDoc = view.props.Document.GetT(KeyStore.AnnotationOn, Document);
-        if (containerDoc && containerDoc instanceof Document) {
-            equalViews = DocumentManager.Instance.getDocumentViews(containerDoc.GetPrototype()!);
+        let containerDoc = FieldValue(Cast(view.props.Document.annotationOn, Doc));
+        if (containerDoc) {
+            equalViews = DocumentManager.Instance.getDocumentViews(containerDoc.proto!);
+        }
+        if (view.props.ContainingCollectionView) {
+            let collid = view.props.ContainingCollectionView.props.Document[Id];
+            Cast(this.props.Document[this.props.fieldKey], listSpec(Doc), []).filter(d => d).map(d => d as Doc).
+                filter(child =>
+                    child[Id] === collid).map(view =>
+                        DocumentManager.Instance.getDocumentViews(view).map(view =>
+                            equalViews.push(view)));
         }
         return equalViews.filter(sv => sv.props.ContainingCollectionView && sv.props.ContainingCollectionView.props.Document === this.props.Document);
     }
@@ -82,12 +98,12 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP
         let connections = DocumentManager.Instance.LinkedDocumentViews.reduce((drawnPairs, connection) => {
             let srcViews = this.documentAnchors(connection.a);
             let targetViews = this.documentAnchors(connection.b);
-            let possiblePairs: { a: Document, b: Document, }[] = [];
+            let possiblePairs: { a: Doc, b: Doc, }[] = [];
             srcViews.map(sv => targetViews.map(tv => possiblePairs.push({ a: sv.props.Document, b: tv.props.Document })));
             possiblePairs.map(possiblePair =>
                 drawnPairs.reduce((found, drawnPair) => {
                     let match = (possiblePair.a === drawnPair.a && possiblePair.b === drawnPair.b);
-                    if (match && !drawnPair.l.reduce((found, link) => found || link.Id === connection.l.Id, false)) {
+                    if (match && !drawnPair.l.reduce((found, link) => found || link[Id] === connection.l[Id], false)) {
                         drawnPair.l.push(connection.l);
                     }
                     return match || found;
@@ -96,8 +112,9 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP
                 drawnPairs.push({ a: possiblePair.a, b: possiblePair.b, l: [connection.l] })
             );
             return drawnPairs;
-        }, [] as { a: Document, b: Document, l: Document[] }[]);
-        return connections.map(c => <CollectionFreeFormLinkView key={Utils.GenerateGuid()} A={c.a} B={c.b} LinkDocs={c.l} />);
+        }, [] as { a: Doc, b: Doc, l: Doc[] }[]);
+        return connections.map(c => <CollectionFreeFormLinkView key={Utils.GenerateGuid()} A={c.a} B={c.b} LinkDocs={c.l}
+            removeDocument={this.props.removeDocument} addDocument={this.props.addDocument} />);
     }
 
     render() {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
index cf0a6de00..40ec8a325 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
@@ -1,27 +1,33 @@
-import { computed } from "mobx";
 import { observer } from "mobx-react";
-import { KeyStore } from "../../../../fields/KeyStore";
-import { CollectionViewProps, CursorEntry } from "../CollectionSubView";
+import { CollectionViewProps } from "../CollectionSubView";
 import "./CollectionFreeFormView.scss";
 import React = require("react");
 import v5 = require("uuid/v5");
 import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils";
+import CursorField from "../../../../new_fields/CursorField";
+import { Cast } from "../../../../new_fields/Types";
+import { listSpec } from "../../../../new_fields/Schema";
 
 @observer
 export class CollectionFreeFormRemoteCursors extends React.Component<CollectionViewProps> {
-    protected getCursors(): CursorEntry[] {
+
+    protected getCursors(): CursorField[] {
         let doc = this.props.Document;
+
         let id = CurrentUserUtils.id;
-        let cursors = doc.GetList(KeyStore.Cursors, [] as CursorEntry[]);
-        let notMe = cursors.filter(entry => entry.Data[0][0] !== id);
-        return id ? notMe : [];
+        if (!id) {
+            return [];
+        }
+
+        let cursors = Cast(doc.cursors, listSpec(CursorField));
+
+        return (cursors || []).filter(cursor => cursor.data.metadata.id !== id);
     }
 
     private crosshairs?: HTMLCanvasElement;
     drawCrosshairs = (backgroundColor: string) => {
         if (this.crosshairs) {
-            let c = this.crosshairs;
-            let ctx = c.getContext('2d');
+            let ctx = this.crosshairs.getContext('2d');
             if (ctx) {
                 ctx.fillStyle = backgroundColor;
                 ctx.fillRect(0, 0, 20, 20);
@@ -50,29 +56,26 @@ export class CollectionFreeFormRemoteCursors extends React.Component<CollectionV
             }
         }
     }
-    @computed
+
     get sharedCursors() {
-        return this.getCursors().map(entry => {
-            if (entry.Data.length > 0) {
-                let id = entry.Data[0][0];
-                let email = entry.Data[0][1];
-                let point = entry.Data[1];
-                this.drawCrosshairs("#" + v5(id, v5.URL).substring(0, 6).toUpperCase() + "22");
-                return (
-                    <div key={id} className="collectionFreeFormRemoteCursors-cont"
-                        style={{ transform: `translate(${point[0] - 10}px, ${point[1] - 10}px)` }}
-                    >
-                        <canvas className="collectionFreeFormRemoteCursors-canvas"
-                            ref={(el) => { if (el) this.crosshairs = el; }}
-                            width={20}
-                            height={20}
-                        />
-                        <p className="collectionFreeFormRemoteCursors-symbol">
-                            {email[0].toUpperCase()}
-                        </p>
-                    </div>
-                );
-            }
+        return this.getCursors().map(c => {
+            let m = c.data.metadata;
+            let l = c.data.position;
+            this.drawCrosshairs("#" + v5(m.id, v5.URL).substring(0, 6).toUpperCase() + "22");
+            return (
+                <div key={m.id} className="collectionFreeFormRemoteCursors-cont"
+                    style={{ transform: `translate(${l.x - 10}px, ${l.y - 10}px)` }}
+                >
+                    <canvas className="collectionFreeFormRemoteCursors-canvas"
+                        ref={(el) => { if (el) this.crosshairs = el; }}
+                        width={20}
+                        height={20}
+                    />
+                    <p className="collectionFreeFormRemoteCursors-symbol">
+                        {m.identifier[0].toUpperCase()}
+                    </p>
+                </div>
+            );
         });
     }
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index 57706b51e..063c9e2cf 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -1,90 +1,107 @@
 @import "../../globalCssVariables";
-.collectionfreeformview {
-  position: inherit;
-  top: 0;
-  left: 0;
-  width: 100%;
-  height: 100%;
-  transform-origin: left top;
+
+.collectionfreeformview-ease {
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    transform-origin: left top;
+    transition: transform 1s;
 }
-.collectionfreeformview-container {
-  .collectionfreeformview > .jsx-parser {
+
+.collectionfreeformview-none {
     position: inherit;
-    height: 100%;
+    top: 0;
+    left: 0;
     width: 100%;
-  }
+    height: 100%;
+    transform-origin: left top;
+}
 
-  //nested freeform views
-  // .collectionfreeformview-container {
+.collectionfreeformview-container {
+    .collectionfreeformview>.jsx-parser {
+        position: inherit;
+        height: 100%;
+        width: 100%;
+    }
+
+    //nested freeform views
+    // .collectionfreeformview-container {
     // background-image: linear-gradient(to right, $light-color-secondary 1px, transparent 1px),
     //   linear-gradient(to bottom, $light-color-secondary 1px, transparent 1px);
     // background-size: 30px 30px;
-  // }
-
-  border-width: $COLLECTION_BORDER_WIDTH;
-  box-shadow: $intermediate-color 0.2vw 0.2vw 0.8vw;
-  border-color:  $light-color-secondary;
-  border-style: solid; 
-  border-radius: $border-radius;
-  box-sizing: border-box;
-  position: absolute;
-  overflow: hidden;
-  top: 0;
-  left: 0;
-  width: 100%;
-  height: 100%;
+    // }
+    box-shadow: $intermediate-color 0.2vw 0.2vw 0.8vw;
+    border: 0px solid $light-color-secondary;
+    border-radius: $border-radius;
+    box-sizing: border-box;
+    position: absolute;
+    .marqueeView {
+        overflow: hidden;
+    }
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
 }
+  
+
 .collectionfreeformview-overlay {
-  .collectionfreeformview > .jsx-parser {
-    position: inherit;
-    height: 100%;
-  }
-  .formattedTextBox-cont {
-    background: $light-color-secondary;
-    overflow: visible;
-  }
- 
-  opacity: 0.99;
-  border-width: 0; 
-  border-color: transparent;
-  border-style: solid; 
-  border-radius: $border-radius;
-  box-sizing: border-box;
-  position: absolute;
-  overflow: hidden;
-  top: 0;
-  left: 0;
-  width: 100%;
-  height: 100%;
-  .collectionfreeformview {
+    .collectionfreeformview>.jsx-parser {
+        position: inherit;
+        height: 100%;
+    }
+
     .formattedTextBox-cont {
-        background:yellow;
+        background: $light-color-secondary;
+        overflow: visible;
+    }
+
+    opacity: 0.99;
+    border: 0px solid transparent;
+    border-radius: $border-radius;
+    box-sizing: border-box;
+    position:absolute;
+    .marqueeView {
+        overflow: hidden;
+    }
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+
+    .collectionfreeformview {
+        .formattedTextBox-cont {
+            background: yellow;
+        }
     }
-  }
 }
 
 // selection border...?
 .border {
-  border-style: solid;
-  box-sizing: border-box;
-  width: 98%;
-  height: 98%;
-  border-radius: $border-radius;
+    border-style: solid;
+    box-sizing: border-box;
+    width: 98%;
+    height: 98%;
+    border-radius: $border-radius;
 }
 
 //this is an animation for the blinking cursor!
 @keyframes blink {
-  0% {
-    opacity: 0;
-  }
-  49% {
-    opacity: 0;
-  }
-  50% {
-    opacity: 1;
-  }
+    0% {
+        opacity: 0;
+    }
+
+    49% {
+        opacity: 0;
+    }
+
+    50% {
+        opacity: 1;
+    }
 }
 
 #prevCursor {
-  animation: blink 1s infinite;
-}
+    animation: blink 1s infinite;
+}
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 228fc937e..6861ce58b 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,8 +1,5 @@
-import { action, computed, observable, trace } from "mobx";
+import { action, computed, trace } from "mobx";
 import { observer } from "mobx-react";
-import Measure from "react-measure";
-import { Document } from "../../../../fields/Document";
-import { KeyStore } from "../../../../fields/KeyStore";
 import { emptyFunction, returnFalse, returnOne } from "../../../../Utils";
 import { DocumentManager } from "../../../util/DocumentManager";
 import { DragManager } from "../../../util/DragManager";
@@ -11,10 +8,9 @@ import { Transform } from "../../../util/Transform";
 import { undoBatch } from "../../../util/UndoManager";
 import { COLLECTION_BORDER_WIDTH } from "../../../views/globalCssVariables.scss";
 import { InkingCanvas } from "../../InkingCanvas";
-import { MainOverlayTextBox } from "../../MainOverlayTextBox";
 import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView";
 import { DocumentContentsView } from "../../nodes/DocumentContentsView";
-import { DocumentViewProps } from "../../nodes/DocumentView";
+import { DocumentViewProps, positionSchema } from "../../nodes/DocumentView";
 import { CollectionSubView } from "../CollectionSubView";
 import { CollectionFreeFormLinksView } from "./CollectionFreeFormLinksView";
 import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors";
@@ -22,72 +18,86 @@ import "./CollectionFreeFormView.scss";
 import { MarqueeView } from "./MarqueeView";
 import React = require("react");
 import v5 = require("uuid/v5");
+import { createSchema, makeInterface, listSpec } from "../../../../new_fields/Schema";
+import { Doc, WidthSym, HeightSym } from "../../../../new_fields/Doc";
+import { FieldValue, Cast, NumCast } from "../../../../new_fields/Types";
+import { pageSchema } from "../../nodes/ImageBox";
+import { Id } from "../../../../new_fields/RefField";
+
+export const panZoomSchema = createSchema({
+    panX: "number",
+    panY: "number",
+    scale: "number"
+});
+
+type PanZoomDocument = makeInterface<[typeof panZoomSchema, typeof positionSchema, typeof pageSchema]>;
+const PanZoomDocument = makeInterface(panZoomSchema, positionSchema, pageSchema);
 
 @observer
-export class CollectionFreeFormView extends CollectionSubView {
+export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
+    public static RIGHT_BTN_DRAG = false;
     private _selectOnLoaded: string = ""; // id of document that should be selected once it's loaded (used for click-to-type)
     private _lastX: number = 0;
     private _lastY: number = 0;
     private get _pwidth() { return this.props.PanelWidth(); }
     private get _pheight() { return this.props.PanelHeight(); }
 
-    @computed get nativeWidth() { return this.props.Document.GetNumber(KeyStore.NativeWidth, 0); }
-    @computed get nativeHeight() { return this.props.Document.GetNumber(KeyStore.NativeHeight, 0); }
+    @computed get nativeWidth() { return FieldValue(this.Document.nativeWidth, 0); }
+    @computed get nativeHeight() { return FieldValue(this.Document.nativeHeight, 0); }
     private get borderWidth() { return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH; }
-    private get isAnnotationOverlay() { return this.props.fieldKey && this.props.fieldKey.Id === KeyStore.Annotations.Id; } // bcz: ? Why do we need to compare Id's?
-    private childViews = () => this.views;
-    private panX = () => this.props.Document.GetNumber(KeyStore.PanX, 0);
-    private panY = () => this.props.Document.GetNumber(KeyStore.PanY, 0);
-    private zoomScaling = () => this.props.Document.GetNumber(KeyStore.Scale, 1);
+    private get isAnnotationOverlay() { return this.props.fieldKey && this.props.fieldKey === "annotations"; }
+    private panX = () => FieldValue(this.Document.panX, 0);
+    private panY = () => FieldValue(this.Document.panY, 0);
+    private zoomScaling = () => FieldValue(this.Document.scale, 1);
     private centeringShiftX = () => !this.nativeWidth ? this._pwidth / 2 : 0;  // shift so pan position is at center of window for non-overlay collections
     private centeringShiftY = () => !this.nativeHeight ? this._pheight / 2 : 0;// shift so pan position is at center of window for non-overlay collections
     private getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth, -this.borderWidth).translate(-this.centeringShiftX(), -this.centeringShiftY()).transform(this.getLocalTransform());
     private getContainerTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth, -this.borderWidth);
     private getLocalTransform = (): Transform => Transform.Identity().scale(1 / this.zoomScaling()).translate(this.panX(), this.panY());
-    private addLiveTextBox = (newBox: Document) => {
-        this._selectOnLoaded = newBox.Id;// track the new text box so we can give it a prop that tells it to focus itself when it's displayed
+    private addLiveTextBox = (newBox: Doc) => {
+        this._selectOnLoaded = newBox[Id];// track the new text box so we can give it a prop that tells it to focus itself when it's displayed
         this.addDocument(newBox, false);
     }
-    private addDocument = (newBox: Document, allowDuplicates: boolean) => {
-        if (this.isAnnotationOverlay) {
-            newBox.SetNumber(KeyStore.Zoom, this.props.Document.GetNumber(KeyStore.Scale, 1));
-        }
-        return this.props.addDocument(this.bringToFront(newBox), false);
+    private addDocument = (newBox: Doc, allowDuplicates: boolean) => {
+        this.props.addDocument(newBox, false);
+        this.bringToFront(newBox);
+        return true;
     }
-    private selectDocuments = (docs: Document[]) => {
+    private selectDocuments = (docs: Doc[]) => {
         SelectionManager.DeselectAll;
         docs.map(doc => DocumentManager.Instance.getDocumentView(doc)).filter(dv => dv).map(dv =>
             SelectionManager.SelectDoc(dv!, true));
     }
     public getActiveDocuments = () => {
-        var curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1);
-        return this.props.Document.GetList(this.props.fieldKey, [] as Document[]).reduce((active, doc) => {
-            var page = doc.GetNumber(KeyStore.Page, -1);
-            if (page === curPage || page === -1) {
-                active.push(doc);
-            }
-            return active;
-        }, [] as Document[]);
+        const curPage = FieldValue(this.Document.curPage, -1);
+        return this.children.filter(doc => {
+            var page = NumCast(doc.page, -1);
+            return page === curPage || page === -1;
+        });
     }
 
     @undoBatch
     @action
     drop = (e: Event, de: DragManager.DropEvent) => {
         if (super.drop(e, de) && de.data instanceof DragManager.DocumentDragData) {
-            const [x, y] = this.getTransform().transformPoint(de.x - de.data.xOffset, de.y - de.data.yOffset);
             if (de.data.droppedDocuments.length) {
-                let dropX = de.data.droppedDocuments[0].GetNumber(KeyStore.X, 0);
-                let dropY = de.data.droppedDocuments[0].GetNumber(KeyStore.Y, 0);
+                let dragDoc = de.data.droppedDocuments[0];
+                let zoom = NumCast(dragDoc.zoomBasis, 1);
+                let [xp, yp] = this.getTransform().transformPoint(de.x, de.y);
+                let x = xp - de.data.xOffset / zoom;
+                let y = yp - de.data.yOffset / zoom;
+                let dropX = NumCast(de.data.droppedDocuments[0].x);
+                let dropY = NumCast(de.data.droppedDocuments[0].y);
                 de.data.droppedDocuments.map(d => {
-                    d.SetNumber(KeyStore.X, x + (d.GetNumber(KeyStore.X, 0) - dropX));
-                    d.SetNumber(KeyStore.Y, y + (d.GetNumber(KeyStore.Y, 0) - dropY));
-                    if (!d.GetNumber(KeyStore.Width, 0)) {
-                        d.SetNumber(KeyStore.Width, 300);
+                    d.x = x + NumCast(d.x) - dropX;
+                    d.y = y + NumCast(d.y) - dropY;
+                    if (!NumCast(d.width)) {
+                        d.width = 300;
                     }
-                    if (!d.GetNumber(KeyStore.Height, 0)) {
-                        let nw = d.GetNumber(KeyStore.NativeWidth, 0);
-                        let nh = d.GetNumber(KeyStore.NativeHeight, 0);
-                        d.SetNumber(KeyStore.Height, nw && nh ? nh / nw * d.Width() : 300);
+                    if (!NumCast(d.height)) {
+                        let nw = NumCast(d.nativeWidth);
+                        let nh = NumCast(d.nativeHeight);
+                        d.height = nw && nh ? nh / nw * NumCast(d.width) : 300;
                     }
                     this.bringToFront(d);
                 });
@@ -98,52 +108,44 @@ export class CollectionFreeFormView extends CollectionSubView {
         return false;
     }
 
-    @action
-    cleanupInteractions = () => {
-        document.removeEventListener("pointermove", this.onPointerMove);
-        document.removeEventListener("pointerup", this.onPointerUp);
-    }
-
     @action
     onPointerDown = (e: React.PointerEvent): void => {
-        let childSelected = this.props.Document.GetList(this.props.fieldKey, [] as Document[]).filter(doc => doc).reduce((childSelected, doc) => {
-            var dv = DocumentManager.Instance.getDocumentView(doc);
-            return childSelected || (dv && SelectionManager.IsSelected(dv) ? true : false);
-        }, false);
-        if (((e.button === 2 && (!this.isAnnotationOverlay || this.zoomScaling() !== 1)) || (e.button === 0 && e.altKey)) && (childSelected || this.props.active())) {
+        if ((CollectionFreeFormView.RIGHT_BTN_DRAG &&
+            (((e.button === 2 && (!this.isAnnotationOverlay || this.zoomScaling() !== 1)) ||
+                (e.button === 0 && e.altKey)) && this.props.active())) ||
+            (!CollectionFreeFormView.RIGHT_BTN_DRAG &&
+                ((e.button === 0 && !e.altKey && (!this.isAnnotationOverlay || this.zoomScaling() !== 1)) && this.props.active()))) {
             document.removeEventListener("pointermove", this.onPointerMove);
-            document.addEventListener("pointermove", this.onPointerMove);
             document.removeEventListener("pointerup", this.onPointerUp);
+            document.addEventListener("pointermove", this.onPointerMove);
             document.addEventListener("pointerup", this.onPointerUp);
             this._lastX = e.pageX;
             this._lastY = e.pageY;
         }
     }
 
-    @action
     onPointerUp = (e: PointerEvent): void => {
-        e.stopPropagation();
-
-        this.cleanupInteractions();
+        document.removeEventListener("pointermove", this.onPointerMove);
+        document.removeEventListener("pointerup", this.onPointerUp);
     }
 
     @action
     onPointerMove = (e: PointerEvent): void => {
         if (!e.cancelBubble) {
-            let x = this.props.Document.GetNumber(KeyStore.PanX, 0);
-            let y = this.props.Document.GetNumber(KeyStore.PanY, 0);
-            let docs = this.props.Document.GetList(this.props.fieldKey, [] as Document[]);
+            let x = Cast(this.props.Document.panX, "number", 0);
+            let y = Cast(this.props.Document.panY, "number", 0);
+            let docs = this.children || [];
             let [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
             if (!this.isAnnotationOverlay) {
-                let minx = docs.length ? docs[0].GetNumber(KeyStore.X, 0) : 0;
-                let maxx = docs.length ? docs[0].Width() + minx : minx;
-                let miny = docs.length ? docs[0].GetNumber(KeyStore.Y, 0) : 0;
-                let maxy = docs.length ? docs[0].Height() + miny : miny;
+                let minx = docs.length ? Cast(docs[0].x, "number", 0) : 0;
+                let maxx = docs.length ? Cast(docs[0].width, "number", 0) + minx : minx;
+                let miny = docs.length ? Cast(docs[0].y, "number", 0) : 0;
+                let maxy = docs.length ? Cast(docs[0].height, "number", 0) + miny : miny;
                 let ranges = docs.filter(doc => doc).reduce((range, doc) => {
-                    let x = doc.GetNumber(KeyStore.X, 0);
-                    let xe = x + doc.GetNumber(KeyStore.Width, 0);
-                    let y = doc.GetNumber(KeyStore.Y, 0);
-                    let ye = y + doc.GetNumber(KeyStore.Height, 0);
+                    let x = Cast(doc.x, "number", 0);
+                    let xe = x + Cast(doc.width, "number", 0);
+                    let y = Cast(doc.y, "number", 0);
+                    let ye = y + Cast(doc.height, "number", 0);
                     return [[range[0][0] > x ? x : range[0][0], range[0][1] < xe ? xe : range[0][1]],
                     [range[1][0] > y ? y : range[1][0], range[1][1] < ye ? ye : range[1][1]]];
                 }, [[minx, maxx], [miny, maxy]]);
@@ -157,7 +159,7 @@ export class CollectionFreeFormView extends CollectionSubView {
             this.setPan(x - dx, y - dy);
             this._lastX = e.pageX;
             this._lastY = e.pageY;
-            e.stopPropagation();
+            e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document'  however it does mark the event as cancelBubble=true which we test for in the move event handlers
             e.preventDefault();
         }
     }
@@ -167,10 +169,10 @@ export class CollectionFreeFormView extends CollectionSubView {
         // if (!this.props.active()) {
         //     return;
         // }
-        let childSelected = this.props.Document.GetList(this.props.fieldKey, [] as Document[]).filter(doc => doc).reduce((childSelected, doc) => {
+        let childSelected = this.children.some(doc => {
             var dv = DocumentManager.Instance.getDocumentView(doc);
-            return childSelected || (dv && SelectionManager.IsSelected(dv) ? true : false);
-        }, false);
+            return dv && SelectionManager.IsSelected(dv) ? true : false;
+        });
         if (!this.props.isSelected() && !childSelected && !this.props.isTopMost) {
             return;
         }
@@ -179,8 +181,8 @@ export class CollectionFreeFormView extends CollectionSubView {
 
         if (e.ctrlKey) {
             let deltaScale = (1 - (e.deltaY / coefficient));
-            this.props.Document.SetNumber(KeyStore.NativeWidth, this.nativeWidth * deltaScale);
-            this.props.Document.SetNumber(KeyStore.NativeHeight, this.nativeHeight * deltaScale);
+            this.props.Document.nativeWidth = this.nativeWidth * deltaScale;
+            this.props.Document.nativeHeight = this.nativeHeight * deltaScale;
             e.stopPropagation();
             e.preventDefault();
         } else {
@@ -190,23 +192,24 @@ export class CollectionFreeFormView extends CollectionSubView {
             if (deltaScale * this.zoomScaling() < 1 && this.isAnnotationOverlay) {
                 deltaScale = 1 / this.zoomScaling();
             }
+            if (deltaScale < 0) deltaScale = -deltaScale;
             let [x, y] = this.getTransform().transformPoint(e.clientX, e.clientY);
             let localTransform = this.getLocalTransform().inverse().scaleAbout(deltaScale, x, y);
 
-            this.props.Document.SetNumber(KeyStore.Scale, localTransform.Scale);
-            this.setPan(-localTransform.TranslateX / localTransform.Scale, -localTransform.TranslateY / localTransform.Scale);
+            let safeScale = Math.abs(localTransform.Scale);
+            this.props.Document.scale = Math.abs(safeScale);
+            this.setPan(-localTransform.TranslateX / safeScale, -localTransform.TranslateY / safeScale);
             e.stopPropagation();
         }
     }
 
     @action
     setPan(panX: number, panY: number) {
-        MainOverlayTextBox.Instance.SetTextDoc();
         var scale = this.getLocalTransform().inverse().Scale;
         const newPanX = Math.min((1 - 1 / scale) * this.nativeWidth, Math.max(0, panX));
         const newPanY = Math.min((1 - 1 / scale) * this.nativeHeight, Math.max(0, panY));
-        this.props.Document.SetNumber(KeyStore.PanX, this.isAnnotationOverlay ? newPanX : panX);
-        this.props.Document.SetNumber(KeyStore.PanY, this.isAnnotationOverlay ? newPanY : panY);
+        this.props.Document.panX = this.isAnnotationOverlay ? newPanX : panX;
+        this.props.Document.panY = this.isAnnotationOverlay ? newPanY : panY;
     }
 
     @action
@@ -218,49 +221,61 @@ export class CollectionFreeFormView extends CollectionSubView {
     onDragOver = (): void => {
     }
 
-    @action
-    bringToFront(doc: Document) {
-        this.props.Document.GetList(this.props.fieldKey, [] as Document[]).slice().sort((doc1, doc2) => {
+    bringToFront = (doc: Doc) => {
+        const docs = this.children;
+        docs.slice().sort((doc1, doc2) => {
             if (doc1 === doc) return 1;
             if (doc2 === doc) return -1;
-            return doc1.GetNumber(KeyStore.ZIndex, 0) - doc2.GetNumber(KeyStore.ZIndex, 0);
-        }).map((doc, index) => doc.SetNumber(KeyStore.ZIndex, index + 1));
-        return doc;
+            return NumCast(doc1.zIndex) - NumCast(doc2.zIndex);
+        }).forEach((doc, index) => doc.zIndex = index + 1);
+        doc.zIndex = docs.length + 1;
     }
 
-    focusDocument = (doc: Document) => {
+    focusDocument = (doc: Doc) => {
+        SelectionManager.DeselectAll();
+        this.props.Document.panTransformType = "Ease";
         this.setPan(
-            doc.GetNumber(KeyStore.X, 0) + doc.Width() / 2,
-            doc.GetNumber(KeyStore.Y, 0) + doc.Height() / 2);
+            NumCast(doc.x) + NumCast(doc.width) / 2,
+            NumCast(doc.y) + NumCast(doc.height) / 2);
         this.props.focus(this.props.Document);
+        if (this.props.Document.panTransformType === "Ease") {
+            setTimeout(() => this.props.Document.panTransformType = "None", 2000);  // wait 3 seconds, then reset to false
+        }
     }
 
-    getDocumentViewProps(document: Document): DocumentViewProps {
+
+    getDocumentViewProps(document: Doc): DocumentViewProps {
         return {
             Document: document,
+            toggleMinimized: emptyFunction,
             addDocument: this.props.addDocument,
             removeDocument: this.props.removeDocument,
             moveDocument: this.props.moveDocument,
             ScreenToLocalTransform: this.getTransform,
             isTopMost: false,
-            selectOnLoad: document.Id === this._selectOnLoaded,
-            PanelWidth: document.Width,
-            PanelHeight: document.Height,
+            selectOnLoad: document[Id] === this._selectOnLoaded,
+            PanelWidth: document[WidthSym],
+            PanelHeight: document[HeightSym],
             ContentScaling: returnOne,
             ContainingCollectionView: this.props.CollectionView,
             focus: this.focusDocument,
             parentActive: this.props.active,
-            whenActiveChanged: this.props.active,
+            whenActiveChanged: this.props.whenActiveChanged,
+            bringToFront: this.bringToFront,
         };
     }
 
-    @computed
+    @computed.struct
     get views() {
-        var curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1);
-        let docviews = this.props.Document.GetList(this.props.fieldKey, [] as Document[]).filter(doc => doc).reduce((prev, doc) => {
-            var page = doc.GetNumber(KeyStore.Page, -1);
+        let curPage = FieldValue(this.Document.curPage, -1);
+        let docviews = this.children.reduce((prev, doc) => {
+            if (!(doc instanceof Doc)) return prev;
+            var page = NumCast(doc.page, -1);
             if (page === curPage || page === -1) {
-                prev.push(<CollectionFreeFormDocumentView key={doc.Id} {...this.getDocumentViewProps(doc)} />);
+                let minim = Cast(doc.isMinimized, "boolean");
+                if (minim === undefined || !minim) {
+                    prev.push(<CollectionFreeFormDocumentView key={doc[Id]} {...this.getDocumentViewProps(doc)} />);
+                }
             }
             return prev;
         }, [] as JSX.Element[]);
@@ -275,17 +290,20 @@ export class CollectionFreeFormView extends CollectionSubView {
         super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY));
     }
 
+    private childViews = () => [...this.views, <CollectionFreeFormBackgroundView key="backgroundView" {...this.props} {...this.getDocumentViewProps(this.props.Document)} />];
     render() {
         const containerName = `collectionfreeformview${this.isAnnotationOverlay ? "-overlay" : "-container"}`;
+        const easing = () => this.props.Document.panTransformType === "Ease";
         return (
             <div className={containerName} ref={this.createDropTarget} onWheel={this.onPointerWheel}
+                style={{ borderRadius: "inherit" }}
                 onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onDragOver={this.onDragOver} >
-                <MarqueeView container={this} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments}
+                <MarqueeView container={this} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments} isSelected={this.props.isSelected}
                     addDocument={this.addDocument} removeDocument={this.props.removeDocument} addLiveTextDocument={this.addLiveTextBox}
                     getContainerTransform={this.getContainerTransform} getTransform={this.getTransform}>
                     <CollectionFreeFormViewPannableContents centeringShiftX={this.centeringShiftX} centeringShiftY={this.centeringShiftY}
-                        zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}>
-                        <CollectionFreeFormBackgroundView {...this.getDocumentViewProps(this.props.Document)} />
+                        easing={easing} zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}>
+
                         <CollectionFreeFormLinksView {...this.props} key="freeformLinks">
                             <InkingCanvas getScreenTransform={this.getTransform} Document={this.props.Document} >
                                 {this.childViews}
@@ -293,20 +311,18 @@ export class CollectionFreeFormView extends CollectionSubView {
                         </CollectionFreeFormLinksView>
                         <CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" />
                     </CollectionFreeFormViewPannableContents>
-                    <CollectionFreeFormOverlayView {...this.getDocumentViewProps(this.props.Document)} />
                 </MarqueeView>
+                <CollectionFreeFormOverlayView {...this.getDocumentViewProps(this.props.Document)} {...this.props} />
             </div>
         );
     }
 }
 
 @observer
-class CollectionFreeFormOverlayView extends React.Component<DocumentViewProps> {
+class CollectionFreeFormOverlayView extends React.Component<DocumentViewProps & { isSelected: () => boolean }> {
     @computed get overlayView() {
-        let overlayLayout = this.props.Document.GetText(KeyStore.OverlayLayout, "");
-        return !overlayLayout ? (null) :
-            (<DocumentContentsView {...this.props} layoutKey={KeyStore.OverlayLayout}
-                isTopMost={this.props.isTopMost} isSelected={returnFalse} select={emptyFunction} />);
+        return (<DocumentContentsView {...this.props} layoutKey={"overlayLayout"}
+            isTopMost={this.props.isTopMost} isSelected={this.props.isSelected} select={emptyFunction} />);
     }
     render() {
         return this.overlayView;
@@ -314,12 +330,10 @@ class CollectionFreeFormOverlayView extends React.Component<DocumentViewProps> {
 }
 
 @observer
-class CollectionFreeFormBackgroundView extends React.Component<DocumentViewProps> {
+class CollectionFreeFormBackgroundView extends React.Component<DocumentViewProps & { isSelected: () => boolean }> {
     @computed get backgroundView() {
-        let backgroundLayout = this.props.Document.GetText(KeyStore.BackgroundLayout, "");
-        return !backgroundLayout ? (null) :
-            (<DocumentContentsView {...this.props} layoutKey={KeyStore.BackgroundLayout}
-                isTopMost={this.props.isTopMost} isSelected={returnFalse} select={emptyFunction} />);
+        return (<DocumentContentsView {...this.props} layoutKey={"backgroundLayout"}
+            isTopMost={this.props.isTopMost} isSelected={this.props.isSelected} select={emptyFunction} />);
     }
     render() {
         return this.backgroundView;
@@ -332,17 +346,19 @@ interface CollectionFreeFormViewPannableContentsProps {
     panX: () => number;
     panY: () => number;
     zoomScaling: () => number;
+    easing: () => boolean;
 }
 
 @observer
 class CollectionFreeFormViewPannableContents extends React.Component<CollectionFreeFormViewPannableContentsProps>{
     render() {
+        let freeformclass = "collectionfreeformview" + (this.props.easing() ? "-ease" : "-none");
         const cenx = this.props.centeringShiftX();
         const ceny = this.props.centeringShiftY();
         const panx = -this.props.panX();
         const pany = -this.props.panY();
         const zoom = this.props.zoomScaling();// needs to be a variable outside of the <Measure> otherwise, reactions won't fire
-        return <div className="collectionfreeformview" style={{ transform: `translate(${cenx}px, ${ceny}px) scale(${zoom}, ${zoom}) translate(${panx}px, ${pany}px)` }}>
+        return <div className={freeformclass} style={{ borderRadius: "inherit", transform: `translate(${cenx}px, ${ceny}px) scale(${zoom}, ${zoom}) translate(${panx}px, ${pany}px)` }}>
             {this.props.children}
         </div>;
     }
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.scss b/src/client/views/collections/collectionFreeForm/MarqueeView.scss
index ae0a9fd48..6e8ec8662 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.scss
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.scss
@@ -21,6 +21,6 @@
         white-space:nowrap;
     }
     .marquee-legend::after {
-        content: "Press: C (collection), or Delete"
+        content: "Press: c (collection), s (summary), r (replace) or Delete"
     }
 }
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 65f461b27..1bf39e335 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -1,28 +1,30 @@
 import { action, computed, observable } from "mobx";
 import { observer } from "mobx-react";
-import { Document } from "../../../../fields/Document";
-import { FieldWaiting } from "../../../../fields/Field";
-import { InkField, StrokeData } from "../../../../fields/InkField";
-import { KeyStore } from "../../../../fields/KeyStore";
-import { Documents } from "../../../documents/Documents";
+import { Docs } from "../../../documents/Documents";
 import { SelectionManager } from "../../../util/SelectionManager";
 import { Transform } from "../../../util/Transform";
-import { undoBatch } from "../../../util/UndoManager";
+import { undoBatch, UndoManager } from "../../../util/UndoManager";
 import { InkingCanvas } from "../../InkingCanvas";
 import { PreviewCursor } from "../../PreviewCursor";
 import { CollectionFreeFormView } from "./CollectionFreeFormView";
 import "./MarqueeView.scss";
 import React = require("react");
+import { Utils } from "../../../../Utils";
+import { Doc } from "../../../../new_fields/Doc";
+import { NumCast, Cast } from "../../../../new_fields/Types";
+import { InkField, StrokeData } from "../../../../new_fields/InkField";
+import { List } from "../../../../new_fields/List";
 
 interface MarqueeViewProps {
     getContainerTransform: () => Transform;
     getTransform: () => Transform;
     container: CollectionFreeFormView;
-    addDocument: (doc: Document, allowDuplicates: false) => boolean;
-    activeDocuments: () => Document[];
-    selectDocuments: (docs: Document[]) => void;
-    removeDocument: (doc: Document) => boolean;
-    addLiveTextDocument: (doc: Document) => void;
+    addDocument: (doc: Doc, allowDuplicates: false) => boolean;
+    activeDocuments: () => Doc[];
+    selectDocuments: (docs: Doc[]) => void;
+    removeDocument: (doc: Doc) => boolean;
+    addLiveTextDocument: (doc: Doc) => void;
+    isSelected: () => boolean;
 }
 
 @observer
@@ -32,52 +34,73 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
     @observable _lastY: number = 0;
     @observable _downX: number = 0;
     @observable _downY: number = 0;
-    @observable _used: boolean = false;
     @observable _visible: boolean = false;
-    _showOnUp: boolean = false;
-    static DRAG_THRESHOLD = 4;
+    _commandExecuted = false;
 
     @action
     cleanupInteractions = (all: boolean = false) => {
         if (all) {
-            document.removeEventListener("pointermove", this.onPointerMove, true);
             document.removeEventListener("pointerup", this.onPointerUp, true);
-        } else {
-            this._used = true;
+            document.removeEventListener("pointermove", this.onPointerMove, true);
         }
         document.removeEventListener("keydown", this.marqueeCommand, true);
         this._visible = false;
     }
 
+    @undoBatch
     @action
     onKeyPress = (e: KeyboardEvent) => {
-        // Mixing events between React and Native is finicky.  In FormattedTextBox, we set the
-        // DASHFormattedTextBoxHandled flag when a text box consumes a key press so that we can ignore
-        // the keyPress here.
-        //if not these keys, make a textbox if preview cursor is active!
-        if (!e.ctrlKey && !e.altKey && !e.defaultPrevented && !(e as any).DASHFormattedTextBoxHandled) {
-            //make textbox and add it to this collection
-            let [x, y] = this.props.getTransform().transformPoint(this._downX, this._downY);
-            let newBox = Documents.TextDocument({ width: 200, height: 100, x: x, y: y, title: "typed text" });
+        //make textbox and add it to this collection
+        let [x, y] = this.props.getTransform().transformPoint(this._downX, this._downY);
+        if (e.key === "q" && e.ctrlKey) {
+            e.preventDefault();
+            (async () => {
+                let text = await navigator.clipboard.readText();
+                let ns = text.split("\n").filter(t => t != "\r");
+                for (let i = 0; i < ns.length - 1; i++) {
+                    if (ns[i].trim() === "") {
+                        ns.splice(i, 1);
+                        continue;
+                    }
+                    while (!(ns[i].trim() === "" || ns[i].endsWith("-\r") || ns[i].endsWith("-") ||
+                        ns[i].endsWith(";\r") || ns[i].endsWith(";") ||
+                        ns[i].endsWith(".\r") || ns[i].endsWith(".") ||
+                        ns[i].endsWith(":\r") || ns[i].endsWith(":")) && i < ns.length - 1) {
+                        let sub = ns[i].endsWith("\r") ? 1 : 0;
+                        let br = ns[i + 1].trim() === "";
+                        ns.splice(i, 2, ns[i].substr(0, ns[i].length - sub) + ns[i + 1].trimLeft());
+                        if (br) break;
+                    }
+                }
+                ns.map(line => {
+                    let indent = line.search(/\S|$/);
+                    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 {
+            let newBox = Docs.TextDocument({ width: 200, height: 100, x: x, y: y, title: "-typed text-" });
             this.props.addLiveTextDocument(newBox);
-            PreviewCursor.Visible = false;
-            e.stopPropagation();
         }
-    }
-    hideCursor = () => {
-        document.removeEventListener("keypress", this.onKeyPress, false);
+        e.stopPropagation();
     }
     @action
     onPointerDown = (e: React.PointerEvent): void => {
-        if (e.buttons === 1 && !e.altKey && !e.metaKey && this.props.container.props.active()) {
-            this._downX = this._lastX = e.pageX;
-            this._downY = this._lastY = e.pageY;
-            this._used = false;
-            this._showOnUp = true;
-            document.removeEventListener("keypress", this.onKeyPress, false);
+        this._downX = this._lastX = e.pageX;
+        this._downY = this._lastY = e.pageY;
+        this._commandExecuted = false;
+        PreviewCursor.Visible = false;
+        if ((CollectionFreeFormView.RIGHT_BTN_DRAG && e.button === 0 && !e.altKey && !e.metaKey && this.props.container.props.active()) ||
+            (!CollectionFreeFormView.RIGHT_BTN_DRAG && (e.button === 2 || (e.button === 0 && e.altKey)) && this.props.container.props.active())) {
             document.addEventListener("pointermove", this.onPointerMove, true);
             document.addEventListener("pointerup", this.onPointerUp, true);
             document.addEventListener("keydown", this.marqueeCommand, true);
+            // bcz: do we need this?   it kills the context menu on the main collection
+            // e.stopPropagation();
+        }
+        if (e.altKey) {
+            e.preventDefault();
         }
     }
 
@@ -86,33 +109,45 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
         this._lastX = e.pageX;
         this._lastY = e.pageY;
         if (!e.cancelBubble) {
-            if (Math.abs(this._downX - e.clientX) > 4 || Math.abs(this._downY - e.clientY) > 4) {
-                this._showOnUp = false;
-                PreviewCursor.Visible = false;
-            }
-            if (!this._used && e.buttons === 1 && !e.altKey && !e.metaKey &&
-                (Math.abs(this._lastX - this._downX) > MarqueeView.DRAG_THRESHOLD || Math.abs(this._lastY - this._downY) > MarqueeView.DRAG_THRESHOLD)) {
-                this._visible = true;
+            if (Math.abs(this._lastX - this._downX) > Utils.DRAG_THRESHOLD ||
+                Math.abs(this._lastY - this._downY) > Utils.DRAG_THRESHOLD) {
+                if (!this._commandExecuted) {
+                    this._visible = true;
+                }
+                e.stopPropagation();
+                e.preventDefault();
             }
-            e.stopPropagation();
+        }
+        if (e.altKey) {
             e.preventDefault();
         }
     }
 
     @action
     onPointerUp = (e: PointerEvent): void => {
-        this.cleanupInteractions(true);
-        this._visible = false;
-        if (this._showOnUp) {
-            PreviewCursor.Show(this.hideCursor, this._downX, this._downY);
-            document.addEventListener("keypress", this.onKeyPress, false);
-        } else {
+        if (this._visible) {
             let mselect = this.marqueeSelect();
             if (!e.shiftKey) {
                 SelectionManager.DeselectAll(mselect.length ? undefined : this.props.container.props.Document);
             }
             this.props.selectDocuments(mselect.length ? mselect : [this.props.container.props.Document]);
         }
+        this.cleanupInteractions(true);
+        if (e.altKey) {
+            e.preventDefault();
+        }
+    }
+
+    @action
+    onClick = (e: React.MouseEvent): void => {
+        if (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD &&
+            Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
+            PreviewCursor.Show(e.clientX, e.clientY, this.onKeyPress);
+            // let the DocumentView stopPropagation of this event when it selects this document
+        } else {  // why do we get a click event when the cursor have moved a big distance?
+            // let's cut it off here so no one else has to deal with it.
+            e.stopPropagation();
+        }
     }
 
     intersectRect(r1: { left: number, top: number, width: number, height: number },
@@ -132,42 +167,99 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
     @undoBatch
     @action
     marqueeCommand = (e: KeyboardEvent) => {
-        if (e.key === "Backspace" || e.key === "Delete") {
+        if (this._commandExecuted) {
+            return;
+        }
+        if (e.key === "Backspace" || e.key === "Delete" || e.key === "d") {
+            this._commandExecuted = true;
             this.marqueeSelect().map(d => this.props.removeDocument(d));
-            let ink = this.props.container.props.Document.GetT(KeyStore.Ink, InkField);
-            if (ink && ink !== FieldWaiting) {
-                this.marqueeInkDelete(ink.Data);
+            let ink = Cast(this.props.container.props.Document.ink, InkField);
+            if (ink) {
+                this.marqueeInkDelete(ink.inkData);
             }
-            this.cleanupInteractions();
+            SelectionManager.DeselectAll();
+            this.cleanupInteractions(false);
+            e.stopPropagation();
         }
-        if (e.key === "c") {
+        if (e.key === "c" || e.key === "r" || e.key === "s" || e.key === "e" || e.key === "p") {
+            this._commandExecuted = true;
+            e.stopPropagation();
             let bounds = this.Bounds;
             let selected = this.marqueeSelect().map(d => {
-                this.props.removeDocument(d);
-                d.SetNumber(KeyStore.X, d.GetNumber(KeyStore.X, 0) - bounds.left - bounds.width / 2);
-                d.SetNumber(KeyStore.Y, d.GetNumber(KeyStore.Y, 0) - bounds.top - bounds.height / 2);
-                d.SetNumber(KeyStore.Page, -1);
+                if (e.key === "s") {
+                    let dCopy = Doc.MakeCopy(d);
+                    dCopy.x = NumCast(d.x) - bounds.left - bounds.width / 2;
+                    dCopy.y = NumCast(d.y) - bounds.top - bounds.height / 2;
+                    dCopy.page = -1;
+                    return dCopy;
+                }
+                else if (e.key !== "r") {
+                    this.props.removeDocument(d);
+                    d.x = NumCast(d.x) - bounds.left - bounds.width / 2;
+                    d.y = NumCast(d.y) - bounds.top - bounds.height / 2;
+                    d.page = -1;
+                }
                 return d;
             });
-            let ink = this.props.container.props.Document.GetT(KeyStore.Ink, InkField);
-            let inkData = ink && ink !== FieldWaiting ? ink.Data : undefined;
-            //setTimeout(() => {
-            let newCollection = Documents.FreeformDocument(selected, {
+            let ink = Cast(this.props.container.props.Document.ink, InkField);
+            let inkData = ink ? ink.inkData : undefined;
+            let zoomBasis = NumCast(this.props.container.props.Document.scale, 1);
+            let newCollection = Docs.FreeformDocument(selected, {
                 x: bounds.left,
                 y: bounds.top,
-                panx: 0,
-                pany: 0,
-                width: bounds.width,
-                height: bounds.height,
-                ink: inkData ? this.marqueeInkSelect(inkData) : undefined,
-                title: "a nested collection"
+                panX: 0,
+                panY: 0,
+                borderRounding: e.key === "e" ? -1 : undefined,
+                scale: zoomBasis,
+                width: bounds.width * zoomBasis,
+                height: bounds.height * zoomBasis,
+                ink: inkData ? new InkField(this.marqueeInkSelect(inkData)) : undefined,
+                title: "a nested collection",
             });
-            this.props.addDocument(newCollection, false);
+
             this.marqueeInkDelete(inkData);
-            // }, 100);
-            this.cleanupInteractions();
-            SelectionManager.DeselectAll();
-        }
+            // SelectionManager.DeselectAll();
+            if (e.key === "s" || e.key === "r" || e.key === "p") {
+                e.preventDefault();
+                let scrpt = this.props.getTransform().inverse().transformPoint(bounds.left, bounds.top);
+                let summary = Docs.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, backgroundColor: "yellow", title: "-summary-" });
+
+                if (e.key === "s" || e.key === "p") {
+                    summary.proto!.maximizeOnRight = true;
+                    newCollection.proto!.summaryDoc = summary;
+                    selected = [newCollection];
+                }
+                summary.proto!.summarizedDocs = new List<Doc>(selected);
+                summary.proto!.isButton = true;
+                selected.map(summarizedDoc => {
+                    let maxx = NumCast(summarizedDoc.x, undefined);
+                    let maxy = NumCast(summarizedDoc.y, undefined);
+                    let maxw = NumCast(summarizedDoc.width, undefined);
+                    let maxh = NumCast(summarizedDoc.height, undefined);
+                    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);
+                SelectionManager.DeselectAll();
+                this.props.selectDocuments([newCollection]);
+            }
+            this.cleanupInteractions(false);
+        } else
+            if (e.key === "s") {
+                // this._commandExecuted = true;
+                // e.stopPropagation();
+                // e.preventDefault();
+                // let bounds = this.Bounds;
+                // let selected = this.marqueeSelect();
+                // SelectionManager.DeselectAll();
+                // let summary = Docs.TextDocument({ x: bounds.left + bounds.width + 25, y: bounds.top, width: 300, height: 100, backgroundColor: "yellow", title: "-summary-" });
+                // this.props.addLiveTextDocument(summary);
+                // selected.forEach(select => Doc.MakeLink(summary.proto!, select.proto!));
+
+                // this.cleanupInteractions(false);
+            }
     }
     @action
     marqueeInkSelect(ink: Map<any, any>) {
@@ -199,19 +291,19 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
             let idata = new Map();
             ink.forEach((value: StrokeData, key: string, map: any) =>
                 !InkingCanvas.IntersectStrokeRect(value, this.Bounds) && idata.set(key, value));
-            this.props.container.props.Document.SetDataOnPrototype(KeyStore.Ink, idata, InkField);
+            Doc.SetOnPrototype(this.props.container.props.Document, "ink", new InkField(idata));
         }
     }
 
     marqueeSelect() {
         let selRect = this.Bounds;
-        let selection: Document[] = [];
+        let selection: Doc[] = [];
         this.props.activeDocuments().map(doc => {
-            var z = doc.GetNumber(KeyStore.Zoom, 1);
-            var x = doc.GetNumber(KeyStore.X, 0);
-            var y = doc.GetNumber(KeyStore.Y, 0);
-            var w = doc.Width() / z;
-            var h = doc.Height() / z;
+            var z = NumCast(doc.zoomBasis, 1);
+            var x = NumCast(doc.x);
+            var y = NumCast(doc.y);
+            var w = NumCast(doc.width) / z;
+            var h = NumCast(doc.height) / z;
             if (this.intersectRect({ left: x, top: y, width: w, height: h }, selRect)) {
                 selection.push(doc);
             }
@@ -229,7 +321,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
     }
 
     render() {
-        return <div className="marqueeView" onPointerDown={this.onPointerDown}>
+        return <div className="marqueeView" style={{ borderRadius: "inherit" }} onClick={this.onClick} onPointerDown={this.onPointerDown}>
             {this.props.children}
             {!this._visible ? (null) : this.marqueeDiv}
         </div>;
diff --git a/src/client/views/globalCssVariables.scss b/src/client/views/globalCssVariables.scss
index 5c8e9c8fc..cb4d1ad87 100644
--- a/src/client/views/globalCssVariables.scss
+++ b/src/client/views/globalCssVariables.scss
@@ -1,7 +1,7 @@
 @import url("https://fonts.googleapis.com/css?family=Noto+Sans:400,700|Crimson+Text:400,400i,700");
 // colors
 $light-color: #fcfbf7;
-$light-color-secondary: rgb(241, 239, 235);
+$light-color-secondary:#f1efeb;
 $main-accent: #61aaa3;
 // $alt-accent: #cdd5ec;
 // $alt-accent: #cdeceb;
@@ -23,7 +23,11 @@ $mainTextInput-zindex: 999; // then text input overlay so that it's context menu
 $docDecorations-zindex: 998; // then doc decorations appear over everything else
 $remoteCursors-zindex: 997; // ... not sure what level the remote cursors should go -- is this right?
 $COLLECTION_BORDER_WIDTH: 1;
+$MINIMIZED_ICON_SIZE:25;
+$MAX_ROW_HEIGHT: 44px;
 :export  {
     contextMenuZindex: $contextMenu-zindex;
     COLLECTION_BORDER_WIDTH: $COLLECTION_BORDER_WIDTH;
+    MINIMIZED_ICON_SIZE: $MINIMIZED_ICON_SIZE;
+    MAX_ROW_HEIGHT: $MAX_ROW_HEIGHT;
 }
\ No newline at end of file
diff --git a/src/client/views/globalCssVariables.scss.d.ts b/src/client/views/globalCssVariables.scss.d.ts
index e874b815d..9788d31f7 100644
--- a/src/client/views/globalCssVariables.scss.d.ts
+++ b/src/client/views/globalCssVariables.scss.d.ts
@@ -1,7 +1,9 @@
 
 interface IGlobalScss {
     contextMenuZindex: string;  // context menu shows up over everything
-    COLLECTION_BORDER_WIDTH: number;
+    COLLECTION_BORDER_WIDTH: string;
+    MINIMIZED_ICON_SIZE: string;
+    MAX_ROW_HEIGHT: string;
 }
 declare const globalCssVariables: IGlobalScss;
 
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 1493ff25b..be12dced3 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -1,36 +1,19 @@
 import React = require("react");
 import { FieldViewProps, FieldView } from './FieldView';
-import { FieldWaiting } from '../../../fields/Field';
 import { observer } from "mobx-react";
-import { ContextMenu } from "../../views/ContextMenu";
-import { observable, action } from 'mobx';
-import { KeyStore } from '../../../fields/KeyStore';
-import { AudioField } from "../../../fields/AudioField";
 import "./AudioBox.scss";
-import { NumberField } from "../../../fields/NumberField";
+import { Cast } from "../../../new_fields/Types";
+import { AudioField } from "../../../new_fields/URLField";
 
+const defaultField: AudioField = new AudioField(new URL("http://techslides.com/demos/samples/sample.mp3"));
 @observer
 export class AudioBox extends React.Component<FieldViewProps> {
 
     public static LayoutString() { return FieldView.LayoutString(AudioBox); }
 
-    constructor(props: FieldViewProps) {
-        super(props);
-    }
-
-
-
-    componentDidMount() {
-    }
-
-    componentWillUnmount() {
-    }
-
-
     render() {
-        let field = this.props.Document.Get(this.props.fieldKey);
-        let path = field === FieldWaiting ? "http://techslides.com/demos/samples/sample.mp3" :
-            field instanceof AudioField ? field.Data.href : "http://techslides.com/demos/samples/sample.mp3";
+        let field = Cast(this.props.Document[this.props.fieldKey], AudioField, defaultField);
+        let path = field.url.href;
 
         return (
             <div>
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 8cf7a0dd2..b05f2eea2 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -1,62 +1,79 @@
-import { computed, trace } from "mobx";
+import { computed, trace, action, reaction, IReactionDisposer } from "mobx";
 import { observer } from "mobx-react";
-import { KeyStore } from "../../../fields/KeyStore";
-import { NumberField } from "../../../fields/NumberField";
 import { Transform } from "../../util/Transform";
-import { DocumentView, DocumentViewProps } from "./DocumentView";
+import { DocumentView, DocumentViewProps, positionSchema } from "./DocumentView";
 import "./DocumentView.scss";
 import React = require("react");
-import { OmitKeys } from "../../../Utils";
+import { DocComponent } from "../DocComponent";
+import { createSchema, makeInterface, listSpec } from "../../../new_fields/Schema";
+import { FieldValue, Cast, NumCast, BoolCast, StrCast } from "../../../new_fields/Types";
+import { OmitKeys, Utils } from "../../../Utils";
+import { SelectionManager } from "../../util/SelectionManager";
+import { Doc, DocListCast, HeightSym } from "../../../new_fields/Doc";
+import { List } from "../../../new_fields/List";
+import { CollectionDockingView } from "../collections/CollectionDockingView";
+import { undoBatch, UndoManager } from "../../util/UndoManager";
 
 export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
 }
 
+const schema = createSchema({
+    zoomBasis: "number",
+    zIndex: "number"
+});
+
+//TODO Types: The import order is wrong, so positionSchema is undefined
+type FreeformDocument = makeInterface<[typeof schema, typeof positionSchema]>;
+const FreeformDocument = makeInterface(schema, positionSchema);
+
 @observer
-export class CollectionFreeFormDocumentView extends React.Component<CollectionFreeFormDocumentViewProps> {
+export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps, FreeformDocument>(FreeformDocument) {
     private _mainCont = React.createRef<HTMLDivElement>();
+    private _downX: number = 0;
+    private _downY: number = 0;
+    _bringToFrontDisposer?: IReactionDisposer;
 
-    @computed
-    get transform(): string {
-        return `scale(${this.props.ContentScaling()}, ${this.props.ContentScaling()}) translate(${this.props.Document.GetNumber(KeyStore.X, 0)}px, ${this.props.Document.GetNumber(KeyStore.Y, 0)}px) scale(${this.zoom}, ${this.zoom}) `;
+    @computed get transform() {
+        return `scale(${this.props.ContentScaling()}, ${this.props.ContentScaling()}) translate(${this.X}px, ${this.Y}px) scale(${this.zoom}, ${this.zoom}) `;
     }
 
-    @computed get zoom(): number { return 1 / this.props.Document.GetNumber(KeyStore.Zoom, 1); }
-    @computed get zIndex(): number { return this.props.Document.GetNumber(KeyStore.ZIndex, 0); }
-    @computed get width(): number { return this.props.Document.Width(); }
-    @computed get height(): number { return this.props.Document.Height(); }
-    @computed get nativeWidth(): number { return this.props.Document.GetNumber(KeyStore.NativeWidth, 0); }
-    @computed get nativeHeight(): number { return this.props.Document.GetNumber(KeyStore.NativeHeight, 0); }
+    @computed get X() { return FieldValue(this.Document.x, 0); }
+    @computed get Y() { return FieldValue(this.Document.y, 0); }
+    @computed get zoom(): number { return 1 / FieldValue(this.Document.zoomBasis, 1); }
+    @computed get nativeWidth(): number { return FieldValue(this.Document.nativeWidth, 0); }
+    @computed get nativeHeight(): number { return FieldValue(this.Document.nativeHeight, 0); }
+    @computed get width(): number { return FieldValue(this.Document.width, 0); }
+    @computed get height(): number { return FieldValue(this.Document.height, 0); }
+    @computed get zIndex(): number { return FieldValue(this.Document.zIndex, 0); }
 
     set width(w: number) {
-        this.props.Document.SetData(KeyStore.Width, w, NumberField);
+        this.Document.width = w;
         if (this.nativeWidth && this.nativeHeight) {
-            this.props.Document.SetNumber(KeyStore.Height, this.nativeHeight / this.nativeWidth * w);
+            this.Document.height = this.nativeHeight / this.nativeWidth * w;
         }
     }
-
     set height(h: number) {
-        this.props.Document.SetData(KeyStore.Height, h, NumberField);
+        this.Document.height = h;
         if (this.nativeWidth && this.nativeHeight) {
-            this.props.Document.SetNumber(KeyStore.Width, this.nativeWidth / this.nativeHeight * h);
+            this.Document.width = this.nativeWidth / this.nativeHeight * h;
         }
     }
-
     set zIndex(h: number) {
-        this.props.Document.SetData(KeyStore.ZIndex, h, NumberField);
+        this.Document.zIndex = h;
     }
 
-    getTransform = (): Transform =>
-        this.props.ScreenToLocalTransform()
-            .translate(-this.props.Document.GetNumber(KeyStore.X, 0), -this.props.Document.GetNumber(KeyStore.Y, 0))
-            .scale(1 / this.contentScaling()).scale(1 / this.zoom)
-
     contentScaling = () => this.nativeWidth > 0 ? this.width / this.nativeWidth : 1;
-    panelWidth = () => this.props.Document.GetBoolean(KeyStore.Minimized, false) ? 10 : this.props.PanelWidth();
-    panelHeight = () => this.props.Document.GetBoolean(KeyStore.Minimized, false) ? 10 : this.props.PanelHeight();
+    panelWidth = () => this.props.PanelWidth();
+    panelHeight = () => this.props.PanelHeight();
+    toggleMinimized = async () => this.toggleIcon(await DocListCast(this.props.Document.maximizedDocs));
+    getTransform = (): Transform => this.props.ScreenToLocalTransform()
+        .translate(-this.X, -this.Y)
+        .scale(1 / this.contentScaling()).scale(1 / this.zoom)
 
     @computed
     get docView() {
-        return <DocumentView {...OmitKeys(this.props, ['zoomFade'])}
+        return <DocumentView {...OmitKeys(this.props, ['zoomFade']).omit}
+            toggleMinimized={this.toggleMinimized}
             ContentScaling={this.contentScaling}
             ScreenToLocalTransform={this.getTransform}
             PanelWidth={this.panelWidth}
@@ -64,30 +81,173 @@ export class CollectionFreeFormDocumentView extends React.Component<CollectionFr
         />;
     }
 
+    componentDidMount() {
+        this._bringToFrontDisposer = reaction(() => this.props.Document.isIconAnimating, (values) => {
+            this.props.bringToFront(this.props.Document);
+            if (values instanceof List) {
+                let scrpt = this.props.ScreenToLocalTransform().transformPoint(values[0], values[1]);
+                this.animateBetweenIcon(true, scrpt, [values[2], values[3]], values[4], values[5], values[6], this.props.Document, values[7] ? true : false);
+            }
+        }, { fireImmediately: true });
+    }
+
+    componentWillUnmount() {
+        if (this._bringToFrontDisposer) this._bringToFrontDisposer();
+    }
+
+    animateBetweenIcon(first: boolean, icon: number[], targ: number[], width: number, height: number, stime: number, target: Doc, maximizing: boolean) {
+        if (first) {
+            if (maximizing) target.width = target.height = 1;
+        }
+        setTimeout(() => {
+            let now = Date.now();
+            let progress = Math.min(1, (now - stime) / 200);
+            let pval = maximizing ?
+                [icon[0] + (targ[0] - icon[0]) * progress, icon[1] + (targ[1] - icon[1]) * progress] :
+                [targ[0] + (icon[0] - targ[0]) * progress, targ[1] + (icon[1] - targ[1]) * progress];
+            target.width = maximizing ? 25 + (width - 25) * progress : width + (25 - width) * progress;
+            target.height = maximizing ? 25 + (height - 25) * progress : height + (25 - height) * progress;
+            target.x = pval[0];
+            target.y = pval[1];
+            if (now < stime + 200) {
+                this.animateBetweenIcon(false, icon, targ, width, height, stime, target, maximizing);
+            }
+            else {
+                if (!maximizing) {
+                    target.isMinimized = true;
+                    target.x = targ[0];
+                    target.y = targ[1];
+                    target.width = width;
+                    target.height = height;
+                }
+                target.isIconAnimating = undefined;
+            }
+        },
+            2);
+    }
+    @action
+    public toggleIcon = async (maximizedDocs: Doc[] | undefined): Promise<void> => {
+        SelectionManager.DeselectAll();
+        let isMinimized: boolean | undefined;
+        let minimizedDoc: Doc | undefined = this.props.Document;
+        if (!maximizedDocs) {
+            minimizedDoc = await Cast(this.props.Document.minimizedDoc, Doc);
+            if (minimizedDoc) maximizedDocs = await DocListCast(minimizedDoc.maximizedDocs);
+        }
+        if (minimizedDoc && maximizedDocs) {
+            let minimizedTarget = minimizedDoc;
+            if (!CollectionFreeFormDocumentView._undoBatch) {
+                CollectionFreeFormDocumentView._undoBatch = UndoManager.StartBatch("iconAnimating");
+            }
+            maximizedDocs.forEach(maximizedDoc => {
+                let iconAnimating = Cast(maximizedDoc.isIconAnimating, List);
+                if (!iconAnimating || (Date.now() - iconAnimating[6] > 1000)) {
+                    if (isMinimized === undefined) {
+                        isMinimized = BoolCast(maximizedDoc.isMinimized, false);
+                    }
+                    let minx = NumCast(minimizedTarget.x, undefined) + NumCast(minimizedTarget.width, undefined) * this.getTransform().Scale * this.contentScaling() / 2;
+                    let miny = NumCast(minimizedTarget.y, undefined) + NumCast(minimizedTarget.height, undefined) * this.getTransform().Scale * this.contentScaling() / 2;
+                    let maxx = NumCast(maximizedDoc.x, undefined);
+                    let maxy = NumCast(maximizedDoc.y, undefined);
+                    let maxw = NumCast(maximizedDoc.width, undefined);
+                    let maxh = NumCast(maximizedDoc.height, undefined);
+                    if (minx !== undefined && miny !== undefined && maxx !== undefined && maxy !== undefined &&
+                        maxw !== undefined && maxh !== undefined) {
+                        let scrpt = this.props.ScreenToLocalTransform().inverse().transformPoint(minx, miny);
+                        if (isMinimized) maximizedDoc.isMinimized = false;
+                        maximizedDoc.isIconAnimating = new List<number>([scrpt[0], scrpt[1], maxx, maxy, maxw, maxh, Date.now(), isMinimized ? 1 : 0])
+                    }
+                }
+            });
+            setTimeout(() => {
+                CollectionFreeFormDocumentView._undoBatch && CollectionFreeFormDocumentView._undoBatch.end();
+                CollectionFreeFormDocumentView._undoBatch = undefined;
+            }, 500);
+        }
+    }
+    static _undoBatch?: UndoManager.Batch = undefined;
+    onPointerDown = (e: React.PointerEvent): void => {
+        this._downX = e.clientX;
+        this._downY = e.clientY;
+        // e.stopPropagation();
+    }
+    onClick = async (e: React.MouseEvent) => {
+        e.stopPropagation();
+        let altKey = e.altKey;
+        if (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD &&
+            Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
+            let isBullet = (e.target as any).id === "isBullet";
+            let isIcon = StrCast(this.props.Document.layout).indexOf("IconBox") !== -1;
+            if (BoolCast(this.props.Document.isButton, false) || isBullet) {
+                let maximizedDocs = await DocListCast(isBullet ? this.props.Document.subBulletDocs : isIcon ? this.props.Document.maximizedDocs : this.props.Document.summarizedDocs);
+                if (maximizedDocs) {   // bcz: need a better way to associate behaviors with click events on widget-documents
+                    if ((altKey && !this.props.Document.maximizeOnRight) || (!altKey && this.props.Document.maximizeOnRight)) {
+                        let dataDocs = await DocListCast(CollectionDockingView.Instance.props.Document.data);
+                        if (dataDocs) {
+                            SelectionManager.DeselectAll();
+                            maximizedDocs.forEach(maxDoc => {
+                                maxDoc.isMinimized = false;
+                                if (!dataDocs || dataDocs.indexOf(maxDoc) == -1) {
+                                    CollectionDockingView.Instance.AddRightSplit(maxDoc);
+                                } else {
+                                    CollectionDockingView.Instance.CloseRightSplit(maxDoc);
+                                }
+                            });
+                        }
+                    } else {
+                        this.props.addDocument && maximizedDocs.forEach(async maxDoc => this.props.addDocument!(await maxDoc, false));
+                        this.toggleIcon(maximizedDocs);
+                    }
+                }
+            }
+        }
+    }
+
+    onPointerEnter = (e: React.PointerEvent): void => { this.props.Document.libraryBrush = true; };
+    onPointerLeave = (e: React.PointerEvent): void => { this.props.Document.libraryBrush = false; };
+
+    borderRounding = () => {
+        let br = NumCast(this.props.Document.borderRounding);
+        return br >= 0 ? br :
+            NumCast(this.props.Document.nativeWidth) === 0 ?
+                Math.min(this.props.PanelWidth(), this.props.PanelHeight())
+                : Math.min(this.Document.nativeWidth || 0, this.Document.nativeHeight || 0);
+    }
+
     render() {
+        let maximizedDoc = FieldValue(Cast(this.props.Document.maximizedDocs, listSpec(Doc)));
         let zoomFade = 1;
-        // //var zoom = doc.GetNumber(KeyStore.Zoom, 1);
-        // let transform = (this.props.ScreenToLocalTransform().scale(this.props.ContentScaling())).inverse();
-        // var [sptX, sptY] = transform.transformPoint(0, 0);
-        // let [bptX, bptY] = transform.transformPoint(this.props.PanelWidth(), this.props.PanelHeight());
-        // let w = bptX - sptX;
-        // //zoomFade = area < 100 || area > 800 ? Math.max(0, Math.min(1, 2 - 5 * (zoom < this.scale ? this.scale / zoom : zoom / this.scale))) : 1;
-        // let fadeUp = .75 * 1800;
-        // let fadeDown = .075 * 1800;
-        // zoomFade = w < fadeDown || w > fadeUp ? Math.max(0, Math.min(1, 2 - (w < fadeDown ? fadeDown / w : w / fadeUp))) : 1;
+        //var zoom = doc.GetNumber(KeyStore.ZoomBasis, 1);
+        let transform = this.getTransform().scale(this.contentScaling()).inverse();
+        var [sptX, sptY] = transform.transformPoint(0, 0);
+        let [bptX, bptY] = transform.transformPoint(this.props.PanelWidth(), this.props.PanelHeight());
+        let w = bptX - sptX;
+        //zoomFade = area < 100 || area > 800 ? Math.max(0, Math.min(1, 2 - 5 * (zoom < this.scale ? this.scale / zoom : zoom / this.scale))) : 1;
+        const screenWidth = Math.min(50 * NumCast(this.props.Document.nativeWidth, 0), 1800);
+        let fadeUp = .75 * screenWidth;
+        let fadeDown = (maximizedDoc ? .0075 : .075) * screenWidth;
+        zoomFade = w < fadeDown  /* || w > fadeUp */ ? Math.max(0.1, Math.min(1, 2 - (w < fadeDown ? fadeDown / w : w / fadeUp))) : 1;
 
         return (
-            <div className="collectionFreeFormDocumentView-container" ref={this._mainCont} style={{
-                opacity: zoomFade,
-                transformOrigin: "left top",
-                transform: this.transform,
-                pointerEvents: "all",
-                width: this.width,
-                height: this.height,
-                position: "absolute",
-                zIndex: this.zIndex,
-                backgroundColor: "transparent"
-            }} >
+            <div className="collectionFreeFormDocumentView-container" ref={this._mainCont}
+                onPointerDown={this.onPointerDown}
+                onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave} onPointerOver={this.onPointerEnter}
+                onClick={this.onClick}
+                style={{
+                    outlineColor: "black",
+                    outlineStyle: "dashed",
+                    outlineWidth: BoolCast(this.props.Document.libraryBrush, false) ? `${0.5 / this.contentScaling()}px` : "0px",
+                    opacity: zoomFade,
+                    borderRadius: `${this.borderRounding()}px`,
+                    transformOrigin: "left top",
+                    transform: this.transform,
+                    pointerEvents: (zoomFade < 0.09 ? "none" : "all"),
+                    width: this.width,
+                    height: this.height,
+                    position: "absolute",
+                    zIndex: this.zIndex,
+                    backgroundColor: "transparent"
+                }} >
                 {this.docView}
             </div>
         );
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 76f852601..f404b7bc6 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -1,9 +1,5 @@
-import { computed } from "mobx";
+import { computed, trace } from "mobx";
 import { observer } from "mobx-react";
-import { FieldWaiting, Field } from "../../../fields/Field";
-import { Key } from "../../../fields/Key";
-import { KeyStore } from "../../../fields/KeyStore";
-import { ListField } from "../../../fields/ListField";
 import { CollectionDockingView } from "../collections/CollectionDockingView";
 import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
 import { CollectionPDFView } from "../collections/CollectionPDFView";
@@ -15,56 +11,84 @@ import { DocumentViewProps } from "./DocumentView";
 import "./DocumentView.scss";
 import { FormattedTextBox } from "./FormattedTextBox";
 import { ImageBox } from "./ImageBox";
+import { IconBox } from "./IconBox";
 import { KeyValueBox } from "./KeyValueBox";
 import { PDFBox } from "./PDFBox";
 import { VideoBox } from "./VideoBox";
+import { FieldView } from "./FieldView";
 import { WebBox } from "./WebBox";
 import { HistogramBox } from "../../northstar/dash-nodes/HistogramBox";
 import React = require("react");
-import { Document } from "../../../fields/Document";
 import { FieldViewProps } from "./FieldView";
 import { Without, OmitKeys } from "../../../Utils";
+import { Cast, StrCast } from "../../../new_fields/Types";
+import { List } from "../../../new_fields/List";
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
 type BindingProps = Without<FieldViewProps, 'fieldKey'>;
 export interface JsxBindings {
     props: BindingProps;
-    [keyName: string]: BindingProps | Field;
 }
 
+class ObserverJsxParser1 extends JsxParser {
+    constructor(props: any) {
+        super(props);
+        observer(this as any);
+    }
+}
+
+const ObserverJsxParser: typeof JsxParser = ObserverJsxParser1 as any;
+
 @observer
 export class DocumentContentsView extends React.Component<DocumentViewProps & {
     isSelected: () => boolean,
     select: (ctrl: boolean) => void,
-    layoutKey: Key
+    layoutKey: string
 }> {
-    @computed get layout(): string { return this.props.Document.GetText(this.props.layoutKey, "<p>Error loading layout data</p>"); }
-    @computed get layoutKeys(): Key[] { return this.props.Document.GetData(KeyStore.LayoutKeys, ListField, new Array<Key>()); }
-    @computed get layoutFields(): Key[] { return this.props.Document.GetData(KeyStore.LayoutFields, ListField, new Array<Key>()); }
-
+    @computed get layout(): string { return Cast(this.props.Document[this.props.layoutKey], "string", this.props.layoutKey === "layout" ? "<p>Error loading layout data</p>" : ""); }
 
     CreateBindings(): JsxBindings {
-        let bindings: JsxBindings = { props: OmitKeys(this.props, ['parentActive'], (obj: any) => obj.active = this.props.parentActive) };
+        return { props: OmitKeys(this.props, ['parentActive'], (obj: any) => obj.active = this.props.parentActive).omit };
+    }
 
-        for (const key of this.layoutKeys) {
-            bindings[key.Name + "Key"] = key; // this maps string values of the form <keyname>Key to an actual key Kestore.keyname  e.g,   "DataKey" => KeyStore.Data
+    @computed get templates(): List<string> {
+        let field = this.props.Document.templates;
+        if (field && field instanceof List) {
+            return field;
         }
-        for (const key of this.layoutFields) {
-            let field = this.props.Document.Get(key);
-            bindings[key.Name] = field && field !== FieldWaiting ? field.GetValue() : field;
+        return new List<string>();
+    }
+    @computed get finalLayout() {
+        const baseLayout = this.layout;
+        let base = baseLayout;
+        let layout = baseLayout;
+
+        // bcz: templates are intended only for a document's primary layout or overlay (not background).  However, 
+        // a DocumentContentsView is used to render  annotation overlays, so we detect that here 
+        // by checking the layoutKey.  This should probably be moved into
+        // a prop so that the overlay can explicitly turn off templates.
+        if ((this.props.layoutKey === "overlayLayout" && StrCast(this.props.Document.layout).indexOf("CollectionView") !== -1) ||
+            (this.props.layoutKey === "layout" && StrCast(this.props.Document.layout).indexOf("CollectionView") === -1)) {
+            this.templates.forEach(template => {
+                let self = this;
+                function convertConstantsToNative(match: string, offset: number, x: string) {
+                    let px = Number(match.replace("px", ""));
+                    return `${px * self.props.ScreenToLocalTransform().Scale}px`;
+                }
+                let nativizedTemplate = template.replace(/([0-9]+)px/g, convertConstantsToNative);
+                layout = nativizedTemplate.replace("{layout}", base);
+                base = layout;
+            });
         }
-        return bindings;
+        return layout;
     }
 
     render() {
-        let lkeys = this.props.Document.GetT(KeyStore.LayoutKeys, ListField);
-        if (!lkeys || lkeys === FieldWaiting) {
-            return <p>Error loading layout keys</p>;
-        }
-        return <JsxParser
-            components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox }}
+        if (!this.layout && (this.props.layoutKey !== "overlayLayout" || !this.templates.length)) return (null);
+        return <ObserverJsxParser
+            components={{ FormattedTextBox, ImageBox, IconBox, FieldView, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox }}
             bindings={this.CreateBindings()}
-            jsx={this.layout}
+            jsx={this.finalLayout}
             showWarnings={true}
             onError={(test: any) => { console.log(test); }}
         />;
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index 690ee50e8..7c72fb6e6 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -4,7 +4,7 @@
   position: inherit;
   top: 0;
   left:0;
-  background: $light-color; //overflow: hidden;
+ // background: $light-color; //overflow: hidden;
   transform-origin: left top;
 
   &.minimized {
@@ -13,7 +13,6 @@
   }
 
   .top {
-    background: #232323;
     height: 20px;
     cursor: pointer;
   }
@@ -31,18 +30,4 @@
 }
 .documentView-node-topmost {
     background: white;
-}
-
-.minimized-box {
-  height: 10px;
-  width: 10px;
-  border-radius: 2px;
-  background: $dark-color;
-  transform-origin: left top;
-}
-
-.minimized-box:hover {
-  background: $main-accent;
-  transform: scale(1.15);
-  cursor: pointer;
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index d74f9fc57..25a75904b 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,17 +1,9 @@
-import { action, computed, runInAction } from "mobx";
+import { action, computed, runInAction, reaction, IReactionDisposer } from "mobx";
 import { observer } from "mobx-react";
-import { BooleanField } from "../../../fields/BooleanField";
-import { Document } from "../../../fields/Document";
-import { Field, FieldWaiting, Opt } from "../../../fields/Field";
-import { Key } from "../../../fields/Key";
-import { KeyStore } from "../../../fields/KeyStore";
-import { ListField } from "../../../fields/ListField";
-import { TextField } from "../../../fields/TextField";
-import { ServerUtils } from "../../../server/ServerUtil";
 import { emptyFunction, Utils } from "../../../Utils";
-import { Documents } from "../../documents/Documents";
+import { Docs } from "../../documents/Documents";
 import { DocumentManager } from "../../util/DocumentManager";
-import { DragManager } from "../../util/DragManager";
+import { DragManager, dropActionType } from "../../util/DragManager";
 import { SelectionManager } from "../../util/SelectionManager";
 import { Transform } from "../../util/Transform";
 import { undoBatch, UndoManager } from "../../util/UndoManager";
@@ -20,14 +12,35 @@ import { CollectionPDFView } from "../collections/CollectionPDFView";
 import { CollectionVideoView } from "../collections/CollectionVideoView";
 import { CollectionView } from "../collections/CollectionView";
 import { ContextMenu } from "../ContextMenu";
+import { Template, Templates } from "./../Templates";
 import { DocumentContentsView } from "./DocumentContentsView";
 import "./DocumentView.scss";
 import React = require("react");
+import { Opt, Doc, WidthSym, HeightSym, DocListCast } from "../../../new_fields/Doc";
+import { DocComponent } from "../DocComponent";
+import { createSchema, makeInterface, listSpec } from "../../../new_fields/Schema";
+import { FieldValue, StrCast, BoolCast, Cast } from "../../../new_fields/Types";
+import { List } from "../../../new_fields/List";
+import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
+import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
+import { DocServer } from "../../DocServer";
+import { Id } from "../../../new_fields/RefField";
+import { PresentationView } from "../PresentationView";
 
+const linkSchema = createSchema({
+    title: "string",
+    linkDescription: "string",
+    linkTags: "string",
+    linkedTo: Doc,
+    linkedFrom: Doc
+});
+
+type LinkDoc = makeInterface<[typeof linkSchema]>;
+const LinkDoc = makeInterface(linkSchema);
 
 export interface DocumentViewProps {
     ContainingCollectionView: Opt<CollectionView | CollectionPDFView | CollectionVideoView>;
-    Document: Document;
+    Document: Doc;
     addDocument?: (doc: Document, allowDuplicates?: boolean) => boolean;
     removeDocument?: (doc: Document) => boolean;
     moveDocument?: (doc: Document, targetCollection: Document, addDocument: (document: Document) => boolean) => boolean;
@@ -40,49 +53,34 @@ export interface DocumentViewProps {
     selectOnLoad: boolean;
     parentActive: () => boolean;
     whenActiveChanged: (isActive: boolean) => void;
-}
-export interface JsxArgs extends DocumentViewProps {
-    Keys: { [name: string]: Key };
-    Fields: { [name: string]: Field };
+    toggleMinimized: () => void;
+    bringToFront: (doc: Doc) => void;
 }
 
-/*
-This function is pretty much a hack that lets us fill out the fields in JsxArgs with something that
-jsx-to-string can recover the jsx from
-Example usage of this function:
-    public static LayoutString() {
-        let args = FakeJsxArgs(["Data"]);
-        return jsxToString(
-            <CollectionFreeFormView
-                doc={args.Document}
-                fieldKey={args.Keys.Data}
-                DocumentViewForField={args.DocumentView} />,
-            { useFunctionCode: true, functionNameOnly: true }
-        )
-    }
-*/
-export function FakeJsxArgs(keys: string[], fields: string[] = []): JsxArgs {
-    let Keys: { [name: string]: any } = {};
-    let Fields: { [name: string]: any } = {};
-    for (const key of keys) {
-        Object.defineProperty(emptyFunction, "name", { value: key + "Key" });
-        Keys[key] = emptyFunction;
-    }
-    for (const field of fields) {
-        Object.defineProperty(emptyFunction, "name", { value: field });
-        Fields[field] = emptyFunction;
-    }
-    let args: JsxArgs = {
-        Document: function Document() { },
-        DocumentView: function DocumentView() { },
-        Keys,
-        Fields
-    } as any;
-    return args;
-}
+const schema = createSchema({
+    layout: "string",
+    nativeWidth: "number",
+    nativeHeight: "number",
+    backgroundColor: "string"
+});
+
+export const positionSchema = createSchema({
+    nativeWidth: "number",
+    nativeHeight: "number",
+    width: "number",
+    height: "number",
+    x: "number",
+    y: "number",
+});
+
+export type PositionDocument = makeInterface<[typeof positionSchema]>;
+export const PositionDocument = makeInterface(positionSchema);
+
+type Document = makeInterface<[typeof schema]>;
+const Document = makeInterface(schema);
 
 @observer
-export class DocumentView extends React.Component<DocumentViewProps> {
+export class DocumentView extends DocComponent<DocumentViewProps, Document>(Document) {
     private _downX: number = 0;
     private _downY: number = 0;
     private _mainCont = React.createRef<HTMLDivElement>();
@@ -91,43 +89,39 @@ export class DocumentView extends React.Component<DocumentViewProps> {
     public get ContentDiv() { return this._mainCont.current; }
     @computed get active(): boolean { return SelectionManager.IsSelected(this) || this.props.parentActive(); }
     @computed get topMost(): boolean { return this.props.isTopMost; }
-    @computed get layout(): string { return this.props.Document.GetText(KeyStore.Layout, "<p>Error loading layout data</p>"); }
-    @computed get layoutKeys(): Key[] { return this.props.Document.GetData(KeyStore.LayoutKeys, ListField, new Array<Key>()); }
-    @computed get layoutFields(): Key[] { return this.props.Document.GetData(KeyStore.LayoutFields, ListField, new Array<Key>()); }
-
-    onPointerDown = (e: React.PointerEvent): void => {
-        this._downX = e.clientX;
-        this._downY = e.clientY;
-        if (e.button === 2 && !this.isSelected()) {
-            return;
-        }
-        if (e.shiftKey && e.buttons === 2) {
-            if (this.props.isTopMost) {
-                this.startDragging(e.pageX, e.pageY, e.altKey || e.ctrlKey);
-            } else {
-                CollectionDockingView.Instance.StartOtherDrag([this.props.Document], e);
-            }
-            e.stopPropagation();
-        } else {
-            if (this.active) {
-                e.stopPropagation();
-                document.removeEventListener("pointermove", this.onPointerMove);
-                document.addEventListener("pointermove", this.onPointerMove);
-                document.removeEventListener("pointerup", this.onPointerUp);
-                document.addEventListener("pointerup", this.onPointerUp);
-            }
+    @computed get templates(): List<string> {
+        let field = this.props.Document.templates;
+        if (field && field instanceof List) {
+            return field;
         }
+        return new List<string>();
     }
+    set templates(templates: List<string>) { this.props.Document.templates = templates; }
+    screenRect = (): ClientRect | DOMRect => this._mainCont.current ? this._mainCont.current.getBoundingClientRect() : new DOMRect();
 
+    _reactionDisposer?: IReactionDisposer;
+    @action
     componentDidMount() {
         if (this._mainCont.current) {
             this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, {
                 handlers: { drop: this.drop.bind(this) }
             });
         }
-        runInAction(() => DocumentManager.Instance.DocumentViews.push(this));
+        // bcz: kind of ugly .. setup a reaction to update the title of a summary document's target (maximizedDocs) whenver the summary doc's title changes
+        this._reactionDisposer = reaction(() => [this.props.Document.maximizedDocs, this.props.Document.summaryDoc, this.props.Document.summaryDoc instanceof Doc ? this.props.Document.summaryDoc.title : ""],
+            async () => {
+                let maxDoc = await DocListCast(this.props.Document.maximizedDocs);
+                if (maxDoc && StrCast(this.props.Document.layout).indexOf("IconBox") !== -1) {
+                    this.props.Document.title = (maxDoc && maxDoc.length === 1 ? maxDoc[0].title + ".icon" : "");
+                }
+                let sumDoc = Cast(this.props.Document.summaryDoc, Doc);
+                if (sumDoc instanceof Doc) {
+                    this.props.Document.title = sumDoc.title + ".expanded";
+                }
+            }, { fireImmediately: true });
+        DocumentManager.Instance.DocumentViews.push(this);
     }
-
+    @action
     componentDidUpdate() {
         if (this._dropDisposer) {
             this._dropDisposer();
@@ -138,142 +132,151 @@ export class DocumentView extends React.Component<DocumentViewProps> {
             });
         }
     }
-
+    @action
     componentWillUnmount() {
-        if (this._dropDisposer) {
-            this._dropDisposer();
-        }
-        runInAction(() => DocumentManager.Instance.DocumentViews.splice(DocumentManager.Instance.DocumentViews.indexOf(this), 1));
+        if (this._reactionDisposer) this._reactionDisposer();
+        if (this._dropDisposer) this._dropDisposer();
+        DocumentManager.Instance.DocumentViews.splice(DocumentManager.Instance.DocumentViews.indexOf(this), 1);
+    }
+
+    stopPropagation = (e: React.SyntheticEvent) => {
+        e.stopPropagation();
     }
 
-    startDragging(x: number, y: number, dropAliasOfDraggedDoc: boolean) {
+    startDragging(x: number, y: number, dropAction: dropActionType, dragSubBullets: boolean) {
         if (this._mainCont.current) {
-            const [left, top] = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
-            let dragData = new DragManager.DocumentDragData([this.props.Document]);
-            dragData.aliasOnDrop = dropAliasOfDraggedDoc;
-            dragData.xOffset = x - left;
-            dragData.yOffset = y - top;
+            let allConnected = dragSubBullets ? [this.props.Document, ...Cast(this.props.Document.subBulletDocs, listSpec(Doc), []).filter(d => d).map(d => d as Doc)] : [this.props.Document];
+            const [left, top] = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint(0, 0);
+            let dragData = new DragManager.DocumentDragData(allConnected);
+            const [xoff, yoff] = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).transformDirection(x - left, y - top);
+            dragData.dropAction = dropAction;
+            dragData.xOffset = xoff;
+            dragData.yOffset = yoff;
             dragData.moveDocument = this.props.moveDocument;
             DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, {
                 handlers: {
                     dragComplete: action(emptyFunction)
                 },
-                hideSource: !dropAliasOfDraggedDoc
+                hideSource: !dropAction
             });
         }
     }
 
-    onPointerMove = (e: PointerEvent): void => {
-        if (e.cancelBubble) {
+    onClick = (e: React.MouseEvent): void => {
+        if (CurrentUserUtils.MainDocId !== this.props.Document[Id] &&
+            (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD &&
+                Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) {
+            SelectionManager.SelectDoc(this, e.ctrlKey);
+        }
+    }
+    _hitIsBullet = false;
+    onPointerDown = (e: React.PointerEvent): void => {
+        this._downX = e.clientX;
+        this._downY = e.clientY;
+        if (CollectionFreeFormView.RIGHT_BTN_DRAG && (e.button === 2 || (e.button === 0 && e.altKey)) && !this.isSelected()) {
             return;
         }
-        if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) {
+        this._hitIsBullet = (e.target && (e.target as any).id === "isBullet");
+        if (e.shiftKey && e.buttons === 1) {
+            if (this.props.isTopMost) {
+                this.startDragging(e.pageX, e.pageY, e.altKey || e.ctrlKey ? "alias" : undefined, this._hitIsBullet);
+            } else if (this.props.Document) {
+                CollectionDockingView.Instance.StartOtherDrag([Doc.MakeAlias(this.props.Document)], e);
+            }
+            e.stopPropagation();
+        } else if (this.active) {
             document.removeEventListener("pointermove", this.onPointerMove);
+            document.addEventListener("pointermove", this.onPointerMove);
             document.removeEventListener("pointerup", this.onPointerUp);
-            if (!this.topMost || e.buttons === 2 || e.altKey) {
-                this.startDragging(this._downX, this._downY, e.ctrlKey || e.altKey);
+            document.addEventListener("pointerup", this.onPointerUp);
+        }
+    }
+    onPointerMove = (e: PointerEvent): void => {
+        if (!e.cancelBubble) {
+            if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) {
+                document.removeEventListener("pointermove", this.onPointerMove);
+                document.removeEventListener("pointerup", this.onPointerUp);
+                if (!e.altKey && !this.topMost && (!CollectionFreeFormView.RIGHT_BTN_DRAG && e.buttons === 1) || (CollectionFreeFormView.RIGHT_BTN_DRAG && e.buttons === 2)) {
+                    this.startDragging(this._downX, this._downY, e.ctrlKey || e.altKey ? "alias" : undefined, this._hitIsBullet);
+                }
             }
+            e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document'  however it does mark the event as cancelBubble=true which we test for in the move event handlers
+            e.preventDefault();
         }
-        e.stopPropagation();
-        e.preventDefault();
     }
     onPointerUp = (e: PointerEvent): void => {
         document.removeEventListener("pointermove", this.onPointerMove);
         document.removeEventListener("pointerup", this.onPointerUp);
-        e.stopPropagation();
-        if (!SelectionManager.IsSelected(this) && e.button !== 2 &&
-            Math.abs(e.clientX - this._downX) < 4 && Math.abs(e.clientY - this._downY) < 4) {
-            SelectionManager.SelectDoc(this, e.ctrlKey);
-        }
-    }
-    stopPropagation = (e: React.SyntheticEvent) => {
-        e.stopPropagation();
     }
 
     deleteClicked = (): void => {
         this.props.removeDocument && this.props.removeDocument(this.props.Document);
     }
-
     fieldsClicked = (e: React.MouseEvent): void => {
-        if (this.props.addDocument) {
-            this.props.addDocument(Documents.KVPDocument(this.props.Document, { width: 300, height: 300 }), false);
+        let kvp = Docs.KVPDocument(this.props.Document, { title: this.props.Document.title + ".kvp", width: 300, height: 300 });
+        CollectionDockingView.Instance.AddRightSplit(kvp);
+    }
+    makeButton = (e: React.MouseEvent): void => {
+        let doc = this.props.Document.proto ? this.props.Document.proto : this.props.Document;
+        doc.isButton = !BoolCast(doc.isButton, false);
+        if (doc.isButton && !doc.nativeWidth) {
+            doc.nativeWidth = doc[WidthSym]();
+            doc.nativeHeight = doc[HeightSym]();
         }
     }
     fullScreenClicked = (e: React.MouseEvent): void => {
-        CollectionDockingView.Instance.OpenFullScreen((this.props.Document.GetPrototype() as Document).MakeDelegate());
-        ContextMenu.Instance.clearItems();
-        ContextMenu.Instance.addItem({ description: "Close Full Screen", event: this.closeFullScreenClicked });
-        ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
-    }
-
-    closeFullScreenClicked = (e: React.MouseEvent): void => {
-        CollectionDockingView.Instance.CloseFullScreen();
+        const doc = Doc.MakeDelegate(FieldValue(this.Document.proto));
+        if (doc) {
+            CollectionDockingView.Instance.OpenFullScreen(doc);
+        }
         ContextMenu.Instance.clearItems();
-        ContextMenu.Instance.addItem({ description: "Full Screen", event: this.fullScreenClicked });
-        ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
-    }
-
-    @action
-    public minimize = (): void => {
-        this.props.Document.SetBoolean(KeyStore.Minimized, true);
         SelectionManager.DeselectAll();
     }
-
     @undoBatch
     @action
-    drop = (e: Event, de: DragManager.DropEvent) => {
+    drop = async (e: Event, de: DragManager.DropEvent) => {
         if (de.data instanceof DragManager.LinkDragData) {
-            let sourceDoc: Document = de.data.linkSourceDocument;
-            let destDoc: Document = this.props.Document;
-            let linkDoc: Document = new Document();
-
-            destDoc.GetTAsync(KeyStore.Prototype, Document).then(protoDest =>
-                sourceDoc.GetTAsync(KeyStore.Prototype, Document).then(protoSrc =>
-                    runInAction(() => {
-                        let batch = UndoManager.StartBatch("document view drop");
-                        linkDoc.SetText(KeyStore.Title, "New Link");
-                        linkDoc.SetText(KeyStore.LinkDescription, "");
-                        linkDoc.SetText(KeyStore.LinkTags, "Default");
+            let sourceDoc = de.data.linkSourceDocument;
+            let destDoc = this.props.Document;
 
-                        let dstTarg = protoDest ? protoDest : destDoc;
-                        let srcTarg = protoSrc ? protoSrc : sourceDoc;
-                        linkDoc.Set(KeyStore.LinkedToDocs, dstTarg);
-                        linkDoc.Set(KeyStore.LinkedFromDocs, srcTarg);
-                        const prom1 = new Promise(resolve => dstTarg.GetOrCreateAsync(
-                            KeyStore.LinkedFromDocs,
-                            ListField,
-                            field => {
-                                (field as ListField<Document>).Data.push(linkDoc);
-                                resolve();
-                            }
-                        ));
-                        const prom2 = new Promise(resolve => srcTarg.GetOrCreateAsync(
-                            KeyStore.LinkedToDocs,
-                            ListField,
-                            field => {
-                                (field as ListField<Document>).Data.push(linkDoc);
-                                resolve();
-                            }
-                        ));
-                        Promise.all([prom1, prom2]).finally(() => batch.end());
-                    })
-                )
-            );
+            const protoDest = destDoc.proto;
+            const protoSrc = sourceDoc.proto;
+            Doc.MakeLink(protoSrc ? protoSrc : sourceDoc, protoDest ? protoDest : destDoc);
+            de.data.droppedDocuments.push(destDoc);
             e.stopPropagation();
         }
     }
 
+    @action
     onDrop = (e: React.DragEvent) => {
         let text = e.dataTransfer.getData("text/plain");
         if (!e.isDefaultPrevented() && text && text.startsWith("<div")) {
-            let oldLayout = this.props.Document.GetText(KeyStore.Layout, "");
+            let oldLayout = FieldValue(this.Document.layout) || "";
             let layout = text.replace("{layout}", oldLayout);
-            this.props.Document.SetText(KeyStore.Layout, layout);
+            this.Document.layout = layout;
             e.stopPropagation();
             e.preventDefault();
         }
     }
 
+
+    @action
+    addTemplate = (template: Template) => {
+        this.templates.push(template.Layout);
+        this.templates = this.templates;
+    }
+
+    @action
+    removeTemplate = (template: Template) => {
+        for (let i = 0; i < this.templates.length; i++) {
+            if (this.templates[i] === template.Layout) {
+                this.templates.splice(i, 1);
+                break;
+            }
+        }
+        this.templates = this.templates;
+    }
+
     @action
     onContextMenu = (e: React.MouseEvent): void => {
         e.stopPropagation();
@@ -284,51 +287,53 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         }
         e.preventDefault();
 
-        !this.isMinimized() && ContextMenu.Instance.addItem({ description: "Minimize", event: this.minimize });
         ContextMenu.Instance.addItem({ description: "Full Screen", event: this.fullScreenClicked });
+        ContextMenu.Instance.addItem({ description: this.props.Document.isButton ? "Remove Button" : "Make Button", event: this.makeButton });
         ContextMenu.Instance.addItem({ description: "Fields", event: this.fieldsClicked });
         ContextMenu.Instance.addItem({ description: "Center", event: () => this.props.focus(this.props.Document) });
         ContextMenu.Instance.addItem({ description: "Open Right", event: () => CollectionDockingView.Instance.AddRightSplit(this.props.Document) });
-        ContextMenu.Instance.addItem({ description: "Copy URL", event: () => Utils.CopyText(ServerUtils.prepend("/doc/" + this.props.Document.Id)) });
-        ContextMenu.Instance.addItem({ description: "Copy ID", event: () => Utils.CopyText(this.props.Document.Id) });
+        ContextMenu.Instance.addItem({ description: "Copy URL", event: () => Utils.CopyText(DocServer.prepend("/doc/" + this.props.Document[Id])) });
+        ContextMenu.Instance.addItem({ description: "Copy ID", event: () => Utils.CopyText(this.props.Document[Id]) });
         //ContextMenu.Instance.addItem({ description: "Docking", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Docking) })
+        ContextMenu.Instance.addItem({ description: "Pin to Presentation", event: () => PresentationView.Instance.PinDoc(this.props.Document) });
         ContextMenu.Instance.addItem({ description: "Delete", event: this.deleteClicked });
+        if (!this.topMost) {
+            // DocumentViews should stop propagation of this event
+            e.stopPropagation();
+        }
         ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
-        if (!SelectionManager.IsSelected(this))
+        if (!SelectionManager.IsSelected(this)) {
             SelectionManager.SelectDoc(this, false);
+        }
     }
 
-    @action
-    expand = () => this.props.Document.SetBoolean(KeyStore.Minimized, false)
-    isMinimized = () => this.props.Document.GetBoolean(KeyStore.Minimized, false);
+
     isSelected = () => SelectionManager.IsSelected(this);
     select = (ctrlPressed: boolean) => SelectionManager.SelectDoc(this, ctrlPressed);
 
-    @computed get nativeWidth() { return this.props.Document.GetNumber(KeyStore.NativeWidth, 0); }
-    @computed get nativeHeight() { return this.props.Document.GetNumber(KeyStore.NativeHeight, 0); }
-    @computed get contents() { return (<DocumentContentsView {...this.props} isSelected={this.isSelected} select={this.select} layoutKey={KeyStore.Layout} />); }
+    @computed get nativeWidth() { return this.Document.nativeWidth || 0; }
+    @computed get nativeHeight() { return this.Document.nativeHeight || 0; }
+    @computed get contents() { return (<DocumentContentsView {...this.props} isSelected={this.isSelected} select={this.select} layoutKey={"layout"} />); }
 
     render() {
         var scaling = this.props.ContentScaling();
         var nativeHeight = this.nativeHeight > 0 ? this.nativeHeight.toString() + "px" : "100%";
         var nativeWidth = this.nativeWidth > 0 ? this.nativeWidth.toString() + "px" : "100%";
 
-        if (this.isMinimized()) {
-            return <div className="minimized-box" ref={this._mainCont} onClick={this.expand} onDrop={this.onDrop}
-                style={{ transform: `scale(${scaling} , ${scaling})` }} onPointerDown={this.onPointerDown} />;
-        }
         return (
             <div className={`documentView-node${this.props.isTopMost ? "-topmost" : ""}`}
                 ref={this._mainCont}
                 style={{
-                    background: this.props.Document.GetText(KeyStore.BackgroundColor, ""),
-                    width: nativeWidth, height: nativeHeight,
+                    borderRadius: "inherit",
+                    background: this.Document.backgroundColor || "",
+                    width: nativeWidth,
+                    height: nativeHeight,
                     transform: `scale(${scaling}, ${scaling})`
                 }}
-                onDrop={this.onDrop} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown}
+                onDrop={this.onDrop} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick}
             >
                 {this.contents}
             </div>
         );
     }
-}
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index e9f6950ff..8bdf34181 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -1,28 +1,23 @@
 import React = require("react");
 import { observer } from "mobx-react";
-import { computed } from "mobx";
-import { Field, FieldWaiting, FieldValue, Opt } from "../../../fields/Field";
-import { Document } from "../../../fields/Document";
-import { TextField } from "../../../fields/TextField";
-import { NumberField } from "../../../fields/NumberField";
-import { RichTextField } from "../../../fields/RichTextField";
-import { ImageField } from "../../../fields/ImageField";
-import { VideoField } from "../../../fields/VideoField";
-import { Key } from "../../../fields/Key";
+import { computed, observable } from "mobx";
 import { FormattedTextBox } from "./FormattedTextBox";
 import { ImageBox } from "./ImageBox";
-import { WebBox } from "./WebBox";
 import { VideoBox } from "./VideoBox";
 import { AudioBox } from "./AudioBox";
-import { AudioField } from "../../../fields/AudioField";
-import { ListField } from "../../../fields/ListField";
 import { DocumentContentsView } from "./DocumentContentsView";
 import { Transform } from "../../util/Transform";
-import { KeyStore } from "../../../fields/KeyStore";
-import { returnFalse, emptyDocFunction, emptyFunction, returnOne } from "../../../Utils";
+import { returnFalse, emptyFunction } from "../../../Utils";
 import { CollectionView } from "../collections/CollectionView";
 import { CollectionPDFView } from "../collections/CollectionPDFView";
 import { CollectionVideoView } from "../collections/CollectionVideoView";
+import { IconBox } from "./IconBox";
+import { Opt, Doc, FieldResult } from "../../../new_fields/Doc";
+import { List } from "../../../new_fields/List";
+import { ImageField, VideoField, AudioField } from "../../../new_fields/URLField";
+import { IconField } from "../../../new_fields/IconField";
+import { RichTextField } from "../../../new_fields/RichTextField";
+import { DateField } from "../../../new_fields/DateField";
 
 
 //
@@ -31,54 +26,62 @@ import { CollectionVideoView } from "../collections/CollectionVideoView";
 // See the LayoutString method on each field view :   ImageBox, FormattedTextBox, etc. 
 //
 export interface FieldViewProps {
-    fieldKey: Key;
+    fieldKey: string;
     ContainingCollectionView: Opt<CollectionView | CollectionPDFView | CollectionVideoView>;
-    Document: Document;
+    Document: Doc;
     isSelected: () => boolean;
     select: (isCtrlPressed: boolean) => void;
     isTopMost: boolean;
     selectOnLoad: boolean;
-    addDocument?: (document: Document, allowDuplicates?: boolean) => boolean;
-    removeDocument?: (document: Document) => boolean;
-    moveDocument?: (document: Document, targetCollection: Document, addDocument: (document: Document) => boolean) => boolean;
+    addDocument?: (document: Doc, allowDuplicates?: boolean) => boolean;
+    removeDocument?: (document: Doc) => boolean;
+    moveDocument?: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
     ScreenToLocalTransform: () => Transform;
     active: () => boolean;
     whenActiveChanged: (isActive: boolean) => void;
-    focus: (doc: Document) => void;
+    focus: (doc: Doc) => void;
+    PanelWidth: () => number;
+    PanelHeight: () => number;
+    setVideoBox?: (player: VideoBox) => void;
 }
 
 @observer
 export class FieldView extends React.Component<FieldViewProps> {
-    public static LayoutString(fieldType: { name: string }, fieldStr: string = "DataKey") {
-        return `<${fieldType.name} {...props} fieldKey={${fieldStr}} />`;
+    public static LayoutString(fieldType: { name: string }, fieldStr: string = "data") {
+        return `<${fieldType.name} {...props} fieldKey={"${fieldStr}"} />`;
     }
 
     @computed
-    get field(): FieldValue<Field> {
-        const { Document: doc, fieldKey } = this.props;
-        return doc.Get(fieldKey);
+    get field(): FieldResult {
+        const { Document, fieldKey } = this.props;
+        return Document[fieldKey];
     }
     render() {
         const field = this.field;
-        if (!field) {
+        if (field === undefined) {
             return <p>{'<null>'}</p>;
         }
-        if (field instanceof TextField) {
-            return <p>{field.Data}</p>;
-        }
+        // if (typeof field === "string") {
+        //     return <p>{field}</p>;
+        // }
         else if (field instanceof RichTextField) {
             return <FormattedTextBox {...this.props} />;
         }
         else if (field instanceof ImageField) {
             return <ImageBox {...this.props} />;
         }
+        else if (field instanceof IconField) {
+            return <IconBox {...this.props} />;
+        }
         else if (field instanceof VideoField) {
             return <VideoBox {...this.props} />;
         }
         else if (field instanceof AudioField) {
             return <AudioBox {...this.props} />;
+        } else if (field instanceof DateField) {
+            return <p>{field.date.toLocaleString()}</p>;
         }
-        else if (field instanceof Document) {
+        else if (field instanceof Doc) {
             return (
                 <DocumentContentsView Document={field}
                     addDocument={undefined}
@@ -89,29 +92,28 @@ export class FieldView extends React.Component<FieldViewProps> {
                     PanelHeight={() => 100}
                     isTopMost={true} //TODO Why is this top most?
                     selectOnLoad={false}
-                    focus={emptyDocFunction}
-                    isSelected={returnFalse}
+                    focus={emptyFunction}
+                    isSelected={this.props.isSelected}
                     select={returnFalse}
-                    layoutKey={KeyStore.Layout}
+                    layoutKey={"layout"}
                     ContainingCollectionView={this.props.ContainingCollectionView}
                     parentActive={this.props.active}
-                    whenActiveChanged={this.props.whenActiveChanged} />
+                    toggleMinimized={emptyFunction}
+                    whenActiveChanged={this.props.whenActiveChanged}
+                    bringToFront={emptyFunction} />
             );
         }
-        else if (field instanceof ListField) {
+        else if (field instanceof List) {
             return (<div>
-                {(field as ListField<Field>).Data.map(f => f instanceof Document ? f.Title : f.GetValue().toString()).join(", ")}
+                {field.map(f => f instanceof Doc ? f.title : f.toString()).join(", ")}
             </div>);
         }
         // bcz: this belongs here, but it doesn't render well so taking it out for now
         // else if (field instanceof HtmlField) {
         //     return <WebBox {...this.props} />
         // }
-        else if (field instanceof NumberField) {
-            return <p>{field.Data}</p>;
-        }
-        else if (field !== FieldWaiting) {
-            return <p>{JSON.stringify(field.GetValue())}</p>;
+        else if (!(field instanceof Promise)) {
+            return <p>{JSON.stringify(field)}</p>;
         }
         else {
             return <p> {"Waiting for server..."} </p>;
diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss
index 5eb2bf7ce..458a62c5b 100644
--- a/src/client/views/nodes/FormattedTextBox.scss
+++ b/src/client/views/nodes/FormattedTextBox.scss
@@ -10,13 +10,14 @@
   outline: none !important;
 }
 
-.formattedTextBox-cont-scroll, .formattedTextBox-cont-hidden {
-  background: $light-color-secondary;
-  padding: 0.9em;
+.formattedTextBox-cont-scroll, .formattedTextBox-cont-hidden  {
+  background: inherit;
+  padding: 0;
   border-width: 0px;
-  border-radius: $border-radius;
+  border-radius: inherit;
   border-color: $intermediate-color;
   box-sizing: border-box;
+  background-color: inherit;
   border-style: solid;
   overflow-y: scroll;
   overflow-x: hidden;
@@ -24,10 +25,19 @@
   height: 100%;
   pointer-events: all;
 }
+
 .formattedTextBox-cont-hidden {
     overflow: hidden;
     pointer-events: none;
 }
+.formattedTextBox-inner-rounded {
+    height: calc(100% - 25px);
+    width: calc(100% - 40px);
+    position: absolute;
+    overflow: auto;
+    top: 15;
+    left: 20;
+}
 
 .menuicon {
   display: inline-block;
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index bd98622fb..fc58e513b 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -1,30 +1,35 @@
-import { action, IReactionDisposer, reaction } 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 { FieldWaiting, Opt } from "../../../fields/Field";
-import { KeyStore } from "../../../fields/KeyStore";
-import { RichTextField } from "../../../fields/RichTextField";
-import { TextField } from "../../../fields/TextField";
-import { Document } from "../../../fields/Document";
+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/ProsemirrorExampleTransfer";
 import { inpRules } from "../../util/RichTextRules";
-import { schema } 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");
-const { buildMenuItems } = require("prosemirror-example-setup");
-const { menuBar } = require("prosemirror-menu");
 
 // FormattedTextBox: Displays an editable plain text node that maps to a specified Key of a Document
 //
-//  HTML Markup:  <FormattedTextBox Doc={Document's ID} FieldKey={Key's name + "Key"}
+//  HTML Markup:  <FormattedTextBox Doc={Document's ID} FieldKey={Key's name}
 //
 //  In Code, the node's HTML is specified in the document's parameterized structure as:
 //        document.SetField(KeyStore.Layout,  "<FormattedTextBox doc={doc} fieldKey={<KEYNAME>Key} />");
@@ -43,51 +48,91 @@ export interface FormattedTextBoxOverlay {
     isOverlay?: boolean;
 }
 
-export class FormattedTextBox extends React.Component<(FieldViewProps & FormattedTextBoxOverlay)> {
-    public static LayoutString(fieldStr: string = "DataKey") {
+const richTextSchema = createSchema({
+    documentText: "string"
+});
+
+type RichTextDocument = makeInterface<[typeof richTextSchema]>;
+const RichTextDocument = makeInterface(richTextSchema);
+
+@observer
+export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTextBoxOverlay), RichTextDocument>(RichTextDocument) {
+    public static LayoutString(fieldStr: string = "data") {
         return FieldView.LayoutString(FormattedTextBox, fieldStr);
     }
     private _ref: React.RefObject<HTMLDivElement>;
+    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.onChange = this.onChange.bind(this);
+        this._proseRef = React.createRef();
+        if (this.props.isOverlay) {
+            DragManager.StartDragFunctions.push(() => FormattedTextBox.InputBoxOverlay = undefined);
+        }
     }
 
     _applyingChange: boolean = false;
 
+    _lastState: any = undefined;
     dispatchTransaction = (tx: Transaction) => {
         if (this._editorView) {
-            const state = this._editorView.state.apply(tx);
+            const state = this._lastState = this._editorView.state.apply(tx);
             this._editorView.updateState(state);
             this._applyingChange = true;
-            this.props.Document.SetDataOnPrototype(
-                this.props.fieldKey,
-                JSON.stringify(state.toJSON()),
-                RichTextField
-            );
-            this.props.Document.SetDataOnPrototype(KeyStore.DocumentText, state.doc.textBetween(0, state.doc.content.size, "\n\n"), TextField);
+            Doc.SetOnPrototype(this.props.Document, this.props.fieldKey, new RichTextField(JSON.stringify(state.toJSON())));
+            Doc.SetOnPrototype(this.props.Document, "documentText", state.doc.textBetween(0, state.doc.content.size, "\n\n"));
             this._applyingChange = false;
-            // doc.SetData(fieldKey, JSON.stringify(state.toJSON()), RichTextField);
+            let title = StrCast(this.props.Document.title);
+            if (title && title.startsWith("-") && this._editorView) {
+                let str = this._editorView.state.doc.textContent;
+                let titlestr = str.substr(0, Math.min(40, str.length));
+                let target = this.props.Document.proto ? this.props.Document.proto : this.props.Document;
+                target.title = "-" + titlestr + (str.length > 40 ? "..." : "");
+            }
+        }
+    }
+
+    @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
             plugins: this.props.isOverlay ? [
+                this.tooltipTextMenuPlugin(),
                 history(),
                 keymap(buildKeymap(schema)),
                 keymap(baseKeymap),
-                this.tooltipTextMenuPlugin(),
                 // this.tooltipLinkingMenuPlugin(),
                 new Plugin({
                     props: {
@@ -102,46 +147,51 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte
         };
 
         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();
                     }
-                    this.setupEditor(config, MainOverlayTextBox.Instance.TextDoc); // bcz: not sure why, but the order of events is such that this.props.Document hasn't updated yet, so without forcing the editor to the MainOverlayTextBox, it will display the previously focused textbox
+                    this.setupEditor(config, this.props.Document);// MainOverlayTextBox.Instance.TextDoc); // bcz: not sure why, but the order of events is such that this.props.Document hasn't updated yet, so without forcing the editor to the MainOverlayTextBox, it will display the previously focused textbox
                 }
             );
         } 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;
+                    }
+                });
         }
 
+
         this._reactionDisposer = reaction(
             () => {
-                const field = this.props.Document ? this.props.Document.GetT(this.props.fieldKey, RichTextField) : undefined;
-                return field && field !== FieldWaiting ? field.Data : undefined;
+                const field = this.props.Document ? Cast(this.props.Document[this.props.fieldKey], RichTextField) : undefined;
+                return field ? field.Data : undefined;
             },
-            field => {
-                if (field && this._editorView && !this._applyingChange) {
-                    this._editorView.updateState(
-                        EditorState.fromJSON(config, JSON.parse(field))
-                    );
-                }
-            }
+            field => field && this._editorView && !this._applyingChange &&
+                this._editorView.updateState(EditorState.fromJSON(config, JSON.parse(field)))
         );
         this.setupEditor(config, this.props.Document);
     }
 
-    shouldComponentUpdate() {
-        return false;
-    }
-
-    private setupEditor(config: any, doc?: Document) {
-        let field = doc ? doc.GetT(this.props.fieldKey, RichTextField) : undefined;
-        if (this._ref.current) {
-            this._editorView = new EditorView(this._ref.current, {
+    private setupEditor(config: any, doc?: Doc) {
+        let field = doc ? Cast(doc[this.props.fieldKey], RichTextField) : undefined;
+        if (this._proseRef.current) {
+            this._editorView = new EditorView(this._proseRef.current, {
                 state: field && field.Data ? EditorState.fromJSON(config, JSON.parse(field.Data)) : EditorState.create(config),
-                dispatchTransaction: this.dispatchTransaction
+                dispatchTransaction: this.dispatchTransaction,
+                nodeViews: {
+                    image(node, view, getPos) { return new ImageResizeView(node, view, getPos) }
+                }
             });
+            let text = StrCast(this.props.Document.documentText);
+            if (text.startsWith("@@@")) {
+                this.props.Document.proto!.documentText = undefined;
+                this._editorView.dispatch(this._editorView.state.tr.insertText(text.replace("@@@", "")));
+            }
         }
 
         if (this.props.selectOnLoad) {
@@ -163,67 +213,81 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte
         if (this._proxyReactionDisposer) {
             this._proxyReactionDisposer();
         }
+        if (this._dropDisposer) {
+            this._dropDisposer();
+        }
     }
 
-    @action
-    onChange(e: React.ChangeEvent<HTMLInputElement>) {
-        const { fieldKey, Document } = this.props;
-        Document.SetOnPrototype(fieldKey, new RichTextField(e.target.value));
-        // doc.SetData(fieldKey, e.target.value, RichTextField);
-    }
     onPointerDown = (e: React.PointerEvent): void => {
-        if (e.button === 1 && this.props.isSelected() && !e.altKey && !e.ctrlKey && !e.metaKey) {
-            console.log("first");
+        if (e.button === 0 && this.props.isSelected() && !e.altKey && !e.ctrlKey && !e.metaKey) {
             e.stopPropagation();
+            if (this._toolTipTextMenu && this._toolTipTextMenu.tooltip) {
+                this._toolTipTextMenu.tooltip.style.opacity = "0";
+            }
         }
-        if (e.button === 2) {
+        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;
-            console.log("second");
             e.preventDefault();
         }
     }
     onPointerUp = (e: React.PointerEvent): void => {
-        console.log("pointer up");
+        if (this._toolTipTextMenu && this._toolTipTextMenu.tooltip) {
+            this._toolTipTextMenu.tooltip.style.opacity = "1";
+        }
         if (e.buttons === 1 && this.props.isSelected() && !e.altKey) {
             e.stopPropagation();
         }
     }
 
+    @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._ref.current) {
-                this._ref.current.scrollTop = MainOverlayTextBox.Instance.TextScroll;
+            if (this._proseRef.current) {
+                this._proseRef.current.scrollTop = FormattedTextBox.InputBoxOverlayScroll;
             }
         }
     }
 
     //REPLACE THIS WITH CAPABILITIES SPECIFIC TO THIS TYPE OF NODE
-    textCapability = (e: React.MouseEvent): void => { };
+    textCapability = (e: React.MouseEvent): void => {
+        if (NumCast(this.props.Document.nativeWidth)) {
+            this.props.Document.nativeWidth = undefined;
+            this.props.Document.nativeHeight = undefined;
 
+        } else {
+            this.props.Document.nativeWidth = this.props.Document[WidthSym]();
+            this.props.Document.nativeHeight = this.props.Document[HeightSym]();
+        }
+    }
     specificContextMenu = (e: React.MouseEvent): void => {
         if (!this._gotDown) {
             e.preventDefault();
             return;
         }
         ContextMenu.Instance.addItem({
-            description: "Text Capability",
+            description: NumCast(this.props.Document.nativeWidth) ? "Unfreeze" : "Freeze",
             event: this.textCapability
         });
-
-        // ContextMenu.Instance.addItem({
-        //     description: "Submenu",
-        //     items: [
-        //         {
-        //             description: "item 1", event:
-        //     },
-        //         {
-        //             description: "item 2", event:
-        //     }
-        //     ]
-        // })
-        // e.stopPropagation()
     }
 
     onPointerWheel = (e: React.WheelEvent): void => {
@@ -232,15 +296,26 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte
         }
     }
 
+    onClick = (e: React.MouseEvent): void => {
+        this._proseRef.current!.focus();
+    }
+    onMouseDown = (e: React.MouseEvent): void => {
+        if (!this.props.isSelected()) { // preventing default allows the onClick to be generated instead of being swallowed by the text box itself
+            e.preventDefault(); // bcz: this would normally be in OnPointerDown - however, if done there, no mouse move events will be generated which makes transititioning to GoldenLayout's drag interactions impossible
+        }
+    }
+
     tooltipTextMenuPlugin() {
         let myprops = this.props;
+        let self = this;
         return new Plugin({
             view(_editorView) {
-                return new TooltipTextMenu(_editorView, myprops);
+                return self._toolTipTextMenu = new TooltipTextMenu(_editorView, myprops);
             }
         });
     }
 
+    _toolTipTextMenu: TooltipTextMenu | undefined = undefined;
     tooltipLinkingMenuPlugin() {
         let myprops = this.props;
         return new Plugin({
@@ -249,28 +324,55 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte
             }
         });
     }
-
-    onKeyPress(e: React.KeyboardEvent) {
+    onBlur = (e: any) => {
+        if (this._undoTyping) {
+            this._undoTyping.end();
+            this._undoTyping = undefined;
+        }
+    }
+    public _undoTyping?: UndoManager.Batch;
+    onKeyPress = (e: React.KeyboardEvent) => {
+        if (e.key === "Escape") {
+            SelectionManager.DeselectAll();
+        }
         e.stopPropagation();
-        if (e.keyCode === 9) e.preventDefault();
+        if (e.key === "Tab") e.preventDefault();
         // stop propagation doesn't seem to stop propagation of native keyboard events.
         // so we set a flag on the native event that marks that the event's been handled.
-        // (e.nativeEvent as any).DASHFormattedTextBoxHandled = true;
+        (e.nativeEvent as any).DASHFormattedTextBoxHandled = true;
+        if (StrCast(this.props.Document.title).startsWith("-") && this._editorView) {
+            let str = this._editorView.state.doc.textContent;
+            let titlestr = str.substr(0, Math.min(40, str.length));
+            let target = this.props.Document.proto ? this.props.Document.proto : this.props.Document;
+            target.title = "-" + titlestr + (str.length > 40 ? "..." : "");
+        }
+        if (!this._undoTyping) {
+            this._undoTyping = UndoManager.StartBatch("undoTyping");
+        }
     }
     render() {
-        let style = this.props.isSelected() || this.props.isOverlay ? "scroll" : "hidden";
+        let style = this.props.isOverlay ? "scroll" : "hidden";
+        let rounded = NumCast(this.props.Document.borderRounding) < 0 ? "-rounded" : "";
+        let interactive = InkingControl.Instance.selectedTool ? "" : "interactive";
         return (
-            <div className={`formattedTextBox-cont-${style}`}
+            <div className={`formattedTextBox-cont-${style}`} ref={this._ref}
+                style={{
+                    pointerEvents: interactive ? "all" : "none",
+                }}
                 onKeyDown={this.onKeyPress}
                 onKeyPress={this.onKeyPress}
                 onFocus={this.onFocused}
+                onClick={this.onClick}
+                onBlur={this.onBlur}
                 onPointerUp={this.onPointerUp}
                 onPointerDown={this.onPointerDown}
+                onMouseDown={this.onMouseDown}
                 onContextMenu={this.specificContextMenu}
                 // tfs: do we need this event handler
                 onWheel={this.onPointerWheel}
-                ref={this._ref}
-            />
+            >
+                <div className={`formattedTextBox-inner${rounded}`} style={{ pointerEvents: this.props.Document.isButton && !this.props.isSelected() ? "none" : "all" }} ref={this._proseRef} />
+            </div>
         );
     }
 }
diff --git a/src/client/views/nodes/IconBox.scss b/src/client/views/nodes/IconBox.scss
new file mode 100644
index 000000000..85bbdeb59
--- /dev/null
+++ b/src/client/views/nodes/IconBox.scss
@@ -0,0 +1,22 @@
+
+@import "../globalCssVariables";
+.iconBox-container {
+    position: absolute;
+    left:0;
+    top:0;
+    height: 100%;
+    width: max-content;
+    // overflow: hidden;
+    pointer-events: all;
+    svg {
+        width:  $MINIMIZED_ICON_SIZE !important;
+        height: 100%;
+        background: white;
+    }
+    .iconBox-label {
+        position: inherit;
+        width:max-content;
+        font-size: 14px;
+        margin-top: 3px;
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/IconBox.tsx b/src/client/views/nodes/IconBox.tsx
new file mode 100644
index 000000000..4bcb4c636
--- /dev/null
+++ b/src/client/views/nodes/IconBox.tsx
@@ -0,0 +1,69 @@
+import React = require("react");
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { faCaretUp, faFilePdf, faFilm, faImage, faObjectGroup, faStickyNote } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { computed, observable, runInAction, reaction, IReactionDisposer } from "mobx";
+import { observer } from "mobx-react";
+import { FieldView, FieldViewProps } from './FieldView';
+import "./IconBox.scss";
+import { Cast, StrCast, BoolCast } from "../../../new_fields/Types";
+import { Doc, DocListCast } from "../../../new_fields/Doc";
+import { IconField } from "../../../new_fields/IconField";
+import { ContextMenu } from "../ContextMenu";
+import Measure from "react-measure";
+import { MINIMIZED_ICON_SIZE } from "../../views/globalCssVariables.scss";
+import { listSpec } from "../../../new_fields/Schema";
+
+
+library.add(faCaretUp);
+library.add(faObjectGroup);
+library.add(faStickyNote);
+library.add(faFilePdf);
+library.add(faFilm);
+
+@observer
+export class IconBox extends React.Component<FieldViewProps> {
+    public static LayoutString() { return FieldView.LayoutString(IconBox); }
+
+    @computed get layout(): string { const field = Cast(this.props.Document[this.props.fieldKey], IconField); return field ? field.icon : "<p>Error loading icon data</p>"; }
+    @computed get minimizedIcon() { return IconBox.DocumentIcon(this.layout); }
+
+    public static DocumentIcon(layout: string) {
+        let button = layout.indexOf("PDFBox") !== -1 ? faFilePdf :
+            layout.indexOf("ImageBox") !== -1 ? faImage :
+                layout.indexOf("Formatted") !== -1 ? faStickyNote :
+                    layout.indexOf("Video") !== -1 ? faFilm :
+                        layout.indexOf("Collection") !== -1 ? faObjectGroup :
+                            faCaretUp;
+        return <FontAwesomeIcon icon={button} className="documentView-minimizedIcon" />;
+    }
+
+    setLabelField = (e: React.MouseEvent): void => {
+        this.props.Document.hideLabel = !BoolCast(this.props.Document.hideLabel);
+    }
+
+    specificContextMenu = (e: React.MouseEvent): void => {
+        ContextMenu.Instance.addItem({
+            description: BoolCast(this.props.Document.hideLabel) ? "show label" : "hide label",
+            event: this.setLabelField
+        });
+    }
+    @observable _panelWidth: number = 0;
+    @observable _panelHeight: number = 0;
+    render() {
+        let labelField = StrCast(this.props.Document.labelField);
+        let hideLabel = BoolCast(this.props.Document.hideLabel);
+        let maxDoc = Cast(this.props.Document.maximizedDocs, listSpec(Doc), []);
+        let firstDoc = maxDoc && maxDoc.length > 0 && maxDoc[0] instanceof Doc ? maxDoc[0] as Doc : undefined;
+        let label = !hideLabel && firstDoc && labelField ? firstDoc[labelField] : "";
+        return (
+            <div className="iconBox-container" onContextMenu={this.specificContextMenu}>
+                {this.minimizedIcon}
+                <Measure onResize={(r) => runInAction(() => { if (r.entry.width || BoolCast(this.props.Document.hideLabel)) this.props.Document.nativeWidth = this.props.Document.width = (r.entry.width + Number(MINIMIZED_ICON_SIZE)); })}>
+                    {({ measureRef }) =>
+                        <span ref={measureRef} className="iconBox-label">{label}</span>
+                    }
+                </Measure>
+            </div>);
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss
index f4b3837ff..2316a050e 100644
--- a/src/client/views/nodes/ImageBox.scss
+++ b/src/client/views/nodes/ImageBox.scss
@@ -6,6 +6,12 @@
   height: auto;
   max-width: 100%;
   max-height: 100%;
+  pointer-events: none;
+}
+.imageBox-cont-interactive {
+  pointer-events: all;
+  width:100%;
+  height:auto;
 }
 
 .imageBox-dot {
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index ce855384c..0e9e904a8 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -3,11 +3,6 @@ import { action, observable } from 'mobx';
 import { observer } from "mobx-react";
 import Lightbox from 'react-image-lightbox';
 import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app
-import { Document } from '../../../fields/Document';
-import { FieldWaiting } from '../../../fields/Field';
-import { ImageField } from '../../../fields/ImageField';
-import { KeyStore } from '../../../fields/KeyStore';
-import { ListField } from '../../../fields/ListField';
 import { Utils } from '../../../Utils';
 import { DragManager } from '../../util/DragManager';
 import { undoBatch } from '../../util/UndoManager';
@@ -15,12 +10,27 @@ import { ContextMenu } from "../../views/ContextMenu";
 import { FieldView, FieldViewProps } from './FieldView';
 import "./ImageBox.scss";
 import React = require("react");
+import { createSchema, makeInterface, listSpec } from '../../../new_fields/Schema';
+import { DocComponent } from '../DocComponent';
+import { positionSchema } from './DocumentView';
+import { FieldValue, Cast, StrCast } from '../../../new_fields/Types';
+import { ImageField } from '../../../new_fields/URLField';
+import { List } from '../../../new_fields/List';
+import { InkingControl } from '../InkingControl';
+import { Doc } from '../../../new_fields/Doc';
+
+export const pageSchema = createSchema({
+    curPage: "number"
+});
+
+type ImageDocument = makeInterface<[typeof pageSchema, typeof positionSchema]>;
+const ImageDocument = makeInterface(pageSchema, positionSchema);
 
 @observer
-export class ImageBox extends React.Component<FieldViewProps> {
+export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageDocument) {
 
     public static LayoutString() { return FieldView.LayoutString(ImageBox); }
-    private _imgRef: React.RefObject<HTMLImageElement>;
+    private _imgRef: React.RefObject<HTMLImageElement> = React.createRef();
     private _downX: number = 0;
     private _downY: number = 0;
     private _lastTap: number = 0;
@@ -28,19 +38,13 @@ export class ImageBox extends React.Component<FieldViewProps> {
     @observable private _isOpen: boolean = false;
     private dropDisposer?: DragManager.DragDropDisposer;
 
-    constructor(props: FieldViewProps) {
-        super(props);
-
-        this._imgRef = React.createRef();
-    }
-
     @action
     onLoad = (target: any) => {
         var h = this._imgRef.current!.naturalHeight;
         var w = this._imgRef.current!.naturalWidth;
         if (this._photoIndex === 0) {
-            this.props.Document.SetNumber(KeyStore.NativeHeight, this.props.Document.GetNumber(KeyStore.NativeWidth, 0) * h / w);
-            this.props.Document.SetNumber(KeyStore.Height, this.props.Document.Width() * h / w);
+            this.Document.nativeHeight = FieldValue(this.Document.nativeWidth, 0) * h / w;
+            this.Document.height = FieldValue(this.Document.width, 0) * h / w;
         }
     }
 
@@ -63,19 +67,18 @@ export class ImageBox extends React.Component<FieldViewProps> {
     @undoBatch
     drop = (e: Event, de: DragManager.DropEvent) => {
         if (de.data instanceof DragManager.DocumentDragData) {
-            de.data.droppedDocuments.map(action((drop: Document) => {
-                let layout = drop.GetText(KeyStore.BackgroundLayout, "");
+            de.data.droppedDocuments.forEach(action((drop: Doc) => {
+                let layout = StrCast(drop.backgroundLayout);
                 if (layout.indexOf(ImageBox.name) !== -1) {
-                    let imgData = this.props.Document.Get(KeyStore.Data);
-                    if (imgData instanceof ImageField && imgData) {
-                        this.props.Document.SetOnPrototype(KeyStore.Data, new ListField([imgData]));
+                    let imgData = this.props.Document[this.props.fieldKey];
+                    if (imgData instanceof ImageField) {
+                        Doc.SetOnPrototype(this.props.Document, "data", new List([imgData]));
                     }
-                    let imgList = this.props.Document.GetList(KeyStore.Data, [] as any[]);
+                    let imgList = Cast(this.props.Document[this.props.fieldKey], listSpec(ImageField), [] as any[]);
                     if (imgList) {
-                        let field = drop.Get(KeyStore.Data);
-                        if (field === FieldWaiting) { }
-                        else if (field instanceof ImageField) imgList.push(field);
-                        else if (field instanceof ListField) imgList.push(field.Data);
+                        let field = drop.data;
+                        if (field instanceof ImageField) imgList.push(field);
+                        else if (field instanceof List) imgList.concat(field);
                     }
                     e.stopPropagation();
                 }
@@ -87,7 +90,6 @@ export class ImageBox extends React.Component<FieldViewProps> {
     onPointerDown = (e: React.PointerEvent): void => {
         if (Date.now() - this._lastTap < 300) {
             if (e.buttons === 1) {
-                e.stopPropagation();
                 this._downX = e.clientX;
                 this._downY = e.clientY;
                 document.removeEventListener("pointerup", this.onPointerUp);
@@ -126,9 +128,9 @@ export class ImageBox extends React.Component<FieldViewProps> {
     }
 
     specificContextMenu = (e: React.MouseEvent): void => {
-        let field = this.props.Document.GetT(this.props.fieldKey, ImageField);
-        if (field && field !== FieldWaiting) {
-            let url = field.Data.href;
+        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);
@@ -140,29 +142,29 @@ export class ImageBox extends React.Component<FieldViewProps> {
     @action
     onDotDown(index: number) {
         this._photoIndex = index;
-        this.props.Document.SetNumber(KeyStore.CurPage, index);
+        this.Document.curPage = index;
     }
 
     dots(paths: string[]) {
-        let nativeWidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 1);
+        let nativeWidth = FieldValue(this.Document.nativeWidth, 1);
         let dist = Math.min(nativeWidth / paths.length, 40);
         let left = (nativeWidth - paths.length * dist) / 2;
         return paths.map((p, i) =>
             <div className="imageBox-placer" key={i} >
-                <div className="imageBox-dot" style={{ background: (i == this._photoIndex ? "black" : "gray"), transform: `translate(${i * dist + left}px, 0px)` }} onPointerDown={(e: React.PointerEvent) => { e.stopPropagation(); this.onDotDown(i); }} />
+                <div className="imageBox-dot" style={{ background: (i === this._photoIndex ? "black" : "gray"), transform: `translate(${i * dist + left}px, 0px)` }} onPointerDown={(e: React.PointerEvent) => { e.stopPropagation(); this.onDotDown(i); }} />
             </div>
         );
     }
 
     render() {
-        let field = this.props.Document.Get(this.props.fieldKey);
+        let field = this.Document[this.props.fieldKey];
         let paths: string[] = ["http://www.cs.brown.edu/~bcz/face.gif"];
-        if (field === FieldWaiting) paths = ["https://image.flaticon.com/icons/svg/66/66163.svg"];
-        else if (field instanceof ImageField) paths = [field.Data.href];
-        else if (field instanceof ListField) paths = field.Data.filter(val => val as ImageField).map(p => (p as ImageField).Data.href);
-        let nativeWidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 1);
+        if (field instanceof ImageField) paths = [field.url.href];
+        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, 1);
+        let interactive = InkingControl.Instance.selectedTool ? "" : "-interactive";
         return (
-            <div className="imageBox-cont" onPointerDown={this.onPointerDown} onDrop={this.onDrop} ref={this.createDropTarget} onContextMenu={this.specificContextMenu}>
+            <div className={`imageBox-cont${interactive}`} onPointerDown={this.onPointerDown} onDrop={this.onDrop} ref={this.createDropTarget} onContextMenu={this.specificContextMenu}>
                 <img src={paths[Math.min(paths.length, this._photoIndex)]} style={{ objectFit: (this._photoIndex === 0 ? undefined : "contain") }} width={nativeWidth} alt="Image not found" ref={this._imgRef} onLoad={this.onLoad} />
                 {paths.length > 1 ? this.dots(paths) : (null)}
                 {this.lightbox(paths)}
diff --git a/src/client/views/nodes/KeyValueBox.scss b/src/client/views/nodes/KeyValueBox.scss
index 6ebd73f2c..20cae03d4 100644
--- a/src/client/views/nodes/KeyValueBox.scss
+++ b/src/client/views/nodes/KeyValueBox.scss
@@ -8,6 +8,7 @@
     border-radius: $border-radius;
     box-sizing: border-box;
     display: inline-block;
+    pointer-events: all;
     .imageBox-cont img {
         width: auto;
     }
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index ddbec014b..876a3c173 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -2,24 +2,22 @@
 import { action, computed, observable } from "mobx";
 import { observer } from "mobx-react";
 import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app
-import { Document } from '../../../fields/Document';
-import { Field, FieldWaiting } from '../../../fields/Field';
-import { Key } from '../../../fields/Key';
-import { KeyStore } from '../../../fields/KeyStore';
-import { CompileScript, ToField } from "../../util/Scripting";
+import { CompileScript } from "../../util/Scripting";
 import { FieldView, FieldViewProps } from './FieldView';
 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";
 
 @observer
 export class KeyValueBox extends React.Component<FieldViewProps> {
     private _mainCont = React.createRef<HTMLDivElement>();
 
-    public static LayoutString(fieldStr: string = "DataKey") { return FieldView.LayoutString(KeyValueBox, fieldStr); }
+    public static LayoutString(fieldStr: string = "data") { return FieldView.LayoutString(KeyValueBox, fieldStr); }
     @observable private _keyInput: string = "";
     @observable private _valueInput: string = "";
-    @computed get splitPercentage() { return this.props.Document.GetNumber(KeyStore.SchemaSplitPercentage, 50); }
+    @computed get splitPercentage() { return NumCast(this.props.Document.schemaSplitPercentage, 50); }
 
 
     constructor(props: FieldViewProps) {
@@ -30,8 +28,8 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
     onEnterKey = (e: React.KeyboardEvent): void => {
         if (e.key === 'Enter') {
             if (this._keyInput && this._valueInput) {
-                let doc = this.props.Document.GetT(KeyStore.Data, Document);
-                if (!doc || doc === FieldWaiting) {
+                let doc = FieldValue(Cast(this.props.Document.data, Doc));
+                if (!doc) {
                     return;
                 }
                 let realDoc = doc;
@@ -43,13 +41,8 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
                 let res = script.run();
                 if (!res.success) return;
                 const field = res.result;
-                if (field instanceof Field) {
-                    realDoc.Set(new Key(this._keyInput), field);
-                } else {
-                    let dataField = ToField(field);
-                    if (dataField) {
-                        realDoc.Set(new Key(this._keyInput), dataField);
-                    }
+                if (IsField(field)) {
+                    realDoc[this._keyInput] = field;
                 }
                 this._keyInput = "";
                 this._valueInput = "";
@@ -67,16 +60,16 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
     }
 
     createTable = () => {
-        let doc = this.props.Document.GetT(KeyStore.Data, Document);
-        if (!doc || doc === FieldWaiting) {
+        let doc = FieldValue(Cast(this.props.Document.data, Doc));
+        if (!doc) {
             return <tr><td>Loading...</td></tr>;
         }
         let realDoc = doc;
 
         let ids: { [key: string]: string } = {};
-        let protos = doc.GetAllPrototypes();
+        let protos = Doc.GetAllPrototypes(doc);
         for (const proto of protos) {
-            proto._proxies.forEach((val: any, key: string) => {
+            Object.keys(proto).forEach(key => {
                 if (!(key in ids)) {
                     ids[key] = key;
                 }
@@ -86,7 +79,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
         let rows: JSX.Element[] = [];
         let i = 0;
         for (let key in ids) {
-            rows.push(<KeyValuePair doc={realDoc} keyWidth={100 - this.splitPercentage} rowStyle={"keyValueBox-" + (i++ % 2 ? "oddRow" : "evenRow")} fieldId={key} key={key} />);
+            rows.push(<KeyValuePair doc={realDoc} keyWidth={100 - this.splitPercentage} rowStyle={"keyValueBox-" + (i++ % 2 ? "oddRow" : "evenRow")} key={key} keyName={key} />);
         }
         return rows;
     }
@@ -116,7 +109,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
     @action
     onDividerMove = (e: PointerEvent): void => {
         let nativeWidth = this._mainCont.current!.getBoundingClientRect();
-        this.props.Document.SetNumber(KeyStore.SchemaSplitPercentage, Math.max(0, 100 - Math.round((e.clientX - nativeWidth.left) / nativeWidth.width * 100)));
+        this.props.Document.schemaSplitPercentage = Math.max(0, 100 - Math.round((e.clientX - nativeWidth.left) / nativeWidth.width * 100));
     }
     @action
     onDividerUp = (e: PointerEvent): void => {
diff --git a/src/client/views/nodes/KeyValuePair.scss b/src/client/views/nodes/KeyValuePair.scss
index 01701e02c..ff6885965 100644
--- a/src/client/views/nodes/KeyValuePair.scss
+++ b/src/client/views/nodes/KeyValuePair.scss
@@ -25,4 +25,5 @@
 }
 .keyValuePair-td-value {
     display:inline-block;
+    overflow: scroll;
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index d480eb5af..5de660d57 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -1,48 +1,33 @@
 import { action, observable } from 'mobx';
 import { observer } from "mobx-react";
 import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app
-import { Document } from '../../../fields/Document';
-import { Field, Opt } from '../../../fields/Field';
-import { Key } from '../../../fields/Key';
-import { emptyDocFunction, emptyFunction, returnFalse } from '../../../Utils';
-import { Server } from "../../Server";
-import { CompileScript, ToField } from "../../util/Scripting";
+import { emptyFunction, returnFalse, returnZero } from '../../../Utils';
+import { CompileScript } from "../../util/Scripting";
 import { Transform } from '../../util/Transform';
 import { EditableView } from "../EditableView";
 import { FieldView, FieldViewProps } from './FieldView';
 import "./KeyValueBox.scss";
 import "./KeyValuePair.scss";
 import React = require("react");
+import { Doc, Opt, IsField } from '../../../new_fields/Doc';
+import { FieldValue } from '../../../new_fields/Types';
 
 // Represents one row in a key value plane
 
 export interface KeyValuePairProps {
     rowStyle: string;
-    fieldId: string;
-    doc: Document;
+    keyName: string;
+    doc: Doc;
     keyWidth: number;
 }
 @observer
 export class KeyValuePair extends React.Component<KeyValuePairProps> {
 
-    @observable private key: Opt<Key>;
-
-    constructor(props: KeyValuePairProps) {
-        super(props);
-        Server.GetField(this.props.fieldId,
-            action((field: Opt<Field>) => field instanceof Key && (this.key = field)));
-
-    }
-
-
     render() {
-        if (!this.key) {
-            return <tr><td>error</td><td /></tr>;
-        }
         let props: FieldViewProps = {
             Document: this.props.doc,
             ContainingCollectionView: undefined,
-            fieldKey: this.key,
+            fieldKey: this.props.keyName,
             isSelected: returnFalse,
             select: emptyFunction,
             isTopMost: false,
@@ -50,29 +35,36 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
             active: returnFalse,
             whenActiveChanged: emptyFunction,
             ScreenToLocalTransform: Transform.Identity,
-            focus: emptyDocFunction,
+            focus: emptyFunction,
+            PanelWidth: returnZero,
+            PanelHeight: returnZero,
         };
         let contents = <FieldView {...props} />;
+        let fieldKey = Object.keys(props.Document).indexOf(props.fieldKey) !== -1 ? props.fieldKey : "(" + props.fieldKey + ")";
         return (
             <tr className={this.props.rowStyle}>
                 <td className="keyValuePair-td-key" style={{ width: `${this.props.keyWidth}%` }}>
                     <div className="keyValuePair-td-key-container">
                         <button className="keyValuePair-td-key-delete" onClick={() => {
-                            let field = props.Document.Get(props.fieldKey);
-                            field && field instanceof Field && props.Document.Set(props.fieldKey, undefined);
+                            if (Object.keys(props.Document).indexOf(props.fieldKey) !== -1)
+                                props.Document[props.fieldKey] = undefined;
+                            else props.Document.proto![props.fieldKey] = undefined;
                         }}>
                             X
                         </button>
-                        <div className="keyValuePair-keyField">{this.key.Name}</div>
+                        <div className="keyValuePair-keyField">{fieldKey}</div>
                     </div>
                 </td>
                 <td className="keyValuePair-td-value" style={{ width: `${100 - this.props.keyWidth}%` }}>
                     <EditableView contents={contents} height={36} GetValue={() => {
-                        let field = props.Document.Get(props.fieldKey);
-                        if (field && field instanceof Field) {
-                            return field.ToScriptString();
+
+                        let field = FieldValue(props.Document[props.fieldKey]);
+                        if (field) {
+                            //TODO Types
+                            return String(field);
+                            // return field.ToScriptString();
                         }
-                        return field || "";
+                        return "";
                     }}
                         SetValue={(value: string) => {
                             let script = CompileScript(value, { addReturn: true });
@@ -82,15 +74,9 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
                             let res = script.run();
                             if (!res.success) return false;
                             const field = res.result;
-                            if (field instanceof Field) {
-                                props.Document.Set(props.fieldKey, field);
+                            if (IsField(field)) {
+                                props.Document[props.fieldKey] = field;
                                 return true;
-                            } else {
-                                let dataField = ToField(field);
-                                if (dataField) {
-                                    props.Document.Set(props.fieldKey, dataField);
-                                    return true;
-                                }
                             }
                             return false;
                         }}>
diff --git a/src/client/views/nodes/LinkBox.scss b/src/client/views/nodes/LinkBox.scss
index 8bc70b48f..639f83b38 100644
--- a/src/client/views/nodes/LinkBox.scss
+++ b/src/client/views/nodes/LinkBox.scss
@@ -1,14 +1,14 @@
 @import "../globalCssVariables";
 .link-container {
     width: 100%;
-    height: 35px;
+    height: 50px;
     display: flex;
     flex-direction: row;
     border-top: 0.5px solid #bababa;
 }
 
 .info-container {
-    width: 55%;
+    width: 65%;
     padding-top: 5px;
     padding-left: 5px;
     display: flex;
@@ -24,7 +24,8 @@
 }
 
 .button-container {
-    width: 45%;
+    width: 35%;
+    padding-top: 8px;
     display: flex;
     flex-direction: row;
 }
@@ -49,17 +50,17 @@
     cursor: pointer;
 }
 
-.fa-icon-view {
-    margin-left: 3px;
-    margin-top: 5px;
-}
+// .fa-icon-view {
+//     margin-left: 3px;
+//     margin-top: 5px;
+// }
 
 .fa-icon-edit {
-    margin-left: 5px;
-    margin-top: 5px;
+    margin-left: 6px;
+    margin-top: 6px;
 }
 
 .fa-icon-delete {
-    margin-left: 6px;
-    margin-top: 5px;
+    margin-left: 7px;
+    margin-top: 6px;
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index 1c0e316e8..08cfa590b 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -2,15 +2,15 @@ import { library } from '@fortawesome/fontawesome-svg-core';
 import { faEdit, faEye, faTimes } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { observer } from "mobx-react";
-import { Document } from "../../../fields/Document";
-import { KeyStore } from '../../../fields/KeyStore';
-import { ListField } from "../../../fields/ListField";
-import { NumberField } from "../../../fields/NumberField";
 import { DocumentManager } from "../../util/DocumentManager";
 import { undoBatch } from "../../util/UndoManager";
 import { CollectionDockingView } from "../collections/CollectionDockingView";
 import './LinkBox.scss';
 import React = require("react");
+import { Doc } from '../../../new_fields/Doc';
+import { Cast, NumCast } from '../../../new_fields/Types';
+import { listSpec } from '../../../new_fields/Schema';
+import { action } from 'mobx';
 
 
 library.add(faEye);
@@ -18,9 +18,9 @@ library.add(faEdit);
 library.add(faTimes);
 
 interface Props {
-    linkDoc: Document;
+    linkDoc: Doc;
     linkName: String;
-    pairedDoc: Document;
+    pairedDoc: Doc;
     type: String;
     showEditor: () => void;
 }
@@ -29,62 +29,54 @@ interface Props {
 export class LinkBox extends React.Component<Props> {
 
     @undoBatch
-    onViewButtonPressed = (e: React.PointerEvent): void => {
+    onViewButtonPressed = async (e: React.PointerEvent): Promise<void> => {
         e.stopPropagation();
         let docView = DocumentManager.Instance.getDocumentView(this.props.pairedDoc);
         if (docView) {
             docView.props.focus(docView.props.Document);
         } else {
-            this.props.pairedDoc.GetAsync(KeyStore.AnnotationOn, (contextDoc: any) => {
-                if (!contextDoc) {
-                    CollectionDockingView.Instance.AddRightSplit(this.props.pairedDoc.MakeDelegate());
-                } else if (contextDoc instanceof Document) {
-                    this.props.pairedDoc.GetTAsync(KeyStore.Page, NumberField).then((pfield: any) => {
-                        contextDoc.GetTAsync(KeyStore.CurPage, NumberField).then((cfield: any) => {
-                            if (pfield !== cfield) {
-                                contextDoc.SetNumber(KeyStore.CurPage, pfield.Data);
-                            }
-                            let contextView = DocumentManager.Instance.getDocumentView(contextDoc);
-                            if (contextView) {
-                                contextView.props.focus(contextDoc);
-                            } else {
-                                CollectionDockingView.Instance.AddRightSplit(contextDoc);
-                            }
-                        });
-                    });
+            const contextDoc = await Cast(this.props.pairedDoc.annotationOn, Doc);
+            if (!contextDoc) {
+                CollectionDockingView.Instance.AddRightSplit(Doc.MakeDelegate(this.props.pairedDoc));
+            } else {
+                const page = NumCast(this.props.pairedDoc.page, undefined);
+                const curPage = NumCast(contextDoc.curPage, undefined);
+                if (page !== curPage) {
+                    contextDoc.curPage = page;
                 }
-            });
+                let contextView = DocumentManager.Instance.getDocumentView(contextDoc);
+                if (contextView) {
+                    contextDoc.panTransformType = "Ease";
+                    contextView.props.focus(contextDoc);
+                } else {
+                    CollectionDockingView.Instance.AddRightSplit(contextDoc);
+                }
+            }
         }
     }
 
     onEditButtonPressed = (e: React.PointerEvent): void => {
-        console.log("edit down");
         e.stopPropagation();
 
         this.props.showEditor();
     }
 
-    onDeleteButtonPressed = (e: React.PointerEvent): void => {
-        console.log("delete down");
+    @action
+    onDeleteButtonPressed = async (e: React.PointerEvent): Promise<void> => {
         e.stopPropagation();
-        this.props.linkDoc.GetTAsync(KeyStore.LinkedFromDocs, Document, field => {
-            if (field) {
-                field.GetTAsync<ListField<Document>>(KeyStore.LinkedToDocs, ListField, field => {
-                    if (field) {
-                        field.Data.splice(field.Data.indexOf(this.props.linkDoc));
-                    }
-                });
+        const [linkedFrom, linkedTo] = await Promise.all([Cast(this.props.linkDoc.linkedFrom, Doc), Cast(this.props.linkDoc.linkedTo, Doc)]);
+        if (linkedFrom) {
+            const linkedToDocs = Cast(linkedFrom.linkedToDocs, listSpec(Doc));
+            if (linkedToDocs) {
+                linkedToDocs.splice(linkedToDocs.indexOf(this.props.linkDoc), 1);
             }
-        });
-        this.props.linkDoc.GetTAsync(KeyStore.LinkedToDocs, Document, field => {
-            if (field) {
-                field.GetTAsync<ListField<Document>>(KeyStore.LinkedFromDocs, ListField, field => {
-                    if (field) {
-                        field.Data.splice(field.Data.indexOf(this.props.linkDoc));
-                    }
-                });
+        }
+        if (linkedTo) {
+            const linkedFromDocs = Cast(linkedTo.linkedFromDocs, listSpec(Doc));
+            if (linkedFromDocs) {
+                linkedFromDocs.splice(linkedFromDocs.indexOf(this.props.linkDoc), 1);
             }
-        });
+        }
     }
 
     render() {
@@ -102,8 +94,8 @@ export class LinkBox extends React.Component<Props> {
                 </div>
 
                 <div className="button-container">
-                    <div title="Follow Link" className="button" onPointerDown={this.onViewButtonPressed}>
-                        <FontAwesomeIcon className="fa-icon-view" icon="eye" size="sm" /></div>
+                    {/* <div title="Follow Link" className="button" onPointerDown={this.onViewButtonPressed}>
+                        <FontAwesomeIcon className="fa-icon-view" icon="eye" size="sm" /></div> */}
                     <div title="Edit Link" className="button" onPointerDown={this.onEditButtonPressed}>
                         <FontAwesomeIcon className="fa-icon-edit" icon="edit" size="sm" /></div>
                     <div title="Delete Link" className="button" onPointerDown={this.onDeleteButtonPressed}>
diff --git a/src/client/views/nodes/LinkEditor.scss b/src/client/views/nodes/LinkEditor.scss
index ea2e7289c..9629585d7 100644
--- a/src/client/views/nodes/LinkEditor.scss
+++ b/src/client/views/nodes/LinkEditor.scss
@@ -22,7 +22,7 @@
 
 .save-button {
     width: 50px;
-    height: 20px;
+    height: 22px;
     pointer-events: auto;
     background-color: $dark-color;
     color: $light-color;
@@ -38,6 +38,5 @@
 
 .save-button:hover {
     background: $main-accent;
-    transform: scale(1.05);
     cursor: pointer;
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/LinkEditor.tsx b/src/client/views/nodes/LinkEditor.tsx
index bde50fed8..71a423338 100644
--- a/src/client/views/nodes/LinkEditor.tsx
+++ b/src/client/views/nodes/LinkEditor.tsx
@@ -3,31 +3,30 @@ import React = require("react");
 import { SelectionManager } from "../../util/SelectionManager";
 import { observer } from "mobx-react";
 import './LinkEditor.scss';
-import { KeyStore } from '../../../fields/KeyStore';
 import { props } from "bluebird";
 import { DocumentView } from "./DocumentView";
-import { Document } from "../../../fields/Document";
-import { TextField } from "../../../fields/TextField";
 import { link } from "fs";
+import { StrCast } from "../../../new_fields/Types";
+import { Doc } from "../../../new_fields/Doc";
 
 interface Props {
-    linkDoc: Document;
+    linkDoc: Doc;
     showLinks: () => void;
 }
 
 @observer
 export class LinkEditor extends React.Component<Props> {
 
-    @observable private _nameInput: string = this.props.linkDoc.GetText(KeyStore.Title, "");
-    @observable private _descriptionInput: string = this.props.linkDoc.GetText(KeyStore.LinkDescription, "");
+    @observable private _nameInput: string = StrCast(this.props.linkDoc.title);
+    @observable private _descriptionInput: string = StrCast(this.props.linkDoc.linkDescription);
 
 
     onSaveButtonPressed = (e: React.PointerEvent): void => {
-        console.log("view down");
         e.stopPropagation();
 
-        this.props.linkDoc.SetData(KeyStore.Title, this._nameInput, TextField);
-        this.props.linkDoc.SetData(KeyStore.LinkDescription, this._descriptionInput, TextField);
+        let linkDoc = this.props.linkDoc.proto ? this.props.linkDoc.proto : this.props.linkDoc;
+        linkDoc.title = this._nameInput;
+        linkDoc.linkDescription = this._descriptionInput;
 
         this.props.showLinks();
     }
diff --git a/src/client/views/nodes/LinkMenu.tsx b/src/client/views/nodes/LinkMenu.tsx
index ac09da305..24901913d 100644
--- a/src/client/views/nodes/LinkMenu.tsx
+++ b/src/client/views/nodes/LinkMenu.tsx
@@ -1,15 +1,14 @@
 import { action, observable } from "mobx";
 import { observer } from "mobx-react";
-import { Document } from "../../../fields/Document";
-import { FieldWaiting } from "../../../fields/Field";
-import { Key } from "../../../fields/Key";
-import { KeyStore } from '../../../fields/KeyStore';
-import { ListField } from "../../../fields/ListField";
 import { DocumentView } from "./DocumentView";
 import { LinkBox } from "./LinkBox";
 import { LinkEditor } from "./LinkEditor";
 import './LinkMenu.scss';
 import React = require("react");
+import { Doc } from "../../../new_fields/Doc";
+import { Cast, FieldValue } from "../../../new_fields/Types";
+import { listSpec } from "../../../new_fields/Schema";
+import { Id } from "../../../new_fields/RefField";
 
 interface Props {
     docView: DocumentView;
@@ -19,28 +18,28 @@ interface Props {
 @observer
 export class LinkMenu extends React.Component<Props> {
 
-    @observable private _editingLink?: Document;
+    @observable private _editingLink?: Doc;
 
-    renderLinkItems(links: Document[], key: Key, type: string) {
+    renderLinkItems(links: Doc[], key: string, type: string) {
         return links.map(link => {
-            let doc = link.GetT(key, Document);
-            if (doc && doc !== FieldWaiting) {
-                return <LinkBox key={doc.Id} linkDoc={link} linkName={link.Title} pairedDoc={doc} showEditor={action(() => this._editingLink = link)} type={type} />;
+            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} />;
             }
         });
     }
 
     render() {
         //get list of links from document
-        let linkFrom: Document[] = this.props.docView.props.Document.GetData(KeyStore.LinkedFromDocs, ListField, []);
-        let linkTo: Document[] = this.props.docView.props.Document.GetData(KeyStore.LinkedToDocs, ListField, []);
+        let linkFrom = Cast(this.props.docView.props.Document.linkedFromDocs, listSpec(Doc), []).filter(d => d).map(d => d as Doc);
+        let linkTo = Cast(this.props.docView.props.Document.linkedToDocs, listSpec(Doc), []).filter(d => d).map(d => d as Doc);
         if (this._editingLink === undefined) {
             return (
                 <div id="linkMenu-container">
                     <input id="linkMenu-searchBar" type="text" placeholder="Search..."></input>
                     <div id="linkMenu-list">
-                        {this.renderLinkItems(linkTo, KeyStore.LinkedToDocs, "Destination: ")}
-                        {this.renderLinkItems(linkFrom, KeyStore.LinkedFromDocs, "Source: ")}
+                        {this.renderLinkItems(linkTo, "linkedTo", "Destination: ")}
+                        {this.renderLinkItems(linkFrom, "linkedFrom", "Source: ")}
                     </div>
                 </div>
             );
diff --git a/src/client/views/nodes/PDFBox.scss b/src/client/views/nodes/PDFBox.scss
index 830dfe6c6..3760e378a 100644
--- a/src/client/views/nodes/PDFBox.scss
+++ b/src/client/views/nodes/PDFBox.scss
@@ -4,6 +4,9 @@
     top: 0;
     left:0;
 }
+.react-pdf__Page__textContent span {
+    user-select: text;
+}
 .react-pdf__Document {
     position: absolute;
 }
@@ -12,6 +15,21 @@
     top: 0;
     left:0;
     z-index: 25;
+    pointer-events: all;
+}
+.pdfButton {
+    pointer-events: all;
+    width: 100px;
+    height:100px;
+}
+.pdfBox-cont {
+    pointer-events: none ;
+    span {
+        pointer-events: none !important;
+    }
+}
+.pdfBox-cont-interactive {
+    pointer-events: all;
 }
 .pdfBox-contentContainer {
     position: absolute;
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 81ceb37f6..caa66cbeb 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -1,24 +1,26 @@
 import * as htmlToImage from "html-to-image";
-import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
+import { action, computed, IReactionDisposer, observable, reaction, Reaction, trace } from 'mobx';
 import { observer } from "mobx-react";
 import 'react-image-lightbox/style.css';
 import Measure from "react-measure";
 //@ts-ignore
 import { Document, Page } from "react-pdf";
 import 'react-pdf/dist/Page/AnnotationLayer.css';
-import { FieldWaiting, Opt } from '../../../fields/Field';
-import { ImageField } from '../../../fields/ImageField';
-import { KeyStore } from '../../../fields/KeyStore';
-import { PDFField } from '../../../fields/PDFField';
 import { RouteStore } from "../../../server/RouteStore";
 import { Utils } from '../../../Utils';
 import { Annotation } from './Annotation';
 import { FieldView, FieldViewProps } from './FieldView';
-import "./ImageBox.scss";
 import "./PDFBox.scss";
-import { Sticky } from './Sticky'; //you should look at sticky and annotation, because they are used here
 import React = require("react");
 import { SelectionManager } from "../../util/SelectionManager";
+import { Cast, FieldValue, NumCast } from "../../../new_fields/Types";
+import { Opt } from "../../../new_fields/Doc";
+import { DocComponent } from "../DocComponent";
+import { makeInterface } from "../../../new_fields/Schema";
+import { positionSchema } from "./DocumentView";
+import { pageSchema } from "./ImageBox";
+import { ImageField, PdfField } from "../../../new_fields/URLField";
+import { InkingControl } from "../InkingControl";
 
 /** ALSO LOOK AT: Annotation.tsx, Sticky.tsx
  * This method renders PDF and puts all kinds of functionalities such as annotation, highlighting, 
@@ -40,46 +42,20 @@ import { SelectionManager } from "../../util/SelectionManager";
  *          4) another method: click on highlight first and then drag on your desired text
  *          5) To make another highlight, you need to reclick on the button 
  * 
- * Draw:
- *          1) click draw and select color. then just draw like there's no tomorrow.
- *          2) once you finish drawing your masterpiece, just reclick on the draw button to end your drawing session. 
- * 
- * Pagination:
- *          1) click on arrows. You'll notice that stickies will stay in those page. But... highlights won't. 
- *          2) to test this out, make few area/stickies and then click on next page then come back. You'll see that they are all saved. 
- *
- * 
  * written by: Andrew Kim 
  */
+
+type PdfDocument = makeInterface<[typeof positionSchema, typeof pageSchema]>;
+const PdfDocument = makeInterface(positionSchema, pageSchema);
+
 @observer
-export class PDFBox extends React.Component<FieldViewProps> {
+export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocument) {
     public static LayoutString() { return FieldView.LayoutString(PDFBox); }
 
     private _mainDiv = React.createRef<HTMLDivElement>();
-    private _pdf = React.createRef<HTMLCanvasElement>();
 
     @observable private _renderAsSvg = true;
 
-    //very useful for keeping track of X and y position throughout the PDF Canvas
-    private initX: number = 0;
-    private initY: number = 0;
-
-    //checks if tool is on
-    private _toolOn: boolean = false; //checks if tool is on
-    private _pdfContext: any = null; //gets pdf context
-    private bool: Boolean = false; //general boolean debounce
-    private currSpan: any;//keeps track of current span (for highlighting)
-
-    private _currTool: any; //keeps track of current tool button reference
-    private _drawToolOn: boolean = false; //boolean that keeps track of the drawing tool 
-    private _drawTool = React.createRef<HTMLButtonElement>();//drawing tool button reference
-
-    private _colorTool = React.createRef<HTMLButtonElement>(); //color button reference
-    private _currColor: string = "black"; //current color that user selected (for ink/pen)
-
-    private _highlightTool = React.createRef<HTMLButtonElement>(); //highlighter button reference
-    private _highlightToolOn: boolean = false;
-    private _pdfCanvas: any;
     private _reactionDisposer: Opt<IReactionDisposer>;
 
     @observable private _perPageInfo: Object[] = []; //stores pageInfo
@@ -89,8 +65,8 @@ export class PDFBox extends React.Component<FieldViewProps> {
     @observable private _interactive: boolean = false;
     @observable private _loaded: boolean = false;
 
-    @computed private get curPage() { return this.props.Document.GetNumber(KeyStore.CurPage, 1); }
-    @computed private get thumbnailPage() { return this.props.Document.GetNumber(KeyStore.ThumbnailPage, -1); }
+    @computed private get curPage() { return FieldValue(this.Document.curPage, 1); }
+    @computed private get thumbnailPage() { return Cast(this.props.Document.thumbnailPage, "number", -1); }
 
     componentDidMount() {
         this._reactionDisposer = reaction(
@@ -111,43 +87,6 @@ export class PDFBox extends React.Component<FieldViewProps> {
         }
     }
 
-    /**
-     * selection tool used for area highlighting (stickies). Kinda temporary
-     */
-    selectionTool = () => {
-        this._toolOn = true;
-    }
-    /**
-     * when user draws on the canvas. When mouse pointer is down 
-     */
-    drawDown = (e: PointerEvent) => {
-        this.initX = e.offsetX;
-        this.initY = e.offsetY;
-        this._pdfContext.beginPath();
-        this._pdfContext.lineTo(this.initX, this.initY);
-        this._pdfContext.strokeStyle = this._currColor;
-        this._pdfCanvas.addEventListener("pointermove", this.drawMove);
-        this._pdfCanvas.addEventListener("pointerup", this.drawUp);
-
-    }
-    //when user drags 
-    drawMove = (e: PointerEvent): void => {
-        //x and y mouse movement
-        let x = this.initX += e.movementX,
-            y = this.initY += e.movementY;
-        //connects the point 
-        this._pdfContext.lineTo(x, y);
-        this._pdfContext.stroke();
-    }
-
-    drawUp = (e: PointerEvent) => {
-        this._pdfContext.closePath();
-        this._pdfCanvas.removeEventListener("pointermove", this.drawMove);
-        this._pdfCanvas.removeEventListener("pointerdown", this.drawDown);
-        this._pdfCanvas.addEventListener("pointerdown", this.drawDown);
-    }
-
-
     /**
      * highlighting helper function
      */
@@ -183,7 +122,7 @@ export class PDFBox extends React.Component<FieldViewProps> {
                                 child.id = "highlighted";
                                 //@ts-ignore
                                 obj.spans.push(child);
-                                child.addEventListener("mouseover", this.onEnter); //adds mouseover annotation handler
+                                // child.addEventListener("mouseover", this.onEnter); //adds mouseover annotation handler
                             }
                         });
                     }
@@ -206,7 +145,7 @@ export class PDFBox extends React.Component<FieldViewProps> {
                     child.id = "highlighted";
                     //@ts-ignore
                     temp.spans.push(child);
-                    child.addEventListener("mouseover", this.onEnter); //adds mouseover annotation handler
+                    // child.addEventListener("mouseover", this.onEnter); //adds mouseover annotation handler
                 }
             });
 
@@ -272,11 +211,21 @@ export class PDFBox extends React.Component<FieldViewProps> {
      * controls the area highlighting (stickies) Kinda temporary
      */
     onPointerDown = (e: React.PointerEvent) => {
-        if (this._toolOn) {
-            let mouse = e.nativeEvent;
-            this.initX = mouse.offsetX;
-            this.initY = mouse.offsetY;
-
+        if (this.props.isSelected() && !InkingControl.Instance.selectedTool && e.buttons === 1) {
+            if (e.altKey) {
+                this._alt = true;
+            } else {
+                if (e.metaKey) {
+                    e.stopPropagation();
+                }
+            }
+            document.removeEventListener("pointerup", this.onPointerUp);
+            document.addEventListener("pointerup", this.onPointerUp);
+        }
+        if (this.props.isSelected() && e.buttons === 2) {
+            this._alt = true;
+            document.removeEventListener("pointerup", this.onPointerUp);
+            document.addEventListener("pointerup", this.onPointerUp);
         }
     }
 
@@ -284,110 +233,28 @@ export class PDFBox extends React.Component<FieldViewProps> {
      * controls area highlighting and partially highlighting. Kinda temporary
      */
     @action
-    onPointerUp = (e: React.PointerEvent) => {
-        if (this._highlightToolOn) {
+    onPointerUp = (e: PointerEvent) => {
+        this._alt = false;
+        document.removeEventListener("pointerup", this.onPointerUp);
+        if (this.props.isSelected()) {
             this.highlight("rgba(76, 175, 80, 0.3)"); //highlights to this default color. 
-            this._highlightToolOn = false;
-        }
-        if (this._toolOn) {
-            let mouse = e.nativeEvent;
-            let finalX = mouse.offsetX;
-            let finalY = mouse.offsetY;
-            let width = Math.abs(finalX - this.initX); //width
-            let height = Math.abs(finalY - this.initY); //height
-
-            //these two if statements are bidirectional dragging. You can drag from any point to another point and generate sticky
-            if (finalX < this.initX) {
-                this.initX = finalX;
-            }
-            if (finalY < this.initY) {
-                this.initY = finalY;
-            }
-
-            if (this._mainDiv.current) {
-                let sticky = <Sticky key={Utils.GenerateGuid()} Height={height} Width={width} X={this.initX} Y={this.initY} />;
-                this._pageInfo.area.push(sticky);
-            }
-            this._toolOn = false;
         }
         this._interactive = true;
     }
 
-    /**
-     * starts drawing the line when user presses down. 
-     */
-    onDraw = () => {
-        if (this._currTool !== null) {
-            this._currTool.style.backgroundColor = "grey";
-        }
-
-        if (this._drawTool.current) {
-            this._currTool = this._drawTool.current;
-            if (this._drawToolOn) {
-                this._drawToolOn = false;
-                this._pdfCanvas.removeEventListener("pointerdown", this.drawDown);
-                this._pdfCanvas.removeEventListener("pointerup", this.drawUp);
-                this._pdfCanvas.removeEventListener("pointermove", this.drawMove);
-                this._drawTool.current.style.backgroundColor = "grey";
-            } else {
-                this._drawToolOn = true;
-                this._pdfCanvas.addEventListener("pointerdown", this.drawDown);
-                this._drawTool.current.style.backgroundColor = "cyan";
-            }
-        }
-    }
-
-
-    /**
-     * for changing color (for ink/pen)
-     */
-    onColorChange = (e: React.PointerEvent) => {
-        if (e.currentTarget.innerHTML === "Red") {
-            this._currColor = "red";
-        } else if (e.currentTarget.innerHTML === "Blue") {
-            this._currColor = "blue";
-        } else if (e.currentTarget.innerHTML === "Green") {
-            this._currColor = "green";
-        } else if (e.currentTarget.innerHTML === "Black") {
-            this._currColor = "black";
-        }
-
-    }
-
-
-    /**
-     * For highlighting (text drag highlighting)
-     */
-    onHighlight = () => {
-        this._drawToolOn = false;
-        if (this._currTool !== null) {
-            this._currTool.style.backgroundColor = "grey";
-        }
-        if (this._highlightTool.current) {
-            this._currTool = this._drawTool.current;
-            if (this._highlightToolOn) {
-                this._highlightToolOn = false;
-                this._highlightTool.current.style.backgroundColor = "grey";
-            } else {
-                this._highlightToolOn = true;
-                this._highlightTool.current.style.backgroundColor = "orange";
-            }
-        }
-    }
 
 
     @action
     saveThumbnail = () => {
         this._renderAsSvg = false;
         setTimeout(() => {
-            var me = this;
-            let nwidth = me.props.Document.GetNumber(KeyStore.NativeWidth, 0);
-            let nheight = me.props.Document.GetNumber(KeyStore.NativeHeight, 0);
+            let nwidth = FieldValue(this.Document.nativeWidth, 0);
+            let nheight = FieldValue(this.Document.nativeHeight, 0);
             htmlToImage.toPng(this._mainDiv.current!, { width: nwidth, height: nheight, quality: 1 })
                 .then(action((dataUrl: string) => {
-                    me.props.Document.SetData(KeyStore.Thumbnail, new URL(dataUrl), ImageField);
-                    me.props.Document.SetNumber(KeyStore.ThumbnailPage, me.props.Document.GetNumber(KeyStore.CurPage, -1));
-                    me._renderAsSvg = true;
+                    this.props.Document.thumbnail = new ImageField(new URL(dataUrl));
+                    this.props.Document.thumbnailPage = FieldValue(this.Document.curPage, -1);
+                    this._renderAsSvg = true;
                 }))
                 .catch(function (error: any) {
                     console.error('oops, something went wrong!', error);
@@ -397,24 +264,8 @@ export class PDFBox extends React.Component<FieldViewProps> {
 
     @action
     onLoaded = (page: any) => {
-        if (this._mainDiv.current) {
-            this._mainDiv.current.childNodes.forEach((element) => {
-                if (element.nodeName === "DIV") {
-                    element.childNodes[0].childNodes.forEach((e) => {
-
-                        if (e instanceof HTMLCanvasElement) {
-                            this._pdfCanvas = e;
-                            this._pdfContext = e.getContext("2d");
-
-                        }
-
-                    });
-                }
-            });
-        }
-
         // bcz: the number of pages should really be set when the document is imported.
-        this.props.Document.SetNumber(KeyStore.NumPages, page._transport.numPages);
+        this.props.Document.numPages = page._transport.numPages;
         if (this._perPageInfo.length === 0) { //Makes sure it only runs once
             this._perPageInfo = [...Array(page._transport.numPages)];
         }
@@ -424,31 +275,38 @@ export class PDFBox extends React.Component<FieldViewProps> {
     @action
     setScaling = (r: any) => {
         // bcz: the nativeHeight should really be set when the document is imported.
-        //      also, the native dimensions could be different for different pages of the PDF
+        //      also, the native dimensions could be different for different pages of the canvas
         //      so this design is flawed.
-        var nativeWidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 0);
-        if (!this.props.Document.GetNumber(KeyStore.NativeHeight, 0)) {
+        var nativeWidth = FieldValue(this.Document.nativeWidth, 0);
+        if (!FieldValue(this.Document.nativeHeight, 0)) {
             var nativeHeight = nativeWidth * r.entry.height / r.entry.width;
-            this.props.Document.SetNumber(KeyStore.Height, nativeHeight / nativeWidth * this.props.Document.GetNumber(KeyStore.Width, 0));
-            this.props.Document.SetNumber(KeyStore.NativeHeight, nativeHeight);
+            this.props.Document.height = nativeHeight / nativeWidth * FieldValue(this.Document.width, 0);
+            this.props.Document.nativeHeight = nativeHeight;
         }
     }
-
+    renderHeight = 2400;
+    @computed
+    get pdfPage() {
+        return <Page height={this.renderHeight} pageNumber={this.curPage} onLoadSuccess={this.onLoaded} />;
+    }
     @computed
     get pdfContent() {
         let page = this.curPage;
         const renderHeight = 2400;
-        let pdfUrl = this.props.Document.GetT(this.props.fieldKey, PDFField);
-        let xf = this.props.Document.GetNumber(KeyStore.NativeHeight, 0) / renderHeight;
+        let pdfUrl = Cast(this.props.Document[this.props.fieldKey], PdfField);
+        let xf = FieldValue(this.Document.nativeHeight, 0) / renderHeight;
+        let body = NumCast(this.props.Document.nativeHeight) ?
+            this.pdfPage :
+            <Measure onResize={this.setScaling}>
+                {({ measureRef }) =>
+                    <div className="pdfBox-page" ref={measureRef}>
+                        {this.pdfPage}
+                    </div>
+                }
+            </Measure>;
         return <div className="pdfBox-contentContainer" key="container" style={{ transform: `scale(${xf}, ${xf})` }}>
-            <Document file={window.origin + RouteStore.corsProxy + `/${pdfUrl}`} renderMode={this._renderAsSvg ? "svg" : ""}>
-                <Measure onResize={this.setScaling}>
-                    {({ measureRef }) =>
-                        <div className="pdfBox-page" ref={measureRef}>
-                            <Page height={renderHeight} pageNumber={page} onLoadSuccess={this.onLoaded} />
-                        </div>
-                    }
-                </Measure>
+            <Document file={window.origin + RouteStore.corsProxy + `/${pdfUrl}`} renderMode={this._renderAsSvg ? "svg" : "canvas"}>
+                {body}
             </Document>
         </div >;
     }
@@ -456,8 +314,8 @@ export class PDFBox extends React.Component<FieldViewProps> {
     @computed
     get pdfRenderer() {
         let proxy = this._loaded ? (null) : this.imageProxyRenderer;
-        let pdfUrl = this.props.Document.GetT(this.props.fieldKey, PDFField);
-        if ((!this._interactive && proxy) || !pdfUrl || pdfUrl === FieldWaiting) {
+        let pdfUrl = Cast(this.props.Document[this.props.fieldKey], PdfField);
+        if ((!this._interactive && proxy) || !pdfUrl) {
             return proxy;
         }
         return [
@@ -470,18 +328,32 @@ export class PDFBox extends React.Component<FieldViewProps> {
 
     @computed
     get imageProxyRenderer() {
-        let thumbField = this.props.Document.Get(KeyStore.Thumbnail);
+        let thumbField = this.props.Document.thumbnail;
         if (thumbField) {
-            let path = thumbField === FieldWaiting || this.thumbnailPage !== this.curPage ? "https://image.flaticon.com/icons/svg/66/66163.svg" :
-                thumbField instanceof ImageField ? thumbField.Data.href : "http://cs.brown.edu/people/bcz/prairie.jpg";
+            let path = this.thumbnailPage !== this.curPage ? "https://image.flaticon.com/icons/svg/66/66163.svg" :
+                thumbField instanceof ImageField ? thumbField.url.href : "http://cs.brown.edu/people/bcz/prairie.jpg";
             return <img src={path} width="100%" />;
         }
         return (null);
     }
-
+    @observable _alt = false;
+    @action
+    onKeyDown = (e: React.KeyboardEvent) => {
+        if (e.key === "Alt") {
+            this._alt = true;
+        }
+    }
+    @action
+    onKeyUp = (e: React.KeyboardEvent) => {
+        if (e.key === "Alt") {
+            this._alt = false;
+        }
+    }
     render() {
+        trace();
+        let classname = "pdfBox-cont" + (this.props.isSelected() && !InkingControl.Instance.selectedTool && !this._alt ? "-interactive" : "");
         return (
-            <div className="pdfBox-cont" ref={this._mainDiv} onPointerDown={this.onPointerDown} onPointerUp={this.onPointerUp} >
+            <div className={classname} tabIndex={0} ref={this._mainDiv} onPointerDown={this.onPointerDown} onKeyDown={this.onKeyDown} onKeyUp={this.onKeyUp} >
                 {this.pdfRenderer}
             </div >
         );
diff --git a/src/client/views/nodes/Sticky.tsx b/src/client/views/nodes/Sticky.tsx
deleted file mode 100644
index 11719831b..000000000
--- a/src/client/views/nodes/Sticky.tsx
+++ /dev/null
@@ -1,83 +0,0 @@
-import "react-image-lightbox/style.css"; // This only needs to be imported once in your app
-import React = require("react");
-import { observer } from "mobx-react";
-import "react-pdf/dist/Page/AnnotationLayer.css";
-
-interface IProps {
-  Height: number;
-  Width: number;
-  X: number;
-  Y: number;
-}
-
-/**
- * Sticky, also known as area highlighting, is used to highlight large selection of the PDF file.
- * Improvements that could be made: maybe store line array and store that somewhere for future rerendering.
- *
- * Written By: Andrew Kim
- */
-@observer
-export class Sticky extends React.Component<IProps> {
-  private initX: number = 0;
-  private initY: number = 0;
-
-  private _ref = React.createRef<HTMLCanvasElement>();
-  private ctx: any; //context that keeps track of sticky canvas
-
-  /**
-   * drawing. Registers the first point that user clicks when mouse button is pressed down on canvas
-   */
-  drawDown = (e: React.PointerEvent) => {
-    if (this._ref.current) {
-      this.ctx = this._ref.current.getContext("2d");
-      let mouse = e.nativeEvent;
-      this.initX = mouse.offsetX;
-      this.initY = mouse.offsetY;
-      this.ctx.beginPath();
-      this.ctx.lineTo(this.initX, this.initY);
-      this.ctx.strokeStyle = "black";
-      document.addEventListener("pointermove", this.drawMove);
-      document.addEventListener("pointerup", this.drawUp);
-    }
-  }
-
-  //when user drags
-  drawMove = (e: PointerEvent): void => {
-    //x and y mouse movement
-    let x = (this.initX += e.movementX),
-      y = (this.initY += e.movementY);
-    //connects the point
-    this.ctx.lineTo(x, y);
-    this.ctx.stroke();
-  }
-
-  /**
-   * when user lifts the mouse, the drawing ends
-   */
-  drawUp = (e: PointerEvent) => {
-    this.ctx.closePath();
-    console.log(this.ctx);
-    document.removeEventListener("pointermove", this.drawMove);
-  }
-
-  render() {
-    return (
-      <div onPointerDown={this.drawDown}>
-        <canvas
-          ref={this._ref}
-          height={this.props.Height}
-          width={this.props.Width}
-          style={{
-            position: "absolute",
-            top: "20px",
-            left: "0px",
-            zIndex: 1,
-            background: "yellow",
-            transform: `translate(${this.props.X}px, ${this.props.Y}px)`,
-            opacity: 0.4
-          }}
-        />
-      </div>
-    );
-  }
-}
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 9d7c2bc56..422508f90 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -1,78 +1,82 @@
 import React = require("react");
 import { observer } from "mobx-react";
-import { FieldWaiting, Opt } from '../../../fields/Field';
-import { VideoField } from '../../../fields/VideoField';
 import { FieldView, FieldViewProps } from './FieldView';
 import "./VideoBox.scss";
+import { action, computed, trace } from "mobx";
+import { DocComponent } from "../DocComponent";
+import { positionSchema } from "./DocumentView";
+import { makeInterface } from "../../../new_fields/Schema";
+import { pageSchema } from "./ImageBox";
+import { Cast, FieldValue, NumCast, ToConstructor, ListSpec } from "../../../new_fields/Types";
+import { VideoField } from "../../../new_fields/URLField";
 import Measure from "react-measure";
-import { action, trace, observable, IReactionDisposer, computed, reaction } from "mobx";
-import { KeyStore } from "../../../fields/KeyStore";
-import { number } from "prop-types";
+import "./VideoBox.scss";
+import { Field, FieldResult, Opt } from "../../../new_fields/Doc";
+
+type VideoDocument = makeInterface<[typeof positionSchema, typeof pageSchema]>;
+const VideoDocument = makeInterface(positionSchema, pageSchema);
 
 @observer
-export class VideoBox extends React.Component<FieldViewProps> {
+export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoDocument) {
 
-    private _reactionDisposer: Opt<IReactionDisposer>;
-    private _videoRef = React.createRef<HTMLVideoElement>();
+    private _videoRef: HTMLVideoElement | null = null;
+    private _loaded: boolean = false;
+    private get initialTimecode() { return FieldValue(this.Document.curPage, -1); }
     public static LayoutString() { return FieldView.LayoutString(VideoBox); }
 
-    constructor(props: FieldViewProps) {
-        super(props);
+    public get player(): HTMLVideoElement | undefined {
+        if (this._videoRef) {
+            return this._videoRef;
+        }
     }
-
-    @computed private get curPage() { return this.props.Document.GetNumber(KeyStore.CurPage, -1); }
-
-
-    _loaded: boolean = false;
-
     @action
     setScaling = (r: any) => {
         if (this._loaded) {
             // bcz: the nativeHeight should really be set when the document is imported.
-            //      also, the native dimensions could be different for different pages of the PDF
-            //      so this design is flawed.
-            var nativeWidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 0);
-            var nativeHeight = this.props.Document.GetNumber(KeyStore.NativeHeight, 0);
+            var nativeWidth = FieldValue(this.Document.nativeWidth, 0);
+            var nativeHeight = FieldValue(this.Document.nativeHeight, 0);
             var newNativeHeight = nativeWidth * r.entry.height / r.entry.width;
             if (!nativeHeight && newNativeHeight !== nativeHeight && !isNaN(newNativeHeight)) {
-                this.props.Document.SetNumber(KeyStore.Height, newNativeHeight / nativeWidth * this.props.Document.GetNumber(KeyStore.Width, 0));
-                this.props.Document.SetNumber(KeyStore.NativeHeight, newNativeHeight);
+                this.Document.height = newNativeHeight / nativeWidth * FieldValue(this.Document.width, 0);
+                this.Document.nativeHeight = newNativeHeight;
             }
         } else {
             this._loaded = true;
         }
     }
 
-    get player(): HTMLVideoElement | undefined {
-        return this._videoRef.current ? this._videoRef.current.getElementsByTagName("video")[0] : undefined;
+    componentDidMount() {
+        if (this.props.setVideoBox) this.props.setVideoBox(this);
     }
 
     @action
     setVideoRef = (vref: HTMLVideoElement | null) => {
-        if (this.curPage >= 0 && vref) {
-            vref.currentTime = this.curPage;
-            (vref as any).AHackBecauseSomethingResetsTheVideoToZero = this.curPage;
+        this._videoRef = vref;
+        if (this.initialTimecode >= 0 && vref) {
+            vref.currentTime = this.initialTimecode;
         }
     }
+    videoContent(path: string) {
+        return <video className="videobox-cont" ref={this.setVideoRef}>
+            <source src={path} type="video/mp4" />
+            Not supported.
+        </video>;
+    }
 
     render() {
-        let field = this.props.Document.GetT(this.props.fieldKey, VideoField);
-        if (!field || field === FieldWaiting) {
+        let field = Cast(this.Document[this.props.fieldKey], VideoField);
+        if (!field) {
             return <div>Loading</div>;
         }
-        let path = field.Data.href;
-        trace();
-        return (
+        let content = this.videoContent(field.url.href);
+        return NumCast(this.props.Document.nativeHeight) ?
+            content :
             <Measure onResize={this.setScaling}>
                 {({ measureRef }) =>
                     <div style={{ width: "100%", height: "auto" }} ref={measureRef}>
-                        <video className="videobox-cont" ref={this.setVideoRef}>
-                            <source src={path} type="video/mp4" />
-                            Not supported.
-                        </video>
+                        {content}
                     </div>
                 }
-            </Measure>
-        );
+            </Measure>;
     }
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss
index 2ad1129a4..eb09b0693 100644
--- a/src/client/views/nodes/WebBox.scss
+++ b/src/client/views/nodes/WebBox.scss
@@ -1,12 +1,19 @@
 
-.webBox-cont {
+.webBox-cont, .webBox-cont-interactive{
     padding: 0vw;
     position: absolute;
     top: 0;
     left:0;
     width: 100%;
     height: 100%;
-    overflow: scroll;
+    overflow: auto;
+    pointer-events: none ;
+}
+.webBox-cont-interactive {
+    pointer-events: all;
+    span {
+        user-select: text !important;
+    }
 }
 
 #webBox-htmlSpan {
@@ -15,6 +22,12 @@
     left:0;
 }
 
+.webBox-overlay {
+    width: 100%;
+    height: 100%;
+    position: absolute;
+}
+
 .webBox-button {
     padding : 0vw;
     border: none;
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 1edb4d826..2239a8e38 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -1,23 +1,18 @@
 import "./WebBox.scss";
 import React = require("react");
-import { WebField } from '../../../fields/WebField';
 import { FieldViewProps, FieldView } from './FieldView';
-import { FieldWaiting } from '../../../fields/Field';
+import { HtmlField } from "../../../new_fields/HtmlField";
+import { WebField } from "../../../new_fields/URLField";
 import { observer } from "mobx-react";
-import { computed } from 'mobx';
-import { KeyStore } from '../../../fields/KeyStore';
+import { computed, reaction, IReactionDisposer } from 'mobx';
+import { DocumentDecorations } from "../DocumentDecorations";
+import { InkingControl } from "../InkingControl";
 
 @observer
 export class WebBox extends React.Component<FieldViewProps> {
 
     public static LayoutString() { return FieldView.LayoutString(WebBox); }
 
-    constructor(props: FieldViewProps) {
-        super(props);
-    }
-
-    @computed get html(): string { return this.props.Document.GetHtml(KeyStore.Data, ""); }
-
     _ignore = 0;
     onPreWheel = (e: React.WheelEvent) => {
         this._ignore = e.timeStamp;
@@ -36,22 +31,29 @@ export class WebBox extends React.Component<FieldViewProps> {
         }
     }
     render() {
-        let field = this.props.Document.Get(this.props.fieldKey);
-        let path = field === FieldWaiting ? "https://image.flaticon.com/icons/svg/66/66163.svg" :
-            field instanceof WebField ? field.Data.href : "https://crossorigin.me/" + "https://cs.brown.edu";
-
+        let field = this.props.Document[this.props.fieldKey];
+        let view;
+        if (field instanceof HtmlField) {
+            view = <span id="webBox-htmlSpan" dangerouslySetInnerHTML={{ __html: field.html }} />;
+        } else if (field instanceof WebField) {
+            view = <iframe src={field.url.href} style={{ position: "absolute", width: "100%", height: "100%" }} />;
+        } else {
+            view = <iframe src={"https://crossorigin.me/https://cs.brown.edu"} style={{ position: "absolute", width: "100%", height: "100%" }} />;
+        }
         let content =
             <div style={{ width: "100%", height: "100%", position: "absolute" }} onWheel={this.onPostWheel} onPointerDown={this.onPostPointer} onPointerMove={this.onPostPointer} onPointerUp={this.onPostPointer}>
-                {this.html ? <span id="webBox-htmlSpan" dangerouslySetInnerHTML={{ __html: this.html }} /> :
-                    <iframe src={path} style={{ position: "absolute", width: "100%", height: "100%" }} />}
+                {view}
             </div>;
 
+        let frozen = !this.props.isSelected() || DocumentDecorations.Instance.Interacting;
+
+        let classname = "webBox-cont" + (this.props.isSelected() && !InkingControl.Instance.selectedTool && !DocumentDecorations.Instance.Interacting ? "-interactive" : "");
         return (
             <>
-                <div className="webBox-cont"  >
+                <div className={classname}  >
                     {content}
                 </div>
-                {this.props.isSelected() ? (null) : <div onWheel={this.onPreWheel} onPointerDown={this.onPrePointer} onPointerMove={this.onPrePointer} onPointerUp={this.onPrePointer} style={{ width: "100%", height: "100%", position: "absolute" }} />}
+                {!frozen ? (null) : <div className="webBox-overlay" onWheel={this.onPreWheel} onPointerDown={this.onPrePointer} onPointerMove={this.onPrePointer} onPointerUp={this.onPrePointer} />}
             </>);
     }
 }
\ No newline at end of file
diff --git a/src/debug/Test.tsx b/src/debug/Test.tsx
index 11f2b0c4e..04ef00722 100644
--- a/src/debug/Test.tsx
+++ b/src/debug/Test.tsx
@@ -1,29 +1,80 @@
 import * as React from 'react';
 import * as ReactDOM from 'react-dom';
-import JsxParser from 'react-jsx-parser';
+import { SerializationHelper } from '../client/util/SerializationHelper';
+import { createSchema, makeInterface, makeStrictInterface, listSpec } from '../new_fields/Schema';
+import { ImageField } from '../new_fields/URLField';
+import { Doc } from '../new_fields/Doc';
+import { List } from '../new_fields/List';
 
-class Hello extends React.Component<{ firstName: string, lastName: string }> {
-    render() {
-        return <div>Hello {this.props.firstName} {this.props.lastName}</div>;
-    }
-}
+const schema1 = createSchema({
+    hello: "number",
+    test: "string",
+    fields: "boolean",
+    url: ImageField,
+    testDoc: Doc
+});
+
+type TestDoc = makeInterface<[typeof schema1]>;
+const TestDoc: (doc?: Doc) => TestDoc = makeInterface(schema1);
+
+const schema2 = createSchema({
+    hello: ImageField,
+    test: "boolean",
+    fields: listSpec("number"),
+    url: "number",
+    testDoc: ImageField
+});
+
+const Test2Doc = makeStrictInterface(schema2);
+type Test2Doc = makeStrictInterface<typeof schema2>;
+
+const assert = (bool: boolean) => {
+    if (!bool) throw new Error();
+};
 
 class Test extends React.Component {
+    onClick = () => {
+        const url = new ImageField(new URL("http://google.com"));
+        const doc = new Doc();
+        const doc2 = new Doc();
+        doc.hello = 5;
+        doc.fields = "test";
+        doc.test = "hello doc";
+        doc.url = url;
+        doc.testDoc = doc2;
+
+
+        const test1: TestDoc = TestDoc(doc);
+        assert(test1.hello === 5);
+        assert(test1.fields === undefined);
+        assert(test1.test === "hello doc");
+        assert(test1.url === url);
+        assert(test1.testDoc === doc2);
+        test1.myField = 20;
+        assert(test1.myField === 20);
+
+        const test2: Test2Doc = Test2Doc(doc);
+        assert(test2.hello === undefined);
+        // assert(test2.fields === "test");
+        assert(test2.test === undefined);
+        assert(test2.url === undefined);
+        assert(test2.testDoc === undefined);
+        test2.url = 35;
+        assert(test2.url === 35);
+        const l = new List<Doc>();
+        //TODO push, and other array functions don't go through the proxy
+        l.push(doc2);
+        //TODO currently length, and any other string fields will get serialized
+        doc.list = l;
+        console.log(l.slice());
+    }
+
     render() {
-        let jsx = "<Hello {...props}/>";
-        let bindings = {
-            props: {
-                firstName: "First",
-                lastName: "Last"
-            }
-        };
-        return <JsxParser jsx={jsx} bindings={bindings} components={{ Hello }}></JsxParser>;
+        return <button onClick={this.onClick}>Click me</button>;
     }
 }
 
-ReactDOM.render((
-    <div style={{ position: "absolute", width: "100%", height: "100%" }}>
-        <Test />
-    </div>),
+ReactDOM.render(
+    <Test />,
     document.getElementById('root')
 );
\ No newline at end of file
diff --git a/src/debug/Viewer.tsx b/src/debug/Viewer.tsx
index 857da1ebb..4cac09dee 100644
--- a/src/debug/Viewer.tsx
+++ b/src/debug/Viewer.tsx
@@ -3,190 +3,184 @@ import "normalize.css";
 import * as React from 'react';
 import * as ReactDOM from 'react-dom';
 import { observer } from 'mobx-react';
-import { Document } from '../fields/Document';
-import { BasicField } from '../fields/BasicField';
-import { ListField } from '../fields/ListField';
-import { Key } from '../fields/Key';
-import { Opt, Field } from '../fields/Field';
-import { Server } from '../client/Server';
-
-configure({
-    enforceActions: "observed"
-});
-
-@observer
-class FieldViewer extends React.Component<{ field: BasicField<any> }> {
-    render() {
-        return <span>{JSON.stringify(this.props.field.Data)} ({this.props.field.Id})</span>;
-    }
-}
-
-@observer
-class KeyViewer extends React.Component<{ field: Key }> {
-    render() {
-        return this.props.field.Name;
-    }
-}
-
-@observer
-class ListViewer extends React.Component<{ field: ListField<Field> }>{
-    @observable
-    expanded = false;
-
-    render() {
-        let content;
-        if (this.expanded) {
-            content = (
-                <div>
-                    {this.props.field.Data.map(field => <DebugViewer fieldId={field.Id} key={field.Id} />)}
-                </div>
-            );
-        } else {
-            content = <>[...] ({this.props.field.Id})</>;
-        }
-        return (
-            <div>
-                <button onClick={action(() => this.expanded = !this.expanded)}>Toggle</button>
-                {content}
-            </div >
-        );
-    }
-}
-
-@observer
-class DocumentViewer extends React.Component<{ field: Document }> {
-    private keyMap: ObservableMap<string, Key> = new ObservableMap;
-
-    private disposer?: Lambda;
-
-    componentDidMount() {
-        let f = () => {
-            Array.from(this.props.field._proxies.keys()).forEach(id => {
-                if (!this.keyMap.has(id)) {
-                    Server.GetField(id, (field) => {
-                        if (field && field instanceof Key) {
-                            this.keyMap.set(id, field);
-                        }
-                    });
-                }
-            });
-        };
-        this.disposer = this.props.field._proxies.observe(f);
-        f();
-    }
-
-    componentWillUnmount() {
-        if (this.disposer) {
-            this.disposer();
-        }
-    }
-
-    render() {
-        let fields = Array.from(this.props.field._proxies.entries()).map(kv => {
-            let key = this.keyMap.get(kv[0]);
-            return (
-                <div key={kv[0]}>
-                    <b>({key ? key.Name : kv[0]}): </b>
-                    <DebugViewer fieldId={kv[1]}></DebugViewer>
-                </div>
-            );
-        });
-        return (
-            <div>
-                Document ({this.props.field.Id})
-                <div style={{ paddingLeft: "25px" }}>
-                    {fields}
-                </div>
-            </div>
-        );
-    }
-}
-
-@observer
-class DebugViewer extends React.Component<{ fieldId: string }> {
-    @observable
-    private field?: Field;
-
-    @observable
-    private error?: string;
-
-    constructor(props: { fieldId: string }) {
-        super(props);
-        this.update();
-    }
-
-    update() {
-        Server.GetField(this.props.fieldId, action((field: Opt<Field>) => {
-            this.field = field;
-            if (!field) {
-                this.error = `Field with id ${this.props.fieldId} not found`;
-            }
-        }));
-
-    }
-
-    render() {
-        let content;
-        if (this.field) {
-            // content = this.field.ToJson();
-            if (this.field instanceof ListField) {
-                content = (<ListViewer field={this.field} />);
-            } else if (this.field instanceof Document) {
-                content = (<DocumentViewer field={this.field} />);
-            } else if (this.field instanceof BasicField) {
-                content = (<FieldViewer field={this.field} />);
-            } else if (this.field instanceof Key) {
-                content = (<KeyViewer field={this.field} />);
-            } else {
-                content = (<span>Unrecognized field type</span>);
-            }
-        } else if (this.error) {
-            content = <span>Field <b>{this.props.fieldId}</b> not found <button onClick={() => this.update()}>Refresh</button></span>;
-        } else {
-            content = <span>Field loading: {this.props.fieldId}</span>;
-        }
-        return content;
-    }
-}
-
-@observer
-class Viewer extends React.Component {
-    @observable
-    private idToAdd: string = '';
-
-    @observable
-    private ids: string[] = [];
-
-    @action
-    inputOnChange = (e: React.ChangeEvent<HTMLInputElement>) => {
-        this.idToAdd = e.target.value;
-    }
-
-    @action
-    onKeyPress = (e: React.KeyboardEvent<HTMLDivElement>) => {
-        if (e.key === "Enter") {
-            this.ids.push(this.idToAdd);
-            this.idToAdd = "";
-        }
-    }
-
-    render() {
-        return (
-            <>
-                <input value={this.idToAdd}
-                    onChange={this.inputOnChange}
-                    onKeyDown={this.onKeyPress} />
-                <div>
-                    {this.ids.map(id => <DebugViewer fieldId={id} key={id}></DebugViewer>)}
-                </div>
-            </>
-        );
-    }
-}
-
-ReactDOM.render((
-    <div style={{ position: "absolute", width: "100%", height: "100%" }}>
-        <Viewer />
-    </div>),
-    document.getElementById('root')
-);
\ No newline at end of file
+
+// configure({
+//     enforceActions: "observed"
+// });
+
+// @observer
+// class FieldViewer extends React.Component<{ field: BasicField<any> }> {
+//     render() {
+//         return <span>{JSON.stringify(this.props.field.Data)} ({this.props.field.Id})</span>;
+//     }
+// }
+
+// @observer
+// class KeyViewer extends React.Component<{ field: Key }> {
+//     render() {
+//         return this.props.field.Name;
+//     }
+// }
+
+// @observer
+// class ListViewer extends React.Component<{ field: ListField<Field> }>{
+//     @observable
+//     expanded = false;
+
+//     render() {
+//         let content;
+//         if (this.expanded) {
+//             content = (
+//                 <div>
+//                     {this.props.field.Data.map(field => <DebugViewer fieldId={field.Id} key={field.Id} />)}
+//                 </div>
+//             );
+//         } else {
+//             content = <>[...] ({this.props.field.Id})</>;
+//         }
+//         return (
+//             <div>
+//                 <button onClick={action(() => this.expanded = !this.expanded)}>Toggle</button>
+//                 {content}
+//             </div >
+//         );
+//     }
+// }
+
+// @observer
+// class DocumentViewer extends React.Component<{ field: Document }> {
+//     private keyMap: ObservableMap<string, Key> = new ObservableMap;
+
+//     private disposer?: Lambda;
+
+//     componentDidMount() {
+//         let f = () => {
+//             Array.from(this.props.field._proxies.keys()).forEach(id => {
+//                 if (!this.keyMap.has(id)) {
+//                     Server.GetField(id, (field) => {
+//                         if (field && field instanceof Key) {
+//                             this.keyMap.set(id, field);
+//                         }
+//                     });
+//                 }
+//             });
+//         };
+//         this.disposer = this.props.field._proxies.observe(f);
+//         f();
+//     }
+
+//     componentWillUnmount() {
+//         if (this.disposer) {
+//             this.disposer();
+//         }
+//     }
+
+//     render() {
+//         let fields = Array.from(this.props.field._proxies.entries()).map(kv => {
+//             let key = this.keyMap.get(kv[0]);
+//             return (
+//                 <div key={kv[0]}>
+//                     <b>({key ? key.Name : kv[0]}): </b>
+//                     <DebugViewer fieldId={kv[1]}></DebugViewer>
+//                 </div>
+//             );
+//         });
+//         return (
+//             <div>
+//                 Document ({this.props.field.Id})
+//                 <div style={{ paddingLeft: "25px" }}>
+//                     {fields}
+//                 </div>
+//             </div>
+//         );
+//     }
+// }
+
+// @observer
+// class DebugViewer extends React.Component<{ fieldId: string }> {
+//     @observable
+//     private field?: Field;
+
+//     @observable
+//     private error?: string;
+
+//     constructor(props: { fieldId: string }) {
+//         super(props);
+//         this.update();
+//     }
+
+//     update() {
+//         Server.GetField(this.props.fieldId, action((field: Opt<Field>) => {
+//             this.field = field;
+//             if (!field) {
+//                 this.error = `Field with id ${this.props.fieldId} not found`;
+//             }
+//         }));
+
+//     }
+
+//     render() {
+//         let content;
+//         if (this.field) {
+//             // content = this.field.ToJson();
+//             if (this.field instanceof ListField) {
+//                 content = (<ListViewer field={this.field} />);
+//             } else if (this.field instanceof Document) {
+//                 content = (<DocumentViewer field={this.field} />);
+//             } else if (this.field instanceof BasicField) {
+//                 content = (<FieldViewer field={this.field} />);
+//             } else if (this.field instanceof Key) {
+//                 content = (<KeyViewer field={this.field} />);
+//             } else {
+//                 content = (<span>Unrecognized field type</span>);
+//             }
+//         } else if (this.error) {
+//             content = <span>Field <b>{this.props.fieldId}</b> not found <button onClick={() => this.update()}>Refresh</button></span>;
+//         } else {
+//             content = <span>Field loading: {this.props.fieldId}</span>;
+//         }
+//         return content;
+//     }
+// }
+
+// @observer
+// class Viewer extends React.Component {
+//     @observable
+//     private idToAdd: string = '';
+
+//     @observable
+//     private ids: string[] = [];
+
+//     @action
+//     inputOnChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+//         this.idToAdd = e.target.value;
+//     }
+
+//     @action
+//     onKeyPress = (e: React.KeyboardEvent<HTMLDivElement>) => {
+//         if (e.key === "Enter") {
+//             this.ids.push(this.idToAdd);
+//             this.idToAdd = "";
+//         }
+//     }
+
+//     render() {
+//         return (
+//             <>
+//                 <input value={this.idToAdd}
+//                     onChange={this.inputOnChange}
+//                     onKeyDown={this.onKeyPress} />
+//                 <div>
+//                     {this.ids.map(id => <DebugViewer fieldId={id} key={id}></DebugViewer>)}
+//                 </div>
+//             </>
+//         );
+//     }
+// }
+
+// ReactDOM.render((
+//     <div style={{ position: "absolute", width: "100%", height: "100%" }}>
+//         <Viewer />
+//     </div>),
+//     document.getElementById('root')
+// );
\ No newline at end of file
diff --git a/src/fields/AudioField.ts b/src/fields/AudioField.ts
deleted file mode 100644
index 87e47a715..000000000
--- a/src/fields/AudioField.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import { BasicField } from "./BasicField";
-import { Field, FieldId } from "./Field";
-import { Types } from "../server/Message";
-
-export class AudioField extends BasicField<URL> {
-    constructor(data: URL | undefined = undefined, id?: FieldId, save: boolean = true) {
-        super(data === undefined ? new URL("http://techslides.com/demos/samples/sample.mp3") : data, save, id);
-    }
-
-    toString(): string {
-        return this.Data.href;
-    }
-
-
-    ToScriptString(): string {
-        return `new AudioField("${this.Data}")`;
-    }
-
-    Copy(): Field {
-        return new AudioField(this.Data);
-    }
-
-    ToJson() {
-        return {
-            type: Types.Audio,
-            data: this.Data.href,
-            id: this.Id
-        };
-    }
-
-}
\ No newline at end of file
diff --git a/src/fields/BasicField.ts b/src/fields/BasicField.ts
deleted file mode 100644
index 17b1fc4e8..000000000
--- a/src/fields/BasicField.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-import { Field, FieldId } from "./Field";
-import { observable, computed, action } from "mobx";
-import { Server } from "../client/Server";
-import { UndoManager } from "../client/util/UndoManager";
-
-export abstract class BasicField<T> extends Field {
-    constructor(data: T, save: boolean, id?: FieldId) {
-        super(id);
-
-        this.data = data;
-        if (save) {
-            Server.UpdateField(this);
-        }
-    }
-
-    UpdateFromServer(data: any) {
-        if (this.data !== data) {
-            this.data = data;
-        }
-    }
-
-    @observable
-    protected data: T;
-
-    @computed
-    get Data(): T {
-        return this.data;
-    }
-
-    set Data(value: T) {
-        if (this.data === value) {
-            return;
-        }
-        let oldValue = this.data;
-        this.setData(value);
-        UndoManager.AddEvent({
-            undo: () => this.Data = oldValue,
-            redo: () => this.Data = value
-        });
-        Server.UpdateField(this);
-    }
-
-    protected setData(value: T) {
-        this.data = value;
-    }
-
-    @action
-    TrySetValue(value: any): boolean {
-        if (typeof value === typeof this.data) {
-            this.Data = value;
-            return true;
-        }
-        return false;
-    }
-
-    GetValue(): any {
-        return this.Data;
-    }
-}
diff --git a/src/fields/BooleanField.ts b/src/fields/BooleanField.ts
deleted file mode 100644
index d49bfe82b..000000000
--- a/src/fields/BooleanField.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { BasicField } from "./BasicField";
-import { FieldId } from "./Field";
-import { Types } from "../server/Message";
-
-export class BooleanField extends BasicField<boolean> {
-    constructor(data: boolean = false as boolean, id?: FieldId, save: boolean = true as boolean) {
-        super(data, save, id);
-    }
-
-    ToScriptString(): string {
-        return `new BooleanField("${this.Data}")`;
-    }
-
-    Copy() {
-        return new BooleanField(this.Data);
-    }
-
-    ToJson() {
-        return {
-            type: Types.Boolean,
-            data: this.Data,
-            id: this.Id
-        };
-    }
-}
diff --git a/src/fields/Document.ts b/src/fields/Document.ts
deleted file mode 100644
index 7cf784f0e..000000000
--- a/src/fields/Document.ts
+++ /dev/null
@@ -1,430 +0,0 @@
-import { Key } from "./Key";
-import { KeyStore } from "./KeyStore";
-import { Field, Cast, FieldWaiting, FieldValue, FieldId, Opt } from "./Field";
-import { NumberField } from "./NumberField";
-import { ObservableMap, computed, action, runInAction } from "mobx";
-import { TextField } from "./TextField";
-import { ListField } from "./ListField";
-import { Server } from "../client/Server";
-import { Types } from "../server/Message";
-import { UndoManager } from "../client/util/UndoManager";
-import { HtmlField } from "./HtmlField";
-import { BooleanField } from "./BooleanField";
-import { allLimit } from "async";
-import { prototype } from "nodemailer/lib/smtp-pool";
-import { HistogramField } from "../client/northstar/dash-fields/HistogramField";
-
-export class Document extends Field {
-    //TODO tfs: We should probably store FieldWaiting in fields when we request it from the server so that we don't set up multiple server gets for the same document and field
-    public fields: ObservableMap<string, { key: Key; field: Field }> = new ObservableMap();
-    public _proxies: ObservableMap<string, FieldId> = new ObservableMap();
-
-    constructor(id?: string, save: boolean = true) {
-        super(id);
-
-        if (save) {
-            Server.UpdateField(this);
-        }
-    }
-    static FromJson(data: any, id: string, save: boolean): Document {
-        let doc = new Document(id, save);
-        let fields = data as [string, string][];
-        fields.forEach(pair => doc._proxies.set(pair[0], pair[1]));
-        return doc;
-    }
-
-    UpdateFromServer(data: [string, string][]) {
-        for (const key in data) {
-            const element = data[key];
-            this._proxies.set(element[0], element[1]);
-        }
-    }
-
-    public Width = () => this.GetNumber(KeyStore.Width, 0);
-    public Height = () => this.GetNumber(KeyStore.Height, this.GetNumber(KeyStore.NativeWidth, 0) ? (this.GetNumber(KeyStore.NativeHeight, 0) / this.GetNumber(KeyStore.NativeWidth, 0)) * this.GetNumber(KeyStore.Width, 0) : 0);
-    public Scale = () => this.GetNumber(KeyStore.Scale, 1);
-
-    @computed
-    public get Title(): string {
-        let title = this.Get(KeyStore.Title, true);
-        if (title || title === FieldWaiting) {
-            if (title !== FieldWaiting && title instanceof TextField) {
-                return title.Data;
-            }
-            else return "-waiting-";
-        }
-        let parTitle = this.GetT(KeyStore.Title, TextField);
-        if (parTitle || parTitle === FieldWaiting) {
-            if (parTitle !== FieldWaiting) return parTitle.Data + ".alias";
-            else return "-waiting-.alias";
-        }
-        return "-untitled-";
-    }
-
-    @computed
-    public get Fields() {
-        return this.fields;
-    }
-
-    /**
-     * Get the field in the document associated with the given key. If the
-     * associated field has not yet been filled in from the server, a request
-     * to the server will automatically be sent, the value will be filled in
-     * when the request is completed, and {@link Field.ts#FieldWaiting} will be returned.
-     * @param key - The key of the value to get
-     * @param ignoreProto - If true, ignore any prototype this document
-     * might have and only search for the value on this immediate document.
-     * If false (default), search up the prototype chain, starting at this document,
-     * for a document that has a field associated with the given key, and return the first
-     * one found.
-     *
-     * @returns If the document does not have a field associated with the given key, returns `undefined`.
-     * If the document does have an associated field, but the field has not been fetched from the server, returns {@link Field.ts#FieldWaiting}.
-     * If the document does have an associated field, and the field has not been fetched from the server, returns the associated field.
-     */
-    Get(key: Key, ignoreProto: boolean = false): FieldValue<Field> {
-        let field: FieldValue<Field>;
-        if (ignoreProto) {
-            if (this.fields.has(key.Id)) {
-                field = this.fields.get(key.Id)!.field;
-            } else if (this._proxies.has(key.Id)) {
-                Server.GetDocumentField(this, key);
-                /*
-                        The field might have been instantly filled from the cache
-                        Maybe we want to just switch back to returning the value
-                        from Server.GetDocumentField if it's in the cache
-                        */
-                if (this.fields.has(key.Id)) {
-                    field = this.fields.get(key.Id)!.field;
-                } else {
-                    field = FieldWaiting;
-                }
-            }
-        } else {
-            let doc: FieldValue<Document> = this;
-            while (doc && field !== FieldWaiting) {
-                let curField = doc.fields.get(key.Id);
-                let curProxy = doc._proxies.get(key.Id);
-                if (!curField || (curProxy && curField.field.Id !== curProxy)) {
-                    if (curProxy) {
-                        Server.GetDocumentField(doc, key);
-                        /*
-                                    The field might have been instantly filled from the cache
-                                    Maybe we want to just switch back to returning the value
-                                    from Server.GetDocumentField if it's in the cache
-                                    */
-                        if (this.fields.has(key.Id)) {
-                            field = this.fields.get(key.Id)!.field;
-                        } else {
-                            field = FieldWaiting;
-                        }
-                        break;
-                    }
-                    if (
-                        doc.fields.has(KeyStore.Prototype.Id) ||
-                        doc._proxies.has(KeyStore.Prototype.Id)
-                    ) {
-                        doc = doc.GetPrototype();
-                    } else {
-                        break;
-                    }
-                } else {
-                    field = curField.field;
-                    break;
-                }
-            }
-            if (doc === FieldWaiting) field = FieldWaiting;
-        }
-
-        return field;
-    }
-
-    /**
-     * Tries to get the field associated with the given key, and if there is an
-     * associated field, calls the given callback with that field.
-     * @param key - The key of the value to get
-     * @param callback - A function that will be called with the associated field, if it exists,
-     * once it is fetched from the server (this may be immediately if the field has already been fetched).
-     * Note: The callback will not be called if there is no associated field.
-     * @returns `true` if the field exists on the document and `callback` will be called, and `false` otherwise
-     */
-    GetAsync(key: Key, callback: (field: Opt<Field>) => void): void {
-        //TODO: This currently doesn't deal with prototypes
-        let field = this.fields.get(key.Id);
-        if (field && field.field) {
-            callback(field.field);
-        } else if (this._proxies.has(key.Id)) {
-            Server.GetDocumentField(this, key, callback);
-        } else if (this._proxies.has(KeyStore.Prototype.Id)) {
-            this.GetTAsync(KeyStore.Prototype, Document, proto => {
-                if (proto) {
-                    proto.GetAsync(key, callback);
-                } else {
-                    callback(undefined);
-                }
-            });
-        } else {
-            callback(undefined);
-        }
-    }
-
-    GetTAsync<T extends Field>(key: Key, ctor: { new(): T }): Promise<Opt<T>>;
-    GetTAsync<T extends Field>(
-        key: Key,
-        ctor: { new(): T },
-        callback: (field: Opt<T>) => void
-    ): void;
-    GetTAsync<T extends Field>(
-        key: Key,
-        ctor: { new(): T },
-        callback?: (field: Opt<T>) => void
-    ): Promise<Opt<T>> | void {
-        let fn = (cb: (field: Opt<T>) => void) => {
-            return this.GetAsync(key, field => {
-                cb(Cast(field, ctor));
-            });
-        };
-        if (callback) {
-            fn(callback);
-        } else {
-            return new Promise(fn);
-        }
-    }
-
-    /**
-     * Same as {@link Document#GetAsync}, except a field of the given type
-     * will be created if there is no field associated with the given key,
-     * or the field associated with the given key is not of the given type.
-     * @param ctor - Constructor of the field type to get. E.g., TextField, ImageField, etc.
-     */
-    GetOrCreateAsync<T extends Field>(
-        key: Key,
-        ctor: { new(): T },
-        callback: (field: T) => void
-    ): void {
-        //This currently doesn't deal with prototypes
-        if (this._proxies.has(key.Id)) {
-            Server.GetDocumentField(this, key, field => {
-                if (field && field instanceof ctor) {
-                    callback(field);
-                } else {
-                    let newField = new ctor();
-                    this.Set(key, newField);
-                    callback(newField);
-                }
-            });
-        } else {
-            let newField = new ctor();
-            this.Set(key, newField);
-            callback(newField);
-        }
-    }
-
-    /**
-     * Same as {@link Document#Get}, except that it will additionally
-     * check if the field is of the given type.
-     * @param ctor - Constructor of the field type to get. E.g., `TextField`, `ImageField`, etc.
-     * @returns Same as {@link Document#Get}, except will return `undefined`
-     * if there is an associated field but it is of the wrong type.
-     */
-    GetT<T extends Field = Field>(
-        key: Key,
-        ctor: { new(...args: any[]): T },
-        ignoreProto: boolean = false
-    ): FieldValue<T> {
-        var getfield = this.Get(key, ignoreProto);
-        if (getfield !== FieldWaiting) {
-            return Cast(getfield, ctor);
-        }
-        return FieldWaiting;
-    }
-
-    GetOrCreate<T extends Field>(
-        key: Key,
-        ctor: { new(): T },
-        ignoreProto: boolean = false
-    ): T {
-        const field = this.GetT(key, ctor, ignoreProto);
-        if (field && field !== FieldWaiting) {
-            return field;
-        }
-        const newField = new ctor();
-        this.Set(key, newField);
-        return newField;
-    }
-
-    GetData<T, U extends Field & { Data: T }>(
-        key: Key,
-        ctor: { new(): U },
-        defaultVal: T
-    ): T {
-        let val = this.Get(key);
-        let vval = val && val instanceof ctor ? val.Data : defaultVal;
-        return vval;
-    }
-
-    GetHtml(key: Key, defaultVal: string): string {
-        return this.GetData(key, HtmlField, defaultVal);
-    }
-
-    GetBoolean(key: Key, defaultVal: boolean): boolean {
-        return this.GetData(key, BooleanField, defaultVal);
-    }
-
-    GetNumber(key: Key, defaultVal: number): number {
-        return this.GetData(key, NumberField, defaultVal);
-    }
-
-    GetText(key: Key, defaultVal: string): string {
-        return this.GetData(key, TextField, defaultVal);
-    }
-
-    GetList<T extends Field>(key: Key, defaultVal: T[]): T[] {
-        return this.GetData<T[], ListField<T>>(key, ListField, defaultVal);
-    }
-
-    @action
-    Set(key: Key, field: Field | undefined, setOnPrototype = false): void {
-        let old = this.fields.get(key.Id);
-        let oldField = old ? old.field : undefined;
-        if (setOnPrototype) {
-            this.SetOnPrototype(key, field);
-        } else {
-            if (field) {
-                this.fields.set(key.Id, { key, field });
-                this._proxies.set(key.Id, field.Id);
-                // Server.AddDocumentField(this, key, field);
-            } else {
-                this.fields.delete(key.Id);
-                this._proxies.delete(key.Id);
-                // Server.DeleteDocumentField(this, key);
-            }
-            Server.UpdateField(this);
-        }
-        if (oldField || field) {
-            UndoManager.AddEvent({
-                undo: () => this.Set(key, oldField, setOnPrototype),
-                redo: () => this.Set(key, field, setOnPrototype)
-            });
-        }
-    }
-
-    @action
-    SetOnPrototype(key: Key, field: Field | undefined): void {
-        this.GetTAsync(KeyStore.Prototype, Document, (f: Opt<Document>) => {
-            f && f.Set(key, field);
-        });
-    }
-
-    @action
-    SetDataOnPrototype<T, U extends Field & { Data: T }>(key: Key, value: T, ctor: { new(): U }, replaceWrongType = true) {
-        this.GetTAsync(KeyStore.Prototype, Document, (f: Opt<Document>) => {
-            f && f.SetData(key, value, ctor, replaceWrongType);
-        });
-    }
-
-    @action
-    SetData<T, U extends Field & { Data: T }>(key: Key, value: T, ctor: { new(data: T): U }, replaceWrongType = true) {
-        let field = this.Get(key, true);
-        if (field instanceof ctor) {
-            field.Data = value;
-        } else if (!field || replaceWrongType) {
-            let newField = new ctor(value);
-            // newField.Data = value;
-            this.Set(key, newField);
-        }
-    }
-
-    @action
-    SetText(key: Key, value: string, replaceWrongType = true) {
-        this.SetData(key, value, TextField, replaceWrongType);
-    }
-    @action
-    SetBoolean(key: Key, value: boolean, replaceWrongType = true) {
-        this.SetData(key, value, BooleanField, replaceWrongType);
-    }
-    @action
-    SetNumber(key: Key, value: number, replaceWrongType = true) {
-        this.SetData(key, value, NumberField, replaceWrongType);
-    }
-
-    GetPrototype(): FieldValue<Document> {
-        return this.GetT(KeyStore.Prototype, Document, true);
-    }
-
-    GetAllPrototypes(): Document[] {
-        let protos: Document[] = [];
-        let doc: FieldValue<Document> = this;
-        while (doc && doc !== FieldWaiting) {
-            protos.push(doc);
-            doc = doc.GetPrototype();
-        }
-        return protos;
-    }
-
-    CreateAlias(id?: string): Document {
-        let alias = new Document(id);
-        this.GetTAsync(KeyStore.Prototype, Document, (f: Opt<Document>) => {
-            f && alias.Set(KeyStore.Prototype, f);
-        });
-
-        return alias;
-    }
-
-    MakeDelegate(id?: string): Document {
-        let delegate = new Document(id);
-
-        delegate.Set(KeyStore.Prototype, this);
-
-        return delegate;
-    }
-
-    ToScriptString(): string {
-        return "";
-    }
-
-    TrySetValue(value: any): boolean {
-        throw new Error("Method not implemented.");
-    }
-    GetValue() {
-        return this.Title;
-        var title = (this._proxies.has(KeyStore.Title.Id) ? "???" : this.Title) + "(" + this.Id + ")";
-        return title;
-        //throw new Error("Method not implemented.");
-    }
-    Copy(copyProto?: boolean, id?: string): Field {
-        let copy = new Document();
-        this._proxies.forEach((fieldid, keyid) => { // copy each prototype field
-            let key = KeyStore.KeyLookup(keyid);
-            if (key) {
-                this.GetAsync(key, (field: Opt<Field>) => {
-                    if (key === KeyStore.Prototype && copyProto) { // handle prototype field specially
-                        if (field instanceof Document) {
-                            copy.Set(key, field.Copy(false)); // only copying one level of prototypes for now...
-                        }
-                    }
-                    else
-                        if (field instanceof Document) {  // ... TODO bcz: should we copy documents or reference them
-                            copy.Set(key!, field);
-                        }
-                        else if (field) {
-                            copy.Set(key!, field.Copy());
-                        }
-                });
-            }
-        });
-        return copy;
-    }
-
-    ToJson() {
-        let fields: [string, string][] = [];
-        this._proxies.forEach((field, key) =>
-            field && fields.push([key, field]));
-
-        return {
-            type: Types.Document,
-            data: fields,
-            id: this.Id
-        };
-    }
-}
diff --git a/src/fields/DocumentReference.ts b/src/fields/DocumentReference.ts
deleted file mode 100644
index 303754177..000000000
--- a/src/fields/DocumentReference.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-import { Field, Opt, FieldValue, FieldId } from "./Field";
-import { Document } from "./Document";
-import { Key } from "./Key";
-import { Types } from "../server/Message";
-import { ObjectID } from "bson";
-
-export class DocumentReference extends Field {
-    get Key(): Key {
-        return this.key;
-    }
-
-    get Document(): Document {
-        return this.document;
-    }
-
-    constructor(private document: Document, private key: Key) {
-        super();
-    }
-
-    UpdateFromServer() {
-
-    }
-
-    Dereference(): FieldValue<Field> {
-        return this.document.Get(this.key);
-    }
-
-    DereferenceToRoot(): FieldValue<Field> {
-        let field: FieldValue<Field> = this;
-        while (field instanceof DocumentReference) {
-            field = field.Dereference();
-        }
-        return field;
-    }
-
-    TrySetValue(value: any): boolean {
-        throw new Error("Method not implemented.");
-    }
-    GetValue() {
-        throw new Error("Method not implemented.");
-    }
-    Copy(): Field {
-        throw new Error("Method not implemented.");
-    }
-
-    ToScriptString(): string {
-        return "";
-    }
-
-    ToJson() {
-        return {
-            type: Types.DocumentReference,
-            data: this.document.Id,
-            id: this.Id
-        };
-    }
-}
\ No newline at end of file
diff --git a/src/fields/Field.ts b/src/fields/Field.ts
deleted file mode 100644
index 3b3e95c2b..000000000
--- a/src/fields/Field.ts
+++ /dev/null
@@ -1,69 +0,0 @@
-
-import { Utils } from "../Utils";
-import { Types, Transferable } from "../server/Message";
-import { computed } from "mobx";
-
-export function Cast<T extends Field>(field: FieldValue<Field>, ctor: { new(): T }): Opt<T> {
-    if (field) {
-        if (ctor && field instanceof ctor) {
-            return field;
-        }
-    }
-    return undefined;
-}
-
-export const FieldWaiting: FIELD_WAITING = null;
-export type FIELD_WAITING = null;
-export type FieldId = string;
-export type Opt<T> = T | undefined;
-export type FieldValue<T> = Opt<T> | FIELD_WAITING;
-
-export abstract class Field {
-    //FieldUpdated: TypedEvent<Opt<FieldUpdatedArgs>> = new TypedEvent<Opt<FieldUpdatedArgs>>();
-
-    init(callback: (res: Field) => any) {
-        callback(this);
-    }
-
-    private id: FieldId;
-
-    @computed
-    get Id(): FieldId {
-        return this.id;
-    }
-
-    constructor(id: Opt<FieldId> = undefined) {
-        this.id = id || Utils.GenerateGuid();
-    }
-
-    Dereference(): FieldValue<Field> {
-        return this;
-    }
-    DereferenceToRoot(): FieldValue<Field> {
-        return this;
-    }
-
-    DereferenceT<T extends Field = Field>(ctor: { new(): T }): FieldValue<T> {
-        return Cast(this.Dereference(), ctor);
-    }
-
-    DereferenceToRootT<T extends Field = Field>(ctor: { new(): T }): FieldValue<T> {
-        return Cast(this.DereferenceToRoot(), ctor);
-    }
-
-    Equals(other: Field): boolean {
-        return this.id === other.id;
-    }
-
-    abstract UpdateFromServer(serverData: any): void;
-
-    abstract ToScriptString(): string;
-
-    abstract TrySetValue(value: any): boolean;
-
-    abstract GetValue(): any;
-
-    abstract Copy(): Field;
-
-    abstract ToJson(): Transferable;
-}
\ No newline at end of file
diff --git a/src/fields/FieldUpdatedArgs.ts b/src/fields/FieldUpdatedArgs.ts
deleted file mode 100644
index 23ccf2a5a..000000000
--- a/src/fields/FieldUpdatedArgs.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import { Field, Opt } from "./Field";
-import { Document } from "./Document";
-import { Key } from "./Key";
-
-export enum FieldUpdatedAction {
-    Add,
-    Remove,
-    Replace,
-    Update
-}
-
-export interface FieldUpdatedArgs {
-    field: Field;
-    action: FieldUpdatedAction;
-}
-
-export interface DocumentUpdatedArgs {
-    field: Document;
-    key: Key;
-
-    oldValue: Opt<Field>;
-    newValue: Opt<Field>;
-
-    fieldArgs?: FieldUpdatedArgs;
-
-    action: FieldUpdatedAction;
-}
diff --git a/src/fields/HtmlField.ts b/src/fields/HtmlField.ts
deleted file mode 100644
index a1d880070..000000000
--- a/src/fields/HtmlField.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { BasicField } from "./BasicField";
-import { Types } from "../server/Message";
-import { FieldId } from "./Field";
-
-export class HtmlField extends BasicField<string> {
-    constructor(data: string = "<html></html>", id?: FieldId, save: boolean = true) {
-        super(data, save, id);
-    }
-
-    ToScriptString(): string {
-        return `new HtmlField("${this.Data}")`;
-    }
-
-    Copy() {
-        return new HtmlField(this.Data);
-    }
-
-    ToJson() {
-        return {
-            type: Types.Html,
-            data: this.Data,
-            id: this.Id,
-        };
-    }
-}
\ No newline at end of file
diff --git a/src/fields/ImageField.ts b/src/fields/ImageField.ts
deleted file mode 100644
index bce20f242..000000000
--- a/src/fields/ImageField.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-import { BasicField } from "./BasicField";
-import { Field, FieldId } from "./Field";
-import { Types } from "../server/Message";
-
-export class ImageField extends BasicField<URL> {
-    constructor(data: URL | undefined = undefined, id?: FieldId, save: boolean = true) {
-        super(data === undefined ? new URL("http://cs.brown.edu/~bcz/bob_fettucine.jpg") : data, save, id);
-    }
-
-    toString(): string {
-        return this.Data.href;
-    }
-
-    ToScriptString(): string {
-        return `new ImageField("${this.Data}")`;
-    }
-
-    Copy(): Field {
-        return new ImageField(this.Data);
-    }
-
-    ToJson() {
-        return {
-            type: Types.Image,
-            data: this.Data.href,
-            id: this.Id
-        };
-    }
-}
\ No newline at end of file
diff --git a/src/fields/InkField.ts b/src/fields/InkField.ts
deleted file mode 100644
index 2eacd7d0c..000000000
--- a/src/fields/InkField.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import { BasicField } from "./BasicField";
-import { Types } from "../server/Message";
-import { FieldId } from "./Field";
-import { observable, ObservableMap } from "mobx";
-
-export enum InkTool {
-    None,
-    Pen,
-    Highlighter,
-    Eraser
-}
-export interface StrokeData {
-    pathData: Array<{ x: number, y: number }>;
-    color: string;
-    width: string;
-    tool: InkTool;
-    page: number;
-}
-export type StrokeMap = Map<string, StrokeData>;
-
-export class InkField extends BasicField<StrokeMap> {
-    constructor(data: StrokeMap = new Map, id?: FieldId, save: boolean = true) {
-        super(data, save, id);
-    }
-
-    ToScriptString(): string {
-        return `new InkField("${this.Data}")`;
-    }
-
-    Copy() {
-        return new InkField(this.Data);
-    }
-
-    ToJson() {
-        return {
-            type: Types.Ink,
-            data: this.Data,
-            id: this.Id,
-        };
-    }
-
-    UpdateFromServer(data: any) {
-        this.data = new ObservableMap(data);
-    }
-
-    static FromJson(id: string, data: any): InkField {
-        let map: StrokeMap = new Map<string, StrokeData>();
-        Object.keys(data).forEach(key => {
-            map.set(key, data[key]);
-        });
-        return new InkField(map, id, false);
-    }
-}
\ No newline at end of file
diff --git a/src/fields/Key.ts b/src/fields/Key.ts
deleted file mode 100644
index 57e2dadf0..000000000
--- a/src/fields/Key.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-import { Field, FieldId } from "./Field";
-import { Utils } from "../Utils";
-import { observable } from "mobx";
-import { Types } from "../server/Message";
-import { Server } from "../client/Server";
-
-export class Key extends Field {
-    private name: string;
-
-    get Name(): string {
-        return this.name;
-    }
-
-    constructor(name: string, id?: string, save: boolean = true) {
-        super(id || Utils.GenerateDeterministicGuid(name));
-
-        this.name = name;
-        if (save) {
-            Server.UpdateField(this);
-        }
-    }
-
-    UpdateFromServer(data: string) {
-        this.name = data;
-    }
-
-    TrySetValue(value: any): boolean {
-        throw new Error("Method not implemented.");
-    }
-
-    GetValue() {
-        return this.Name;
-    }
-
-    Copy(): Field {
-        return this;
-    }
-
-    ToScriptString(): string {
-        return name;
-    }
-
-    ToJson() {
-        return {
-            type: Types.Key,
-            data: this.name,
-            id: this.Id
-        };
-    }
-}
\ No newline at end of file
diff --git a/src/fields/KeyStore.ts b/src/fields/KeyStore.ts
deleted file mode 100644
index 16a909eb8..000000000
--- a/src/fields/KeyStore.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-import { Key } from "./Key";
-
-export namespace KeyStore {
-    export const Prototype = new Key("Prototype");
-    export const X = new Key("X");
-    export const Y = new Key("Y");
-    export const Page = new Key("Page");
-    export const Title = new Key("Title");
-    export const Author = new Key("Author");
-    export const PanX = new Key("PanX");
-    export const PanY = new Key("PanY");
-    export const Scale = new Key("Scale");
-    export const NativeWidth = new Key("NativeWidth");
-    export const NativeHeight = new Key("NativeHeight");
-    export const Width = new Key("Width");
-    export const Height = new Key("Height");
-    export const ZIndex = new Key("ZIndex");
-    export const Zoom = new Key("Zoom");
-    export const Data = new Key("Data");
-    export const Annotations = new Key("Annotations");
-    export const ViewType = new Key("ViewType");
-    export const Layout = new Key("Layout");
-    export const BackgroundColor = new Key("BackgroundColor");
-    export const BackgroundLayout = new Key("BackgroundLayout");
-    export const OverlayLayout = new Key("OverlayLayout");
-    export const LayoutKeys = new Key("LayoutKeys");
-    export const LayoutFields = new Key("LayoutFields");
-    export const ColumnsKey = new Key("SchemaColumns");
-    export const SchemaSplitPercentage = new Key("SchemaSplitPercentage");
-    export const Caption = new Key("Caption");
-    export const ActiveWorkspace = new Key("ActiveWorkspace");
-    export const DocumentText = new Key("DocumentText");
-    export const BrushingDocs = new Key("BrushingDocs");
-    export const LinkedToDocs = new Key("LinkedToDocs");
-    export const LinkedFromDocs = new Key("LinkedFromDocs");
-    export const LinkDescription = new Key("LinkDescription");
-    export const LinkTags = new Key("LinkTag");
-    export const Thumbnail = new Key("Thumbnail");
-    export const ThumbnailPage = new Key("ThumbnailPage");
-    export const CurPage = new Key("CurPage");
-    export const AnnotationOn = new Key("AnnotationOn");
-    export const NumPages = new Key("NumPages");
-    export const Ink = new Key("Ink");
-    export const Cursors = new Key("Cursors");
-    export const OptionalRightCollection = new Key("OptionalRightCollection");
-    export const Archives = new Key("Archives");
-    export const Workspaces = new Key("Workspaces");
-    export const Minimized = new Key("Minimized");
-    export const CopyDraggedItems = new Key("CopyDraggedItems");
-
-    export const KeyList: Key[] = [Prototype, X, Y, Page, Title, Author, PanX, PanY, Scale, NativeWidth, NativeHeight,
-        Width, Height, ZIndex, Zoom, Data, Annotations, ViewType, Layout, BackgroundColor, BackgroundLayout, OverlayLayout, LayoutKeys,
-        LayoutFields, ColumnsKey, SchemaSplitPercentage, Caption, ActiveWorkspace, DocumentText, BrushingDocs, LinkedToDocs, LinkedFromDocs,
-        LinkDescription, LinkTags, Thumbnail, ThumbnailPage, CurPage, AnnotationOn, NumPages, Ink, Cursors, OptionalRightCollection,
-        Archives, Workspaces, Minimized, CopyDraggedItems
-    ];
-    export function KeyLookup(keyid: string) {
-        for (const key of KeyList) {
-            if (key.Id === keyid) {
-                return key;
-            }
-        }
-        return undefined;
-    }
-}
diff --git a/src/fields/ListField.ts b/src/fields/ListField.ts
deleted file mode 100644
index e24099126..000000000
--- a/src/fields/ListField.ts
+++ /dev/null
@@ -1,196 +0,0 @@
-import { action, IArrayChange, IArraySplice, IObservableArray, observe, observable, Lambda } from "mobx";
-import { Server } from "../client/Server";
-import { UndoManager } from "../client/util/UndoManager";
-import { Types } from "../server/Message";
-import { BasicField } from "./BasicField";
-import { Field, FieldId } from "./Field";
-import { FieldMap } from "../client/SocketStub";
-import { ScriptField } from "./ScriptField";
-
-export class ListField<T extends Field> extends BasicField<T[]> {
-    private _proxies: string[] = [];
-    private _scriptIds: string[] = [];
-    private scripts: ScriptField[] = [];
-
-    constructor(data: T[] = [], scripts: ScriptField[] = [], id?: FieldId, save: boolean = true) {
-        super(data, save, id);
-        this.scripts = scripts;
-        this.updateProxies();
-        this._scriptIds = this.scripts.map(script => script.Id);
-        if (save) {
-            Server.UpdateField(this);
-        }
-        this.observeList();
-    }
-
-    private _processingServerUpdate: boolean = false;
-
-    private observeDisposer: Lambda | undefined;
-    private observeList(): void {
-        if (this.observeDisposer) {
-            this.observeDisposer();
-        }
-        this.observeDisposer = observe(this.Data as IObservableArray<T>, (change: IArrayChange<T> | IArraySplice<T>) => {
-            const target = change.object;
-            this.updateProxies();
-            if (change.type === "splice") {
-                this.runScripts(change.removed, false);
-                UndoManager.AddEvent({
-                    undo: () => target.splice(change.index, change.addedCount, ...change.removed),
-                    redo: () => target.splice(change.index, change.removedCount, ...change.added)
-                });
-                this.runScripts(change.added, true);
-            } else {
-                this.runScripts([change.oldValue], false);
-                UndoManager.AddEvent({
-                    undo: () => target[change.index] = change.oldValue,
-                    redo: () => target[change.index] = change.newValue
-                });
-                this.runScripts([change.newValue], true);
-            }
-            if (!this._processingServerUpdate) {
-                Server.UpdateField(this);
-            }
-        });
-    }
-
-    private runScripts(fields: T[], added: boolean) {
-        for (const script of this.scripts) {
-            this.runScript(fields, script, added);
-        }
-    }
-
-    private runScript(fields: T[], script: ScriptField, added: boolean) {
-        if (!this._processingServerUpdate) {
-            for (const field of fields) {
-                script.script.run({ field, added });
-            }
-        }
-    }
-
-    addScript(script: ScriptField) {
-        this.scripts.push(script);
-        this._scriptIds.push(script.Id);
-
-        this.runScript(this.Data, script, true);
-        UndoManager.AddEvent({
-            undo: () => this.removeScript(script),
-            redo: () => this.addScript(script),
-        });
-        Server.UpdateField(this);
-    }
-
-    removeScript(script: ScriptField) {
-        const index = this.scripts.indexOf(script);
-        if (index === -1) {
-            return;
-        }
-        this.scripts.splice(index, 1);
-        this._scriptIds.splice(index, 1);
-        UndoManager.AddEvent({
-            undo: () => this.addScript(script),
-            redo: () => this.removeScript(script),
-        });
-        this.runScript(this.Data, script, false);
-        Server.UpdateField(this);
-    }
-
-    protected setData(value: T[]) {
-        this.runScripts(this.data, false);
-
-        this.data = observable(value);
-        this.updateProxies();
-        this.observeList();
-        this.runScripts(this.data, true);
-    }
-
-    private updateProxies() {
-        this._proxies = this.Data.map(field => field.Id);
-    }
-
-    private arraysEqual(a: any[], b: any[]) {
-        if (a === b) return true;
-        if (a === null || b === null) return false;
-        if (a.length !== b.length) return false;
-
-        // If you don't care about the order of the elements inside
-        // the array, you should sort both arrays here.
-        // Please note that calling sort on an array will modify that array.
-        // you might want to clone your array first.
-
-        for (var i = 0; i < a.length; ++i) {
-            if (a[i] !== b[i]) return false;
-        }
-        return true;
-    }
-
-    init(callback: (field: Field) => any) {
-        const fieldsPromise = Server.GetFields(this._proxies).then(action((fields: FieldMap) => {
-            if (!this.arraysEqual(this._proxies, this.data.map(field => field.Id))) {
-                var dataids = this.data.map(d => d.Id);
-                var proxies = this._proxies.map(p => p);
-                var added = this.data.length < this._proxies.length;
-                var deleted = this.data.length > this._proxies.length;
-                for (let i = 0; i < dataids.length && added; i++) {
-                    added = proxies.indexOf(dataids[i]) !== -1;
-                }
-                for (let i = 0; i < this._proxies.length && deleted; i++) {
-                    deleted = dataids.indexOf(proxies[i]) !== -1;
-                }
-
-                this._processingServerUpdate = true;
-                for (let i = 0; i < proxies.length && added; i++) {
-                    if (dataids.indexOf(proxies[i]) === -1) {
-                        this.Data.splice(i, 0, fields[proxies[i]] as T);
-                    }
-                }
-                for (let i = dataids.length - 1; i >= 0 && deleted; i--) {
-                    if (proxies.indexOf(dataids[i]) === -1) {
-                        this.Data.splice(i, 1);
-                    }
-                }
-                if (!added && !deleted) {// otherwise, just rebuild the whole list
-                    this.setData(proxies.map(id => fields[id] as T));
-                }
-                this._processingServerUpdate = false;
-            }
-        }));
-
-        const scriptsPromise = Server.GetFields(this._scriptIds).then((fields: FieldMap) => {
-            this.scripts = this._scriptIds.map(id => fields[id] as ScriptField);
-        });
-
-        Promise.all([fieldsPromise, scriptsPromise]).then(() => callback(this));
-    }
-
-    ToScriptString(): string {
-        return "new ListField([" + this.Data.map(field => field.ToScriptString()).join(", ") + "])";
-    }
-
-    Copy(): Field {
-        return new ListField<T>(this.Data);
-    }
-
-
-    UpdateFromServer(data: { fields: string[], scripts: string[] }) {
-        this._proxies = data.fields;
-        this._scriptIds = data.scripts;
-    }
-    ToJson() {
-        return {
-            type: Types.List,
-            data: {
-                fields: this._proxies,
-                scripts: this._scriptIds,
-            },
-            id: this.Id
-        };
-    }
-
-    static FromJson(id: string, data: { fields: string[], scripts: string[] }): ListField<Field> {
-        let list = new ListField([], [], id, false);
-        list._proxies = data.fields;
-        list._scriptIds = data.scripts;
-        return list;
-    }
-}
\ No newline at end of file
diff --git a/src/fields/NumberField.ts b/src/fields/NumberField.ts
deleted file mode 100644
index 7eea360c0..000000000
--- a/src/fields/NumberField.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { BasicField } from "./BasicField";
-import { Types } from "../server/Message";
-import { FieldId } from "./Field";
-
-export class NumberField extends BasicField<number> {
-    constructor(data: number = 0, id?: FieldId, save: boolean = true) {
-        super(data, save, id);
-    }
-
-    ToScriptString(): string {
-        return `new NumberField(${this.Data})`;
-    }
-
-    Copy() {
-        return new NumberField(this.Data);
-    }
-
-    ToJson() {
-        return {
-            id: this.Id,
-            type: Types.Number,
-            data: this.Data
-        };
-    }
-}
\ No newline at end of file
diff --git a/src/fields/PDFField.ts b/src/fields/PDFField.ts
deleted file mode 100644
index 718a1a4c0..000000000
--- a/src/fields/PDFField.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-import { BasicField } from "./BasicField";
-import { Field, FieldId } from "./Field";
-import { observable } from "mobx";
-import { Types } from "../server/Message";
-
-
-
-export class PDFField extends BasicField<URL> {
-    constructor(data: URL | undefined = undefined, id?: FieldId, save: boolean = true) {
-        super(data === undefined ? new URL("http://cs.brown.edu/~bcz/bob_fettucine.jpg") : data, save, id);
-    }
-
-    toString(): string {
-        return this.Data.href;
-    }
-
-    Copy(): Field {
-        return new PDFField(this.Data);
-    }
-
-    ToScriptString(): string {
-        return `new PDFField("${this.Data}")`;
-    }
-
-    ToJson() {
-        return {
-            type: Types.PDF,
-            data: this.Data.href,
-            id: this.Id
-        };
-    }
-
-    @observable
-    Page: Number = 1;
-
-}
\ No newline at end of file
diff --git a/src/fields/RichTextField.ts b/src/fields/RichTextField.ts
deleted file mode 100644
index f53f48ca6..000000000
--- a/src/fields/RichTextField.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import { BasicField } from "./BasicField";
-import { Types } from "../server/Message";
-import { FieldId } from "./Field";
-
-export class RichTextField extends BasicField<string> {
-    constructor(data: string = "", id?: FieldId, save: boolean = true) {
-        super(data, save, id);
-    }
-
-    ToScriptString(): string {
-        return `new RichTextField(${this.Data})`;
-    }
-
-    Copy() {
-        return new RichTextField(this.Data);
-    }
-
-    ToJson() {
-        return {
-            type: Types.RichText,
-            data: this.Data,
-            id: this.Id
-        };
-    }
-
-}
\ No newline at end of file
diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts
index 7f87be45d..ae532c9e2 100644
--- a/src/fields/ScriptField.ts
+++ b/src/fields/ScriptField.ts
@@ -1,101 +1,101 @@
-import { Field, FieldId } from "./Field";
-import { Types } from "../server/Message";
-import { CompileScript, ScriptOptions, CompiledScript } from "../client/util/Scripting";
-import { Server } from "../client/Server";
-import { Without } from "../Utils";
+// import { Field, FieldId } from "./Field";
+// import { Types } from "../server/Message";
+// import { CompileScript, ScriptOptions, CompiledScript } from "../client/util/Scripting";
+// import { Server } from "../client/Server";
+// import { Without } from "../Utils";
 
-export interface SerializableOptions extends Without<ScriptOptions, "capturedVariables"> {
-    capturedIds: { [id: string]: string };
-}
+// export interface SerializableOptions extends Without<ScriptOptions, "capturedVariables"> {
+//     capturedIds: { [id: string]: string };
+// }
 
-export interface ScriptData {
-    script: string;
-    options: SerializableOptions;
-}
+// export interface ScriptData {
+//     script: string;
+//     options: SerializableOptions;
+// }
 
-export class ScriptField extends Field {
-    private _script?: CompiledScript;
-    get script(): CompiledScript {
-        return this._script!;
-    }
-    private options?: ScriptData;
+// export class ScriptField extends Field {
+//     private _script?: CompiledScript;
+//     get script(): CompiledScript {
+//         return this._script!;
+//     }
+//     private options?: ScriptData;
 
-    constructor(script?: CompiledScript, id?: FieldId, save: boolean = true) {
-        super(id);
+//     constructor(script?: CompiledScript, id?: FieldId, save: boolean = true) {
+//         super(id);
 
-        this._script = script;
+//         this._script = script;
 
-        if (save) {
-            Server.UpdateField(this);
-        }
-    }
+//         if (save) {
+//             Server.UpdateField(this);
+//         }
+//     }
 
-    ToScriptString() {
-        return "new ScriptField(...)";
-    }
+//     ToScriptString() {
+//         return "new ScriptField(...)";
+//     }
 
-    GetValue() {
-        return this.script;
-    }
+//     GetValue() {
+//         return this.script;
+//     }
 
-    TrySetValue(): boolean {
-        throw new Error("Script fields currently can't be modified");
-    }
+//     TrySetValue(): boolean {
+//         throw new Error("Script fields currently can't be modified");
+//     }
 
-    UpdateFromServer() {
-        throw new Error("Script fields currently can't be updated");
-    }
+//     UpdateFromServer() {
+//         throw new Error("Script fields currently can't be updated");
+//     }
 
-    static FromJson(id: string, data: ScriptData): ScriptField {
-        let field = new ScriptField(undefined, id, false);
-        field.options = data;
-        return field;
-    }
+//     static FromJson(id: string, data: ScriptData): ScriptField {
+//         let field = new ScriptField(undefined, id, false);
+//         field.options = data;
+//         return field;
+//     }
 
-    init(callback: (res: Field) => any) {
-        const options = this.options!;
-        const keys = Object.keys(options.options.capturedIds);
-        Server.GetFields(keys).then(fields => {
-            let captured: { [name: string]: Field } = {};
-            keys.forEach(key => captured[options.options.capturedIds[key]] = fields[key]);
-            const opts: ScriptOptions = {
-                addReturn: options.options.addReturn,
-                params: options.options.params,
-                requiredType: options.options.requiredType,
-                capturedVariables: captured
-            };
-            const script = CompileScript(options.script, opts);
-            if (!script.compiled) {
-                throw new Error("Can't compile script");
-            }
-            this._script = script;
-            callback(this);
-        });
-    }
+//     init(callback: (res: Field) => any) {
+//         const options = this.options!;
+//         const keys = Object.keys(options.options.capturedIds);
+//         Server.GetFields(keys).then(fields => {
+//             let captured: { [name: string]: Field } = {};
+//             keys.forEach(key => captured[options.options.capturedIds[key]] = fields[key]);
+//             const opts: ScriptOptions = {
+//                 addReturn: options.options.addReturn,
+//                 params: options.options.params,
+//                 requiredType: options.options.requiredType,
+//                 capturedVariables: captured
+//             };
+//             const script = CompileScript(options.script, opts);
+//             if (!script.compiled) {
+//                 throw new Error("Can't compile script");
+//             }
+//             this._script = script;
+//             callback(this);
+//         });
+//     }
 
-    ToJson() {
-        const { options, originalScript } = this.script;
-        let capturedIds: { [id: string]: string } = {};
-        for (const capt in options.capturedVariables) {
-            capturedIds[options.capturedVariables[capt].Id] = capt;
-        }
-        const opts: SerializableOptions = {
-            ...options,
-            capturedIds
-        };
-        delete (opts as any).capturedVariables;
-        return {
-            id: this.Id,
-            type: Types.Script,
-            data: {
-                script: originalScript,
-                options: opts,
-            },
-        };
-    }
+//     ToJson() {
+//         const { options, originalScript } = this.script;
+//         let capturedIds: { [id: string]: string } = {};
+//         for (const capt in options.capturedVariables) {
+//             capturedIds[options.capturedVariables[capt].Id] = capt;
+//         }
+//         const opts: SerializableOptions = {
+//             ...options,
+//             capturedIds
+//         };
+//         delete (opts as any).capturedVariables;
+//         return {
+//             id: this.Id,
+//             type: Types.Script,
+//             data: {
+//                 script: originalScript,
+//                 options: opts,
+//             },
+//         };
+//     }
 
-    Copy(): Field {
-        //Script fields are currently immutable, so we can fake copy them
-        return this;
-    }
-}
\ No newline at end of file
+//     Copy(): Field {
+//         //Script fields are currently immutable, so we can fake copy them
+//         return this;
+//     }
+// }
\ No newline at end of file
diff --git a/src/fields/TextField.ts b/src/fields/TextField.ts
deleted file mode 100644
index ddedec9b1..000000000
--- a/src/fields/TextField.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { BasicField } from "./BasicField";
-import { FieldId } from "./Field";
-import { Types } from "../server/Message";
-
-export class TextField extends BasicField<string> {
-    constructor(data: string = "", id?: FieldId, save: boolean = true) {
-        super(data, save, id);
-    }
-
-    ToScriptString(): string {
-        return `new TextField("${this.Data}")`;
-    }
-
-    Copy() {
-        return new TextField(this.Data);
-    }
-
-    ToJson() {
-        return {
-            type: Types.Text,
-            data: this.Data,
-            id: this.Id
-        };
-    }
-}
\ No newline at end of file
diff --git a/src/fields/TupleField.ts b/src/fields/TupleField.ts
deleted file mode 100644
index 347f1fa05..000000000
--- a/src/fields/TupleField.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-import { action, IArrayChange, IArraySplice, IObservableArray, observe, observable, Lambda } from "mobx";
-import { Server } from "../client/Server";
-import { UndoManager } from "../client/util/UndoManager";
-import { Types } from "../server/Message";
-import { BasicField } from "./BasicField";
-import { Field, FieldId } from "./Field";
-
-export class TupleField<T, U> extends BasicField<[T, U]> {
-    constructor(data: [T, U], id?: FieldId, save: boolean = true) {
-        super(data, save, id);
-        if (save) {
-            Server.UpdateField(this);
-        }
-        this.observeTuple();
-    }
-
-    private observeDisposer: Lambda | undefined;
-    private observeTuple(): void {
-        this.observeDisposer = observe(this.Data as (T | U)[] as IObservableArray<T | U>, (change: IArrayChange<T | U> | IArraySplice<T | U>) => {
-            if (change.type === "update") {
-                UndoManager.AddEvent({
-                    undo: () => this.Data[change.index] = change.oldValue,
-                    redo: () => this.Data[change.index] = change.newValue
-                });
-                Server.UpdateField(this);
-            } else {
-                throw new Error("Why are you messing with the length of a tuple, huh?");
-            }
-        });
-    }
-
-    protected setData(value: [T, U]) {
-        if (this.observeDisposer) {
-            this.observeDisposer();
-        }
-        this.data = observable(value) as (T | U)[] as [T, U];
-        this.observeTuple();
-    }
-
-    UpdateFromServer(values: [T, U]) {
-        this.setData(values);
-    }
-
-    ToScriptString(): string {
-        return `new TupleField([${this.Data[0], this.Data[1]}])`;
-    }
-
-    Copy(): Field {
-        return new TupleField<T, U>(this.Data);
-    }
-
-    ToJson() {
-        return {
-            type: Types.Tuple,
-            data: this.Data,
-            id: this.Id
-        };
-    }
-}
\ No newline at end of file
diff --git a/src/fields/VideoField.ts b/src/fields/VideoField.ts
deleted file mode 100644
index 838b811b1..000000000
--- a/src/fields/VideoField.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import { BasicField } from "./BasicField";
-import { Field, FieldId } from "./Field";
-import { Types } from "../server/Message";
-
-export class VideoField extends BasicField<URL> {
-    constructor(data: URL | undefined = undefined, id?: FieldId, save: boolean = true) {
-        super(data === undefined ? new URL("http://techslides.com/demos/sample-videos/small.mp4") : data, save, id);
-    }
-
-    toString(): string {
-        return this.Data.href;
-    }
-
-    ToScriptString(): string {
-        return `new VideoField("${this.Data}")`;
-    }
-
-    Copy(): Field {
-        return new VideoField(this.Data);
-    }
-
-    ToJson() {
-        return {
-            type: Types.Video,
-            data: this.Data.href,
-            id: this.Id
-        };
-    }
-
-}
\ No newline at end of file
diff --git a/src/fields/WebField.ts b/src/fields/WebField.ts
deleted file mode 100644
index 8b276a552..000000000
--- a/src/fields/WebField.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import { BasicField } from "./BasicField";
-import { Field, FieldId } from "./Field";
-import { Types } from "../server/Message";
-
-export class WebField extends BasicField<URL> {
-    constructor(data: URL | undefined = undefined, id?: FieldId, save: boolean = true) {
-        super(data === undefined ? new URL("https://crossorigin.me/" + "https://cs.brown.edu/") : data, save, id);
-    }
-
-    toString(): string {
-        return this.Data.href;
-    }
-
-    ToScriptString(): string {
-        return `new WebField("${this.Data}")`;
-    }
-
-    Copy(): Field {
-        return new WebField(this.Data);
-    }
-
-    ToJson() {
-        return {
-            type: Types.Web,
-            data: this.Data.href,
-            id: this.Id
-        };
-    }
-
-}
\ No newline at end of file
diff --git a/src/mobile/ImageUpload.tsx b/src/mobile/ImageUpload.tsx
index ec89a1194..1f9e160ce 100644
--- a/src/mobile/ImageUpload.tsx
+++ b/src/mobile/ImageUpload.tsx
@@ -1,15 +1,14 @@
 import * as ReactDOM from 'react-dom';
 import * as rp from 'request-promise';
-import { Documents } from '../client/documents/Documents';
-import { Server } from '../client/Server';
-import { Document } from '../fields/Document';
-import { KeyStore } from '../fields/KeyStore';
-import { ListField } from '../fields/ListField';
+import { Docs } from '../client/documents/Documents';
 import { RouteStore } from '../server/RouteStore';
-import { ServerUtils } from '../server/ServerUtil';
 import "./ImageUpload.scss";
 import React = require('react');
-import { Opt } from '../fields/Field';
+import { DocServer } from '../client/DocServer';
+import { Opt, Doc } from '../new_fields/Doc';
+import { Cast } from '../new_fields/Types';
+import { listSpec } from '../new_fields/Schema';
+import { List } from '../new_fields/List';
 
 
 
@@ -38,21 +37,24 @@ const onFileLoad = async (file: any) => {
             const json = await res.json();
             json.map(async (file: any) => {
                 let path = window.location.origin + file;
-                var doc: Document = Documents.ImageDocument(path, { nativeWidth: 200, width: 200 });
+                var doc = Docs.ImageDocument(path, { nativeWidth: 200, width: 200 });
 
-                const res = await rp.get(ServerUtils.prepend(RouteStore.getUserDocumentId));
+                const res = await rp.get(DocServer.prepend(RouteStore.getUserDocumentId));
                 if (!res) {
                     throw new Error("No user id returned");
                 }
-                const field = await Server.GetField(res);
-                let pending: Opt<Document>;
-                if (field instanceof Document) {
-                    pending = await field.GetTAsync(KeyStore.OptionalRightCollection, Document);
+                const field = await DocServer.GetRefField(res);
+                let pending: Opt<Doc>;
+                if (field instanceof Doc) {
+                    pending = await Cast(field.optionalRightCollection, Doc);
                 }
                 if (pending) {
-                    pending.GetOrCreateAsync(KeyStore.Data, ListField, list => {
-                        list.Data.push(doc);
-                    });
+                    const data = await Cast(pending.data, listSpec(Doc));
+                    if (data) {
+                        data.push(doc);
+                    } else {
+                        pending.data = new List([doc]);
+                    }
                 }
             });
 
diff --git a/src/new_fields/CursorField.ts b/src/new_fields/CursorField.ts
new file mode 100644
index 000000000..7fd326a5f
--- /dev/null
+++ b/src/new_fields/CursorField.ts
@@ -0,0 +1,55 @@
+import { ObjectField, Copy, OnUpdate } from "./ObjectField";
+import { observable } from "mobx";
+import { Deserializable } from "../client/util/SerializationHelper";
+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,
+    y: true
+});
+
+const MetadataSchema = createSimpleSchema({
+    id: true,
+    identifier: true
+});
+
+const CursorSchema = createSimpleSchema({
+    metadata: object(MetadataSchema),
+    position: object(PositionSchema)
+});
+
+@Deserializable("cursor")
+export default class CursorField extends ObjectField {
+
+    @serializable(object(CursorSchema))
+    readonly data: CursorData;
+
+    constructor(data: CursorData) {
+        super();
+        this.data = data;
+    }
+
+    setPosition(position: CursorPosition) {
+        this.data.position = position;
+        this[OnUpdate]();
+    }
+
+    [Copy]() {
+        return new CursorField(this.data);
+    }
+}
\ No newline at end of file
diff --git a/src/new_fields/DateField.ts b/src/new_fields/DateField.ts
new file mode 100644
index 000000000..c0a79f267
--- /dev/null
+++ b/src/new_fields/DateField.ts
@@ -0,0 +1,18 @@
+import { Deserializable } from "../client/util/SerializationHelper";
+import { serializable, date } from "serializr";
+import { ObjectField, Copy } from "./ObjectField";
+
+@Deserializable("date")
+export class DateField extends ObjectField {
+    @serializable(date())
+    readonly date: Date;
+
+    constructor(date: Date = new Date()) {
+        super();
+        this.date = date;
+    }
+
+    [Copy]() {
+        return new DateField(this.date);
+    }
+}
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
new file mode 100644
index 000000000..3ccc06d44
--- /dev/null
+++ b/src/new_fields/Doc.ts
@@ -0,0 +1,228 @@
+import { observable, action } from "mobx";
+import { serializable, primitive, map, alias, list } from "serializr";
+import { autoObject, SerializationHelper, Deserializable } from "../client/util/SerializationHelper";
+import { DocServer } from "../client/DocServer";
+import { setter, getter, getField, updateFunction, deleteProperty } from "./util";
+import { Cast, ToConstructor, PromiseValue, FieldValue, NumCast } from "./Types";
+import { UndoManager, undoBatch } from "../client/util/UndoManager";
+import { listSpec } from "./Schema";
+import { List } from "./List";
+import { ObjectField, Parent, OnUpdate } from "./ObjectField";
+import { RefField, FieldId, Id, HandleUpdate } from "./RefField";
+import { Docs } from "../client/documents/Documents";
+
+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");
+
+export function DocListCast(field: FieldResult): Promise<Doc[] | undefined>;
+export function DocListCast(field: FieldResult, defaultValue: Doc[]): Promise<Doc[]>;
+export function DocListCast(field: FieldResult, defaultValue?: Doc[]) {
+    const list = Cast(field, listSpec(Doc));
+    return list ? Promise.all(list) : Promise.resolve(defaultValue);
+}
+
+@Deserializable("doc").withFields(["id"])
+export class Doc extends RefField {
+    constructor(id?: FieldId, forceSave?: boolean) {
+        super(id);
+        const doc = new Proxy<this>(this, {
+            set: setter,
+            get: getter,
+            has: (target, key) => key in target.__fields,
+            ownKeys: target => Object.keys(target.__fields),
+            getOwnPropertyDescriptor: (target, prop) => {
+                if (prop in target.__fields) {
+                    return {
+                        configurable: true,//TODO Should configurable be true?
+                        enumerable: true,
+                    };
+                }
+                return Reflect.getOwnPropertyDescriptor(target, prop);
+            },
+            deleteProperty: deleteProperty,
+            defineProperty: () => { throw new Error("Currently properties can't be defined on documents using Object.defineProperty"); },
+        });
+        this[SelfProxy] = doc;
+        if (!id || forceSave) {
+            DocServer.CreateField(doc);
+        }
+        return doc;
+    }
+
+    proto: Opt<Doc>;
+    [key: string]: FieldResult;
+
+    @serializable(alias("fields", map(autoObject())))
+    private get __fields() {
+        return this.___fields;
+    }
+
+    private set __fields(value) {
+        this.___fields = value;
+        for (const key in value) {
+            const field = value[key];
+            if (!(field instanceof ObjectField)) continue;
+            field[Parent] = this[Self];
+            field[OnUpdate] = updateFunction(this[Self], key, field, this[SelfProxy]);
+        }
+    }
+
+    @observable
+    //{ [key: string]: Field | FieldWaiting | undefined }
+    private ___fields: any = {};
+
+    private [Update] = (diff: any) => {
+        DocServer.UpdateField(this[Id], diff);
+    }
+
+    private [Self] = this;
+    private [SelfProxy]: any;
+    public [WidthSym] = () => NumCast(this.__fields.width);  // bcz: is this the right way to access width/height?   it didn't work with : this.width
+    public [HeightSym] = () => NumCast(this.__fields.height);
+
+    public [HandleUpdate](diff: any) {
+        const set = diff.$set;
+        if (set) {
+            for (const key in set) {
+                if (!key.startsWith("fields.")) {
+                    continue;
+                }
+                const value = SerializationHelper.Deserialize(set[key]);
+                const fKey = key.substring(7);
+                this[fKey] = value;
+            }
+        }
+    }
+}
+
+export namespace Doc {
+    // export function GetAsync(doc: Doc, key: string, ignoreProto: boolean = false): Promise<Field | undefined> {
+    //     const self = doc[Self];
+    //     return new Promise(res => getField(self, key, ignoreProto, res));
+    // }
+    // export function GetTAsync<T extends Field>(doc: Doc, key: string, ctor: ToConstructor<T>, ignoreProto: boolean = false): Promise<T | undefined> {
+    //     return new Promise(async res => {
+    //         const field = await GetAsync(doc, key, ignoreProto);
+    //         return Cast(field, ctor);
+    //     });
+    // }
+    export function Get(doc: Doc, key: string, ignoreProto: boolean = false): FieldResult {
+        const self = doc[Self];
+        return getField(self, key, ignoreProto);
+    }
+    export function GetT<T extends Field>(doc: Doc, key: string, ctor: ToConstructor<T>, ignoreProto: boolean = false): T | null | undefined {
+        return Cast(Get(doc, key, ignoreProto), ctor) as T | null | undefined;
+    }
+    export async function SetOnPrototype(doc: Doc, key: string, value: Field) {
+        const proto = doc.proto;
+        if (proto) {
+            proto[key] = value;
+        }
+    }
+    export function GetAllPrototypes(doc: Doc): Doc[] {
+        const protos: Doc[] = [];
+        let d: Opt<Doc> = doc;
+        while (d) {
+            protos.push(d);
+            d = FieldValue(d.proto);
+        }
+        return protos;
+    }
+    export function assign<K extends string>(doc: Doc, fields: Partial<Record<K, Opt<Field>>>) {
+        for (const key in fields) {
+            if (fields.hasOwnProperty(key)) {
+                const value = fields[key];
+                if (value !== undefined) {
+                    doc[key] = value;
+                }
+            }
+        }
+        return doc;
+    }
+
+    export function MakeAlias(doc: Doc) {
+        const alias = new Doc;
+
+        PromiseValue(Cast(doc.proto, Doc)).then(proto => {
+            if (proto) {
+                alias.proto = proto;
+            }
+        });
+
+        return alias;
+    }
+
+    export function MakeCopy(doc: Doc, copyProto: boolean = false): Doc {
+        const copy = new Doc;
+        Object.keys(doc).forEach(key => {
+            const field = doc[key];
+            if (key === "proto" && copyProto) {
+                if (field instanceof Doc) {
+                    copy[key] = Doc.MakeCopy(field);
+                }
+            } else {
+                if (field instanceof RefField) {
+                    copy[key] = field;
+                } else if (field instanceof ObjectField) {
+                    copy[key] = ObjectField.MakeCopy(field);
+                } else {
+                    copy[key] = field;
+                }
+            }
+        });
+        return copy;
+    }
+
+    export function MakeLink(source: Doc, target: Doc) {
+        UndoManager.RunInBatch(() => {
+            let linkDoc = Docs.TextDocument({ width: 100, height: 30, borderRounding: -1 });
+            //let linkDoc = new Doc;
+            linkDoc.proto!.title = "-link name-";
+            linkDoc.linkDescription = "";
+            linkDoc.linkTags = "Default";
+
+            linkDoc.linkedTo = target;
+            linkDoc.linkedFrom = source;
+
+            let linkedFrom = Cast(target.linkedFromDocs, listSpec(Doc));
+            if (!linkedFrom) {
+                target.linkedFromDocs = linkedFrom = new List<Doc>();
+            }
+            linkedFrom.push(linkDoc);
+
+            let linkedTo = Cast(source.linkedToDocs, listSpec(Doc));
+            if (!linkedTo) {
+                source.linkedToDocs = linkedTo = new List<Doc>();
+            }
+            linkedTo.push(linkDoc);
+            return linkDoc;
+        }, "make link");
+    }
+
+    export function MakeDelegate(doc: Doc): Doc;
+    export function MakeDelegate(doc: Opt<Doc>): Opt<Doc>;
+    export function MakeDelegate(doc: Opt<Doc>): Opt<Doc> {
+        if (!doc) {
+            return undefined;
+        }
+        const delegate = new Doc();
+        delegate.proto = doc;
+        return delegate;
+    }
+    export const Prototype = Symbol("Prototype");
+}
\ No newline at end of file
diff --git a/src/new_fields/HtmlField.ts b/src/new_fields/HtmlField.ts
new file mode 100644
index 000000000..d998746bb
--- /dev/null
+++ b/src/new_fields/HtmlField.ts
@@ -0,0 +1,18 @@
+import { Deserializable } from "../client/util/SerializationHelper";
+import { serializable, primitive } from "serializr";
+import { ObjectField, Copy } from "./ObjectField";
+
+@Deserializable("html")
+export class HtmlField extends ObjectField {
+    @serializable(primitive())
+    readonly html: string;
+
+    constructor(html: string) {
+        super();
+        this.html = html;
+    }
+
+    [Copy]() {
+        return new HtmlField(this.html);
+    }
+}
diff --git a/src/new_fields/IconField.ts b/src/new_fields/IconField.ts
new file mode 100644
index 000000000..1a928389d
--- /dev/null
+++ b/src/new_fields/IconField.ts
@@ -0,0 +1,18 @@
+import { Deserializable } from "../client/util/SerializationHelper";
+import { serializable, primitive } from "serializr";
+import { ObjectField, Copy } from "./ObjectField";
+
+@Deserializable("icon")
+export class IconField extends ObjectField {
+    @serializable(primitive())
+    readonly icon: string;
+
+    constructor(icon: string) {
+        super();
+        this.icon = icon;
+    }
+
+    [Copy]() {
+        return new IconField(this.icon);
+    }
+}
diff --git a/src/new_fields/InkField.ts b/src/new_fields/InkField.ts
new file mode 100644
index 000000000..2d75f8a19
--- /dev/null
+++ b/src/new_fields/InkField.ts
@@ -0,0 +1,43 @@
+import { Deserializable } from "../client/util/SerializationHelper";
+import { serializable, custom, createSimpleSchema, list, object, map } from "serializr";
+import { ObjectField, Copy } from "./ObjectField";
+import { deepCopy } from "../Utils";
+
+export enum InkTool {
+    None,
+    Pen,
+    Highlighter,
+    Eraser
+}
+
+export interface StrokeData {
+    pathData: Array<{ x: number, y: number }>;
+    color: string;
+    width: string;
+    tool: InkTool;
+    page: number;
+}
+
+const pointSchema = createSimpleSchema({
+    x: true, y: true
+});
+
+const strokeDataSchema = createSimpleSchema({
+    pathData: list(object(pointSchema)),
+    "*": true
+});
+
+@Deserializable("ink")
+export class InkField extends ObjectField {
+    @serializable(map(object(strokeDataSchema)))
+    readonly inkData: Map<string, StrokeData>;
+
+    constructor(data?: Map<string, StrokeData>) {
+        super();
+        this.inkData = data || new Map;
+    }
+
+    [Copy]() {
+        return new InkField(deepCopy(this.inkData));
+    }
+}
diff --git a/src/new_fields/List.ts b/src/new_fields/List.ts
new file mode 100644
index 000000000..88a65eba4
--- /dev/null
+++ b/src/new_fields/List.ts
@@ -0,0 +1,279 @@
+import { Deserializable, autoObject } from "../client/util/SerializationHelper";
+import { Field, Update, Self, FieldResult, SelfProxy } 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 { RefField } from "./RefField";
+import { ProxyField } from "./Proxy";
+
+const listHandlers: any = {
+    /// Mutator methods
+    copyWithin() {
+        throw new Error("copyWithin not supported yet");
+    },
+    fill(value: any, start?: number, end?: number) {
+        if (value instanceof RefField) {
+            throw new Error("fill with RefFields not supported yet");
+        }
+        const res = this[Self].__fields.fill(value, start, end);
+        this[Update]();
+        return res;
+    },
+    pop(): any {
+        const field = toRealField(this[Self].__fields.pop());
+        this[Update]();
+        return field;
+    },
+    push: action(function (this: any, ...items: any[]) {
+        items = items.map(toObjectField);
+        const list = this[Self];
+        const length = list.__fields.length;
+        for (let i = 0; i < items.length; i++) {
+            const item = items[i];
+            //TODO Error checking to make sure parent doesn't already exist
+            if (item instanceof ObjectField) {
+                item[Parent] = list;
+                item[OnUpdate] = updateFunction(list, i + length, item, this);
+            }
+        }
+        const res = list.__fields.push(...items);
+        this[Update]();
+        return res;
+    }),
+    reverse() {
+        const res = this[Self].__fields.reverse();
+        this[Update]();
+        return res;
+    },
+    shift() {
+        const res = toRealField(this[Self].__fields.shift());
+        this[Update]();
+        return res;
+    },
+    sort(cmpFunc: any) {
+        const res = this[Self].__fields.sort(cmpFunc ? (first: any, second: any) => cmpFunc(toRealField(first), toRealField(second)) : undefined);
+        this[Update]();
+        return res;
+    },
+    splice: action(function (this: any, start: number, deleteCount: number, ...items: any[]) {
+        items = items.map(toObjectField);
+        const list = this[Self];
+        for (let i = 0; i < items.length; i++) {
+            const item = items[i];
+            //TODO Error checking to make sure parent doesn't already exist
+            //TODO Need to change indices of other fields in array
+            if (item instanceof ObjectField) {
+                item[Parent] = list;
+                item[OnUpdate] = updateFunction(list, i + start, item, this);
+            }
+        }
+        const res = list.__fields.splice(start, deleteCount, ...items);
+        this[Update]();
+        return res.map(toRealField);
+    }),
+    unshift(...items: any[]) {
+        items = items.map(toObjectField);
+        const list = this[Self];
+        const length = list.__fields.length;
+        for (let i = 0; i < items.length; i++) {
+            const item = items[i];
+            //TODO Error checking to make sure parent doesn't already exist
+            //TODO Need to change indices of other fields in array
+            if (item instanceof ObjectField) {
+                item[Parent] = list;
+                item[OnUpdate] = updateFunction(list, i, item, this);
+            }
+        }
+        const res = this[Self].__fields.unshift(...items);
+        this[Update]();
+        return res;
+
+    },
+    /// Accessor methods
+    concat: action(function (this: any, ...items: any[]) {
+        return this[Self].__fields.map(toRealField).concat(...items);
+    }),
+    includes(valueToFind: any, fromIndex: number) {
+        const fields = this[Self].__fields;
+        if (valueToFind instanceof RefField) {
+            return fields.map(toRealField).includes(valueToFind, fromIndex);
+        } else {
+            return fields.includes(valueToFind, fromIndex);
+        }
+    },
+    indexOf(valueToFind: any, fromIndex: number) {
+        const fields = this[Self].__fields;
+        if (valueToFind instanceof RefField) {
+            return fields.map(toRealField).indexOf(valueToFind, fromIndex);
+        } else {
+            return fields.indexOf(valueToFind, fromIndex);
+        }
+    },
+    join(separator: any) {
+        return this[Self].__fields.map(toRealField).join(separator);
+    },
+    lastIndexOf(valueToFind: any, fromIndex: number) {
+        const fields = this[Self].__fields;
+        if (valueToFind instanceof RefField) {
+            return fields.map(toRealField).lastIndexOf(valueToFind, fromIndex);
+        } else {
+            return fields.lastIndexOf(valueToFind, fromIndex);
+        }
+    },
+    slice(begin: number, end: number) {
+        return this[Self].__fields.slice(begin, end).map(toRealField);
+    },
+
+    /// Iteration methods
+    entries() {
+        return this[Self].__fields.map(toRealField).entries();
+    },
+    every(callback: any, thisArg: any) {
+        return this[Self].__fields.map(toRealField).every(callback, thisArg);
+        // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+        // If we don't want to support the array parameter, we should use this version instead
+        // return this[Self].__fields.every((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+    },
+    filter(callback: any, thisArg: any) {
+        return this[Self].__fields.map(toRealField).filter(callback, thisArg);
+        // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+        // If we don't want to support the array parameter, we should use this version instead
+        // return this[Self].__fields.filter((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+    },
+    find(callback: any, thisArg: any) {
+        return this[Self].__fields.map(toRealField).find(callback, thisArg);
+        // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+        // If we don't want to support the array parameter, we should use this version instead
+        // return this[Self].__fields.find((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+    },
+    findIndex(callback: any, thisArg: any) {
+        return this[Self].__fields.map(toRealField).findIndex(callback, thisArg);
+        // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+        // If we don't want to support the array parameter, we should use this version instead
+        // return this[Self].__fields.findIndex((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+    },
+    forEach(callback: any, thisArg: any) {
+        return this[Self].__fields.map(toRealField).forEach(callback, thisArg);
+        // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+        // If we don't want to support the array parameter, we should use this version instead
+        // return this[Self].__fields.forEach((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+    },
+    map(callback: any, thisArg: any) {
+        return this[Self].__fields.map(toRealField).map(callback, thisArg);
+        // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+        // If we don't want to support the array parameter, we should use this version instead
+        // return this[Self].__fields.map((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+    },
+    reduce(callback: any, initialValue: any) {
+        return this[Self].__fields.map(toRealField).reduce(callback, initialValue);
+        // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+        // If we don't want to support the array parameter, we should use this version instead
+        // return this[Self].__fields.reduce((acc:any, element:any, index:number, array:any) => callback(acc, toRealField(element), index, array), initialValue);
+    },
+    reduceRight(callback: any, initialValue: any) {
+        return this[Self].__fields.map(toRealField).reduceRight(callback, initialValue);
+        // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+        // If we don't want to support the array parameter, we should use this version instead
+        // return this[Self].__fields.reduceRight((acc:any, element:any, index:number, array:any) => callback(acc, toRealField(element), index, array), initialValue);
+    },
+    some(callback: any, thisArg: any) {
+        return this[Self].__fields.map(toRealField).some(callback, thisArg);
+        // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+        // If we don't want to support the array parameter, we should use this version instead
+        // return this[Self].__fields.some((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+    },
+    values() {
+        return this[Self].__fields.map(toRealField).values();
+    },
+    [Symbol.iterator]() {
+        return this[Self].__fields.map(toRealField).values();
+    }
+};
+
+function toObjectField(field: Field) {
+    return field instanceof RefField ? new ProxyField(field) : field;
+}
+
+function toRealField(field: Field) {
+    return field instanceof ProxyField ? field.value() : field;
+}
+
+function listGetter(target: any, prop: string | number | symbol, receiver: any): any {
+    if (listHandlers.hasOwnProperty(prop)) {
+        return listHandlers[prop];
+    }
+    return getter(target, prop, receiver);
+}
+
+interface ListSpliceUpdate<T> {
+    type: "splice";
+    index: number;
+    added: T[];
+    removedCount: number;
+}
+
+interface ListIndexUpdate<T> {
+    type: "update";
+    index: number;
+    newValue: T;
+}
+
+type ListUpdate<T> = ListSpliceUpdate<T> | ListIndexUpdate<T>;
+
+type StoredType<T extends Field> = T extends RefField ? ProxyField<T> : T;
+
+@Deserializable("list")
+class ListImpl<T extends Field> extends ObjectField {
+    constructor(fields: T[] = []) {
+        super();
+        const list = new Proxy<this>(this, {
+            set: setter,
+            get: listGetter,
+            deleteProperty: deleteProperty,
+            defineProperty: () => { throw new Error("Currently properties can't be defined on documents using Object.defineProperty"); },
+        });
+        this[SelfProxy] = list;
+        (list as any).push(...fields);
+        return list;
+    }
+
+    [key: number]: T | (T extends RefField ? Promise<T> : never);
+
+    @serializable(alias("fields", list(autoObject())))
+    private get __fields() {
+        return this.___fields;
+    }
+
+    private set __fields(value) {
+        this.___fields = value;
+        for (const key in value) {
+            const field = value[key];
+            if (!(field instanceof ObjectField)) continue;
+            (field as ObjectField)[Parent] = this[Self];
+            (field as ObjectField)[OnUpdate] = updateFunction(this[Self], key, field, this[SelfProxy]);
+        }
+    }
+
+    [Copy]() {
+        let copiedData = this[Self].__fields.map(f => f instanceof ObjectField ? f[Copy]() : f);
+        let deepCopy = new ListImpl<T>(copiedData as any);
+        return deepCopy;
+    }
+
+    // @serializable(alias("fields", list(autoObject())))
+    @observable
+    private ___fields: StoredType<T>[] = [];
+
+    private [Update] = (diff: any) => {
+        // console.log(diff);
+        const update = this[OnUpdate];
+        // update && update(diff);
+        update && update();
+    }
+
+    private [Self] = this;
+    private [SelfProxy]: any;
+}
+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
new file mode 100644
index 000000000..f276bfa67
--- /dev/null
+++ b/src/new_fields/ObjectField.ts
@@ -0,0 +1,18 @@
+import { Doc } from "./Doc";
+import { RefField } from "./RefField";
+
+export const OnUpdate = Symbol("OnUpdate");
+export const Parent = Symbol("Parent");
+export const Copy = Symbol("Copy");
+
+export abstract class ObjectField {
+    protected [OnUpdate](diff?: any) { };
+    private [Parent]?: RefField | ObjectField;
+    abstract [Copy](): ObjectField;
+}
+
+export namespace ObjectField {
+    export function MakeCopy<T extends ObjectField>(field: T) {
+        return field[Copy]();
+    }
+}
diff --git a/src/new_fields/Proxy.ts b/src/new_fields/Proxy.ts
new file mode 100644
index 000000000..fd99ae1c0
--- /dev/null
+++ b/src/new_fields/Proxy.ts
@@ -0,0 +1,65 @@
+import { Deserializable } from "../client/util/SerializationHelper";
+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";
+
+@Deserializable("proxy")
+export class ProxyField<T extends RefField> extends ObjectField {
+    constructor();
+    constructor(value: T);
+    constructor(fieldId: string);
+    constructor(value?: T | string) {
+        super();
+        if (typeof value === "string") {
+            this.fieldId = value;
+        } else if (value) {
+            this.cache = value;
+            this.fieldId = value[Id];
+        }
+    }
+
+    [Copy]() {
+        if (this.cache) return new ProxyField<T>(this.cache);
+        return new ProxyField<T>(this.fieldId);
+    }
+
+    @serializable(primitive())
+    readonly fieldId: string = "";
+
+    // This getter/setter and nested object thing is 
+    // because mobx doesn't play well with observable proxies
+    @observable.ref
+    private _cache: { readonly field: T | undefined } = { field: undefined };
+    private get cache(): T | undefined {
+        return this._cache.field;
+    }
+    private set cache(field: T | undefined) {
+        this._cache = { field };
+    }
+
+    private failed = false;
+    private promise?: Promise<any>;
+
+    value(callback?: ((field: T | undefined) => void)): T | undefined | FieldWaiting {
+        if (this.cache) {
+            callback && callback(this.cache);
+            return this.cache;
+        }
+        if (this.failed) {
+            return undefined;
+        }
+        if (!this.promise) {
+            this.promise = DocServer.GetRefField(this.fieldId).then(action((field: any) => {
+                this.promise = undefined;
+                this.cache = field;
+                if (field === undefined) this.failed = true;
+                return field;
+            }));
+        }
+        callback && this.promise.then(callback);
+        return this.promise;
+    }
+}
diff --git a/src/new_fields/RefField.ts b/src/new_fields/RefField.ts
new file mode 100644
index 000000000..202c65f21
--- /dev/null
+++ b/src/new_fields/RefField.ts
@@ -0,0 +1,18 @@
+import { serializable, primitive, alias } from "serializr";
+import { Utils } from "../Utils";
+
+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;
+    readonly [Id]: FieldId;
+
+    constructor(id?: FieldId) {
+        this.__id = id || Utils.GenerateGuid();
+        this[Id] = this.__id;
+    }
+
+    protected [HandleUpdate]?(diff: any): void;
+}
diff --git a/src/new_fields/RichTextField.ts b/src/new_fields/RichTextField.ts
new file mode 100644
index 000000000..eb30e76de
--- /dev/null
+++ b/src/new_fields/RichTextField.ts
@@ -0,0 +1,18 @@
+import { ObjectField, Copy } from "./ObjectField";
+import { serializable } from "serializr";
+import { Deserializable } from "../client/util/SerializationHelper";
+
+@Deserializable("RichTextField")
+export class RichTextField extends ObjectField {
+    @serializable(true)
+    readonly Data: string;
+
+    constructor(data: string) {
+        super();
+        this.Data = data;
+    }
+
+    [Copy]() {
+        return new RichTextField(this.Data);
+    }
+}
\ No newline at end of file
diff --git a/src/new_fields/Schema.ts b/src/new_fields/Schema.ts
new file mode 100644
index 000000000..b821baec9
--- /dev/null
+++ b/src/new_fields/Schema.ts
@@ -0,0 +1,82 @@
+import { Interface, ToInterface, Cast, ToConstructor, HasTail, Head, Tail, ListSpec, ToType } from "./Types";
+import { Doc, Field } from "./Doc";
+
+type AllToInterface<T extends Interface[]> = {
+    1: ToInterface<Head<T>> & AllToInterface<Tail<T>>,
+    0: ToInterface<Head<T>>
+}[HasTail<T> extends true ? 1 : 0];
+
+export const emptySchema = createSchema({});
+export const Document = makeInterface(emptySchema);
+export type Document = makeInterface<[typeof emptySchema]>;
+
+export type makeInterface<T extends Interface[]> = Partial<AllToInterface<T>> & Doc & { proto: Doc | undefined };
+// export function makeInterface<T extends Interface[], U extends Doc>(schemas: T): (doc: U) => All<T, U>;
+// export function makeInterface<T extends Interface, U extends Doc>(schema: T): (doc: U) => makeInterface<T, U>; 
+export function makeInterface<T extends Interface[]>(...schemas: T): (doc?: Doc) => makeInterface<T> {
+    let schema: Interface = {};
+    for (const s of schemas) {
+        for (const key in s) {
+            schema[key] = s[key];
+        }
+    }
+    const proto = new Proxy({}, {
+        get(target: any, prop, receiver) {
+            const field = receiver.doc[prop];
+            if (prop in schema) {
+                return Cast(field, (schema as any)[prop]);
+            }
+            return field;
+        },
+        set(target: any, prop, value, receiver) {
+            receiver.doc[prop] = value;
+            return true;
+        }
+    });
+    return function (doc?: Doc) {
+        doc = doc || new Doc;
+        if (!(doc instanceof Doc)) {
+            throw new Error("Currently wrapping a schema in another schema isn't supported");
+        }
+        const obj = Object.create(proto, { doc: { value: doc, writable: false } });
+        return obj;
+    };
+}
+
+export type makeStrictInterface<T extends Interface> = Partial<ToInterface<T>>;
+export function makeStrictInterface<T extends Interface>(schema: T): (doc: Doc) => makeStrictInterface<T> {
+    const proto = {};
+    for (const key in schema) {
+        const type = schema[key];
+        Object.defineProperty(proto, key, {
+            get() {
+                return Cast(this.__doc[key], type as any);
+            },
+            set(value) {
+                value = Cast(value, type as any);
+                if (value !== undefined) {
+                    this.__doc[key] = value;
+                    return;
+                }
+                throw new TypeError("Expected type " + type);
+            }
+        });
+    }
+    return function (doc: any) {
+        if (!(doc instanceof Doc)) {
+            throw new Error("Currently wrapping a schema in another schema isn't supported");
+        }
+        const obj = Object.create(proto);
+        obj.__doc = doc;
+        return obj;
+    };
+}
+
+export function createSchema<T extends Interface>(schema: T): T & { proto: ToConstructor<Doc> } {
+    schema.proto = Doc;
+    return schema as any;
+}
+
+export function listSpec<U extends ToConstructor<Field>>(type: U): ListSpec<ToType<U>> {
+    return { List: type as any };//TODO Types
+}
\ No newline at end of file
diff --git a/src/new_fields/Types.ts b/src/new_fields/Types.ts
new file mode 100644
index 000000000..4b4c58eb8
--- /dev/null
+++ b/src/new_fields/Types.ts
@@ -0,0 +1,88 @@
+import { Field, Opt, FieldResult, Doc } from "./Doc";
+import { List } from "./List";
+import { RefField } from "./RefField";
+
+export type ToType<T extends ToConstructor<Field> | ListSpec<Field>> =
+    T extends "string" ? string :
+    T extends "number" ? number :
+    T extends "boolean" ? boolean :
+    T extends ListSpec<infer U> ? List<U> :
+    // T extends { new(...args: any[]): infer R } ? (R | Promise<R>) : never;
+    T extends { new(...args: any[]): List<Field> } ? never :
+    T extends { new(...args: any[]): infer R } ? R : never;
+
+export type ToConstructor<T extends Field> =
+    T extends string ? "string" :
+    T extends number ? "number" :
+    T extends boolean ? "boolean" :
+    T extends List<infer U> ? ListSpec<U> :
+    new (...args: any[]) => T;
+
+export type ToInterface<T extends Interface> = {
+    [P in Exclude<keyof T, "proto">]: FieldResult<ToType<T[P]>>;
+};
+
+// type ListSpec<T extends Field[]> = { List: ToContructor<Head<T>> | ListSpec<Tail<T>> };
+export type ListSpec<T extends Field> = { List: ToConstructor<T> };
+
+// type ListType<U extends Field[]> = { 0: List<ListType<Tail<U>>>, 1: ToType<Head<U>> }[HasTail<U> extends true ? 0 : 1];
+
+export type Head<T extends any[]> = T extends [any, ...any[]] ? T[0] : never;
+export type Tail<T extends any[]> =
+    ((...t: T) => any) extends ((_: any, ...tail: infer TT) => any) ? TT : [];
+export type HasTail<T extends any[]> = T extends ([] | [any]) ? false : true;
+
+//TODO Allow you to optionally specify default values for schemas, which should then make that field not be partial
+export interface Interface {
+    [key: string]: ToConstructor<Field> | ListSpec<Field>;
+    // [key: string]: ToConstructor<Field> | ListSpec<Field[]>;
+}
+
+export function Cast<T extends ToConstructor<Field> | ListSpec<Field>>(field: FieldResult, ctor: T): FieldResult<ToType<T>>;
+export function Cast<T extends ToConstructor<Field> | ListSpec<Field>>(field: FieldResult, ctor: T, defaultVal: WithoutList<ToType<T>> | null): WithoutList<ToType<T>>;
+export function Cast<T extends ToConstructor<Field> | ListSpec<Field>>(field: FieldResult, ctor: T, defaultVal?: ToType<T> | null): FieldResult<ToType<T>> | undefined {
+    if (field instanceof Promise) {
+        return defaultVal === undefined ? field.then(f => Cast(f, ctor) as any) as any : defaultVal === null ? undefined : defaultVal;
+    }
+    if (field !== undefined && !(field instanceof Promise)) {
+        if (typeof ctor === "string") {
+            if (typeof field === ctor) {
+                return field as ToType<T>;
+            }
+        } else if (typeof ctor === "object") {
+            if (field instanceof List) {
+                return field as any;
+            }
+        } else if (field instanceof (ctor as any)) {
+            return field as ToType<T>;
+        }
+    }
+    return defaultVal === null ? undefined : defaultVal;
+}
+
+export function NumCast(field: FieldResult, defaultVal: number | null = 0) {
+    return Cast(field, "number", defaultVal);
+}
+
+export function StrCast(field: FieldResult, defaultVal: string | null = "") {
+    return Cast(field, "string", defaultVal);
+}
+
+export function BoolCast(field: FieldResult, defaultVal: boolean | null = null) {
+    return Cast(field, "boolean", defaultVal);
+}
+
+type WithoutList<T extends Field> = T extends List<infer R> ? (R extends RefField ? (R | Promise<R>)[] : R[]) : T;
+
+export function FieldValue<T extends Field, U extends WithoutList<T>>(field: FieldResult<T>, defaultValue: U): WithoutList<T>;
+export function FieldValue<T extends Field>(field: FieldResult<T>): Opt<T>;
+export function FieldValue<T extends Field>(field: FieldResult<T>, defaultValue?: T): Opt<T> {
+    return (field instanceof Promise || field === undefined) ? defaultValue : field;
+}
+
+export interface PromiseLike<T> {
+    then(callback: (field: Opt<T>) => void): void;
+}
+export function PromiseValue<T extends Field>(field: FieldResult<T>): PromiseLike<Opt<T>> {
+    return field instanceof Promise ? field : { then(cb: ((field: Opt<T>) => void)) { return cb(field); } };
+}
\ No newline at end of file
diff --git a/src/new_fields/URLField.ts b/src/new_fields/URLField.ts
new file mode 100644
index 000000000..d00a95a16
--- /dev/null
+++ b/src/new_fields/URLField.ts
@@ -0,0 +1,34 @@
+import { Deserializable } from "../client/util/SerializationHelper";
+import { serializable, custom } from "serializr";
+import { ObjectField, Copy } from "./ObjectField";
+
+function url() {
+    return custom(
+        function (value: URL) {
+            return value.href;
+        },
+        function (jsonValue: string) {
+            return new URL(jsonValue);
+        }
+    );
+}
+
+export class URLField extends ObjectField {
+    @serializable(url())
+    readonly url: URL;
+
+    constructor(url: URL) {
+        super();
+        this.url = url;
+    }
+
+    [Copy](): this {
+        return new (this.constructor as any)(this.url);
+    }
+}
+
+@Deserializable("audio") export class AudioField extends URLField { }
+@Deserializable("image") export class ImageField extends URLField { }
+@Deserializable("video") export class VideoField extends URLField { }
+@Deserializable("pdf") export class PdfField extends URLField { }
+@Deserializable("web") export class WebField extends URLField { }
\ No newline at end of file
diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts
new file mode 100644
index 000000000..bbd8157f6
--- /dev/null
+++ b/src/new_fields/util.ts
@@ -0,0 +1,104 @@
+import { UndoManager } from "../client/util/UndoManager";
+import { Update, 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 { action } from "mobx";
+
+export const setter = action(function (target: any, prop: string | symbol | number, value: any, receiver: any): boolean {
+    if (SerializationHelper.IsSerializing()) {
+        target[prop] = value;
+        return true;
+    }
+    if (typeof prop === "symbol") {
+        target[prop] = value;
+        return true;
+    }
+    const curValue = target.__fields[prop];
+    if (curValue === value || (curValue instanceof ProxyField && value instanceof RefField && curValue.fieldId === value[Id])) {
+        // TODO This kind of checks correctly in the case that curValue is a ProxyField and value is a RefField, but technically
+        // curValue should get filled in with value if it isn't already filled in, in case we fetched the referenced field some other way
+        return true;
+    }
+    if (value instanceof RefField) {
+        value = new ProxyField(value);
+    }
+    if (value instanceof ObjectField) {
+        //TODO Instead of target, maybe use target[Self]
+        if (value[Parent] && value[Parent] !== target) {
+            throw new Error("Can't put the same object in multiple documents at the same time");
+        }
+        value[Parent] = target;
+        value[OnUpdate] = updateFunction(target, prop, value, receiver);
+    }
+    if (curValue instanceof ObjectField) {
+        delete curValue[Parent];
+        delete curValue[OnUpdate];
+    }
+    target.__fields[prop] = value;
+    target[Update]({ '$set': { ["fields." + prop]: value instanceof ObjectField ? SerializationHelper.Serialize(value) : (value === undefined ? null : value) } });
+    UndoManager.AddEvent({
+        redo: () => receiver[prop] = value,
+        undo: () => receiver[prop] = curValue
+    });
+    return true;
+});
+
+export function getter(target: any, prop: string | symbol | number, receiver: any): any {
+    if (typeof prop === "symbol") {
+        return target.__fields[prop] || target[prop];
+    }
+    if (SerializationHelper.IsSerializing()) {
+        return target[prop];
+    }
+    return getField(target, prop);
+}
+
+export function getField(target: any, prop: string | number, ignoreProto: boolean = false, callback?: (field: Field | undefined) => void): any {
+    const field = target.__fields[prop];
+    if (field instanceof ProxyField) {
+        return field.value(callback);
+    }
+    if (field === undefined && !ignoreProto) {
+        const proto = getField(target, "proto", true);
+        if (proto instanceof Doc) {
+            let field = proto[prop];
+            if (field instanceof Promise) {
+                callback && field.then(callback);
+                return undefined;
+            } else {
+                callback && callback(field);
+                return field;
+            }
+        }
+    }
+    callback && callback(field);
+    return field;
+}
+
+export function deleteProperty(target: any, prop: string | number | symbol) {
+    if (typeof prop === "symbol") {
+        delete target[prop];
+        return true;
+    }
+    throw new Error("Currently properties can't be deleted from documents, assign to undefined instead");
+}
+
+export function updateFunction(target: any, prop: any, value: any, receiver: any) {
+    let current = ObjectField.MakeCopy(value);
+    return (diff?: any) => {
+        if (true || !diff) {
+            diff = { '$set': { ["fields." + prop]: SerializationHelper.Serialize(value) } };
+            const oldValue = current;
+            const newValue = ObjectField.MakeCopy(value);
+            current = newValue;
+            UndoManager.AddEvent({
+                redo() { receiver[prop] = newValue; },
+                undo() { receiver[prop] = oldValue; }
+            });
+        }
+        target[Update](diff);
+    };
+}
\ No newline at end of file
diff --git a/src/server/Message.ts b/src/server/Message.ts
index bbe4ffcad..e9a8b0f0c 100644
--- a/src/server/Message.ts
+++ b/src/server/Message.ts
@@ -14,8 +14,8 @@ export class Message<T> {
 }
 
 export enum Types {
-    Number, List, Key, Image, Web, Document, Text, RichText, DocumentReference,
-    Html, Video, Audio, Ink, PDF, Tuple, HistogramOp, Boolean, Script,
+    Number, List, Key, Image, Web, Document, Text, Icon, RichText, DocumentReference,
+    Html, Video, Audio, Ink, PDF, Tuple, HistogramOp, Boolean, Script, Templates
 }
 
 export interface Transferable {
@@ -24,6 +24,14 @@ export interface Transferable {
     readonly data?: any;
 }
 
+export interface Reference {
+    readonly id: string;
+}
+
+export interface Diff extends Reference {
+    readonly diff: any;
+}
+
 export namespace MessageStore {
     export const Foo = new Message<string>("Foo");
     export const Bar = new Message<string>("Bar");
@@ -32,4 +40,9 @@ export namespace MessageStore {
     export const GetFields = new Message<string[]>("Get Fields"); // send string[] of 'id' get Transferable[] back
     export const GetDocument = new Message<string>("Get Document");
     export const DeleteAll = new Message<any>("Delete All");
+
+    export const GetRefField = new Message<string>("Get Ref Field");
+    export const GetRefFields = new Message<string[]>("Get Ref Fields");
+    export const UpdateField = new Message<Diff>("Update Ref Field");
+    export const CreateField = new Message<Reference>("Create Ref Field");
 }
diff --git a/src/server/ServerUtil.ts b/src/server/ServerUtil.ts
deleted file mode 100644
index 818230c1a..000000000
--- a/src/server/ServerUtil.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-import { HistogramField } from "../client/northstar/dash-fields/HistogramField";
-import { AudioField } from "../fields/AudioField";
-import { BooleanField } from "../fields/BooleanField";
-import { HtmlField } from "../fields/HtmlField";
-import { InkField } from "../fields/InkField";
-import { PDFField } from "../fields/PDFField";
-import { ScriptField } from "../fields/ScriptField";
-import { TupleField } from "../fields/TupleField";
-import { VideoField } from "../fields/VideoField";
-import { WebField } from "../fields/WebField";
-import { Utils } from "../Utils";
-import { Document } from "./../fields/Document";
-import { Field } from "./../fields/Field";
-import { ImageField } from "./../fields/ImageField";
-import { Key } from "./../fields/Key";
-import { ListField } from "./../fields/ListField";
-import { NumberField } from "./../fields/NumberField";
-import { RichTextField } from "./../fields/RichTextField";
-import { TextField } from "./../fields/TextField";
-import { Transferable, Types } from "./Message";
-
-export class ServerUtils {
-    public static prepend(extension: string): string {
-        return window.location.origin + extension;
-    }
-
-    public static FromJson(json: Transferable): Field {
-
-        if (!(json.data !== undefined && json.id && json.type !== undefined)) {
-            console.log(
-                "how did you manage to get an object that doesn't have a data or an id?"
-            );
-            return new TextField("Something to fill the space", Utils.GenerateGuid());
-        }
-
-        switch (json.type) {
-            case Types.Boolean: return new BooleanField(json.data, json.id, false);
-            case Types.Number: return new NumberField(json.data, json.id, false);
-            case Types.Text: return new TextField(json.data, json.id, false);
-            case Types.Html: return new HtmlField(json.data, json.id, false);
-            case Types.Web: return new WebField(new URL(json.data), json.id, false);
-            case Types.RichText: return new RichTextField(json.data, json.id, false);
-            case Types.Key: return new Key(json.data, json.id, false);
-            case Types.Image: return new ImageField(new URL(json.data), json.id, false);
-            case Types.HistogramOp: return HistogramField.FromJson(json.id, json.data);
-            case Types.PDF: return new PDFField(new URL(json.data), json.id, false);
-            case Types.List: return ListField.FromJson(json.id, json.data);
-            case Types.Script: return ScriptField.FromJson(json.id, json.data);
-            case Types.Audio: return new AudioField(new URL(json.data), json.id, false);
-            case Types.Video: return new VideoField(new URL(json.data), json.id, false);
-            case Types.Tuple: return new TupleField(json.data, json.id, false);
-            case Types.Ink: return InkField.FromJson(json.id, json.data);
-            case Types.Document: return Document.FromJson(json.data, json.id, false);
-            default:
-                throw Error(
-                    "Error, unrecognized field type received from server. If you just created a new field type, be sure to add it here"
-                );
-        }
-    }
-}
diff --git a/src/server/authentication/controllers/WorkspacesMenu.css b/src/server/authentication/controllers/WorkspacesMenu.css
deleted file mode 100644
index b89039965..000000000
--- a/src/server/authentication/controllers/WorkspacesMenu.css
+++ /dev/null
@@ -1,3 +0,0 @@
-.ids:hover {
-    color: darkblue;
-}
\ No newline at end of file
diff --git a/src/server/authentication/controllers/WorkspacesMenu.tsx b/src/server/authentication/controllers/WorkspacesMenu.tsx
deleted file mode 100644
index b08c1aebe..000000000
--- a/src/server/authentication/controllers/WorkspacesMenu.tsx
+++ /dev/null
@@ -1,89 +0,0 @@
-import * as React from 'react';
-import { observable, action, configure, reaction, computed, ObservableMap, runInAction } from 'mobx';
-import { observer } from "mobx-react";
-import './WorkspacesMenu.css';
-import { Document } from '../../../fields/Document';
-import { EditableView } from '../../../client/views/EditableView';
-import { KeyStore } from '../../../fields/KeyStore';
-
-export interface WorkspaceMenuProps {
-    active: Document | undefined;
-    open: (workspace: Document) => void;
-    new: () => void;
-    allWorkspaces: Document[];
-    isShown: () => boolean;
-    toggle: () => void;
-}
-
-@observer
-export class WorkspacesMenu extends React.Component<WorkspaceMenuProps> {
-    constructor(props: WorkspaceMenuProps) {
-        super(props);
-        this.addNewWorkspace = this.addNewWorkspace.bind(this);
-    }
-
-    @action
-    addNewWorkspace() {
-        this.props.new();
-        this.props.toggle();
-    }
-
-    render() {
-        return (
-            <div
-                style={{
-                    width: "auto",
-                    maxHeight: '200px',
-                    overflow: 'scroll',
-                    borderRadius: 5,
-                    position: "absolute",
-                    top: 78,
-                    left: this.props.isShown() ? 11 : -500,
-                    background: "white",
-                    border: "black solid 2px",
-                    transition: "all 1s ease",
-                    zIndex: 15,
-                    padding: 10,
-                    paddingRight: 12,
-                }}>
-                <img
-                    src="https://bit.ly/2IBBkxk"
-                    style={{
-                        width: 20,
-                        height: 20,
-                        marginTop: 3,
-                        marginLeft: 3,
-                        marginBottom: 3,
-                        cursor: "grab"
-                    }}
-                    onClick={this.addNewWorkspace}
-                />
-                {this.props.allWorkspaces.map((s, i) =>
-                    <div
-                        key={s.Id}
-                        onContextMenu={(e) => {
-                            e.preventDefault();
-                            this.props.open(s);
-                        }}
-                        style={{
-                            marginTop: 10,
-                            color: s === this.props.active ? "red" : "black"
-                        }}
-                    >
-                        <span>{i + 1} - </span>
-                        <EditableView
-                            display={"inline"}
-                            GetValue={() => s.Title}
-                            SetValue={(title: string): boolean => {
-                                s.SetText(KeyStore.Title, title);
-                                return true;
-                            }}
-                            contents={s.Title}
-                            height={20}
-                        />
-                    </div>
-                )}
-            </div>
-        );
-    }
-}
\ No newline at end of file
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 5d4479c88..5f45d7bcc 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -1,19 +1,20 @@
 import { computed, observable, action, runInAction } from "mobx";
 import * as rp from 'request-promise';
-import { Documents } from "../../../client/documents/Documents";
+import { Docs } from "../../../client/documents/Documents";
 import { Attribute, AttributeGroup, Catalog, Schema } from "../../../client/northstar/model/idea/idea";
 import { ArrayUtil } from "../../../client/northstar/utils/ArrayUtil";
-import { Server } from "../../../client/Server";
-import { Document } from "../../../fields/Document";
-import { KeyStore } from "../../../fields/KeyStore";
-import { ListField } from "../../../fields/ListField";
 import { RouteStore } from "../../RouteStore";
-import { ServerUtils } from "../../ServerUtil";
+import { DocServer } from "../../../client/DocServer";
+import { Doc } 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";
 
 export class CurrentUserUtils {
     private static curr_email: string;
     private static curr_id: string;
-    @observable private static user_document: Document;
+    @observable private static user_document: Doc;
     //TODO tfs: these should be temporary...
     private static mainDocId: string | undefined;
 
@@ -23,15 +24,21 @@ export class CurrentUserUtils {
     public static get MainDocId() { return this.mainDocId; }
     public static set MainDocId(id: string | undefined) { this.mainDocId = id; }
 
-    private static createUserDocument(id: string): Document {
-        let doc = new Document(id);
-        doc.Set(KeyStore.Workspaces, new ListField<Document>());
-        doc.Set(KeyStore.OptionalRightCollection, Documents.SchemaDocument([], { title: "Pending documents" }));
+    private static createUserDocument(id: string): Doc {
+        let doc = new Doc(id, true);
+        doc.viewType = CollectionViewType.Tree;
+        doc.layout = CollectionView.LayoutString();
+        doc.title = this.email;
+        doc.data = new List<Doc>();
+        doc.excludeFromLibrary = true;
+        doc.optionalRightCollection = Docs.SchemaDocument([], { title: "Pending documents" });
+        // doc.library = Docs.TreeDocument([doc], { title: `Library: ${CurrentUserUtils.email}` });
+        // (doc.library as Doc).excludeFromLibrary = true;
         return doc;
     }
 
     public static loadCurrentUser(): Promise<any> {
-        let userPromise = rp.get(ServerUtils.prepend(RouteStore.getCurrUser)).then(response => {
+        let userPromise = rp.get(DocServer.prepend(RouteStore.getCurrUser)).then(response => {
             if (response) {
                 let obj = JSON.parse(response);
                 CurrentUserUtils.curr_id = obj.id as string;
@@ -40,10 +47,10 @@ export class CurrentUserUtils {
                 throw new Error("There should be a user! Why does Dash think there isn't one?");
             }
         });
-        let userDocPromise = rp.get(ServerUtils.prepend(RouteStore.getUserDocumentId)).then(id => {
+        let userDocPromise = rp.get(DocServer.prepend(RouteStore.getUserDocumentId)).then(id => {
             if (id) {
-                return Server.GetField(id).then(field =>
-                    runInAction(() => this.user_document = field instanceof Document ? field : this.createUserDocument(id)));
+                return DocServer.GetRefField(id).then(field =>
+                    runInAction(() => this.user_document = field instanceof Doc ? field : this.createUserDocument(id)));
             } else {
                 throw new Error("There should be a user id! Why does Dash think there isn't one?");
             }
diff --git a/src/server/database.ts b/src/server/database.ts
index 5457e4dd5..69005d2d3 100644
--- a/src/server/database.ts
+++ b/src/server/database.ts
@@ -13,22 +13,15 @@ export class Database {
         this.MongoClient.connect(this.url, (err, client) => this.db = client.db());
     }
 
-    public update(id: string, value: any, callback: () => void) {
+    public update(id: string, value: any, callback: () => void, upsert = true, collectionName = Database.DocumentsCollection) {
         if (this.db) {
-            let collection = this.db.collection('documents');
+            let collection = this.db.collection(collectionName);
             const prom = this.currentWrites[id];
             let newProm: Promise<void>;
             const run = (): Promise<void> => {
                 return new Promise<void>(resolve => {
-                    collection.updateOne({ _id: id }, { $set: value }, { upsert: true }
+                    collection.updateOne({ _id: id }, value, { upsert }
                         , (err, res) => {
-                            if (err) {
-                                console.log(err.message);
-                                console.log(err.errmsg);
-                            }
-                            // if (res) {
-                            //     console.log(JSON.stringify(res.result));
-                            // }
                             if (this.currentWrites[id] === newProm) {
                                 delete this.currentWrites[id];
                             }
@@ -51,24 +44,53 @@ export class Database {
             this.db && this.db.collection(collectionName).deleteMany({}, res));
     }
 
-    public insert(kvpairs: any, collectionName = Database.DocumentsCollection) {
-        this.db && this.db.collection(collectionName).insertOne(kvpairs, (err, res) =>
-            err // &&  console.log(err)
-        );
+    public insert(value: any, collectionName = Database.DocumentsCollection) {
+        if (!this.db) { return; }
+        if ("id" in value) {
+            value._id = value.id;
+            delete value.id;
+        }
+        const id = value._id;
+        const collection = this.db.collection(collectionName);
+        const prom = this.currentWrites[id];
+        let newProm: Promise<void>;
+        const run = (): Promise<void> => {
+            return new Promise<void>(resolve => {
+                collection.insertOne(value, (err, res) => {
+                    if (this.currentWrites[id] === newProm) {
+                        delete this.currentWrites[id];
+                    }
+                    resolve();
+                });
+            });
+        };
+        newProm = prom ? prom.then(run) : run();
+        this.currentWrites[id] = newProm;
     }
 
     public getDocument(id: string, fn: (result?: Transferable) => void, collectionName = Database.DocumentsCollection) {
-        this.db && this.db.collection(collectionName).findOne({ id: id }, (err, result) =>
-            fn(result ? ({ id: result._id, type: result.type, data: result.data }) : undefined));
+        this.db && this.db.collection(collectionName).findOne({ _id: id }, (err, result) => {
+            if (result) {
+                result.id = result._id;
+                delete result._id;
+                fn(result);
+            } else {
+                fn(undefined);
+            }
+        });
     }
 
     public getDocuments(ids: string[], fn: (result: Transferable[]) => void, collectionName = Database.DocumentsCollection) {
-        this.db && this.db.collection(collectionName).find({ id: { "$in": ids } }).toArray((err, docs) => {
+        this.db && this.db.collection(collectionName).find({ _id: { "$in": ids } }).toArray((err, docs) => {
             if (err) {
                 console.log(err.message);
                 console.log(err.errmsg);
             }
-            fn(docs.map(doc => ({ id: doc._id, type: doc.type, data: doc.data })));
+            fn(docs.map(doc => {
+                doc.id = doc._id;
+                delete doc._id;
+                return doc;
+            }));
         });
     }
 
diff --git a/src/server/index.ts b/src/server/index.ts
index 70a7d266c..6801b3132 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -16,13 +16,12 @@ import { Socket } from 'socket.io';
 import * as webpack from 'webpack';
 import * as wdm from 'webpack-dev-middleware';
 import * as whm from 'webpack-hot-middleware';
-import { Field, FieldId } from '../fields/Field';
 import { Utils } from '../Utils';
 import { getForgot, getLogin, getLogout, getReset, getSignup, postForgot, postLogin, postReset, postSignup } from './authentication/controllers/user_controller';
 import { DashUserModel } from './authentication/models/user_model';
 import { Client } from './Client';
 import { Database } from './database';
-import { MessageStore, Transferable } from "./Message";
+import { MessageStore, Transferable, Diff } from "./Message";
 import { RouteStore } from './RouteStore';
 const app = express();
 const config = require('../../webpack.config');
@@ -232,14 +231,21 @@ server.on("connection", function (socket: Socket) {
     Utils.AddServerHandlerCallback(socket, MessageStore.GetField, getField);
     Utils.AddServerHandlerCallback(socket, MessageStore.GetFields, getFields);
     Utils.AddServerHandler(socket, MessageStore.DeleteAll, deleteFields);
+
+    Utils.AddServerHandler(socket, MessageStore.CreateField, CreateField);
+    Utils.AddServerHandler(socket, MessageStore.UpdateField, diff => UpdateField(socket, diff));
+    Utils.AddServerHandlerCallback(socket, MessageStore.GetRefField, GetRefField);
+    Utils.AddServerHandlerCallback(socket, MessageStore.GetRefFields, GetRefFields);
 });
 
-function deleteFields() {
-    return Database.Instance.deleteAll();
+async function deleteFields() {
+    await Database.Instance.deleteAll();
+    await Database.Instance.deleteAll('newDocuments');
 }
 
 async function deleteAll() {
     await Database.Instance.deleteAll();
+    await Database.Instance.deleteAll('newDocuments');
     await Database.Instance.deleteAll('sessions');
     await Database.Instance.deleteAll('users');
 }
@@ -262,5 +268,22 @@ function setField(socket: Socket, newValue: Transferable) {
         socket.broadcast.emit(MessageStore.SetField.Message, newValue));
 }
 
+function GetRefField([id, callback]: [string, (result?: Transferable) => void]) {
+    Database.Instance.getDocument(id, callback, "newDocuments");
+}
+
+function GetRefFields([ids, callback]: [string[], (result?: Transferable[]) => void]) {
+    Database.Instance.getDocuments(ids, callback, "newDocuments");
+}
+
+function UpdateField(socket: Socket, diff: Diff) {
+    Database.Instance.update(diff.id, diff.diff,
+        () => socket.broadcast.emit(MessageStore.UpdateField.Message, diff), false, "newDocuments");
+}
+
+function CreateField(newValue: any) {
+    Database.Instance.insert(newValue, "newDocuments");
+}
+
 server.listen(serverPort);
 console.log(`listening on port ${serverPort}`);
\ No newline at end of file
diff --git a/test/test.ts b/test/test.ts
index 16cace026..91dc43379 100644
--- a/test/test.ts
+++ b/test/test.ts
@@ -1,171 +1,35 @@
-import { NumberField } from "../src/fields/NumberField";
 import { expect } from 'chai';
 import 'mocha';
-import { Key } from "../src/fields/Key";
-import { Document } from "../src/fields/Document";
 import { autorun, reaction } from "mobx";
-import { DocumentReference } from "../src/fields/DocumentReference";
-import { TextField } from "../src/fields/TextField";
-import { Field, FieldWaiting } from "../src/fields/Field";
-
-describe('Number Controller', () => {
-    it('Should be constructable', () => {
-        const numController = new NumberField(15);
-        expect(numController.Data).to.equal(15);
-    });
-
-    it('Should update', () => {
-        const numController = new NumberField(15);
-        let ran = false;
-        reaction(() => numController.Data, (data) => { ran = true; });
-        expect(ran).to.equal(false);
-        numController.Data = 5;
-        expect(ran).to.equal(true);
-    });
-});
+import { Doc } from '../src/new_fields/Doc';
+import { Cast } from '../src/new_fields/Types';
 
 describe("Document", () => {
     it('should hold fields', () => {
-        let key = new Key("Test");
-        let key2 = new Key("Test2");
-        let field = new NumberField(15);
-        let doc = new Document();
-        doc.Set(key, field);
-        let getField = doc.GetT(key, NumberField);
-        let getField2 = doc.GetT(key2, NumberField);
+        let key = "Test";
+        let key2 = "Test2";
+        let field = 15;
+        let doc = new Doc();
+        doc[key] = field;
+        let getField = Cast(doc[key], "number");
+        let getField2 = Cast(doc[key2], "number");
         expect(getField).to.equal(field);
         expect(getField2).to.equal(undefined);
     });
 
     it('should update', () => {
-        let doc = new Document();
-        let key = new Key("Test");
-        let key2 = new Key("Test2");
+        let doc = new Doc();
+        let key = "Test";
+        let key2 = "Test2";
         let ran = false;
-        reaction(() => doc.Get(key), (field) => { ran = true; });
+        reaction(() => doc[key], (field) => { ran = true; });
         expect(ran).to.equal(false);
 
-        doc.Set(key2, new NumberField(4));
+        doc[key2] = 4;
         expect(ran).to.equal(false);
 
-        doc.Set(key, new NumberField(5));
+        doc[key] = 5;
 
         expect(ran).to.equal(true);
     });
 });
-
-describe("Reference", () => {
-    it('should dereference', () => {
-        let doc = new Document();
-        let doc2 = new Document();
-        const key = new Key("test");
-        const key2 = new Key("test2");
-
-        const numCont = new NumberField(55);
-        doc.Set(key, numCont);
-        let ref = new DocumentReference(doc, key);
-        let ref2 = new DocumentReference(doc, key2);
-        doc2.Set(key2, ref);
-
-        let ref3 = new DocumentReference(doc2, key2);
-        let ref4 = new DocumentReference(doc2, key);
-
-        expect(ref.Dereference()).to.equal(numCont);
-        expect(ref.DereferenceToRoot()).to.equal(numCont);
-        expect(ref2.Dereference()).to.equal(undefined);
-        expect(ref2.DereferenceToRoot()).to.equal(undefined);
-        expect(ref3.Dereference()).to.equal(ref);
-        expect(ref3.DereferenceToRoot()).to.equal(numCont);
-        expect(ref4.Dereference()).to.equal(undefined);
-        expect(ref4.DereferenceToRoot()).to.equal(undefined);
-    });
-
-    it('should work with prototypes', () => {
-        let doc = new Document;
-        let doc2 = doc.MakeDelegate();
-        let key = new Key("test");
-        expect(doc.Get(key)).to.equal(undefined);
-        expect(doc2.Get(key)).to.equal(undefined);
-        let num = new NumberField(55);
-        let num2 = new NumberField(56);
-
-        doc.Set(key, num);
-        expect(doc.Get(key)).to.equal(num);
-        expect(doc2.Get(key)).to.equal(num);
-
-        doc2.Set(key, num2);
-        expect(doc.Get(key)).to.equal(num);
-        expect(doc2.Get(key)).to.equal(num2);
-    });
-
-    it('should update through layers', () => {
-        let doc = new Document();
-        let doc2 = new Document();
-        let doc3 = new Document();
-        const key = new Key("test");
-        const key2 = new Key("test2");
-        const key3 = new Key("test3");
-
-        const numCont = new NumberField(55);
-        doc.Set(key, numCont);
-        const ref = new DocumentReference(doc, key);
-        doc2.Set(key2, ref);
-        const ref3 = new DocumentReference(doc2, key2);
-        doc3.Set(key3, ref3);
-
-        let ran = false;
-        reaction(() => {
-            let field = (<Field>(<Field>doc3.Get(key3)).DereferenceToRoot()).GetValue();
-            return field;
-        }, (field) => {
-            ran = true;
-        });
-        expect(ran).to.equal(false);
-
-        numCont.Data = 44;
-        expect(ran).to.equal(true);
-        ran = false;
-
-        doc.Set(key, new NumberField(33));
-        expect(ran).to.equal(true);
-        ran = false;
-
-        doc.Set(key2, new NumberField(4));
-        expect(ran).to.equal(false);
-
-        doc2.Set(key2, new TextField("hello"));
-        expect(ran).to.equal(true);
-        ran = false;
-
-        doc3.Set(key3, new TextField("world"));
-        expect(ran).to.equal(true);
-        ran = false;
-    });
-
-    it('should update with prototypes', () => {
-        let doc = new Document();
-        let doc2 = doc.MakeDelegate();
-        const key = new Key("test");
-
-        const numCont = new NumberField(55);
-
-        let ran = false;
-        reaction(() => {
-            let field = doc2.GetT(key, NumberField);
-            if (field && field !== FieldWaiting) {
-                return field.Data;
-            }
-            return undefined;
-        }, (field) => {
-            ran = true;
-        });
-        expect(ran).to.equal(false);
-
-        doc.Set(key, numCont);
-        expect(ran).to.equal(true);
-
-        ran = false;
-        numCont.Data = 1;
-        expect(ran).to.equal(true);
-    });
-});
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From 11c6ecdebdfb84246169f1e573c0e47e5336eff6 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <abdullah_ahmed@brown.edu>
Date: Thu, 9 May 2019 21:30:58 -0400
Subject: restored external linking

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

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

diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 18ec5732b..358fdc81b 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -129,6 +129,8 @@ export class TooltipTextMenu {
         this.link.style.color = "white";
         this.tooltip.appendChild(this.link);
 
+        this.tooltip.appendChild(this.createLink().render(this.view).dom);
+
         this.update(view, undefined);
     }
 
-- 
cgit v1.2.3-70-g09d2


From 0d10fd88041334a20cf37a57808a8b9c58f35f25 Mon Sep 17 00:00:00 2001
From: ab <abdullah_ahmed@brown.edu>
Date: Tue, 4 Jun 2019 15:06:56 -0400
Subject: exploring prosemirror

---
 src/client/util/ProsemirrorKeymap.ts |  2 +-
 src/client/util/TooltipTextMenu.tsx  | 16 ++++++++--------
 2 files changed, 9 insertions(+), 9 deletions(-)

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

diff --git a/src/client/util/ProsemirrorKeymap.ts b/src/client/util/ProsemirrorKeymap.ts
index 00d086b97..71644cd88 100644
--- a/src/client/util/ProsemirrorKeymap.ts
+++ b/src/client/util/ProsemirrorKeymap.ts
@@ -50,7 +50,7 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?:
     }
 
     if (type = schema.nodes.bullet_list) {
-        bind("Ctrl-b", wrapInList(type));
+        bind("Ctrl-.", wrapInList(type));
     }
     if (type = schema.nodes.ordered_list) {
         bind("Ctrl-n", wrapInList(type));
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index f517f757a..625f5ee54 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -68,12 +68,12 @@ export class TooltipTextMenu {
         library.add(faListUl);
         //add the buttons to the tooltip
         let items = [
-            { command: toggleMark(schema.marks.strong), dom: this.icon("B", "strong") },
-            { command: toggleMark(schema.marks.em), dom: this.icon("i", "em") },
-            { command: toggleMark(schema.marks.underline), dom: this.icon("U", "underline") },
-            { command: toggleMark(schema.marks.strikethrough), dom: this.icon("S", "strikethrough") },
-            { command: toggleMark(schema.marks.superscript), dom: this.icon("s", "superscript") },
-            { command: toggleMark(schema.marks.subscript), dom: this.icon("s", "subscript") },
+            { command: toggleMark(schema.marks.strong), dom: this.icon("B", "strong", "Bold") },
+            { command: toggleMark(schema.marks.em), dom: this.icon("i", "em", "Italic") },
+            { command: toggleMark(schema.marks.underline), dom: this.icon("U", "underline", "Underline") },
+            { command: toggleMark(schema.marks.strikethrough), dom: this.icon("S", "strikethrough", "Strikethrough") },
+            { command: toggleMark(schema.marks.superscript), dom: this.icon("s", "superscript", "Superscript") },
+            { command: toggleMark(schema.marks.subscript), dom: this.icon("s", "subscript", "Subscript") },
             // { command: wrapInList(schema.nodes.bullet_list), dom: this.icon(":", "bullets") },
             // { command: wrapInList(schema.nodes.ordered_list), dom: this.icon("1)", "bullets") },
             // { command: lift, dom: this.icon("<", "lift") },
@@ -349,10 +349,10 @@ export class TooltipTextMenu {
     }
 
     // Helper function to create menu icons
-    icon(text: string, name: string) {
+    icon(text: string, name: string, title: string = name) {
         let span = document.createElement("span");
         span.className = name + " menuicon";
-        span.title = name;
+        span.title = title;
         span.textContent = text;
         span.style.color = "white";
         return span;
-- 
cgit v1.2.3-70-g09d2


From 3fa27ef718ff3ed855e0ae2b4fc35d51fe60fb25 Mon Sep 17 00:00:00 2001
From: ab <abdullah_ahmed@brown.edu>
Date: Wed, 5 Jun 2019 15:25:30 -0400
Subject: menu doesn't flicker

---
 src/client/util/TooltipTextMenu.scss        | 23 +++++++++---------
 src/client/util/TooltipTextMenu.tsx         | 33 ++++++++++++++------------
 src/client/views/nodes/FormattedTextBox.tsx | 36 +++++++++++------------------
 3 files changed, 44 insertions(+), 48 deletions(-)

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

diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss
index 437da0d63..e2862f093 100644
--- a/src/client/util/TooltipTextMenu.scss
+++ b/src/client/util/TooltipTextMenu.scss
@@ -36,7 +36,7 @@
     position: relative;
     padding-right: 15px;
     margin: 3px;
-    background: #333333;
+    background: white;
     border-radius: 3px;
     text-align: center;
   }
@@ -69,6 +69,7 @@
   .ProseMirror-menu-dropdown-menu {
     z-index: 15;
     min-width: 6em;
+    background: white;
   }
   
   .linking {
@@ -82,7 +83,7 @@
   }
   
   .ProseMirror-menu-dropdown-item:hover {
-    background: #2e2b2b;
+    background: white;
   }
   
   .ProseMirror-menu-submenu-wrap {
@@ -155,7 +156,7 @@
   }
   
   .ProseMirror-icon svg {
-    fill: currentColor;
+    fill:white;
     height: 1em;
   }
   
@@ -184,7 +185,7 @@
     position: fixed;
     border-radius: 3px;
     z-index: 11;
-    box-shadow: -.5px 2px 5px rgba(0, 0, 0, .2);
+    box-shadow: -.5px 2px 5px white(255, 255, 255, 0.2);
   }
   
   .ProseMirror-prompt h5 {
@@ -196,7 +197,7 @@
   
   .ProseMirror-prompt input[type="text"],
   .ProseMirror-prompt textarea {
-    background: #eee;
+    background: white;
     border: none;
     outline: none;
   }
@@ -233,12 +234,12 @@
 
 .tooltipMenu {
     position: absolute;
-    z-index: 200;
-    background: $dark-color;
+    z-index: 50;
+    background: whitesmoke;
     border: 1px solid silver;
-    border-radius: 4px;
+    border-radius: 15px;
     padding: 2px 10px;
-    margin-bottom: 7px;
+    margin-bottom: 15px;
     -webkit-transform: translateX(-50%);
     transform: translateX(-50%);
     pointer-events: all;
@@ -282,9 +283,9 @@
 
   .menuicon {
     display: inline-block;
-    border-right: 1px solid rgba(0, 0, 0, 0.2);
+    border-right: 1px solid white(0, 0, 0, 0.2);
     //color: rgb(19, 18, 18);
-    color: white;
+    color: rgb(226, 21, 21);
     line-height: 1;
     padding: 0px 2px;
     margin: 1px;
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 625f5ee54..7c5437510 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -86,7 +86,10 @@ export class TooltipTextMenu {
             dom.addEventListener("pointerdown", e => {
                 e.preventDefault();
                 view.focus();
-                command(view.state, view.dispatch, view);
+                if (dom.contains(e.target as Node)) {
+                    e.stopPropagation();
+                    command(view.state, view.dispatch, view);
+                }
             });
 
         });
@@ -131,13 +134,13 @@ export class TooltipTextMenu {
         //font SIZES
         let fontSizeBtns: MenuItem[] = [];
         this.fontSizeToNum.forEach((number, mark) => {
-            fontSizeBtns.push(this.dropdownMarkBtn(String(number), "width: 50px;", mark, this.view, this.changeToMarkInGroup, this.fontSizes));
+            fontSizeBtns.push(this.dropdownMarkBtn(String(number), "color: black; width: 50px;", mark, this.view, this.changeToMarkInGroup, this.fontSizes));
         });
 
         if (this.fontSizeDom) { this.tooltip.removeChild(this.fontSizeDom); }
         this.fontSizeDom = (new Dropdown(cut(fontSizeBtns), {
             label: label,
-            css: "color:white; min-width: 60px; padding-left: 5px; margin-right: 0;"
+            css: "color:black; min-width: 60px; padding-left: 5px; margin-right: 0;"
         }) as MenuItem).render(this.view).dom;
         this.tooltip.appendChild(this.fontSizeDom);
     }
@@ -150,13 +153,13 @@ export class TooltipTextMenu {
         //font STYLES
         let fontBtns: MenuItem[] = [];
         this.fontStylesToName.forEach((name, mark) => {
-            fontBtns.push(this.dropdownMarkBtn(name, "font-family: " + name + ", sans-serif; width: 125px;", mark, this.view, this.changeToMarkInGroup, this.fontStyles));
+            fontBtns.push(this.dropdownMarkBtn(name, "color: black; font-family: " + name + ", sans-serif; width: 125px;", mark, this.view, this.changeToMarkInGroup, this.fontStyles));
         });
 
         if (this.fontStyleDom) { this.tooltip.removeChild(this.fontStyleDom); }
         this.fontStyleDom = (new Dropdown(cut(fontBtns), {
             label: label,
-            css: "color:white; width: 125px; margin-left: -3px; padding-left: 2px;"
+            css: "color:black; width: 125px; margin-left: -3px; padding-left: 2px;"
         }) as MenuItem).render(this.view).dom;
 
         this.tooltip.appendChild(this.fontStyleDom);
@@ -165,7 +168,7 @@ export class TooltipTextMenu {
     updateLinkMenu() {
         if (!this.linkEditor || !this.linkText) {
             this.linkEditor = document.createElement("div");
-            this.linkEditor.style.color = "white";
+            this.linkEditor.style.color = "black";
             this.linkText = document.createElement("div");
             this.linkText.style.cssFloat = "left";
             this.linkText.style.marginRight = "5px";
@@ -174,13 +177,13 @@ export class TooltipTextMenu {
             this.linkText.style.whiteSpace = "nowrap";
             this.linkText.style.width = "150px";
             this.linkText.style.overflow = "hidden";
-            this.linkText.style.color = "white";
+            this.linkText.style.color = "black";
             this.linkText.onpointerdown = (e: PointerEvent) => { e.stopPropagation(); };
             let linkBtn = document.createElement("div");
             linkBtn.textContent = ">>";
-            linkBtn.style.width = "20px";
-            linkBtn.style.height = "20px";
-            linkBtn.style.color = "white";
+            linkBtn.style.width = "10px";
+            linkBtn.style.height = "10px";
+            linkBtn.style.color = "black";
             linkBtn.style.cssFloat = "left";
             linkBtn.onpointerdown = (e: PointerEvent) => {
                 let node = this.view.state.selection.$from.nodeAfter;
@@ -207,7 +210,7 @@ export class TooltipTextMenu {
             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.color = "black";
             this.linkDrag.style.background = "black";
             this.linkDrag.style.cssFloat = "left";
             this.linkDrag.onpointerdown = (e: PointerEvent) => {
@@ -261,14 +264,14 @@ export class TooltipTextMenu {
         //Make a dropdown of all list types
         let toAdd: MenuItem[] = [];
         this.listTypeToIcon.forEach((icon, type) => {
-            toAdd.push(this.dropdownNodeBtn(icon, "width: 40px;", type, this.view, this.listTypes, this.changeToNodeType));
+            toAdd.push(this.dropdownNodeBtn(icon, "color: black; width: 40px;", type, this.view, this.listTypes, this.changeToNodeType));
         });
         //option to remove the list formatting
-        toAdd.push(this.dropdownNodeBtn("X", "width: 40px;", undefined, this.view, this.listTypes, this.changeToNodeType));
+        toAdd.push(this.dropdownNodeBtn("X", "color: black; width: 40px;", undefined, this.view, this.listTypes, this.changeToNodeType));
 
         listTypeBtn = (new Dropdown(toAdd, {
             label: label,
-            css: "color:white; width: 40px;"
+            css: "color:black; width: 40px;"
         }) as MenuItem).render(this.view).dom;
 
         //add this new button and return it
@@ -354,7 +357,7 @@ export class TooltipTextMenu {
         span.className = name + " menuicon";
         span.title = title;
         span.textContent = text;
-        span.style.color = "white";
+        span.style.color = "black";
         return span;
     }
 
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index e6617bf65..5c635cc0c 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -7,12 +7,11 @@ import { history } from "prosemirror-history";
 import { keymap } from "prosemirror-keymap";
 import { EditorState, Plugin, Transaction } from "prosemirror-state";
 import { EditorView } from "prosemirror-view";
-import { Doc, Opt } from "../../../new_fields/Doc";
+import { Doc, Field, Opt, WidthSym, HeightSym } 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 { DocUtils } from '../../documents/Documents';
 import { DocumentManager } from "../../util/DocumentManager";
 import { DragManager } from "../../util/DragManager";
 import buildKeymap from "../../util/ProsemirrorKeymap";
@@ -22,11 +21,14 @@ 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 { 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 { DocUtils } from '../../documents/Documents';
 
 library.add(faEdit);
 library.add(faSmile);
@@ -149,10 +151,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 ]
         };
 
-        const config2 = {
-
-        };
-
         if (this.props.isOverlay) {
             this._inputReactionDisposer = reaction(() => FormattedTextBox.InputBoxOverlay,
                 () => {
@@ -225,7 +223,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         }
     }
 
-    _linkClicked = "";
     onPointerDown = (e: React.PointerEvent): void => {
         if (e.button === 0 && this.props.isSelected() && !e.altKey && !e.ctrlKey && !e.metaKey) {
             e.stopPropagation();
@@ -233,17 +230,23 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 this._toolTipTextMenu.tooltip.style.opacity = "0";
             }
         }
-        this._linkClicked = "";
+        let ctrlKey = e.ctrlKey;
         if (e.button === 0 && ((!this.props.isSelected() && !e.ctrlKey) || (this.props.isSelected() && e.ctrlKey)) && !e.metaKey && e.target) {
             let href = (e.target as any).href;
             for (let parent = (e.target as any).parentNode; !href && parent; parent = parent.parentNode) {
                 href = parent.childNodes[0].href;
             }
-            if (href && href.indexOf(DocServer.prepend("/doc/")) === 0) {
-                this._linkClicked = href.replace(DocServer.prepend("/doc/"), "").split("?")[0];
+            if (href) {
+                if (href.indexOf(DocServer.prepend("/doc/")) === 0) {
+                    let docid = href.replace(DocServer.prepend("/doc/"), "").split("?")[0];
+                    DocServer.GetRefField(docid).then(f => {
+                        (f instanceof Doc) && DocumentManager.Instance.jumpToDocument(f, ctrlKey, document => this.props.addDocTab(document, "inTab"))
+                    });
+                }
                 e.stopPropagation();
                 e.preventDefault();
             }
+
         }
         if (e.button === 2 || (e.button === 0 && e.ctrlKey)) {
             this._gotDown = true;
@@ -254,14 +257,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         if (this._toolTipTextMenu && this._toolTipTextMenu.tooltip) {
             this._toolTipTextMenu.tooltip.style.opacity = "1";
         }
-        let ctrlKey = e.ctrlKey;
-        if (this._linkClicked) {
-            DocServer.GetRefField(this._linkClicked).then(f => {
-                (f instanceof Doc) && DocumentManager.Instance.jumpToDocument(f, ctrlKey, document => this.props.addDocTab(document, "inTab"));
-            });
-            e.stopPropagation();
-            e.preventDefault();
-        }
         if (e.buttons === 1 && this.props.isSelected() && !e.altKey) {
             e.stopPropagation();
         }
@@ -285,10 +280,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
 
     onClick = (e: React.MouseEvent): void => {
         this._proseRef.current!.focus();
-        if (this._linkClicked) {
-            e.preventDefault();
-            e.stopPropagation();
-        }
     }
     onMouseDown = (e: React.MouseEvent): void => {
         if (!this.props.isSelected()) { // preventing default allows the onClick to be generated instead of being swallowed by the text box itself
@@ -372,6 +363,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 onPointerUp={this.onPointerUp}
                 onPointerDown={this.onPointerDown}
                 onMouseDown={this.onMouseDown}
+                onContextMenu={this.specificContextMenu}
                 // tfs: do we need this event handler
                 onWheel={this.onPointerWheel}
                 onPointerEnter={this.onPointerEnter}
-- 
cgit v1.2.3-70-g09d2


From 08134c962bab2cd62507415f1a67cd921069d8e3 Mon Sep 17 00:00:00 2001
From: ab <abdullah_ahmed@brown.edu>
Date: Fri, 7 Jun 2019 15:00:36 -0400
Subject: fixed marquee

---
 src/client/util/ProsemirrorKeymap.ts               | 19 ++++++++++++-
 src/client/util/RichTextSchema.tsx                 | 17 ++++++++++++
 src/client/util/TooltipTextMenu.scss               |  6 +++--
 src/client/util/TooltipTextMenu.tsx                | 31 +++++++++++++++++++++-
 src/client/views/MainOverlayTextBox.tsx            |  2 +-
 .../collections/collectionFreeForm/MarqueeView.tsx |  9 ++++---
 6 files changed, 76 insertions(+), 8 deletions(-)

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

diff --git a/src/client/util/ProsemirrorKeymap.ts b/src/client/util/ProsemirrorKeymap.ts
index 71644cd88..89d01486e 100644
--- a/src/client/util/ProsemirrorKeymap.ts
+++ b/src/client/util/ProsemirrorKeymap.ts
@@ -1,4 +1,4 @@
-import { Schema } from "prosemirror-model";
+import { Schema, NodeType } from "prosemirror-model";
 import {
     wrapIn, setBlockType, chainCommands, toggleMark, exitCode,
     joinUp, joinDown, lift, selectParentNode
@@ -24,6 +24,22 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?:
         keys[key] = cmd;
     }
 
+    function insertStar(state: EditorState<S>, dispatch: ((tr: Transaction<S>) => void)) {
+        console.log("creating star...");
+        let type = schema.nodes.star as NodeType<S>;
+        let { $from } = state.selection;
+        if (!$from.parent.canReplaceWith($from.index(), $from.index(), type)) {
+            return false;
+        }
+        if (dispatch) {
+            dispatch(state.tr.replaceSelectionWith(type.create()));
+        }
+        return true;
+    }
+
+    console.log("star? hullo");
+    bind("Mod-space", insertStar);
+
     bind("Mod-z", undo);
     bind("Shift-Mod-z", redo);
     bind("Backspace", undoInputRule);
@@ -79,6 +95,7 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?:
     }
     if (type = schema.nodes.paragraph) {
         bind("Shift-Ctrl-0", setBlockType(type));
+        bind("Mod-space", insertStar);
     }
     if (type = schema.nodes.code_block) {
         bind("Shift-Ctrl-\\", setBlockType(type));
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 3e3e98206..8fae821a5 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -26,6 +26,13 @@ export const nodes: { [index: string]: NodeSpec } = {
         toDOM() { return pDOM; }
     },
 
+    star: {
+        inline: true,
+        group: "inline",
+        toDOM() { return ["star", "🟊"]; },
+        parseDOM: [{ tag: "star" }]
+    },
+
     // :: NodeSpec A blockquote (`<blockquote>`) wrapping one or more blocks.
     blockquote: {
         content: "block+",
@@ -222,6 +229,15 @@ export const marks: { [index: string]: MarkSpec } = {
         toDOM: () => ['sup']
     },
 
+    collapse: {
+        parseDOM: [{ style: 'color: blue' }],
+        toDOM() {
+            return ['span', {
+                style: 'color: blue'
+            }]
+        }
+    },
+
 
     // :: MarkSpec Code font mark. Represented as a `<code>` element.
     code: {
@@ -280,6 +296,7 @@ export const marks: { [index: string]: MarkSpec } = {
         }]
     },
 
+
     /** FONT SIZES */
 
     p10: {
diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss
index e2862f093..0720a73a3 100644
--- a/src/client/util/TooltipTextMenu.scss
+++ b/src/client/util/TooltipTextMenu.scss
@@ -133,7 +133,7 @@
     position: relative;
     min-height: 1em;
     color: white;
-    padding: 1px 6px;
+    padding: 10px 10px;
     top: 0; left: 0; right: 0;
     border-bottom: 1px solid silver;
     background:$dark-color;
@@ -239,10 +239,12 @@
     border: 1px solid silver;
     border-radius: 15px;
     padding: 2px 10px;
-    margin-bottom: 15px;
+    margin-bottom: 60px;
     -webkit-transform: translateX(-50%);
     transform: translateX(-50%);
     pointer-events: all;
+    height: 100px;
+    width:500px;
     .ProseMirror-example-setup-style hr {
       padding: 2px 10px;
       border: none;
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 7c5437510..1f1ad9cd4 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 { 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";
+import { Utils } from "../../Utils";
 
 const SVG = "http://www.w3.org/2000/svg";
 
@@ -74,6 +74,7 @@ export class TooltipTextMenu {
             { command: toggleMark(schema.marks.strikethrough), dom: this.icon("S", "strikethrough", "Strikethrough") },
             { command: toggleMark(schema.marks.superscript), dom: this.icon("s", "superscript", "Superscript") },
             { command: toggleMark(schema.marks.subscript), dom: this.icon("s", "subscript", "Subscript") },
+            { command: toggleMark(schema.marks.collapse), dom: this.icon("C", 'collapse', 'Collapse') }
             // { command: wrapInList(schema.nodes.bullet_list), dom: this.icon(":", "bullets") },
             // { command: wrapInList(schema.nodes.ordered_list), dom: this.icon("1)", "bullets") },
             // { command: lift, dom: this.icon("<", "lift") },
@@ -231,6 +232,15 @@ export class TooltipTextMenu {
             this.linkEditor.appendChild(this.linkText);
             this.linkEditor.appendChild(linkBtn);
             this.tooltip.appendChild(this.linkEditor);
+
+            let starButton = document.createElement("button");
+            starButton.textContent = "ST";
+            starButton.onclick = () => {
+                let state = this.view.state;
+                this.insertStar(state, this.view.dispatch);
+            }
+
+            this.tooltip.appendChild(starButton);
         }
 
         let node = this.view.state.selection.$from.nodeAfter;
@@ -256,6 +266,19 @@ export class TooltipTextMenu {
         link = node && node.marks.find(m => m.type.name === "link");
     }
 
+    insertStar(state: any, dispatch: any) {
+        console.log("creating star...");
+        let type = schema.nodes.star;
+        let { $from } = state.selection;
+        if (!$from.parent.canReplaceWith($from.index(), $from.index(), type)) {
+            return false;
+        }
+        if (dispatch) {
+            dispatch(state.tr.replaceSelectionWith(type.create()));
+        }
+        return true;
+    }
+
     //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
@@ -426,6 +449,12 @@ export class TooltipTextMenu {
 
         this.tooltip.style.width = 225 + "px";
         this.tooltip.style.bottom = (box.bottom - start.top) * this.editorProps.ScreenToLocalTransform().Scale + "px";
+        this.tooltip.style.top = "-100px";
+        //this.tooltip.style.height = "100px";
+
+        // let transform = this.editorProps.ScreenToLocalTransform();
+        // this.tooltip.style.width = `${225 / transform.Scale}px`;
+        // Utils
 
         //UPDATE LIST ITEM DROPDOWN
         this.listTypeBtnDom = this.updateListItemDropdown(":", this.listTypeBtnDom!);
diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx
index 24327b995..d1224febe 100644
--- a/src/client/views/MainOverlayTextBox.tsx
+++ b/src/client/views/MainOverlayTextBox.tsx
@@ -102,6 +102,6 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
                 </div>
             </ div>;
         }
-        else return (null); Z
+        else return (null);
     }
 }
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 9ec941eff..563fbb186 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -45,12 +45,14 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
     _commandExecuted = false;
 
     @action
-    cleanupInteractions = (all: boolean = false) => {
+    cleanupInteractions = (all: boolean = false, rem_keydown: boolean = true) => {
         if (all) {
             document.removeEventListener("pointerup", this.onPointerUp, true);
             document.removeEventListener("pointermove", this.onPointerMove, true);
         }
-        document.removeEventListener("keydown", this.marqueeCommand, true);
+        if (rem_keydown) {
+            document.removeEventListener("keydown", this.marqueeCommand, true);
+        }
         this._visible = false;
     }
 
@@ -180,6 +182,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
 
     @action
     onPointerUp = (e: PointerEvent): void => {
+        console.log("pointer up!");
         if (this._visible) {
             let mselect = this.marqueeSelect();
             if (!e.shiftKey) {
@@ -187,7 +190,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
             }
             this.props.selectDocuments(mselect.length ? mselect : [this.props.container.props.Document]);
         }
-        this.cleanupInteractions(true);
+        this.cleanupInteractions(true, false);
         if (e.altKey) {
             e.preventDefault();
         }
-- 
cgit v1.2.3-70-g09d2


From ef24cc445aa466cae3b6c40029f7d7bc9baa81b7 Mon Sep 17 00:00:00 2001
From: ab <abdullah_ahmed@brown.edu>
Date: Fri, 7 Jun 2019 16:37:10 -0400
Subject: frustrated!

---
 src/client/util/ProsemirrorKeymap.ts               | 17 ---------------
 src/client/util/TooltipTextMenu.scss               | 11 +++++-----
 src/client/util/TooltipTextMenu.tsx                | 24 +++++++++++++++-------
 .../collections/collectionFreeForm/MarqueeView.tsx |  9 +++++++-
 4 files changed, 31 insertions(+), 30 deletions(-)

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

diff --git a/src/client/util/ProsemirrorKeymap.ts b/src/client/util/ProsemirrorKeymap.ts
index 89d01486e..cf656dc17 100644
--- a/src/client/util/ProsemirrorKeymap.ts
+++ b/src/client/util/ProsemirrorKeymap.ts
@@ -24,22 +24,6 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?:
         keys[key] = cmd;
     }
 
-    function insertStar(state: EditorState<S>, dispatch: ((tr: Transaction<S>) => void)) {
-        console.log("creating star...");
-        let type = schema.nodes.star as NodeType<S>;
-        let { $from } = state.selection;
-        if (!$from.parent.canReplaceWith($from.index(), $from.index(), type)) {
-            return false;
-        }
-        if (dispatch) {
-            dispatch(state.tr.replaceSelectionWith(type.create()));
-        }
-        return true;
-    }
-
-    console.log("star? hullo");
-    bind("Mod-space", insertStar);
-
     bind("Mod-z", undo);
     bind("Shift-Mod-z", redo);
     bind("Backspace", undoInputRule);
@@ -95,7 +79,6 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?:
     }
     if (type = schema.nodes.paragraph) {
         bind("Shift-Ctrl-0", setBlockType(type));
-        bind("Mod-space", insertStar);
     }
     if (type = schema.nodes.code_block) {
         bind("Shift-Ctrl-\\", setBlockType(type));
diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss
index 0720a73a3..0099c1e4d 100644
--- a/src/client/util/TooltipTextMenu.scss
+++ b/src/client/util/TooltipTextMenu.scss
@@ -239,12 +239,13 @@
     border: 1px solid silver;
     border-radius: 15px;
     padding: 2px 10px;
-    margin-bottom: 60px;
-    -webkit-transform: translateX(-50%);
-    transform: translateX(-50%);
+    //margin-bottom: 100px;
+    //-webkit-transform: translateX(-50%);
+    //transform: translateX(-50%);
+    transform: translateY(-50%);
     pointer-events: all;
-    height: 100px;
-    width:500px;
+    height: 125px;
+    width:400px;
     .ProseMirror-example-setup-style hr {
       padding: 2px 10px;
       border: none;
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 1f1ad9cd4..366105ad9 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -234,7 +234,12 @@ export class TooltipTextMenu {
             this.tooltip.appendChild(this.linkEditor);
 
             let starButton = document.createElement("button");
-            starButton.textContent = "ST";
+            // starButton.style.width = '10px';
+            // starButton.style.height = '10px';
+            starButton.style.marginLeft = '10px';
+            starButton.textContent = "Summarize";
+            starButton.style.color = 'black';
+            starButton.style.backgroundColor = 'white';
             starButton.onclick = () => {
                 let state = this.view.state;
                 this.insertStar(state, this.view.dispatch);
@@ -266,16 +271,21 @@ export class TooltipTextMenu {
         link = node && node.marks.find(m => m.type.name === "link");
     }
 
-    insertStar(state: any, dispatch: any) {
+    insertStar(state: EditorState<any>, dispatch: any) {
         console.log("creating star...");
         let type = schema.nodes.star;
-        let { $from } = state.selection;
-        if (!$from.parent.canReplaceWith($from.index(), $from.index(), type)) {
-            return false;
-        }
+        //let {$from} = state.selection;
+        let select = state.selection;
         if (dispatch) {
-            dispatch(state.tr.replaceSelectionWith(type.create()));
+            dispatch(state.tr.setMeta('select.visible', false));
         }
+        // console.log($from);
+        // if (!$from.parent.canReplaceWith($from.index(), $from.index(), type)) {
+        //     return false;
+        // }
+        // if (dispatch) {
+        //     dispatch(state.tr.replaceSelectionWith(type.create()));
+        // }
         return true;
     }
 
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 563fbb186..6b81cc597 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -147,6 +147,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
         this._downY = this._lastY = e.pageY;
         this._commandExecuted = false;
         PreviewCursor.Visible = false;
+        this.cleanupInteractions(true);
         if (e.button === 2 || (e.button === 0 && e.altKey)) {
             if (!this.props.container.props.active()) this.props.selectDocuments([this.props.container.props.Document]);
             document.addEventListener("pointermove", this.onPointerMove, true);
@@ -184,13 +185,19 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
     onPointerUp = (e: PointerEvent): void => {
         console.log("pointer up!");
         if (this._visible) {
+            console.log("visible");
             let mselect = this.marqueeSelect();
             if (!e.shiftKey) {
                 SelectionManager.DeselectAll(mselect.length ? undefined : this.props.container.props.Document);
             }
             this.props.selectDocuments(mselect.length ? mselect : [this.props.container.props.Document]);
+            mselect.length ? this.cleanupInteractions(true, false) : this.cleanupInteractions(true);
         }
-        this.cleanupInteractions(true, false);
+        else {
+            console.log("invisible");
+            this.cleanupInteractions(true);
+        }
+
         if (e.altKey) {
             e.preventDefault();
         }
-- 
cgit v1.2.3-70-g09d2


From bf73b629c78d0db0b8d4bcf6aadd6609b3fe1689 Mon Sep 17 00:00:00 2001
From: ab <abdullah_ahmed@brown.edu>
Date: Tue, 11 Jun 2019 12:02:00 -0400
Subject: star node, trying to set selection as its attribute for copy/paste
 like behavior

---
 src/client/util/RichTextSchema.tsx          | 13 +++++++++++++
 src/client/util/TooltipTextMenu.tsx         | 23 ++++++++++++-----------
 src/client/views/nodes/FormattedTextBox.tsx |  6 ++++--
 3 files changed, 29 insertions(+), 13 deletions(-)

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

diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 8fae821a5..a29036f19 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -28,6 +28,7 @@ export const nodes: { [index: string]: NodeSpec } = {
 
     star: {
         inline: true,
+        attrs: { oldtext: { default: "suhhhh" } },
         group: "inline",
         toDOM() { return ["star", "🟊"]; },
         parseDOM: [{ tag: "star" }]
@@ -424,6 +425,18 @@ export class ImageResizeView {
         this._handle.style.display = "none";
     }
 }
+
+export class SummarizedView {
+    _collapsed: HTMLElement;
+    _selection: any;
+    constructor(node: any) {
+        this._collapsed = document.createElement("star");
+        this._collapsed.onpointerdown = function (e: any) {
+            console.log("star pressed!");
+        };
+
+    }
+}
 // :: Schema
 // This schema rougly corresponds to the document schema used by
 // [CommonMark](http://commonmark.org/), minus the list elements,
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 366105ad9..c5d016547 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -1,6 +1,6 @@
 import { action, IReactionDisposer, reaction } from "mobx";
 import { Dropdown, DropdownSubmenu, MenuItem, MenuItemSpec, renderGrouped, icons, } from "prosemirror-menu"; //no import css
-import { baseKeymap, lift } from "prosemirror-commands";
+import { baseKeymap, lift, deleteSelection } from "prosemirror-commands";
 import { history, redo, undo } from "prosemirror-history";
 import { keymap } from "prosemirror-keymap";
 import { EditorState, Transaction, NodeSelection, TextSelection } from "prosemirror-state";
@@ -28,6 +28,7 @@ import { CollectionDockingView } from "../views/collections/CollectionDockingVie
 import { DocumentManager } from "./DocumentManager";
 import { Id } from "../../new_fields/FieldSymbols";
 import { Utils } from "../../Utils";
+// import { wrap } from "module";
 
 const SVG = "http://www.w3.org/2000/svg";
 
@@ -74,7 +75,7 @@ export class TooltipTextMenu {
             { command: toggleMark(schema.marks.strikethrough), dom: this.icon("S", "strikethrough", "Strikethrough") },
             { command: toggleMark(schema.marks.superscript), dom: this.icon("s", "superscript", "Superscript") },
             { command: toggleMark(schema.marks.subscript), dom: this.icon("s", "subscript", "Subscript") },
-            { command: toggleMark(schema.marks.collapse), dom: this.icon("C", 'collapse', 'Collapse') }
+            { command: deleteSelection, dom: this.icon("C", 'collapse', 'Collapse') }
             // { command: wrapInList(schema.nodes.bullet_list), dom: this.icon(":", "bullets") },
             // { command: wrapInList(schema.nodes.ordered_list), dom: this.icon("1)", "bullets") },
             // { command: lift, dom: this.icon("<", "lift") },
@@ -274,18 +275,18 @@ export class TooltipTextMenu {
     insertStar(state: EditorState<any>, dispatch: any) {
         console.log("creating star...");
         let type = schema.nodes.star;
-        //let {$from} = state.selection;
         let select = state.selection;
+        let node = select.$from.nodeAfter;
+        if (node) {
+            console.log("node");
+            console.log(node.type.name);
+            if (node.type.name === "star") {
+                console.log(node.attrs.oldtext);
+            }
+        }
         if (dispatch) {
-            dispatch(state.tr.setMeta('select.visible', false));
+            dispatch(state.tr.replaceSelectionWith(type.create({ attrs: { oldtext: select } })));
         }
-        // console.log($from);
-        // if (!$from.parent.canReplaceWith($from.index(), $from.index(), type)) {
-        //     return false;
-        // }
-        // if (dispatch) {
-        //     dispatch(state.tr.replaceSelectionWith(type.create()));
-        // }
         return true;
     }
 
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 5c635cc0c..b40c6d580 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -16,7 +16,7 @@ import { DocumentManager } from "../../util/DocumentManager";
 import { DragManager } from "../../util/DragManager";
 import buildKeymap from "../../util/ProsemirrorKeymap";
 import { inpRules } from "../../util/RichTextRules";
-import { ImageResizeView, schema } from "../../util/RichTextSchema";
+import { SummarizedView, ImageResizeView, schema } from "../../util/RichTextSchema";
 import { SelectionManager } from "../../util/SelectionManager";
 import { TooltipLinkingMenu } from "../../util/TooltipLinkingMenu";
 import { TooltipTextMenu } from "../../util/TooltipTextMenu";
@@ -29,6 +29,7 @@ import { FieldView, FieldViewProps } from "./FieldView";
 import "./FormattedTextBox.scss";
 import React = require("react");
 import { DocUtils } from '../../documents/Documents';
+import { start } from 'repl';
 
 library.add(faEdit);
 library.add(faSmile);
@@ -189,7 +190,8 @@ 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); },
+                    //star(node, view, getPos) { return new SummarizedView(node); }
                 }
             });
             let text = StrCast(this.props.Document.documentText);
-- 
cgit v1.2.3-70-g09d2


From 366ac7e434be306ffd979d7f22ef50ad77fbc1eb Mon Sep 17 00:00:00 2001
From: ab <abdullah_ahmed@brown.edu>
Date: Tue, 11 Jun 2019 14:13:22 -0400
Subject: lolz

---
 src/client/util/TooltipTextMenu.tsx | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

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

diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index c5d016547..905f1969c 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -278,14 +278,18 @@ export class TooltipTextMenu {
         let select = state.selection;
         let node = select.$from.nodeAfter;
         if (node) {
-            console.log("node");
-            console.log(node.type.name);
             if (node.type.name === "star") {
-                console.log(node.attrs.oldtext);
+                let oldselection = node.attrs.oldtext;
+                if (dispatch) {
+                    dispatch(state.tr.replaceSelection(oldselection.content()));
+                }
+                return true;
             }
         }
         if (dispatch) {
-            dispatch(state.tr.replaceSelectionWith(type.create({ attrs: { oldtext: select } })));
+            let newNode = type.create();
+            newNode.attrs.oldtext = select;
+            dispatch(state.tr.replaceSelectionWith(newNode));
         }
         return true;
     }
-- 
cgit v1.2.3-70-g09d2


From a7cb61db19c3e34dcf3c91152c04d4aea2980682 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 12 Jun 2019 03:00:59 -0400
Subject: prototyep working version of elision -- however, it doesn't work once
 the text box is deselected/reloaded.  didn't look into why

---
 src/client/util/RichTextSchema.tsx          | 64 +++++++++++++++++++++++------
 src/client/util/TooltipTextMenu.tsx         | 18 ++------
 src/client/views/nodes/FormattedTextBox.tsx |  2 +-
 3 files changed, 57 insertions(+), 27 deletions(-)

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

diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index a29036f19..84c2fd5c3 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -1,9 +1,9 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Schema, NodeSpec, MarkSpec, DOMOutputSpecArray, NodeType } from "prosemirror-model";
-import { joinUp, lift, setBlockType, toggleMark, wrapIn } from 'prosemirror-commands';
+import { joinUp, lift, setBlockType, toggleMark, wrapIn, selectNodeForward, deleteSelection } from 'prosemirror-commands';
 import { redo, undo } from 'prosemirror-history';
 import { orderedList, bulletList, listItem, } from 'prosemirror-schema-list';
-import { EditorState, Transaction, NodeSelection, } from "prosemirror-state";
+import { EditorState, Transaction, NodeSelection, TextSelection, Selection, } from "prosemirror-state";
 import { EditorView, } from "prosemirror-view";
 
 const pDOM: DOMOutputSpecArray = ["p", 0], blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"],
@@ -26,13 +26,6 @@ export const nodes: { [index: string]: NodeSpec } = {
         toDOM() { return pDOM; }
     },
 
-    star: {
-        inline: true,
-        attrs: { oldtext: { default: "suhhhh" } },
-        group: "inline",
-        toDOM() { return ["star", "🟊"]; },
-        parseDOM: [{ tag: "star" }]
-    },
 
     // :: NodeSpec A blockquote (`<blockquote>`) wrapping one or more blocks.
     blockquote: {
@@ -85,6 +78,29 @@ export const nodes: { [index: string]: NodeSpec } = {
         group: "inline"
     },
 
+    star: {
+        inline: true,
+        attrs: {
+            visibility: { default: false },
+            oldtext: { default: undefined },
+            oldtextlen: { default: 0 }
+
+        },
+        group: "inline",
+        toDOM(node) {
+            const attrs = { style: `width: 40px` };
+            return ["span", { ...node.attrs, ...attrs }];
+        },
+        parseDOM: [{
+            tag: "span", getAttrs(dom: any) {
+                return {
+                    visibility: dom.getAttribute("visibility"),
+                    oldtext: dom.getAttribute("oldtext"),
+                    oldtextlen: dom.getAttribute("oldtextlen"),
+                }
+            }
+        }]
+    },
     // :: NodeSpec An inline image (`<img>`) node. Supports `src`,
     // `alt`, and `href` attributes. The latter two default to the empty
     // string.
@@ -428,13 +444,37 @@ export class ImageResizeView {
 
 export class SummarizedView {
     _collapsed: HTMLElement;
-    _selection: any;
-    constructor(node: any) {
-        this._collapsed = document.createElement("star");
+    constructor(node: any, view: any, getPos: any) {
+        this._collapsed = document.createElement("span");
+        this._collapsed.textContent = "...";
+        this._collapsed.style.opacity = "0.5";
+        this._collapsed.style.background = "yellow";
+        this._collapsed.style.position = "relative";
+        this._collapsed.style.width = "40px";
+        this._collapsed.style.height = "20px";
         this._collapsed.onpointerdown = function (e: any) {
             console.log("star pressed!");
+            if (node.attrs.visibility) {
+                let y = getPos();
+                view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.doc, y + 1, y + 1 + node.attrs.oldtextlen)));
+                view.dispatch(view.state.tr.deleteSelection(view.state, () => { }));
+
+            } else {
+                let y = getPos();
+                view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.doc, y + 1, y + 1)));
+                view.dispatch(view.state.tr.replaceSelection(node.attrs.oldtext));
+            }
+            node.attrs.visibility = !node.attrs.visibility;
+            e.preventDefault();
+            e.stopPropagation();
         };
+        (this as any).dom = this._collapsed;
+
+    }
+    selectNode() {
+    }
 
+    deselectNode() {
     }
 }
 // :: Schema
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 905f1969c..6cc71be39 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -274,21 +274,11 @@ export class TooltipTextMenu {
 
     insertStar(state: EditorState<any>, dispatch: any) {
         console.log("creating star...");
-        let type = schema.nodes.star;
-        let select = state.selection;
-        let node = select.$from.nodeAfter;
-        if (node) {
-            if (node.type.name === "star") {
-                let oldselection = node.attrs.oldtext;
-                if (dispatch) {
-                    dispatch(state.tr.replaceSelection(oldselection.content()));
-                }
-                return true;
-            }
-        }
+        let newNode = schema.nodes.star.create();
         if (dispatch) {
-            let newNode = type.create();
-            newNode.attrs.oldtext = select;
+            newNode.attrs.visibility = false;
+            newNode.attrs.oldtext = state.selection.content();
+            newNode.attrs.oldtextlen = state.selection.to - state.selection.from;
             dispatch(state.tr.replaceSelectionWith(newNode));
         }
         return true;
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index b40c6d580..3ba108a57 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -191,7 +191,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 dispatchTransaction: this.dispatchTransaction,
                 nodeViews: {
                     image(node, view, getPos) { return new ImageResizeView(node, view, getPos); },
-                    //star(node, view, getPos) { return new SummarizedView(node); }
+                    star(node, view, getPos) { return new SummarizedView(node, view, getPos); }
                 }
             });
             let text = StrCast(this.props.Document.documentText);
-- 
cgit v1.2.3-70-g09d2


From a4f5005fedae93bd4bb1529561acaa8d4f0474c5 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Wed, 12 Jun 2019 12:35:59 -0400
Subject: "fixed" prosemirror serialization of 'star' schema node

---
 src/client/util/RichTextSchema.tsx  | 10 +++++-----
 src/client/util/TooltipTextMenu.tsx |  2 +-
 2 files changed, 6 insertions(+), 6 deletions(-)

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

diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index db62f3ac1..d1282403f 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -90,6 +90,7 @@ export const nodes: { [index: string]: NodeSpec } = {
         attrs: {
             visibility: { default: false },
             oldtext: { default: undefined },
+            oldtextslice: { default: undefined },
             oldtextlen: { default: 0 }
 
         },
@@ -502,10 +503,9 @@ export const schema = new Schema({ nodes, marks });
 const fromJson = schema.nodeFromJSON;
 
 schema.nodeFromJSON = (json: any) => {
-    if (json.type !== "star") {
-        return fromJson(json);
+    let node = fromJson(json);
+    if (json.type === "star") {
+        node.attrs.oldtext = Slice.fromJSON(schema, node.attrs.oldtextslice);
     }
-    let x = fromJson(json);
-    x.attrs.oldtext = Slice.fromJSON(x.attrs.oldtext);
-    return x;
+    return node;
 }
\ No newline at end of file
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index a2288a496..87a4c33a1 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -274,7 +274,7 @@ export class TooltipTextMenu {
 
     insertStar(state: EditorState<any>, dispatch: any) {
         console.log("creating star...");
-        let newNode = schema.nodes.star.create({ visibility: false, oldtext: state.selection.content(), oldtextlen: state.selection.to - state.selection.from });
+        let newNode = schema.nodes.star.create({ visibility: false, oldtext: state.selection.content(), oldtextslice: state.selection.content().toJSON(), oldtextlen: state.selection.to - state.selection.from });
         if (dispatch) {
             console.log(newNode.attrs.oldtext.toString());
             dispatch(state.tr.replaceSelectionWith(newNode));
-- 
cgit v1.2.3-70-g09d2


From b3144df1abdd4919d3a04c85fe9a75de1cdc2782 Mon Sep 17 00:00:00 2001
From: Abdullah Ahmed <abdullah_ahmed@brown.edu>
Date: Wed, 12 Jun 2019 17:40:23 -0400
Subject: ui changes

---
 src/client/util/TooltipTextMenu.scss | 48 ++++++++++++++++++------------------
 src/client/util/TooltipTextMenu.tsx  |  6 +++--
 2 files changed, 28 insertions(+), 26 deletions(-)

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

diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss
index 0099c1e4d..4d4eb386d 100644
--- a/src/client/util/TooltipTextMenu.scss
+++ b/src/client/util/TooltipTextMenu.scss
@@ -244,8 +244,8 @@
     //transform: translateX(-50%);
     transform: translateY(-50%);
     pointer-events: all;
-    height: 125px;
-    width:400px;
+    height: auto;
+    width:inherit;
     .ProseMirror-example-setup-style hr {
       padding: 2px 10px;
       border: none;
@@ -261,28 +261,28 @@
     }
 }
 
-.tooltipMenu:before {
-    content: "";
-    height: 0; width: 0;
-    position: absolute;
-    left: 50%;
-    margin-left: -5px;
-    bottom: -6px;
-    border: 5px solid transparent;
-    border-bottom-width: 0;
-    border-top-color: silver;
-  }
-  .tooltipMenu:after {
-    content: "";
-    height: 0; width: 0;
-    position: absolute;
-    left: 50%;
-    margin-left: -5px;
-    bottom: -4.5px;
-    border: 5px solid transparent;
-    border-bottom-width: 0;
-    border-top-color: $dark-color;
-  }
+// .tooltipMenu:before {
+//     content: "";
+//     height: 0; width: 0;
+//     position: absolute;
+//     left: 50%;
+//     margin-left: -5px;
+//     bottom: -6px;
+//     border: 5px solid transparent;
+//     border-bottom-width: 0;
+//     border-top-color: silver;
+//   }
+//   .tooltipMenu:after {
+//     content: "";
+//     height: 0; width: 0;
+//     position: absolute;
+//     left: 50%;
+//     margin-left: -5px;
+//     bottom: -4.5px;
+//     border: 5px solid transparent;
+//     border-bottom-width: 0;
+//     border-top-color: $dark-color;
+//   }
 
   .menuicon {
     display: inline-block;
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 87a4c33a1..e12d4ed3c 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -234,13 +234,15 @@ export class TooltipTextMenu {
             this.linkEditor.appendChild(linkBtn);
             this.tooltip.appendChild(this.linkEditor);
 
-            let starButton = document.createElement("button");
+            let starButton = document.createElement("span");
             // starButton.style.width = '10px';
             // starButton.style.height = '10px';
             starButton.style.marginLeft = '10px';
             starButton.textContent = "Summarize";
             starButton.style.color = 'black';
+            starButton.style.height = '20px';
             starButton.style.backgroundColor = 'white';
+            starButton.style.textAlign = 'center';
             starButton.onclick = () => {
                 let state = this.view.state;
                 this.insertStar(state, this.view.dispatch);
@@ -450,7 +452,7 @@ export class TooltipTextMenu {
         let width = Math.abs(start.left - end.left) / 2 * this.editorProps.ScreenToLocalTransform().Scale;
         let mid = Math.min(start.left, end.left) + width;
 
-        this.tooltip.style.width = 225 + "px";
+        //this.tooltip.style.width = 225 + "px";
         this.tooltip.style.bottom = (box.bottom - start.top) * this.editorProps.ScreenToLocalTransform().Scale + "px";
         this.tooltip.style.top = "-100px";
         //this.tooltip.style.height = "100px";
-- 
cgit v1.2.3-70-g09d2


From 0a6c03f109254e36556482e75fa5fb14491d1626 Mon Sep 17 00:00:00 2001
From: ab <abdullah_ahmed@brown.edu>
Date: Thu, 13 Jun 2019 12:03:39 -0400
Subject: demo

---
 src/client/util/RichTextSchema.tsx           | 12 ++++++++----
 src/client/util/TooltipTextMenu.scss         | 11 +++++++++--
 src/client/util/TooltipTextMenu.tsx          | 15 ++++++---------
 src/client/views/nodes/FormattedTextBox.scss |  2 +-
 4 files changed, 24 insertions(+), 16 deletions(-)

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

diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 61ca4af5e..c0225f1f9 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -259,7 +259,7 @@ export const marks: { [index: string]: MarkSpec } = {
         toDOM() {
             return ['span', {
                 style: 'color: blue'
-            }]
+            }];
         }
     },
 
@@ -461,6 +461,7 @@ export class SummarizedView {
         this._collapsed.style.position = "relative";
         this._collapsed.style.width = "40px";
         this._collapsed.style.height = "20px";
+        let self = this;
         this._collapsed.onpointerdown = function (e: any) {
             console.log("star pressed!");
             if (node.attrs.visibility) {
@@ -469,16 +470,19 @@ export class SummarizedView {
                 let y = getPos();
                 view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.doc, y + 1, y + 1 + node.attrs.oldtextlen)));
                 view.dispatch(view.state.tr.deleteSelection(view.state, () => { }));
-                //this._collapsed.textContent = "㊀";
+                self._collapsed.textContent = "㊉";
             } else {
                 node.attrs.visibility = !node.attrs.visibility;
                 console.log("content is invisible");
                 let y = getPos();
+                console.log(y);
                 let mark = view.state.schema.mark(view.state.schema.marks.underline);
                 console.log("PASTING " + node.attrs.oldtext.toString());
                 view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.doc, y + 1, y + 1)));
-                view.dispatch(view.state.tr.replaceSelection(node.attrs.oldtext).addMark(view.state.selection.from, view.state.selection.from + node.attrs.oldtextlen, mark));
-                //this._collapsed.textContent = "㊉";
+                const from = view.state.selection.from;
+                view.dispatch(view.state.tr.replaceSelection(node.attrs.oldtext).addMark(from, from + node.attrs.oldtextlen, mark));
+                //view.dispatch(view.state.tr.setSelection(view.state.doc, from + node.attrs.oldtextlen + 1, from + node.attrs.oldtextlen + 1));
+                self._collapsed.textContent = "㊀";
             }
             e.preventDefault();
             e.stopPropagation();
diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss
index 4d4eb386d..af449071e 100644
--- a/src/client/util/TooltipTextMenu.scss
+++ b/src/client/util/TooltipTextMenu.scss
@@ -244,8 +244,8 @@
     //transform: translateX(-50%);
     transform: translateY(-50%);
     pointer-events: all;
-    height: auto;
-    width:inherit;
+    height: 100;
+    width:250;
     .ProseMirror-example-setup-style hr {
       padding: 2px 10px;
       border: none;
@@ -306,3 +306,10 @@
       font-size: 12px;
       padding-right: 0px;
   }
+  .summarize{
+      margin-left: 15px;
+      color: black;
+      height: 20px;
+      background-color: white;
+      text-align: center;
+  }
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index e12d4ed3c..7d4d5566c 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -234,19 +234,16 @@ export class TooltipTextMenu {
             this.linkEditor.appendChild(linkBtn);
             this.tooltip.appendChild(this.linkEditor);
 
+            // SUMMARIZE BUTTON
+
             let starButton = document.createElement("span");
-            // starButton.style.width = '10px';
-            // starButton.style.height = '10px';
-            starButton.style.marginLeft = '10px';
-            starButton.textContent = "Summarize";
-            starButton.style.color = 'black';
-            starButton.style.height = '20px';
-            starButton.style.backgroundColor = 'white';
-            starButton.style.textAlign = 'center';
+            starButton.className = "summarize";
+            starButton.textContent = "★";
+            starButton.title = 'Summarize';
             starButton.onclick = () => {
                 let state = this.view.state;
                 this.insertStar(state, this.view.dispatch);
-            }
+            };
 
             this.tooltip.appendChild(starButton);
         }
diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss
index 4a29c1949..e3e40860c 100644
--- a/src/client/views/nodes/FormattedTextBox.scss
+++ b/src/client/views/nodes/FormattedTextBox.scss
@@ -1,7 +1,7 @@
 @import "../globalCssVariables";
 .ProseMirror {
   width: 100%;
-  height: auto;
+  height: 100;
   min-height: 100%;
   font-family: $serif;
 }
-- 
cgit v1.2.3-70-g09d2


From 552e7b558f6627d91471ca1ee33d3505a94a3a86 Mon Sep 17 00:00:00 2001
From: ab <abdullah_ahmed@brown.edu>
Date: Thu, 13 Jun 2019 16:07:03 -0400
Subject: highlighting

---
 src/client/util/RichTextSchema.tsx   | 18 ++++++++++++++----
 src/client/util/TooltipTextMenu.scss |  8 ++++----
 src/client/util/TooltipTextMenu.tsx  |  6 +++---
 3 files changed, 21 insertions(+), 11 deletions(-)

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

diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index c0225f1f9..789f3e0cf 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -5,6 +5,7 @@ import { redo, undo } from 'prosemirror-history';
 import { orderedList, bulletList, listItem, } from 'prosemirror-schema-list';
 import { EditorState, Transaction, NodeSelection, TextSelection, Selection, } from "prosemirror-state";
 import { EditorView, } from "prosemirror-view";
+import { View } from '@react-pdf/renderer';
 
 const pDOM: DOMOutputSpecArray = ["p", 0], blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"],
     preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0];
@@ -254,11 +255,11 @@ export const marks: { [index: string]: MarkSpec } = {
         toDOM: () => ['sup']
     },
 
-    collapse: {
-        parseDOM: [{ style: 'color: blue' }],
+    highlight: {
+        parseDOM: [{ style: 'background: #9aa8a4' }],
         toDOM() {
             return ['span', {
-                style: 'color: blue'
+                style: 'background: #9aa8a4'
             }];
         }
     },
@@ -453,6 +454,7 @@ export class ImageResizeView {
 export class SummarizedView {
     // TODO: highlight text that is summarized. to find end of region, walk along mark
     _collapsed: HTMLElement;
+    _view: any;
     constructor(node: any, view: any, getPos: any) {
         this._collapsed = document.createElement("span");
         this._collapsed.textContent = "㊉";
@@ -462,6 +464,7 @@ export class SummarizedView {
         this._collapsed.style.width = "40px";
         this._collapsed.style.height = "20px";
         let self = this;
+        this._view = view;
         this._collapsed.onpointerdown = function (e: any) {
             console.log("star pressed!");
             if (node.attrs.visibility) {
@@ -471,17 +474,19 @@ export class SummarizedView {
                 view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.doc, y + 1, y + 1 + node.attrs.oldtextlen)));
                 view.dispatch(view.state.tr.deleteSelection(view.state, () => { }));
                 self._collapsed.textContent = "㊉";
+                self.updateSummarizedText();
             } else {
                 node.attrs.visibility = !node.attrs.visibility;
                 console.log("content is invisible");
                 let y = getPos();
                 console.log(y);
-                let mark = view.state.schema.mark(view.state.schema.marks.underline);
+                let mark = view.state.schema.mark(view.state.schema.marks.highlight);
                 console.log("PASTING " + node.attrs.oldtext.toString());
                 view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.doc, y + 1, y + 1)));
                 const from = view.state.selection.from;
                 view.dispatch(view.state.tr.replaceSelection(node.attrs.oldtext).addMark(from, from + node.attrs.oldtextlen, mark));
                 //view.dispatch(view.state.tr.setSelection(view.state.doc, from + node.attrs.oldtextlen + 1, from + node.attrs.oldtextlen + 1));
+                view.dispatch(view.state.tr.removeStoredMark(mark));
                 self._collapsed.textContent = "㊀";
             }
             e.preventDefault();
@@ -493,6 +498,11 @@ export class SummarizedView {
     selectNode() {
     }
 
+    updateSummarizedText(mark?: any) {
+        console.log(this._view.state.doc.marks);
+        console.log(this._view.state.doc.childCount);
+    }
+
     deselectNode() {
     }
 }
diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss
index af449071e..676411535 100644
--- a/src/client/util/TooltipTextMenu.scss
+++ b/src/client/util/TooltipTextMenu.scss
@@ -235,7 +235,7 @@
 .tooltipMenu {
     position: absolute;
     z-index: 50;
-    background: whitesmoke;
+    background: black;
     border: 1px solid silver;
     border-radius: 15px;
     padding: 2px 10px;
@@ -308,8 +308,8 @@
   }
   .summarize{
       margin-left: 15px;
-      color: black;
+      color: white;
       height: 20px;
-      background-color: white;
+    //   background-color: white;
       text-align: center;
-  }
+  }
\ No newline at end of file
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 7d4d5566c..f3f27335f 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -179,13 +179,13 @@ export class TooltipTextMenu {
             this.linkText.style.whiteSpace = "nowrap";
             this.linkText.style.width = "150px";
             this.linkText.style.overflow = "hidden";
-            this.linkText.style.color = "black";
+            this.linkText.style.color = "white";
             this.linkText.onpointerdown = (e: PointerEvent) => { e.stopPropagation(); };
             let linkBtn = document.createElement("div");
             linkBtn.textContent = ">>";
             linkBtn.style.width = "10px";
             linkBtn.style.height = "10px";
-            linkBtn.style.color = "black";
+            linkBtn.style.color = "white";
             linkBtn.style.cssFloat = "left";
             linkBtn.onpointerdown = (e: PointerEvent) => {
                 let node = this.view.state.selection.$from.nodeAfter;
@@ -382,7 +382,7 @@ export class TooltipTextMenu {
         span.className = name + " menuicon";
         span.title = title;
         span.textContent = text;
-        span.style.color = "black";
+        span.style.color = "white";
         return span;
     }
 
-- 
cgit v1.2.3-70-g09d2


From c22feda3d2df8f6814c0837ea18ad293975ae2e4 Mon Sep 17 00:00:00 2001
From: ab <abdullah_ahmed@brown.edu>
Date: Fri, 14 Jun 2019 17:20:46 -0400
Subject: f this

---
 src/client/util/RichTextSchema.tsx   | 41 +++++++++++++++++++++++++-----------
 src/client/util/TooltipTextMenu.scss |  2 +-
 src/client/util/TooltipTextMenu.tsx  |  4 ++--
 3 files changed, 32 insertions(+), 15 deletions(-)

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

diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 789f3e0cf..8c56a32e8 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -1,5 +1,5 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Schema, NodeSpec, MarkSpec, DOMOutputSpecArray, NodeType, Slice } from "prosemirror-model";
+import { Schema, NodeSpec, MarkSpec, DOMOutputSpecArray, NodeType, Slice, Mark } from "prosemirror-model";
 import { joinUp, lift, setBlockType, toggleMark, wrapIn, selectNodeForward, deleteSelection } from 'prosemirror-commands';
 import { redo, undo } from 'prosemirror-history';
 import { orderedList, bulletList, listItem, } from 'prosemirror-schema-list';
@@ -90,7 +90,7 @@ export const nodes: { [index: string]: NodeSpec } = {
         inline: true,
         attrs: {
             visibility: { default: false },
-            oldtext: { default: undefined },
+            text: { default: undefined },
             oldtextslice: { default: undefined },
             oldtextlen: { default: 0 }
 
@@ -256,10 +256,10 @@ export const marks: { [index: string]: MarkSpec } = {
     },
 
     highlight: {
-        parseDOM: [{ style: 'background: #9aa8a4' }],
+        parseDOM: [{ style: 'background: #d9dbdd' }],
         toDOM() {
             return ['span', {
-                style: 'background: #9aa8a4'
+                style: 'background: #d9dbdd'
             }];
         }
     },
@@ -471,20 +471,23 @@ export class SummarizedView {
                 node.attrs.visibility = !node.attrs.visibility;
                 console.log("content is visible");
                 let y = getPos();
-                view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.doc, y + 1, y + 1 + node.attrs.oldtextlen)));
+                let { from, to } = self.updateSummarizedText(y + 1, view.state.schema.marks.highlight);
+                let length = to - from;
+                let newSelection = TextSelection.create(view.state.doc, y + 1, y + 1 + length);
+                node.attrs.text = newSelection.content();
+                view.dispatch(view.state.tr.setSelection(newSelection));
                 view.dispatch(view.state.tr.deleteSelection(view.state, () => { }));
                 self._collapsed.textContent = "㊉";
-                self.updateSummarizedText();
             } else {
                 node.attrs.visibility = !node.attrs.visibility;
                 console.log("content is invisible");
                 let y = getPos();
                 console.log(y);
                 let mark = view.state.schema.mark(view.state.schema.marks.highlight);
-                console.log("PASTING " + node.attrs.oldtext.toString());
+                console.log("PASTING " + node.attrs.text.toString());
                 view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.doc, y + 1, y + 1)));
                 const from = view.state.selection.from;
-                view.dispatch(view.state.tr.replaceSelection(node.attrs.oldtext).addMark(from, from + node.attrs.oldtextlen, mark));
+                view.dispatch(view.state.tr.replaceSelection(node.attrs.text).addMark(from, from + node.attrs.oldtextlen, mark));
                 //view.dispatch(view.state.tr.setSelection(view.state.doc, from + node.attrs.oldtextlen + 1, from + node.attrs.oldtextlen + 1));
                 view.dispatch(view.state.tr.removeStoredMark(mark));
                 self._collapsed.textContent = "㊀";
@@ -498,9 +501,23 @@ export class SummarizedView {
     selectNode() {
     }
 
-    updateSummarizedText(mark?: any) {
-        console.log(this._view.state.doc.marks);
-        console.log(this._view.state.doc.childCount);
+    updateSummarizedText(start?: any, mark?: any) {
+        let $start = this._view.state.doc.resolve(start);
+        let first_startPos = $start.start(), endPos = first_startPos;
+        while ($start.nodeAfter !== null) {
+            let startIndex = $start.index(), endIndex = $start.indexAfter();
+            while (startIndex > 0 && mark.isInSet($start.parent.child(startIndex - 1).marks)) startIndex--;
+            while (endIndex < $start.parent.childCount && mark.isInSet($start.parent.child(endIndex).marks)) endIndex++;
+            let startPos = $start.start(), endPos = startPos;
+            for (let i = 0; i < endIndex; i++) {
+                let size = $start.parent.child(i).nodeSize;
+                console.log($start.parent.child(i).childCount);
+                if (i < startIndex) startPos += size;
+                endPos += size;
+            }
+            $start = this._view.state.doc.resolve(start + (endPos - startPos) + 1);
+        }
+        return { from: first_startPos, to: endPos };
     }
 
     deselectNode() {
@@ -524,4 +541,4 @@ schema.nodeFromJSON = (json: any) => {
         node.attrs.oldtext = Slice.fromJSON(schema, node.attrs.oldtextslice);
     }
     return node;
-}
\ No newline at end of file
+};
\ No newline at end of file
diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss
index 676411535..c10a61558 100644
--- a/src/client/util/TooltipTextMenu.scss
+++ b/src/client/util/TooltipTextMenu.scss
@@ -235,7 +235,7 @@
 .tooltipMenu {
     position: absolute;
     z-index: 50;
-    background: black;
+    background: #121721;
     border: 1px solid silver;
     border-radius: 15px;
     padding: 2px 10px;
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index f3f27335f..b8ca299dd 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -273,9 +273,9 @@ export class TooltipTextMenu {
 
     insertStar(state: EditorState<any>, dispatch: any) {
         console.log("creating star...");
-        let newNode = schema.nodes.star.create({ visibility: false, oldtext: state.selection.content(), oldtextslice: state.selection.content().toJSON(), oldtextlen: state.selection.to - state.selection.from });
+        let newNode = schema.nodes.star.create({ visibility: false, text: state.selection.content(), oldtextslice: state.selection.content().toJSON(), oldtextlen: state.selection.to - state.selection.from });
         if (dispatch) {
-            console.log(newNode.attrs.oldtext.toString());
+            console.log(newNode.attrs.text.toString());
             dispatch(state.tr.replaceSelectionWith(newNode));
         }
         return true;
-- 
cgit v1.2.3-70-g09d2


From 13e301dea2f537b67b338cc6a98d3f3b5a8e1f36 Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Tue, 18 Jun 2019 20:58:32 -0400
Subject: Fixed linter errors

---
 src/client/northstar/dash-nodes/HistogramBox.tsx             |  3 +--
 src/client/util/DocumentManager.ts                           |  6 +++---
 src/client/util/DragManager.ts                               |  4 +++-
 src/client/util/RichTextSchema.tsx                           | 10 +++++-----
 src/client/util/TooltipTextMenu.tsx                          |  2 +-
 src/client/views/ContextMenu.tsx                             |  4 ++--
 src/client/views/DocumentDecorations.tsx                     |  2 +-
 src/client/views/MainOverlayTextBox.tsx                      |  2 +-
 src/client/views/MainView.tsx                                |  7 ++++---
 src/client/views/PresentationView.tsx                        |  4 ++--
 src/client/views/collections/CollectionBaseView.tsx          |  2 +-
 src/client/views/collections/CollectionDockingView.tsx       |  5 +++--
 src/client/views/collections/CollectionPDFView.tsx           |  2 +-
 src/client/views/collections/CollectionSchemaView.tsx        | 11 ++++++-----
 src/client/views/collections/CollectionStackingView.tsx      |  4 ++--
 src/client/views/collections/CollectionTreeView.tsx          | 10 +++++-----
 .../collectionFreeForm/CollectionFreeFormLinksView.tsx       |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx            |  2 +-
 .../views/collections/collectionFreeForm/MarqueeView.tsx     |  2 +-
 src/client/views/nodes/CollectionFreeFormDocumentView.tsx    |  2 +-
 src/client/views/nodes/DocumentView.tsx                      | 12 ++++++------
 src/client/views/nodes/FormattedTextBox.tsx                  |  2 +-
 src/client/views/nodes/ImageBox.tsx                          |  3 ++-
 src/client/views/pdf/PDFAnnotationLayer.tsx                  |  2 +-
 src/client/views/pdf/PDFMenu.tsx                             |  2 +-
 src/client/views/pdf/PDFViewer.tsx                           |  4 ++--
 src/server/index.ts                                          |  2 +-
 27 files changed, 59 insertions(+), 54 deletions(-)

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

diff --git a/src/client/northstar/dash-nodes/HistogramBox.tsx b/src/client/northstar/dash-nodes/HistogramBox.tsx
index d7732ee86..a60eaea85 100644
--- a/src/client/northstar/dash-nodes/HistogramBox.tsx
+++ b/src/client/northstar/dash-nodes/HistogramBox.tsx
@@ -125,8 +125,7 @@ export class HistogramBox extends React.Component<FieldViewProps> {
                                 let mapped = brushingDocs.map((brush, i) => {
                                     brush.backgroundColor = StyleConstants.BRUSH_COLORS[i % StyleConstants.BRUSH_COLORS.length];
                                     let brushed = DocListCast(brush.brushingDocs);
-                                    if (!brushed.length)
-                                        return null;
+                                    if (!brushed.length) return null;
                                     return { l: brush, b: brushed[0][Id] === proto[Id] ? brushed[1] : brushed[0] };
                                 });
                                 runInAction(() => this.HistoOp.BrushLinks.splice(0, this.HistoOp.BrushLinks.length, ...mapped.filter(m => m) as { l: Doc, b: Doc }[]));
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index ff0c1560b..862395d74 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -35,9 +35,9 @@ export class DocumentManager {
         let toReturn: DocumentView | null = null;
         let passes = preferredCollection ? [preferredCollection, undefined] : [undefined];
 
-        for (let i = 0; i < passes.length; i++) {
+        for (let pass of passes) {
             DocumentManager.Instance.DocumentViews.map(view => {
-                if (view.props.Document[Id] === id && (!passes[i] || view.props.ContainingCollectionView === preferredCollection)) {
+                if (view.props.Document[Id] === id && (!pass || view.props.ContainingCollectionView === preferredCollection)) {
                     toReturn = view;
                     return;
                 }
@@ -45,7 +45,7 @@ export class DocumentManager {
             if (!toReturn) {
                 DocumentManager.Instance.DocumentViews.map(view => {
                     let doc = view.props.Document.proto;
-                    if (doc && doc[Id] === id && (!passes[i] || view.props.ContainingCollectionView === preferredCollection)) {
+                    if (doc && doc[Id] === id && (!pass || view.props.ContainingCollectionView === preferredCollection)) {
                         toReturn = view;
                     }
                 });
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 89566e777..c3c92daa5 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -282,11 +282,13 @@ export namespace DragManager {
             // }
             let set = dragElement.getElementsByTagName('*');
             if (dragElement.hasAttribute("style")) (dragElement as any).style.pointerEvents = "none";
-            for (let i = 0; i < set.length; i++)
+            // tslint:disable-next-line: prefer-for-of
+            for (let i = 0; i < set.length; i++) {
                 if (set[i].hasAttribute("style")) {
                     let s = set[i];
                     (s as any).style.pointerEvents = "none";
                 }
+            }
 
 
             dragDiv.appendChild(dragElement);
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index e1e595925..943cdb4d1 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -156,12 +156,12 @@ export const nodes: { [index: string]: NodeSpec } = {
                     title: dom.getAttribute("title"),
                     alt: dom.getAttribute("alt"),
                     width: Math.min(100, Number(dom.getAttribute("width"))),
-                }
+                };
             }
         }],
         toDOM(node) {
-            const attrs = { style: `width: ${node.attrs.width}` }
-            return ["video", { ...node.attrs, ...attrs }]
+            const attrs = { style: `width: ${node.attrs.width}` };
+            return ["video", { ...node.attrs, ...attrs }];
         }
     },
 
@@ -285,7 +285,7 @@ export const marks: { [index: string]: MarkSpec } = {
         toDOM() {
             return ['span', {
                 style: 'color: blue'
-            }]
+            }];
         }
     },
 
@@ -536,4 +536,4 @@ schema.nodeFromJSON = (json: any) => {
         node.attrs.oldtext = Slice.fromJSON(schema, node.attrs.oldtextslice);
     }
     return node;
-}
\ No newline at end of file
+};
\ No newline at end of file
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 0a61b7e7d..f2559db74 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -256,7 +256,7 @@ export class TooltipTextMenu {
             starButton.onclick = () => {
                 let state = this.view.state;
                 this.insertStar(state, this.view.dispatch);
-            }
+            };
 
             this.tooltip.appendChild(starButton);
         }
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index da374455e..eb1937683 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -1,8 +1,8 @@
 import React = require("react");
 import { ContextMenuItem, ContextMenuProps } from "./ContextMenuItem";
 import { observable, action } from "mobx";
-import { observer } from "mobx-react"
-import "./ContextMenu.scss"
+import { observer } from "mobx-react";
+import "./ContextMenu.scss";
 import { library } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { faSearch, faCircle } from '@fortawesome/free-solid-svg-icons';
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index ceca940b6..e60f8c86c 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -466,7 +466,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         SelectionManager.SelectedDocuments().forEach(element => {
             const rect = element.ContentDiv ? element.ContentDiv.getBoundingClientRect() : new DOMRect();
 
-            if (rect.width !== 0 && (dX != 0 || dY != 0 || dW != 0 || dH != 0)) {
+            if (rect.width !== 0 && (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0)) {
                 let doc = PositionDocument(element.props.Document);
                 let nwidth = doc.nativeWidth || 0;
                 let nheight = doc.nativeHeight || 0;
diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx
index 0de880175..b4ad5f4d7 100644
--- a/src/client/views/MainOverlayTextBox.tsx
+++ b/src/client/views/MainOverlayTextBox.tsx
@@ -40,7 +40,7 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
                     this.TextDoc = box.props.Document;
                     let sxf = Utils.GetScreenTransform(box ? box.CurrentDiv : undefined);
                     let xf = () => { box.props.ScreenToLocalTransform(); return new Transform(-sxf.translateX, -sxf.translateY, 1 / sxf.scale); };
-                    this.setTextDoc(box.props.fieldKey, box.CurrentDiv, xf, BoolCast(box.props.Document.autoHeight, false) || box.props.height === "min-content")
+                    this.setTextDoc(box.props.fieldKey, box.CurrentDiv, xf, BoolCast(box.props.Document.autoHeight, false) || box.props.height === "min-content");
                 }
                 else {
                     this.TextDoc = undefined;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index fd76cbbd3..7f979cd3b 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -237,10 +237,11 @@ export class MainView extends React.Component {
     }
 
     onColorClick = (e: React.MouseEvent) => {
-        let target = (e.nativeEvent! as any).path[0];
-        let parent = (e.nativeEvent! as any).path[1];
-        if (target.localName === "input" || parent.localName === "span")
+        let target = (e.nativeEvent as any).path[0];
+        let parent = (e.nativeEvent as any).path[1];
+        if (target.localName === "input" || parent.localName === "span") {
             e.stopPropagation();
+        }
     }
 
 
diff --git a/src/client/views/PresentationView.tsx b/src/client/views/PresentationView.tsx
index d2d41a4ba..1dacbb663 100644
--- a/src/client/views/PresentationView.tsx
+++ b/src/client/views/PresentationView.tsx
@@ -40,8 +40,8 @@ class PresentationViewList extends React.Component<PresListProps> {
             //this doc is selected
             className += " presentationView-selected";
         }
-        let onEnter = (e: React.PointerEvent) => { document.libraryBrush = true; }
-        let onLeave = (e: React.PointerEvent) => { document.libraryBrush = false; }
+        let onEnter = (e: React.PointerEvent) => { document.libraryBrush = true; };
+        let onLeave = (e: React.PointerEvent) => { document.libraryBrush = false; };
         return (
             <div className={className} key={document[Id] + index}
                 onPointerEnter={onEnter} onPointerLeave={onLeave}
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index 5238ad114..1e42593d1 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -130,7 +130,7 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
 
     @action.bound
     removeDocument(doc: Doc): boolean {
-        let docView = DocumentManager.Instance.getDocumentView(doc, this.props.ContainingCollectionView)
+        let docView = DocumentManager.Instance.getDocumentView(doc, this.props.ContainingCollectionView);
         docView && SelectionManager.DeselectDoc(docView);
         const props = this.props;
         //TODO This won't create the field if it doesn't already exist
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index f2b3528b8..5beb89315 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -347,7 +347,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                             tab.header.parent.setActiveContentItem(tab.contentItem);
                         }
                         tab.setActive(true);
-                    }
+                    };
                     ReactDOM.render(<ParentDocSelector Document={doc} addDocTab={(doc, location) => CollectionDockingView.Instance.AddTab(stack, doc)} />, upDiv);
                     tab.reactComponents = [upDiv];
                     tab.element.append(upDiv);
@@ -432,8 +432,9 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
     @observable private _document: Opt<Doc>;
     get _stack(): any {
         let parent = (this.props as any).glContainer.parent.parent;
-        if (this._document && this._document.excludeFromLibrary && parent.parent && parent.parent.contentItems.length > 1)
+        if (this._document && this._document.excludeFromLibrary && parent.parent && parent.parent.contentItems.length > 1) {
             return parent.parent.contentItems[1];
+        }
         return parent;
     }
     constructor(props: any) {
diff --git a/src/client/views/collections/CollectionPDFView.tsx b/src/client/views/collections/CollectionPDFView.tsx
index b62d3f7bb..b2d016934 100644
--- a/src/client/views/collections/CollectionPDFView.tsx
+++ b/src/client/views/collections/CollectionPDFView.tsx
@@ -40,7 +40,7 @@ export class CollectionPDFView extends React.Component<FieldViewProps> {
                 // console.log(this.props.Document[HeightSym]());
             },
             { fireImmediately: true }
-        )
+        );
     }
 
     public static LayoutString(fieldKey: string = "data") {
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 14a7d19d0..faea8d44d 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -87,8 +87,9 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
             let columnDocs = DocListCast(schemaDoc.data);
             if (columnDocs) {
                 let ddoc = columnDocs.find(doc => doc.title === columnName);
-                if (ddoc)
+                if (ddoc) {
                     return ddoc;
+                }
             }
         }
         return this.props.Document;
@@ -285,7 +286,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
     }
 
     getPreviewTransform = (): Transform => this.props.ScreenToLocalTransform().translate(
-        - this.borderWidth - this.DIVIDER_WIDTH - this.tableWidth, - this.borderWidth);
+        - this.borderWidth - this.DIVIDER_WIDTH - this.tableWidth, - this.borderWidth)
 
 
     get documentKeysCheckList() {
@@ -334,7 +335,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
             columns={this.tableColumns}
             column={{ ...ReactTableDefaults.column, Cell: this.renderCell, }}
             getTrProps={this.getTrProps}
-        />
+        />;
     }
 
     @computed
@@ -360,7 +361,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
             addDocTab={this.props.addDocTab}
             setPreviewScript={this.setPreviewScript}
             previewScript={this.previewScript}
-        />
+        />;
     }
     @action
     setPreviewScript = (script: string) => {
@@ -409,7 +410,7 @@ export class CollectionSchemaPreview extends React.Component<CollectionSchemaPre
     }
     private PanelWidth = () => this.nativeWidth * this.contentScaling();
     private PanelHeight = () => this.nativeHeight * this.contentScaling();
-    private getTransform = () => this.props.getTransform().translate(-this.centeringOffset, 0).scale(1 / this.contentScaling())
+    private getTransform = () => this.props.getTransform().translate(-this.centeringOffset, 0).scale(1 / this.contentScaling());
     get centeringOffset() { return (this.props.width() - this.nativeWidth * this.contentScaling()) / 2; }
     @action
     onPreviewScriptChange = (e: React.ChangeEvent<HTMLInputElement>) => {
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index e1ac3505b..f5ad4ee95 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -101,7 +101,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
             let childFocus = (doc: Doc) => {
                 doc.libraryBrush = true;
                 this.props.focus(this.props.Document); // just focus on this collection, not the underlying document because the API doesn't support adding an offset to focus on and we can't pan zoom our contents to be centered.
-            }
+            };
             return (<div className="collectionStackingView-masonryDoc"
                 key={d[Id]}
                 ref={dref}
@@ -131,7 +131,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
                     whenActiveChanged={this.props.whenActiveChanged}
                 />
             </div>);
-        })
+        });
     }
     onContextMenu = (e: React.MouseEvent): void => {
         if (!e.isPropagationStopped() && this.props.Document[Id] !== "mainDoc") { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index b13694e9d..3e495b734 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -67,8 +67,8 @@ class TreeView extends React.Component<TreeViewProps> {
     @undoBatch delete = () => this.props.deleteDoc(this.props.document);
     @undoBatch openRight = async () => this.props.addDocTab(this.props.document, "openRight");
 
-    @action onMouseEnter = () => { this._isOver = true; }
-    @action onMouseLeave = () => { this._isOver = false; }
+    @action onMouseEnter = () => { this._isOver = true; };
+    @action onMouseLeave = () => { this._isOver = false; };
 
     onPointerEnter = (e: React.PointerEvent): void => {
         this.props.active() && (this.props.document.libraryBrush = true);
@@ -89,8 +89,8 @@ class TreeView extends React.Component<TreeViewProps> {
         let bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left, rect.top + rect.height / 2);
         let before = x[1] < bounds[1];
         let inside = x[0] > bounds[0] + 75 || (!before && this._bulletType === BulletType.Collapsible);
-        this._header!.current!.className = "treeViewItem-header"
-        if (inside && this._bulletType != BulletType.List) this._header!.current!.className += " treeViewItem-header-inside";
+        this._header!.current!.className = "treeViewItem-header";
+        if (inside && this._bulletType !== BulletType.List) this._header!.current!.className += " treeViewItem-header-inside";
         else if (before) this._header!.current!.className += " treeViewItem-header-above";
         else if (!before) this._header!.current!.className += " treeViewItem-header-below";
         e.stopPropagation();
@@ -192,7 +192,7 @@ class TreeView extends React.Component<TreeViewProps> {
             if (inside) {
                 let docList = Cast(this.props.document.data, listSpec(Doc));
                 if (docList !== undefined) {
-                    addDoc = (doc: Doc) => { docList && docList.push(doc); return true; }
+                    addDoc = (doc: Doc) => { docList && docList.push(doc); return true; };
                 }
             }
             let added = false;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index c4dd534ed..be75c6c5c 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -109,7 +109,7 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP
                     }
                     return match || found;
                 }, false)) {
-                    drawnPairs.push({ a: possiblePair.a, b: possiblePair.b, l: [connection.l] })
+                    drawnPairs.push({ a: possiblePair.a, b: possiblePair.b, l: [connection.l] });
                 }
             });
             return drawnPairs;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 250b6bf5d..84841e469 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -342,7 +342,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     private childViews = () => [
         <CollectionFreeFormBackgroundView key="backgroundView" {...this.props} {...this.getDocumentViewProps(this.props.Document)} />,
         ...this.views
-    ];
+    ]
     render() {
         const containerName = `collectionfreeformview${this.isAnnotationOverlay ? "-overlay" : "-container"}`;
         const easing = () => this.props.Document.panTransformType === "Ease";
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index dedcc3172..05dc6f534 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -321,7 +321,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                             summary.imageSummary = imageSummary;
                             this.props.addDocument(imageSummary, false);
                         }
-                    })
+                    });
                     newCollection.proto!.summaryDoc = summary;
                     selected = [newCollection];
                     newCollection.x = bounds.left + bounds.width;
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index f6b1c62ee..858959d81 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -48,7 +48,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     panelHeight = () => this.props.PanelHeight();
     getTransform = (): Transform => this.props.ScreenToLocalTransform()
         .translate(-this.X, -this.Y)
-        .scale(1 / this.contentScaling()).scale(1 / this.zoom);
+        .scale(1 / this.contentScaling()).scale(1 / this.zoom)
 
     animateBetweenIcon = (icon: number[], stime: number, maximizing: boolean) => {
         this.props.bringToFront(this.props.Document);
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 7c058d91c..4992669df 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -168,8 +168,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     public static animateBetweenIconFunc = (doc: Doc, width: number, height: number, stime: number, maximizing: boolean, cb?: (progress: number) => void) => {
         setTimeout(() => {
             let now = Date.now();
-            let progress = now < stime + 200 ? Math.min(1, (now - stime) / 200) : 1
-            doc.width = progress === 1 ? width : maximizing ? 25 + (width - 25) * progress : width + (25 - width) * progress
+            let progress = now < stime + 200 ? Math.min(1, (now - stime) / 200) : 1;
+            doc.width = progress === 1 ? width : maximizing ? 25 + (width - 25) * progress : width + (25 - width) * progress;
             doc.height = progress === 1 ? height : maximizing ? 25 + (height - 25) * progress : height + (25 - height) * progress;
             cb && cb(progress);
             if (now < stime + 200) {
@@ -229,7 +229,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         if (minimizedDoc) {
             let scrpt = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint(
                 NumCast(minimizedDoc.x) - NumCast(this.Document.x), NumCast(minimizedDoc.y) - NumCast(this.Document.y));
-            this.collapseTargetsToPoint(scrpt, await DocListCastAsync(minimizedDoc.maximizedDocs))
+            this.collapseTargetsToPoint(scrpt, await DocListCastAsync(minimizedDoc.maximizedDocs));
         }
     }
 
@@ -372,8 +372,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         this._lastTap = Date.now();
     }
 
-    deleteClicked = (): void => { this.props.removeDocument && this.props.removeDocument(this.props.Document); }
-    fieldsClicked = (): void => { this.props.addDocTab(Docs.KVPDocument(this.props.Document, { width: 300, height: 300 }), "onRight") };
+    deleteClicked = (): void => { this.props.removeDocument && this.props.removeDocument(this.props.Document); };
+    fieldsClicked = (): void => { this.props.addDocTab(Docs.KVPDocument(this.props.Document, { width: 300, height: 300 }), "onRight"); };
     makeBtnClicked = (): void => {
         let doc = Doc.GetProto(this.props.Document);
         doc.isButton = !BoolCast(doc.isButton, false);
@@ -527,7 +527,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     onPointerLeave = (e: React.PointerEvent): void => { this.props.Document.libraryBrush = false; };
 
     isSelected = () => SelectionManager.IsSelected(this);
-    @action select = (ctrlPressed: boolean) => { SelectionManager.SelectDoc(this, ctrlPressed); }
+    @action select = (ctrlPressed: boolean) => { SelectionManager.SelectDoc(this, ctrlPressed); };
 
     @computed get nativeWidth() { return this.Document.nativeWidth || 0; }
     @computed get nativeHeight() { return this.Document.nativeHeight || 0; }
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 36d902c4f..df12f261b 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -161,7 +161,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                     de.data.moveDocument(de.data.draggedDocuments[0], stackDoc, (doc) => {
                         Cast(stackDoc.data, listSpec(Doc))!.push(doc);
                         return true;
-                    })
+                    });
                 }
             }
         }
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 6b8b64c5f..f208ce2ce 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -188,8 +188,9 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
     }
     @action onError = () => {
         let timeout = this._curSuffix === "_s" ? this._smallRetryCount : this._curSuffix === "_m" ? this._mediumRetryCount : this._largeRetryCount;
-        if (timeout < 10)
+        if (timeout < 10) {
             setTimeout(this.retryPath, Math.min(10000, timeout * 5));
+        }
     }
     _curSuffix = "_m";
     render() {
diff --git a/src/client/views/pdf/PDFAnnotationLayer.tsx b/src/client/views/pdf/PDFAnnotationLayer.tsx
index e92dcacbf..1f49e0d2f 100644
--- a/src/client/views/pdf/PDFAnnotationLayer.tsx
+++ b/src/client/views/pdf/PDFAnnotationLayer.tsx
@@ -19,6 +19,6 @@ export class PDFAnnotationLayer extends React.Component {
             <div className="pdfAnnotationLayer-cont" style={{ width: "100%", height: "100%", position: "relative", top: "-200%" }} onPointerDown={this.onPointerDown}>
 
             </div>
-        )
+        );
     }
 }
\ No newline at end of file
diff --git a/src/client/views/pdf/PDFMenu.tsx b/src/client/views/pdf/PDFMenu.tsx
index 7817e8c26..2ed891131 100644
--- a/src/client/views/pdf/PDFMenu.tsx
+++ b/src/client/views/pdf/PDFMenu.tsx
@@ -162,6 +162,6 @@ export default class PDFMenu extends React.Component {
                 </button>
                 <div className="pdfMenu-dragger" onPointerDown={this.dragStart} style={{ width: this._pinned ? "20px" : "0px" }} />
             </div >
-        )
+        );
     }
 }
\ No newline at end of file
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 69372f43b..8c0aaea00 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -198,7 +198,7 @@ class Viewer extends React.Component<IViewerProps> {
                 <Page
                     pdf={this.props.pdf}
                     page={page}
-                    numPages={this.props.pdf!.numPages}
+                    numPages={this.props.pdf.numPages}
                     key={`rendered-${page + 1}`}
                     name={`${this.props.pdf.fingerprint + `-page${page + 1}`}`}
                     pageLoaded={this.pageLoaded}
@@ -219,7 +219,7 @@ class Viewer extends React.Component<IViewerProps> {
     @action
     getPageImage = async (page: number) => {
         let handleError = () => this.getRenderedPage(page);
-        if (this._isPage[page] != "image") {
+        if (this._isPage[page] !== "image") {
             this._isPage[page] = "image";
             const address = this.props.url;
             let res = JSON.parse(await rp.get(DocServer.prepend(`/thumbnail${address.substring("files/".length, address.length - ".pdf".length)}-${page + 1}.PNG`)));
diff --git a/src/server/index.ts b/src/server/index.ts
index 7ef542b01..2901f61ed 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -172,7 +172,7 @@ function LoadPage(file: string, pageNumber: number, res: Response) {
                     canvasContext: canvasAndContext.context,
                     viewport: viewport,
                     canvasFactory: factory
-                }
+                };
                 console.log("read " + pageNumber);
 
                 page.render(renderContext).promise
-- 
cgit v1.2.3-70-g09d2


From 90e8adc8578aa29a153cd41256b2f546a429a5bb Mon Sep 17 00:00:00 2001
From: ab <abdullah_ahmed@brown.edu>
Date: Wed, 19 Jun 2019 12:03:13 -0400
Subject: ui changes

---
 src/client/util/TooltipTextMenu.scss        | 14 +++---
 src/client/util/TooltipTextMenu.tsx         | 72 ++++++++++++++++-------------
 src/client/views/nodes/FormattedTextBox.tsx |  4 +-
 3 files changed, 49 insertions(+), 41 deletions(-)

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

diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss
index c10a61558..1867a697a 100644
--- a/src/client/util/TooltipTextMenu.scss
+++ b/src/client/util/TooltipTextMenu.scss
@@ -233,19 +233,19 @@
   }
 
 .tooltipMenu {
-    position: absolute;
-    z-index: 50;
+    // position: absolute;
+    // z-index: 50;
     background: #121721;
     border: 1px solid silver;
     border-radius: 15px;
-    padding: 2px 10px;
+    //padding: 2px 10px;
     //margin-bottom: 100px;
     //-webkit-transform: translateX(-50%);
     //transform: translateX(-50%);
-    transform: translateY(-50%);
+    //transform: translateY(-50%);
     pointer-events: all;
-    height: 100;
-    width:250;
+    // height: 100;
+    // width:250;
     .ProseMirror-example-setup-style hr {
       padding: 2px 10px;
       border: none;
@@ -307,7 +307,7 @@
       padding-right: 0px;
   }
   .summarize{
-      margin-left: 15px;
+      //margin-left: 15px;
       color: white;
       height: 20px;
     //   background-color: white;
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index a19b6c1d8..06a916832 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -66,7 +66,7 @@ export class TooltipTextMenu {
         this.tooltip.className = "tooltipMenu";
 
         //add the div which is the tooltip
-        view.dom.parentNode!.parentNode!.appendChild(this.tooltip);
+        //view.dom.parentNode!.parentNode!.appendChild(this.tooltip);
 
         //add additional icons
         library.add(faListUl);
@@ -131,11 +131,17 @@ export class TooltipTextMenu {
         this.link = document.createElement("a");
         this.link.target = "_blank";
         this.link.style.color = "white";
-        this.tooltip.appendChild(this.link);
+        //this.tooltip.appendChild(this.link);
 
         this.tooltip.appendChild(this.createLink().render(this.view).dom);
 
+        this.tooltip.appendChild(this.createStar().render(this.view).dom);
+
         this.update(view, undefined);
+
+        if (view.dom.parentNode !== null) {
+            view.dom.parentNode.insertBefore(this.tooltip, view.dom);
+        }
     }
 
     //label of dropdown will change to given label
@@ -239,23 +245,10 @@ export class TooltipTextMenu {
                         hideSource: false
                     });
             };
-            this.linkEditor.appendChild(this.linkDrag);
-            this.linkEditor.appendChild(this.linkText);
-            this.linkEditor.appendChild(linkBtn);
-            this.tooltip.appendChild(this.linkEditor);
-
-            // SUMMARIZE BUTTON
-
-            let starButton = document.createElement("span");
-            starButton.className = "summarize";
-            starButton.textContent = "★";
-            starButton.title = 'Summarize';
-            starButton.onclick = () => {
-                let state = this.view.state;
-                this.insertStar(state, this.view.dispatch);
-            };
-
-            this.tooltip.appendChild(starButton);
+            // this.linkEditor.appendChild(this.linkDrag);
+            // this.linkEditor.appendChild(this.linkText);
+            // this.linkEditor.appendChild(linkBtn);
+            //this.tooltip.appendChild(this.linkEditor);
         }
 
         let node = this.view.state.selection.$from.nodeAfter;
@@ -370,6 +363,21 @@ export class TooltipTextMenu {
         });
     }
 
+    createStar() {
+        return new MenuItem({
+            title: "Summarize",
+            label: "Summarize",
+            icon: icons.join,
+            css: "color:white;",
+            class: "summarize",
+            execEvent: "",
+            run: (state, dispatch, view) => {
+                this.insertStar(state, dispatch);
+            }
+
+        });
+    }
+
     createLink() {
         let markType = schema.marks.link;
         return new MenuItem({
@@ -499,34 +507,34 @@ export class TooltipTextMenu {
 
         // Hide the tooltip if the selection is empty
         if (state.selection.empty) {
-            this.tooltip.style.display = "none";
-            return;
+            //this.tooltip.style.display = "none";
+            //return;
         }
 
         let linksInSelection = this.activeMarksOnSelection([schema.marks.link]);
-        if (linksInSelection.length > 0) {
-            let attributes = this.getMarksInSelection(this.view.state, [schema.marks.link])[0].attrs;
-            this.link.href = attributes.href;
-            this.link.textContent = attributes.title;
-            this.link.style.visibility = "visible";
-        } else this.link.style.visibility = "hidden";
+        // if (linksInSelection.length > 0) {
+        //     let attributes = this.getMarksInSelection(this.view.state, [schema.marks.link])[0].attrs;
+        //     this.link.href = attributes.href;
+        //     this.link.textContent = attributes.title;
+        //     this.link.style.visibility = "visible";
+        // } else this.link.style.visibility = "hidden";
 
         // Otherwise, reposition it and update its content
-        this.tooltip.style.display = "";
+        //this.tooltip.style.display = "";
         let { from, to } = state.selection;
         let start = view.coordsAtPos(from), end = view.coordsAtPos(to);
         // The box in which the tooltip is positioned, to use as base
-        let box = this.tooltip.offsetParent!.getBoundingClientRect();
+        //let box = this.tooltip.offsetParent!.getBoundingClientRect();
         // Find a center-ish x position from the selection endpoints (when
         // crossing lines, end may be more to the left)
         let left = Math.max((start.left + end.left) / 2, start.left + 3);
-        this.tooltip.style.left = (left - box.left) * this.editorProps.ScreenToLocalTransform().Scale + "px";
+        //this.tooltip.style.left = (left - box.left) * this.editorProps.ScreenToLocalTransform().Scale + "px";
         let width = Math.abs(start.left - end.left) / 2 * this.editorProps.ScreenToLocalTransform().Scale;
         let mid = Math.min(start.left, end.left) + width;
 
         //this.tooltip.style.width = 225 + "px";
-        this.tooltip.style.bottom = (box.bottom - start.top) * this.editorProps.ScreenToLocalTransform().Scale + "px";
-        this.tooltip.style.top = "-100px";
+        // this.tooltip.style.bottom = (box.bottom - start.top) * this.editorProps.ScreenToLocalTransform().Scale + "px";
+        // this.tooltip.style.top = "-100px";
         //this.tooltip.style.height = "100px";
 
         // let transform = this.editorProps.ScreenToLocalTransform();
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index df12f261b..223e6df0a 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -254,7 +254,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         if (e.button === 0 && this.props.isSelected() && !e.altKey && !e.ctrlKey && !e.metaKey) {
             e.stopPropagation();
             if (this._toolTipTextMenu && this._toolTipTextMenu.tooltip) {
-                this._toolTipTextMenu.tooltip.style.opacity = "0";
+                //this._toolTipTextMenu.tooltip.style.opacity = "0";
             }
         }
         let ctrlKey = e.ctrlKey;
@@ -289,7 +289,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     }
     onPointerUp = (e: React.PointerEvent): void => {
         if (this._toolTipTextMenu && this._toolTipTextMenu.tooltip) {
-            this._toolTipTextMenu.tooltip.style.opacity = "1";
+            //this._toolTipTextMenu.tooltip.style.opacity = "1";
         }
         if (e.buttons === 1 && this.props.isSelected() && !e.altKey) {
             e.stopPropagation();
-- 
cgit v1.2.3-70-g09d2


From 47e125b9c7a8a61d820c013a5dd5f9cf654205af Mon Sep 17 00:00:00 2001
From: ab <abdullah_ahmed@brown.edu>
Date: Wed, 19 Jun 2019 14:01:05 -0400
Subject: moved tooltip inside textbox

---
 src/client/util/TooltipTextMenu.tsx                             | 2 +-
 src/client/views/collections/collectionFreeForm/MarqueeView.tsx | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

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

diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 06a916832..06862a79a 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -278,7 +278,7 @@ export class TooltipTextMenu {
         console.log("creating star...");
         let newNode = schema.nodes.star.create({ visibility: false, text: state.selection.content(), oldtextslice: state.selection.content().toJSON(), oldtextlen: state.selection.to - state.selection.from });
         if (dispatch) {
-            console.log(newNode.attrs.text.toString());
+            //console.log(newNode.attrs.text.toString());
             dispatch(state.tr.replaceSelectionWith(newNode));
         }
         return true;
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 05dc6f534..b8bcfcc89 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -50,7 +50,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
             document.removeEventListener("pointerup", this.onPointerUp, true);
             document.removeEventListener("pointermove", this.onPointerMove, true);
         }
-        if (all) {
+        if (rem_keydown) {
             document.removeEventListener("keydown", this.marqueeCommand, true);
         }
         this._visible = false;
-- 
cgit v1.2.3-70-g09d2


From 6259738cf77c2749f556e6d57addae8dac1b32d2 Mon Sep 17 00:00:00 2001
From: ab <abdullah_ahmed@brown.edu>
Date: Thu, 20 Jun 2019 11:17:22 -0400
Subject: moving div

---
 src/client/util/TooltipTextMenu.scss                            | 7 ++++---
 src/client/util/TooltipTextMenu.tsx                             | 6 +++---
 src/client/views/MainOverlayTextBox.scss                        | 2 +-
 src/client/views/MainOverlayTextBox.tsx                         | 7 +++++--
 src/client/views/collections/collectionFreeForm/MarqueeView.tsx | 2 +-
 src/client/views/nodes/FormattedTextBox.scss                    | 2 +-
 src/client/views/nodes/FormattedTextBox.tsx                     | 2 ++
 7 files changed, 17 insertions(+), 11 deletions(-)

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

diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss
index 1867a697a..456953fb6 100644
--- a/src/client/util/TooltipTextMenu.scss
+++ b/src/client/util/TooltipTextMenu.scss
@@ -233,16 +233,17 @@
   }
 
 .tooltipMenu {
-    // position: absolute;
+    position: absolute;
     // z-index: 50;
     background: #121721;
     border: 1px solid silver;
     border-radius: 15px;
+    //height: 60px;
     //padding: 2px 10px;
-    //margin-bottom: 100px;
+    //margin-top: 100px;
     //-webkit-transform: translateX(-50%);
     //transform: translateX(-50%);
-    //transform: translateY(-50%);
+    transform: translateY(-85px);
     pointer-events: all;
     // height: 100;
     // width:250;
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 06862a79a..641514af9 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -139,9 +139,9 @@ export class TooltipTextMenu {
 
         this.update(view, undefined);
 
-        if (view.dom.parentNode !== null) {
-            view.dom.parentNode.insertBefore(this.tooltip, view.dom);
-        }
+        view.dom.parentNode!.parentNode!.insertBefore(this.tooltip, view.dom.parentNode);
+
+        //console.log("hi");
     }
 
     //label of dropdown will change to given label
diff --git a/src/client/views/MainOverlayTextBox.scss b/src/client/views/MainOverlayTextBox.scss
index f6a746e63..fd6ca3471 100644
--- a/src/client/views/MainOverlayTextBox.scss
+++ b/src/client/views/MainOverlayTextBox.scss
@@ -1,7 +1,7 @@
 @import "globalCssVariables";
 .mainOverlayTextBox-textInput {
     background-color: rgba(248, 6, 6, 0.001);
-    width: 200px;
+    width: 400px;
     height: 200px;
     position:absolute;
     overflow: visible;
diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx
index b4ad5f4d7..f1c4b9708 100644
--- a/src/client/views/MainOverlayTextBox.tsx
+++ b/src/client/views/MainOverlayTextBox.tsx
@@ -24,6 +24,7 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
     private _textHideOnLeave?: boolean;
     private _textTargetDiv: HTMLDivElement | undefined;
     private _textProxyDiv: React.RefObject<HTMLDivElement>;
+    private _outerdiv: React.RefObject<HTMLDivElement>;
     private _textBottom: boolean | undefined;
     private _textAutoHeight: boolean | undefined;
     private _textBox: FormattedTextBox | undefined;
@@ -32,6 +33,7 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
     constructor(props: MainOverlayTextBoxProps) {
         super(props);
         this._textProxyDiv = React.createRef();
+        this._outerdiv = React.createRef();
         MainOverlayTextBox.Instance = this;
         reaction(() => FormattedTextBox.InputBoxOverlay,
             (box?: FormattedTextBox) => {
@@ -114,16 +116,17 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
             let s = this._textXf().Scale;
             let location = this._textBottom ? textRect.bottom : textRect.top;
             let hgt = this._textAutoHeight || this._textBottom ? "auto" : this._textTargetDiv.clientHeight;
-            return <div className="mainOverlayTextBox-textInput" style={{ transform: `translate(${textRect.left}px, ${location}px) scale(${1 / s},${1 / s})`, width: "auto", height: "0px" }} >
+            return <div ref={this._outerdiv} className="unscaled_div"><div className="mainOverlayTextBox-textInput" style={{ transform: `translate(${textRect.left}px, ${location}px) scale(${1 / s},${1 / s})`, width: "auto", height: "0px" }} >
                 <div className="mainOverlayTextBox-textInput" onPointerDown={this.textBoxDown} ref={this._textProxyDiv} onScroll={this.textScroll}
                     style={{ width: `${textRect.width * s}px`, height: "0px" }}>
                     <div style={{ height: hgt, width: "100%", position: "absolute", bottom: this._textBottom ? "0px" : undefined }}>
                         <FormattedTextBox color={`${this._textColor}`} fieldKey={this.TextFieldKey} hideOnLeave={this._textHideOnLeave} 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} addDocTab={this.addDocTab} />
+                            ScreenToLocalTransform={this._textXf} PanelWidth={returnZero} PanelHeight={returnZero} focus={emptyFunction} addDocTab={this.addDocTab} outer_div={this._outerdiv} />
                     </div>
                 </div>
+            </div>
             </ div>;
         }
         else return (null);
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index b8bcfcc89..b1e0e62ea 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -95,7 +95,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                 }
             });
         } else if (!e.ctrlKey) {
-            let newBox = Docs.TextDocument({ width: 200, height: 30, x: x, y: y, title: "-typed text-" });
+            let newBox = Docs.TextDocument({ width: 200, height: 50, x: x, y: y, title: "-typed text-" });
             newBox.proto!.autoHeight = true;
             this.props.addLiveTextDocument(newBox);
         }
diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss
index e3e40860c..d3045ae2f 100644
--- a/src/client/views/nodes/FormattedTextBox.scss
+++ b/src/client/views/nodes/FormattedTextBox.scss
@@ -1,7 +1,7 @@
 @import "../globalCssVariables";
 .ProseMirror {
   width: 100%;
-  height: 100;
+  height: 100%;
   min-height: 100%;
   font-family: $serif;
 }
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 223e6df0a..370b24e95 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -45,6 +45,7 @@ export interface FormattedTextBoxProps {
     hideOnLeave?: boolean;
     height?: string;
     color?: string;
+    outer_div?: React.RefObject<HTMLDivElement>;
 }
 
 const richTextSchema = createSchema({
@@ -97,6 +98,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
 
     constructor(props: FieldViewProps) {
         super(props);
+        this.props.outer_div ? console.log("smd") : console.log("bye");
 
         this._ref = React.createRef();
         if (this.props.isOverlay) {
-- 
cgit v1.2.3-70-g09d2


From 7ef1f39703a378df3f1116d046f9b57a4427d64d Mon Sep 17 00:00:00 2001
From: ab <abdullah_ahmed@brown.edu>
Date: Thu, 20 Jun 2019 16:54:16 -0400
Subject: div moved, but z-index is fd

---
 src/client/util/TooltipTextMenu.scss        | 12 +++++++-----
 src/client/util/TooltipTextMenu.tsx         | 13 +++++++++----
 src/client/views/DocumentDecorations.tsx    |  2 +-
 src/client/views/MainOverlayTextBox.scss    |  4 ++++
 src/client/views/MainOverlayTextBox.tsx     |  8 ++++----
 src/client/views/nodes/FormattedTextBox.tsx |  8 ++++++--
 6 files changed, 31 insertions(+), 16 deletions(-)

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

diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss
index 456953fb6..d19ded68f 100644
--- a/src/client/util/TooltipTextMenu.scss
+++ b/src/client/util/TooltipTextMenu.scss
@@ -18,6 +18,7 @@
   .ProseMirror-menuitem {
     margin-right: 3px;
     display: inline-block;
+    z-index: 100000;
   }
   
   .ProseMirror-menuseparator {
@@ -67,7 +68,7 @@
   }
   
   .ProseMirror-menu-dropdown-menu {
-    z-index: 15;
+    z-index: 100000;
     min-width: 6em;
     background: white;
   }
@@ -80,6 +81,7 @@
     cursor: pointer;
     padding: 2px 8px 2px 4px;
     width: auto;
+    z-index: 100000;
   }
   
   .ProseMirror-menu-dropdown-item:hover {
@@ -233,8 +235,8 @@
   }
 
 .tooltipMenu {
-    position: absolute;
-    // z-index: 50;
+    position: relative;
+    z-index: 2000;
     background: #121721;
     border: 1px solid silver;
     border-radius: 15px;
@@ -245,8 +247,8 @@
     //transform: translateX(-50%);
     transform: translateY(-85px);
     pointer-events: all;
-    // height: 100;
-    // width:250;
+    height: 30px;
+    width:500px;
     .ProseMirror-example-setup-style hr {
       padding: 2px 10px;
       border: none;
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 641514af9..06c8bbc1a 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -29,6 +29,7 @@ import { CollectionDockingView } from "../views/collections/CollectionDockingVie
 import { DocumentManager } from "./DocumentManager";
 import { Id } from "../../new_fields/FieldSymbols";
 import { Utils } from "../../Utils";
+import { FormattedTextBoxProps } from "../views/nodes/FormattedTextBox";
 // import { wrap } from "module";
 
 const SVG = "http://www.w3.org/2000/svg";
@@ -42,7 +43,7 @@ export class TooltipTextMenu {
     private fontStyles: MarkType[];
     private fontSizes: MarkType[];
     private listTypes: NodeType[];
-    private editorProps: FieldViewProps;
+    private editorProps: FieldViewProps & FormattedTextBoxProps;
     private state: EditorState;
     private fontSizeToNum: Map<MarkType, number>;
     private fontStylesToName: Map<MarkType, string>;
@@ -58,7 +59,7 @@ export class TooltipTextMenu {
     private fontStyleDom?: Node;
     private listTypeBtnDom?: Node;
 
-    constructor(view: EditorView, editorProps: FieldViewProps) {
+    constructor(view: EditorView, editorProps: FieldViewProps & FormattedTextBoxProps) {
         this.view = view;
         this.state = view.state;
         this.editorProps = editorProps;
@@ -139,9 +140,13 @@ export class TooltipTextMenu {
 
         this.update(view, undefined);
 
-        view.dom.parentNode!.parentNode!.insertBefore(this.tooltip, view.dom.parentNode);
+        //view.dom.parentNode!.parentNode!.insertBefore(this.tooltip, view.dom.parentNode);
 
-        //console.log("hi");
+        // quick and dirty null check
+        const outer_div = this.editorProps.outer_div;
+        outer_div && outer_div(this.tooltip);
+
+        console.log("hi");
     }
 
     //label of dropdown will change to given label
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index d8642d675..3d802964a 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -619,7 +619,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 left: bounds.x - this._resizeBorderWidth / 2,
                 top: bounds.y - this._resizeBorderWidth / 2,
                 pointerEvents: this.Interacting ? "none" : "all",
-                zIndex: SelectionManager.SelectedDocuments().length > 1 ? 1000 : 0,
+                zIndex: SelectionManager.SelectedDocuments().length > 1 ? 900 : 0,
             }} onPointerDown={this.onBackgroundDown} onContextMenu={(e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); }} >
             </div>
             <div className="documentDecorations-container" style={{
diff --git a/src/client/views/MainOverlayTextBox.scss b/src/client/views/MainOverlayTextBox.scss
index fd6ca3471..c294e1b35 100644
--- a/src/client/views/MainOverlayTextBox.scss
+++ b/src/client/views/MainOverlayTextBox.scss
@@ -17,4 +17,8 @@
         top: 0;
         left: 0;
     }
+}
+.unscaled_div{
+    width: 500px;
+    z-index: 10000;
 }
\ No newline at end of file
diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx
index f1c4b9708..4488d1046 100644
--- a/src/client/views/MainOverlayTextBox.tsx
+++ b/src/client/views/MainOverlayTextBox.tsx
@@ -24,16 +24,16 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
     private _textHideOnLeave?: boolean;
     private _textTargetDiv: HTMLDivElement | undefined;
     private _textProxyDiv: React.RefObject<HTMLDivElement>;
-    private _outerdiv: React.RefObject<HTMLDivElement>;
+    private _outerdiv = (dominus: HTMLElement | null) => this._dominus && dominus && dominus.appendChild(this._dominus);
     private _textBottom: boolean | undefined;
     private _textAutoHeight: boolean | undefined;
     private _textBox: FormattedTextBox | undefined;
+    private _dominus?: HTMLElement;
     @observable public TextDoc?: Doc;
 
     constructor(props: MainOverlayTextBoxProps) {
         super(props);
         this._textProxyDiv = React.createRef();
-        this._outerdiv = React.createRef();
         MainOverlayTextBox.Instance = this;
         reaction(() => FormattedTextBox.InputBoxOverlay,
             (box?: FormattedTextBox) => {
@@ -116,14 +116,14 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
             let s = this._textXf().Scale;
             let location = this._textBottom ? textRect.bottom : textRect.top;
             let hgt = this._textAutoHeight || this._textBottom ? "auto" : this._textTargetDiv.clientHeight;
-            return <div ref={this._outerdiv} className="unscaled_div"><div className="mainOverlayTextBox-textInput" style={{ transform: `translate(${textRect.left}px, ${location}px) scale(${1 / s},${1 / s})`, width: "auto", height: "0px" }} >
+            return <div ref={this._outerdiv} className="unscaled_div" style={{ transform: `translate(${textRect.left}px, ${location}px)` }} ><div className="mainOverlayTextBox-textInput" style={{ transform: `scale(${1 / s},${1 / s})`, width: "auto", height: "0px" }} >
                 <div className="mainOverlayTextBox-textInput" onPointerDown={this.textBoxDown} ref={this._textProxyDiv} onScroll={this.textScroll}
                     style={{ width: `${textRect.width * s}px`, height: "0px" }}>
                     <div style={{ height: hgt, width: "100%", position: "absolute", bottom: this._textBottom ? "0px" : undefined }}>
                         <FormattedTextBox color={`${this._textColor}`} fieldKey={this.TextFieldKey} hideOnLeave={this._textHideOnLeave} 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} addDocTab={this.addDocTab} outer_div={this._outerdiv} />
+                            ScreenToLocalTransform={this._textXf} PanelWidth={returnZero} PanelHeight={returnZero} focus={emptyFunction} addDocTab={this.addDocTab} outer_div={(dominus: HTMLElement) => this._dominus = dominus} />
                     </div>
                 </div>
             </div>
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 370b24e95..421267b2a 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -45,7 +45,7 @@ export interface FormattedTextBoxProps {
     hideOnLeave?: boolean;
     height?: string;
     color?: string;
-    outer_div?: React.RefObject<HTMLDivElement>;
+    outer_div?: (domminus: HTMLElement) => void;
 }
 
 const richTextSchema = createSchema({
@@ -61,6 +61,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         return FieldView.LayoutString(FormattedTextBox, fieldStr);
     }
     private _ref: React.RefObject<HTMLDivElement>;
+    private _outerdiv?: (dominus: HTMLElement) => void;
     private _proseRef?: HTMLDivElement;
     private _editorView: Opt<EditorView>;
     private _toolTipTextMenu: TooltipTextMenu | undefined = undefined;
@@ -98,7 +99,10 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
 
     constructor(props: FieldViewProps) {
         super(props);
-        this.props.outer_div ? console.log("smd") : console.log("bye");
+        if (this.props.outer_div) {
+            this._outerdiv = this.props.outer_div;
+            console.log("yay");
+        }
 
         this._ref = React.createRef();
         if (this.props.isOverlay) {
-- 
cgit v1.2.3-70-g09d2


From 30fd6dc9431cd8121a6d761ffdfba5d0dae641a6 Mon Sep 17 00:00:00 2001
From: ab <abdullah_ahmed@brown.edu>
Date: Fri, 21 Jun 2019 13:39:36 -0400
Subject: marks now reflect cursor position, not selection. Still need to
 consider when there is no nodeAfter

---
 src/client/util/TooltipTextMenu.scss     |  2 +-
 src/client/util/TooltipTextMenu.tsx      | 54 +++++++++++++++++++++++++-------
 src/client/views/MainOverlayTextBox.scss |  1 +
 3 files changed, 45 insertions(+), 12 deletions(-)

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

diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss
index d19ded68f..b10573b3e 100644
--- a/src/client/util/TooltipTextMenu.scss
+++ b/src/client/util/TooltipTextMenu.scss
@@ -60,7 +60,6 @@
   }
   
   .ProseMirror-menu-dropdown-menu, .ProseMirror-menu-submenu {
-    position: absolute;
     background: $dark-color;
     color:white;
     border: 1px solid rgb(223, 223, 223);
@@ -71,6 +70,7 @@
     z-index: 100000;
     min-width: 6em;
     background: white;
+    position: absolute;
   }
   
   .linking {
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 06c8bbc1a..c9216199b 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -7,6 +7,7 @@ import { EditorState, Transaction, NodeSelection, TextSelection } from "prosemir
 import { EditorView } from "prosemirror-view";
 import { schema } from "./RichTextSchema";
 import { Schema, NodeType, MarkType, Mark } from "prosemirror-model";
+import { Node as ProsNode } from "prosemirror-model"
 import React = require("react");
 import "./TooltipTextMenu.scss";
 const { toggleMark, setBlockType, wrapIn } = require("prosemirror-commands");
@@ -30,6 +31,7 @@ import { DocumentManager } from "./DocumentManager";
 import { Id } from "../../new_fields/FieldSymbols";
 import { Utils } from "../../Utils";
 import { FormattedTextBoxProps } from "../views/nodes/FormattedTextBox";
+import { text } from "body-parser";
 // import { wrap } from "module";
 
 const SVG = "http://www.w3.org/2000/svg";
@@ -290,7 +292,7 @@ export class TooltipTextMenu {
     }
 
     //will display a remove-list-type button if selection is in list, otherwise will show list type dropdown
-    updateListItemDropdown(label: string, listTypeBtn: Node) {
+    updateListItemDropdown(label: string, listTypeBtn: any) {
         //remove old btn
         if (listTypeBtn) { this.tooltip.removeChild(listTypeBtn); }
 
@@ -516,7 +518,7 @@ export class TooltipTextMenu {
             //return;
         }
 
-        let linksInSelection = this.activeMarksOnSelection([schema.marks.link]);
+        //let linksInSelection = this.activeMarksOnSelection([schema.marks.link]);
         // if (linksInSelection.length > 0) {
         //     let attributes = this.getMarksInSelection(this.view.state, [schema.marks.link])[0].attrs;
         //     this.link.href = attributes.href;
@@ -583,17 +585,47 @@ export class TooltipTextMenu {
         let { empty, $cursor, ranges } = this.view.state.selection as TextSelection;
         let state = this.view.state;
         let dispatch = this.view.dispatch;
-
-        let activeMarks = markGroup.filter(mark => {
-            if (dispatch) {
-                let has = false, tr = state.tr;
-                for (let i = 0; !has && i < ranges.length; i++) {
-                    let { $from, $to } = ranges[i];
-                    return state.doc.rangeHasMark($from.pos, $to.pos, mark);
+        let activeMarks: MarkType[];
+        if (!empty) {
+            activeMarks = markGroup.filter(mark => {
+                if (dispatch) {
+                    let has = false, tr = state.tr;
+                    for (let i = 0; !has && i < ranges.length; i++) {
+                        let { $from, $to } = ranges[i];
+                        return state.doc.rangeHasMark($from.pos, $to.pos, mark);
+                    }
                 }
+                return false;
+            });
+        }
+        else {
+            let pos = this.view.state.selection.$from;
+            let ref_node: ProsNode;
+            if (pos.nodeAfter !== null && pos.nodeAfter !== undefined) {
+                ref_node = pos.nodeAfter;
             }
-            return false;
-        });
+            else if (pos.nodeBefore !== null && pos.nodeBefore !== undefined) {
+                ref_node = pos.nodeBefore;
+            }
+            else {
+                return [];
+            }
+            let text_node_type: NodeType;
+            if (ref_node.isText) {
+                text_node_type = ref_node.type;
+            }
+            else {
+                return [];
+            }
+
+            activeMarks = markGroup.filter(mark_type => {
+                if (dispatch) {
+                    let mark = state.schema.mark(mark_type);
+                    return ref_node.marks.includes(mark);
+                }
+                return false;
+            });
+        }
         return activeMarks;
     }
 
diff --git a/src/client/views/MainOverlayTextBox.scss b/src/client/views/MainOverlayTextBox.scss
index c294e1b35..1093ff671 100644
--- a/src/client/views/MainOverlayTextBox.scss
+++ b/src/client/views/MainOverlayTextBox.scss
@@ -21,4 +21,5 @@
 .unscaled_div{
     width: 500px;
     z-index: 10000;
+    position: absolute;
 }
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From 6df09d7d646c16e6469b198e7d270b6a1e45b0c7 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Mon, 24 Jun 2019 13:23:28 -0400
Subject: fixes for templates with annotations and more.

---
 src/client/util/TooltipTextMenu.tsx                |   2 +-
 src/client/views/DocumentDecorations.tsx           |  55 +++++------
 src/client/views/InkingCanvas.tsx                  |   7 +-
 src/client/views/InkingControl.tsx                 |   3 +-
 src/client/views/MainView.tsx                      |   4 +-
 .../views/collections/CollectionBaseView.tsx       |   1 -
 .../views/collections/CollectionTreeView.tsx       | 106 +++++++++------------
 src/client/views/collections/CollectionView.tsx    |  17 +---
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   5 +-
 src/client/views/pdf/PDFViewer.tsx                 |   1 +
 src/new_fields/Doc.ts                              |   2 +-
 11 files changed, 82 insertions(+), 121 deletions(-)

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

diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index c9216199b..048fb7133 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -222,7 +222,7 @@ export class TooltipTextMenu {
                                 if (DocumentManager.Instance.getDocumentView(f)) {
                                     DocumentManager.Instance.getDocumentView(f)!.props.focus(f);
                                 }
-                                else if (CollectionDockingView.Instance) CollectionDockingView.Instance.AddRightSplit(f);
+                                else if (CollectionDockingView.Instance) CollectionDockingView.Instance.AddRightSplit(f, f);
                             }
                         }));
                     }
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 0d5cca9f1..9be5c9cd6 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -24,9 +24,10 @@ import { LinkMenu } from "./nodes/LinkMenu";
 import { TemplateMenu } from "./TemplateMenu";
 import { Template, Templates } from "./Templates";
 import React = require("react");
-import { URLField } from '../../new_fields/URLField';
+import { URLField, ImageField } from '../../new_fields/URLField';
 import { templateLiteral } from 'babel-types';
 import { CollectionViewType } from './collections/CollectionBaseView';
+import { ImageBox } from './nodes/ImageBox';
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -75,41 +76,29 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
             if (text[0] === '#') {
                 this._fieldKey = text.slice(1, text.length);
                 this._title = this.selectionTitle;
-            } else if (text.startsWith(">>>")) {
-                let metaKey = text.slice(3, text.length);
-                let collection = SelectionManager.SelectedDocuments()[0].props.ContainingCollectionView!.props.Document;
-                Doc.GetProto(collection)[metaKey] = new List<Doc>([
-                    Docs.ImageDocument("http://www.cs.brown.edu/~bcz/face.gif", { width: 300, height: 300 }),
-                    Docs.TextDocument({ documentText: "hello world!", width: 300, height: 300 }),
-                ]);
-                let template = Doc.MakeAlias(collection);
+            } else if (text.startsWith(">")) {
+                let metaKey = text.slice(text.startsWith(">>>") ? 3 : text.startsWith(">>") ? 2 : 1, text.length);
+                let field = SelectionManager.SelectedDocuments()[0];
+                let collectionKey = field.props.ContainingCollectionView!.props.fieldKey;
+                let collection = field.props.ContainingCollectionView!.props.Document;
+                let collectionKeyProp = `fieldKey={"${collectionKey}"}`;
+                let collectionAnnotationsKeyProp = `fieldKey={"annotations"}`;
+                let metaKeyProp = `fieldKey={"${metaKey}"}`;
+                let metaAnnotationsKeyProp = `fieldKey={"${metaKey}_annotations"}`;
+                let template = Doc.MakeAlias(field.props.Document);
+                template.proto = collection;
                 template.title = metaKey;
+                template.nativeWidth = Cast(field.nativeWidth, "number");
+                template.nativeHeight = Cast(field.nativeHeight, "number");
                 template.embed = true;
-                template.layout = CollectionView.LayoutString(metaKey);
-                template.viewType = CollectionViewType.Freeform;
-                template.x = 0;
-                template.y = 0;
-                template.width = 300;
-                template.height = 300;
                 template.isTemplate = true;
-                template.templates = new List<string>([Templates.TitleBar(metaKey)]);//`{props.DataDoc.${metaKey}_text}`)]);
-                Doc.AddDocToList(collection, "data", template);
-                SelectionManager.SelectedDocuments().map(dv => dv.props.removeDocument && dv.props.removeDocument(dv.props.Document));
-            } else if (text[0] === ">") {
-                let metaKey = text.slice(1, text.length);
-                let first = SelectionManager.SelectedDocuments()[0].props.Document!;
-                let collection = SelectionManager.SelectedDocuments()[0].props.ContainingCollectionView!.props.Document;
-                Doc.GetProto(collection)[metaKey] = "-empty field-";
-                let template = Doc.MakeAlias(collection);
-                template.title = metaKey;
-                template.layout = FormattedTextBox.LayoutString(metaKey);
-                template.isTemplate = true;
-                template.x = NumCast(first.x);
-                template.y = NumCast(first.y);
-                template.width = first[WidthSym]();
-                template.height = first[HeightSym]();
-                template.templates = new List<string>([Templates.TitleBar(metaKey)]);//`{props.DataDoc.${metaKey}_text}`)]);
-                Doc.AddDocToList(collection, "data", template);
+                template.templates = new List<string>([Templates.TitleBar(metaKey)]);
+                template.layout = StrCast(field.props.Document.layout).replace(collectionKeyProp, metaKeyProp);
+                if (field.props.Document.backgroundLayout) {
+                    template.layout = StrCast(field.props.Document.layout).replace(collectionAnnotationsKeyProp, metaAnnotationsKeyProp);
+                    template.backgroundLayout = StrCast(field.props.Document.backgroundLayout).replace(collectionKeyProp, metaKeyProp);
+                }
+                Doc.AddDocToList(collection, collectionKey, template);
                 SelectionManager.SelectedDocuments().map(dv => dv.props.removeDocument && dv.props.removeDocument(dv.props.Document));
             }
             else {
diff --git a/src/client/views/InkingCanvas.tsx b/src/client/views/InkingCanvas.tsx
index 5d4ea76cd..fd7e5b07d 100644
--- a/src/client/views/InkingCanvas.tsx
+++ b/src/client/views/InkingCanvas.tsx
@@ -14,6 +14,7 @@ import { Cast, PromiseValue, NumCast } from "../../new_fields/Types";
 interface InkCanvasProps {
     getScreenTransform: () => Transform;
     Document: Doc;
+    inkFieldKey: string;
     children: () => JSX.Element[];
 }
 
@@ -40,7 +41,7 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
     }
 
     componentDidMount() {
-        PromiseValue(Cast(this.props.Document.ink, InkField)).then(ink => runInAction(() => {
+        PromiseValue(Cast(this.props.Document[this.props.inkFieldKey], InkField)).then(ink => runInAction(() => {
             if (ink) {
                 let bounds = Array.from(ink.inkData).reduce(([mix, max, miy, may], [id, strokeData]) =>
                     strokeData.pathData.reduce(([mix, max, miy, may], p) =>
@@ -55,12 +56,12 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
 
     @computed
     get inkData(): Map<string, StrokeData> {
-        let map = Cast(this.props.Document.ink, InkField);
+        let map = Cast(this.props.Document[this.props.inkFieldKey], InkField);
         return !map ? new Map : new Map(map.inkData);
     }
 
     set inkData(value: Map<string, StrokeData>) {
-        Doc.GetProto(this.props.Document).ink = new InkField(value);
+        Doc.GetProto(this.props.Document)[this.props.inkFieldKey] = new InkField(value);
     }
 
     @action
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index 0837e07a9..6cde73933 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -8,6 +8,7 @@ import { faPen, faHighlighter, faEraser, faBan } from '@fortawesome/free-solid-s
 import { SelectionManager } from "../util/SelectionManager";
 import { InkTool } from "../../new_fields/InkField";
 import { Doc } from "../../new_fields/Doc";
+import { InkingCanvas } from "./InkingCanvas";
 
 library.add(faPen, faHighlighter, faEraser, faBan);
 
@@ -39,7 +40,7 @@ export class InkingControl extends React.Component {
     @action
     switchColor = (color: ColorResult): void => {
         this._selectedColor = color.hex + (color.rgb.a !== undefined ? this.decimalToHexString(Math.round(color.rgb.a * 255)) : "ff");
-        SelectionManager.SelectedDocuments().forEach(doc => Doc.GetProto(doc.props.Document).backgroundColor = this._selectedColor);
+        if (InkingControl.Instance.selectedTool === InkTool.None) SelectionManager.SelectedDocuments().forEach(doc => Doc.GetProto(doc.props.Document).backgroundColor = this._selectedColor);
     }
 
     @action
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 8198b88d2..a72f25b99 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -144,7 +144,7 @@ export class MainView extends React.Component {
         const list = Cast(CurrentUserUtils.UserDocument.data, listSpec(Doc));
         if (list) {
             let freeformDoc = Docs.FreeformDocument([], { x: 0, y: 400, width: this.pwidth * .7, height: this.pheight, title: `WS collection ${list.length + 1}` });
-            var dockingLayout = { content: [{ type: 'row', content: [CollectionDockingView.makeDocumentConfig(CurrentUserUtils.UserDocument, 150), CollectionDockingView.makeDocumentConfig(freeformDoc, 600)] }] };
+            var dockingLayout = { content: [{ type: 'row', content: [CollectionDockingView.makeDocumentConfig(CurrentUserUtils.UserDocument, CurrentUserUtils.UserDocument, 150), CollectionDockingView.makeDocumentConfig(freeformDoc, freeformDoc, 600)] }] };
             let mainDoc = Docs.DockDocument([CurrentUserUtils.UserDocument, freeformDoc], JSON.stringify(dockingLayout), { title: `Workspace ${list.length + 1}` }, id);
             list.push(mainDoc);
             // bcz: strangely, we need a timeout to prevent exceptions/issues initializing GoldenLayout (the rendering engine for Main Container)
@@ -177,7 +177,7 @@ export class MainView extends React.Component {
 
     openNotifsCol = () => {
         if (this._notifsCol && CollectionDockingView.Instance) {
-            CollectionDockingView.Instance.AddRightSplit(this._notifsCol);
+            CollectionDockingView.Instance.AddRightSplit(this._notifsCol, this._notifsCol);
         }
     }
 
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index 75bdf755c..79a9f3be0 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -100,7 +100,6 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
         }
         return false;
     }
-    @computed get isAnnotationOverlay() { return this.props.fieldKey === "annotations"; }
 
     @action.bound
     addDocument(doc: Doc, allowDuplicates: boolean = false): boolean {
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index b3f1b1c88..f5f323269 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -28,7 +28,6 @@ import React = require("react");
 import { FormattedTextBox } from '../nodes/FormattedTextBox';
 import { ImageField } from '../../../new_fields/URLField';
 import { ImageBox } from '../nodes/ImageBox';
-import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
 import { CollectionView } from './CollectionView';
 
 
@@ -71,11 +70,23 @@ class TreeView extends React.Component<TreeViewProps> {
     @observable _collapsed: boolean = true;
 
     @computed get fieldKey() {
+        let keys = Array.from(Object.keys(this.dataDoc));
+        if (this.dataDoc.proto instanceof Doc) {
+            keys.push(...Array.from(Object.keys(this.dataDoc.proto)));
+            while (keys.indexOf("proto") !== -1) keys.splice(keys.indexOf("proto"), 1);
+        }
+        let keyList: string[] = [];
+        keys.map(key => {
+            let docList = Cast(this.dataDoc[key], listSpec(Doc));
+            if (docList && docList.length > 0) {
+                keyList.push(key);
+            }
+        });
         let layout = StrCast(this.props.document.layout);
         if (layout.indexOf("fieldKey={\"") !== -1) {
             return layout.split("fieldKey={\"")[1].split("\"")[0];
         }
-        return "data";
+        return keyList.length ? keyList[0] : "data";
     }
 
     @computed get dataDoc() { return (BoolCast(this.props.document.isTemplate) ? this.props.dataDoc : this.props.document); }
@@ -163,63 +174,39 @@ class TreeView extends React.Component<TreeViewProps> {
         SetValue={(value: string) => {
             let res = (Doc.GetProto(this.dataDoc)[key] = value) ? true : true;
 
-            if (value.startsWith(">>>")) {
-                let metaKey = value.slice(3, value.length);
+            if (value.startsWith(">")) {
+                let metaKey = value.slice(value.startsWith(">>>") ? 3 : value.startsWith(">>") ? 2 : 1, value.length);
                 let collection = this.props.containingCollection;
-                Doc.GetProto(collection)[metaKey] = new List<Doc>([
-                    Docs.ImageDocument("http://www.cs.brown.edu/~bcz/face.gif", { width: 300, height: 300 }),
-                    Docs.TextDocument({ documentText: "hello world!", width: 300, height: 300 }),
-                ]);
                 let template = Doc.MakeAlias(collection);
                 template.title = metaKey;
                 template.embed = true;
-                template.layout = CollectionView.LayoutString(metaKey);
-                template.viewType = CollectionViewType.Freeform;
                 template.x = 0;
                 template.y = 0;
                 template.width = 300;
                 template.height = 300;
                 template.isTemplate = true;
                 template.templates = new List<string>([Templates.TitleBar(metaKey)]);//`{props.DataDoc.${metaKey}_text}`)]);
-                Doc.AddDocToList(collection, "data", template);
-                this.delete();
-            } else
-                if (value.startsWith(">>")) {
-                    let metaKey = value.slice(2, value.length);
-                    let collection = this.props.containingCollection;
+                if (value.startsWith(">>>")) { // Collection
+                    Doc.GetProto(collection)[metaKey] = new List<Doc>([
+                        Docs.ImageDocument("http://www.cs.brown.edu/~bcz/face.gif", { width: 300, height: 300 }),
+                        Docs.TextDocument({ documentText: "hello world!", width: 300, height: 300 }),
+                    ]);
+                    template.layout = CollectionView.LayoutString(metaKey);
+                    template.viewType = CollectionViewType.Freeform;
+                } else if (value.startsWith(">>")) { // Image
                     Doc.GetProto(collection)[metaKey] = new ImageField("http://www.cs.brown.edu/~bcz/face.gif");
-                    let template = Doc.MakeAlias(collection);
-                    template.title = metaKey;
-                    template.embed = true;
                     template.layout = ImageBox.LayoutString(metaKey);
-                    template.x = 0;
-                    template.y = 0;
                     template.nativeWidth = 300;
                     template.nativeHeight = 300;
-                    template.width = 300;
-                    template.height = 300;
-                    template.isTemplate = true;
-                    template.templates = new List<string>([Templates.TitleBar(metaKey)]);//`{props.DataDoc.${metaKey}_text}`)]);
-                    Doc.AddDocToList(collection, "data", template);
-                    this.delete();
-                } else
-                    if (value.startsWith(">")) {
-                        let metaKey = value.slice(1, value.length);
-                        let collection = this.props.containingCollection;
-                        Doc.GetProto(collection)[metaKey] = "-empty field-";
-                        let template = Doc.MakeAlias(collection);
-                        template.title = metaKey;
-                        template.embed = true;
-                        template.layout = FormattedTextBox.LayoutString(metaKey);
-                        template.x = 0;
-                        template.y = 0;
-                        template.width = 100;
-                        template.height = 50;
-                        template.isTemplate = true;
-                        template.templates = new List<string>([Templates.TitleBar(metaKey)]);//`{props.DataDoc.${metaKey}_text}`)]);
-                        Doc.AddDocToList(collection, "data", template);
-                        this.delete();
-                    }
+                } else if (value.startsWith(">")) {  // Text
+                    Doc.GetProto(collection)[metaKey] = "-empty field-";
+                    template.layout = FormattedTextBox.LayoutString(metaKey);
+                    template.width = 100;
+                    template.height = 50;
+                }
+                Doc.AddDocToList(collection, "data", template);
+                this.delete();
+            }
 
             return res;
         }}
@@ -238,14 +225,8 @@ class TreeView extends React.Component<TreeViewProps> {
             keys.push(...Array.from(Object.keys(this.dataDoc.proto)));
             while (keys.indexOf("proto") !== -1) keys.splice(keys.indexOf("proto"), 1);
         }
-        let keyList: string[] = [];
-        keys.map(key => {
-            let docList = Cast(this.dataDoc[key], listSpec(Doc));
-            let doc = Cast(this.dataDoc[key], Doc);
-            if (doc instanceof Doc || docList) {
-                keyList.push(key);
-            }
-        });
+        let keyList: string[] = keys.reduce((l, key) => Cast(this.dataDoc[key], listSpec(Doc)) ? [...l, key] : l, [] as string[]);
+        keys.map(key => Cast(this.dataDoc[key], Doc) instanceof Doc && keyList.push(key));
         if (keyList.indexOf(this.fieldKey) !== -1) {
             keyList.splice(keyList.indexOf(this.fieldKey), 1);
         }
@@ -291,16 +272,16 @@ class TreeView extends React.Component<TreeViewProps> {
     onWorkspaceContextMenu = (e: React.MouseEvent): void => {
         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.dataDoc)) });
-            ContextMenu.Instance.addItem({ description: "Open Fields", event: () => { let kvp = Docs.KVPDocument(this.dataDoc, { width: 300, height: 300 }); this.props.addDocTab(kvp, kvp, "onRight"); }, icon: "layer-group" });
+            ContextMenu.Instance.addItem({ description: "Open Fields", event: () => { let kvp = Docs.KVPDocument(this.props.document, { width: 300, height: 300 }); this.props.addDocTab(kvp, kvp, "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, this.props.document, "inTab"), icon: "folder" });
-                ContextMenu.Instance.addItem({ description: "Open Right", event: () => this.props.addDocTab(this.props.document, this.props.document, "onRight"), icon: "caret-square-right" });
+                ContextMenu.Instance.addItem({ description: "Open Tab", event: () => this.props.addDocTab(this.props.document, this.dataDoc, "inTab"), icon: "folder" });
+                ContextMenu.Instance.addItem({ description: "Open Right", event: () => this.props.addDocTab(this.props.document, this.dataDoc, "onRight"), icon: "caret-square-right" });
                 if (DocumentManager.Instance.getDocumentViews(this.dataDoc).length) {
                     ContextMenu.Instance.addItem({ description: "Focus", event: () => DocumentManager.Instance.getDocumentViews(this.dataDoc).map(view => view.props.focus(this.props.document)) });
                 }
-                ContextMenu.Instance.addItem({ description: "Delete Item", event: undoBatch(() => this.props.deleteDoc(this.dataDoc)) });
+                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.dataDoc)) });
+                ContextMenu.Instance.addItem({ description: "Delete Workspace", event: undoBatch(() => this.props.deleteDoc(this.props.document)) });
             }
             ContextMenu.Instance.displayMenu(e.pageX > 156 ? e.pageX - 156 : 0, e.pageY - 15);
             e.stopPropagation();
@@ -349,14 +330,14 @@ class TreeView extends React.Component<TreeViewProps> {
         if (!this._collapsed) {
             if (!this.props.document.embed) {
                 contentElement = <ul key={this._chosenKey + "more"}>
-                    {TreeView.GetChildElements(doc instanceof Doc ? [doc] : DocListCast(docList), this.props.treeViewId, this.props.document, this.props.dataDoc, this._chosenKey, addDoc, remDoc, this.move,
+                    {TreeView.GetChildElements(doc instanceof Doc ? [doc] : DocListCast(docList), this.props.treeViewId, this.props.document, this.dataDoc, this._chosenKey, addDoc, remDoc, this.move,
                         this.props.dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active, this.props.panelWidth)}
                 </ul >;
             } else {
                 contentElement = <div ref={this._dref} style={{ display: "inline-block", height: this.props.panelHeight() }} key={this.props.document[Id]}>
                     <CollectionSchemaPreview
                         Document={this.props.document}
-                        DataDocument={this.props.dataDoc}
+                        DataDocument={this.dataDoc}
                         width={docWidth}
                         height={this.props.panelHeight}
                         getTransform={this.docTransform}
@@ -400,7 +381,7 @@ class TreeView extends React.Component<TreeViewProps> {
         active: () => boolean,
         panelWidth: () => number,
     ) {
-        let docList = docs.filter(child => !child.excludeFromLibrary && (key !== this.fieldKey || !child.isMinimized));
+        let docList = docs.filter(child => !child.excludeFromLibrary);
         let rowWidth = () => panelWidth() - 20;
         return docList.map((child, i) => {
             let indent = i === 0 ? undefined : () => {
@@ -475,6 +456,7 @@ export class CollectionTreeView extends CollectionSubView(Document) {
 
     outerXf = () => Utils.GetScreenTransform(this._mainEle!);
     onTreeDrop = (e: React.DragEvent) => this.onDrop(e, {});
+    @computed get dataDoc() { return (BoolCast(this.props.DataDoc.isTemplate) ? this.props.DataDoc : this.props.Document); }
 
     render() {
         let dropAction = StrCast(this.props.Document.dropAction) as dropActionType;
@@ -502,7 +484,7 @@ export class CollectionTreeView extends CollectionSubView(Document) {
                     }} />
                 <ul className="no-indent" style={{ width: "max-content" }} >
                     {
-                        TreeView.GetChildElements(this.childDocs, this.props.Document[Id], this.props.Document, this.props.DataDoc, this.props.fieldKey, addDoc, this.remove,
+                        TreeView.GetChildElements(this.childDocs, this.props.Document[Id], this.props.Document, this.dataDoc, this.props.fieldKey, addDoc, this.remove,
                             moveDoc, dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform, this.outerXf, this.props.active, this.props.PanelWidth)
                     }
                 </ul>
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 872cb3f1c..cc097f371 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -2,8 +2,10 @@ import { library } from '@fortawesome/fontawesome-svg-core';
 import { faProjectDiagram, faSignature, faSquare, faTh, faThList, faTree } from '@fortawesome/free-solid-svg-icons';
 import { observer } from "mobx-react";
 import * as React from 'react';
+import { Doc } from '../../../new_fields/Doc';
 import { Id } from '../../../new_fields/FieldSymbols';
 import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
+import { Docs } from '../../documents/Documents';
 import { undoBatch } from '../../util/UndoManager';
 import { ContextMenu } from "../ContextMenu";
 import { ContextMenuProps } from '../ContextMenuItem';
@@ -14,11 +16,6 @@ import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormV
 import { CollectionSchemaView } from "./CollectionSchemaView";
 import { CollectionStackingView } from './CollectionStackingView';
 import { CollectionTreeView } from "./CollectionTreeView";
-import { Doc } from '../../../new_fields/Doc';
-import { FormattedTextBox } from '../nodes/FormattedTextBox';
-import { Docs } from '../../documents/Documents';
-import { List } from '../../../new_fields/List';
-import { ImageField } from '../../../new_fields/URLField';
 export const COLLECTION_BORDER_WIDTH = 2;
 
 library.add(faTh);
@@ -62,17 +59,7 @@ export class CollectionView extends React.Component<FieldViewProps> {
             ContextMenu.Instance.addItem({
                 description: "Apply Template", event: undoBatch(() => {
                     let otherdoc = Docs.TextDocument({ width: 100, height: 50, title: "applied template" });
-                    Doc.GetProto(otherdoc).description = "THIS DESCRIPTION IS REALLY IMPORTANT!";
-                    Doc.GetProto(otherdoc).summary = "THIS SUMMARY IS MEANINGFUL!";
-                    Doc.GetProto(otherdoc).photo = new ImageField("http://www.cs.brown.edu/~bcz/snowbeast.JPG");
                     Doc.GetProto(otherdoc).layout = Doc.MakeDelegate(this.props.Document);
-                    Doc.GetProto(otherdoc).publication = new List<Doc>([
-                        Docs.TextDocument({ documentText: "hello world!", width: 300, height: 300 }),
-                        Docs.ImageDocument("http://www.cs.brown.edu/~bcz/face.gif", { width: 300, height: 300 }),
-                        Docs.ImageDocument("http://www.cs.brown.edu/~bcz/face.gif", { width: 300, height: 300 }),
-                        Docs.ImageDocument("http://www.cs.brown.edu/~bcz/face.gif", { width: 300, height: 300 }),
-                        Docs.TextDocument({ documentText: "hello world!", width: 300, height: 300 }),
-                    ]);
                     this.props.addDocTab && this.props.addDocTab(otherdoc, otherdoc, "onRight");
                 }), icon: "project-diagram"
             });
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 71964ef82..c6f003a81 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -370,7 +370,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     }
 
     private childViews = () => [
-        <CollectionFreeFormBackgroundView key="backgroundView" {...this.props} {...this.getDocumentViewProps(this.props.Document)} />,
+        <CollectionFreeFormBackgroundView key="backgroundView" {...this.props} {...this.getDocumentViewProps(this.props.Document)} DataDoc={this.props.DataDoc} />,
         ...this.views
     ]
     render() {
@@ -387,7 +387,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                         easing={easing} zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}>
 
                         <CollectionFreeFormLinksView {...this.props} key="freeformLinks">
-                            <InkingCanvas getScreenTransform={this.getTransform} Document={this.props.Document} >
+                            <InkingCanvas getScreenTransform={this.getTransform} Document={this.props.DataDoc} inkFieldKey={this.props.fieldKey + "_ink"} >
                                 {this.childViews}
                             </InkingCanvas>
                         </CollectionFreeFormLinksView>
@@ -414,6 +414,7 @@ class CollectionFreeFormOverlayView extends React.Component<DocumentViewProps &
 @observer
 class CollectionFreeFormBackgroundView extends React.Component<DocumentViewProps & { isSelected: () => boolean }> {
     @computed get backgroundView() {
+        let props = this.props;
         return (<DocumentContentsView {...this.props} layoutKey={"backgroundLayout"}
             isTopMost={this.props.isTopMost} isSelected={this.props.isSelected} select={emptyFunction} />);
     }
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 7000352e7..6adead626 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -203,6 +203,7 @@ class Viewer extends React.Component<IViewerProps> {
             this._isPage[page] = "page";
             this._visibleElements[page] = (
                 <Page
+                    size={this._pageSizes[page]}
                     pdf={this.props.pdf}
                     page={page}
                     numPages={this.props.pdf.numPages}
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 27b3473f8..0a1964dea 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -215,7 +215,7 @@ export namespace Doc {
 
     // gets the document's prototype or returns the document if it is a prototype
     export function GetProto(doc: Doc) {
-        return Doc.GetT(doc, "isPrototype", "boolean", true) ? doc : (doc.proto || doc);
+        return Doc.GetT(doc, "isPrototype", "boolean", true) || doc.isTemplate ? doc : (doc.proto || doc);
     }
 
     export function allKeys(doc: Doc): string[] {
-- 
cgit v1.2.3-70-g09d2


From 4270bd26e1b28f429ca11e43504b1a39a0a77baa Mon Sep 17 00:00:00 2001
From: ab <abdullah_ahmed@brown.edu>
Date: Mon, 24 Jun 2019 16:11:29 -0400
Subject: idrk

---
 src/client/util/RichTextSchema.tsx  |  33 ++-----
 src/client/util/TooltipTextMenu.tsx | 168 ++++++++++++++++++++++--------------
 2 files changed, 110 insertions(+), 91 deletions(-)

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

diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index f3f6655af..936dcbfd6 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -91,8 +91,8 @@ export const nodes: { [index: string]: NodeSpec } = {
         attrs: {
             visibility: { default: false },
             text: { default: undefined },
-            oldtextslice: { default: undefined },
-            oldtextlen: { default: 0 }
+            textslice: { default: undefined },
+            textlen: { default: 0 }
 
         },
         group: "inline",
@@ -495,31 +495,26 @@ export class SummarizedView {
         this._collapsed = document.createElement("span");
         this._collapsed.textContent = "㊉";
         this._collapsed.style.opacity = "0.5";
-        // this._collapsed.style.background = "yellow";
         this._collapsed.style.position = "relative";
         this._collapsed.style.width = "40px";
         this._collapsed.style.height = "20px";
         let self = this;
         this._view = view;
         this._collapsed.onpointerdown = function (e: any) {
-            console.log("star pressed!");
             if (node.attrs.visibility) {
                 node.attrs.visibility = !node.attrs.visibility;
-                console.log("content is visible");
                 let y = getPos();
                 let { from, to } = self.updateSummarizedText(y + 1, view.state.schema.marks.highlight);
                 let length = to - from;
                 let newSelection = TextSelection.create(view.state.doc, y + 1, y + 1 + length);
+                // update attrs of node
                 node.attrs.text = newSelection.content();
                 view.dispatch(view.state.tr.setSelection(newSelection).deleteSelection(view.state, () => { }));
                 self._collapsed.textContent = "㊉";
             } else {
                 node.attrs.visibility = !node.attrs.visibility;
-                console.log("content is invisible");
                 let y = getPos();
-                console.log(y);
                 let mark = view.state.schema.mark(view.state.schema.marks.highlight);
-                console.log("PASTING " + node.attrs.text.toString());
                 view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.doc, y + 1, y + 1)));
                 const from = view.state.selection.from;
                 let size = node.attrs.text.size;
@@ -538,39 +533,21 @@ export class SummarizedView {
     updateSummarizedText(start?: any, mark?: any) {
         let $start = this._view.state.doc.resolve(start);
         let endPos = start;
-        //let first_startPos = $start.start(), endPos = first_startPos;
-        // let startIndex = $start.index(), endIndex = $start.indexAfter();
-        // let nodeAfter = $start.nodeAfter;
-        // while (startIndex > 0 && mark.isInSet($start.parent.child(startIndex - 1).marks)) startIndex--;
-        // while (endIndex < $start.parent.childCount && mark.isInSet($start.parent.child(endIndex).marks)) endIndex++;
-        // let startPos = $start.start(), endPos = startPos;
-        // for (let i = 0; i < endIndex; i++) {
-        //     let size = $start.parent.child(i).nodeSize;
-        //     console.log($start.parent.child(i).childCount);
-        //     if (i < startIndex) startPos += size;
-        //     endPos += size;
-        // }
+
         let _mark = this._view.state.schema.mark(this._view.state.schema.marks.highlight);
-        // first_startPos = start;
-        // endPos = first_startPos;
         let visited = new Set();
         for (let i: number = start + 1; i < this._view.state.doc.nodeSize - 1; i++) {
-            console.log("ITER:", i);
             let skip = false;
             this._view.state.doc.nodesBetween(start, i, (node: Node, pos: number, parent: Node, index: number) => {
                 if (node.isLeaf && !visited.has(node) && !skip) {
                     if (node.marks.includes(_mark)) {
                         visited.add(node);
-                        //endPos += node.nodeSize + 1;
                         endPos = i + node.nodeSize - 1;
-                        console.log("node contains mark!");
                     }
                     else skip = true;
                 }
             });
         }
-        console.log(start);
-        console.log(endPos);
         return { from: start, to: endPos };
     }
 
@@ -592,7 +569,7 @@ const fromJson = schema.nodeFromJSON;
 schema.nodeFromJSON = (json: any) => {
     let node = fromJson(json);
     if (json.type === "star") {
-        node.attrs.oldtext = Slice.fromJSON(schema, node.attrs.oldtextslice);
+        node.attrs.text = Slice.fromJSON(schema, node.attrs.textslice);
     }
     return node;
 };
\ No newline at end of file
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index c9216199b..c42d5fab8 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -6,7 +6,7 @@ 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, Mark } from "prosemirror-model";
+import { Schema, NodeType, MarkType, Mark, ResolvedPos } from "prosemirror-model";
 import { Node as ProsNode } from "prosemirror-model"
 import React = require("react");
 import "./TooltipTextMenu.scss";
@@ -32,6 +32,7 @@ import { Id } from "../../new_fields/FieldSymbols";
 import { Utils } from "../../Utils";
 import { FormattedTextBoxProps } from "../views/nodes/FormattedTextBox";
 import { text } from "body-parser";
+import { type } from "os";
 // import { wrap } from "module";
 
 const SVG = "http://www.w3.org/2000/svg";
@@ -61,6 +62,8 @@ export class TooltipTextMenu {
     private fontStyleDom?: Node;
     private listTypeBtnDom?: Node;
 
+    private _activeMarks: Mark[] = [];
+
     constructor(view: EditorView, editorProps: FieldViewProps & FormattedTextBoxProps) {
         this.view = view;
         this.state = view.state;
@@ -123,6 +126,7 @@ export class TooltipTextMenu {
         this.fontSizeToNum.set(schema.marks.p32, 32);
         this.fontSizeToNum.set(schema.marks.p48, 48);
         this.fontSizeToNum.set(schema.marks.p72, 72);
+        //this.fontSizeToNum.set(schema.marks.pFontSize,schema.marks.pFontSize.)
         this.fontSizes = Array.from(this.fontSizeToNum.keys());
 
         //list types
@@ -282,8 +286,20 @@ export class TooltipTextMenu {
     }
 
     insertStar(state: EditorState<any>, dispatch: any) {
-        console.log("creating star...");
-        let newNode = schema.nodes.star.create({ visibility: false, text: state.selection.content(), oldtextslice: state.selection.content().toJSON(), oldtextlen: state.selection.to - state.selection.from });
+        if (state.selection.empty) {
+            let mark = state.schema.mark(state.schema.marks.highlight)
+            if (this._activeMarks.includes(mark)) {
+                const ind = this._activeMarks.indexOf(mark);
+                this._activeMarks.splice(ind, 1);
+            }
+            else {
+                this._activeMarks.push(mark);
+            }
+            dispatch(state.tr.insertText(" ").setStoredMarks(this._activeMarks));
+            //dispatch(state.tr.setStoredMarks(this._activeMarks));
+            return true;
+        }
+        let newNode = schema.nodes.star.create({ visibility: false, text: state.selection.content(), textslice: state.selection.content().toJSON(), textlen: state.selection.to - state.selection.from });
         if (dispatch) {
             //console.log(newNode.attrs.text.toString());
             dispatch(state.tr.replaceSelectionWith(newNode));
@@ -315,7 +331,7 @@ export class TooltipTextMenu {
     }
 
     //for a specific grouping of marks (passed in), remove all and apply the passed-in one to the selected text
-    changeToMarkInGroup(markType: MarkType, view: EditorView, fontMarks: MarkType[]) {
+    changeToMarkInGroup = (markType: MarkType, view: EditorView, fontMarks: MarkType[]) => {
         let { empty, $cursor, ranges } = view.state.selection as TextSelection;
         let state = view.state;
         let dispatch = view.dispatch;
@@ -341,7 +357,18 @@ export class TooltipTextMenu {
                     }
                 }
             }
-        }); //actually apply font
+        });
+        // fontsize
+        // if (markType.name[0] === 'p') {
+        //     let size = this.fontSizeToNum.get(markType);
+        //     if (size) { this.updateFontSizeDropdown(String(size) + " pt"); }
+        // }
+        // else {
+        //     let fontName = this.fontStylesToName.get(markType);
+        //     if (fontName) { this.updateFontStyleDropdown(fontName); }
+        // }
+        //this.update(view, undefined);
+        //actually apply font
         return toggleMark(markType)(view.state, view.dispatch, view);
     }
 
@@ -529,53 +556,46 @@ export class TooltipTextMenu {
         // Otherwise, reposition it and update its content
         //this.tooltip.style.display = "";
         let { from, to } = state.selection;
-        let start = view.coordsAtPos(from), end = view.coordsAtPos(to);
-        // The box in which the tooltip is positioned, to use as base
-        //let box = this.tooltip.offsetParent!.getBoundingClientRect();
-        // Find a center-ish x position from the selection endpoints (when
-        // crossing lines, end may be more to the left)
-        let left = Math.max((start.left + end.left) / 2, start.left + 3);
-        //this.tooltip.style.left = (left - box.left) * this.editorProps.ScreenToLocalTransform().Scale + "px";
-        let width = Math.abs(start.left - end.left) / 2 * this.editorProps.ScreenToLocalTransform().Scale;
-        let mid = Math.min(start.left, end.left) + width;
-
-        //this.tooltip.style.width = 225 + "px";
-        // this.tooltip.style.bottom = (box.bottom - start.top) * this.editorProps.ScreenToLocalTransform().Scale + "px";
-        // this.tooltip.style.top = "-100px";
-        //this.tooltip.style.height = "100px";
-
-        // let transform = this.editorProps.ScreenToLocalTransform();
-        // this.tooltip.style.width = `${225 / transform.Scale}px`;
-        // Utils
 
         //UPDATE LIST ITEM DROPDOWN
         this.listTypeBtnDom = this.updateListItemDropdown(":", this.listTypeBtnDom!);
+        //this._activeMarks = [];
 
         //UPDATE FONT STYLE DROPDOWN
         let activeStyles = this.activeMarksOnSelection(this.fontStyles);
-        if (activeStyles.length === 1) {
-            // if we want to update something somewhere with active font name
-            let fontName = this.fontStylesToName.get(activeStyles[0]);
-            if (fontName) { this.updateFontStyleDropdown(fontName); }
-        } else if (activeStyles.length === 0) {
-            //crimson on default
-            this.updateFontStyleDropdown("Crimson Text");
-        } else {
-            this.updateFontStyleDropdown("Various");
+        if (activeStyles !== undefined) {
+            // activeStyles.forEach((markType) => {
+            //     this._activeMarks.push(this.view.state.schema.mark(markType));
+            // });
+            if (activeStyles.length === 1) {
+                // if we want to update something somewhere with active font name
+                let fontName = this.fontStylesToName.get(activeStyles[0]);
+                if (fontName) { this.updateFontStyleDropdown(fontName); }
+            } else if (activeStyles.length === 0) {
+                //crimson on default
+                this.updateFontStyleDropdown("Crimson Text");
+            } else {
+                this.updateFontStyleDropdown("Various");
+            }
         }
 
         //UPDATE FONT SIZE DROPDOWN
         let activeSizes = this.activeMarksOnSelection(this.fontSizes);
-        if (activeSizes.length === 1) { //if there's only one active font size
-            let size = this.fontSizeToNum.get(activeSizes[0]);
-            if (size) { this.updateFontSizeDropdown(String(size) + " pt"); }
-        } else if (activeSizes.length === 0) {
-            //should be 14 on default  
-            this.updateFontSizeDropdown("14 pt");
-        } else { //multiple font sizes selected
-            this.updateFontSizeDropdown("Various");
+        if (activeSizes !== undefined) {
+            if (activeSizes.length === 1) { //if there's only one active font size
+                // activeSizes.forEach((markType) => {
+                //     this._activeMarks.push(this.view.state.schema.mark(markType));
+                // });
+                let size = this.fontSizeToNum.get(activeSizes[0]);
+                if (size) { this.updateFontSizeDropdown(String(size) + " pt"); }
+            } else if (activeSizes.length === 0) {
+                //should be 14 on default  
+                this.updateFontSizeDropdown("14 pt");
+            } else { //multiple font sizes selected
+                this.updateFontSizeDropdown("Various");
+            }
         }
-
+        this.view.dispatch(this.view.state.tr.setStoredMarks(this._activeMarks));
         this.updateLinkMenu();
     }
 
@@ -599,34 +619,56 @@ export class TooltipTextMenu {
             });
         }
         else {
-            let pos = this.view.state.selection.$from;
-            let ref_node: ProsNode;
-            if (pos.nodeAfter !== null && pos.nodeAfter !== undefined) {
-                ref_node = pos.nodeAfter;
-            }
-            else if (pos.nodeBefore !== null && pos.nodeBefore !== undefined) {
-                ref_node = pos.nodeBefore;
-            }
-            else {
-                return [];
-            }
-            let text_node_type: NodeType;
-            if (ref_node.isText) {
-                text_node_type = ref_node.type;
+            const pos = this.view.state.selection.$from;
+            const ref_node: ProsNode = this.reference_node(pos);
+            if (ref_node !== null && ref_node !== this.view.state.doc) {
+                let text_node_type: NodeType;
+                if (ref_node.isText) {
+                    text_node_type = ref_node.type;
+                }
+                else {
+                    return [];
+                }
+
+                this._activeMarks = ref_node.marks;
+
+                activeMarks = markGroup.filter(mark_type => {
+                    if (dispatch) {
+                        let mark = state.schema.mark(mark_type);
+                        return ref_node.marks.includes(mark);
+                    }
+                    return false;
+                });
+                return activeMarks;
             }
             else {
                 return [];
             }
 
-            activeMarks = markGroup.filter(mark_type => {
-                if (dispatch) {
-                    let mark = state.schema.mark(mark_type);
-                    return ref_node.marks.includes(mark);
-                }
-                return false;
-            });
         }
-        return activeMarks;
+    }
+
+    reference_node(pos: ResolvedPos<any>): ProsNode {
+        let ref_node: ProsNode = this.view.state.doc;
+        if (pos.nodeAfter !== null && pos.nodeAfter !== undefined) {
+            ref_node = pos.nodeAfter;
+        }
+        else if (pos.nodeBefore !== null && pos.nodeBefore !== undefined) {
+            ref_node = pos.nodeBefore;
+        }
+        else if (pos.pos > 0) {
+            let skip = false;
+            for (let i: number = pos.pos - 1; i > 0; i--) {
+                this.view.state.doc.nodesBetween(i, pos.pos, (node: ProsNode, pos: number, parent: ProsNode, index: number) => {
+                    if (node.isLeaf && !skip) {
+                        ref_node = node;
+                        skip = true;
+                    }
+
+                });
+            }
+        }
+        return ref_node;
     }
 
     destroy() { this.tooltip.remove(); }
-- 
cgit v1.2.3-70-g09d2


From c2c04325e0f29c01cd5ab0617d17a62183a53611 Mon Sep 17 00:00:00 2001
From: ab <abdullah_ahmed@brown.edu>
Date: Tue, 25 Jun 2019 14:00:42 -0400
Subject: fixed most stuff, still the zooming and focus bugs

---
 src/client/util/RichTextSchema.tsx                 |  1 +
 src/client/util/TooltipTextMenu.tsx                | 54 ++++++++++------------
 .../collections/collectionFreeForm/MarqueeView.tsx |  2 +-
 3 files changed, 26 insertions(+), 31 deletions(-)

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

diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 936dcbfd6..63c879d67 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -509,6 +509,7 @@ export class SummarizedView {
                 let newSelection = TextSelection.create(view.state.doc, y + 1, y + 1 + length);
                 // update attrs of node
                 node.attrs.text = newSelection.content();
+                node.attrs.textslice = newSelection.content().toJSON();
                 view.dispatch(view.state.tr.setSelection(newSelection).deleteSelection(view.state, () => { }));
                 self._collapsed.textContent = "㊉";
             } else {
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index c42d5fab8..d08048e21 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -84,7 +84,7 @@ export class TooltipTextMenu {
             { command: toggleMark(schema.marks.strikethrough), dom: this.icon("S", "strikethrough", "Strikethrough") },
             { command: toggleMark(schema.marks.superscript), dom: this.icon("s", "superscript", "Superscript") },
             { command: toggleMark(schema.marks.subscript), dom: this.icon("s", "subscript", "Subscript") },
-            { command: deleteSelection, dom: this.icon("C", 'collapse', 'Collapse') }
+            { command: toggleMark(schema.marks.highlight), dom: this.icon("H", 'blue', 'Blue') }
             // { command: wrapInList(schema.nodes.bullet_list), dom: this.icon(":", "bullets") },
             // { command: wrapInList(schema.nodes.ordered_list), dom: this.icon("1)", "bullets") },
             // { command: lift, dom: this.icon("<", "lift") },
@@ -144,6 +144,8 @@ export class TooltipTextMenu {
 
         this.tooltip.appendChild(this.createStar().render(this.view).dom);
 
+        this.updateListItemDropdown(":", this.listTypeBtnDom);
+
         this.update(view, undefined);
 
         //view.dom.parentNode!.parentNode!.insertBefore(this.tooltip, view.dom.parentNode);
@@ -166,12 +168,15 @@ export class TooltipTextMenu {
             fontSizeBtns.push(this.dropdownMarkBtn(String(number), "color: black; width: 50px;", mark, this.view, this.changeToMarkInGroup, this.fontSizes));
         });
 
-        if (this.fontSizeDom) { this.tooltip.removeChild(this.fontSizeDom); }
-        this.fontSizeDom = (new Dropdown(cut(fontSizeBtns), {
+        let newfontSizeDom = (new Dropdown(cut(fontSizeBtns), {
             label: label,
             css: "color:black; min-width: 60px; padding-left: 5px; margin-right: 0;"
         }) as MenuItem).render(this.view).dom;
-        this.tooltip.appendChild(this.fontSizeDom);
+        if (this.fontSizeDom) { this.tooltip.replaceChild(newfontSizeDom, this.fontSizeDom); }
+        else {
+            this.tooltip.appendChild(newfontSizeDom);
+        }
+        this.fontSizeDom = newfontSizeDom;
     }
 
     //label of dropdown will change to given label
@@ -185,13 +190,16 @@ export class TooltipTextMenu {
             fontBtns.push(this.dropdownMarkBtn(name, "color: black; font-family: " + name + ", sans-serif; width: 125px;", mark, this.view, this.changeToMarkInGroup, this.fontStyles));
         });
 
-        if (this.fontStyleDom) { this.tooltip.removeChild(this.fontStyleDom); }
-        this.fontStyleDom = (new Dropdown(cut(fontBtns), {
+        let newfontStyleDom = (new Dropdown(cut(fontBtns), {
             label: label,
             css: "color:black; width: 125px; margin-left: -3px; padding-left: 2px;"
         }) as MenuItem).render(this.view).dom;
+        if (this.fontStyleDom) { this.tooltip.replaceChild(newfontStyleDom, this.fontStyleDom); }
+        else {
+            this.tooltip.appendChild(newfontStyleDom);
+        }
+        this.fontStyleDom = newfontStyleDom;
 
-        this.tooltip.appendChild(this.fontStyleDom);
     }
 
     updateLinkMenu() {
@@ -286,19 +294,6 @@ export class TooltipTextMenu {
     }
 
     insertStar(state: EditorState<any>, dispatch: any) {
-        if (state.selection.empty) {
-            let mark = state.schema.mark(state.schema.marks.highlight)
-            if (this._activeMarks.includes(mark)) {
-                const ind = this._activeMarks.indexOf(mark);
-                this._activeMarks.splice(ind, 1);
-            }
-            else {
-                this._activeMarks.push(mark);
-            }
-            dispatch(state.tr.insertText(" ").setStoredMarks(this._activeMarks));
-            //dispatch(state.tr.setStoredMarks(this._activeMarks));
-            return true;
-        }
         let newNode = schema.nodes.star.create({ visibility: false, text: state.selection.content(), textslice: state.selection.content().toJSON(), textlen: state.selection.to - state.selection.from });
         if (dispatch) {
             //console.log(newNode.attrs.text.toString());
@@ -359,15 +354,14 @@ export class TooltipTextMenu {
             }
         });
         // fontsize
-        // if (markType.name[0] === 'p') {
-        //     let size = this.fontSizeToNum.get(markType);
-        //     if (size) { this.updateFontSizeDropdown(String(size) + " pt"); }
-        // }
-        // else {
-        //     let fontName = this.fontStylesToName.get(markType);
-        //     if (fontName) { this.updateFontStyleDropdown(fontName); }
-        // }
-        //this.update(view, undefined);
+        if (markType.name[0] === 'p') {
+            let size = this.fontSizeToNum.get(markType);
+            if (size) { this.updateFontSizeDropdown(String(size) + " pt"); }
+        }
+        else {
+            let fontName = this.fontStylesToName.get(markType);
+            if (fontName) { this.updateFontStyleDropdown(fontName); }
+        }
         //actually apply font
         return toggleMark(markType)(view.state, view.dispatch, view);
     }
@@ -558,7 +552,7 @@ export class TooltipTextMenu {
         let { from, to } = state.selection;
 
         //UPDATE LIST ITEM DROPDOWN
-        this.listTypeBtnDom = this.updateListItemDropdown(":", this.listTypeBtnDom!);
+        //this.listTypeBtnDom = this.updateListItemDropdown(":", this.listTypeBtnDom!);
         //this._activeMarks = [];
 
         //UPDATE FONT STYLE DROPDOWN
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 796ff029c..d2a6ceafa 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -95,7 +95,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                 }
             });
         } else if (!e.ctrlKey) {
-            let newBox = Docs.TextDocument({ width: 200, height: 50, x: x, y: y, title: "-typed text-" });
+            let newBox = Docs.TextDocument({ width: 200, height: 100, x: x, y: y, title: "-typed text-" });
             newBox.proto!.autoHeight = true;
             this.props.addLiveTextDocument(newBox);
         }
-- 
cgit v1.2.3-70-g09d2


From 76e444f3016fa2d550f664520ee27ead3eb39837 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Tue, 25 Jun 2019 14:45:46 -0400
Subject: fixed tooltip appearing.

---
 src/client/util/TooltipTextMenu.tsx     |  4 +++-
 src/client/views/MainOverlayTextBox.tsx | 13 +++++++++----
 2 files changed, 12 insertions(+), 5 deletions(-)

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

diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index c42d5fab8..5ff244b5f 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -671,5 +671,7 @@ export class TooltipTextMenu {
         return ref_node;
     }
 
-    destroy() { this.tooltip.remove(); }
+    destroy() {
+        this.tooltip.remove();
+    }
 }
diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx
index 4488d1046..58946845c 100644
--- a/src/client/views/MainOverlayTextBox.tsx
+++ b/src/client/views/MainOverlayTextBox.tsx
@@ -24,13 +24,18 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
     private _textHideOnLeave?: boolean;
     private _textTargetDiv: HTMLDivElement | undefined;
     private _textProxyDiv: React.RefObject<HTMLDivElement>;
-    private _outerdiv = (dominus: HTMLElement | null) => this._dominus && dominus && dominus.appendChild(this._dominus);
     private _textBottom: boolean | undefined;
     private _textAutoHeight: boolean | undefined;
+    private _setouterdiv = (outerdiv: HTMLElement | null) => { this._outerdiv = outerdiv; this.updateTooltip(); }
+    private _outerdiv: HTMLElement | null = null;
     private _textBox: FormattedTextBox | undefined;
-    private _dominus?: HTMLElement;
+    private _tooltip?: HTMLElement;
     @observable public TextDoc?: Doc;
 
+    updateTooltip = () => {
+        this._outerdiv && this._tooltip && !this._outerdiv.contains(this._tooltip) && this._outerdiv.appendChild(this._tooltip)
+    }
+
     constructor(props: MainOverlayTextBoxProps) {
         super(props);
         this._textProxyDiv = React.createRef();
@@ -116,14 +121,14 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
             let s = this._textXf().Scale;
             let location = this._textBottom ? textRect.bottom : textRect.top;
             let hgt = this._textAutoHeight || this._textBottom ? "auto" : this._textTargetDiv.clientHeight;
-            return <div ref={this._outerdiv} className="unscaled_div" style={{ transform: `translate(${textRect.left}px, ${location}px)` }} ><div className="mainOverlayTextBox-textInput" style={{ transform: `scale(${1 / s},${1 / s})`, width: "auto", height: "0px" }} >
+            return <div ref={this._setouterdiv} className="unscaled_div" style={{ transform: `translate(${textRect.left}px, ${location}px)` }} ><div className="mainOverlayTextBox-textInput" style={{ transform: `scale(${1 / s},${1 / s})`, width: "auto", height: "0px" }} >
                 <div className="mainOverlayTextBox-textInput" onPointerDown={this.textBoxDown} ref={this._textProxyDiv} onScroll={this.textScroll}
                     style={{ width: `${textRect.width * s}px`, height: "0px" }}>
                     <div style={{ height: hgt, width: "100%", position: "absolute", bottom: this._textBottom ? "0px" : undefined }}>
                         <FormattedTextBox color={`${this._textColor}`} fieldKey={this.TextFieldKey} hideOnLeave={this._textHideOnLeave} 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} addDocTab={this.addDocTab} outer_div={(dominus: HTMLElement) => this._dominus = dominus} />
+                            ScreenToLocalTransform={this._textXf} PanelWidth={returnZero} PanelHeight={returnZero} focus={emptyFunction} addDocTab={this.addDocTab} outer_div={(tooltip: HTMLElement) => { this._tooltip = tooltip; this.updateTooltip(); }} />
                     </div>
                 </div>
             </div>
-- 
cgit v1.2.3-70-g09d2


From 4b69ee75fbc98d81eac1bbad0749cf266ccd4c3c Mon Sep 17 00:00:00 2001
From: ab <abdullah_ahmed@brown.edu>
Date: Wed, 26 Jun 2019 13:12:39 -0400
Subject: idk

---
 src/client/util/TooltipTextMenu.tsx         | 7 ++-----
 src/client/views/nodes/FormattedTextBox.tsx | 1 -
 2 files changed, 2 insertions(+), 6 deletions(-)

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

diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index d08048e21..c42a29806 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -153,8 +153,6 @@ export class TooltipTextMenu {
         // quick and dirty null check
         const outer_div = this.editorProps.outer_div;
         outer_div && outer_div(this.tooltip);
-
-        console.log("hi");
     }
 
     //label of dropdown will change to given label
@@ -552,8 +550,6 @@ export class TooltipTextMenu {
         let { from, to } = state.selection;
 
         //UPDATE LIST ITEM DROPDOWN
-        //this.listTypeBtnDom = this.updateListItemDropdown(":", this.listTypeBtnDom!);
-        //this._activeMarks = [];
 
         //UPDATE FONT STYLE DROPDOWN
         let activeStyles = this.activeMarksOnSelection(this.fontStyles);
@@ -606,6 +602,7 @@ export class TooltipTextMenu {
                     let has = false, tr = state.tr;
                     for (let i = 0; !has && i < ranges.length; i++) {
                         let { $from, $to } = ranges[i];
+                        let hasmark: boolean = state.doc.rangeHasMark($from.pos, $to.pos, mark);
                         return state.doc.rangeHasMark($from.pos, $to.pos, mark);
                     }
                 }
@@ -633,13 +630,13 @@ export class TooltipTextMenu {
                     }
                     return false;
                 });
-                return activeMarks;
             }
             else {
                 return [];
             }
 
         }
+        return activeMarks;
     }
 
     reference_node(pos: ResolvedPos<any>): ProsNode {
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 391e42b57..ca2a58cfe 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -101,7 +101,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         super(props);
         if (this.props.outer_div) {
             this._outerdiv = this.props.outer_div;
-            console.log("yay");
         }
 
         this._ref = React.createRef();
-- 
cgit v1.2.3-70-g09d2


From cad1871a1a8860b67eec61df225b7abf99900029 Mon Sep 17 00:00:00 2001
From: ab <abdullah_ahmed@brown.edu>
Date: Thu, 27 Jun 2019 12:37:04 -0400
Subject: keyboard shortcut for summarize

---
 src/client/util/ProsemirrorExampleTransfer.ts      |  4 ++
 src/client/util/TooltipTextMenu.tsx                | 53 +++++++++++++++++++++-
 src/client/views/MainOverlayTextBox.scss           |  2 +-
 src/client/views/MainOverlayTextBox.tsx            |  4 +-
 .../collections/collectionFreeForm/MarqueeView.tsx |  6 +--
 5 files changed, 61 insertions(+), 8 deletions(-)

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

diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts
index 091926d0a..fa9e2e5af 100644
--- a/src/client/util/ProsemirrorExampleTransfer.ts
+++ b/src/client/util/ProsemirrorExampleTransfer.ts
@@ -7,6 +7,8 @@ import { wrapInList, splitListItem, liftListItem, sinkListItem } from "prosemirr
 import { undo, redo } from "prosemirror-history";
 import { undoInputRule } from "prosemirror-inputrules";
 import { Transaction, EditorState } from "prosemirror-state";
+import { TooltipTextMenu } from "./TooltipTextMenu";
+import { Statement } from "../northstar/model/idea/idea";
 
 const mac = typeof navigator !== "undefined" ? /Mac/.test(navigator.platform) : false;
 
@@ -96,5 +98,7 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?:
         });
     }
 
+    bind("Mod-s", TooltipTextMenu.insertStar);
+
     return keys;
 }
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 385c9a2a9..3a72a1ede 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -64,6 +64,8 @@ export class TooltipTextMenu {
 
     private _activeMarks: Mark[] = [];
 
+    private _collapseBtn?: MenuItem;
+
     constructor(view: EditorView, editorProps: FieldViewProps & FormattedTextBoxProps) {
         this.view = view;
         this.state = view.state;
@@ -71,6 +73,10 @@ export class TooltipTextMenu {
         this.tooltip = document.createElement("div");
         this.tooltip.className = "tooltipMenu";
 
+        // this.createCollapse();
+        // if (this._collapseBtn) {
+        //     this.tooltip.appendChild(this._collapseBtn.render(this.view).dom);
+        // }
         //add the div which is the tooltip
         //view.dom.parentNode!.parentNode!.appendChild(this.tooltip);
 
@@ -144,6 +150,8 @@ export class TooltipTextMenu {
 
         this.tooltip.appendChild(this.createStar().render(this.view).dom);
 
+
+
         this.updateListItemDropdown(":", this.listTypeBtnDom);
 
         this.update(view, undefined);
@@ -291,7 +299,7 @@ export class TooltipTextMenu {
         link = node && node.marks.find(m => m.type.name === "link");
     }
 
-    insertStar(state: EditorState<any>, dispatch: any) {
+    public static insertStar(state: EditorState<any>, dispatch: any) {
         let newNode = schema.nodes.star.create({ visibility: false, text: state.selection.content(), textslice: state.selection.content().toJSON(), textlen: state.selection.to - state.selection.from });
         if (dispatch) {
             //console.log(newNode.attrs.text.toString());
@@ -398,12 +406,53 @@ export class TooltipTextMenu {
             class: "summarize",
             execEvent: "",
             run: (state, dispatch, view) => {
-                this.insertStar(state, dispatch);
+                TooltipTextMenu.insertStar(state, dispatch);
             }
 
         });
     }
 
+    createCollapse() {
+        this._collapseBtn = new MenuItem({
+            title: "Collapse",
+            //label: "Collapse",
+            icon: icons.join,
+            execEvent: "",
+            css: "color:white;",
+            class: "summarize",
+            run: (state, dispatch, view) => {
+                this.collapseToolTip();
+            }
+        });
+    }
+
+    collapseToolTip() {
+        if (this._collapseBtn) {
+            if (this._collapseBtn.spec.title === "Collapse") {
+                // const newcollapseBtn = new MenuItem({
+                //     title: "Expand",
+                //     icon: icons.join,
+                //     execEvent: "",
+                //     css: "color:white;",
+                //     class: "summarize",
+                //     run: (state, dispatch, view) => {
+                //         this.collapseToolTip();
+                //     }
+                // });
+                // this.tooltip.replaceChild(newcollapseBtn.render(this.view).dom, this._collapseBtn.render(this.view).dom);
+                // this._collapseBtn = newcollapseBtn;
+                this.tooltip.style.width = "30px";
+                this._collapseBtn.spec.title = "Expand";
+                this._collapseBtn.render(this.view);
+            }
+            else {
+                this._collapseBtn.spec.title = "Collapse";
+                this.tooltip.style.width = "550px";
+                this._collapseBtn.render(this.view);
+            }
+        }
+    }
+
     createLink() {
         let markType = schema.marks.link;
         return new MenuItem({
diff --git a/src/client/views/MainOverlayTextBox.scss b/src/client/views/MainOverlayTextBox.scss
index 1093ff671..5381a63b3 100644
--- a/src/client/views/MainOverlayTextBox.scss
+++ b/src/client/views/MainOverlayTextBox.scss
@@ -19,7 +19,7 @@
     }
 }
 .unscaled_div{
-    width: 500px;
+    // width: 500px;
     z-index: 10000;
     position: absolute;
 }
\ No newline at end of file
diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx
index 58946845c..479713fbd 100644
--- a/src/client/views/MainOverlayTextBox.tsx
+++ b/src/client/views/MainOverlayTextBox.tsx
@@ -26,14 +26,14 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
     private _textProxyDiv: React.RefObject<HTMLDivElement>;
     private _textBottom: boolean | undefined;
     private _textAutoHeight: boolean | undefined;
-    private _setouterdiv = (outerdiv: HTMLElement | null) => { this._outerdiv = outerdiv; this.updateTooltip(); }
+    private _setouterdiv = (outerdiv: HTMLElement | null) => { this._outerdiv = outerdiv; this.updateTooltip(); };
     private _outerdiv: HTMLElement | null = null;
     private _textBox: FormattedTextBox | undefined;
     private _tooltip?: HTMLElement;
     @observable public TextDoc?: Doc;
 
     updateTooltip = () => {
-        this._outerdiv && this._tooltip && !this._outerdiv.contains(this._tooltip) && this._outerdiv.appendChild(this._tooltip)
+        this._outerdiv && this._tooltip && !this._outerdiv.contains(this._tooltip) && this._outerdiv.appendChild(this._tooltip);
     }
 
     constructor(props: MainOverlayTextBoxProps) {
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index d2a6ceafa..5a25a4f9a 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -184,9 +184,9 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
 
     @action
     onPointerUp = (e: PointerEvent): void => {
-        console.log("pointer up!");
+        // console.log("pointer up!");
         if (this._visible) {
-            console.log("visible");
+            // console.log("visible");
             let mselect = this.marqueeSelect();
             if (!e.shiftKey) {
                 SelectionManager.DeselectAll(mselect.length ? undefined : this.props.container.props.Document);
@@ -195,7 +195,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
             mselect.length ? this.cleanupInteractions(true, false) : this.cleanupInteractions(true);
         }
         else {
-            console.log("invisible");
+            //console.log("invisible");
             this.cleanupInteractions(true);
         }
 
-- 
cgit v1.2.3-70-g09d2


From d3c36913fa0252103d12f062a69cb6126098a3e5 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 29 Jun 2019 10:10:24 -0400
Subject: more datadoc fixes

---
 src/client/util/DocumentManager.ts      | 11 +++++------
 src/client/util/TooltipTextMenu.tsx     |  2 +-
 src/client/views/MainView.tsx           |  2 +-
 src/client/views/nodes/DocumentView.tsx |  4 ++--
 4 files changed, 9 insertions(+), 10 deletions(-)

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

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index d0014dbf3..acd8dcef7 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -44,15 +44,14 @@ export class DocumentManager {
             DocumentManager.Instance.DocumentViews.map(view => {
                 let doc = view.props.Document.proto;
                 if (doc && doc[Id]) {
-                    if(doc[Id] === id)
-                    {toReturn.push(view);}
+                    if (doc[Id] === id) { toReturn.push(view); }
                 }
             });
         }
         return toReturn;
     }
 
-    public getAllDocumentViews(doc: Doc){
+    public getAllDocumentViews(doc: Doc) {
         return this.getDocumentViewsById(doc[Id]);
     }
 
@@ -150,7 +149,7 @@ export class DocumentManager {
                         docContext.panTransformType = "Ease";
                         targetContextView.props.focus(docDelegate, willZoom);
                     } else {
-                        (dockFunc || CollectionDockingView.Instance.AddRightSplit)(docContext, docContext);
+                        (dockFunc || CollectionDockingView.Instance.AddRightSplit)(docContext, undefined);
                         setTimeout(() => {
                             this.jumpToDocument(docDelegate, willZoom, forceDockFunc, dockFunc, linkPage);
                         }, 10);
@@ -159,7 +158,7 @@ export class DocumentManager {
                     const actualDoc = Doc.MakeAlias(docDelegate);
                     actualDoc.libraryBrush = true;
                     if (linkPage !== undefined) actualDoc.curPage = linkPage;
-                    (dockFunc || CollectionDockingView.Instance.AddRightSplit)(actualDoc, actualDoc);
+                    (dockFunc || CollectionDockingView.Instance.AddRightSplit)(actualDoc, undefined);
                 }
             } else {
                 let contextView: DocumentView | null;
@@ -168,7 +167,7 @@ export class DocumentManager {
                     contextDoc.panTransformType = "Ease";
                     contextView.props.focus(docDelegate, willZoom);
                 } else {
-                    (dockFunc || CollectionDockingView.Instance.AddRightSplit)(contextDoc, contextDoc);
+                    (dockFunc || CollectionDockingView.Instance.AddRightSplit)(contextDoc, undefined);
                     setTimeout(() => {
                         this.jumpToDocument(docDelegate, willZoom, forceDockFunc, dockFunc, linkPage);
                     }, 10);
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index a96615488..390bb2f44 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -240,7 +240,7 @@ export class TooltipTextMenu {
                                 if (DocumentManager.Instance.getDocumentView(f)) {
                                     DocumentManager.Instance.getDocumentView(f)!.props.focus(f, false);
                                 }
-                                else if (CollectionDockingView.Instance) CollectionDockingView.Instance.AddRightSplit(f, f);
+                                else if (CollectionDockingView.Instance) CollectionDockingView.Instance.AddRightSplit(f, undefined);
                             }
                         }));
                     }
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 629827f11..fe3ff2392 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -203,7 +203,7 @@ export class MainView extends React.Component {
 
     openNotifsCol = () => {
         if (this._notifsCol && CollectionDockingView.Instance) {
-            CollectionDockingView.Instance.AddRightSplit(this._notifsCol, this._notifsCol);
+            CollectionDockingView.Instance.AddRightSplit(this._notifsCol, undefined);
         }
     }
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 525ca52a6..1d03fa04d 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -333,7 +333,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     let linkedPages = [linkedDocs.length ? NumCast(linkedDocs[0].anchor1Page, undefined) : NumCast(linkedDocs[0].anchor2Page, undefined),
                     linkedDocs.length ? NumCast(linkedDocs[0].anchor2Page, undefined) : NumCast(linkedDocs[0].anchor1Page, undefined)];
                     let maxLocation = StrCast(linkedDoc.maximizeLocation, "inTab");
-                    DocumentManager.Instance.jumpToDocument(linkedDoc, ctrlKey, false, document => this.props.addDocTab(document, document, maxLocation), linkedPages[altKey ? 1 : 0]);
+                    DocumentManager.Instance.jumpToDocument(linkedDoc, ctrlKey, false, document => this.props.addDocTab(document, undefined, maxLocation), linkedPages[altKey ? 1 : 0]);
 
                     // else if (linkedToDocs.length || linkedFromDocs.length) {
                     //     let linkedFwdDocs = [
@@ -503,7 +503,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         cm.addItem({
             description: "Find aliases", event: async () => {
                 const aliases = await SearchUtil.GetAliasesOfDocument(this.props.Document);
-                this.props.addDocTab && this.props.addDocTab(Docs.SchemaDocument(["title"], aliases, {}), Docs.SchemaDocument(["title"], aliases, {}), "onRight"); // bcz: dataDoc?
+                this.props.addDocTab && this.props.addDocTab(Docs.SchemaDocument(["title"], aliases, {}), undefined, "onRight"); // bcz: dataDoc?
             }, icon: "search"
         });
         cm.addItem({ description: "Center View", event: () => this.props.focus(this.props.Document, false), icon: "crosshairs" });
-- 
cgit v1.2.3-70-g09d2


From 13fcbc4c500fec36e69cac4fc3c0ca6822322809 Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Sun, 30 Jun 2019 16:04:52 -0400
Subject: Fixed errors and linter warnings

---
 src/client/util/TooltipTextMenu.tsx                | 70 ++++++----------------
 .../views/collections/CollectionStackingView.tsx   |  2 +-
 .../views/collections/CollectionTreeView.tsx       |  3 +-
 src/client/views/pdf/Annotation.tsx                |  6 +-
 src/client/views/pdf/PDFViewer.tsx                 |  2 +-
 .../views/presentationview/PresentationElement.tsx |  3 +-
 src/client/views/search/FilterBox.tsx              |  7 ++-
 src/client/views/search/SearchItem.tsx             | 15 +++--
 src/new_fields/Doc.ts                              |  3 +-
 9 files changed, 37 insertions(+), 74 deletions(-)

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

diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 390bb2f44..9f8d0b2f6 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -1,57 +1,37 @@
-import { action, IReactionDisposer, reaction } from "mobx";
-import { Dropdown, DropdownSubmenu, MenuItem, MenuItemSpec, renderGrouped, icons, } from "prosemirror-menu"; //no import css
-import { baseKeymap, lift, deleteSelection } from "prosemirror-commands";
-import { history, redo, undo } from "prosemirror-history";
-import { keymap } from "prosemirror-keymap";
-import { EditorState, Transaction, NodeSelection, TextSelection } from "prosemirror-state";
+import { action } from "mobx";
+import { Dropdown, MenuItem, icons, } from "prosemirror-menu"; //no import css
+import { EditorState, NodeSelection, TextSelection } from "prosemirror-state";
 import { EditorView } from "prosemirror-view";
 import { schema } from "./RichTextSchema";
 import { Schema, NodeType, MarkType, Mark, ResolvedPos } from "prosemirror-model";
-import { Node as ProsNode } from "prosemirror-model"
-import React = require("react");
+import { Node as ProsNode } from "prosemirror-model";
 import "./TooltipTextMenu.scss";
-const { toggleMark, setBlockType, wrapIn } = require("prosemirror-commands");
+const { toggleMark, setBlockType } = require("prosemirror-commands");
 import { library } from '@fortawesome/fontawesome-svg-core';
-import { wrapInList, bulletList, liftListItem, listItem, } from 'prosemirror-schema-list';
-import { liftTarget, RemoveMarkStep, AddMarkStep } from 'prosemirror-transform';
-import {
-    faListUl, faGrinTongueSquint,
-} from '@fortawesome/free-solid-svg-icons';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { wrapInList, liftListItem, } from 'prosemirror-schema-list';
+import { faListUl } from '@fortawesome/free-solid-svg-icons';
 import { FieldViewProps } from "../views/nodes/FieldView";
-import { throwStatement } from "babel-types";
 const { openPrompt, TextField } = require("./ProsemirrorCopy/prompt.js");
-import { View } from "@react-pdf/renderer";
 import { DragManager } from "./DragManager";
 import { Doc, Opt, Field } from "../../new_fields/Doc";
 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";
-import { Utils } from "../../Utils";
 import { FormattedTextBoxProps } from "../views/nodes/FormattedTextBox";
-import { text } from "body-parser";
-import { type } from "os";
-// import { wrap } from "module";
-
-const SVG = "http://www.w3.org/2000/svg";
 
 //appears above a selection of text in a RichTextBox to give user options such as Bold, Italics, etc.
 export class TooltipTextMenu {
 
     public tooltip: HTMLElement;
-    private num_icons = 0;
     private view: EditorView;
     private fontStyles: MarkType[];
     private fontSizes: MarkType[];
     private listTypes: NodeType[];
     private editorProps: FieldViewProps & FormattedTextBoxProps;
-    private state: EditorState;
     private fontSizeToNum: Map<MarkType, number>;
     private fontStylesToName: Map<MarkType, string>;
     private listTypeToIcon: Map<NodeType, string>;
-    private fontSizeIndicator: HTMLSpanElement = document.createElement("span");
     private link: HTMLAnchorElement;
 
     private linkEditor?: HTMLDivElement;
@@ -68,7 +48,6 @@ export class TooltipTextMenu {
 
     constructor(view: EditorView, editorProps: FieldViewProps & FormattedTextBoxProps) {
         this.view = view;
-        this.state = view.state;
         this.editorProps = editorProps;
         this.tooltip = document.createElement("div");
         this.tooltip.className = "tooltipMenu";
@@ -333,7 +312,7 @@ export class TooltipTextMenu {
 
     //for a specific grouping of marks (passed in), remove all and apply the passed-in one to the selected text
     changeToMarkInGroup = (markType: MarkType, view: EditorView, fontMarks: MarkType[]) => {
-        let { empty, $cursor, ranges } = view.state.selection as TextSelection;
+        let { $cursor, ranges } = view.state.selection as TextSelection;
         let state = view.state;
         let dispatch = view.dispatch;
 
@@ -345,13 +324,12 @@ export class TooltipTextMenu {
                         dispatch(state.tr.removeStoredMark(type));
                     }
                 } else {
-                    let has = false, tr = state.tr;
+                    let has = false;
                     for (let i = 0; !has && i < ranges.length; i++) {
                         let { $from, $to } = ranges[i];
                         has = state.doc.rangeHasMark($from.pos, $to.pos, type);
                     }
                     for (let i of ranges) {
-                        let { $from, $to } = i;
                         if (has) {
                             toggleMark(type)(view.state, view.dispatch, view);
                         }
@@ -373,7 +351,7 @@ export class TooltipTextMenu {
     }
 
     //remove all node typeand apply the passed-in one to the selected text
-    changeToNodeType(nodeType: NodeType | undefined, view: EditorView, allNodes: NodeType[]) {
+    changeToNodeType(nodeType: NodeType | undefined, view: EditorView) {
         //remove old
         liftListItem(schema.nodes.list_item)(view.state, view.dispatch);
         if (nodeType) { //add new
@@ -390,7 +368,7 @@ export class TooltipTextMenu {
             execEvent: "",
             class: "menuicon",
             css: css,
-            enable(state) { return true; },
+            enable() { return true; },
             run() {
                 changeToMarkInGroup(markType, view, groupMarks);
             }
@@ -405,7 +383,7 @@ export class TooltipTextMenu {
             css: "color:white;",
             class: "summarize",
             execEvent: "",
-            run: (state, dispatch, view) => {
+            run: (state, dispatch) => {
                 TooltipTextMenu.insertStar(state, dispatch);
             }
 
@@ -420,7 +398,7 @@ export class TooltipTextMenu {
             execEvent: "",
             css: "color:white;",
             class: "summarize",
-            run: (state, dispatch, view) => {
+            run: () => {
                 this.collapseToolTip();
             }
         });
@@ -499,7 +477,7 @@ export class TooltipTextMenu {
             execEvent: "",
             class: "menuicon",
             css: css,
-            enable(state) { return true; },
+            enable() { return true; },
             run() {
                 changeToNodeInGroup(nodeType, view, groupNodes);
             }
@@ -586,17 +564,6 @@ export class TooltipTextMenu {
             //return;
         }
 
-        //let linksInSelection = this.activeMarksOnSelection([schema.marks.link]);
-        // if (linksInSelection.length > 0) {
-        //     let attributes = this.getMarksInSelection(this.view.state, [schema.marks.link])[0].attrs;
-        //     this.link.href = attributes.href;
-        //     this.link.textContent = attributes.title;
-        //     this.link.style.visibility = "visible";
-        // } else this.link.style.visibility = "hidden";
-
-        // Otherwise, reposition it and update its content
-        //this.tooltip.style.display = "";
-        let { from, to } = state.selection;
 
         //UPDATE LIST ITEM DROPDOWN
 
@@ -641,17 +608,16 @@ export class TooltipTextMenu {
     //finds all active marks on selection in given group
     activeMarksOnSelection(markGroup: MarkType[]) {
         //current selection
-        let { empty, $cursor, ranges } = this.view.state.selection as TextSelection;
+        let { empty, ranges } = this.view.state.selection as TextSelection;
         let state = this.view.state;
         let dispatch = this.view.dispatch;
         let activeMarks: MarkType[];
         if (!empty) {
             activeMarks = markGroup.filter(mark => {
                 if (dispatch) {
-                    let has = false, tr = state.tr;
+                    let has = false;
                     for (let i = 0; !has && i < ranges.length; i++) {
                         let { $from, $to } = ranges[i];
-                        let hasmark: boolean = state.doc.rangeHasMark($from.pos, $to.pos, mark);
                         return state.doc.rangeHasMark($from.pos, $to.pos, mark);
                     }
                 }
@@ -662,9 +628,7 @@ export class TooltipTextMenu {
             const pos = this.view.state.selection.$from;
             const ref_node: ProsNode = this.reference_node(pos);
             if (ref_node !== null && ref_node !== this.view.state.doc) {
-                let text_node_type: NodeType;
                 if (ref_node.isText) {
-                    text_node_type = ref_node.type;
                 }
                 else {
                     return [];
@@ -699,7 +663,7 @@ export class TooltipTextMenu {
         else if (pos.pos > 0) {
             let skip = false;
             for (let i: number = pos.pos - 1; i > 0; i--) {
-                this.view.state.doc.nodesBetween(i, pos.pos, (node: ProsNode, pos: number, parent: ProsNode, index: number) => {
+                this.view.state.doc.nodesBetween(i, pos.pos, (node: ProsNode) => {
                     if (node.isLeaf && !skip) {
                         ref_node = node;
                         skip = true;
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 088a9bae8..b10907937 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -78,7 +78,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
                 style={{ width: width(), height: height() }} >
                 <CollectionSchemaPreview
                     Document={layoutDoc}
-                    DataDocument={d != this.props.DataDoc ? this.props.DataDoc : undefined}
+                    DataDocument={d !== this.props.DataDoc ? this.props.DataDoc : undefined}
                     renderDepth={this.props.renderDepth}
                     width={width}
                     height={height}
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index dd165e90c..927aa363f 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -186,8 +186,7 @@ class TreeView extends React.Component<TreeViewProps> {
         }
         let keyList: string[] = keys.reduce((l, key) => {
             let listspec = DocListCast(this.resolvedDataDoc[key]);
-            if (listspec && listspec.length)
-                return [...l, key];
+            if (listspec && listspec.length) return [...l, key];
             return l;
         }, [] as string[]);
         keys.map(key => Cast(this.resolvedDataDoc[key], Doc) instanceof Doc && keyList.push(key));
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index 74f4be51a..73fca229b 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -10,9 +10,9 @@ import PDFMenu from "./PDFMenu";
 import { DocumentManager } from "../../util/DocumentManager";
 
 interface IAnnotationProps {
-    anno: Doc,
-    index: number,
-    parent: Viewer
+    anno: Doc;
+    index: number;
+    parent: Viewer;
 }
 
 export default class Annotation extends React.Component<IAnnotationProps> {
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 6fab390d4..bb148e738 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -426,7 +426,7 @@ export class Viewer extends React.Component<IViewerProps> {
     }
 
     renderAnnotation = (anno: Doc, index: number): JSX.Element => {
-        return <Annotation anno={anno} index={index} parent={this} />
+        return <Annotation anno={anno} index={index} parent={this} />;
     }
 
     @action
diff --git a/src/client/views/presentationview/PresentationElement.tsx b/src/client/views/presentationview/PresentationElement.tsx
index 4afc0210f..d63c0b066 100644
--- a/src/client/views/presentationview/PresentationElement.tsx
+++ b/src/client/views/presentationview/PresentationElement.tsx
@@ -13,12 +13,11 @@ import { faFile as fileRegular } from '@fortawesome/free-regular-svg-icons';
 import { List } from "../../../new_fields/List";
 import { listSpec } from "../../../new_fields/Schema";
 
-
 library.add(faArrowUp);
 library.add(fileSolid);
 library.add(faLocationArrow);
+library.add(fileRegular as any);
 library.add(faSearch);
-library.add(fileRegular);
 
 interface PresentationElementProps {
     mainDocument: Doc;
diff --git a/src/client/views/search/FilterBox.tsx b/src/client/views/search/FilterBox.tsx
index c39ec1145..8bbbf3012 100644
--- a/src/client/views/search/FilterBox.tsx
+++ b/src/client/views/search/FilterBox.tsx
@@ -65,9 +65,9 @@ export class FilterBox extends React.Component {
 
     setupAccordion() {
         $('document').ready(function () {
-            var acc = document.getElementsByClassName('filter-header');
-
-            for (var i = 0; i < acc.length; i++) {
+            const acc = document.getElementsByClassName('filter-header');
+            // tslint:disable-next-line: prefer-for-of
+            for (let i = 0; i < acc.length; i++) {
                 acc[i].addEventListener("click", function (this: HTMLElement) {
                     this.classList.toggle("active");
 
@@ -96,6 +96,7 @@ export class FilterBox extends React.Component {
         $('document').ready(function () {
             var acc = document.getElementsByClassName('filter-header');
 
+            // tslint:disable-next-line: prefer-for-of
             for (var i = 0; i < acc.length; i++) {
                 let classList = acc[i].classList;
                 if (classList.contains("active")) {
diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx
index b72f8c814..f8a0c7b16 100644
--- a/src/client/views/search/SearchItem.tsx
+++ b/src/client/views/search/SearchItem.tsx
@@ -83,9 +83,8 @@ export class SelectorContextMenu extends React.Component<SearchItemProps> {
                             <FontAwesomeIcon icon={faStickyNote} />
                         </div>
                         <a className="title" onClick={this.getOnClick(doc)}>{doc.col.title}</a>
-                    </div>
-                }
-                )}
+                    </div>;
+                })}
             </div>
         );
     }
@@ -111,7 +110,7 @@ export class SearchItem extends React.Component<SearchItemProps> {
             if (layoutresult.indexOf(DocTypes.COL) !== -1) {
                 renderDoc = Doc.MakeDelegate(renderDoc);
                 let bounds = DocListCast(renderDoc.data).reduce((bounds, doc) => {
-                    var [sptX, sptY] = [NumCast(doc.x), NumCast(doc.y)]
+                    var [sptX, sptY] = [NumCast(doc.x), NumCast(doc.y)];
                     let [bptX, bptY] = [sptX + doc[WidthSym](), sptY + doc[HeightSym]()];
                     return {
                         x: Math.min(sptX, bounds.x), y: Math.min(sptY, bounds.y),
@@ -124,7 +123,7 @@ export class SearchItem extends React.Component<SearchItemProps> {
             let returnYDimension = () => this._displayDim;
             let scale = () => returnXDimension() / NumCast(renderDoc.nativeWidth, returnXDimension());
             return <div
-                onPointerDown={action(() => { this._useIcons = !this._useIcons; this._displayDim = this._useIcons ? 50 : Number(SEARCH_THUMBNAIL_SIZE) })}
+                onPointerDown={action(() => { this._useIcons = !this._useIcons; this._displayDim = this._useIcons ? 50 : Number(SEARCH_THUMBNAIL_SIZE); })}
                 onPointerEnter={action(() => this._displayDim = this._useIcons ? 50 : Number(SEARCH_THUMBNAIL_SIZE))}
                 onPointerLeave={action(() => this._displayDim = 50)} >
                 <DocumentView
@@ -147,7 +146,7 @@ export class SearchItem extends React.Component<SearchItemProps> {
                     ContainingCollectionView={undefined}
                     ContentScaling={scale}
                 />
-            </div>
+            </div>;
         }
 
         let button = layoutresult.indexOf(DocTypes.PDF) !== -1 ? faFilePdf :
@@ -160,7 +159,7 @@ export class SearchItem extends React.Component<SearchItemProps> {
                                     layoutresult.indexOf(DocTypes.HIST) !== -1 ? faChartBar :
                                         layoutresult.indexOf(DocTypes.WEB) !== -1 ? faGlobeAsia :
                                             faCaretUp;
-        return <div onPointerDown={action(() => { this._useIcons = false; this._displayDim = Number(SEARCH_THUMBNAIL_SIZE) })} >
+        return <div onPointerDown={action(() => { this._useIcons = false; this._displayDim = Number(SEARCH_THUMBNAIL_SIZE); })} >
             <FontAwesomeIcon icon={button} size="2x" />
         </div>;
     }
@@ -189,7 +188,7 @@ export class SearchItem extends React.Component<SearchItemProps> {
     }
 
     @action
-    pointerDown = (e: React.PointerEvent) => SearchBox.Instance.openSearch(e);
+    pointerDown = (e: React.PointerEvent) => SearchBox.Instance.openSearch(e)
 
     highlightDoc = (e: React.PointerEvent) => {
         if (this.props.doc.type === DocTypes.LINK) {
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index e76e94d63..734a90731 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -293,11 +293,12 @@ export namespace Doc {
         if (expandedTemplateLayout instanceof Doc) {
             return expandedTemplateLayout;
         }
-        if (expandedTemplateLayout === undefined)
+        if (expandedTemplateLayout === undefined) {
             setTimeout(() => {
                 templateLayoutDoc["_expanded_" + dataDoc[Id]] = Doc.MakeDelegate(templateLayoutDoc);
                 (templateLayoutDoc["_expanded_" + dataDoc[Id]] as Doc).title = templateLayoutDoc.title + " applied to " + dataDoc.title;
             }, 0);
+        }
         return templateLayoutDoc;
     }
 
-- 
cgit v1.2.3-70-g09d2


From d479f5e6bcdb7d1a0edb2f8cf366549abe2a910b Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 4 Jul 2019 14:08:00 -0400
Subject: added text font color setting.   adding interactive border radius.
 moved main toolbar.

---
 src/client/documents/Documents.ts                  |  4 +--
 src/client/util/DragManager.ts                     |  1 +
 src/client/util/RichTextSchema.tsx                 | 11 ++++++++
 src/client/util/TooltipTextMenu.tsx                |  3 +-
 src/client/views/DocumentDecorations.scss          | 22 +++++++++++++++
 src/client/views/DocumentDecorations.tsx           | 32 ++++++++++++++++++++++
 src/client/views/InkingControl.tsx                 |  3 ++
 src/client/views/Main.scss                         |  2 +-
 src/client/views/MainOverlayTextBox.tsx            |  5 ++++
 src/client/views/MainView.tsx                      |  2 +-
 .../collections/collectionFreeForm/MarqueeView.tsx | 16 ++++-------
 .../views/nodes/CollectionFreeFormDocumentView.tsx | 15 ++++++----
 src/client/views/nodes/DocumentView.tsx            |  4 +--
 src/client/views/nodes/FormattedTextBox.tsx        | 11 +++++++-
 14 files changed, 106 insertions(+), 25 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 7a976e7d7..2bddf053a 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -77,7 +77,7 @@ export interface DocumentOptions {
     backgroundLayout?: string;
     curPage?: number;
     documentText?: string;
-    borderRounding?: number;
+    borderRounding?: string;
     schemaColumns?: List<string>;
     dockingConfig?: string;
     dbDoc?: Doc;
@@ -93,7 +93,7 @@ export namespace DocUtils {
         if (target === CurrentUserUtils.UserDocument) return;
 
         UndoManager.RunInBatch(() => {
-            let linkDoc = Docs.TextDocument({ width: 100, height: 30, borderRounding: -1 });
+            let linkDoc = Docs.TextDocument({ width: 100, height: 30, borderRounding: "100%" });
             linkDoc.type = DocTypes.LINK;
             let linkDocProto = Doc.GetProto(linkDoc);
 
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 7dc48fb78..d4b1bc8f4 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -315,6 +315,7 @@ export namespace DragManager {
             scaleYs.push(scaleY);
             let dragElement = ele.cloneNode(true) as HTMLElement;
             dragElement.style.opacity = "0.7";
+            dragElement.style.borderRadius = getComputedStyle(ele).borderRadius;
             dragElement.style.position = "absolute";
             dragElement.style.margin = "0";
             dragElement.style.top = "0";
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 63c879d67..2a57180d3 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -348,6 +348,17 @@ export const marks: { [index: string]: MarkSpec } = {
         }]
     },
 
+    pFontColor: {
+        attrs: {
+            color: { default: "yellow" }
+        },
+        parseDOM: [{ style: 'background: #d9dbdd' }],
+        toDOM: (node) => {
+            return ['span', {
+                style: `color: ${node.attrs.color}`
+            }];
+        }
+    },
 
     /** FONT SIZES */
     pFontSize: {
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 9f8d0b2f6..e3e26d1f4 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -111,7 +111,8 @@ export class TooltipTextMenu {
         this.fontSizeToNum.set(schema.marks.p32, 32);
         this.fontSizeToNum.set(schema.marks.p48, 48);
         this.fontSizeToNum.set(schema.marks.p72, 72);
-        //this.fontSizeToNum.set(schema.marks.pFontSize,schema.marks.pFontSize.)
+        this.fontSizeToNum.set(schema.marks.pFontSize, 10);
+        this.fontSizeToNum.set(schema.marks.pFontSize, 10);
         this.fontSizes = Array.from(this.fontSizeToNum.keys());
 
         //list types
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index ba9f32d7d..2d430512b 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -26,6 +26,14 @@ $linkGap : 3px;
         opacity: 0.8;
     }
 
+    .documentDecorations-radius {
+        pointer-events: auto;
+        background: black;
+        opacity: 0.8;
+        transform: translate(10px, 10px);
+        grid-row: 4;
+    }
+
     #documentDecorations-topLeftResizer,
     #documentDecorations-leftResizer,
     #documentDecorations-bottomLeftResizer {
@@ -44,11 +52,25 @@ $linkGap : 3px;
         grid-column-start: 5;
         grid-column-end: 7;
     }
+    
+    #documentDecorations-borderRadius{
+        grid-column-start: 5;
+        grid-column-end: 7;
+        border-radius: 100%;
+        .borderRadiusTooltip{
+            width:10px;
+            height:10px;
+            position:absolute;
+        }
+    }
 
     #documentDecorations-topLeftResizer,
     #documentDecorations-bottomRightResizer {
         cursor: nwse-resize;
     }
+    #documentDecorations-bottomRightResizer {
+        grid-row:4;
+    }
 
     #documentDecorations-topRightResizer,
     #documentDecorations-bottomLeftResizer {
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 3df520428..c7990647a 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -341,6 +341,37 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         iconDoc.y = where[1] + NumCast(selView.props.Document.y);
     }
 
+    _radiusDown = [0, 0];
+    @action
+    onRadiusDown = (e: React.PointerEvent): void => {
+        e.stopPropagation();
+        if (e.button === 0) {
+            this._radiusDown = [e.clientX, e.clientY];
+            this._isPointerDown = true;
+            this._resizeUndo = UndoManager.StartBatch("DocDecs set radius");
+            document.removeEventListener("pointermove", this.onRadiusMove);
+            document.removeEventListener("pointerup", this.onRadiusUp);
+            document.addEventListener("pointermove", this.onRadiusMove);
+            document.addEventListener("pointerup", this.onRadiusUp);
+        }
+    }
+
+    onRadiusMove = (e: PointerEvent): void => {
+        let dist = Math.sqrt((e.clientX - this._radiusDown[0]) * (e.clientX - this._radiusDown[0]) + (e.clientY - this._radiusDown[1]) * (e.clientY - this._radiusDown[1]));
+        SelectionManager.SelectedDocuments().map(dv => Doc.GetProto(dv.props.Document).borderRounding = `${Math.min(100, dist)}%`);
+        e.stopPropagation();
+        e.preventDefault();
+    }
+
+    onRadiusUp = (e: PointerEvent): void => {
+        e.stopPropagation();
+        e.preventDefault();
+        this._isPointerDown = false;
+        this._resizeUndo && this._resizeUndo.end();
+        document.removeEventListener("pointermove", this.onRadiusMove);
+        document.removeEventListener("pointerup", this.onRadiusUp);
+    }
+
     @action
     onPointerDown = (e: React.PointerEvent): void => {
         e.stopPropagation();
@@ -705,6 +736,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 <div id="documentDecorations-bottomLeftResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
                 <div id="documentDecorations-bottomResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
                 <div id="documentDecorations-bottomRightResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
+                <div id="documentDecorations-borderRadius" className="documentDecorations-radius" onPointerDown={this.onRadiusDown} onContextMenu={(e) => e.preventDefault()}><span className="borderRadiusTooltip" title="Drag Corner Radius"></span></div>
                 <div className="link-button-container">
                     <div className="linkButtonWrapper">
                         <div title="View Links" className="linkFlyout" ref={this._linkButton}> {linkButton}  </div>
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index 0461d7299..c7f7bdb66 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -10,6 +10,8 @@ import { InkTool } from "../../new_fields/InkField";
 import { Doc } from "../../new_fields/Doc";
 import { undoBatch, UndoManager } from "../util/UndoManager";
 import { StrCast } from "../../new_fields/Types";
+import { FormattedTextBox } from "./nodes/FormattedTextBox";
+import { MainOverlayTextBox } from "./MainOverlayTextBox";
 
 library.add(faPen, faHighlighter, faEraser, faBan);
 
@@ -42,6 +44,7 @@ export class InkingControl extends React.Component {
     switchColor = action((color: ColorResult): void => {
         this._selectedColor = color.hex + (color.rgb.a !== undefined ? this.decimalToHexString(Math.round(color.rgb.a * 255)) : "ff");
         if (InkingControl.Instance.selectedTool === InkTool.None) {
+            if (MainOverlayTextBox.Instance.SetColor(color.hex)) return;
             let selected = SelectionManager.SelectedDocuments();
             let oldColors = selected.map(view => {
                 let targetDoc = view.props.Document.isTemplate ? view.props.Document : Doc.GetProto(view.props.Document);
diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss
index 44c1cb9fc..b85a8040a 100644
--- a/src/client/views/Main.scss
+++ b/src/client/views/Main.scss
@@ -144,7 +144,7 @@ button:hover {
 #add-nodes-menu {
     position: absolute;
     bottom: 22px;
-    left: 24px;
+    left: 250px;
 
     > label {
         background: $dark-color;
diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx
index 7d15702a2..d8aaea259 100644
--- a/src/client/views/MainOverlayTextBox.tsx
+++ b/src/client/views/MainOverlayTextBox.tsx
@@ -36,6 +36,11 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
         this._outerdiv && this._tooltip && !this._outerdiv.contains(this._tooltip) && this._outerdiv.appendChild(this._tooltip);
     }
 
+    public SetColor(color: string) {
+        return this._textBox && this._textBox.setFontColor(color);
+    }
+
+
     constructor(props: MainOverlayTextBoxProps) {
         super(props);
         this._textProxyDiv = React.createRef();
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 1542fedef..61ccf4c1d 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -354,7 +354,7 @@ export class MainView extends React.Component {
             [React.createRef<HTMLDivElement>(), "tree", "Add Tree", addTreeNode],
         ];
 
-        return < div id="add-nodes-menu" >
+        return < div id="add-nodes-menu" style={{ left: this.flyoutWidth + 5 }} >
             <input type="checkbox" id="add-menu-toggle" ref={this.addMenuToggle} />
             <label htmlFor="add-menu-toggle" title="Add Node"><p>+</p></label>
 
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 31c0fe499..4850c6218 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -44,14 +44,12 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
     _commandExecuted = false;
 
     @action
-    cleanupInteractions = (all: boolean = false, rem_keydown: boolean = true) => {
+    cleanupInteractions = (all: boolean = false) => {
         if (all) {
             document.removeEventListener("pointerup", this.onPointerUp, true);
             document.removeEventListener("pointermove", this.onPointerMove, true);
         }
-        if (rem_keydown) {
-            document.removeEventListener("keydown", this.marqueeCommand, true);
-        }
+        document.removeEventListener("keydown", this.marqueeCommand, true);
         this._visible = false;
     }
 
@@ -191,12 +189,9 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                 SelectionManager.DeselectAll(mselect.length ? undefined : this.props.container.props.Document);
             }
             this.props.selectDocuments(mselect.length ? mselect : [this.props.container.props.Document]);
-            mselect.length ? this.cleanupInteractions(true, false) : this.cleanupInteractions(true);
-        }
-        else {
-            //console.log("invisible");
-            this.cleanupInteractions(true);
         }
+        //console.log("invisible");
+        this.cleanupInteractions(true);
 
         if (e.altKey) {
             e.preventDefault();
@@ -266,13 +261,12 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
             }
             let ink = Cast(this.props.container.props.Document.ink, InkField);
             let inkData = ink ? ink.inkData : undefined;
-            let zoomBasis = NumCast(this.props.container.props.Document.scale, 1);
             let newCollection = Docs.FreeformDocument(selected, {
                 x: bounds.left,
                 y: bounds.top,
                 panX: 0,
                 panY: 0,
-                borderRounding: e.key === "e" ? -1 : undefined,
+                borderRounding: e.key === "e" ? "100%" : undefined,
                 backgroundColor: this.props.container.isAnnotationOverlay ? undefined : "white",
                 width: bounds.width,
                 height: bounds.height,
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 30cf74f3e..1c00687ed 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -70,11 +70,14 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     }
 
     borderRounding = () => {
-        let br = NumCast(this.props.Document.borderRounding);
-        return br >= 0 ? br :
-            NumCast(this.props.Document.nativeWidth) === 0 ?
-                Math.min(this.props.PanelWidth(), this.props.PanelHeight())
-                : Math.min(this.Document.nativeWidth || 0, this.Document.nativeHeight || 0);
+        let br = StrCast(this.props.Document.borderRounding);
+        if (br.endsWith("%")) {
+            let percent = Number(br.substr(0, br.length - 1)) / 100;
+            let nativeDim = Math.min(NumCast(this.props.Document.nativeWidth), NumCast(this.props.Document.nativeHeight));
+            let minDim = percent * (nativeDim ? nativeDim : Math.min(this.props.PanelWidth(), this.props.PanelHeight()));
+            return minDim;
+        }
+        return undefined;
     }
 
     render() {
@@ -84,7 +87,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                     transformOrigin: "left top",
                     position: "absolute",
                     backgroundColor: "transparent",
-                    borderRadius: `${this.borderRounding()}px`,
+                    borderRadius: this.borderRounding(),
                     transform: this.transform,
                     transition: StrCast(this.props.Document.transition),
                     width: this.width,
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index f751a45fe..a84cac37f 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -589,9 +589,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 style={{
                     outlineColor: "maroon",
                     outlineStyle: "dashed",
-                    outlineWidth: BoolCast(this.props.Document.libraryBrush) && !NumCast(this.props.Document.borderRounding) ?
+                    outlineWidth: BoolCast(this.props.Document.libraryBrush) && !StrCast(this.props.Document.borderRounding) ?
                         `${this.props.ScreenToLocalTransform().Scale}px` : "0px",
-                    border: BoolCast(this.props.Document.libraryBrush) && NumCast(this.props.Document.borderRounding) ?
+                    border: BoolCast(this.props.Document.libraryBrush) && StrCast(this.props.Document.borderRounding) ?
                         `dashed maroon ${this.props.ScreenToLocalTransform().Scale}px` : undefined,
                     borderRadius: "inherit",
                     background: backgroundColor,
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 1eeb04755..bf6f4c764 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -97,6 +97,15 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         return "";
     }
 
+    @undoBatch
+    public setFontColor(color: string) {
+        if (this._editorView!.state.selection.from === this._editorView!.state.selection.to) return false;
+        let colorMark = this._editorView!.state.schema.mark(this._editorView!.state.schema.marks.pFontColor, { color: color });
+        this._editorView!.dispatch(this._editorView!.state.tr.addMark(this._editorView!.state.selection.from,
+            this._editorView!.state.selection.to, colorMark));
+        return true;
+    }
+
     constructor(props: FieldViewProps) {
         super(props);
         if (this.props.outer_div) {
@@ -418,7 +427,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     }
     render() {
         let style = this.props.isOverlay ? "scroll" : "hidden";
-        let rounded = NumCast(this.props.Document.borderRounding) < 0 ? "-rounded" : "";
+        let rounded = StrCast(this.props.Document.borderRounding) === "100%" ? "-rounded" : "";
         let interactive = InkingControl.Instance.selectedTool ? "" : "interactive";
         return (
             <div className={`formattedTextBox-cont-${style}`} ref={this._ref}
-- 
cgit v1.2.3-70-g09d2


From 149fdb6c172a84c8ec2fbb170bbdd5625c1ac0fa Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 5 Jul 2019 00:34:00 -0400
Subject: restored dragging hyperlink from menu for formattedText boxes

---
 src/client/util/TooltipTextMenu.scss |  2 +-
 src/client/util/TooltipTextMenu.tsx  | 32 ++++++++++++++++++--------------
 2 files changed, 19 insertions(+), 15 deletions(-)

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

diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss
index b10573b3e..40ac3abb9 100644
--- a/src/client/util/TooltipTextMenu.scss
+++ b/src/client/util/TooltipTextMenu.scss
@@ -248,7 +248,7 @@
     transform: translateY(-85px);
     pointer-events: all;
     height: 30px;
-    width:500px;
+    width:550px;
     .ProseMirror-example-setup-style hr {
       padding: 2px 10px;
       border: none;
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index e3e26d1f4..cb7ed976a 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -32,7 +32,6 @@ export class TooltipTextMenu {
     private fontSizeToNum: Map<MarkType, number>;
     private fontStylesToName: Map<MarkType, string>;
     private listTypeToIcon: Map<NodeType, string>;
-    private link: HTMLAnchorElement;
 
     private linkEditor?: HTMLDivElement;
     private linkText?: HTMLDivElement;
@@ -89,6 +88,7 @@ export class TooltipTextMenu {
             });
 
         });
+        this.updateLinkMenu();
 
         //list of font styles
         this.fontStylesToName = new Map();
@@ -121,11 +121,6 @@ export class TooltipTextMenu {
         this.listTypeToIcon.set(schema.nodes.ordered_list, "1)");
         this.listTypes = Array.from(this.listTypeToIcon.keys());
 
-        this.link = document.createElement("a");
-        this.link.target = "_blank";
-        this.link.style.color = "white";
-        //this.tooltip.appendChild(this.link);
-
         this.tooltip.appendChild(this.createLink().render(this.view).dom);
 
         this.tooltip.appendChild(this.createStar().render(this.view).dom);
@@ -191,6 +186,7 @@ export class TooltipTextMenu {
     updateLinkMenu() {
         if (!this.linkEditor || !this.linkText) {
             this.linkEditor = document.createElement("div");
+            this.linkEditor.className = "ProseMirror-icon menuicon";
             this.linkEditor.style.color = "black";
             this.linkText = document.createElement("div");
             this.linkText.style.cssFloat = "left";
@@ -231,8 +227,9 @@ export class TooltipTextMenu {
             };
             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.width = "15px";
+            this.linkDrag.style.height = "15px";
+            this.linkDrag.title = "Drag to create link";
             this.linkDrag.style.color = "black";
             this.linkDrag.style.background = "black";
             this.linkDrag.style.cssFloat = "left";
@@ -250,10 +247,10 @@ export class TooltipTextMenu {
                         hideSource: false
                     });
             };
-            // this.linkEditor.appendChild(this.linkDrag);
+            this.linkEditor.appendChild(this.linkDrag);
             // this.linkEditor.appendChild(this.linkText);
             // this.linkEditor.appendChild(linkBtn);
-            //this.tooltip.appendChild(this.linkEditor);
+            this.tooltip.appendChild(this.linkEditor);
         }
 
         let node = this.view.state.selection.$from.nodeAfter;
@@ -444,16 +441,24 @@ export class TooltipTextMenu {
             enable(state) { return !state.selection.empty; },
             run: (state, dispatch, view) => {
                 // to remove link
+                let curLink = "";
                 if (this.markActive(state, markType)) {
-                    toggleMark(markType)(state, dispatch);
-                    return true;
+
+                    let { from, $from, to, empty } = state.selection;
+                    let node = state.doc.nodeAt(from);
+                    node && node.marks.map(m => {
+                        m.type === markType && (curLink = m.attrs.href);
+                    })
+                    //toggleMark(markType)(state, dispatch);
+                    //return true;
                 }
                 // to create link
                 openPrompt({
                     title: "Create a link",
                     fields: {
                         href: new TextField({
-                            label: "Link target",
+                            value: curLink,
+                            label: "Link Target",
                             required: true
                         }),
                         title: new TextField({ label: "Title" })
@@ -603,7 +608,6 @@ export class TooltipTextMenu {
             }
         }
         this.view.dispatch(this.view.state.tr.setStoredMarks(this._activeMarks));
-        this.updateLinkMenu();
     }
 
     //finds all active marks on selection in given group
-- 
cgit v1.2.3-70-g09d2


From 0058f56fda4e9e6a70ec7c23e1c141927715f9c1 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 11 Jul 2019 00:38:37 -0400
Subject: trying to make everything work the same way

---
 src/client/documents/Documents.ts                  |  8 +++---
 src/client/util/LinkManager.ts                     |  4 +--
 src/client/util/TooltipTextMenu.tsx                |  2 +-
 .../views/collections/CollectionBaseView.tsx       | 19 ++++++++++---
 src/client/views/collections/CollectionPDFView.tsx |  4 +--
 .../views/collections/CollectionStackingView.tsx   | 10 +++----
 src/client/views/collections/CollectionSubView.tsx | 10 ++++---
 .../views/collections/CollectionTreeView.tsx       |  2 +-
 .../views/collections/CollectionVideoView.tsx      |  4 +--
 src/client/views/collections/CollectionView.tsx    |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.scss |  4 +--
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 33 +++++++++++-----------
 src/client/views/nodes/DocumentView.tsx            |  2 +-
 src/client/views/nodes/FieldView.tsx               |  4 +--
 src/client/views/nodes/FormattedTextBox.tsx        |  3 +-
 src/client/views/pdf/PDFViewer.tsx                 |  2 +-
 src/new_fields/Doc.ts                              |  2 +-
 17 files changed, 64 insertions(+), 51 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index d1c9feb32..46255b5d5 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -174,7 +174,7 @@ export namespace Docs {
     }
 
     function CreateImagePrototype(): Doc {
-        let imageProto = setupPrototypeOptions(imageProtoId, "IMAGE_PROTO", CollectionView.LayoutString("annotations"),
+        let imageProto = setupPrototypeOptions(imageProtoId, "IMAGE_PROTO", CollectionView.LayoutString("data", "annotations"),
             { x: 0, y: 0, nativeWidth: 600, width: 300, backgroundLayout: ImageBox.LayoutString(), curPage: 0, type: DocTypes.IMG });
         return imageProto;
     }
@@ -185,7 +185,7 @@ export namespace Docs {
     }
 
     function CreateHistogramPrototype(): Doc {
-        let histoProto = setupPrototypeOptions(histoProtoId, "HISTO PROTO", CollectionView.LayoutString("annotations"),
+        let histoProto = setupPrototypeOptions(histoProtoId, "HISTO PROTO", CollectionView.LayoutString("data"),
             { x: 0, y: 0, width: 300, height: 300, backgroundColor: "black", backgroundLayout: HistogramBox.LayoutString(), type: DocTypes.HIST });
         return histoProto;
     }
@@ -200,7 +200,7 @@ export namespace Docs {
         return textProto;
     }
     function CreatePdfPrototype(): Doc {
-        let pdfProto = setupPrototypeOptions(pdfProtoId, "PDF_PROTO", CollectionPDFView.LayoutString("annotations"),
+        let pdfProto = setupPrototypeOptions(pdfProtoId, "PDF_PROTO", CollectionPDFView.LayoutString("data", "annotations"),
             { x: 0, y: 0, width: 300, height: 300, backgroundLayout: PDFBox.LayoutString(), curPage: 1, type: DocTypes.PDF });
         return pdfProto;
     }
@@ -221,7 +221,7 @@ export namespace Docs {
         return kvpProto;
     }
     function CreateVideoPrototype(): Doc {
-        let videoProto = setupPrototypeOptions(videoProtoId, "VIDEO_PROTO", CollectionVideoView.LayoutString("annotations"),
+        let videoProto = setupPrototypeOptions(videoProtoId, "VIDEO_PROTO", CollectionVideoView.LayoutString("data", "annotations"),
             { x: 0, y: 0, nativeWidth: 600, width: 300, backgroundLayout: VideoBox.LayoutString(), curPage: 0, type: DocTypes.VID });
         return videoProto;
     }
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index 944bc532f..d6ea6013b 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -234,9 +234,9 @@ export class LinkManager {
     //TODO This should also await the return value of the anchor so we don't filter out promises
     public getOppositeAnchor(linkDoc: Doc, anchor: Doc): Doc {
         if (Doc.AreProtosEqual(anchor, Cast(linkDoc.anchor1, Doc, null))) {
-            return Cast(linkDoc.anchor2, Doc, null)!;
+            return Cast(linkDoc.anchor2, Doc, null);
         } else {
-            return Cast(linkDoc.anchor1, Doc, null)!;
+            return Cast(linkDoc.anchor1, Doc, null);
         }
     }
 }
\ No newline at end of file
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index cb7ed976a..4fcc16ddd 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -448,7 +448,7 @@ export class TooltipTextMenu {
                     let node = state.doc.nodeAt(from);
                     node && node.marks.map(m => {
                         m.type === markType && (curLink = m.attrs.href);
-                    })
+                    });
                     //toggleMark(markType)(state, dispatch);
                     //return true;
                 }
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index e4f9b5058..079a44b88 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -74,21 +74,27 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
         this.props.whenActiveChanged(isActive);
     }
 
+    public get isAnnotationOverlay() { return this.props.fieldKey === "annotations" || this.props.fieldExt === "annotations"; }
+    @computed get extensionDoc() { return Doc.resolvedFieldDataDoc(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, this.isAnnotationOverlay ? "dummy" : ""); }
+
     @action.bound
     addDocument(doc: Doc, allowDuplicates: boolean = false): boolean {
+        let self = this;
         var curPage = NumCast(this.props.Document.curPage, -1);
         Doc.GetProto(doc).page = curPage;
         if (curPage >= 0) {
             Doc.GetProto(doc).annotationOn = this.props.Document;
         }
         allowDuplicates = true;
-        const value = Cast(this.dataDoc[this.dataField], listSpec(Doc));
+        let targetDataDoc = this.props.fieldExt || this.props.Document.isTemplate ? this.extensionDoc : this.props.Document;
+        let targetField = (this.props.fieldExt || this.props.Document.isTemplate) && this.props.fieldExt ? this.props.fieldExt : this.props.fieldKey;
+        const value = Cast(targetDataDoc[targetField], listSpec(Doc));
         if (value !== undefined) {
             if (allowDuplicates || !value.some(v => v instanceof Doc && v[Id] === doc[Id])) {
                 value.push(doc);
             }
         } else {
-            Doc.GetProto(this.dataDoc)[this.dataField] = new List([doc]);
+            Doc.GetProto(targetDataDoc)[targetField] = new List([doc]);
         }
         return true;
     }
@@ -98,7 +104,9 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
         let docView = DocumentManager.Instance.getDocumentView(doc, this.props.ContainingCollectionView);
         docView && SelectionManager.DeselectDoc(docView);
         //TODO This won't create the field if it doesn't already exist
-        const value = Cast(this.dataDoc[this.dataField], listSpec(Doc), []);
+        let targetDataDoc = this.props.fieldExt || this.props.Document.isTemplate ? this.extensionDoc : this.props.Document;
+        let targetField = (this.props.fieldExt || this.props.Document.isTemplate) && this.props.fieldExt ? this.props.fieldExt : this.props.fieldKey;
+        let value = Cast(targetDataDoc[targetField], listSpec(Doc), []);
         let index = value.reduce((p, v, i) => (v instanceof Doc && v[Id] === doc[Id]) ? i : p, -1);
         PromiseValue(Cast(doc.annotationOn, Doc)).then(annotationOn =>
             annotationOn === this.dataDoc.Document && (doc.annotationOn = undefined)
@@ -116,7 +124,10 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
 
     @action.bound
     moveDocument(doc: Doc, targetCollection: Doc, addDocument: (doc: Doc) => boolean): boolean {
-        if (Doc.AreProtosEqual(this.dataDoc, targetCollection)) {
+        let self = this;
+        let targetDataDoc = this.props.fieldExt || this.props.Document.isTemplate ? this.extensionDoc : this.props.Document;
+        if (Doc.AreProtosEqual(targetDataDoc, targetCollection)) {
+            //if (Doc.AreProtosEqual(this.extensionDoc, targetCollection)) {
             return true;
         }
         if (this.removeDocument(doc)) {
diff --git a/src/client/views/collections/CollectionPDFView.tsx b/src/client/views/collections/CollectionPDFView.tsx
index 31a73ab36..c97443785 100644
--- a/src/client/views/collections/CollectionPDFView.tsx
+++ b/src/client/views/collections/CollectionPDFView.tsx
@@ -47,8 +47,8 @@ export class CollectionPDFView extends React.Component<FieldViewProps> {
         this._reactionDisposer && this._reactionDisposer();
     }
 
-    public static LayoutString(fieldKey: string = "data") {
-        return FieldView.LayoutString(CollectionPDFView, fieldKey);
+    public static LayoutString(fieldKey: string = "data", fieldExt: string = "annotations") {
+        return FieldView.LayoutString(CollectionPDFView, fieldKey, fieldExt);
     }
     @observable _inThumb = false;
 
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 372891923..9266fc8fd 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -50,11 +50,11 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     }
 
     overlays = (doc: Doc) => {
-        return doc.type === DocTypes.IMG ? { title: "title", caption: "caption" } : {}
+        return doc.type === DocTypes.IMG ? { title: "title", caption: "caption" } : {};
     }
 
     getDisplayDoc(layoutDoc: Doc, d: Doc, dxf: () => Transform) {
-        let dataDoc = d !== this.props.DataDoc ? this.props.DataDoc : undefined
+        let dataDoc = d !== this.props.DataDoc ? this.props.DataDoc : undefined;
         let width = () => d.nativeWidth ? Math.min(layoutDoc[WidthSym](), this.columnWidth) : this.columnWidth;
         let height = () => this.getDocHeight(layoutDoc);
         let finalDxf = () => dxf().scale(this.columnWidth / layoutDoc[WidthSym]());
@@ -75,7 +75,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
             addDocTab={this.props.addDocTab}
             setPreviewScript={emptyFunction}
             previewScript={undefined}>
-        </CollectionSchemaPreview>
+        </CollectionSchemaPreview>;
     }
     getDocHeight(d: Doc) {
         let nw = NumCast(d.nativeWidth);
@@ -176,7 +176,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
                 if (where[0] > pos[0] && where[0] < pos1[0] && where[1] > pos[1] && where[1] < pos1[1]) {
                     targInd = i;
                 }
-            })
+            });
         }
         if (super.drop(e, de)) {
             let newDoc = de.data.droppedDocuments[0];
@@ -202,7 +202,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
             if (where[0] > pos[0] && where[0] < pos1[0] && where[1] > pos[1] && where[1] < pos1[1]) {
                 targInd = i;
             }
-        })
+        });
         super.onDrop(e, {}, () => {
             if (targInd !== -1) {
                 let newDoc = this.childDocs[this.childDocs.length - 1];
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 496f7e461..e72e69330 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -46,18 +46,20 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
             this.createDropTarget(ele);
         }
 
-        @computed get extensionDoc() { return Doc.resolvedFieldDataDoc(BoolCast(this.props.Document.isTemplate) && this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, this.props.fieldExt); }
+        public get isAnnotationOverlay() { return this.props.fieldKey === "annotations" || this.props.fieldExt === "annotations"; }
+        @computed get extensionDoc() { return Doc.resolvedFieldDataDoc(BoolCast(this.props.Document.isTemplate) && this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, this.isAnnotationOverlay ? "dummy" : ""); }
+
 
         get childDocs() {
             let self = this;
             //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 DocListCast((BoolCast(this.props.Document.isTemplate) ? this.extensionDoc : this.props.Document)[this.props.fieldExt ? this.props.fieldExt : this.props.fieldKey]);
+            return DocListCast(this.extensionDoc[this.props.fieldExt ? this.props.fieldExt : this.props.fieldKey]);
         }
         get childDocList() {
             //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((BoolCast(this.props.Document.isTemplate) ? this.extensionDoc : this.props.Document)[this.props.fieldExt ? this.props.fieldExt : this.props.fieldKey], listSpec(Doc));
+            return Cast(this.extensionDoc[this.props.fieldExt ? this.props.fieldExt : this.props.fieldKey], listSpec(Doc));
         }
 
         @action
@@ -102,7 +104,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
                 } else if (de.data.moveDocument) {
                     let movedDocs = de.data.options === this.props.Document[Id] ? de.data.draggedDocuments : de.data.droppedDocuments;
                     added = movedDocs.reduce((added: boolean, d) =>
-                        de.data.moveDocument(d, this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.addDocument) || added, false);
+                        de.data.moveDocument(d, /*this.props.DataDoc ? this.props.DataDoc :*/ this.props.Document, this.props.addDocument) || added, false);
                 } else {
                     added = de.data.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d) || added, false);
                 }
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 3674a743a..1280d515c 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -75,7 +75,7 @@ class TreeView extends React.Component<TreeViewProps> {
     @computed get fieldKey() {
         let keys = Array.from(Object.keys(this.resolvedDataDoc));  // bcz: Argh -- make untracked to avoid this rerunning whenever 'libraryBrush' is set
         if (this.resolvedDataDoc.proto instanceof Doc) {
-            let arr = Array.from(Object.keys(this.resolvedDataDoc.proto!));// bcz: Argh -- make untracked to avoid this rerunning whenever 'libraryBrush' is set
+            let arr = Array.from(Object.keys(this.resolvedDataDoc.proto));// bcz: Argh -- make untracked to avoid this rerunning whenever 'libraryBrush' is set
             keys.push(...arr);
             while (keys.indexOf("proto") !== -1) keys.splice(keys.indexOf("proto"), 1);
         }
diff --git a/src/client/views/collections/CollectionVideoView.tsx b/src/client/views/collections/CollectionVideoView.tsx
index 1984965ba..16287a658 100644
--- a/src/client/views/collections/CollectionVideoView.tsx
+++ b/src/client/views/collections/CollectionVideoView.tsx
@@ -21,8 +21,8 @@ import { Docs, DocUtils } from "../../documents/Documents";
 export class CollectionVideoView extends React.Component<FieldViewProps> {
     private _videoBox?: VideoBox;
 
-    public static LayoutString(fieldKey: string = "data") {
-        return FieldView.LayoutString(CollectionVideoView, fieldKey);
+    public static LayoutString(fieldKey: string = "data", fieldExt: string = "annotations") {
+        return FieldView.LayoutString(CollectionVideoView, fieldKey, fieldExt);
     }
     private get uIButtons() {
         let scaling = Math.min(1.8, this.props.ScreenToLocalTransform().Scale);
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index e500e5c70..e69696390 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -27,7 +27,7 @@ library.add(faThList);
 
 @observer
 export class CollectionView extends React.Component<FieldViewProps> {
-    public static LayoutString(fieldStr: string = "data") { return FieldView.LayoutString(CollectionView, fieldStr); }
+    public static LayoutString(fieldStr: string = "data", fieldExt: string = "") { return FieldView.LayoutString(CollectionView, fieldStr, fieldExt); }
 
     private SubView = (type: CollectionViewType, renderProps: CollectionRenderProps) => {
         let props = { ...this.props, ...renderProps };
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index ccf261c95..51cfa77b1 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -38,7 +38,7 @@
     // }
     box-shadow: $intermediate-color 0.2vw 0.2vw 0.8vw;
     border: 0px solid $light-color-secondary;
-    border-radius: $border-radius;
+    border-radius: inherit;
     box-sizing: border-box;
     position: absolute;
 
@@ -71,7 +71,7 @@
 
     opacity: 0.99;
     border: 0px solid transparent;
-    border-radius: $border-radius;
+    border-radius: inherit;
     box-sizing: border-box;
     position:absolute;
     z-index: -1;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 2c9b6b6a9..c931c74bc 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -54,7 +54,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
     @computed get nativeWidth() { return this.Document.nativeWidth || 0; }
     @computed get nativeHeight() { return this.Document.nativeHeight || 0; }
-    public get isAnnotationOverlay() { return this.props.fieldKey === "annotations" || this.props.fieldExt === "annotations"; }
+    public get isAnnotationOverlay() { return this.props.fieldExt === "annotations"; }
     private get borderWidth() { return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH; }
     private panX = () => this.props.fitToBox ? this.props.fitToBox()[0] : this.Document.panX || 0;
     private panY = () => this.props.fitToBox ? this.props.fitToBox()[1] : this.Document.panY || 0;
@@ -86,6 +86,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         });
     }
 
+    @computed get extensionDoc() { return Doc.resolvedFieldDataDoc(BoolCast(this.props.Document.isTemplate) && this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, this.isAnnotationOverlay ? "dummy" : ""); }
+
+
     @undoBatch
     @action
     drop = (e: Event, de: DragManager.DropEvent) => {
@@ -93,10 +96,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             if (de.data instanceof DragManager.DocumentDragData) {
                 if (de.data.droppedDocuments.length) {
                     let dragDoc = de.data.droppedDocuments[0];
-                    let zoom = NumCast(dragDoc.zoomBasis, 1);
                     let [xp, yp] = this.getTransform().transformPoint(de.x, de.y);
-                    let x = xp - de.data.xOffset / zoom;
-                    let y = yp - de.data.yOffset / zoom;
+                    let x = xp - de.data.xOffset;
+                    let y = yp - de.data.yOffset;
                     let dropX = NumCast(de.data.droppedDocuments[0].x);
                     let dropY = NumCast(de.data.droppedDocuments[0].y);
                     de.data.droppedDocuments.forEach(d => {
@@ -117,10 +119,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             else if (de.data instanceof DragManager.AnnotationDragData) {
                 if (de.data.dropDocument) {
                     let dragDoc = de.data.dropDocument;
-                    let zoom = NumCast(dragDoc.zoomBasis, 1);
                     let [xp, yp] = this.getTransform().transformPoint(de.x, de.y);
-                    let x = xp - de.data.xOffset / zoom;
-                    let y = yp - de.data.yOffset / zoom;
+                    let x = xp - de.data.xOffset;
+                    let y = yp - de.data.yOffset;
                     let dropX = NumCast(de.data.dropDocument.x);
                     let dropY = NumCast(de.data.dropDocument.y);
                     dragDoc.x = x + NumCast(dragDoc.x) - dropX;
@@ -159,14 +160,14 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             if (!this.isAnnotationOverlay) {
                 PDFMenu.Instance.fadeOut(true);
                 let minx = docs.length ? NumCast(docs[0].x) : 0;
-                let maxx = docs.length ? NumCast(docs[0].width) / NumCast(docs[0].zoomBasis, 1) + minx : minx;
+                let maxx = docs.length ? NumCast(docs[0].width) + minx : minx;
                 let miny = docs.length ? NumCast(docs[0].y) : 0;
-                let maxy = docs.length ? NumCast(docs[0].height) / NumCast(docs[0].zoomBasis, 1) + miny : miny;
+                let maxy = docs.length ? NumCast(docs[0].height) + miny : miny;
                 let ranges = docs.filter(doc => doc).reduce((range, doc) => {
                     let x = NumCast(doc.x);
-                    let xe = x + NumCast(doc.width) / NumCast(doc.zoomBasis, 1);
+                    let xe = x + NumCast(doc.width);
                     let y = NumCast(doc.y);
-                    let ye = y + NumCast(doc.height) / NumCast(doc.zoomBasis, 1);
+                    let ye = y + NumCast(doc.height);
                     return [[range[0][0] > x ? x : range[0][0], range[0][1] < xe ? xe : range[0][1]],
                     [range[1][0] > y ? y : range[1][0], range[1][1] < ye ? ye : range[1][1]]];
                 }, [[minx, maxx], [miny, maxy]]);
@@ -299,8 +300,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             }
         }
         SelectionManager.DeselectAll();
-        const newPanX = NumCast(doc.x) + NumCast(doc.width) / NumCast(doc.zoomBasis, 1) / 2;
-        const newPanY = NumCast(doc.y) + NumCast(doc.height) / NumCast(doc.zoomBasis, 1) / 2;
+        const newPanX = NumCast(doc.x) + NumCast(doc.width) / 2;
+        const newPanY = NumCast(doc.y) + NumCast(doc.height) / 2;
         const newState = HistoryUtil.getState();
         newState.initializers[id] = { panX: newPanX, panY: newPanY };
         HistoryUtil.pushState(newState);
@@ -501,19 +502,17 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         const containerName = `collectionfreeformview${this.isAnnotationOverlay ? "-overlay" : "-container"}`;
         const easing = () => this.props.Document.panTransformType === "Ease";
 
-        if (this.props.fieldExt) Doc.UpdateDocumentExtensionForField(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey);
+        Doc.UpdateDocumentExtensionForField(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey);
         return (
             <div className={containerName} ref={this.createDropTarget} onWheel={this.onPointerWheel}
-                style={{ borderRadius: "inherit" }}
                 onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onDragOver={this.onDragOver} onContextMenu={this.onContextMenu}>
                 <MarqueeView container={this} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments} isSelected={this.props.isSelected}
                     addDocument={this.addDocument} removeDocument={this.props.removeDocument} addLiveTextDocument={this.addLiveTextBox}
                     getContainerTransform={this.getContainerTransform} getTransform={this.getTransform}>
                     <CollectionFreeFormViewPannableContents centeringShiftX={this.centeringShiftX} centeringShiftY={this.centeringShiftY}
                         easing={easing} zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}>
-
                         <CollectionFreeFormLinksView {...this.props} key="freeformLinks">
-                            <InkingCanvas getScreenTransform={this.getTransform} Document={this.extensionDoc} inkFieldKey={this.props.fieldExt ? "ink" : this.props.fieldKey + "_ink"} >
+                            <InkingCanvas getScreenTransform={this.getTransform} Document={this.extensionDoc} inkFieldKey={"ink"} >
                                 {this.childViews}
                             </InkingCanvas>
                         </CollectionFreeFormLinksView>
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 6f3adb6b5..613cbf98e 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -530,7 +530,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 this.props.addDocument && this.props.addDocument(coll);
                 this.props.removeDocument && this.props.removeDocument(this.props.Document);
             }, icon: "window-restore"
-        })
+        });
         cm.addItem({
             description: "Find aliases", event: async () => {
                 const aliases = await SearchUtil.GetAliasesOfDocument(this.props.Document);
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index c5fc6c65a..abd0d9e12 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -55,8 +55,8 @@ export interface FieldViewProps {
 
 @observer
 export class FieldView extends React.Component<FieldViewProps> {
-    public static LayoutString(fieldType: { name: string }, fieldStr: string = "data") {
-        return `<${fieldType.name} {...props} fieldKey={"${fieldStr}"} />`;
+    public static LayoutString(fieldType: { name: string }, fieldStr: string = "data", fieldExt: string = "") {
+        return `<${fieldType.name} {...props} fieldKey={"${fieldStr}"} fieldExt={"${fieldExt}"} />`;
     }
 
     @computed
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 5475aa2b2..084fd4c96 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -232,9 +232,10 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 return field ? field.Data : `{"doc":{"type":"doc","content":[]},"selection":{"type":"text","anchor":0,"head":0}}`;
             },
             field2 => {
-                if (StrCast(this.props.Document.layout).indexOf("\"" + this.props.fieldKey + "\"") !== -1) // bcz: UGH!  why is this needed... something is happening out of order.  test with making a collection, then adding a text note and converting that to a template field.
+                if (StrCast(this.props.Document.layout).indexOf("\"" + this.props.fieldKey + "\"") !== -1) { // bcz: UGH!  why is this needed... something is happening out of order.  test with making a collection, then adding a text note and converting that to a template field.
                     this._editorView && !this._applyingChange &&
                         this._editorView.updateState(EditorState.fromJSON(config, JSON.parse(field2)));
+                }
             }
         );
 
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 8af29110f..8bba05850 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -588,7 +588,7 @@ export class Viewer extends React.Component<IViewerProps> {
             }
             return true;
         });
-        this.Index = Math.min(this.Index + 1, filtered.length - 1)
+        this.Index = Math.min(this.Index + 1, filtered.length - 1);
     }
 
     nextResult = () => {
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 3d46243e0..cc0dc71dd 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -372,7 +372,7 @@ export namespace Doc {
         }
         let layout = StrCast(fieldLayoutDoc.layout).replace(/fieldKey={"[^"]*"}/, `fieldKey={"${metaKey}"}`);
         if (backgroundLayout) {
-            layout = StrCast(fieldLayoutDoc.layout).replace(/fieldKey={"annotations"}/, `fieldKey={"${metaKey}"} fieldExt={"annotations"}`);
+            layout = StrCast(fieldLayoutDoc.layout).replace(/fieldKey={"[^"]*"}/, `fieldKey={"${metaKey}"} fieldExt={"annotations"}`);
             backgroundLayout = backgroundLayout.replace(/fieldKey={"[^"]*"}/, `fieldKey={"${metaKey}"}`);
         }
         let nw = Cast(fieldTemplate.nativeWidth, "number");
-- 
cgit v1.2.3-70-g09d2


From a69a43015ec414bd1a9577c03fbeb5c3b27fb55f Mon Sep 17 00:00:00 2001
From: yipstanley <stanley_yip@brown.edu>
Date: Thu, 11 Jul 2019 12:36:34 -0400
Subject: fixes

---
 src/client/util/DocumentManager.ts                 |  8 +++--
 src/client/util/DragManager.ts                     | 35 ++++++++++++--------
 src/client/util/LinkManager.ts                     |  6 ++--
 src/client/util/RichTextSchema.tsx                 | 14 ++++++++
 src/client/util/TooltipTextMenu.tsx                |  2 ++
 src/client/views/MainView.tsx                      |  6 ++--
 .../views/collections/CollectionTreeView.tsx       |  9 +++++-
 src/client/views/nodes/LinkEditor.tsx              | 37 +++++++++++++---------
 src/client/views/nodes/LinkMenuGroup.tsx           | 16 ++++++++--
 src/client/views/nodes/PDFBox.tsx                  |  8 +++--
 src/client/views/pdf/PDFMenu.tsx                   |  5 +--
 src/client/views/pdf/PDFViewer.tsx                 |  4 +--
 12 files changed, 102 insertions(+), 48 deletions(-)

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

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 8fa460bca..bb1345044 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -111,9 +111,11 @@ export class DocumentManager {
             pairs.push(...linksList.reduce((pairs, link) => {
                 if (link) {
                     let linkToDoc = LinkManager.Instance.getOppositeAnchor(link, dv.props.Document);
-                    DocumentManager.Instance.getDocumentViews(linkToDoc).map(docView1 => {
-                        pairs.push({ a: dv, b: docView1, l: link });
-                    });
+                    if (linkToDoc) {
+                        DocumentManager.Instance.getDocumentViews(linkToDoc).map(docView1 => {
+                            pairs.push({ a: dv, b: docView1, l: link });
+                        });
+                    }
                 }
                 return pairs;
             }, [] as { a: DocumentView, b: DocumentView, l: Doc }[]));
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index cb71db2c5..8e44631a3 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -61,16 +61,18 @@ export function SetupDrag(
 export async function DragLinkAsDocument(dragEle: HTMLElement, x: number, y: number, linkDoc: Doc, sourceDoc: Doc) {
     let draggeddoc = LinkManager.Instance.getOppositeAnchor(linkDoc, sourceDoc);
 
-    let moddrag = await Cast(draggeddoc.annotationOn, Doc);
-    let dragdocs = moddrag ? [moddrag] : [draggeddoc];
-    let dragData = new DragManager.DocumentDragData(dragdocs, dragdocs);
-    dragData.dropAction = "alias" as dropActionType;
-    DragManager.StartLinkedDocumentDrag([dragEle], sourceDoc, dragData, x, y, {
-        handlers: {
-            dragComplete: action(emptyFunction),
-        },
-        hideSource: false
-    });
+    if (draggeddoc) {
+        let moddrag = await Cast(draggeddoc.annotationOn, Doc);
+        let dragdocs = moddrag ? [moddrag] : [draggeddoc];
+        let dragData = new DragManager.DocumentDragData(dragdocs, dragdocs);
+        dragData.dropAction = "alias" as dropActionType;
+        DragManager.StartLinkedDocumentDrag([dragEle], sourceDoc, dragData, x, y, {
+            handlers: {
+                dragComplete: action(emptyFunction),
+            },
+            hideSource: false
+        });
+    }
 }
 
 export async function DragLinksAsDocuments(dragEle: HTMLElement, x: number, y: number, sourceDoc: Doc) {
@@ -80,9 +82,16 @@ export async function DragLinksAsDocuments(dragEle: HTMLElement, x: number, y: n
     if (srcTarg) {
         let linkDocs = LinkManager.Instance.getAllRelatedLinks(srcTarg);
         if (linkDocs) {
-            draggedDocs = linkDocs.map(link => {
-                return LinkManager.Instance.getOppositeAnchor(link, sourceDoc);
-            });
+            linkDocs.forEach((doc) => {
+                let opp = LinkManager.Instance.getOppositeAnchor(doc, sourceDoc);
+                if (opp) {
+                    draggedDocs.push(opp);
+                }
+            }
+            )
+            // draggedDocs = linkDocs.map(link => {
+            //     return LinkManager.Instance.getOppositeAnchor(link, sourceDoc);
+            // });
         }
     }
     if (draggedDocs.length) {
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index 944bc532f..1ed040aa4 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -232,11 +232,11 @@ export class LinkManager {
     // finds the opposite anchor of a given anchor in a link
     //TODO This should probably return undefined if there isn't an opposite anchor
     //TODO This should also await the return value of the anchor so we don't filter out promises
-    public getOppositeAnchor(linkDoc: Doc, anchor: Doc): Doc {
+    public getOppositeAnchor(linkDoc: Doc, anchor: Doc): Doc | undefined {
         if (Doc.AreProtosEqual(anchor, Cast(linkDoc.anchor1, Doc, null))) {
-            return Cast(linkDoc.anchor2, Doc, null)!;
+            return Cast(linkDoc.anchor2, Doc, null);
         } else {
-            return Cast(linkDoc.anchor1, Doc, null)!;
+            return Cast(linkDoc.anchor1, Doc, null);
         }
     }
 }
\ No newline at end of file
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 2a57180d3..b6402da13 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -400,6 +400,20 @@ export const marks: { [index: string]: MarkSpec } = {
         }]
     },
 
+    p18: {
+        parseDOM: [{ style: 'font-size: 18px;' }],
+        toDOM: () => ['span', {
+            style: 'font-size: 18px;'
+        }]
+    },
+
+    p20: {
+        parseDOM: [{ style: 'font-size: 20px;' }],
+        toDOM: () => ['span', {
+            style: 'font-size: 20px;'
+        }]
+    },
+
     p24: {
         parseDOM: [{ style: 'font-size: 24px;' }],
         toDOM: () => ['span', {
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index cb7ed976a..81a966923 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -107,6 +107,8 @@ export class TooltipTextMenu {
         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.p18, 18);
+        this.fontSizeToNum.set(schema.marks.p20, 20);
         this.fontSizeToNum.set(schema.marks.p24, 24);
         this.fontSizeToNum.set(schema.marks.p32, 32);
         this.fontSizeToNum.set(schema.marks.p48, 48);
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index bf4ee84d6..df5c2020a 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -394,9 +394,9 @@ export class MainView extends React.Component {
                         </div></li>)}
                     <li key="undoTest"><button className="add-button round-button" onClick={() => UndoManager.PrintBatches()}><FontAwesomeIcon icon="exclamation" size="sm" /></button></li>
                     <li key="ink" style={{ paddingRight: "6px" }}><button className="toolbar-button round-button" title="Ink" onClick={() => InkingControl.Instance.toggleDisplay()}><FontAwesomeIcon icon="pen-nib" size="sm" /> </button></li>
-                    <li key="pen"><button onClick={() => InkingControl.Instance.switchTool(InkTool.Pen)} style={this.selected(InkTool.Pen)}><FontAwesomeIcon icon="pen" size="lg" title="Pen" /></button></li>
-                    <li key="marker"><button onClick={() => InkingControl.Instance.switchTool(InkTool.Highlighter)} style={this.selected(InkTool.Highlighter)}><FontAwesomeIcon icon="highlighter" size="lg" title="Pen" /></button></li>
-                    <li key="eraser"><button onClick={() => InkingControl.Instance.switchTool(InkTool.Eraser)} style={this.selected(InkTool.Eraser)}><FontAwesomeIcon icon="eraser" size="lg" title="Pen" /></button></li>
+                    <li key="pen"><button onClick={() => InkingControl.Instance.switchTool(InkTool.Pen)} title="Pen" style={this.selected(InkTool.Pen)}><FontAwesomeIcon icon="pen" size="lg" /></button></li>
+                    <li key="marker"><button onClick={() => InkingControl.Instance.switchTool(InkTool.Highlighter)} title="Highlighter" style={this.selected(InkTool.Highlighter)}><FontAwesomeIcon icon="highlighter" size="lg" /></button></li>
+                    <li key="eraser"><button onClick={() => InkingControl.Instance.switchTool(InkTool.Eraser)} title="Eraser" style={this.selected(InkTool.Eraser)}><FontAwesomeIcon icon="eraser" size="lg" /></button></li>
                     <li key="inkControls"><InkingControl /></li>
                 </ul>
             </div>
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 3674a743a..f8e5f72e4 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -301,7 +301,14 @@ class TreeView extends React.Component<TreeViewProps> {
         let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.props.document, this._chosenKey, doc, addBefore, before);
         let groups = LinkManager.Instance.getRelatedGroupedLinks(this.props.document);
         groups.forEach((groupLinkDocs, groupType) => {
-            let destLinks = groupLinkDocs.map(d => LinkManager.Instance.getOppositeAnchor(d, this.props.document));
+            // let destLinks = groupLinkDocs.map(d => LinkManager.Instance.getOppositeAnchor(d, this.props.document));
+            let destLinks: Doc[] = [];
+            groupLinkDocs.forEach((doc) => {
+                let opp = LinkManager.Instance.getOppositeAnchor(doc, this.props.document);
+                if (opp) {
+                    destLinks.push(opp);
+                }
+            });
             ele.push(
                 <div key={"treeviewlink-" + groupType + "subtitle"}>
                     <div className="collectionTreeView-subtitle">{groupType}:</div>
diff --git a/src/client/views/nodes/LinkEditor.tsx b/src/client/views/nodes/LinkEditor.tsx
index a97ec8831..df56a19d0 100644
--- a/src/client/views/nodes/LinkEditor.tsx
+++ b/src/client/views/nodes/LinkEditor.tsx
@@ -200,7 +200,9 @@ export class LinkGroupEditor extends React.Component<LinkGroupEditorProps> {
         destGroupDoc.type = groupType;
         destGroupDoc.metadata = destMdDoc;
 
-        LinkManager.Instance.addGroupToAnchor(this.props.linkDoc, destDoc, destGroupDoc, true);
+        if (destDoc) {
+            LinkManager.Instance.addGroupToAnchor(this.props.linkDoc, destDoc, destGroupDoc, true);
+        }
     }
 
     @action
@@ -308,7 +310,10 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
         // create new metadata document for group
         let mdDoc = new Doc();
         mdDoc.anchor1 = this.props.sourceDoc.title;
-        mdDoc.anchor2 = LinkManager.Instance.getOppositeAnchor(this.props.linkDoc, this.props.sourceDoc).title;
+        let opp = LinkManager.Instance.getOppositeAnchor(this.props.linkDoc, this.props.sourceDoc);
+        if (opp) {
+            mdDoc.anchor2 = opp.title;
+        }
 
         // create new group document
         let groupDoc = new Doc();
@@ -326,20 +331,22 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
             return <LinkGroupEditor key={"gred-" + StrCast(groupDoc.type)} linkDoc={this.props.linkDoc} sourceDoc={this.props.sourceDoc} groupDoc={groupDoc} />;
         });
 
-        return (
-            <div className="linkEditor">
-                <button className="linkEditor-back" onPointerDown={() => this.props.showLinks()}><FontAwesomeIcon icon="arrow-left" size="sm" /></button>
-                <div className="linkEditor-info">
-                    <p className="linkEditor-linkedTo">editing link to: <b>{destination.proto!.title}</b></p>
-                    <button className="linkEditor-button" onPointerDown={() => this.deleteLink()} title="Delete link"><FontAwesomeIcon icon="trash" size="sm" /></button>
-                </div>
-                <div className="linkEditor-groupsLabel">
-                    <b>Relationships:</b>
-                    <button className="linkEditor-button" onClick={() => this.addGroup()} title=" Add Group"><FontAwesomeIcon icon="plus" size="sm" /></button>
+        if (destination) {
+            return (
+                <div className="linkEditor">
+                    <button className="linkEditor-back" onPointerDown={() => this.props.showLinks()}><FontAwesomeIcon icon="arrow-left" size="sm" /></button>
+                    <div className="linkEditor-info">
+                        <p className="linkEditor-linkedTo">editing link to: <b>{destination.proto!.title}</b></p>
+                        <button className="linkEditor-button" onPointerDown={() => this.deleteLink()} title="Delete link"><FontAwesomeIcon icon="trash" size="sm" /></button>
+                    </div>
+                    <div className="linkEditor-groupsLabel">
+                        <b>Relationships:</b>
+                        <button className="linkEditor-button" onClick={() => this.addGroup()} title=" Add Group"><FontAwesomeIcon icon="plus" size="sm" /></button>
+                    </div>
+                    {groups.length > 0 ? groups : <div className="linkEditor-group">There are currently no relationships associated with this link.</div>}
                 </div>
-                {groups.length > 0 ? groups : <div className="linkEditor-group">There are currently no relationships associated with this link.</div>}
-            </div>
 
-        );
+            );
+        }
     }
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/LinkMenuGroup.tsx b/src/client/views/nodes/LinkMenuGroup.tsx
index e4cf56d20..97f2e13ae 100644
--- a/src/client/views/nodes/LinkMenuGroup.tsx
+++ b/src/client/views/nodes/LinkMenuGroup.tsx
@@ -45,7 +45,15 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
             document.removeEventListener("pointermove", this.onLinkButtonMoved);
             document.removeEventListener("pointerup", this.onLinkButtonUp);
 
-            let draggedDocs = this.props.group.map(linkDoc => LinkManager.Instance.getOppositeAnchor(linkDoc, this.props.sourceDoc));
+            let draggedDocs: Doc[] = [];
+            this.props.group.forEach(
+                (doc: Doc) => {
+                    let opp = LinkManager.Instance.getOppositeAnchor(doc, this.props.sourceDoc);
+                    if (opp) {
+                        draggedDocs.push(opp);
+                    }
+                }
+            )
             let dragData = new DragManager.DocumentDragData(draggedDocs, draggedDocs.map(d => undefined));
 
             DragManager.StartLinkedDocumentDrag([this._drag.current], this.props.sourceDoc, dragData, e.x, e.y, {
@@ -72,8 +80,10 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
     render() {
         let groupItems = this.props.group.map(linkDoc => {
             let destination = LinkManager.Instance.getOppositeAnchor(linkDoc, this.props.sourceDoc);
-            return <LinkMenuItem key={destination[Id] + this.props.sourceDoc[Id]} groupType={this.props.groupType}
-                linkDoc={linkDoc} sourceDoc={this.props.sourceDoc} destinationDoc={destination} showEditor={this.props.showEditor} />;
+            if (destination) {
+                return <LinkMenuItem key={destination[Id] + this.props.sourceDoc[Id]} groupType={this.props.groupType}
+                    linkDoc={linkDoc} sourceDoc={this.props.sourceDoc} destinationDoc={destination} showEditor={this.props.showEditor} />;
+            }
         });
 
         return (
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index cc02bb282..2ff5c089f 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -23,9 +23,11 @@ import { CompileScript } from '../../util/Scripting';
 import { Flyout, anchorPoints } from '../DocumentDecorations';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { ScriptField } from '../../../new_fields/ScriptField';
+import { KeyCodes } from '../../northstar/utils/KeyCodes';
 
 type PdfDocument = makeInterface<[typeof positionSchema, typeof pageSchema]>;
 const PdfDocument = makeInterface(positionSchema, pageSchema);
+export const handleBackspace = (e: React.KeyboardEvent) => { if (e.keyCode === KeyCodes.BACKSPACE) e.stopPropagation() }
 
 @observer
 export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocument) {
@@ -175,13 +177,13 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
                             Annotation View Settings
                         </div>
                         <div className="pdfBox-settingsFlyout-kvpInput">
-                            <input placeholder="Key" className="pdfBox-settingsFlyout-input" onChange={this.newKeyChange}
+                            <input placeholder="Key" className="pdfBox-settingsFlyout-input" onKeyDown={handleBackspace} onChange={this.newKeyChange}
                                 style={{ gridColumn: 1 }} ref={this._keyRef} />
-                            <input placeholder="Value" className="pdfBox-settingsFlyout-input" onChange={this.newValueChange}
+                            <input placeholder="Value" className="pdfBox-settingsFlyout-input" onKeyDown={handleBackspace} onChange={this.newValueChange}
                                 style={{ gridColumn: 3 }} ref={this._valueRef} />
                         </div>
                         <div className="pdfBox-settingsFlyout-kvpInput">
-                            <input placeholder="Custom Script" onChange={this.newScriptChange} style={{ gridColumn: "1 / 4" }} ref={this._scriptRef} />
+                            <input placeholder="Custom Script" onChange={this.newScriptChange} onKeyDown={handleBackspace} style={{ gridColumn: "1 / 4" }} ref={this._scriptRef} />
                         </div>
                         <div className="pdfBox-settingsFlyout-kvpInput">
                             <button style={{ gridColumn: 1 }} onClick={this.resetFilters}>
diff --git a/src/client/views/pdf/PDFMenu.tsx b/src/client/views/pdf/PDFMenu.tsx
index d6970e7f4..feaa1f652 100644
--- a/src/client/views/pdf/PDFMenu.tsx
+++ b/src/client/views/pdf/PDFMenu.tsx
@@ -5,6 +5,7 @@ import { observer } from "mobx-react";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { emptyFunction, returnFalse } from "../../../Utils";
 import { Doc } from "../../../new_fields/Doc";
+import { handleBackspace } from "../nodes/PDFBox";
 
 @observer
 export default class PDFMenu extends React.Component {
@@ -251,8 +252,8 @@ export default class PDFMenu extends React.Component {
                 <button className="pdfMenu-button" title="Delete Anchor" onPointerDown={this.deleteClicked}><FontAwesomeIcon icon="trash-alt" size="lg" key="1" /></button>,
                 <button className="pdfMenu-button" title="Pin to Presentation" onPointerDown={this.PinToPres}><FontAwesomeIcon icon="map-pin" size="lg" key="2" /></button>,
                 <div className="pdfMenu-addTag" key="3">
-                    <input onChange={this.keyChanged} placeholder="Key" style={{ gridColumn: 1 }} />
-                    <input onChange={this.valueChanged} placeholder="Value" style={{ gridColumn: 3 }} />
+                    <input onKeyDown={handleBackspace} onChange={this.keyChanged} placeholder="Key" style={{ gridColumn: 1 }} />
+                    <input onKeyDown={handleBackspace} onChange={this.valueChanged} placeholder="Value" style={{ gridColumn: 3 }} />
                 </div>,
                 <button className="pdfMenu-button" title={`Add tag: ${this._keyValue} with value: ${this._valueValue}`} onPointerDown={this.addTag}><FontAwesomeIcon style={{ transition: "all .2s" }} color={this._added ? "#42f560" : "white"} icon="check" size="lg" key="4" /></button>,
             ];
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 8af29110f..761db7922 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -14,7 +14,7 @@ import { Docs, DocUtils, DocumentOptions } from "../../documents/Documents";
 import { DocumentManager } from "../../util/DocumentManager";
 import { DragManager } from "../../util/DragManager";
 import { DocumentView } from "../nodes/DocumentView";
-import { PDFBox } from "../nodes/PDFBox";
+import { PDFBox, handleBackspace } from "../nodes/PDFBox";
 import Page from "./Page";
 import "./PDFViewer.scss";
 import React = require("react");
@@ -648,7 +648,7 @@ export class Viewer extends React.Component<IViewerProps> {
                     <button className="pdfViewer-overlayButton" title="Open Search Bar"></button>
                     {/* <button title="Previous Result" onClick={() => this.search(this._searchString)}><FontAwesomeIcon icon="arrow-up" size="3x" color="white" /></button>
                     <button title="Next Result" onClick={this.nextResult}><FontAwesomeIcon icon="arrow-down" size="3x" color="white" /></button> */}
-                    <input placeholder="Search" className="pdfViewer-overlaySearchBar" onChange={this.searchStringChanged} />
+                    <input onKeyDown={handleBackspace} placeholder="Search" className="pdfViewer-overlaySearchBar" onChange={this.searchStringChanged} />
                     <button title="Search" onClick={() => this.search(this._searchString)}><FontAwesomeIcon icon="search" size="3x" color="white" /></button>
                 </div>
                 <button className="pdfViewer-overlayButton" onClick={this.prevAnnotation} title="Previous Annotation"
-- 
cgit v1.2.3-70-g09d2


From 59d32987f4f220c97a3b3cd4886ba47f3e8c4341 Mon Sep 17 00:00:00 2001
From: yipstanley <stanley_yip@brown.edu>
Date: Fri, 12 Jul 2019 20:18:24 -0400
Subject: second link icon commented out in rich text bar

---
 src/client/util/TooltipTextMenu.tsx | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

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

diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 930379c65..d287ff9b1 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -252,7 +252,7 @@ export class TooltipTextMenu {
             this.linkEditor.appendChild(this.linkDrag);
             // this.linkEditor.appendChild(this.linkText);
             // this.linkEditor.appendChild(linkBtn);
-            this.tooltip.appendChild(this.linkEditor);
+            // this.tooltip.appendChild(this.linkEditor);
         }
 
         let node = this.view.state.selection.$from.nodeAfter;
@@ -266,7 +266,7 @@ export class TooltipTextMenu {
                 e.preventDefault();
             }
         };
-        this.tooltip.appendChild(this.linkEditor);
+        // this.tooltip.appendChild(this.linkEditor);
     }
 
     makeLink = (target: string) => {
-- 
cgit v1.2.3-70-g09d2


From 81d23dbd7b918fd1812b770960687bc8a12a0a34 Mon Sep 17 00:00:00 2001
From: yipstanley <stanley_yip@brown.edu>
Date: Sat, 13 Jul 2019 17:49:18 -0400
Subject: wrong link button hidden

---
 src/client/util/TooltipTextMenu.tsx | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

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

diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index d287ff9b1..0d3adef2e 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -123,7 +123,7 @@ export class TooltipTextMenu {
         this.listTypeToIcon.set(schema.nodes.ordered_list, "1)");
         this.listTypes = Array.from(this.listTypeToIcon.keys());
 
-        this.tooltip.appendChild(this.createLink().render(this.view).dom);
+        // this.tooltip.appendChild(this.createLink().render(this.view).dom);
 
         this.tooltip.appendChild(this.createStar().render(this.view).dom);
 
@@ -252,7 +252,7 @@ export class TooltipTextMenu {
             this.linkEditor.appendChild(this.linkDrag);
             // this.linkEditor.appendChild(this.linkText);
             // this.linkEditor.appendChild(linkBtn);
-            // this.tooltip.appendChild(this.linkEditor);
+            this.tooltip.appendChild(this.linkEditor);
         }
 
         let node = this.view.state.selection.$from.nodeAfter;
-- 
cgit v1.2.3-70-g09d2


From c9bf8852a77298e1e958cfc72559418b6cb5981a Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Mon, 15 Jul 2019 11:57:23 -0400
Subject: tweaks

---
 src/client/util/TooltipTextMenu.tsx                    | 2 +-
 src/client/views/collections/CollectionDockingView.tsx | 1 +
 src/server/Search.ts                                   | 2 +-
 3 files changed, 3 insertions(+), 2 deletions(-)

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

diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 0d3adef2e..9ba339285 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -243,7 +243,7 @@ export class TooltipTextMenu {
                         handlers: {
                             dragComplete: action(() => {
                                 let m = dragData.droppedDocuments;
-                                this.makeLink(DocServer.prepend("/doc/" + m[0][Id]));
+                                m.length && this.makeLink(DocServer.prepend("/doc/" + m[0][Id]));
                             }),
                         },
                         hideSource: false
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 781bafec0..1069ebbdb 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -260,6 +260,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                         // Because this is in a set timeout, if this component unmounts right after mounting,
                         // we will leak a GoldenLayout, because we try to destroy it before we ever create it
                         setTimeout(() => this.setupGoldenLayout(), 1);
+                        DocListCast((CurrentUserUtils.UserDocument.workspaces as Doc).data).map(d => d.workspaceBrush = false);
                         this.props.Document.workspaceBrush = true;
                     }
                     this._ignoreStateChange = "";
diff --git a/src/server/Search.ts b/src/server/Search.ts
index 98f421937..11092c5be 100644
--- a/src/server/Search.ts
+++ b/src/server/Search.ts
@@ -26,7 +26,7 @@ export class Search {
             });
             return res;
         } catch (e) {
-            // console.warn("Search error: " + e + document);
+            // console.warn("Search error: ", e, documents);
         }
     }
 
-- 
cgit v1.2.3-70-g09d2


From 89e0d0d3edd5385cab8859b1d0784251b4aac15b Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Mon, 15 Jul 2019 12:56:12 -0400
Subject: Added AB's tooltip dragging code

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

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

diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 0d3adef2e..add6f902c 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -51,6 +51,7 @@ export class TooltipTextMenu {
         this.tooltip = document.createElement("div");
         this.tooltip.className = "tooltipMenu";
 
+        this.dragElement(this.tooltip);
         // this.createCollapse();
         // if (this._collapseBtn) {
         //     this.tooltip.appendChild(this._collapseBtn.render(this.view).dom);
@@ -269,6 +270,47 @@ export class TooltipTextMenu {
         // this.tooltip.appendChild(this.linkEditor);
     }
 
+    dragElement(elmnt: HTMLElement) {
+        var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
+        if (elmnt) {
+            // if present, the header is where you move the DIV from:
+            elmnt.onpointerdown = dragMouseDown;
+        }
+        const self = this;
+
+        function dragMouseDown(e: PointerEvent) {
+            e = e || window.event;
+            //e.preventDefault();
+            // get the mouse cursor position at startup:
+            pos3 = e.clientX;
+            pos4 = e.clientY;
+            document.onpointerup = closeDragElement;
+            // call a function whenever the cursor moves:
+            document.onpointermove = elementDrag;
+        }
+
+        function elementDrag(e: PointerEvent) {
+            e = e || window.event;
+            //e.preventDefault();
+            // calculate the new cursor position:
+            pos1 = pos3 - e.clientX;
+            pos2 = pos4 - e.clientY;
+            pos3 = e.clientX;
+            pos4 = e.clientY;
+            // set the element's new position:
+            elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
+            elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";
+        }
+
+        function closeDragElement() {
+            // stop moving when mouse button is released:
+            document.onpointerup = null;
+            document.onpointermove = null;
+            //self.highlightSearchTerms(self.state, ["hello"]);
+            //FormattedTextBox.Instance.unhighlightSearchTerms();
+        }
+    }
+
     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 });
-- 
cgit v1.2.3-70-g09d2


From 78faa73f1d4a315a3d76613bdcbb894e2428ee10 Mon Sep 17 00:00:00 2001
From: yipstanley <stanley_yip@brown.edu>
Date: Tue, 16 Jul 2019 12:06:31 -0400
Subject: rich text bar link dragging fixed

---
 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 9ba339285..ba3543b72 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -238,6 +238,7 @@ export class TooltipTextMenu {
             this.linkDrag.onpointerdown = (e: PointerEvent) => {
                 let dragData = new DragManager.LinkDragData(this.editorProps.Document);
                 dragData.dontClearTextBox = true;
+                e.stopPropagation();
                 DragManager.StartLinkDrag(this.linkDrag!, dragData, e.clientX, e.clientY,
                     {
                         handlers: {
-- 
cgit v1.2.3-70-g09d2


From 7a4310f95da38cf5de55e487030284157acc58d8 Mon Sep 17 00:00:00 2001
From: yipstanley <stanley_yip@brown.edu>
Date: Tue, 16 Jul 2019 17:48:11 -0400
Subject: link ctrl makes link show in context

---
 .vscode/launch.json                         |  2 +-
 src/client/documents/Documents.ts           | 10 ++++++----
 src/client/util/RichTextSchema.tsx          |  3 ++-
 src/client/util/TooltipTextMenu.tsx         | 12 +++++++-----
 src/client/views/nodes/DocumentView.tsx     |  6 ++++--
 src/client/views/nodes/FormattedTextBox.tsx | 12 ++++++++++--
 src/server/index.ts                         |  1 +
 7 files changed, 31 insertions(+), 15 deletions(-)

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

diff --git a/.vscode/launch.json b/.vscode/launch.json
index 3b6549767..e4196600e 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -38,7 +38,7 @@
             "protocol": "inspector",
             "port": 9229,
             "localRoot": "${workspaceFolder}",
-            "remoteRoot": "."
+            "remoteRoot": "${workspaceFolder}"
         },
         {
             "type": "node",
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 177810444..85fc721da 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -483,18 +483,20 @@ export namespace Docs {
 
 export namespace DocUtils {
 
-    export function MakeLink(source: Doc, target: Doc, targetContext?: Doc, title: string = "", description: string = "", tags: string = "Default") {
+    export function MakeLink(source: Doc, target: Doc, targetContext?: Doc, title: string = "", description: string = "", tags: string = "Default", sourceContext?: Doc) {
         if (LinkManager.Instance.doesLinkExist(source, target)) return;
         let sv = DocumentManager.Instance.getDocumentView(source);
         if (sv && sv.props.ContainingCollectionView && sv.props.ContainingCollectionView.props.Document === target) return;
         if (target === CurrentUserUtils.UserDocument) return;
 
+        let linkDoc;
         UndoManager.RunInBatch(() => {
-            let linkDoc = Docs.Create.TextDocument({ width: 100, height: 30, borderRounding: "100%" });
+            linkDoc = Docs.Create.TextDocument({ width: 100, height: 30, borderRounding: "100%" });
             linkDoc.type = DocumentType.LINK;
             let linkDocProto = Doc.GetProto(linkDoc);
 
-            linkDocProto.context = targetContext;
+            linkDocProto.targetContext = targetContext;
+            linkDocProto.sourceContext = sourceContext;
             linkDocProto.title = title === "" ? source.title + " to " + target.title : title;
             linkDocProto.linkDescription = description;
             linkDocProto.linkTags = tags;
@@ -509,8 +511,8 @@ export namespace DocUtils {
 
             LinkManager.Instance.addLink(linkDoc);
 
-            return linkDoc;
         }, "make link");
+        return linkDoc;
     }
 
 }
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index e0ff3074b..269de0f42 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -215,12 +215,13 @@ export const marks: { [index: string]: MarkSpec } = {
     link: {
         attrs: {
             href: {},
+            location: { default: null },
             title: { default: null }
         },
         inclusive: false,
         parseDOM: [{
             tag: "a[href]", getAttrs(dom: any) {
-                return { href: dom.getAttribute("href"), title: dom.getAttribute("title") };
+                return { href: dom.getAttribute("href"), location: dom.getAttribute("location"), title: dom.getAttribute("title") };
             }
         }],
         toDOM(node: any) { return ["a", node.attrs, 0]; }
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 3e804651c..f4579fc51 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -240,12 +240,14 @@ export class TooltipTextMenu {
                 let dragData = new DragManager.LinkDragData(this.editorProps.Document);
                 dragData.dontClearTextBox = true;
                 e.stopPropagation();
+                let ctrlKey = e.ctrlKey;
                 DragManager.StartLinkDrag(this.linkDrag!, dragData, e.clientX, e.clientY,
                     {
                         handlers: {
                             dragComplete: action(() => {
-                                let m = dragData.droppedDocuments;
-                                m.length && this.makeLink(DocServer.prepend("/doc/" + m[0][Id]));
+                                // let m = dragData.droppedDocuments;
+                                let linkDoc = dragData.linkDocument;
+                                linkDoc instanceof Doc && this.makeLink(DocServer.prepend("/doc/" + linkDoc[Id]), ctrlKey ? "onRight" : "inTab");
                             }),
                         },
                         hideSource: false
@@ -263,7 +265,7 @@ export class TooltipTextMenu {
 
         this.linkText.onkeydown = (e: KeyboardEvent) => {
             if (e.key === "Enter") {
-                this.makeLink(this.linkText!.textContent!);
+                // this.makeLink(this.linkText!.textContent!);
                 e.stopPropagation();
                 e.preventDefault();
             }
@@ -312,9 +314,9 @@ export class TooltipTextMenu {
         }
     }
 
-    makeLink = (target: string) => {
+    makeLink = (target: string, location: string) => {
         let node = this.view.state.selection.$from.nodeAfter;
-        let link = this.view.state.schema.mark(this.view.state.schema.marks.link, { href: target });
+        let link = this.view.state.schema.mark(this.view.state.schema.marks.link, { href: target, location: location });
         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;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index f7a33466e..970ef24d8 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -344,7 +344,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     let first = linkedDocs.filter(d => Doc.AreProtosEqual(d.anchor1 as Doc, this.props.Document));
                     let linkedFwdDocs = first.length ? [first[0].anchor2 as Doc, first[0].anchor1 as Doc] : [expandedDocs[0], expandedDocs[0]];
 
-                    let linkedFwdContextDocs = [first.length ? await (first[0].context) as Doc : undefined, undefined];
+                    // @TODO: shouldn't always follow target context
+                    let linkedFwdContextDocs = [first.length ? await (first[0].targetContext) as Doc : undefined, undefined];
 
                     let linkedFwdPage = [first.length ? NumCast(first[0].linkedToPage, undefined) : undefined, undefined];
 
@@ -454,8 +455,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             else {
                 // const docs = await SearchUtil.Search(`data_l:"${destDoc[Id]}"`, true);
                 // const views = docs.map(d => DocumentManager.Instance.getDocumentView(d)).filter(d => d).map(d => d as DocumentView);
-                DocUtils.MakeLink(sourceDoc, destDoc, this.props.ContainingCollectionView ? this.props.ContainingCollectionView.props.Document : undefined);
+                let linkDoc = DocUtils.MakeLink(sourceDoc, destDoc, this.props.ContainingCollectionView ? this.props.ContainingCollectionView.props.Document : undefined);
                 de.data.droppedDocuments.push(destDoc);
+                de.data.linkDocument = linkDoc;
             }
         }
     }
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 82c2cef26..45e7171d2 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -303,15 +303,23 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         let ctrlKey = e.ctrlKey;
         if (e.button === 0 && ((!this.props.isSelected() && !e.ctrlKey) || (this.props.isSelected() && e.ctrlKey)) && !e.metaKey && e.target) {
             let href = (e.target as any).href;
+            let location = (e.target as any).attributes.location.value;
             for (let parent = (e.target as any).parentNode; !href && parent; parent = parent.parentNode) {
                 href = parent.childNodes[0].href ? parent.childNodes[0].href : parent.href;
             }
             if (href) {
+                ``
                 if (href.indexOf(DocServer.prepend("/doc/")) === 0) {
                     this._linkClicked = href.replace(DocServer.prepend("/doc/"), "").split("?")[0];
                     if (this._linkClicked) {
-                        DocServer.GetRefField(this._linkClicked).then(f => {
-                            (f instanceof Doc) && DocumentManager.Instance.jumpToDocument(f, ctrlKey, false, document => this.props.addDocTab(document, undefined, "inTab"));
+                        DocServer.GetRefField(this._linkClicked).then(async linkDoc => {
+                            if (linkDoc instanceof Doc) {
+                                let proto = Doc.GetProto(linkDoc);
+                                let targetContext = await Cast(proto.targetContext, Doc);
+                                if (targetContext) {
+                                    DocumentManager.Instance.jumpToDocument(targetContext, ctrlKey, false, document => this.props.addDocTab(document, undefined, location ? location : "inTab"));
+                                }
+                            }
                         });
                         e.stopPropagation();
                         e.preventDefault();
diff --git a/src/server/index.ts b/src/server/index.ts
index 06f8358e1..37eabf4c8 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -190,6 +190,7 @@ app.get("/thumbnail/:filename", (req, res) => {
             probe(input, (err: any, result: any) => {
                 if (err) {
                     console.log(err);
+                    console.log(filename);
                     return;
                 }
                 res.send({ path: "/files/" + filename, width: result.width, height: result.height });
-- 
cgit v1.2.3-70-g09d2

