From 17b27d3575d3f91f461262e5ad72a457238d198a Mon Sep 17 00:00:00 2001
From: ab <abdullah_ahmed@brown.edu>
Date: Wed, 7 Aug 2019 16:28:51 -0400
Subject: correlation matrix completed

---
 .../collectionFreeForm/CollectionFreeFormView.tsx     | 19 +++++++++++++++++--
 1 file changed, 17 insertions(+), 2 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 29f9b1429..9344b43d2 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,6 +1,6 @@
 import { library } from "@fortawesome/fontawesome-svg-core";
 import { faEye } from "@fortawesome/free-regular-svg-icons";
-import { faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faPaintBrush, faTable, faUpload } from "@fortawesome/free-solid-svg-icons";
+import { faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faPaintBrush, faTable, faUpload, faBrain } from "@fortawesome/free-solid-svg-icons";
 import { action, computed } from "mobx";
 import { observer } from "mobx-react";
 import { Doc, DocListCastAsync, HeightSym, WidthSym } from "../../../../new_fields/Doc";
@@ -37,8 +37,9 @@ import "./CollectionFreeFormView.scss";
 import { MarqueeView } from "./MarqueeView";
 import React = require("react");
 import v5 = require("uuid/v5");
+import { ClientRecommender } from "../../../ClientRecommender";
 
-library.add(faEye, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload);
+library.add(faEye, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBrain);
 
 export const panZoomSchema = createSchema({
     panX: "number",
@@ -596,6 +597,20 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 input.click();
             }
         });
+        ContextMenu.Instance.addItem({
+            description: "Recommender System",
+            event: async () => {
+                new ClientRecommender();
+                let activedocs = this.getActiveDocuments();
+                await Promise.all(activedocs.map((doc: Doc) => {
+                    console.log(StrCast(doc.title));
+                    const extdoc = doc.data_ext as Doc;
+                    return ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc);
+                }));
+                console.log(ClientRecommender.Instance.createDistanceMatrix());
+            },
+            icon: "brain"
+        });
     }
 
 
-- 
cgit v1.2.3-70-g09d2


From 17f28e393e0c24fcace33a3ecd5564bc766fe685 Mon Sep 17 00:00:00 2001
From: ab <abdullah_ahmed@brown.edu>
Date: Thu, 8 Aug 2019 13:00:51 -0400
Subject: fuck you react

---
 src/client/ClientRecommender.ts                    | 101 --------------
 src/client/ClientRecommender.tsx                   | 148 +++++++++++++++++++++
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   4 +-
 src/server/Recommender.ts                          |   2 +
 4 files changed, 153 insertions(+), 102 deletions(-)
 delete mode 100644 src/client/ClientRecommender.ts
 create mode 100644 src/client/ClientRecommender.tsx

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

diff --git a/src/client/ClientRecommender.ts b/src/client/ClientRecommender.ts
deleted file mode 100644
index 7ff79ab50..000000000
--- a/src/client/ClientRecommender.ts
+++ /dev/null
@@ -1,101 +0,0 @@
-import { Doc } from "../new_fields/Doc";
-import { StrCast } from "../new_fields/Types";
-import { List } from "../new_fields/List";
-import { CognitiveServices } from "./cognitive_services/CognitiveServices";
-
-
-var assert = require('assert');
-
-export class ClientRecommender {
-
-    static Instance: ClientRecommender;
-    private docVectors: Set<number[]>;
-
-    constructor() {
-        //console.log("creating client recommender...");
-        ClientRecommender.Instance = this;
-        this.docVectors = new Set<number[]>();
-    }
-
-
-    /***
-     * Computes the cosine similarity between two vectors in Euclidean space. 
-     */
-
-    private distance(vector1: number[], vector2: number[]) {
-        assert(vector1.length === vector2.length, "Vectors are not the same length");
-        var dotproduct = 0;
-        var mA = 0;
-        var mB = 0;
-        for (let i = 0; i < vector1.length; i++) { // here you missed the i++
-            dotproduct += (vector1[i] * vector2[i]);
-            mA += (vector1[i] * vector1[i]);
-            mB += (vector2[i] * vector2[i]);
-        }
-        mA = Math.sqrt(mA);
-        mB = Math.sqrt(mB);
-        var similarity = (dotproduct) / ((mA) * (mB)); // here you needed extra brackets
-        return similarity;
-    }
-
-    /***
-     * Computes the mean of a set of vectors
-     */
-
-    public mean(paragraph: Set<number[]>) {
-        const n = 200;
-        const num_words = paragraph.size;
-        let meanVector = new Array<number>(n).fill(0); // mean vector
-        paragraph.forEach((wordvec: number[]) => {
-            for (let i = 0; i < n; i++) {
-                meanVector[i] += wordvec[i];
-            }
-        });
-        meanVector = meanVector.map(x => x / num_words);
-        this.addToDocSet(meanVector);
-        return meanVector;
-    }
-
-    private addToDocSet(vector: number[]) {
-        if (this.docVectors) {
-            this.docVectors.add(vector);
-        }
-    }
-
-    /***
-     * Uses Cognitive Services to extract keywords from a document
-     */
-
-    public async extractText(dataDoc: Doc, extDoc: Doc) {
-        let data = StrCast(dataDoc.title);
-        //console.log(data);
-        let converter = (results: any) => {
-            let keyterms = new List<string>();
-            results.documents.forEach((doc: any) => {
-                let keyPhrases = doc.keyPhrases;
-                keyPhrases.map((kp: string) => keyterms.push(kp));
-            });
-            return keyterms;
-        };
-        await CognitiveServices.Text.Manager.analyzer(extDoc, ["key words"], data, converter);
-    }
-
-    /***
-     * Creates distance matrix for all Documents analyzed
-     */
-
-    public createDistanceMatrix(documents: Set<number[]> = this.docVectors) {
-        const documents_list = Array.from(documents);
-        const n = documents_list.length;
-        var matrix = new Array(n).fill(0).map(() => new Array(n).fill(0));
-        for (let i = 0; i < n; i++) {
-            var doc1 = documents_list[i];
-            for (let j = 0; j < n; j++) {
-                var doc2 = documents_list[j];
-                matrix[i][j] = this.distance(doc1, doc2);
-            }
-        }
-        return matrix;
-    }
-
-}
\ No newline at end of file
diff --git a/src/client/ClientRecommender.tsx b/src/client/ClientRecommender.tsx
new file mode 100644
index 000000000..2344ee490
--- /dev/null
+++ b/src/client/ClientRecommender.tsx
@@ -0,0 +1,148 @@
+import { Doc } from "../new_fields/Doc";
+import { StrCast } from "../new_fields/Types";
+import { List } from "../new_fields/List";
+import { CognitiveServices } from "./cognitive_services/CognitiveServices";
+import React = require("react");
+import { observer } from "mobx-react";
+import { observable, action, computed, reaction } from "mobx";
+var assert = require('assert');
+import Table from 'react-bootstrap/Table';
+
+export interface RecommenderProps {
+    title: string;
+}
+
+@observer
+export class ClientRecommender extends React.Component<RecommenderProps> {
+
+    static Instance: ClientRecommender;
+    private docVectors: Set<number[]>;
+    private corr_matrix = observable([[0, 0], [0, 0]]);
+    @observable private firstnum = 0;
+    //@observable private corr_matrix: number[][] = [[0, 0], [0, 0]];
+
+    constructor(props: RecommenderProps) {
+        //console.log("creating client recommender...");
+        super(props);
+        ClientRecommender.Instance = this;
+        this.docVectors = new Set<number[]>();
+        //this.corr_matrix = [[0, 0], [0, 0]];
+    }
+
+
+    /***
+     * Computes the cosine similarity between two vectors in Euclidean space. 
+     */
+
+    private distance(vector1: number[], vector2: number[]) {
+        assert(vector1.length === vector2.length, "Vectors are not the same length");
+        var dotproduct = 0;
+        var mA = 0;
+        var mB = 0;
+        for (let i = 0; i < vector1.length; i++) { // here you missed the i++
+            dotproduct += (vector1[i] * vector2[i]);
+            mA += (vector1[i] * vector1[i]);
+            mB += (vector2[i] * vector2[i]);
+        }
+        mA = Math.sqrt(mA);
+        mB = Math.sqrt(mB);
+        var similarity = (dotproduct) / ((mA) * (mB)); // here you needed extra brackets
+        return similarity;
+    }
+
+    /***
+     * Computes the mean of a set of vectors
+     */
+
+    public mean(paragraph: Set<number[]>) {
+        const n = 200;
+        const num_words = paragraph.size;
+        let meanVector = new Array<number>(n).fill(0); // mean vector
+        paragraph.forEach((wordvec: number[]) => {
+            for (let i = 0; i < n; i++) {
+                meanVector[i] += wordvec[i];
+            }
+        });
+        meanVector = meanVector.map(x => x / num_words);
+        this.addToDocSet(meanVector);
+        return meanVector;
+    }
+
+    private addToDocSet(vector: number[]) {
+        if (this.docVectors) {
+            this.docVectors.add(vector);
+        }
+    }
+
+    /***
+     * Uses Cognitive Services to extract keywords from a document
+     */
+
+    public async extractText(dataDoc: Doc, extDoc: Doc) {
+        let data = StrCast(dataDoc.title);
+        //console.log(data);
+        let converter = (results: any) => {
+            let keyterms = new List<string>();
+            results.documents.forEach((doc: any) => {
+                let keyPhrases = doc.keyPhrases;
+                keyPhrases.map((kp: string) => keyterms.push(kp));
+            });
+            return keyterms;
+        };
+        await CognitiveServices.Text.Manager.analyzer(extDoc, ["key words"], data, converter);
+    }
+
+    /***
+     * Creates distance matrix for all Documents analyzed
+     */
+
+    @action
+    public createDistanceMatrix(documents: Set<number[]> = this.docVectors) {
+        //this.corr_matrix[0][0] = 500;
+        this.firstnum = 500;
+        const documents_list = Array.from(documents);
+        const n = documents_list.length;
+        var matrix = new Array<number>(n).fill(0).map(() => new Array<number>(n).fill(0));
+        for (let i = 0; i < n; i++) {
+            var doc1 = documents_list[i];
+            for (let j = 0; j < n; j++) {
+                var doc2 = documents_list[j];
+                matrix[i][j] = this.distance(doc1, doc2);
+            }
+        }
+        //this.corr_matrix = matrix;
+
+        return matrix;
+    }
+
+    @computed get first_num() {
+        return this.firstnum;
+    }
+
+    dumb_reaction = reaction(
+        () => this.first_num,
+        number => {
+            console.log("number has changed", number);
+            this.forceUpdate();
+        }
+    );
+
+    render() {
+        return (<div>
+            <h3>{this.props.title ? this.props.title : "hello"}</h3>
+            <table>
+                <tbody>
+                    <tr>
+                        <td>{this.first_num}</td>
+                        <td>{this.corr_matrix[0][1]}</td>
+                    </tr>
+                    <tr>
+                        <td>{this.corr_matrix[1][0]}</td>
+                        <td>{this.corr_matrix[1][1]}</td>
+                    </tr>
+                </tbody>
+            </table>
+        </div>);
+    }
+
+}
\ 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 9344b43d2..5259b9b49 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -523,6 +523,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY));
     }
 
+    @action
     onContextMenu = (e: React.MouseEvent) => {
         let layoutItems: ContextMenuProps[] = [];
         layoutItems.push({
@@ -600,7 +601,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         ContextMenu.Instance.addItem({
             description: "Recommender System",
             event: async () => {
-                new ClientRecommender();
+                // if (!ClientRecommender.Instance) new ClientRecommender({ title: "Client Recommender" });
                 let activedocs = this.getActiveDocuments();
                 await Promise.all(activedocs.map((doc: Doc) => {
                     console.log(StrCast(doc.title));
@@ -715,6 +716,7 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF
         const zoom = this.props.zoomScaling();// needs to be a variable outside of the <Measure> otherwise, reactions won't fire
         return <div className={freeformclass} style={{ borderRadius: "inherit", transform: `translate(${cenx}px, ${ceny}px) scale(${zoom}, ${zoom}) translate(${panx}px, ${pany}px)` }}>
             {this.props.children}
+            <ClientRecommender title="Correlation Matrix" />
         </div>;
     }
 }
\ No newline at end of file
diff --git a/src/server/Recommender.ts b/src/server/Recommender.ts
index ea59703c3..d175b67c7 100644
--- a/src/server/Recommender.ts
+++ b/src/server/Recommender.ts
@@ -10,6 +10,7 @@ export class Recommender {
 
     private _model: any;
     static Instance: Recommender;
+    private dimension: number = 0;
 
     constructor() {
         console.log("creating recommender...");
@@ -25,6 +26,7 @@ export class Recommender {
         return new Promise(res => {
             w2v.loadModel("./node_modules/word2vec/vectors.txt", function (err: any, model: any) {
                 self._model = model;
+                self.dimension = model.size;
                 res(model);
             });
         });
-- 
cgit v1.2.3-70-g09d2


From 116f218c2eeec377fb465027bcfaa7521d9af492 Mon Sep 17 00:00:00 2001
From: ab <abdullah_ahmed@brown.edu>
Date: Thu, 8 Aug 2019 17:48:58 -0400
Subject: euclidian

---
 src/client/ClientRecommender.scss                  |   8 ++
 src/client/ClientRecommender.tsx                   | 100 +++++++++++++--------
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   3 +-
 3 files changed, 71 insertions(+), 40 deletions(-)
 create mode 100644 src/client/ClientRecommender.scss

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

diff --git a/src/client/ClientRecommender.scss b/src/client/ClientRecommender.scss
new file mode 100644
index 000000000..710d80f34
--- /dev/null
+++ b/src/client/ClientRecommender.scss
@@ -0,0 +1,8 @@
+@import "/views/globalCssVariables.scss";
+
+.space{
+    border: 1px dashed blue;
+    border-spacing: 25px;
+    border-collapse: separate;
+    align-content: center;
+}
\ No newline at end of file
diff --git a/src/client/ClientRecommender.tsx b/src/client/ClientRecommender.tsx
index 2344ee490..0793f0887 100644
--- a/src/client/ClientRecommender.tsx
+++ b/src/client/ClientRecommender.tsx
@@ -6,7 +6,7 @@ import React = require("react");
 import { observer } from "mobx-react";
 import { observable, action, computed, reaction } from "mobx";
 var assert = require('assert');
-import Table from 'react-bootstrap/Table';
+import "./ClientRecommender.scss";
 
 export interface RecommenderProps {
     title: string;
@@ -17,37 +17,53 @@ export class ClientRecommender extends React.Component<RecommenderProps> {
 
     static Instance: ClientRecommender;
     private docVectors: Set<number[]>;
-    private corr_matrix = observable([[0, 0], [0, 0]]);
-    @observable private firstnum = 0;
-    //@observable private corr_matrix: number[][] = [[0, 0], [0, 0]];
+    @observable private corr_matrix = [[0, 0], [0, 0]];
 
     constructor(props: RecommenderProps) {
         //console.log("creating client recommender...");
         super(props);
-        ClientRecommender.Instance = this;
+        if (!ClientRecommender.Instance) ClientRecommender.Instance = this;
         this.docVectors = new Set<number[]>();
         //this.corr_matrix = [[0, 0], [0, 0]];
     }
 
+    @action
+    public reset_docs() {
+        this.docVectors = new Set();
+        this.corr_matrix = [[0, 0], [0, 0]];
+    }
 
     /***
      * Computes the cosine similarity between two vectors in Euclidean space. 
      */
 
-    private distance(vector1: number[], vector2: number[]) {
+    private distance(vector1: number[], vector2: number[], metric: string = "cosine") {
         assert(vector1.length === vector2.length, "Vectors are not the same length");
-        var dotproduct = 0;
-        var mA = 0;
-        var mB = 0;
-        for (let i = 0; i < vector1.length; i++) { // here you missed the i++
-            dotproduct += (vector1[i] * vector2[i]);
-            mA += (vector1[i] * vector1[i]);
-            mB += (vector2[i] * vector2[i]);
+        let similarity: number;
+        switch (metric) {
+            case "cosine":
+                var dotproduct = 0;
+                var mA = 0;
+                var mB = 0;
+                for (let i = 0; i < vector1.length; i++) { // here you missed the i++
+                    dotproduct += (vector1[i] * vector2[i]);
+                    mA += (vector1[i] * vector1[i]);
+                    mB += (vector2[i] * vector2[i]);
+                }
+                mA = Math.sqrt(mA);
+                mB = Math.sqrt(mB);
+                similarity = (dotproduct) / ((mA) * (mB)); // here you needed extra brackets
+                return similarity;
+            case "euclidian":
+                var sum = 0;
+                for (let i = 0; i < vector1.length; i++) {
+                    sum += Math.pow(vector1[i] - vector2[i], 2);
+                }
+                similarity = Math.sqrt(sum);
+                return similarity;
+            default:
+                return 0;
         }
-        mA = Math.sqrt(mA);
-        mB = Math.sqrt(mB);
-        var similarity = (dotproduct) / ((mA) * (mB)); // here you needed extra brackets
-        return similarity;
     }
 
     /***
@@ -98,8 +114,6 @@ export class ClientRecommender extends React.Component<RecommenderProps> {
 
     @action
     public createDistanceMatrix(documents: Set<number[]> = this.docVectors) {
-        //this.corr_matrix[0][0] = 500;
-        this.firstnum = 500;
         const documents_list = Array.from(documents);
         const n = documents_list.length;
         var matrix = new Array<number>(n).fill(0).map(() => new Array<number>(n).fill(0));
@@ -107,40 +121,48 @@ export class ClientRecommender extends React.Component<RecommenderProps> {
             var doc1 = documents_list[i];
             for (let j = 0; j < n; j++) {
                 var doc2 = documents_list[j];
-                matrix[i][j] = this.distance(doc1, doc2);
+                matrix[i][j] = this.distance(doc1, doc2, "euclidian");
             }
         }
-        //this.corr_matrix = matrix;
-
+        this.corr_matrix = matrix;
         return matrix;
     }
 
-    @computed get first_num() {
-        return this.firstnum;
-    }
-
-    dumb_reaction = reaction(
-        () => this.first_num,
-        number => {
-            console.log("number has changed", number);
-            this.forceUpdate();
+    @computed
+    private get generateRows() {
+        const n = this.corr_matrix.length;
+        let rows: React.ReactElement[] = [];
+        for (let i = 0; i < n; i++) {
+            let children: React.ReactElement[] = [];
+            for (let j = 0; j < n; j++) {
+                let cell = React.createElement("td", this.corr_matrix[i][j]);
+                children.push(cell);
+            }
+            let row = React.createElement("tr", { children: children });
+            rows.push(row);
         }
-    );
+        return rows;
+    }
 
     render() {
         return (<div>
             <h3>{this.props.title ? this.props.title : "hello"}</h3>
-            <table>
+            {/* <table className="space" >
                 <tbody>
-                    <tr>
-                        <td>{this.first_num}</td>
-                        <td>{this.corr_matrix[0][1]}</td>
+                    <tr key="1">
+                        <td key="1">{this.corr_matrix[0][0].toFixed(4)}</td>
+                        <td key="2">{this.corr_matrix[0][1].toFixed(4)}</td>
                     </tr>
-                    <tr>
-                        <td>{this.corr_matrix[1][0]}</td>
-                        <td>{this.corr_matrix[1][1]}</td>
+                    <tr key="2">
+                        <td key="1">{this.corr_matrix[1][0].toFixed(4)}</td>
+                        <td key="2">{this.corr_matrix[1][1].toFixed(4)}</td>
                     </tr>
                 </tbody>
+            </table> */}
+            <table>
+                <tbody>
+                    {this.generateRows}
+                </tbody>
             </table>
         </div>);
     }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 5259b9b49..2b9f32136 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -603,6 +603,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             event: async () => {
                 // if (!ClientRecommender.Instance) new ClientRecommender({ title: "Client Recommender" });
                 let activedocs = this.getActiveDocuments();
+                ClientRecommender.Instance.reset_docs();
                 await Promise.all(activedocs.map((doc: Doc) => {
                     console.log(StrCast(doc.title));
                     const extdoc = doc.data_ext as Doc;
@@ -716,7 +717,7 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF
         const zoom = this.props.zoomScaling();// needs to be a variable outside of the <Measure> otherwise, reactions won't fire
         return <div className={freeformclass} style={{ borderRadius: "inherit", transform: `translate(${cenx}px, ${ceny}px) scale(${zoom}, ${zoom}) translate(${panx}px, ${pany}px)` }}>
             {this.props.children}
-            <ClientRecommender title="Correlation Matrix" />
+            <ClientRecommender title="Distance Matrix" />
         </div>;
     }
 }
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From b8ab5a823ae22b021c09dfd713b77211a51b3eae Mon Sep 17 00:00:00 2001
From: ab <abdullah_ahmed@brown.edu>
Date: Fri, 9 Aug 2019 17:04:25 -0400
Subject: idk man

---
 solr-8.1.1/server/solr/dash/conf/schema.xml         |  2 +-
 src/client/ClientRecommender.scss                   |  4 ++++
 src/client/ClientRecommender.tsx                    | 17 ++++++++++-------
 src/client/util/SearchUtil.ts                       | 21 ++++++++++++++++++++-
 src/client/views/SearchBox.tsx                      |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx   |  6 +++++-
 src/server/Search.ts                                |  2 +-
 7 files changed, 42 insertions(+), 12 deletions(-)

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

diff --git a/solr-8.1.1/server/solr/dash/conf/schema.xml b/solr-8.1.1/server/solr/dash/conf/schema.xml
index 8610786af..c0a4bab07 100644
--- a/solr-8.1.1/server/solr/dash/conf/schema.xml
+++ b/solr-8.1.1/server/solr/dash/conf/schema.xml
@@ -52,7 +52,7 @@
   <dynamicField name="*_i" type="string" indexed="true" stored="true" uninvertible="false" docValues="false"/>
   <dynamicField name="*_b" type="boolean" indexed="true" stored="true" uninvertible="false" docValues="false"/>
 
-  <dynamicField name="*_a" type="text" indexed="true" stored="false" uninvertible="false" docValues="false"/>
+  <dynamicField name="*_a" type="text" indexed="true" stored="false" uninvertible="false" docValues="false" multiValued="true"/>
 
   <copyField source="*_t" dest="text"/>
 
diff --git a/src/client/ClientRecommender.scss b/src/client/ClientRecommender.scss
index 710d80f34..49163cdc8 100644
--- a/src/client/ClientRecommender.scss
+++ b/src/client/ClientRecommender.scss
@@ -5,4 +5,8 @@
     border-spacing: 25px;
     border-collapse: separate;
     align-content: center;
+}
+
+.wrapper{
+    text-align: -webkit-center;
 }
\ No newline at end of file
diff --git a/src/client/ClientRecommender.tsx b/src/client/ClientRecommender.tsx
index 0793f0887..ddaa8a7fc 100644
--- a/src/client/ClientRecommender.tsx
+++ b/src/client/ClientRecommender.tsx
@@ -7,6 +7,7 @@ import { observer } from "mobx-react";
 import { observable, action, computed, reaction } from "mobx";
 var assert = require('assert');
 import "./ClientRecommender.scss";
+import { JSXElement } from "babel-types";
 
 export interface RecommenderProps {
     title: string;
@@ -131,22 +132,24 @@ export class ClientRecommender extends React.Component<RecommenderProps> {
     @computed
     private get generateRows() {
         const n = this.corr_matrix.length;
-        let rows: React.ReactElement[] = [];
+        let rows: JSX.Element[] = [];
         for (let i = 0; i < n; i++) {
-            let children: React.ReactElement[] = [];
+            let children: JSX.Element[] = [];
             for (let j = 0; j < n; j++) {
-                let cell = React.createElement("td", this.corr_matrix[i][j]);
+                //let cell = React.createElement("td", this.corr_matrix[i][j]);
+                let cell = <td>{this.corr_matrix[i][j].toFixed(4)}</td>;
                 children.push(cell);
             }
-            let row = React.createElement("tr", { children: children });
+            //let row = React.createElement("tr", { children: children, key: i });
+            let row = <tr>{children}</tr>;
             rows.push(row);
         }
         return rows;
     }
 
     render() {
-        return (<div>
-            <h3>{this.props.title ? this.props.title : "hello"}</h3>
+        return (<div className="wrapper">
+            <h3 >{this.props.title ? this.props.title : "hello"}</h3>
             {/* <table className="space" >
                 <tbody>
                     <tr key="1">
@@ -159,7 +162,7 @@ export class ClientRecommender extends React.Component<RecommenderProps> {
                     </tr>
                 </tbody>
             </table> */}
-            <table>
+            <table className="space">
                 <tbody>
                     {this.generateRows}
                 </tbody>
diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts
index ee5a83710..3a3ba1803 100644
--- a/src/client/util/SearchUtil.ts
+++ b/src/client/util/SearchUtil.ts
@@ -77,4 +77,23 @@ export namespace SearchUtil {
         aliasContexts.forEach(result => contexts.aliasContexts.push(...result.ids));
         return contexts;
     }
-}
\ No newline at end of file
+
+    export async function GetAllDocs() {
+        const query = "*";
+        let response = await rp.get(Utils.prepend('/search'), {
+            qs: {
+                query
+            }
+        });
+        let res: string[] = JSON.parse(response);
+        const fields = await DocServer.GetRefFields(res);
+        const docs: Doc[] = [];
+        for (const id of res) {
+            const field = fields[id];
+            if (field instanceof Doc) {
+                docs.push(field);
+            }
+        }
+        return docs;
+    }
+}
diff --git a/src/client/views/SearchBox.tsx b/src/client/views/SearchBox.tsx
index 33cb63df5..17f99fa05 100644
--- a/src/client/views/SearchBox.tsx
+++ b/src/client/views/SearchBox.tsx
@@ -47,7 +47,7 @@ export class SearchBox extends React.Component {
     }
 
     @action
-    getResults = async (query: string) => {
+    public getResults = async (query: string) => {
         let response = await rp.get(Utils.prepend('/search'), {
             qs: {
                 query
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 2b9f32136..0beb0086b 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -38,6 +38,8 @@ import { MarqueeView } from "./MarqueeView";
 import React = require("react");
 import v5 = require("uuid/v5");
 import { ClientRecommender } from "../../../ClientRecommender";
+import { SearchUtil } from "../../../util/SearchUtil";
+import { SearchBox } from "../../SearchBox";
 
 library.add(faEye, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBrain);
 
@@ -603,9 +605,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             event: async () => {
                 // if (!ClientRecommender.Instance) new ClientRecommender({ title: "Client Recommender" });
                 let activedocs = this.getActiveDocuments();
+                let allDocs = await SearchUtil.GetAllDocs();
+                allDocs.forEach(doc => console.log(doc.title));
                 ClientRecommender.Instance.reset_docs();
                 await Promise.all(activedocs.map((doc: Doc) => {
-                    console.log(StrCast(doc.title));
+                    //console.log(StrCast(doc.title));
                     const extdoc = doc.data_ext as Doc;
                     return ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc);
                 }));
diff --git a/src/server/Search.ts b/src/server/Search.ts
index 723dc101b..44c1b1298 100644
--- a/src/server/Search.ts
+++ b/src/server/Search.ts
@@ -14,7 +14,7 @@ export class Search {
             });
             return res;
         } catch (e) {
-            // console.warn("Search error: " + e + document);
+            console.warn("Search error: " + e + document);
         }
     }
 
-- 
cgit v1.2.3-70-g09d2


From 9dd2a31b72e5e527e2dae3b68f856ab8da879e93 Mon Sep 17 00:00:00 2001
From: ab <abdullah_ahmed@brown.edu>
Date: Mon, 12 Aug 2019 16:41:23 -0400
Subject: documentation

---
 package.json                                       |  2 +-
 src/client/ClientRecommender.tsx                   | 18 +++++-----
 src/client/cognitive_services/CognitiveServices.ts | 42 ++++++++++++----------
 src/client/util/SearchUtil.ts                      | 13 ++++---
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  1 +
 src/server/Recommender.ts                          |  1 +
 6 files changed, 45 insertions(+), 32 deletions(-)

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

diff --git a/package.json b/package.json
index 1e2c74411..1c7a10ac8 100644
--- a/package.json
+++ b/package.json
@@ -225,4 +225,4 @@
         "xoauth2": "^1.2.0",
         "youtube": "^0.1.0"
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/ClientRecommender.tsx b/src/client/ClientRecommender.tsx
index ddaa8a7fc..63f85c737 100644
--- a/src/client/ClientRecommender.tsx
+++ b/src/client/ClientRecommender.tsx
@@ -75,13 +75,15 @@ export class ClientRecommender extends React.Component<RecommenderProps> {
         const n = 200;
         const num_words = paragraph.size;
         let meanVector = new Array<number>(n).fill(0); // mean vector
-        paragraph.forEach((wordvec: number[]) => {
-            for (let i = 0; i < n; i++) {
-                meanVector[i] += wordvec[i];
-            }
-        });
-        meanVector = meanVector.map(x => x / num_words);
-        this.addToDocSet(meanVector);
+        if (num_words > 0) { // check to see if paragraph actually was vectorized
+            paragraph.forEach((wordvec: number[]) => {
+                for (let i = 0; i < n; i++) {
+                    meanVector[i] += wordvec[i];
+                }
+            });
+            meanVector = meanVector.map(x => x / num_words);
+            this.addToDocSet(meanVector);
+        }
         return meanVector;
     }
 
@@ -106,7 +108,7 @@ export class ClientRecommender extends React.Component<RecommenderProps> {
             });
             return keyterms;
         };
-        await CognitiveServices.Text.Manager.analyzer(extDoc, ["key words"], data, converter);
+        await CognitiveServices.Text.Appliers.analyzer(extDoc, ["key words"], data, converter);
     }
 
     /***
diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts
index 954a05585..75d0760ed 100644
--- a/src/client/cognitive_services/CognitiveServices.ts
+++ b/src/client/cognitive_services/CognitiveServices.ts
@@ -258,32 +258,38 @@ export namespace CognitiveServices {
                 };
                 console.log("requested!");
                 return request.post(options);
-            },
-            analyzer: async (target: Doc, keys: string[], data: string, converter: Converter) => {
-                let results = await ExecuteQuery<string, any>(Service.Text, Manager, data);
+            }
+        };
+
+        export namespace Appliers {
+
+            export async function vectorize(keyterms: any) {
+                console.log("vectorizing...");
+                //keyterms = ["father", "king"];
+                let args = { method: 'POST', uri: Utils.prepend("/recommender"), body: { keyphrases: keyterms }, json: true };
+                await requestPromise.post(args).then(async (wordvecs) => {
+                    var vectorValues = new Set<number[]>();
+                    wordvecs.forEach((wordvec: any) => {
+                        //console.log(wordvec.word);
+                        vectorValues.add(wordvec.values as number[]);
+                    });
+                    ClientRecommender.Instance.mean(vectorValues);
+                    //console.log(vectorValues.size);
+                });
+            }
+
+            export const analyzer = async (target: Doc, keys: string[], data: string, converter: Converter) => {
+                let results = await ExecuteQuery(Service.Text, Manager, data);
                 console.log(results);
                 let keyterms = converter(results);
                 //target[keys[0]] = Docs.Get.DocumentHierarchyFromJson(results, "Key Word Analysis");
                 target[keys[0]] = keyterms;
                 console.log("analyzed!");
                 await vectorize(keyterms);
-            }
-        };
-        async function vectorize(keyterms: any) {
-            console.log("vectorizing...");
-            //keyterms = ["father", "king"];
-            let args = { method: 'POST', uri: Utils.prepend("/recommender"), body: { keyphrases: keyterms }, json: true };
-            await requestPromise.post(args).then(async (wordvecs) => {
-                var vectorValues = new Set<number[]>();
-                wordvecs.forEach((wordvec: any) => {
-                    //console.log(wordvec.word);
-                    vectorValues.add(wordvec.values as number[]);
-                });
-                ClientRecommender.Instance.mean(vectorValues);
-                //console.log(vectorValues.size);
-            });
+            };
         }
 
     }
 
+
 }
\ No newline at end of file
diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts
index 3a3ba1803..1fce995d7 100644
--- a/src/client/util/SearchUtil.ts
+++ b/src/client/util/SearchUtil.ts
@@ -82,18 +82,21 @@ export namespace SearchUtil {
         const query = "*";
         let response = await rp.get(Utils.prepend('/search'), {
             qs: {
-                query
+                q: query
             }
         });
-        let res: string[] = JSON.parse(response);
-        const fields = await DocServer.GetRefFields(res);
+        let result: IdSearchResult = JSON.parse(response);
+        const { ids, numFound, highlighting } = result;
+        const docMap = await DocServer.GetRefFields(ids);
         const docs: Doc[] = [];
-        for (const id of res) {
-            const field = fields[id];
+        for (const id of ids) {
+            const field = docMap[id];
             if (field instanceof Doc) {
                 docs.push(field);
             }
         }
         return docs;
+        // const docs = ids.map((id: string) => docMap[id]).filter((doc: any) => doc instanceof Doc);
+        // return docs as Doc[];
     }
 }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index e9791df4e..d1e8031fd 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -894,6 +894,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 let activedocs = this.getActiveDocuments();
                 let allDocs = await SearchUtil.GetAllDocs();
                 allDocs.forEach(doc => console.log(doc.title));
+                // clears internal representation of documents as vectors
                 ClientRecommender.Instance.reset_docs();
                 await Promise.all(activedocs.map((doc: Doc) => {
                     //console.log(StrCast(doc.title));
diff --git a/src/server/Recommender.ts b/src/server/Recommender.ts
index d175b67c7..1c95d7ea4 100644
--- a/src/server/Recommender.ts
+++ b/src/server/Recommender.ts
@@ -70,6 +70,7 @@ export class Recommender {
         }
         if (this._model) {
             let word_vecs = this._model.getVectors(text);
+
             return word_vecs;
         }
     }
-- 
cgit v1.2.3-70-g09d2


From e03a1b2cc90e0fdb7789f4826e482e9040aa7075 Mon Sep 17 00:00:00 2001
From: ab <abdullah_ahmed@brown.edu>
Date: Tue, 13 Aug 2019 17:26:49 -0400
Subject: 80% done, garbage collection is much needed

---
 src/Recommendations.scss                           |  21 ---
 src/Recommendations.tsx                            |  28 ----
 src/client/util/SearchUtil.ts                      |   7 +-
 src/client/views/MainView.tsx                      |   2 +
 src/client/views/Recommendations.scss              |  65 ++++++++
 src/client/views/Recommendations.tsx               | 169 +++++++++++++++++++++
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  13 +-
 src/client/views/nodes/DocumentView.tsx            |  34 ++++-
 src/client/views/nodes/ImageBox.tsx                |  21 ++-
 9 files changed, 297 insertions(+), 63 deletions(-)
 delete mode 100644 src/Recommendations.scss
 delete mode 100644 src/Recommendations.tsx
 create mode 100644 src/client/views/Recommendations.scss
 create mode 100644 src/client/views/Recommendations.tsx

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

diff --git a/src/Recommendations.scss b/src/Recommendations.scss
deleted file mode 100644
index 5129a59d9..000000000
--- a/src/Recommendations.scss
+++ /dev/null
@@ -1,21 +0,0 @@
-.recommendation-content *{
-    display: inline-block;
-    margin: auto;
-    border: 1px dashed grey;
-    padding: 2px 2px;
-}
-
-.recommendation-content {
-    float: left;
-    border: 1px solid green;
-    width: 200px;
-    align-content: center;
-}
-
-.rec-scroll {
-    overflow-y: scroll;
-    height: 300px; 
-    width: auto;
-    position: absolute;
-    background: #cdcdcd;
-}
\ No newline at end of file
diff --git a/src/Recommendations.tsx b/src/Recommendations.tsx
deleted file mode 100644
index ca1123ef9..000000000
--- a/src/Recommendations.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-import { observer } from "mobx-react";
-import React = require("react");
-import { Doc } from "./new_fields/Doc";
-import { NumCast } from "./new_fields/Types";
-
-export interface RecProps {
-    documents: { preview: string, similarity: number }[],
-    node: Doc;
-}
-
-@observer
-export class Recommendations extends React.Component<RecProps> {
-    render() {
-        const transform = "translate(" + (NumCast(this.props.node.x) + 350) + "px, " + NumCast(this.props.node.y) + "px"
-        return (
-            <div className="rec-scroll" style={{ transform: transform }}>
-                {this.props.documents.map(doc => {
-                    return (
-                        <div className="recommendation-content">
-                            <img src={doc.preview} />
-                            <div>{doc.similarity}</div>
-                        </div>
-                    )
-                })}
-            </div>
-        )
-    }
-}
\ No newline at end of file
diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts
index 1fce995d7..85e593529 100644
--- a/src/client/util/SearchUtil.ts
+++ b/src/client/util/SearchUtil.ts
@@ -81,12 +81,13 @@ export namespace SearchUtil {
     export async function GetAllDocs() {
         const query = "*";
         let response = await rp.get(Utils.prepend('/search'), {
-            qs: {
-                q: query
-            }
+            qs:
+                { start: 0, rows: 10000, q: query },
+
         });
         let result: IdSearchResult = JSON.parse(response);
         const { ids, numFound, highlighting } = result;
+        console.log(ids.length);
         const docMap = await DocServer.GetRefFields(ids);
         const docs: Doc[] = [];
         for (const id of ids) {
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 419b15697..0b6fe3876 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -41,6 +41,7 @@ import { ClientUtils } from '../util/ClientUtils';
 import { SchemaHeaderField, RandomPastel } from '../../new_fields/SchemaHeaderField';
 //import { DocumentManager } from '../util/DocumentManager';
 import { DictationManager } from '../util/DictationManager';
+import { Recommendations } from './Recommendations';
 
 @observer
 export class MainView extends React.Component {
@@ -581,6 +582,7 @@ export class MainView extends React.Component {
                 {this.mainContent}
                 <PreviewCursor />
                 <ContextMenu />
+                <Recommendations />
                 {this.nodesMenu()}
                 {this.miscButtons}
                 <PDFMenu />
diff --git a/src/client/views/Recommendations.scss b/src/client/views/Recommendations.scss
new file mode 100644
index 000000000..5d8f17e37
--- /dev/null
+++ b/src/client/views/Recommendations.scss
@@ -0,0 +1,65 @@
+@import "globalCssVariables";
+
+.rec-content *{
+    display: inline-block;
+    margin: auto;
+    width: 50;
+    height: 30px;
+    border: 1px dashed grey;
+    padding: 10px 10px;
+}
+
+.rec-content {
+    float: left;
+    width: inherit;
+    align-content: center;
+}
+
+.rec-scroll {
+    overflow-y: scroll;
+    overflow-x: hidden;
+    position: absolute;
+    // display: flex;
+    z-index: 10000;
+    box-shadow: gray 0.2vw 0.2vw 0.4vw;
+    // flex-direction: column;
+    background: whitesmoke;
+    padding-bottom: 10px;
+    border-radius: 15px;
+    border: solid #BBBBBBBB 1px;
+    width: 200px;
+    text-align: center;
+    max-height: 250px;
+    text-transform: uppercase;
+    color: grey;
+    letter-spacing: 2px;
+}
+
+.content {
+    padding: 10px;
+    display: flex;
+    flex-direction: row;
+    align-items: center;
+    justify-content: center;
+}
+
+.image-background {
+    pointer-events: none;
+    background-color: transparent;
+    width: 50%;
+    text-align: center;
+    height: 35px;
+    margin-left: 5px;
+}
+
+img{
+    width: 100%;
+    height: 100%;
+}
+
+.score {
+    // margin-left: 15px;
+    width: 50%;
+    height: 100%;
+    text-align: center;
+}
diff --git a/src/client/views/Recommendations.tsx b/src/client/views/Recommendations.tsx
new file mode 100644
index 000000000..8569996b3
--- /dev/null
+++ b/src/client/views/Recommendations.tsx
@@ -0,0 +1,169 @@
+import { observer } from "mobx-react";
+import React = require("react");
+import { observable, action } from "mobx";
+import Measure from "react-measure";
+import "./Recommendations.scss";
+import { Doc, DocListCast, WidthSym, HeightSym } from "../../new_fields/Doc";
+import { DocumentIcon } from "./nodes/DocumentIcon";
+import { StrCast, NumCast } from "../../new_fields/Types";
+import { returnFalse, emptyFunction, returnEmptyString, returnOne } from "../../Utils";
+import { Transform } from "../util/Transform";
+import { ObjectField } from "../../new_fields/ObjectField";
+import { DocumentView } from "./nodes/DocumentView";
+import { DocumentType } from "../documents/Documents";
+
+
+export interface RecProps {
+    documents: { preview: Doc, similarity: number }[];
+    node: Doc;
+}
+
+@observer
+export class Recommendations extends React.Component<{}> {
+
+    static Instance: Recommendations;
+    @observable private _display: boolean = false;
+    @observable private _pageX: number = 0;
+    @observable private _pageY: number = 0;
+    @observable private _width: number = 0;
+    @observable private _height: number = 0;
+    @observable private _documents: { preview: Doc, score: number }[] = [];
+
+    constructor(props: {}) {
+        super(props);
+        Recommendations.Instance = this;
+    }
+
+    private DocumentIcon(doc: Doc) {
+        let layoutresult = StrCast(doc.type);
+        let renderDoc = doc;
+        //let box: number[] = [];
+        if (layoutresult.indexOf(DocumentType.COL) !== -1) {
+            renderDoc = Doc.MakeDelegate(renderDoc);
+            let bounds = DocListCast(renderDoc.data).reduce((bounds, doc) => {
+                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),
+                    r: Math.max(bptX, bounds.r), b: Math.max(bptY, bounds.b)
+                };
+            }, { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE });
+        }
+        let returnXDimension = () => 50;
+        let returnYDimension = () => 50;
+        let scale = () => returnXDimension() / NumCast(renderDoc.nativeWidth, returnXDimension());
+        let newRenderDoc = Doc.MakeDelegate(renderDoc); ///   newRenderDoc -> renderDoc -> render"data"Doc -> TextProt
+        const docview = <div>
+            {/* 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
+                fitToBox={StrCast(doc.type).indexOf(DocumentType.COL) !== -1}
+                Document={doc}
+                addDocument={returnFalse}
+                removeDocument={returnFalse}
+                ScreenToLocalTransform={Transform.Identity}
+                addDocTab={returnFalse}
+                renderDepth={1}
+                PanelWidth={returnXDimension}
+                PanelHeight={returnYDimension}
+                focus={emptyFunction}
+                backgroundColor={returnEmptyString}
+                selectOnLoad={false}
+                parentActive={returnFalse}
+                whenActiveChanged={returnFalse}
+                bringToFront={emptyFunction}
+                zoomToScale={emptyFunction}
+                getScale={returnOne}
+                ContainingCollectionView={undefined}
+                ContentScaling={scale}
+            />
+        </div>;
+        const data = renderDoc.data;
+        if (data instanceof ObjectField) newRenderDoc.data = ObjectField.MakeCopy(data);
+        newRenderDoc.preview = true;
+        return docview;
+
+    }
+
+    @action
+    closeMenu = () => {
+        this._display = false;
+    }
+
+    @action
+    resetDocuments = () => {
+        this._documents = [];
+    }
+
+    @action
+    addDocuments = (documents: { preview: Doc, score: number }[]) => {
+        this._documents = documents;
+    }
+
+    @action
+    displayRecommendations(x: number, y: number) {
+        this._pageX = x;
+        this._pageY = y;
+        this._display = true;
+    }
+
+    static readonly buffer = 20;
+
+    get pageX() {
+        const x = this._pageX;
+        if (x < 0) {
+            return 0;
+        }
+        const width = this._width;
+        if (x + width > window.innerWidth - Recommendations.buffer) {
+            return window.innerWidth - Recommendations.buffer - width;
+        }
+        return x;
+    }
+
+    get pageY() {
+        const y = this._pageY;
+        if (y < 0) {
+            return 0;
+        }
+        const height = this._height;
+        if (y + height > window.innerHeight - Recommendations.buffer) {
+            return window.innerHeight - Recommendations.buffer - height;
+        }
+        return y;
+    }
+
+    render() {
+        if (!this._display) {
+            return null;
+        }
+        let style = { left: this.pageX, top: this.pageY };
+        //const transform = "translate(" + (NumCast(this.props.node.x) + 350) + "px, " + NumCast(this.props.node.y) + "px"
+        return (
+            <Measure offset onResize={action((r: any) => { this._width = r.offset.width; this._height = r.offset.height; })}>
+                {({ measureRef }) => (
+                    <div className="rec-scroll" style={style} ref={measureRef}>
+                        <p>Recommendations</p>
+                        {this._documents.map(doc => {
+                            return (
+                                <div className="content">
+                                    <span className="image-background">
+                                        {this.DocumentIcon(doc.preview)}
+                                    </span>
+                                    <span className="score">{doc.score}</span>
+                                </div>
+                            );
+                        })}
+
+                    </div>
+                )
+                }
+
+            </Measure>
+        );
+    }
+}
\ 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 d1e8031fd..50f7e2dc8 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -896,10 +896,13 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 allDocs.forEach(doc => console.log(doc.title));
                 // clears internal representation of documents as vectors
                 ClientRecommender.Instance.reset_docs();
-                await Promise.all(activedocs.map((doc: Doc) => {
-                    //console.log(StrCast(doc.title));
-                    const extdoc = doc.data_ext as Doc;
-                    return ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc);
+                await Promise.all(allDocs.map((doc: Doc) => {
+                    console.log(StrCast(doc.title));
+                    if (doc.type === DocumentType.IMG) {
+                        console.log(doc.title);
+                        const extdoc = doc.data_ext as Doc;
+                        return ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc);
+                    }
                 }));
                 console.log(ClientRecommender.Instance.createDistanceMatrix());
             },
@@ -1013,7 +1016,7 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF
         const zoom = this.props.zoomScaling();// needs to be a variable outside of the <Measure> otherwise, reactions won't fire
         return <div className={freeformclass} style={{ borderRadius: "inherit", transform: `translate(${cenx}px, ${ceny}px) scale(${zoom}, ${zoom}) translate(${panx}px, ${pany}px)` }}>
             {this.props.children}
-            <ClientRecommender title="Distance Matrix" />
+            {/* <ClientRecommender title="Distance Matrix" /> */}
         </div>;
     }
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 24bcc0217..3ce4dbd4f 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -40,9 +40,13 @@ import React = require("react");
 import { DictationManager } from '../../util/DictationManager';
 import { MainView } from '../MainView';
 import requestPromise = require('request-promise');
-import { Recommendations } from '../../../Recommendations';
+import { Recommendations } from '../Recommendations';
+import { SearchUtil } from '../../util/SearchUtil';
+import { ClientRecommender } from '../../ClientRecommender';
+import { DocumentType } from '../../documents/Documents';
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
+library.add(fa.faBrain);
 library.add(fa.faTrash);
 library.add(fa.faShare);
 library.add(fa.faDownload);
@@ -610,6 +614,33 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 a.click();
             }
         });
+        cm.addItem({
+            description: "Recommender System",
+            event: async () => {
+                if (!ClientRecommender.Instance) new ClientRecommender({ title: "Client Recommender" });
+                let documents: Doc[] = [];
+                let allDocs = await SearchUtil.GetAllDocs();
+                allDocs.forEach(doc => console.log(doc.title));
+                // clears internal representation of documents as vectors
+                ClientRecommender.Instance.reset_docs();
+                await Promise.all(allDocs.map((doc: Doc) => {
+                    if (doc.type === DocumentType.IMG) {
+                        console.log(StrCast(doc.title));
+                        documents.push(doc);
+                        const extdoc = doc.data_ext as Doc;
+                        return ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc);
+                    }
+                }));
+                console.log(ClientRecommender.Instance.createDistanceMatrix());
+                let recDocs: { preview: Doc, score: number }[] = [];
+                for (let i = 0; i < documents.length; i++) {
+                    recDocs.push({ preview: documents[i], score: i });
+                }
+                Recommendations.Instance.addDocuments(recDocs);
+                Recommendations.Instance.displayRecommendations(e.pageX + 100, e.pageY);
+            },
+            icon: "brain"
+        });
         cm.addItem({ description: "Delete", event: this.deleteClicked, icon: "trash" });
         type User = { email: string, userDocumentId: string };
         let usersMenu: ContextMenuProps[] = [];
@@ -758,7 +789,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                         </div>
                     }
                 </div>
-                <Recommendations documents={documents} node={this.props.Document}></Recommendations>
             </div>
         );
     }
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 73b892e26..45d389ba6 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -28,6 +28,9 @@ import FaceRectangles from './FaceRectangles';
 import { FieldView, FieldViewProps } from './FieldView';
 import "./ImageBox.scss";
 import React = require("react");
+import { SearchUtil } from '../../util/SearchUtil';
+import { ClientRecommender } from '../../ClientRecommender';
+import { DocumentType } from '../../documents/Documents';
 var requestImageSize = require('../../util/request-image-size');
 var path = require('path');
 const { Howl } = require('howler');
@@ -240,10 +243,20 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
         }
     }
 
-    extractText = () => {
-        //Recommender.Instance.extractText(this.dataDoc, this.extensionDoc);
-        // request recommender 
-        //fetch(Utils.prepend("/recommender"), { body: body, method: "POST", headers: { "content-type": "application/json" } }).then((value) => console.log(value));
+    extractText = async () => {
+        //let activedocs = this.getActiveDocuments();
+        let allDocs = await SearchUtil.GetAllDocs();
+        allDocs.forEach(doc => console.log(doc.title));
+        // clears internal representation of documents as vectors
+        ClientRecommender.Instance.reset_docs();
+        await Promise.all(allDocs.map((doc: Doc) => {
+            //console.log(StrCast(doc.title));
+            if (doc.type === DocumentType.IMG) {
+                const extdoc = doc.data_ext as Doc;
+                return ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc);
+            }
+        }));
+        console.log(ClientRecommender.Instance.createDistanceMatrix());
     }
 
     generateMetadata = (threshold: Confidence = Confidence.Excellent) => {
-- 
cgit v1.2.3-70-g09d2


From e0bfe978e029268b3901b5d098f946b1a6fc7d0d Mon Sep 17 00:00:00 2001
From: ab <abdullah_ahmed@brown.edu>
Date: Thu, 29 Aug 2019 18:43:32 -0400
Subject: ui fixes, datadoc resolved

---
 src/client/ClientRecommender.tsx                   | 83 +++++++++++++++-------
 src/client/cognitive_services/CognitiveServices.ts | 24 ++++---
 src/client/views/MainView.tsx                      |  1 +
 src/client/views/Recommendations.tsx               | 21 ++++--
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 21 ------
 src/client/views/nodes/DocumentView.tsx            | 26 ++++---
 src/client/views/nodes/ImageBox.tsx                | 16 -----
 7 files changed, 104 insertions(+), 88 deletions(-)

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

diff --git a/src/client/ClientRecommender.tsx b/src/client/ClientRecommender.tsx
index 63f85c737..a6d1a32b3 100644
--- a/src/client/ClientRecommender.tsx
+++ b/src/client/ClientRecommender.tsx
@@ -1,5 +1,5 @@
 import { Doc } from "../new_fields/Doc";
-import { StrCast } from "../new_fields/Types";
+import { StrCast, Cast } from "../new_fields/Types";
 import { List } from "../new_fields/List";
 import { CognitiveServices } from "./cognitive_services/CognitiveServices";
 import React = require("react");
@@ -8,30 +8,42 @@ import { observable, action, computed, reaction } from "mobx";
 var assert = require('assert');
 import "./ClientRecommender.scss";
 import { JSXElement } from "babel-types";
+import { ToPlainText, RichTextField } from "../new_fields/RichTextField";
 
 export interface RecommenderProps {
     title: string;
 }
 
+export interface RecommenderDocument {
+    actualDoc: Doc;
+    vectorDoc: number[];
+    score: number;
+}
+
 @observer
 export class ClientRecommender extends React.Component<RecommenderProps> {
 
     static Instance: ClientRecommender;
-    private docVectors: Set<number[]>;
+    private mainDoc?: RecommenderDocument;
+    private docVectors: Set<RecommenderDocument> = new Set();
     @observable private corr_matrix = [[0, 0], [0, 0]];
 
     constructor(props: RecommenderProps) {
         //console.log("creating client recommender...");
         super(props);
         if (!ClientRecommender.Instance) ClientRecommender.Instance = this;
-        this.docVectors = new Set<number[]>();
-        //this.corr_matrix = [[0, 0], [0, 0]];
+        ClientRecommender.Instance.docVectors = new Set();
+        //ClientRecommender.Instance.corr_matrix = [[0, 0], [0, 0]];
     }
 
     @action
     public reset_docs() {
-        this.docVectors = new Set();
-        this.corr_matrix = [[0, 0], [0, 0]];
+        ClientRecommender.Instance.docVectors = new Set();
+        ClientRecommender.Instance.corr_matrix = [[0, 0], [0, 0]];
+    }
+
+    public deleteDocs() {
+        console.log("deleting previews...");
     }
 
     /***
@@ -67,11 +79,24 @@ export class ClientRecommender extends React.Component<RecommenderProps> {
         }
     }
 
+    public computeSimilarities() {
+        ClientRecommender.Instance.docVectors.forEach((doc: RecommenderDocument) => {
+            if (ClientRecommender.Instance.mainDoc) {
+                const distance = ClientRecommender.Instance.distance(ClientRecommender.Instance.mainDoc.vectorDoc, doc.vectorDoc, "euclidian");
+                doc.score = distance;
+            }
+        }
+        );
+        let doclist = Array.from(ClientRecommender.Instance.docVectors);
+        doclist.sort((a: RecommenderDocument, b: RecommenderDocument) => a.score - b.score);
+        return doclist;
+    }
+
     /***
      * Computes the mean of a set of vectors
      */
 
-    public mean(paragraph: Set<number[]>) {
+    public mean(paragraph: Set<number[]>, dataDoc: Doc, mainDoc: boolean) {
         const n = 200;
         const num_words = paragraph.size;
         let meanVector = new Array<number>(n).fill(0); // mean vector
@@ -82,14 +107,16 @@ export class ClientRecommender extends React.Component<RecommenderProps> {
                 }
             });
             meanVector = meanVector.map(x => x / num_words);
-            this.addToDocSet(meanVector);
+            const internalDoc: RecommenderDocument = { actualDoc: dataDoc, vectorDoc: meanVector, score: 0 };
+            if (mainDoc) ClientRecommender.Instance.mainDoc = internalDoc;
+            ClientRecommender.Instance.addToDocSet(internalDoc);
         }
         return meanVector;
     }
 
-    private addToDocSet(vector: number[]) {
-        if (this.docVectors) {
-            this.docVectors.add(vector);
+    private addToDocSet(internalDoc: RecommenderDocument) {
+        if (ClientRecommender.Instance.docVectors) {
+            ClientRecommender.Instance.docVectors.add(internalDoc);
         }
     }
 
@@ -97,9 +124,11 @@ export class ClientRecommender extends React.Component<RecommenderProps> {
      * Uses Cognitive Services to extract keywords from a document
      */
 
-    public async extractText(dataDoc: Doc, extDoc: Doc) {
-        let data = StrCast(dataDoc.title);
-        //console.log(data);
+    public async extractText(dataDoc: Doc, extDoc: Doc, mainDoc: boolean = false) {
+        let fielddata = Cast(dataDoc.data, RichTextField);
+        let data: string;
+        fielddata ? data = fielddata[ToPlainText]() : data = "";
+        console.log(data);
         let converter = (results: any) => {
             let keyterms = new List<string>();
             results.documents.forEach((doc: any) => {
@@ -108,7 +137,7 @@ export class ClientRecommender extends React.Component<RecommenderProps> {
             });
             return keyterms;
         };
-        await CognitiveServices.Text.Appliers.analyzer(extDoc, ["key words"], data, converter);
+        await CognitiveServices.Text.Appliers.analyzer(dataDoc, extDoc, ["key words"], data, converter, mainDoc);
     }
 
     /***
@@ -116,7 +145,7 @@ export class ClientRecommender extends React.Component<RecommenderProps> {
      */
 
     @action
-    public createDistanceMatrix(documents: Set<number[]> = this.docVectors) {
+    public createDistanceMatrix(documents: Set<RecommenderDocument> = ClientRecommender.Instance.docVectors) {
         const documents_list = Array.from(documents);
         const n = documents_list.length;
         var matrix = new Array<number>(n).fill(0).map(() => new Array<number>(n).fill(0));
@@ -124,22 +153,22 @@ export class ClientRecommender extends React.Component<RecommenderProps> {
             var doc1 = documents_list[i];
             for (let j = 0; j < n; j++) {
                 var doc2 = documents_list[j];
-                matrix[i][j] = this.distance(doc1, doc2, "euclidian");
+                matrix[i][j] = ClientRecommender.Instance.distance(doc1.vectorDoc, doc2.vectorDoc, "euclidian");
             }
         }
-        this.corr_matrix = matrix;
+        ClientRecommender.Instance.corr_matrix = matrix;
         return matrix;
     }
 
     @computed
     private get generateRows() {
-        const n = this.corr_matrix.length;
+        const n = ClientRecommender.Instance.corr_matrix.length;
         let rows: JSX.Element[] = [];
         for (let i = 0; i < n; i++) {
             let children: JSX.Element[] = [];
             for (let j = 0; j < n; j++) {
-                //let cell = React.createElement("td", this.corr_matrix[i][j]);
-                let cell = <td>{this.corr_matrix[i][j].toFixed(4)}</td>;
+                //let cell = React.createElement("td", ClientRecommender.Instance.corr_matrix[i][j]);
+                let cell = <td>{ClientRecommender.Instance.corr_matrix[i][j].toFixed(4)}</td>;
                 children.push(cell);
             }
             //let row = React.createElement("tr", { children: children, key: i });
@@ -151,22 +180,22 @@ export class ClientRecommender extends React.Component<RecommenderProps> {
 
     render() {
         return (<div className="wrapper">
-            <h3 >{this.props.title ? this.props.title : "hello"}</h3>
+            <h3 >{ClientRecommender.Instance.props.title ? ClientRecommender.Instance.props.title : "hello"}</h3>
             {/* <table className="space" >
                 <tbody>
                     <tr key="1">
-                        <td key="1">{this.corr_matrix[0][0].toFixed(4)}</td>
-                        <td key="2">{this.corr_matrix[0][1].toFixed(4)}</td>
+                        <td key="1">{ClientRecommender.Instance.corr_matrix[0][0].toFixed(4)}</td>
+                        <td key="2">{ClientRecommender.Instance.corr_matrix[0][1].toFixed(4)}</td>
                     </tr>
                     <tr key="2">
-                        <td key="1">{this.corr_matrix[1][0].toFixed(4)}</td>
-                        <td key="2">{this.corr_matrix[1][1].toFixed(4)}</td>
+                        <td key="1">{ClientRecommender.Instance.corr_matrix[1][0].toFixed(4)}</td>
+                        <td key="2">{ClientRecommender.Instance.corr_matrix[1][1].toFixed(4)}</td>
                     </tr>
                 </tbody>
             </table> */}
             <table className="space">
                 <tbody>
-                    {this.generateRows}
+                    {ClientRecommender.Instance.generateRows}
                 </tbody>
             </table>
         </div>);
diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts
index 75d0760ed..874ee433d 100644
--- a/src/client/cognitive_services/CognitiveServices.ts
+++ b/src/client/cognitive_services/CognitiveServices.ts
@@ -263,29 +263,35 @@ export namespace CognitiveServices {
 
         export namespace Appliers {
 
-            export async function vectorize(keyterms: any) {
+            export async function vectorize(keyterms: any, dataDoc: Doc, mainDoc: boolean = false) {
                 console.log("vectorizing...");
                 //keyterms = ["father", "king"];
                 let args = { method: 'POST', uri: Utils.prepend("/recommender"), body: { keyphrases: keyterms }, json: true };
                 await requestPromise.post(args).then(async (wordvecs) => {
-                    var vectorValues = new Set<number[]>();
-                    wordvecs.forEach((wordvec: any) => {
-                        //console.log(wordvec.word);
-                        vectorValues.add(wordvec.values as number[]);
-                    });
-                    ClientRecommender.Instance.mean(vectorValues);
+                    if (wordvecs.length > 0) {
+                        console.log("successful vectorization!");
+                        var vectorValues = new Set<number[]>();
+                        wordvecs.forEach((wordvec: any) => {
+                            //console.log(wordvec.word);
+                            vectorValues.add(wordvec.values as number[]);
+                        });
+                        ClientRecommender.Instance.mean(vectorValues, dataDoc, mainDoc);
+                    } // adds document to internal doc set
+                    else {
+                        console.log("unsuccessful :( word(s) not in vocabulary");
+                    }
                     //console.log(vectorValues.size);
                 });
             }
 
-            export const analyzer = async (target: Doc, keys: string[], data: string, converter: Converter) => {
+            export const analyzer = async (dataDoc: Doc, target: Doc, keys: string[], data: string, converter: Converter, mainDoc: boolean = false) => {
                 let results = await ExecuteQuery(Service.Text, Manager, data);
                 console.log(results);
                 let keyterms = converter(results);
                 //target[keys[0]] = Docs.Get.DocumentHierarchyFromJson(results, "Key Word Analysis");
                 target[keys[0]] = keyterms;
                 console.log("analyzed!");
-                await vectorize(keyterms);
+                await vectorize(keyterms, dataDoc, mainDoc);
             };
         }
 
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 57eb30439..3a5795077 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -204,6 +204,7 @@ export class MainView extends React.Component {
         const targets = document.elementsFromPoint(e.x, e.y);
         if (targets && targets.length && targets[0].className.toString().indexOf("contextMenu") === -1) {
             ContextMenu.Instance.closeMenu();
+            Recommendations.Instance.closeMenu();
         }
     });
 
diff --git a/src/client/views/Recommendations.tsx b/src/client/views/Recommendations.tsx
index 8569996b3..cf1974c69 100644
--- a/src/client/views/Recommendations.tsx
+++ b/src/client/views/Recommendations.tsx
@@ -10,8 +10,10 @@ import { returnFalse, emptyFunction, returnEmptyString, returnOne } from "../../
 import { Transform } from "../util/Transform";
 import { ObjectField } from "../../new_fields/ObjectField";
 import { DocumentView } from "./nodes/DocumentView";
-import { DocumentType } from "../documents/Documents";
-
+import { DocumentType } from '../documents/DocumentTypes';
+import { ClientRecommender } from "../ClientRecommender";
+import { DocServer } from "../DocServer";
+import { Id } from "../../new_fields/FieldSymbols";
 
 export interface RecProps {
     documents: { preview: Doc, similarity: number }[];
@@ -28,6 +30,7 @@ export class Recommendations extends React.Component<{}> {
     @observable private _width: number = 0;
     @observable private _height: number = 0;
     @observable private _documents: { preview: Doc, score: number }[] = [];
+    private previewDocs: Doc[] = [];
 
     constructor(props: {}) {
         super(props);
@@ -52,7 +55,8 @@ export class Recommendations extends React.Component<{}> {
         let returnXDimension = () => 50;
         let returnYDimension = () => 50;
         let scale = () => returnXDimension() / NumCast(renderDoc.nativeWidth, returnXDimension());
-        let newRenderDoc = Doc.MakeDelegate(renderDoc); ///   newRenderDoc -> renderDoc -> render"data"Doc -> TextProt
+        //let scale = () => 1;
+        //let newRenderDoc = Doc.MakeDelegate(renderDoc); ///   newRenderDoc -> renderDoc -> render"data"Doc -> TextProt
         const docview = <div>
             {/* onPointerDown={action(() => {
                 this._useIcons = !this._useIcons;
@@ -62,7 +66,7 @@ export class Recommendations extends React.Component<{}> {
             onPointerLeave={action(() => this._displayDim = 50)} > */}
             <DocumentView
                 fitToBox={StrCast(doc.type).indexOf(DocumentType.COL) !== -1}
-                Document={doc}
+                Document={renderDoc}
                 addDocument={returnFalse}
                 removeDocument={returnFalse}
                 ScreenToLocalTransform={Transform.Identity}
@@ -82,9 +86,10 @@ export class Recommendations extends React.Component<{}> {
                 ContentScaling={scale}
             />
         </div>;
-        const data = renderDoc.data;
-        if (data instanceof ObjectField) newRenderDoc.data = ObjectField.MakeCopy(data);
-        newRenderDoc.preview = true;
+        // const data = renderDoc.data;
+        // if (data instanceof ObjectField) newRenderDoc.data = ObjectField.MakeCopy(data);
+        // newRenderDoc.preview = true;
+        // this.previewDocs.push(newRenderDoc);
         return docview;
 
     }
@@ -92,6 +97,8 @@ export class Recommendations extends React.Component<{}> {
     @action
     closeMenu = () => {
         this._display = false;
+        this.previewDocs.forEach(doc => DocServer.DeleteDocument(doc[Id]));
+        this.previewDocs = [];
     }
 
     @action
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 2d4775070..3cef93383 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -857,27 +857,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 input.click();
             }
         });
-        ContextMenu.Instance.addItem({
-            description: "Recommender System",
-            event: async () => {
-                // if (!ClientRecommender.Instance) new ClientRecommender({ title: "Client Recommender" });
-                let activedocs = this.getActiveDocuments();
-                let allDocs = await SearchUtil.GetAllDocs();
-                allDocs.forEach(doc => console.log(doc.title));
-                // clears internal representation of documents as vectors
-                ClientRecommender.Instance.reset_docs();
-                await Promise.all(allDocs.map((doc: Doc) => {
-                    console.log(StrCast(doc.title));
-                    if (doc.type === DocumentType.IMG) {
-                        console.log(doc.title);
-                        const extdoc = doc.data_ext as Doc;
-                        return ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc);
-                    }
-                }));
-                console.log(ClientRecommender.Instance.createDistanceMatrix());
-            },
-            icon: "brain"
-        });
         layoutItems.push({ description: `${this.fitToBox ? "Unset" : "Set"} Fit To Container`, event: this.fitToContainer, icon: !this.fitToBox ? "expand-arrows-alt" : "compress-arrows-alt" });
         layoutItems.push({ description: "reset view", event: () => { this.props.Document.panX = this.props.Document.panY = 0; this.props.Document.scale = 1; }, icon: "compress-arrows-alt" });
         layoutItems.push({
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 2a6e91272..f708a7a3a 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -648,21 +648,31 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 if (!ClientRecommender.Instance) new ClientRecommender({ title: "Client Recommender" });
                 let documents: Doc[] = [];
                 let allDocs = await SearchUtil.GetAllDocs();
-                allDocs.forEach(doc => console.log(doc.title));
+                //allDocs.forEach(doc => console.log(doc.title));
                 // clears internal representation of documents as vectors
                 ClientRecommender.Instance.reset_docs();
                 await Promise.all(allDocs.map((doc: Doc) => {
-                    if (doc.type === DocumentType.IMG) {
-                        console.log(StrCast(doc.title));
-                        documents.push(doc);
-                        const extdoc = doc.data_ext as Doc;
-                        return ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc);
+                    let mainDoc: boolean = false;
+                    const dataDoc = Doc.GetDataDoc(doc);
+                    if (doc.type === DocumentType.TEXT) {
+                        if (dataDoc === Doc.GetDataDoc(this.props.Document)) {
+                            mainDoc = true;
+                            console.log(StrCast(doc.title));
+                        }
+                        if (!documents.includes(dataDoc)) {
+                            documents.push(dataDoc);
+                            const extdoc = doc.data_ext as Doc;
+                            return ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc, mainDoc);
+                        }
                     }
                 }));
                 console.log(ClientRecommender.Instance.createDistanceMatrix());
+                const doclist = ClientRecommender.Instance.computeSimilarities();
                 let recDocs: { preview: Doc, score: number }[] = [];
-                for (let i = 0; i < documents.length; i++) {
-                    recDocs.push({ preview: documents[i], score: i });
+                // tslint:disable-next-line: prefer-for-of
+                for (let i = 0; i < doclist.length; i++) {
+                    console.log(doclist[i].score);
+                    recDocs.push({ preview: doclist[i].actualDoc, score: doclist[i].score });
                 }
                 Recommendations.Instance.addDocuments(recDocs);
                 Recommendations.Instance.displayRecommendations(e.pageX + 100, e.pageY);
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index ec35465eb..d94e92847 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -240,22 +240,6 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
         }
     }
 
-    extractText = async () => {
-        //let activedocs = this.getActiveDocuments();
-        let allDocs = await SearchUtil.GetAllDocs();
-        allDocs.forEach(doc => console.log(doc.title));
-        // clears internal representation of documents as vectors
-        ClientRecommender.Instance.reset_docs();
-        await Promise.all(allDocs.map((doc: Doc) => {
-            //console.log(StrCast(doc.title));
-            if (doc.type === DocumentType.IMG) {
-                const extdoc = doc.data_ext as Doc;
-                return ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc);
-            }
-        }));
-        console.log(ClientRecommender.Instance.createDistanceMatrix());
-    }
-
     generateMetadata = (threshold: Confidence = Confidence.Excellent) => {
         let converter = (results: any) => {
             let tagDoc = new Doc;
-- 
cgit v1.2.3-70-g09d2


From c6e2eee8e5b035ed4cab1e7bb1315a36d43255c5 Mon Sep 17 00:00:00 2001
From: ab <abdullah_ahmed@brown.edu>
Date: Tue, 8 Oct 2019 16:02:25 -0400
Subject: switching out

---
 src/client/views/MainView.tsx                           |  1 -
 src/client/views/Recommendations.tsx                    |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx       |  3 ---
 src/client/views/nodes/DocumentContentsView.tsx         |  6 ------
 src/client/views/nodes/DocumentView.tsx                 |  4 ++--
 src/client/views/nodes/ImageBox.tsx                     |  1 -
 src/new_fields/RichTextField.ts                         | 17 ++++++++++++++++-
 7 files changed, 19 insertions(+), 15 deletions(-)

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

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 17de708a2..e6f500e75 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -41,7 +41,6 @@ import { FilterBox } from './search/FilterBox';
 import { SchemaHeaderField, RandomPastel } from '../../new_fields/SchemaHeaderField';
 //import { DocumentManager } from '../util/DocumentManager';
 import { RecommendationsBox } from './Recommendations';
-import PresModeMenu from './presentationview/PresentationModeMenu';
 import { PresBox } from './nodes/PresBox';
 import { OverlayView } from './OverlayView';
 
diff --git a/src/client/views/Recommendations.tsx b/src/client/views/Recommendations.tsx
index b7b1d84d0..c44dfc032 100644
--- a/src/client/views/Recommendations.tsx
+++ b/src/client/views/Recommendations.tsx
@@ -174,7 +174,7 @@ export class RecommendationsBox extends React.Component<FieldViewProps> {
                                 {this.DocumentIcon(doc)}
                             </span>
                             <span className="score">{NumCast(doc.score).toFixed(4)}</span>
-                            <div style={{ marginRight: 50 }} onClick={() => DocumentManager.Instance.jumpToDocument(doc, true, undefined, undefined, undefined, this.props.Document.sourceDocContext as Doc)}>
+                            <div style={{ marginRight: 50 }} onClick={() => DocumentManager.Instance.jumpToDocument(doc, false)}>
                                 <FontAwesomeIcon className="documentdecorations-icon" icon={"bullseye"} size="sm" />
                             </div>
                             <div style={{ marginRight: 50 }} onClick={() => DocUtils.MakeLink(this.props.Document.sourceDoc as Doc, doc, undefined, "User Selected Link", "Generated from Recommender", undefined)}>
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 3ee069e4c..d2b8afa02 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -12,7 +12,6 @@ import { BoolCast, Cast, DateCast, NumCast, StrCast } from "../../../../new_fiel
 import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils";
 import { aggregateBounds, emptyFunction, intersectRect, returnEmptyString, returnOne, Utils } from "../../../../Utils";
 import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
-import { DocServer } from "../../../DocServer";
 import { Docs } from "../../../documents/Documents";
 import { DocumentType } from "../../../documents/DocumentTypes";
 import { DocumentManager } from "../../../util/DocumentManager";
@@ -28,7 +27,6 @@ import { InkingCanvas } from "../../InkingCanvas";
 import { CollectionFreeFormDocumentView, positionSchema } from "../../nodes/CollectionFreeFormDocumentView";
 import { DocumentContentsView } from "../../nodes/DocumentContentsView";
 import { documentSchema, DocumentViewProps } from "../../nodes/DocumentView";
-import { FormattedTextBox } from "../../nodes/FormattedTextBox";
 import { pageSchema } from "../../nodes/ImageBox";
 import PDFMenu from "../../pdf/PDFMenu";
 import { CollectionSubView } from "../CollectionSubView";
@@ -41,7 +39,6 @@ import React = require("react");
 import v5 = require("uuid/v5");
 import { ClientRecommender } from "../../../ClientRecommender";
 import { SearchUtil } from "../../../util/SearchUtil";
-import { SearchBox } from "../../SearchBox";
 import { RouteStore } from "../../../../server/RouteStore";
 import { string, number, elementType } from "prop-types";
 import { DocServer } from "../../../DocServer";
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index e5eb8dbce..919c13055 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -2,8 +2,6 @@ import { computed } from "mobx";
 import { observer } from "mobx-react";
 import { Doc } from "../../../new_fields/Doc";
 import { ScriptField } from "../../../new_fields/ScriptField";
-import { Cast } from "../../../new_fields/Types";
-import { OmitKeys, Without } from "../../../Utils";
 import { HistogramBox } from "../../northstar/dash-nodes/HistogramBox";
 import DirectoryImportBox from "../../util/Import & Export/DirectoryImportBox";
 import { CollectionDockingView } from "../collections/CollectionDockingView";
@@ -30,14 +28,10 @@ import { PresElementBox } from "../presentationview/PresElementBox";
 import { VideoBox } from "./VideoBox";
 import { WebBox } from "./WebBox";
 import React = require("react");
-import { FieldViewProps } from "./FieldView";
 import { Without, OmitKeys } from "../../../Utils";
 import { Cast, StrCast, NumCast } from "../../../new_fields/Types";
 import { List } from "../../../new_fields/List";
-import { Doc } from "../../../new_fields/Doc";
-import DirectoryImportBox from "../../util/Import & Export/DirectoryImportBox";
 import { RecommendationsBox } from "../../views/Recommendations";
-import { ScriptField } from "../../../new_fields/ScriptField";
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
 type BindingProps = Without<FieldViewProps, 'fieldKey'>;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 0064b98c3..070b1f426 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -621,7 +621,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         recommendations.documentIconHeight = 150;
         recommendations.sourceDoc = this.props.Document;
         recommendations.sourceDocContext = this.props.ContainingCollectionView!.props.Document;
-        CollectionDockingView.Instance.AddRightSplit(recommendations, undefined);
+        CollectionDockingView.AddRightSplit(recommendations, undefined);
 
         // RecommendationsBox.Instance.displayRecommendations(e.pageX + 100, e.pageY);
     }
@@ -641,7 +641,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             body.href = urls[i];
             bodies.push(body);
         }
-        CollectionDockingView.Instance.AddRightSplit(Docs.Create.SchemaDocument(headers, bodies, { title: `Showing External Recommendations for "${StrCast(doc.title)}"` }), undefined);
+        CollectionDockingView.AddRightSplit(Docs.Create.SchemaDocument(headers, bodies, { title: `Showing External Recommendations for "${StrCast(doc.title)}"` }), undefined);
     }
 
     onPointerEnter = (e: React.PointerEvent): void => { Doc.BrushDoc(this.props.Document); };
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index f36b9895f..7ffe64b9b 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -29,7 +29,6 @@ import "./ImageBox.scss";
 import React = require("react");
 import { SearchUtil } from '../../util/SearchUtil';
 import { ClientRecommender } from '../../ClientRecommender';
-import { DocumentType } from '../../documents/Documents';
 var requestImageSize = require('../../util/request-image-size');
 var path = require('path');
 const { Howl } = require('howler');
diff --git a/src/new_fields/RichTextField.ts b/src/new_fields/RichTextField.ts
index 390045ee1..f41ea0350 100644
--- a/src/new_fields/RichTextField.ts
+++ b/src/new_fields/RichTextField.ts
@@ -4,6 +4,9 @@ import { Deserializable } from "../client/util/SerializationHelper";
 import { Copy, ToScriptString, ToPlainText } from "./FieldSymbols";
 import { scriptingGlobal } from "../client/util/Scripting";
 
+const delimiter = "\n";
+const joiner = "";
+
 @scriptingGlobal
 @Deserializable("RichTextField")
 export class RichTextField extends ObjectField {
@@ -24,7 +27,19 @@ export class RichTextField extends ObjectField {
     }
 
     [ToPlainText]() {
-        return this.Data;
+        // Because we're working with plain text, just concatenate all paragraphs
+        let content = JSON.parse(this.Data).doc.content;
+        let paragraphs = content.filter((item: any) => item.type === "paragraph");
+
+        // Functions to flatten ProseMirror paragraph objects (and their components) to plain text
+        // While this function already exists in state.doc.textBeteen(), it doesn't account for newlines 
+        let blockText = (block: any) => block.text;
+        let concatenateParagraph = (p: any) => (p.content ? p.content.map(blockText).join(joiner) : "") + delimiter;
+
+        // Concatentate paragraphs and string the result together
+        let textParagraphs: string[] = paragraphs.map(concatenateParagraph);
+        let plainText = textParagraphs.join(joiner);
+        return plainText.substring(0, plainText.length - 1);
     }
 
 }
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From e103be2d58da2a6809dd4ad5f0b5f445d8d6c96b Mon Sep 17 00:00:00 2001
From: ab <abdullah_ahmed@brown.edu>
Date: Sat, 16 Nov 2019 16:45:41 -0500
Subject: ibm integrated, bing search -> server next time

---
 package.json                                       |  7 ++-
 src/client/ClientRecommender.tsx                   | 66 ++++++++++++++++++++--
 src/client/apis/IBM_Recommender.ts                 |  6 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  5 --
 src/client/views/nodes/DocumentView.tsx            |  2 +-
 src/server/index.ts                                |  2 +-
 6 files changed, 70 insertions(+), 18 deletions(-)

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

diff --git a/package.json b/package.json
index 80b65cc68..d41119a47 100644
--- a/package.json
+++ b/package.json
@@ -57,9 +57,9 @@
         "@hig/theme-context": "^2.1.3",
         "@hig/theme-data": "^2.3.3",
         "@tensorflow-models/universal-sentence-encoder": "^1.2.0",
-        "@tensorflow/tfjs": "^1.3.1",
+        "@tensorflow/tfjs-converter": "^1.3.2",
         "@tensorflow/tfjs-core": "^1.3.1",
-        "@tensorflow/tfjs-node": "^1.3.1",
+        "@tensorflow/tfjs-node": "1.3.1",
         "@trendmicro/react-dropdown": "^1.3.0",
         "@types/adm-zip": "^0.4.32",
         "@types/animejs": "^2.0.2",
@@ -238,8 +238,9 @@
         "url-loader": "^1.1.2",
         "uuid": "^3.3.2",
         "wikijs": "^6.0.1",
+        "word2vec": "^1.1.4",
         "words-to-numbers": "^1.5.1",
         "xoauth2": "^1.2.0",
         "youtube": "^0.1.0"
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/ClientRecommender.tsx b/src/client/ClientRecommender.tsx
index 364ec0fe0..a37434c0d 100644
--- a/src/client/ClientRecommender.tsx
+++ b/src/client/ClientRecommender.tsx
@@ -8,6 +8,7 @@ import { observable, action, computed, reaction } from "mobx";
 var assert = require('assert');
 var sw = require('stopword');
 var FeedParser = require('feedparser');
+var https = require('https');
 import "./ClientRecommender.scss";
 import { JSXElement } from "babel-types";
 import { RichTextField } from "../new_fields/RichTextField";
@@ -111,7 +112,7 @@ export class ClientRecommender extends React.Component<RecommenderProps> {
         }
         );
         let doclist = Array.from(ClientRecommender.Instance.docVectors);
-        if (distance_metric == "euclidian") {
+        if (distance_metric === "euclidian") {
             doclist.sort((a: RecommenderDocument, b: RecommenderDocument) => a.score - b.score);
         }
         else {
@@ -245,7 +246,27 @@ export class ClientRecommender extends React.Component<RecommenderProps> {
             if (kp_string.length > 2) kp_string = kp_string.substring(0, kp_string.length - 2);
             console.log("kp string: ", kp_string);
             let values = "";
-            if (!internal) values = await this.sendRequest(highKP);
+            if (!internal) {
+                const parameters: any = {
+                    'language': 'en',
+                    'text': data,
+                    'features': {
+                        'keywords': {
+                            'sentiment': true,
+                            'emotion': true,
+                            'limit': 3
+                        }
+                    }
+                };
+                await Identified.PostToServer("/IBMAnalysis", parameters).then(response => {
+                    const sorted_keywords = response.result.keywords;
+                    if (sorted_keywords.length > 0) {
+                        console.log("IBM keyphrase", sorted_keywords[0]);
+                        highKP = [sorted_keywords[0].text];
+                    }
+                });
+                values = await this.sendRequest(highKP, "bing");
+            }
             return { keyterms: keyterms, external_recommendations: values, kp_string: [kp_string] };
         };
         if (data !== "") {
@@ -290,11 +311,46 @@ export class ClientRecommender extends React.Component<RecommenderProps> {
      * API for sending arXiv request.
      */
 
-    private async sendRequest(keywords: string[]) {
+    private async sendRequest(keywords: string[], api: string) {
         let query = "";
         keywords.forEach((kp: string) => query += " " + kp);
-        return new Promise<any>(resolve => {
-            this.arxivrequest(query).then(resolve);
+        if (api === "arxiv") {
+            return new Promise<any>(resolve => {
+                this.arxivrequest(query).then(resolve);
+            });
+        }
+        else if (api === "bing") {
+            await this.bingWebSearch(query);
+        }
+        else {
+            return new Promise<any>(resolve => {
+                this.arxivrequest(query).then(resolve);
+            });
+        }
+
+    }
+
+    bingWebSearch = async (query: string) => {
+        https.get({
+            hostname: 'api.cognitive.microsoft.com',
+            path: '/bing/v5.0/search?q=' + encodeURIComponent(query),
+            headers: { 'Ocp-Apim-Subscription-Key': process.env.BING },
+        }, (res: any) => {
+            let body = '';
+            res.on('data', (part: any) => body += part);
+            res.on('end', () => {
+                for (var header in res.headers) {
+                    if (header.startsWith("bingapis-") || header.startsWith("x-msedge-")) {
+                        console.log(header + ": " + res.headers[header])
+                    }
+                }
+                console.log('\nJSON Response:\n');
+                console.dir(JSON.parse(body), { colors: false, depth: null });
+            })
+            res.on('error', (e: any) => {
+                console.log('Error: ' + e.message);
+                throw e;
+            });
         });
     }
 
diff --git a/src/client/apis/IBM_Recommender.ts b/src/client/apis/IBM_Recommender.ts
index 5f80f5126..da6257f28 100644
--- a/src/client/apis/IBM_Recommender.ts
+++ b/src/client/apis/IBM_Recommender.ts
@@ -16,19 +16,19 @@ export namespace IBM_Recommender {
     });
 
     const analyzeParams = {
-        'url': 'www.ibm.com',
+        'text': 'this is a test of the keyword extraction feature I am integrating into the program',
         'features': {
             'keywords': {
                 'sentiment': true,
                 'emotion': true,
                 'limit': 3
-            }
+            },
         }
     };
 
     export const analyze = async (_parameters: any): Promise<Opt<string>> => {
         try {
-            const response = await naturalLanguageUnderstanding.analyze(analyzeParams);
+            const response = await naturalLanguageUnderstanding.analyze(_parameters);
             console.log(response);
             return (JSON.stringify(response, null, 2));
         } catch (err) {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index bb5d99a28..bfec545c6 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -34,11 +34,6 @@ import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCurso
 import "./CollectionFreeFormView.scss";
 import { MarqueeView } from "./MarqueeView";
 import React = require("react");
-import v5 = require("uuid/v5");
-import { ClientRecommender } from "../../../ClientRecommender";
-import { SearchUtil } from "../../../util/SearchUtil";
-import { RouteStore } from "../../../../server/RouteStore";
-import { string, number, elementType } from "prop-types";
 import { DocServer } from "../../../DocServer";
 import { documentSchema, positionSchema } from "../../../../new_fields/documentSchemas";
 import { DocumentViewProps } from "../../nodes/DocumentView";
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 55063a52c..c6ad2f9d7 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -772,7 +772,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     }
     render() {
         if (!this.props.Document) return (null);
-        trace();
+        //trace();
         const animDims = this.Document.animateToDimensions ? Array.from(this.Document.animateToDimensions) : undefined;
         const ruleColor = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleColor_" + this.Document.heading]) : undefined;
         const ruleRounding = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleRounding_" + this.Document.heading]) : undefined;
diff --git a/src/server/index.ts b/src/server/index.ts
index 2a669f147..45fc7fc07 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -1086,7 +1086,7 @@ addSecureRoute({
         const batched = BatchedArray.from<GooglePhotosUploadUtils.MediaInput>(media, { batchSize: 25 });
         const newMediaItems = await batched.batchedMapPatientInterval<NewMediaItem>(
             { magnitude: 100, unit: TimeUnit.Milliseconds },
-            async (batch, collector) => {
+            async (batch: any, collector: any): Promise<any> => {
                 for (let index = 0; index < batch.length; index++) {
                     const { url, description } = batch[index];
                     const uploadToken = await GooglePhotosUploadUtils.DispatchGooglePhotosUpload(url);
-- 
cgit v1.2.3-70-g09d2


From b4c2d2add2b863060ce559e49b3953f24050f162 Mon Sep 17 00:00:00 2001
From: Fawn <fangrui_tong@brown.edu>
Date: Tue, 14 Jan 2020 14:47:44 -0500
Subject: can add strokes to mobile ink

---
 src/client/views/collections/CollectionSubView.tsx |   3 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   1 +
 src/mobile/MobileInterface.tsx                     | 108 +++++++++++++++------
 .../authentication/models/current_user_utils.ts    |   4 +
 4 files changed, 87 insertions(+), 29 deletions(-)

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

diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 73dc7edc6..d8b575092 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -53,7 +53,9 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
         protected createDropAndGestureTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
             this.dropDisposer && this.dropDisposer();
             this.gestureDisposer && this.gestureDisposer();
+            console.log("create drop", ele);
             if (ele) {
+                console.log("create drop 2", ele);
                 this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
                 this.gestureDisposer = GestureUtils.MakeGestureTarget(ele, this.onGesture.bind(this));
             }
@@ -135,7 +137,6 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
 
         @undoBatch
         protected onGesture(e: Event, ge: GestureUtils.GestureEvent) {
-
         }
 
         @undoBatch
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 84945c6e6..c3e131184 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -356,6 +356,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
     @undoBatch
     onGesture = (e: Event, ge: GestureUtils.GestureEvent) => {
+        console.log("on gesture");
         switch (ge.gesture) {
             case GestureUtils.Gestures.Stroke:
                 const points = ge.points;
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index 1e0920686..8d342d749 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -10,8 +10,10 @@ import { DocumentView } from '../client/views/nodes/DocumentView';
 import { emptyPath, emptyFunction, returnFalse, returnOne, returnEmptyString, returnTrue } from '../Utils';
 import { Transform } from '../client/util/Transform';
 import { library } from '@fortawesome/fontawesome-svg-core';
-import { faPenNib, faHighlighter, faEraser, faMousePointer } from '@fortawesome/free-solid-svg-icons';
+import { faPenNib, faHighlighter, faEraser, faMousePointer, faBreadSlice } from '@fortawesome/free-solid-svg-icons';
 import { Scripting } from '../client/util/Scripting';
+import { CollectionFreeFormView } from '../client/views/collections/collectionFreeForm/CollectionFreeFormView';
+import GestureOverlay from '../client/views/GestureOverlay';
 
 @observer
 export default class MobileInterface extends React.Component {
@@ -35,52 +37,102 @@ export default class MobileInterface extends React.Component {
         }
     }
 
-    @action switchCurrentView = (view: "main" | "ink" | "library") => { this.currentView = view; }
+    @action
+    switchCurrentView = (view: "main" | "ink" | "library") => {
+        this.currentView = view;
+
+        if (this.userDoc) {
+            switch (view) {
+                case "main": {
+                    const doc = CurrentUserUtils.setupMobileDoc(this.userDoc);
+                    this.userDoc.activeMobile = doc;
+                    break;
+                }
+                case "ink": {
+                    const doc = CurrentUserUtils.setupMobileInkingDoc(this.userDoc);
+                    this.userDoc.activeMobile = doc;
+                    break;
+                }
+            }
+        }
+    }
 
     @computed
     get mainContent() {
         if (this.mainContainer) {
-            switch (this.currentView) {
-                case "main":
-                    return <DocumentView
+            return <DocumentView
+                Document={this.mainContainer}
+                DataDoc={undefined}
+                LibraryPath={emptyPath}
+                addDocument={undefined}
+                addDocTab={returnFalse}
+                pinToPres={emptyFunction}
+                removeDocument={undefined}
+                ruleProvider={undefined}
+                onClick={undefined}
+                ScreenToLocalTransform={Transform.Identity}
+                ContentScaling={returnOne}
+                PanelWidth={() => window.screen.width}
+                PanelHeight={() => window.screen.height}
+                renderDepth={0}
+                focus={emptyFunction}
+                backgroundColor={returnEmptyString}
+                parentActive={returnTrue}
+                whenActiveChanged={emptyFunction}
+                bringToFront={emptyFunction}
+                ContainingCollectionView={undefined}
+                ContainingCollectionDoc={undefined}
+                zoomToScale={emptyFunction}
+                getScale={returnOne}>
+            </DocumentView>;
+        }
+        return "hello";
+    }
+
+    @computed
+    get inkContent() {
+        // return <div>INK</div>;
+        if (this.mainContainer) {
+            return (
+                <GestureOverlay>
+                    <CollectionFreeFormView
                         Document={this.mainContainer}
                         DataDoc={undefined}
                         LibraryPath={emptyPath}
-                        addDocument={undefined}
+                        fieldKey={""}
+                        addDocument={returnFalse}
+                        removeDocument={returnFalse}
+                        moveDocument={returnFalse}
                         addDocTab={returnFalse}
                         pinToPres={emptyFunction}
-                        removeDocument={undefined}
-                        ruleProvider={undefined}
-                        onClick={undefined}
-                        ScreenToLocalTransform={Transform.Identity}
-                        ContentScaling={returnOne}
-                        PanelWidth={() => window.screen.width}
                         PanelHeight={() => window.screen.height}
-                        renderDepth={0}
+                        PanelWidth={() => window.screen.width}
+                        annotationsKey={""}
+                        isAnnotationOverlay={false}
                         focus={emptyFunction}
-                        backgroundColor={returnEmptyString}
-                        parentActive={returnTrue}
-                        whenActiveChanged={emptyFunction}
-                        bringToFront={emptyFunction}
+                        isSelected={returnTrue} //
+                        select={emptyFunction}
+                        active={returnTrue} //
+                        ContentScaling={returnOne}
+                        whenActiveChanged={returnFalse}
+                        CollectionView={undefined}
+                        ScreenToLocalTransform={Transform.Identity}
+                        ruleProvider={undefined}
+                        renderDepth={0}
                         ContainingCollectionView={undefined}
                         ContainingCollectionDoc={undefined}
-                        zoomToScale={emptyFunction}
-                        getScale={returnOne}>
-                    </DocumentView>;
-                case "ink":
-                    return <div>INK</div>;
-                case "library":
-                    return <div>LIBRARY</div>;
-            }
+                        chromeCollapsed={true}>
+                    </CollectionFreeFormView>
+                </GestureOverlay>
+            );
         }
-        return "hello";
     }
 
     render() {
-        console.log("rendering mobile");
+        const content = this.currentView === "main" ? this.mainContent : this.currentView === "ink" ? this.inkContent : <></>;
         return (
             <div className="mobile-container">
-                {this.mainContent}
+                {content}
             </div>
         );
     }
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 71369b625..b40179fbc 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -140,6 +140,10 @@ export class CurrentUserUtils {
         });
     }
 
+    static setupMobileInkingDoc(userDoc: Doc) {
+        return Docs.Create.FreeformDocument([], { x: 0, y: 0, width: 10, height: 20, title: "Mobile Inking", backgroundColor: "red" });
+    }
+
     // setup the Creator button which will display the creator panel.  This panel will include the drag creators and the color picker.  when clicked, this panel will be displayed in the target container (ie, sidebarContainer)  
     static setupToolsPanel(sidebarContainer: Doc, doc: Doc) {
         // setup a masonry view of all he creators
-- 
cgit v1.2.3-70-g09d2


From 3cca58612cde96a3082ca8e190fe2166d531d556 Mon Sep 17 00:00:00 2001
From: Fawn <fangrui_tong@brown.edu>
Date: Tue, 14 Jan 2020 17:40:50 -0500
Subject: drawn strokes get added to mobile ink collection

---
 src/client/views/GestureOverlay.tsx                |  2 +-
 src/client/views/collections/CollectionSubView.tsx |  2 -
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  8 +--
 src/mobile/MobileInterface.scss                    |  7 +++
 src/mobile/MobileInterface.tsx                     | 62 ++++++++++++++--------
 .../authentication/models/current_user_utils.ts    |  2 +-
 6 files changed, 53 insertions(+), 30 deletions(-)
 create mode 100644 src/mobile/MobileInterface.scss

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

diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 632f2f0d6..4d487c032 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -231,9 +231,9 @@ export default class GestureOverlay extends Touchable {
     render() {
         return (
             <div className="gestureOverlay-cont" onPointerDown={this.onPointerDown} onTouchStart={this.onTouchStart}>
+                {this.currentStroke}
                 {this.props.children}
                 {this._palette}
-                {this.currentStroke}
             </div>);
     }
 }
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index d8b575092..e94f24f2c 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -53,9 +53,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
         protected createDropAndGestureTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
             this.dropDisposer && this.dropDisposer();
             this.gestureDisposer && this.gestureDisposer();
-            console.log("create drop", ele);
             if (ele) {
-                console.log("create drop 2", ele);
                 this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
                 this.gestureDisposer = GestureUtils.MakeGestureTarget(ele, this.onGesture.bind(this));
             }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index c3e131184..8d376fb57 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -114,6 +114,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     }
     private addDocument = (newBox: Doc) => {
         const added = this.props.addDocument(newBox);
+        console.log("adding doc from freeform", added);
         added && this.bringToFront(newBox);
         added && this.updateCluster(newBox);
         return added;
@@ -356,12 +357,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
     @undoBatch
     onGesture = (e: Event, ge: GestureUtils.GestureEvent) => {
-        console.log("on gesture");
         switch (ge.gesture) {
             case GestureUtils.Gestures.Stroke:
                 const points = ge.points;
                 const B = this.getTransform().transformBounds(ge.bounds.left, ge.bounds.top, ge.bounds.width, ge.bounds.height);
                 const inkDoc = Docs.Create.InkDocument(InkingControl.Instance.selectedColor, InkingControl.Instance.selectedTool, parseInt(InkingControl.Instance.selectedWidth), points, { x: B.x, y: B.y, width: B.width, height: B.height });
+                console.log("make stroke", inkDoc);
                 this.addDocument(inkDoc);
                 e.stopPropagation();
                 break;
@@ -403,14 +404,14 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     @action
     pan = (e: PointerEvent | React.Touch | { clientX: number, clientY: number }): void => {
         // I think it makes sense for the marquee menu to go away when panned. -syip2
-        MarqueeOptionsMenu.Instance.fadeOut(true);
+        MarqueeOptionsMenu.Instance && MarqueeOptionsMenu.Instance.fadeOut(true);
 
         let x = this.Document.panX || 0;
         let y = this.Document.panY || 0;
         const docs = this.childLayoutPairs.map(pair => pair.layout);
         const [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
         if (!this.isAnnotationOverlay) {
-            PDFMenu.Instance.fadeOut(true);
+            PDFMenu.Instance && PDFMenu.Instance.fadeOut(true);
             const minx = docs.length ? NumCast(docs[0].x) : 0;
             const maxx = docs.length ? NumCast(docs[0].width) + minx : minx;
             const miny = docs.length ? NumCast(docs[0].y) : 0;
@@ -990,6 +991,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         // otherwise, they are stored in fieldKey.  All annotations to this document are stored in the extension document
         if (!this.extensionDoc) return (null);
         // let lodarea = this.Document[WidthSym]() * this.Document[HeightSym]() / this.props.ScreenToLocalTransform().Scale / this.props.ScreenToLocalTransform().Scale;
+        console.log("height freeform", this.isAnnotationOverlay, this.Document.scrollHeight, this.props.PanelHeight());
         return <div className={"collectionfreeformview-container"} ref={this.createDropAndGestureTarget} onWheel={this.onPointerWheel}//pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined,
             style={{ pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined, height: this.isAnnotationOverlay ? (this.props.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight() }}
             onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onContextMenu={this.onContextMenu} onTouchStart={this.onTouchStart}>
diff --git a/src/mobile/MobileInterface.scss b/src/mobile/MobileInterface.scss
new file mode 100644
index 000000000..e4cc919a5
--- /dev/null
+++ b/src/mobile/MobileInterface.scss
@@ -0,0 +1,7 @@
+.mobileInterface-topButtons {
+    position: absolute;
+    display: flex;
+    justify-content: space-between;
+    width: 100%;
+    z-index: 9999;
+}
\ No newline at end of file
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index 8d342d749..3cb08afa7 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -10,10 +10,16 @@ import { DocumentView } from '../client/views/nodes/DocumentView';
 import { emptyPath, emptyFunction, returnFalse, returnOne, returnEmptyString, returnTrue } from '../Utils';
 import { Transform } from '../client/util/Transform';
 import { library } from '@fortawesome/fontawesome-svg-core';
-import { faPenNib, faHighlighter, faEraser, faMousePointer, faBreadSlice } from '@fortawesome/free-solid-svg-icons';
+import { faPenNib, faHighlighter, faEraser, faMousePointer, faBreadSlice, faTrash, faCheck } from '@fortawesome/free-solid-svg-icons';
 import { Scripting } from '../client/util/Scripting';
 import { CollectionFreeFormView } from '../client/views/collections/collectionFreeForm/CollectionFreeFormView';
 import GestureOverlay from '../client/views/GestureOverlay';
+import { InkingControl } from '../client/views/InkingControl';
+import { InkTool } from '../new_fields/InkField';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import "./MobileInterface.scss";
+
+library.add(faTrash, faCheck);
 
 @observer
 export default class MobileInterface extends React.Component {
@@ -22,6 +28,9 @@ export default class MobileInterface extends React.Component {
     @computed private get mainContainer() { return this.userDoc ? FieldValue(Cast(this.userDoc.activeMobile, Doc)) : CurrentUserUtils.GuestMobile; }
     @observable private currentView: "main" | "ink" | "library" = "main";
 
+    private mainDoc = CurrentUserUtils.setupMobileDoc(this.userDoc);
+    private inkDoc?: Doc;
+
     constructor(props: Readonly<{}>) {
         super(props);
         MobileInterface.Instance = this;
@@ -32,8 +41,8 @@ export default class MobileInterface extends React.Component {
         library.add(...[faPenNib, faHighlighter, faEraser, faMousePointer]);
 
         if (this.userDoc && !this.mainContainer) {
-            const doc = CurrentUserUtils.setupMobileDoc(this.userDoc);
-            this.userDoc.activeMobile = doc;
+            // const doc = CurrentUserUtils.setupMobileDoc(this.userDoc);
+            this.userDoc.activeMobile = this.mainDoc;
         }
     }
 
@@ -44,13 +53,14 @@ export default class MobileInterface extends React.Component {
         if (this.userDoc) {
             switch (view) {
                 case "main": {
-                    const doc = CurrentUserUtils.setupMobileDoc(this.userDoc);
-                    this.userDoc.activeMobile = doc;
+                    // const doc = CurrentUserUtils.setupMobileDoc(this.userDoc);
+                    this.userDoc.activeMobile = this.mainDoc;
                     break;
                 }
                 case "ink": {
-                    const doc = CurrentUserUtils.setupMobileInkingDoc(this.userDoc);
-                    this.userDoc.activeMobile = doc;
+                    this.inkDoc = CurrentUserUtils.setupMobileInkingDoc(this.userDoc);
+                    this.userDoc.activeMobile = this.inkDoc;
+                    InkingControl.Instance.switchTool(InkTool.Pen);
                     break;
                 }
             }
@@ -64,7 +74,7 @@ export default class MobileInterface extends React.Component {
                 Document={this.mainContainer}
                 DataDoc={undefined}
                 LibraryPath={emptyPath}
-                addDocument={undefined}
+                addDocument={returnFalse}
                 addDocTab={returnFalse}
                 pinToPres={emptyFunction}
                 removeDocument={undefined}
@@ -89,40 +99,46 @@ export default class MobileInterface extends React.Component {
         return "hello";
     }
 
+    onClick = (e: React.MouseEvent) => {
+        // insert ink as collection into active displayed doc view
+        // reset ink collection
+        this.inkDoc = undefined;
+        console.log("clicked");
+        this.switchCurrentView("main");
+        InkingControl.Instance.switchTool(InkTool.None); // TODO: switch to previous tool
+    }
+
     @computed
     get inkContent() {
-        // return <div>INK</div>;
+        // TODO: get props from active collection and pass to the new freeform
         if (this.mainContainer) {
             return (
                 <GestureOverlay>
-                    <CollectionFreeFormView
+                    <div className="mobileInterface-topButtons">
+                        <button className="mobileInterface-button cancel" onClick={this.onClick} title="Cancel drawing"><FontAwesomeIcon icon="trash" /></button>
+                        <button className="mobileInterface-button cancel" onClick={this.onClick} title="Insert drawing"><FontAwesomeIcon icon="check" /></button>
+                    </div>
+                    <CollectionView
                         Document={this.mainContainer}
                         DataDoc={undefined}
                         LibraryPath={emptyPath}
                         fieldKey={""}
-                        addDocument={returnFalse}
-                        removeDocument={returnFalse}
-                        moveDocument={returnFalse}
                         addDocTab={returnFalse}
                         pinToPres={emptyFunction}
-                        PanelHeight={() => window.screen.height}
-                        PanelWidth={() => window.screen.width}
-                        annotationsKey={""}
-                        isAnnotationOverlay={false}
+                        PanelHeight={() => window.innerHeight}
+                        PanelWidth={() => window.innerWidth}
                         focus={emptyFunction}
-                        isSelected={returnTrue} //
+                        isSelected={returnTrue}
                         select={emptyFunction}
-                        active={returnTrue} //
+                        active={returnTrue}
                         ContentScaling={returnOne}
                         whenActiveChanged={returnFalse}
-                        CollectionView={undefined}
                         ScreenToLocalTransform={Transform.Identity}
                         ruleProvider={undefined}
                         renderDepth={0}
                         ContainingCollectionView={undefined}
-                        ContainingCollectionDoc={undefined}
-                        chromeCollapsed={true}>
-                    </CollectionFreeFormView>
+                        ContainingCollectionDoc={undefined}>
+                    </CollectionView>
                 </GestureOverlay>
             );
         }
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index b40179fbc..98092ddfe 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -141,7 +141,7 @@ export class CurrentUserUtils {
     }
 
     static setupMobileInkingDoc(userDoc: Doc) {
-        return Docs.Create.FreeformDocument([], { x: 0, y: 0, width: 10, height: 20, title: "Mobile Inking", backgroundColor: "red" });
+        return Docs.Create.FreeformDocument([], { title: "Mobile Inking", backgroundColor: "white" });
     }
 
     // setup the Creator button which will display the creator panel.  This panel will include the drag creators and the color picker.  when clicked, this panel will be displayed in the target container (ie, sidebarContainer)  
-- 
cgit v1.2.3-70-g09d2


From 65e164eaec42d1850de7f5e1eba1d4302c3e8230 Mon Sep 17 00:00:00 2001
From: Fawn <fangrui_tong@brown.edu>
Date: Wed, 15 Jan 2020 16:08:48 -0500
Subject: mobile interface emits events when switched to inking view and when
 strokes are drawn, currently with dummy callbacks

---
 src/client/DocServer.ts                            | 22 ++++++++++++++++++-
 src/client/views/GestureOverlay.tsx                | 21 ++++++++++++++++++
 src/client/views/MainView.tsx                      | 13 +++++++++--
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  3 ---
 src/mobile/MobileInterface.tsx                     | 25 +++++++++++++++-------
 src/server/Message.ts                              | 13 +++++++++++
 src/server/Websocket/Websocket.ts                  | 12 ++++++++++-
 7 files changed, 94 insertions(+), 15 deletions(-)

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

diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index ed7fbd7ba..cfd9612ed 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -1,10 +1,11 @@
 import * as OpenSocket from 'socket.io-client';
-import { MessageStore, YoutubeQueryTypes } from "./../server/Message";
+import { MessageStore, YoutubeQueryTypes, GestureContent } from "./../server/Message";
 import { Opt, Doc } from '../new_fields/Doc';
 import { Utils, emptyFunction } from '../Utils';
 import { SerializationHelper } from './util/SerializationHelper';
 import { RefField } from '../new_fields/RefField';
 import { Id, HandleUpdate } from '../new_fields/FieldSymbols';
+import GestureOverlay from './views/GestureOverlay';
 
 /**
  * This class encapsulates the transfer and cross-client synchronization of
@@ -64,6 +65,19 @@ export namespace DocServer {
         }
     }
 
+    export namespace Mobile {
+
+        export function dispatchGesturePoints(content: GestureContent) {
+            Utils.Emit(_socket, MessageStore.GesturePoints, content);
+        }
+
+        export function dispatchBoxTrigger(enableBox: boolean) {
+            // _socket.emit("dispatchBoxTrigger");
+            Utils.Emit(_socket, MessageStore.MobileInkBoxTrigger, enableBox);
+        }
+
+    }
+
     export function init(protocol: string, hostname: string, port: number, identifier: string) {
         _cache = {};
         GUID = identifier;
@@ -85,6 +99,12 @@ export namespace DocServer {
         Utils.AddServerHandler(_socket, MessageStore.ConnectionTerminated, () => {
             alert("Your connection to the server has been terminated.");
         });
+        _socket.addEventListener("receiveGesturePoints", (content: GestureContent) => {
+            GestureOverlay.Instance.manualDispatch(content);
+        });
+        _socket.addEventListener("receiveBoxTrigger", (enableBox: boolean) => {
+            GestureOverlay.Instance.showBox(enableBox);
+        });
     }
 
     function errorFunc(): never {
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 4d487c032..4b8dfa119 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -16,6 +16,10 @@ import { Scripting } from "../util/Scripting";
 import { FieldValue, Cast } from "../../new_fields/Types";
 import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils";
 import Palette from "./Palette";
+import MobileInterface from "../../mobile/MobileInterface";
+import { MainView } from "./MainView";
+import { DocServer } from "../DocServer";
+import { GestureContent } from "../../server/Message";
 
 @observer
 export default class GestureOverlay extends Touchable {
@@ -35,6 +39,14 @@ export default class GestureOverlay extends Touchable {
         GestureOverlay.Instance = this;
     }
 
+    manualDispatch = (content: GestureContent) => {
+        console.log(content);
+    }
+
+    showBox = (enableBox: boolean) => {
+        console.log("enable box?", enableBox);
+    }
+
     @action
     handleHandDown = (e: React.TouchEvent) => {
         const fingers = InteractionUtils.GetMyTargetTouches(e, this.prevPoints, true);
@@ -139,6 +151,15 @@ export default class GestureOverlay extends Touchable {
             const B = this.svgBounds;
             const points = this._points.map(p => ({ X: p.X - B.left, Y: p.Y - B.top }));
 
+            if (MobileInterface.Instance.drawingInk) {
+                const { selectedColor, selectedWidth } = InkingControl.Instance;
+                DocServer.Mobile.dispatchGesturePoints({
+                    points: this._points,
+                    color: selectedColor,
+                    width: selectedWidth
+                });
+            }
+
             const result = GestureUtils.GestureRecognizer.Recognize(new Array(points));
             let actionPerformed = false;
             if (result && result.Score > 0.7) {
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 93fb0a07c..f1f7c37a3 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -49,6 +49,7 @@ export class MainView extends React.Component {
     private _flyoutSizeOnDown = 0;
     private _urlState: HistoryUtil.DocUrl;
     private _docBtnRef = React.createRef<HTMLDivElement>();
+    private _mainViewRef = React.createRef<HTMLDivElement>();
 
     @observable private _panelWidth: number = 0;
     @observable private _panelHeight: number = 0;
@@ -507,8 +508,16 @@ export class MainView extends React.Component {
         return (null);
     }
 
+    get mainViewElement() {
+        return document.getElementById("mainView-container");
+    }
+
+    get mainViewRef() {
+        return this._mainViewRef;
+    }
+
     render() {
-        return (<div id="mainView-container">
+        return (<div id="mainView-container" ref={this._mainViewRef}>
             <DictationOverlay />
             <SharingManager />
             <GoogleAuthenticationManager />
@@ -518,7 +527,7 @@ export class MainView extends React.Component {
             </GestureOverlay>
             <PreviewCursor />
             <ContextMenu />
-            <RadialMenu/>
+            <RadialMenu />
             <PDFMenu />
             <MarqueeOptionsMenu />
             <OverlayView />
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 8d376fb57..bbfd22f50 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -114,7 +114,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     }
     private addDocument = (newBox: Doc) => {
         const added = this.props.addDocument(newBox);
-        console.log("adding doc from freeform", added);
         added && this.bringToFront(newBox);
         added && this.updateCluster(newBox);
         return added;
@@ -362,7 +361,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 const points = ge.points;
                 const B = this.getTransform().transformBounds(ge.bounds.left, ge.bounds.top, ge.bounds.width, ge.bounds.height);
                 const inkDoc = Docs.Create.InkDocument(InkingControl.Instance.selectedColor, InkingControl.Instance.selectedTool, parseInt(InkingControl.Instance.selectedWidth), points, { x: B.x, y: B.y, width: B.width, height: B.height });
-                console.log("make stroke", inkDoc);
                 this.addDocument(inkDoc);
                 e.stopPropagation();
                 break;
@@ -991,7 +989,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         // otherwise, they are stored in fieldKey.  All annotations to this document are stored in the extension document
         if (!this.extensionDoc) return (null);
         // let lodarea = this.Document[WidthSym]() * this.Document[HeightSym]() / this.props.ScreenToLocalTransform().Scale / this.props.ScreenToLocalTransform().Scale;
-        console.log("height freeform", this.isAnnotationOverlay, this.Document.scrollHeight, this.props.PanelHeight());
         return <div className={"collectionfreeformview-container"} ref={this.createDropAndGestureTarget} onWheel={this.onPointerWheel}//pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined,
             style={{ pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined, height: this.isAnnotationOverlay ? (this.props.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight() }}
             onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onContextMenu={this.onContextMenu} onTouchStart={this.onTouchStart}>
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index 3cb08afa7..1429226b4 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -2,7 +2,7 @@ import React = require('react');
 import { observer } from 'mobx-react';
 import { computed, action, observable } from 'mobx';
 import { CurrentUserUtils } from '../server/authentication/models/current_user_utils';
-import { FieldValue, Cast } from '../new_fields/Types';
+import { FieldValue, Cast, StrCast } from '../new_fields/Types';
 import { Doc } from '../new_fields/Doc';
 import { Docs } from '../client/documents/Documents';
 import { CollectionView } from '../client/views/collections/CollectionView';
@@ -18,6 +18,10 @@ import { InkingControl } from '../client/views/InkingControl';
 import { InkTool } from '../new_fields/InkField';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import "./MobileInterface.scss";
+import { SelectionManager } from '../client/util/SelectionManager';
+import { DateField } from '../new_fields/DateField';
+import { GestureUtils } from '../pen-gestures/GestureUtils';
+import { DocServer } from '../client/DocServer';
 
 library.add(faTrash, faCheck);
 
@@ -30,6 +34,7 @@ export default class MobileInterface extends React.Component {
 
     private mainDoc = CurrentUserUtils.setupMobileDoc(this.userDoc);
     private inkDoc?: Doc;
+    public drawingInk: boolean = false;
 
     constructor(props: Readonly<{}>) {
         super(props);
@@ -61,6 +66,10 @@ export default class MobileInterface extends React.Component {
                     this.inkDoc = CurrentUserUtils.setupMobileInkingDoc(this.userDoc);
                     this.userDoc.activeMobile = this.inkDoc;
                     InkingControl.Instance.switchTool(InkTool.Pen);
+                    this.drawingInk = true;
+
+                    DocServer.Mobile.dispatchBoxTrigger(true);
+
                     break;
                 }
             }
@@ -100,17 +109,17 @@ export default class MobileInterface extends React.Component {
     }
 
     onClick = (e: React.MouseEvent) => {
-        // insert ink as collection into active displayed doc view
-        // reset ink collection
-        this.inkDoc = undefined;
-        console.log("clicked");
         this.switchCurrentView("main");
         InkingControl.Instance.switchTool(InkTool.None); // TODO: switch to previous tool
+
+        DocServer.Mobile.dispatchBoxTrigger(false);
+
+        this.inkDoc = undefined;
+        this.drawingInk = false;
     }
 
     @computed
     get inkContent() {
-        // TODO: get props from active collection and pass to the new freeform
         if (this.mainContainer) {
             return (
                 <GestureOverlay>
@@ -128,9 +137,9 @@ export default class MobileInterface extends React.Component {
                         PanelHeight={() => window.innerHeight}
                         PanelWidth={() => window.innerWidth}
                         focus={emptyFunction}
-                        isSelected={returnTrue}
+                        isSelected={returnFalse}
                         select={emptyFunction}
-                        active={returnTrue}
+                        active={returnFalse}
                         ContentScaling={returnOne}
                         whenActiveChanged={returnFalse}
                         ScreenToLocalTransform={Transform.Identity}
diff --git a/src/server/Message.ts b/src/server/Message.ts
index 621abfd1e..fe74dafa5 100644
--- a/src/server/Message.ts
+++ b/src/server/Message.ts
@@ -1,4 +1,5 @@
 import { Utils } from "../Utils";
+import { Point } from "../pen-gestures/ndollar";
 
 export class Message<T> {
     private _name: string;
@@ -42,6 +43,16 @@ export interface Diff extends Reference {
     readonly diff: any;
 }
 
+export interface GestureContent {
+    readonly points: Array<Point>;
+    readonly width?: string;
+    readonly color?: string;
+}
+
+export interface MobileInkBoxTriggerContent {
+    readonly enableBox: boolean;
+}
+
 export namespace MessageStore {
     export const Foo = new Message<string>("Foo");
     export const Bar = new Message<string>("Bar");
@@ -51,6 +62,8 @@ export namespace MessageStore {
     export const GetDocument = new Message<string>("Get Document");
     export const DeleteAll = new Message<any>("Delete All");
     export const ConnectionTerminated = new Message<string>("Connection Terminated");
+    export const GesturePoints = new Message<GestureContent>("Gesture Points");
+    export const MobileInkBoxTrigger = new Message<boolean>("Trigger Mobile Ink Box");
 
     export const GetRefField = new Message<string>("Get Ref Field");
     export const GetRefFields = new Message<string[]>("Get Ref Fields");
diff --git a/src/server/Websocket/Websocket.ts b/src/server/Websocket/Websocket.ts
index 578147d60..67777f023 100644
--- a/src/server/Websocket/Websocket.ts
+++ b/src/server/Websocket/Websocket.ts
@@ -1,5 +1,5 @@
 import { Utils } from "../../Utils";
-import { MessageStore, Transferable, Types, Diff, YoutubeQueryInput, YoutubeQueryTypes } from "../Message";
+import { MessageStore, Transferable, Types, Diff, YoutubeQueryInput, YoutubeQueryTypes, GestureContent } from "../Message";
 import { Client } from "../Client";
 import { Socket } from "socket.io";
 import { Database } from "../database";
@@ -54,6 +54,8 @@ export namespace WebSocket {
             Utils.AddServerHandler(socket, MessageStore.UpdateField, diff => UpdateField(socket, diff));
             Utils.AddServerHandler(socket, MessageStore.DeleteField, id => DeleteField(socket, id));
             Utils.AddServerHandler(socket, MessageStore.DeleteFields, ids => DeleteFields(socket, ids));
+            Utils.AddServerHandler(socket, MessageStore.GesturePoints, content => processGesturePoints(socket, content));
+            Utils.AddServerHandler(socket, MessageStore.MobileInkBoxTrigger, enableBox => processBoxTrigger(socket, enableBox));
             Utils.AddServerHandlerCallback(socket, MessageStore.GetRefField, GetRefField);
             Utils.AddServerHandlerCallback(socket, MessageStore.GetRefFields, GetRefFields);
 
@@ -68,6 +70,14 @@ export namespace WebSocket {
         logPort("websocket", socketPort);
     }
 
+    function processGesturePoints(socket: Socket, content: GestureContent) {
+        socket.broadcast.emit("receiveGesturePoints", content);
+    }
+
+    function processBoxTrigger(socket: Socket, enableBox: boolean) {
+        socket.broadcast.emit("receiveBoxTrigger", enableBox);
+    }
+
     function HandleYoutubeQuery([query, callback]: [YoutubeQueryInput, (result?: any[]) => void]) {
         const { ProjectCredentials } = GoogleCredentialsLoader;
         switch (query.type) {
-- 
cgit v1.2.3-70-g09d2


From d1c1e9f10b8523270a872c6f67df25ebb1d9aa25 Mon Sep 17 00:00:00 2001
From: kimdahey <claire_kim1@brown.edu>
Date: Mon, 27 Jan 2020 20:34:27 -0500
Subject: switching event type, some bugs :(

---
 src/client/views/Touchable.tsx                     | 65 +++++++++++++--
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  1 +
 src/client/views/nodes/DocumentView.tsx            | 94 +++++++++-------------
 3 files changed, 100 insertions(+), 60 deletions(-)

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

diff --git a/src/client/views/Touchable.tsx b/src/client/views/Touchable.tsx
index 7800c4019..eed72e3c5 100644
--- a/src/client/views/Touchable.tsx
+++ b/src/client/views/Touchable.tsx
@@ -8,9 +8,12 @@ const HOLD_DURATION = 1000;
 
 export abstract class Touchable<T = {}> extends React.Component<T> {
     //private holdTimer: NodeJS.Timeout | undefined;
-    private holdTimer: NodeJS.Timeout | undefined;
+    holdTimer: NodeJS.Timeout | undefined;
     private moveDisposer?: InteractionUtils.MultiTouchEventDisposer;
     private endDisposer?: InteractionUtils.MultiTouchEventDisposer;
+    private holdMoveDisposer?: InteractionUtils.MultiTouchEventDisposer;
+    private holdEndDisposer?: InteractionUtils.MultiTouchEventDisposer;
+
 
     protected abstract multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
     protected _touchDrag: boolean = false;
@@ -65,8 +68,10 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
                     //     clearTimeout(this.holdTimer)
                     //     this.holdTimer = undefined;
                     // }
-                    this.holdTimer = setTimeout(() => this.handle1PointerHoldStart(te, me), HOLD_DURATION);
-                    // e.stopPropagation();
+                    console.log(this.holdTimer);
+                    if (!this.holdTimer) {
+                        this.holdTimer = setTimeout(() => this.handle1PointerHoldStart(e, me), HOLD_DURATION);
+                    }
                     // console.log(this.holdTimer);
                     break;
                 case 2:
@@ -128,7 +133,12 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
             }
         }
         if (this.holdTimer) {
+            console.log(this.holdTimer);
             clearTimeout(this.holdTimer);
+            console.log(this.holdTimer);
+
+            this.holdTimer = undefined;
+            console.log(this.holdTimer);
             console.log("clear");
         }
         this._touchDrag = false;
@@ -174,10 +184,17 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
         this.addEndListeners();
     }
 
-    handle1PointerHoldStart = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): any => {
+    handle1PointerHoldStart = (e: Event, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): any => {
         e.stopPropagation();
-        e.preventDefault();
+        me.touchEvent.stopPropagation();
+        this.holdTimer = undefined;
         this.removeMoveListeners();
+        this.removeEndListeners();
+        this.removeHoldMoveListeners();
+        this.removeHoldEndListeners();
+        this.addHoldMoveListeners();
+        this.addHoldEndListeners();
+
     }
 
     addMoveListeners = () => {
@@ -200,6 +217,44 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
         this.endDisposer && this.endDisposer();
     }
 
+    addHoldMoveListeners = () => {
+        const handler = (e: Event) => this.handle1PointerHoldMove(e, (e as CustomEvent<InteractionUtils.MultiTouchEvent<TouchEvent>>).detail);
+        document.addEventListener("dashOnTouchHoldMove", handler);
+        this.holdMoveDisposer = () => document.removeEventListener("dashOnTouchHoldMove", handler);
+    }
+
+    addHoldEndListeners = () => {
+        const handler = (e: Event) => this.handle1PointerHoldEnd(e, (e as CustomEvent<InteractionUtils.MultiTouchEvent<TouchEvent>>).detail);
+        document.addEventListener("dashOnTouchHoldEnd", handler);
+        this.holdEndDisposer = () => document.removeEventListener("dashOnTouchHoldEnd", handler);
+    }
+
+    removeHoldMoveListeners = () => {
+        this.holdMoveDisposer && this.holdMoveDisposer();
+    }
+
+    removeHoldEndListeners = () => {
+        this.holdEndDisposer && this.holdEndDisposer();
+    }
+
+
+    handle1PointerHoldMove = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => {
+        e.stopPropagation();
+        me.touchEvent.stopPropagation();
+    }
+
+
+    handle1PointerHoldEnd = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => {
+        e.stopPropagation();
+        me.touchEvent.stopPropagation();
+        this.removeHoldMoveListeners();
+        this.removeHoldEndListeners();
+
+        me.touchEvent.stopPropagation();
+        me.touchEvent.preventDefault();
+    }
+
+
     handleHandDown = (e: React.TouchEvent) => {
         // e.stopPropagation();
         // e.preventDefault();
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index aaa585b55..2622d9b28 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -494,6 +494,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true);
             const pt1 = myTouches[0];
             const pt2 = myTouches[1];
+            console.log(myTouches);
 
             if (this.prevPoints.size === 2) {
                 const oldPoint1 = this.prevPoints.get(pt1.identifier);
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 0b6a284d6..ae2ee9e41 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -119,61 +119,45 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     private _firstY: number = 0;
 
 
-    // handle1PointerHoldStart = (e: React.TouchEvent): any => {
-    //     this.onRadialMenu(e);
-    //     const pt = InteractionUtils.GetMyTargetTouches(e, this.prevPoints, true)[0];
-    //     this._firstX = pt.pageX;
-    //     this._firstY = pt.pageY;
-    //     e.stopPropagation();
-    //     e.preventDefault();
-
-    //     document.removeEventListener("touchmove", this.onTouch);
-    //     document.removeEventListener("touchmove", this.handle1PointerHoldMove);
-    //     document.addEventListener("touchmove", this.handle1PointerHoldMove);
-    //     document.removeEventListener("touchend", this.handle1PointerHoldEnd);
-    //     document.addEventListener("touchend", this.handle1PointerHoldEnd);
-    // }
-
-    // handle1PointerHoldMove = (e: TouchEvent): void => {
-    //     const pt = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true)[0];
-    //     if (Math.abs(pt.pageX - this._firstX) > 150 || Math.abs(pt.pageY - this._firstY) > 150) {
-    //         this.handleRelease();
-    //     }
-    //     document.removeEventListener("touchmove", this.handle1PointerHoldMove);
-    //     document.addEventListener("touchmove", this.handle1PointerHoldMove);
-    //     document.removeEventListener("touchend", this.handle1PointerHoldEnd);
-    //     document.addEventListener("touchend", this.handle1PointerHoldEnd);
-    // }
-
-    // handleRelease() {
-    //     RadialMenu.Instance.closeMenu();
-    //     document.removeEventListener("touchmove", this.handle1PointerHoldMove);
-    //     document.removeEventListener("touchend", this.handle1PointerHoldEnd);
-    // }
-
-    // handle1PointerHoldEnd = (e: TouchEvent): void => {
-    //     RadialMenu.Instance.closeMenu();
-    //     document.removeEventListener("touchmove", this.handle1PointerHoldMove);
-    //     document.removeEventListener("touchend", this.handle1PointerHoldEnd);
-    // }
-
-    // @action
-    // onRadialMenu = (e: React.TouchEvent): void => {
-    //     const pt = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true)[0];
-
-    //     RadialMenu.Instance.openMenu();
-
-    //     RadialMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { width: 300, height: 300 }), undefined, "onRight"), icon: "layer-group", selected: -1 });
-    //     RadialMenu.Instance.addItem({ description: "Delete this document", event: () => this.props.ContainingCollectionView?.removeDocument(this.props.Document), icon: "trash", selected: -1 });
-    //     RadialMenu.Instance.addItem({ description: "Open in a new tab", event: () => this.props.addDocTab(this.props.Document, undefined, "onRight"), icon: "folder", selected: -1 });
-    //     RadialMenu.Instance.addItem({ description: "Pin to Presentation", event: () => this.props.pinToPres(this.props.Document), icon: "map-pin", selected: -1 });
-
-    //     RadialMenu.Instance.displayMenu(pt.pageX - 15, pt.pageY - 15);
-    //     if (!SelectionManager.IsSelected(this, true)) {
-    //         SelectionManager.SelectDoc(this, false);
-    //     }
-    //     e.stopPropagation();
-    // }
+    handle1PointerHoldStart = (e: Event, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): any => {
+        console.log("S");
+        this.onRadialMenu(e, me);
+        const pt = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true)[0];
+        this._firstX = pt.pageX;
+        this._firstY = pt.pageY;
+
+    }
+
+    handle1PointerHoldMove = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => {
+        console.log("K");
+        const pt = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true)[0];
+        if (Math.abs(pt.pageX - this._firstX) > 150 || Math.abs(pt.pageY - this._firstY) > 150) {
+            this.handle1PointerHoldEnd(e, me);
+        }
+    }
+
+    handle1PointerHoldEnd = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => {
+        console.log("E");
+        RadialMenu.Instance.closeMenu();
+    }
+
+    @action
+    onRadialMenu = (e: Event, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): void => {
+        const pt = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true)[0];
+
+        RadialMenu.Instance.openMenu();
+
+        RadialMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { width: 300, height: 300 }), undefined, "onRight"), icon: "layer-group", selected: -1 });
+        RadialMenu.Instance.addItem({ description: "Delete this document", event: () => this.props.ContainingCollectionView?.removeDocument(this.props.Document), icon: "trash", selected: -1 });
+        RadialMenu.Instance.addItem({ description: "Open in a new tab", event: () => this.props.addDocTab(this.props.Document, undefined, "onRight"), icon: "folder", selected: -1 });
+        RadialMenu.Instance.addItem({ description: "Pin to Presentation", event: () => this.props.pinToPres(this.props.Document), icon: "map-pin", selected: -1 });
+
+        RadialMenu.Instance.displayMenu(pt.pageX - 15, pt.pageY - 15);
+        if (!SelectionManager.IsSelected(this, true)) {
+            SelectionManager.SelectDoc(this, false);
+        }
+        e.stopPropagation();
+    }
 
     @action
     componentDidMount() {
-- 
cgit v1.2.3-70-g09d2


From ecf0f5b8f426db9e66c05e759f61294811b15fca Mon Sep 17 00:00:00 2001
From: vellichora <fangrui_tong@brown.edu>
Date: Sat, 1 Feb 2020 14:56:19 -0500
Subject: mobile ink overlay is draggable from desktop

---
 src/client/DocServer.ts                            |  19 ++-
 src/client/util/InteractionUtils.ts                | 165 -------------------
 src/client/util/InteractionUtils.tsx               | 182 +++++++++++++++++++++
 src/client/views/GestureOverlay.scss               |   4 -
 src/client/views/GestureOverlay.tsx                |  25 +--
 src/client/views/InkingStroke.tsx                  |  17 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   1 -
 src/client/views/nodes/DocumentView.tsx            |   1 +
 src/mobile/MobileInkOverlay.scss                   |  38 +++++
 src/mobile/MobileInkOverlay.tsx                    |  91 +++++++++--
 src/mobile/MobileInterface.scss                    |   2 +-
 src/mobile/MobileInterface.tsx                     |  90 ++++++----
 src/server/Message.ts                              |  14 +-
 src/server/Websocket/Websocket.ts                  |   8 +-
 14 files changed, 393 insertions(+), 264 deletions(-)
 delete mode 100644 src/client/util/InteractionUtils.ts
 create mode 100644 src/client/util/InteractionUtils.tsx
 create mode 100644 src/mobile/MobileInkOverlay.scss

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

diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index a1cb42df2..b20cd3521 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -1,5 +1,5 @@
 import * as OpenSocket from 'socket.io-client';
-import { MessageStore, YoutubeQueryTypes, GestureContent, MobileInkBoxContent } from "./../server/Message";
+import { MessageStore, YoutubeQueryTypes, GestureContent, MobileInkOverlayContent, UpdateMobileInkOverlayPosition } from "./../server/Message";
 import { Opt, Doc } from '../new_fields/Doc';
 import { Utils, emptyFunction } from '../Utils';
 import { SerializationHelper } from './util/SerializationHelper';
@@ -72,9 +72,13 @@ export namespace DocServer {
             Utils.Emit(_socket, MessageStore.GesturePoints, content);
         }
 
-        export function dispatchBoxTrigger(content: MobileInkBoxContent) {
+        export function dispatchOverlayTrigger(content: MobileInkOverlayContent) {
             // _socket.emit("dispatchBoxTrigger");
-            Utils.Emit(_socket, MessageStore.MobileInkBoxTrigger, content);
+            Utils.Emit(_socket, MessageStore.MobileInkOverlayTrigger, content);
+        }
+
+        export function dispatchOverlayPositionUpdate(content: UpdateMobileInkOverlayPosition) {
+            Utils.Emit(_socket, MessageStore.UpdateMobileInkOverlayPosition, content);
         }
 
     }
@@ -100,13 +104,18 @@ export namespace DocServer {
         Utils.AddServerHandler(_socket, MessageStore.ConnectionTerminated, () => {
             alert("Your connection to the server has been terminated.");
         });
+
+        // mobile ink overlay socket events to communicate between mobile view and desktop view
         _socket.addEventListener("receiveGesturePoints", (content: GestureContent) => {
             MobileInkOverlay.Instance.drawStroke(content);
         });
-        _socket.addEventListener("receiveBoxTrigger", (content: MobileInkBoxContent) => {
-            GestureOverlay.Instance.enableMobileInkBox(content);
+        _socket.addEventListener("receiveOverlayTrigger", (content: MobileInkOverlayContent) => {
+            GestureOverlay.Instance.enableMobileInkOverlay(content);
             MobileInkOverlay.Instance.initMobileInkOverlay(content);
         });
+        _socket.addEventListener("updateMobileOverlayPosition", (content: UpdateMobileInkOverlayPosition) => {
+            MobileInkOverlay.Instance.updatePosition(content);
+        });
     }
 
     function errorFunc(): never {
diff --git a/src/client/util/InteractionUtils.ts b/src/client/util/InteractionUtils.ts
deleted file mode 100644
index 76b43da3c..000000000
--- a/src/client/util/InteractionUtils.ts
+++ /dev/null
@@ -1,165 +0,0 @@
-export namespace InteractionUtils {
-    export const MOUSETYPE = "mouse";
-    export const TOUCHTYPE = "touch";
-    export const PENTYPE = "pen";
-    export const ERASERTYPE = "eraser";
-
-    const POINTER_PEN_BUTTON = -1;
-    const REACT_POINTER_PEN_BUTTON = 0;
-    const ERASER_BUTTON = 5;
-
-    export function GetMyTargetTouches(e: TouchEvent | React.TouchEvent, prevPoints: Map<number, React.Touch>, ignorePen: boolean): React.Touch[] {
-        const myTouches = new Array<React.Touch>();
-        for (let i = 0; i < e.targetTouches.length; i++) {
-            const pt: any = e.targetTouches.item(i);
-            if (pt && prevPoints.has(pt.identifier)) {
-                if (ignorePen || (pt.radiusX > 1 && pt.radiusY > 1)) {
-                    myTouches.push(pt);
-                }
-            }
-        }
-        return myTouches;
-    }
-
-    export function IsType(e: PointerEvent | React.PointerEvent, type: string): boolean {
-        switch (type) {
-            // pen and eraser are both pointer type 'pen', but pen is button 0 and eraser is button 5. -syip2
-            case PENTYPE:
-                return e.pointerType === PENTYPE && (e.button === -1 || e.button === 0);
-            case ERASERTYPE:
-                return e.pointerType === PENTYPE && e.button === (e instanceof PointerEvent ? ERASER_BUTTON : ERASER_BUTTON);
-            default:
-                return e.pointerType === type;
-        }
-    }
-
-    export function TwoPointEuclidist(pt1: React.Touch, pt2: React.Touch): number {
-        return Math.sqrt(Math.pow(pt1.clientX - pt2.clientX, 2) + Math.pow(pt1.clientY - pt2.clientY, 2));
-    }
-
-    /**
-     * Returns the centroid of an n-arbitrary long list of points (takes the average the x and y components of each point)
-     * @param pts - n-arbitrary long list of points
-     */
-    export function CenterPoint(pts: React.Touch[]): { X: number, Y: number } {
-        const centerX = pts.map(pt => pt.clientX).reduce((a, b) => a + b, 0) / pts.length;
-        const centerY = pts.map(pt => pt.clientY).reduce((a, b) => a + b, 0) / pts.length;
-        return { X: centerX, Y: centerY };
-    }
-
-    /**
-     * Returns -1 if pinching out, 0 if not pinching, and 1 if pinching in
-     * @param pt1 - new point that corresponds to oldPoint1
-     * @param pt2 - new point that corresponds to oldPoint2
-     * @param oldPoint1 - previous point 1
-     * @param oldPoint2 - previous point 2
-     */
-    export function Pinching(pt1: React.Touch, pt2: React.Touch, oldPoint1: React.Touch, oldPoint2: React.Touch): number {
-        const threshold = 4;
-        const oldDist = TwoPointEuclidist(oldPoint1, oldPoint2);
-        const newDist = TwoPointEuclidist(pt1, pt2);
-
-        /** if they have the same sign, then we are either pinching in or out.
-          * threshold it by 10 (it has to be pinching by at least threshold to be a valid pinch)
-          * so that it can still pan without freaking out
-          */
-        if (Math.sign(oldDist) === Math.sign(newDist) && Math.abs(oldDist - newDist) > threshold) {
-            return Math.sign(oldDist - newDist);
-        }
-        return 0;
-    }
-
-    /**
-     * Returns -1 if pinning and pinching out, 0 if not pinning, and 1 if pinching in
-     * @param pt1 - new point that corresponds to oldPoint1
-     * @param pt2 - new point that corresponds to oldPoint2
-     * @param oldPoint1 - previous point 1
-     * @param oldPoint2 - previous point 2
-     */
-    export function Pinning(pt1: React.Touch, pt2: React.Touch, oldPoint1: React.Touch, oldPoint2: React.Touch): number {
-        const threshold = 4;
-
-        const pt1Dist = TwoPointEuclidist(oldPoint1, pt1);
-        const pt2Dist = TwoPointEuclidist(oldPoint2, pt2);
-
-        const pinching = Pinching(pt1, pt2, oldPoint1, oldPoint2);
-
-        if (pinching !== 0) {
-            if ((pt1Dist < threshold && pt2Dist > threshold) || (pt1Dist > threshold && pt2Dist < threshold)) {
-                return pinching;
-            }
-        }
-        return 0;
-    }
-
-    export function IsDragging(oldTouches: Map<number, React.Touch>, newTouches: React.Touch[], leniency: number): boolean {
-        for (const touch of newTouches) {
-            if (touch) {
-                const oldTouch = oldTouches.get(touch.identifier);
-                if (oldTouch) {
-                    if (TwoPointEuclidist(touch, oldTouch) >= leniency) {
-                        return true;
-                    }
-                }
-            }
-        }
-        return false;
-    }
-
-    // These might not be very useful anymore, but I'll leave them here for now -syip2
-    {
-
-
-        /**
-         * Returns the type of Touch Interaction from a list of points.
-         * Also returns any data that is associated with a Touch Interaction
-         * @param pts - List of points
-         */
-        // export function InterpretPointers(pts: React.Touch[]): { type: Opt<TouchInteraction>, data?: any } {
-        //     const leniency = 200;
-        //     switch (pts.length) {
-        //         case 1:
-        //             return { type: OneFinger };
-        //         case 2:
-        //             return { type: TwoSeperateFingers };
-        //         case 3:
-        //             let pt1 = pts[0];
-        //             let pt2 = pts[1];
-        //             let pt3 = pts[2];
-        //             if (pt1 && pt2 && pt3) {
-        //                 let dist12 = TwoPointEuclidist(pt1, pt2);
-        //                 let dist23 = TwoPointEuclidist(pt2, pt3);
-        //                 let dist13 = TwoPointEuclidist(pt1, pt3);
-        //                 console.log(`distances: ${dist12}, ${dist23}, ${dist13}`);
-        //                 let dist12close = dist12 < leniency;
-        //                 let dist23close = dist23 < leniency;
-        //                 let dist13close = dist13 < leniency;
-        //                 let xor2313 = dist23close ? !dist13close : dist13close;
-        //                 let xor = dist12close ? !xor2313 : xor2313;
-        //                 // three input xor because javascript doesn't have logical xor's
-        //                 if (xor) {
-        //                     let points: number[] = [];
-        //                     let min = Math.min(dist12, dist23, dist13);
-        //                     switch (min) {
-        //                         case dist12:
-        //                             points = [0, 1, 2];
-        //                             break;
-        //                         case dist23:
-        //                             points = [1, 2, 0];
-        //                             break;
-        //                         case dist13:
-        //                             points = [0, 2, 1];
-        //                             break;
-        //                     }
-        //                     return { type: TwoToOneFingers, data: points };
-        //                 }
-        //                 else {
-        //                     return { type: ThreeSeperateFingers, data: null };
-        //                 }
-        //             }
-        //         default:
-        //             return { type: undefined };
-        //     }
-        // }
-    }
-}
\ No newline at end of file
diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx
new file mode 100644
index 000000000..34c7cfa74
--- /dev/null
+++ b/src/client/util/InteractionUtils.tsx
@@ -0,0 +1,182 @@
+import React = require("react");
+
+export namespace InteractionUtils {
+    export const MOUSETYPE = "mouse";
+    export const TOUCHTYPE = "touch";
+    export const PENTYPE = "pen";
+    export const ERASERTYPE = "eraser";
+
+    const POINTER_PEN_BUTTON = -1;
+    const REACT_POINTER_PEN_BUTTON = 0;
+    const ERASER_BUTTON = 5;
+
+    export function GetMyTargetTouches(e: TouchEvent | React.TouchEvent, prevPoints: Map<number, React.Touch>, ignorePen: boolean): React.Touch[] {
+        const myTouches = new Array<React.Touch>();
+        for (let i = 0; i < e.targetTouches.length; i++) {
+            const pt: any = e.targetTouches.item(i);
+            if (pt && prevPoints.has(pt.identifier)) {
+                if (ignorePen || (pt.radiusX > 1 && pt.radiusY > 1)) {
+                    myTouches.push(pt);
+                }
+            }
+        }
+        return myTouches;
+    }
+
+    // TODO: find a way to reference this function from InkingStroke instead of copy pastign here. copied bc of weird error when on mobile view
+    export function CreatePolyline(points: { X: number, Y: number }[], left: number, top: number, color: string, width: number) {
+        const pts = points.reduce((acc: string, pt: { X: number, Y: number }) => acc + `${pt.X - left},${pt.Y - top} `, "");
+        return (
+            <polyline
+                points={pts}
+                style={{
+                    fill: "none",
+                    stroke: color,
+                    strokeWidth: width
+                }}
+            />
+        );
+    }
+
+    export function IsType(e: PointerEvent | React.PointerEvent, type: string): boolean {
+        switch (type) {
+            // pen and eraser are both pointer type 'pen', but pen is button 0 and eraser is button 5. -syip2
+            case PENTYPE:
+                return e.pointerType === PENTYPE && (e.button === -1 || e.button === 0);
+            case ERASERTYPE:
+                return e.pointerType === PENTYPE && e.button === (e instanceof PointerEvent ? ERASER_BUTTON : ERASER_BUTTON);
+            default:
+                return e.pointerType === type;
+        }
+    }
+
+    export function TwoPointEuclidist(pt1: React.Touch, pt2: React.Touch): number {
+        return Math.sqrt(Math.pow(pt1.clientX - pt2.clientX, 2) + Math.pow(pt1.clientY - pt2.clientY, 2));
+    }
+
+    /**
+     * Returns the centroid of an n-arbitrary long list of points (takes the average the x and y components of each point)
+     * @param pts - n-arbitrary long list of points
+     */
+    export function CenterPoint(pts: React.Touch[]): { X: number, Y: number } {
+        const centerX = pts.map(pt => pt.clientX).reduce((a, b) => a + b, 0) / pts.length;
+        const centerY = pts.map(pt => pt.clientY).reduce((a, b) => a + b, 0) / pts.length;
+        return { X: centerX, Y: centerY };
+    }
+
+    /**
+     * Returns -1 if pinching out, 0 if not pinching, and 1 if pinching in
+     * @param pt1 - new point that corresponds to oldPoint1
+     * @param pt2 - new point that corresponds to oldPoint2
+     * @param oldPoint1 - previous point 1
+     * @param oldPoint2 - previous point 2
+     */
+    export function Pinching(pt1: React.Touch, pt2: React.Touch, oldPoint1: React.Touch, oldPoint2: React.Touch): number {
+        const threshold = 4;
+        const oldDist = TwoPointEuclidist(oldPoint1, oldPoint2);
+        const newDist = TwoPointEuclidist(pt1, pt2);
+
+        /** if they have the same sign, then we are either pinching in or out.
+          * threshold it by 10 (it has to be pinching by at least threshold to be a valid pinch)
+          * so that it can still pan without freaking out
+          */
+        if (Math.sign(oldDist) === Math.sign(newDist) && Math.abs(oldDist - newDist) > threshold) {
+            return Math.sign(oldDist - newDist);
+        }
+        return 0;
+    }
+
+    /**
+     * Returns -1 if pinning and pinching out, 0 if not pinning, and 1 if pinching in
+     * @param pt1 - new point that corresponds to oldPoint1
+     * @param pt2 - new point that corresponds to oldPoint2
+     * @param oldPoint1 - previous point 1
+     * @param oldPoint2 - previous point 2
+     */
+    export function Pinning(pt1: React.Touch, pt2: React.Touch, oldPoint1: React.Touch, oldPoint2: React.Touch): number {
+        const threshold = 4;
+
+        const pt1Dist = TwoPointEuclidist(oldPoint1, pt1);
+        const pt2Dist = TwoPointEuclidist(oldPoint2, pt2);
+
+        const pinching = Pinching(pt1, pt2, oldPoint1, oldPoint2);
+
+        if (pinching !== 0) {
+            if ((pt1Dist < threshold && pt2Dist > threshold) || (pt1Dist > threshold && pt2Dist < threshold)) {
+                return pinching;
+            }
+        }
+        return 0;
+    }
+
+    export function IsDragging(oldTouches: Map<number, React.Touch>, newTouches: React.Touch[], leniency: number): boolean {
+        for (const touch of newTouches) {
+            if (touch) {
+                const oldTouch = oldTouches.get(touch.identifier);
+                if (oldTouch) {
+                    if (TwoPointEuclidist(touch, oldTouch) >= leniency) {
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    // These might not be very useful anymore, but I'll leave them here for now -syip2
+    {
+
+
+        /**
+         * Returns the type of Touch Interaction from a list of points.
+         * Also returns any data that is associated with a Touch Interaction
+         * @param pts - List of points
+         */
+        // export function InterpretPointers(pts: React.Touch[]): { type: Opt<TouchInteraction>, data?: any } {
+        //     const leniency = 200;
+        //     switch (pts.length) {
+        //         case 1:
+        //             return { type: OneFinger };
+        //         case 2:
+        //             return { type: TwoSeperateFingers };
+        //         case 3:
+        //             let pt1 = pts[0];
+        //             let pt2 = pts[1];
+        //             let pt3 = pts[2];
+        //             if (pt1 && pt2 && pt3) {
+        //                 let dist12 = TwoPointEuclidist(pt1, pt2);
+        //                 let dist23 = TwoPointEuclidist(pt2, pt3);
+        //                 let dist13 = TwoPointEuclidist(pt1, pt3);
+        //                 console.log(`distances: ${dist12}, ${dist23}, ${dist13}`);
+        //                 let dist12close = dist12 < leniency;
+        //                 let dist23close = dist23 < leniency;
+        //                 let dist13close = dist13 < leniency;
+        //                 let xor2313 = dist23close ? !dist13close : dist13close;
+        //                 let xor = dist12close ? !xor2313 : xor2313;
+        //                 // three input xor because javascript doesn't have logical xor's
+        //                 if (xor) {
+        //                     let points: number[] = [];
+        //                     let min = Math.min(dist12, dist23, dist13);
+        //                     switch (min) {
+        //                         case dist12:
+        //                             points = [0, 1, 2];
+        //                             break;
+        //                         case dist23:
+        //                             points = [1, 2, 0];
+        //                             break;
+        //                         case dist13:
+        //                             points = [0, 2, 1];
+        //                             break;
+        //                     }
+        //                     return { type: TwoToOneFingers, data: points };
+        //                 }
+        //                 else {
+        //                     return { type: ThreeSeperateFingers, data: null };
+        //                 }
+        //             }
+        //         default:
+        //             return { type: undefined };
+        //     }
+        // }
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/GestureOverlay.scss b/src/client/views/GestureOverlay.scss
index 2996b7073..31601efd4 100644
--- a/src/client/views/GestureOverlay.scss
+++ b/src/client/views/GestureOverlay.scss
@@ -5,8 +5,4 @@
     top: 0;
     left: 0;
     touch-action: none;
-}
-
-.mobileInkOverlay {
-    border: 5px dashed red;
 }
\ No newline at end of file
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 81284b543..a01a86b53 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -19,7 +19,7 @@ import Palette from "./Palette";
 import MobileInterface from "../../mobile/MobileInterface";
 import { MainView } from "./MainView";
 import { DocServer } from "../DocServer";
-import { GestureContent, MobileInkBoxContent } from "../../server/Message";
+import { GestureContent, MobileInkOverlayContent } from "../../server/Message";
 import { Point } from "../northstar/model/idea/idea";
 import MobileInkOverlay from "../../mobile/MobileInkOverlay";
 
@@ -217,21 +217,6 @@ export default class GestureOverlay extends Touchable {
         return { right: right, left: left, bottom: bottom, top: top, width: right - left, height: bottom - top };
     }
 
-    // TODO: find a way to reference this function from InkingStroke instead of copy pastign here. copied bc of weird error when on mobile view
-    CreatePolyline(points: { X: number, Y: number }[], left: number, top: number, color?: string, width?: number) {
-        const pts = points.reduce((acc: string, pt: { X: number, Y: number }) => acc + `${pt.X - left},${pt.Y - top} `, "");
-        return (
-            <polyline
-                points={pts}
-                style={{
-                    fill: "none",
-                    stroke: color ?? InkingControl.Instance.selectedColor,
-                    strokeWidth: width ?? InkingControl.Instance.selectedWidth
-                }}
-            />
-        );
-    }
-
     @computed get currentStroke() {
         if (this._points.length <= 1) {
             return (null);
@@ -241,23 +226,23 @@ export default class GestureOverlay extends Touchable {
 
         return (
             <svg width={B.width} height={B.height} style={{ transform: `translate(${B.left}px, ${B.top}px)`, pointerEvents: "none", position: "absolute", zIndex: 30000 }}>
-                {this.CreatePolyline(this._points, B.left, B.top, this.Color, this.Width)}
+                {InteractionUtils.CreatePolyline(this._points, B.left, B.top, this.Color, this.Width)}
             </svg>
         );
     }
 
     @action
-    enableMobileInkBox = (content: MobileInkBoxContent) => {
-        this.showMobileInkOverlay = content.enableBox;
+    enableMobileInkOverlay = (content: MobileInkOverlayContent) => {
+        this.showMobileInkOverlay = content.enableOverlay;
     }
 
     render() {
         return (
             <div className="gestureOverlay-cont" onPointerDown={this.onPointerDown} onTouchStart={this.onTouchStart}>
+                {this.showMobileInkOverlay ? <MobileInkOverlay /> : <></>}
                 {this.currentStroke}
                 {this.props.children}
                 {this._palette}
-                {this.showMobileInkOverlay ? <MobileInkOverlay /> : <></>}
             </div>);
     }
 }
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 8b346d5d9..aca507147 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -10,24 +10,11 @@ import "./InkingStroke.scss";
 import { FieldView, FieldViewProps } from "./nodes/FieldView";
 import React = require("react");
 import { TraceMobx } from "../../new_fields/util";
+import { InteractionUtils } from "../util/InteractionUtils";
 
 type InkDocument = makeInterface<[typeof documentSchema]>;
 const InkDocument = makeInterface(documentSchema);
 
-export function CreatePolyline(points: { X: number, Y: number }[], left: number, top: number, color?: string, width?: number) {
-    const pts = points.reduce((acc: string, pt: { X: number, Y: number }) => acc + `${pt.X - left},${pt.Y - top} `, "");
-    return (
-        <polyline
-            points={pts}
-            style={{
-                fill: "none",
-                stroke: color ?? InkingControl.Instance.selectedColor,
-                strokeWidth: width ?? InkingControl.Instance.selectedWidth
-            }}
-        />
-    );
-}
-
 @observer
 export class InkingStroke extends DocExtendableComponent<FieldViewProps, InkDocument>(InkDocument) {
     public static LayoutString(fieldStr: string) { return FieldView.LayoutString(InkingStroke, fieldStr); }
@@ -44,7 +31,7 @@ export class InkingStroke extends DocExtendableComponent<FieldViewProps, InkDocu
         const top = Math.min(...ys);
         const right = Math.max(...xs);
         const bottom = Math.max(...ys);
-        const points = CreatePolyline(data, left, top, this.Document.color, this.Document.strokeWidth);
+        const points = InteractionUtils.CreatePolyline(data, left, top, this.Document.color ?? InkingControl.Instance.selectedColor, this.Document.strokeWidth ?? parseInt(InkingControl.Instance.selectedWidth));
         const width = right - left;
         const height = bottom - top;
         const scaleX = this.PanelWidth / width;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index bbfd22f50..4d216cb2a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -26,7 +26,6 @@ import { COLLECTION_BORDER_WIDTH } from "../../../views/globalCssVariables.scss"
 import { ContextMenu } from "../../ContextMenu";
 import { ContextMenuProps } from "../../ContextMenuItem";
 import { InkingControl } from "../../InkingControl";
-import { CreatePolyline } from "../../InkingStroke";
 import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView";
 import { DocumentViewProps } from "../../nodes/DocumentView";
 import { FormattedTextBox } from "../../nodes/FormattedTextBox";
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 2e0ae09ba..03f37595f 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -443,6 +443,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         if (!(InteractionUtils.IsType(e, InteractionUtils.MOUSETYPE) || InkingControl.Instance.selectedTool === InkTool.Highlighter || InkingControl.Instance.selectedTool === InkTool.Pen)) {
             if (!InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
                 e.stopPropagation();
+                // TODO: check here for panning/inking
             }
             return;
         }
diff --git a/src/mobile/MobileInkOverlay.scss b/src/mobile/MobileInkOverlay.scss
new file mode 100644
index 000000000..2e45d0441
--- /dev/null
+++ b/src/mobile/MobileInkOverlay.scss
@@ -0,0 +1,38 @@
+.mobileInkOverlay {
+    border: 5px dashed red;
+}
+
+.mobileInkOverlay-border {
+    background-color: rgba(0, 255, 0, .4);
+    position: absolute;
+    pointer-events: auto;
+    cursor: pointer;
+
+    &.top {
+        width: calc(100% + 20px);
+        height: 10px;
+        top: -10px;
+        left: -10px;
+    }
+
+    &.left {
+        width: 10px;
+        height: calc(100% + 20px);
+        top: -10px;
+        left: -10px;
+    }
+
+    &.right {
+        width: 10px;
+        height: calc(100% + 20px);
+        top: -10px;
+        right: -10px;
+    }
+
+    &.bottom {
+        width: calc(100% + 20px);
+        height: 10px;
+        bottom: -10px;
+        left: -10px;
+    }
+}
\ No newline at end of file
diff --git a/src/mobile/MobileInkOverlay.tsx b/src/mobile/MobileInkOverlay.tsx
index 71dd20c51..5efc7b83a 100644
--- a/src/mobile/MobileInkOverlay.tsx
+++ b/src/mobile/MobileInkOverlay.tsx
@@ -1,8 +1,9 @@
 import React = require('react');
 import { observer } from "mobx-react";
-import { MobileInkBoxContent, GestureContent } from "../server/Message";
+import { MobileInkOverlayContent, GestureContent, UpdateMobileInkOverlayPosition } from "../server/Message";
 import { observable, action } from "mobx";
 import { GestureUtils } from "../pen-gestures/GestureUtils";
+import "./MobileInkOverlay.scss";
 
 
 @observer
@@ -15,6 +16,11 @@ export default class MobileInkOverlay extends React.Component {
     @observable private _x: number = -300;
     @observable private _y: number = -300;
 
+    @observable private _offsetX: number = 0;
+    @observable private _offsetY: number = 0;
+    @observable private _isDragging: boolean = false;
+    private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
+
     constructor(props: Readonly<{}>) {
         super(props);
         MobileInkOverlay.Instance = this;
@@ -28,18 +34,27 @@ export default class MobileInkOverlay extends React.Component {
     }
 
     @action
-    initMobileInkOverlay(content: MobileInkBoxContent) {
+    initMobileInkOverlay(content: MobileInkOverlayContent) {
         const { width, height } = content;
         const scaledSize = this.initialSize(width ? width : 0, height ? height : 0);
-        this._width = scaledSize.width;
-        this._height = scaledSize.height;
-        this._scale = scaledSize.scale;
+        this._width = scaledSize.width * .5;
+        this._height = scaledSize.height * .5;
+        this._scale = .5; //scaledSize.scale;
         this._x = 300; // TODO: center on screen
         this._y = 25; // TODO: center on screen
     }
 
+    @action
+    updatePosition(content: UpdateMobileInkOverlayPosition) {
+        const { dx, dy, dsize } = content;
+        console.log(dx, dy, dsize);
+    }
+
     drawStroke = (content: GestureContent) => {
+        // TODO: figure out why strokes drawn in corner of mobile interface dont get inserted
+
         const { points, bounds } = content;
+        console.log("received points", points, bounds);
 
         const B = {
             right: (bounds.right * this._scale) + this._x,
@@ -65,16 +80,66 @@ export default class MobileInkOverlay extends React.Component {
         );
     }
 
+    @action
+    dragStart = (e: React.PointerEvent) => {
+        console.log("pointer down");
+        document.removeEventListener("pointermove", this.dragging);
+        document.removeEventListener("pointerup", this.dragEnd);
+        document.addEventListener("pointermove", this.dragging);
+        document.addEventListener("pointerup", this.dragEnd);
+
+        this._isDragging = true;
+        this._offsetX = e.pageX - this._mainCont.current!.getBoundingClientRect().left;
+        this._offsetY = e.pageY - this._mainCont.current!.getBoundingClientRect().top;
+
+        e.preventDefault();
+        e.stopPropagation();
+    }
+
+    @action
+    dragging = (e: PointerEvent) => {
+        const x = e.pageX - this._offsetX;
+        const y = e.pageY - this._offsetY;
+
+        // TODO: don't allow drag over library?
+        this._x = Math.min(Math.max(x, 0), window.innerWidth - this._width);
+        this._y = Math.min(Math.max(y, 0), window.innerHeight - this._height);
+
+        e.preventDefault();
+        e.stopPropagation();
+    }
+
+    @action
+    dragEnd = (e: PointerEvent) => {
+        document.removeEventListener("pointermove", this.dragging);
+        document.removeEventListener("pointerup", this.dragEnd);
+
+        this._isDragging = false;
+
+        e.preventDefault();
+        e.stopPropagation();
+    }
+
     render() {
+
         return (
-            <div className="mobileInkOverlay" style={{
-                width: this._width,
-                height: this._height,
-                position: "absolute",
-                transform: `translate(${this._x}px, ${this._y}px)`,
-                zIndex: 30000,
-                pointerEvents: "none"
-            }}></div>
+            <div className="mobileInkOverlay"
+                style={{
+                    width: this._width,
+                    height: this._height,
+                    position: "absolute",
+                    transform: `translate(${this._x}px, ${this._y}px)`,
+                    zIndex: 30000,
+                    pointerEvents: "none",
+                    borderStyle: this._isDragging ? "solid" : "dashed"
+                }}
+                ref={this._mainCont}
+            >
+                <div className="mobileInkOverlay-border top" onPointerDown={this.dragStart}></div>
+                <div className="mobileInkOverlay-border bottom" onPointerDown={this.dragStart}></div>
+                <div className="mobileInkOverlay-border left" onPointerDown={this.dragStart}></div>
+                <div className="mobileInkOverlay-border right" onPointerDown={this.dragStart}></div>
+            </div>
         );
     }
 }
\ No newline at end of file
diff --git a/src/mobile/MobileInterface.scss b/src/mobile/MobileInterface.scss
index e4cc919a5..8abe5a40d 100644
--- a/src/mobile/MobileInterface.scss
+++ b/src/mobile/MobileInterface.scss
@@ -1,4 +1,4 @@
-.mobileInterface-topButtons {
+.mobileInterface-inkInterfaceButtons {
     position: absolute;
     display: flex;
     justify-content: space-between;
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index 4840ea374..b191b3afb 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -10,7 +10,7 @@ import { DocumentView } from '../client/views/nodes/DocumentView';
 import { emptyPath, emptyFunction, returnFalse, returnOne, returnEmptyString, returnTrue } from '../Utils';
 import { Transform } from '../client/util/Transform';
 import { library } from '@fortawesome/fontawesome-svg-core';
-import { faPenNib, faHighlighter, faEraser, faMousePointer, faBreadSlice, faTrash, faCheck } from '@fortawesome/free-solid-svg-icons';
+import { faPenNib, faHighlighter, faEraser, faMousePointer, faBreadSlice, faTrash, faCheck, faLongArrowAltLeft } from '@fortawesome/free-solid-svg-icons';
 import { Scripting } from '../client/util/Scripting';
 import { CollectionFreeFormView } from '../client/views/collections/collectionFreeForm/CollectionFreeFormView';
 import GestureOverlay from '../client/views/GestureOverlay';
@@ -23,7 +23,7 @@ import { DateField } from '../new_fields/DateField';
 import { GestureUtils } from '../pen-gestures/GestureUtils';
 import { DocServer } from '../client/DocServer';
 
-library.add(faTrash, faCheck);
+library.add(faLongArrowAltLeft);
 
 @observer
 export default class MobileInterface extends React.Component {
@@ -68,8 +68,8 @@ export default class MobileInterface extends React.Component {
                     InkingControl.Instance.switchTool(InkTool.Pen);
                     this.drawingInk = true;
 
-                    DocServer.Mobile.dispatchBoxTrigger({
-                        enableBox: true,
+                    DocServer.Mobile.dispatchOverlayTrigger({
+                        enableOverlay: true,
                         width: window.innerWidth,
                         height: window.innerHeight
                     });
@@ -112,12 +112,12 @@ export default class MobileInterface extends React.Component {
         return "hello";
     }
 
-    onClick = (e: React.MouseEvent) => {
+    onBack = (e: React.MouseEvent) => {
         this.switchCurrentView("main");
         InkingControl.Instance.switchTool(InkTool.None); // TODO: switch to previous tool
 
-        DocServer.Mobile.dispatchBoxTrigger({
-            enableBox: false,
+        DocServer.Mobile.dispatchOverlayTrigger({
+            enableOverlay: false,
             width: window.innerWidth,
             height: window.innerHeight
         });
@@ -126,37 +126,61 @@ export default class MobileInterface extends React.Component {
         this.drawingInk = false;
     }
 
+    shiftLeft = (e: React.MouseEvent) => {
+        DocServer.Mobile.dispatchOverlayPositionUpdate({
+            dx: -10
+        });
+    }
+
+    shiftRight = (e: React.MouseEvent) => {
+        DocServer.Mobile.dispatchOverlayPositionUpdate({
+            dx: 10
+        });
+    }
+
     @computed
     get inkContent() {
+        // TODO: support panning and zooming
+        // TODO: handle moving of ink strokes
         if (this.mainContainer) {
             return (
-                <GestureOverlay>
-                    <div className="mobileInterface-topButtons">
-                        <button className="mobileInterface-button cancel" onClick={this.onClick} title="Cancel drawing"><FontAwesomeIcon icon="trash" /></button>
-                        <button className="mobileInterface-button cancel" onClick={this.onClick} title="Insert drawing"><FontAwesomeIcon icon="check" /></button>
+                <div className="mobileInterface">
+                    <div className="mobileInterface-inkInterfaceButtons">
+                        <div className="navButtons">
+                            <button className="mobileInterface-button cancel" onClick={this.onBack} title="Cancel drawing"><FontAwesomeIcon icon="long-arrow-alt-left" /></button>
+                        </div>
+                        <div className="inkSettingButtons">
+                            <button className="mobileInterface-button cancel" onClick={this.onBack} title="Cancel drawing"><FontAwesomeIcon icon="long-arrow-alt-left" /></button>
+                        </div>
+                        <div className="navButtons">
+                            <button className="mobileInterface-button" onClick={this.shiftLeft} title="Shift left">left</button>
+                            <button className="mobileInterface-button" onClick={this.shiftRight} title="Shift right">right</button>
+                        </div>
                     </div>
-                    <CollectionView
-                        Document={this.mainContainer}
-                        DataDoc={undefined}
-                        LibraryPath={emptyPath}
-                        fieldKey={""}
-                        addDocTab={returnFalse}
-                        pinToPres={emptyFunction}
-                        PanelHeight={() => window.innerHeight}
-                        PanelWidth={() => window.innerWidth}
-                        focus={emptyFunction}
-                        isSelected={returnFalse}
-                        select={emptyFunction}
-                        active={returnFalse}
-                        ContentScaling={returnOne}
-                        whenActiveChanged={returnFalse}
-                        ScreenToLocalTransform={Transform.Identity}
-                        ruleProvider={undefined}
-                        renderDepth={0}
-                        ContainingCollectionView={undefined}
-                        ContainingCollectionDoc={undefined}>
-                    </CollectionView>
-                </GestureOverlay>
+                    <GestureOverlay>
+                        <CollectionView
+                            Document={this.mainContainer}
+                            DataDoc={undefined}
+                            LibraryPath={emptyPath}
+                            fieldKey={""}
+                            addDocTab={returnFalse}
+                            pinToPres={emptyFunction}
+                            PanelHeight={() => window.innerHeight}
+                            PanelWidth={() => window.innerWidth}
+                            focus={emptyFunction}
+                            isSelected={returnFalse}
+                            select={emptyFunction}
+                            active={returnFalse}
+                            ContentScaling={returnOne}
+                            whenActiveChanged={returnFalse}
+                            ScreenToLocalTransform={Transform.Identity}
+                            ruleProvider={undefined}
+                            renderDepth={0}
+                            ContainingCollectionView={undefined}
+                            ContainingCollectionDoc={undefined}>
+                        </CollectionView>
+                    </GestureOverlay>
+                </div>
             );
         }
     }
diff --git a/src/server/Message.ts b/src/server/Message.ts
index 1958286df..064a19653 100644
--- a/src/server/Message.ts
+++ b/src/server/Message.ts
@@ -50,12 +50,18 @@ export interface GestureContent {
     readonly color?: string;
 }
 
-export interface MobileInkBoxContent {
-    readonly enableBox: boolean;
+export interface MobileInkOverlayContent {
+    readonly enableOverlay: boolean;
     readonly width?: number;
     readonly height?: number;
 }
 
+export interface UpdateMobileInkOverlayPosition {
+    readonly dx?: number;
+    readonly dy?: number;
+    readonly dsize?: number;
+}
+
 export namespace MessageStore {
     export const Foo = new Message<string>("Foo");
     export const Bar = new Message<string>("Bar");
@@ -65,8 +71,10 @@ export namespace MessageStore {
     export const GetDocument = new Message<string>("Get Document");
     export const DeleteAll = new Message<any>("Delete All");
     export const ConnectionTerminated = new Message<string>("Connection Terminated");
+
     export const GesturePoints = new Message<GestureContent>("Gesture Points");
-    export const MobileInkBoxTrigger = new Message<MobileInkBoxContent>("Trigger Mobile Ink Box");
+    export const MobileInkOverlayTrigger = new Message<MobileInkOverlayContent>("Trigger Mobile Ink Overlay");
+    export const UpdateMobileInkOverlayPosition = new Message<UpdateMobileInkOverlayPosition>("Update Mobile Ink Overlay Position");
 
     export const GetRefField = new Message<string>("Get Ref Field");
     export const GetRefFields = new Message<string[]>("Get Ref Fields");
diff --git a/src/server/Websocket/Websocket.ts b/src/server/Websocket/Websocket.ts
index 16e34bdfc..fe253400c 100644
--- a/src/server/Websocket/Websocket.ts
+++ b/src/server/Websocket/Websocket.ts
@@ -1,5 +1,5 @@
 import { Utils } from "../../Utils";
-import { MessageStore, Transferable, Types, Diff, YoutubeQueryInput, YoutubeQueryTypes, GestureContent, MobileInkBoxContent } from "../Message";
+import { MessageStore, Transferable, Types, Diff, YoutubeQueryInput, YoutubeQueryTypes, GestureContent, MobileInkOverlayContent } from "../Message";
 import { Client } from "../Client";
 import { Socket } from "socket.io";
 import { Database } from "../database";
@@ -55,7 +55,7 @@ export namespace WebSocket {
             Utils.AddServerHandler(socket, MessageStore.DeleteField, id => DeleteField(socket, id));
             Utils.AddServerHandler(socket, MessageStore.DeleteFields, ids => DeleteFields(socket, ids));
             Utils.AddServerHandler(socket, MessageStore.GesturePoints, content => processGesturePoints(socket, content));
-            Utils.AddServerHandler(socket, MessageStore.MobileInkBoxTrigger, content => processBoxTrigger(socket, content));
+            Utils.AddServerHandler(socket, MessageStore.MobileInkOverlayTrigger, content => processOverlayTrigger(socket, content));
             Utils.AddServerHandlerCallback(socket, MessageStore.GetRefField, GetRefField);
             Utils.AddServerHandlerCallback(socket, MessageStore.GetRefFields, GetRefFields);
 
@@ -74,8 +74,8 @@ export namespace WebSocket {
         socket.broadcast.emit("receiveGesturePoints", content);
     }
 
-    function processBoxTrigger(socket: Socket, content: MobileInkBoxContent) {
-        socket.broadcast.emit("receiveBoxTrigger", content);
+    function processOverlayTrigger(socket: Socket, content: MobileInkOverlayContent) {
+        socket.broadcast.emit("receiveOverlayTrigger", content);
     }
 
     function HandleYoutubeQuery([query, callback]: [YoutubeQueryInput, (result?: any[]) => void]) {
-- 
cgit v1.2.3-70-g09d2


From 1007cfb325f2dcddc4365538e4b354d06eb85f2f Mon Sep 17 00:00:00 2001
From: Stanley Yip <stanley_yip@brown.edu>
Date: Tue, 4 Feb 2020 18:12:47 -0500
Subject: ok so the toolglass is working, but it's super slow... i'll fix that
 later lol

---
 src/client/views/GestureOverlay.scss               |  13 +-
 src/client/views/GestureOverlay.tsx                | 151 ++++++++++++---------
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   6 +-
 src/pen-gestures/GestureUtils.ts                   |  11 +-
 .../authentication/models/current_user_utils.ts    |   2 +-
 5 files changed, 109 insertions(+), 74 deletions(-)

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

diff --git a/src/client/views/GestureOverlay.scss b/src/client/views/GestureOverlay.scss
index 60b53c528..f425c438e 100644
--- a/src/client/views/GestureOverlay.scss
+++ b/src/client/views/GestureOverlay.scss
@@ -16,11 +16,13 @@
 .inkToTextDoc-cont {
     position: absolute;
     width: 300px;
-    height: 300px;
     overflow: hidden;
 
     .inkToTextDoc-scroller {
         overflow: visible;
+        position: absolute;
+        width: 100%;
+        left: -24px;
 
         .collectionView {
             overflow: visible;
@@ -30,6 +32,15 @@
             }
         }
     }
+
+    .shadow {
+        width: 100%;
+        height: calc(100% - 25px);
+        position: absolute;
+        top: 25px;
+        background-color: black;
+        opacity: 0.2;
+    }
 }
 
 .filter-cont {
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 0c2e9e1bc..0fd6f3dba 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -2,7 +2,7 @@ import React = require("react");
 import { Touchable } from "./Touchable";
 import { observer } from "mobx-react";
 import "./GestureOverlay.scss";
-import { computed, observable, action, runInAction, IReactionDisposer, reaction, flow } from "mobx";
+import { computed, observable, action, runInAction, IReactionDisposer, reaction, flow, trace } from "mobx";
 import { GestureUtils } from "../../pen-gestures/GestureUtils";
 import { InteractionUtils } from "../util/InteractionUtils";
 import { InkingControl } from "./InkingControl";
@@ -25,6 +25,8 @@ import htmlToImage from "html-to-image";
 import { ScriptField } from "../../new_fields/ScriptField";
 import { listSpec } from "../../new_fields/Schema";
 import { List } from "../../new_fields/List";
+import { CollectionViewType } from "./collections/CollectionView";
+import InkCanvas from "./InkCanvas";
 
 @observer
 export default class GestureOverlay extends Touchable {
@@ -63,6 +65,11 @@ export default class GestureOverlay extends Touchable {
         GestureOverlay.Instance = this;
     }
 
+    componentDidMount = () => {
+        this._thumbDoc = FieldValue(Cast(CurrentUserUtils.setupThumbDoc(CurrentUserUtils.UserDocument), Doc));
+        this._inkToTextDoc = FieldValue(Cast(this._thumbDoc?.inkToTextDoc, Doc));
+    }
+
     getNewTouches(e: React.TouchEvent | TouchEvent) {
         const ntt: (React.Touch | Touch)[] = Array.from(e.targetTouches);
         const nct: (React.Touch | Touch)[] = Array.from(e.changedTouches);
@@ -313,14 +320,23 @@ export default class GestureOverlay extends Touchable {
             this._palette = undefined;
             this.thumbIdentifier = undefined;
             this._thumbDoc = undefined;
-            this._strokes.forEach(s => {
-                this.dispatchGesture(GestureUtils.Gestures.Stroke, s);
-            });
-            this._strokes = [];
-            if (NumCast(this._inkToTextDoc?.selectedIndex) > 0) {
-                const selectedButton = this._possibilities[NumCast(this._inkToTextDoc?.selectedIndex) - 1];
-                Cast(selectedButton?.proto?.onClick, ScriptField)?.script.run({ this: selectedButton.proto }, console.log);
+
+            let scriptWorked = false;
+            if (NumCast(this._inkToTextDoc?.selectedIndex) > -1) {
+                const selectedButton = this._possibilities[NumCast(this._inkToTextDoc?.selectedIndex)];
+                if (Cast(selectedButton?.proto?.onClick, ScriptField)?.script.run({ this: selectedButton }, console.log).success) {
+                    scriptWorked = true;
+                    console.log("success");
+                };
+                console.log(Cast(selectedButton?.proto?.onClick, ScriptField)?.script);
+            }
+
+            if (!scriptWorked) {
+                this._strokes.forEach(s => {
+                    this.dispatchGesture(GestureUtils.Gestures.Stroke, s);
+                });
             }
+            this._strokes = [];
             this._possibilities = [];
             document.removeEventListener("touchend", this.handleHandUp);
         }
@@ -409,14 +425,14 @@ export default class GestureOverlay extends Touchable {
                         const l = Math.min(this.svgBounds.left, ...this._strokes.map(s => this.getBounds(s).left));
                         const t = Math.min(this.svgBounds.top, ...this._strokes.map(s => this.getBounds(s).top));
                         runInAction(() => {
-                            console.log(possibilities);
                             const buttons = possibilities.map(p => Docs.Create.ButtonDocument({
-                                _height: r - l, _width: 25, backgroundColor: "lightgrey", color: "rgb(34, 34, 34)", x: l, y: t,
+                                _height: 25, _width: r - l, backgroundColor: "lightgrey", color: "rgb(34, 34, 34)", x: l, y: t, _viewType: CollectionViewType.Linear,
+                                isExpanded: true,
                                 title: p, fontSize: 10, letterSpacing: "0px", textTransform: "unset", boxShadow: ".5px .5px 0px rgb(34, 34, 34)",
-                                onClick: ScriptField.MakeScript('Docs.Create.TextDocument(this.title, {_width: 200, _height: 35, x: this.x, y: this.y})')
+                                onClick: ScriptField.MakeScript('createText(this.proto.title, this.x, this.y)')
                             }));
-                            if (this._inkToTextDoc) {
-                                this._inkToTextDoc.data = new List<Doc>(buttons);
+                            if (this._inkToTextDoc && this._thumbDoc) {
+                                this._inkToTextDoc = this._thumbDoc.inkToTextDoc = Docs.Create.LinearDocument(buttons, { _width: 300, _height: 25, _autoHeight: true, _chromeStatus: "disabled", isExpanded: true, flexDirection: "column" });
                                 this._inkToTextDoc.selectedIndex = 0;
                             }
                             this._possibilities = buttons;
@@ -459,7 +475,7 @@ export default class GestureOverlay extends Touchable {
         document.removeEventListener("pointerup", this.onPointerUp);
     }
 
-    dispatchGesture = (gesture: GestureUtils.Gestures, stroke?: InkData) => {
+    dispatchGesture = (gesture: GestureUtils.Gestures, stroke?: InkData, data?: any) => {
         const target = document.elementFromPoint((stroke ?? this._points)[0].X, (stroke ?? this._points)[0].Y);
         target?.dispatchEvent(
             new CustomEvent<GestureUtils.GestureEvent>("dashOnGesture",
@@ -468,7 +484,8 @@ export default class GestureOverlay extends Touchable {
                     detail: {
                         points: stroke ?? this._points,
                         gesture: gesture,
-                        bounds: this.getBounds(stroke ?? this._points)
+                        bounds: this.getBounds(stroke ?? this._points),
+                        text: data
                     }
                 }
             )
@@ -489,27 +506,21 @@ export default class GestureOverlay extends Touchable {
         return this.getBounds(this._points);
     }
 
-    @computed get currentStrokes() {
+    @computed get elements() {
         const B = this.svgBounds;
+        return [
+            this.props.children,
+            this._palette,
 
-        return (
             [this._strokes.map(l => {
                 const b = this.getBounds(l);
                 return <svg width={b.width} height={b.height} style={{ transform: `translate(${b.left}px, ${b.top}px)`, pointerEvents: "none", position: "absolute", zIndex: 30000 }}>
-                    {InteractionUtils.CreatePolyline(l, b.left, b.top, this.Color, this.Width)}
+                    {InteractionUtils.CreatePolyline(l, b.left, b.top, GestureOverlay.Instance.Color, GestureOverlay.Instance.Width)}
                 </svg>;
             }),
             this._points.length <= 1 ? (null) : <svg width={B.width} height={B.height} style={{ transform: `translate(${B.left}px, ${B.top}px)`, pointerEvents: "none", position: "absolute", zIndex: 30000 }}>
-                {InteractionUtils.CreatePolyline(this._points, B.left, B.top, this.Color, this.Width)}
+                {InteractionUtils.CreatePolyline(this._points, B.left, B.top, GestureOverlay.Instance.Color, GestureOverlay.Instance.Width)}
             </svg>]
-        );
-    }
-
-    @computed get elements() {
-        return [
-            this.props.children,
-            this._palette,
-            this.currentStrokes
         ];
     }
 
@@ -548,53 +559,59 @@ export default class GestureOverlay extends Touchable {
     }
 
     @computed
+    private get suggestionContent() {
+        const b = Math.max(this.svgBounds.bottom, ...this._strokes.map(s => this.getBounds(s).bottom));
+        const r = Math.max(this.svgBounds.right, ...this._strokes.map(s => this.getBounds(s).right));
+        const l = Math.min(this.svgBounds.left, ...this._strokes.map(s => this.getBounds(s).left));
+        const t = Math.min(this.svgBounds.top, ...this._strokes.map(s => this.getBounds(s).top));
+        return (
+            <div className="inkToTextDoc-cont" style={{
+                transform: `translate(${l}px, ${b}px)`,
+                width: r - l,
+                height: this._possibilities.length * 25
+            }}>
+                <div className="inkToTextDoc-scroller" style={{ transform: `translate(0, ${-NumCast(this._inkToTextDoc!.selectedIndex) * 25}px)` }}>
+                    <DocumentView
+                        Document={this._inkToTextDoc!}
+                        DataDoc={undefined}
+                        LibraryPath={emptyPath}
+                        addDocument={undefined}
+                        addDocTab={returnFalse}
+                        pinToPres={emptyFunction}
+                        onClick={undefined}
+                        removeDocument={undefined}
+                        ScreenToLocalTransform={() => new Transform(-l, -(b + NumCast(this._inkToTextDoc?.selectedIndex, 0) * 25), 1)}
+                        ContentScaling={returnOne}
+                        PanelWidth={() => 300}
+                        PanelHeight={() => 300}
+                        renderDepth={0}
+                        backgroundColor={returnEmptyString}
+                        focus={emptyFunction}
+                        parentActive={returnTrue}
+                        whenActiveChanged={emptyFunction}
+                        bringToFront={emptyFunction}
+                        ContainingCollectionView={undefined}
+                        ContainingCollectionDoc={undefined}
+                        zoomToScale={emptyFunction}
+                        getScale={returnOne}
+                    />;
+                    </div>
+                <div className="shadow" style={{ height: `calc(100% - 25px - ${NumCast(this._inkToTextDoc!.selectedIndex) * 25}px)` }}>
+                </div>
+            </div>)
+    }
+
     private get inkToTextSuggestions() {
-        console.log(this._possibilities.length);
         if (this._inkToTextDoc && this._possibilities.length) {
-            const b = Math.max(this.svgBounds.bottom, ...this._strokes.map(s => this.getBounds(s).bottom));
-            const r = Math.max(this.svgBounds.right, ...this._strokes.map(s => this.getBounds(s).right));
-            const l = Math.min(this.svgBounds.left, ...this._strokes.map(s => this.getBounds(s).left));
-            const t = Math.min(this.svgBounds.top, ...this._strokes.map(s => this.getBounds(s).top));
-
             return (
-                <div className="inkToTextDoc-cont" style={{
-                    transform: `translate(${l}px, ${b}px)`,
-                    width: r - l,
-                    height: 25,
-                }}>
-                    <div className="inkToTextDoc-scroller" style={{ transform: `translate(0, ${-NumCast(this._inkToTextDoc.selectedIndex) * 25}px)` }}>
-                        <DocumentView
-                            Document={this._inkToTextDoc}
-                            DataDoc={undefined}
-                            LibraryPath={emptyPath}
-                            addDocument={undefined}
-                            addDocTab={returnFalse}
-                            pinToPres={emptyFunction}
-                            onClick={undefined}
-                            removeDocument={undefined}
-                            ScreenToLocalTransform={() => new Transform(-l, -(b + NumCast(this._inkToTextDoc?.selectedIndex, 0) * 25), 1)}
-                            ContentScaling={returnOne}
-                            PanelWidth={() => 300}
-                            PanelHeight={() => 300}
-                            renderDepth={0}
-                            backgroundColor={returnEmptyString}
-                            focus={emptyFunction}
-                            parentActive={returnTrue}
-                            whenActiveChanged={emptyFunction}
-                            bringToFront={emptyFunction}
-                            ContainingCollectionView={undefined}
-                            ContainingCollectionDoc={undefined}
-                            zoomToScale={emptyFunction}
-                            getScale={returnOne}
-                        />;
-                    </div>
-                </div>
+                this.suggestionContent
             );
         }
         return null;
     }
 
     render() {
+        trace();
         return (
             <div className="gestureOverlay-cont" onPointerDown={this.onPointerDown} onTouchStart={this.onReactTouchStart}>
                 {this.elements}
@@ -647,4 +664,8 @@ Scripting.addGlobal(function resetPen() {
         GestureOverlay.Instance.Color = GestureOverlay.Instance.SavedColor ?? "rgb(244, 67, 54)";
         GestureOverlay.Instance.Width = GestureOverlay.Instance.SavedWidth ?? 5;
     });
+});
+Scripting.addGlobal(function createText(text: any, x: any, y: any) {
+    console.log("creating");
+    GestureOverlay.Instance.dispatchGesture("text", [{ X: x, Y: y }], text);
 });
\ 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 41ef8c2a6..17139d9d9 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -412,7 +412,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 this.addDocument(Docs.Create.FreeformDocument(sel, { title: "nested collection", x: bounds.x, y: bounds.y, _width: bWidth, _height: bHeight, _panX: 0, _panY: 0 }));
                 sel.forEach(d => this.props.removeDocument(d));
                 break;
-
+            case GestureUtils.Gestures.Text:
+                if (ge.text) {
+                    const B = this.getTransform().transformPoint(ge.points[0].X, ge.points[0].Y);
+                    this.addDocument(Docs.Create.TextDocument(ge.text, { title: ge.text, x: B[0], y: B[1] }));
+                }
         }
     }
 
diff --git a/src/pen-gestures/GestureUtils.ts b/src/pen-gestures/GestureUtils.ts
index 4b5ad6684..4e3493c1c 100644
--- a/src/pen-gestures/GestureUtils.ts
+++ b/src/pen-gestures/GestureUtils.ts
@@ -6,18 +6,16 @@ import { Doc, WidthSym, HeightSym } from "../new_fields/Doc";
 import { NumCast } from "../new_fields/Types";
 import { CollectionFreeFormView } from "../client/views/collections/collectionFreeForm/CollectionFreeFormView";
 import { Rect } from "react-measure";
+import { Scripting } from "../client/util/Scripting";
 
 export namespace GestureUtils {
-    namespace GestureDataTypes {
-        export type BoxData = Array<Doc>;
-    }
-
     export class GestureEvent {
         constructor(
             readonly gesture: Gestures,
             readonly points: PointData[],
             readonly bounds: Rect,
-            readonly callbackFn?: Function
+            readonly callbackFn?: Function,
+            readonly text?: any
         ) { }
     }
 
@@ -38,7 +36,8 @@ export namespace GestureUtils {
         Box = "box",
         Line = "line",
         Stroke = "stroke",
-        Scribble = "scribble"
+        Scribble = "scribble",
+        Text = "text"
     }
 
     export const GestureRecognizer = new NDollarRecognizer(false);
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 6c37380f3..b0ea2f9ad 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -137,7 +137,7 @@ export class CurrentUserUtils {
             thumbDoc.inkToTextDoc = Docs.Create.LinearDocument([], { _width: 300, _height: 25, _autoHeight: true, _chromeStatus: "disabled", isExpanded: true, flexDirection: "column" });
             userDoc.thumbDoc = thumbDoc;
         }
-        return userDoc.thumbDoc;
+        return Cast(userDoc.thumbDoc, Doc);
     }
 
     static setupMobileDoc(userDoc: Doc) {
-- 
cgit v1.2.3-70-g09d2


From 8767ec49fbb927ccde96f9f89562109703535d4e Mon Sep 17 00:00:00 2001
From: Stanley Yip <stanley_yip@brown.edu>
Date: Thu, 6 Feb 2020 12:40:57 -0500
Subject: asdjklf

---
 src/client/documents/Documents.ts                  |   1 +
 src/client/views/GestureOverlay.scss               |  13 +--
 src/client/views/GestureOverlay.tsx                | 127 ++++++---------------
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   1 +
 .../collectionFreeForm/MarqueeOptionsMenu.tsx      |   8 ++
 .../collections/collectionFreeForm/MarqueeView.tsx |  29 ++++-
 6 files changed, 78 insertions(+), 101 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 64583ae55..1c13eb079 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -138,6 +138,7 @@ export interface DocumentOptions {
     textTransform?: string; // is linear view expanded
     letterSpacing?: string; // is linear view expanded
     flexDirection?: "unset" | "row" | "column" | "row-reverse" | "column-reverse";
+    selectedIndex?: number;
 }
 
 class EmptyBox {
diff --git a/src/client/views/GestureOverlay.scss b/src/client/views/GestureOverlay.scss
index f425c438e..2fad87ee0 100644
--- a/src/client/views/GestureOverlay.scss
+++ b/src/client/views/GestureOverlay.scss
@@ -17,19 +17,18 @@
     position: absolute;
     width: 300px;
     overflow: hidden;
+    pointer-events: none;
 
     .inkToTextDoc-scroller {
         overflow: visible;
         position: absolute;
         width: 100%;
-        left: -24px;
 
-        .collectionView {
-            overflow: visible;
-
-            .collectionLinearView-outer {
-                overflow: visible;
-            }
+        .menuItem-cont {
+            width: 100%;
+            height: 20px;
+            padding: 2.5px;
+            border-bottom: .5px solid black;
         }
     }
 
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 0fd6f3dba..e8262af1b 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -26,7 +26,7 @@ import { ScriptField } from "../../new_fields/ScriptField";
 import { listSpec } from "../../new_fields/Schema";
 import { List } from "../../new_fields/List";
 import { CollectionViewType } from "./collections/CollectionView";
-import InkCanvas from "./InkCanvas";
+import TouchScrollableMenu, { TouchScrollableMenuItem } from "./TouchScrollableMenu";
 
 @observer
 export default class GestureOverlay extends Touchable {
@@ -40,12 +40,15 @@ export default class GestureOverlay extends Touchable {
 
     @observable private _thumbX?: number;
     @observable private _thumbY?: number;
+    @observable private _selectedIndex: number = -1;
+    @observable private _menuX: number = -300;
+    @observable private _menuY: number = -300;
     @observable private _pointerY?: number;
     @observable private _points: { X: number, Y: number }[] = [];
     @observable private _strokes: InkData[] = [];
     @observable private _palette?: JSX.Element;
     @observable private _clipboardDoc?: JSX.Element;
-    @observable private _possibilities: Doc[] = [];
+    @observable private _possibilities: JSX.Element[] = [];
 
     @computed private get height(): number { return Math.max(this._pointerY && this._thumbY ? this._thumbY - this._pointerY : 300, 300); }
     @computed private get showBounds() { return this.Tool !== ToolglassTools.None; }
@@ -254,6 +257,8 @@ export default class GestureOverlay extends Touchable {
                 this._thumbDoc = thumbDoc;
                 this._thumbX = thumb.clientX;
                 this._thumbY = thumb.clientY;
+                this._menuX = thumb.clientX + 50;
+                this._menuY = thumb.clientY;
                 this._palette = <HorizontalPalette x={minX} y={minY} thumb={[thumb.clientX, thumb.clientY]} thumbDoc={thumbDoc} />;
             });
         }
@@ -294,15 +299,14 @@ export default class GestureOverlay extends Touchable {
             const pt = e.changedTouches.item(i);
             if (pt && pt.identifier === this.thumbIdentifier && this._thumbY) {
                 if (this._thumbX && this._thumbDoc) {
-                    if (Math.abs(pt.clientX - this._thumbX) > 20) {
+                    if (Math.abs(pt.clientX - this._thumbX) > 30) {
                         this._thumbDoc.selectedIndex = Math.max(0, NumCast(this._thumbDoc.selectedIndex) - Math.sign(pt.clientX - this._thumbX));
                         this._thumbX = pt.clientX;
                     }
                 }
                 if (this._thumbY && this._inkToTextDoc) {
                     if (Math.abs(pt.clientY - this._thumbY) > 20) {
-                        this._inkToTextDoc.selectedIndex = Math.max(0, NumCast(this._inkToTextDoc.selectedIndex) - Math.sign(pt.clientY - this._thumbY));
-                        this._thumbY = pt.clientY;
+                        this._selectedIndex = Math.max(0, -Math.floor((pt.clientY - this._thumbY) / 20));
                     }
                 }
             }
@@ -324,11 +328,10 @@ export default class GestureOverlay extends Touchable {
             let scriptWorked = false;
             if (NumCast(this._inkToTextDoc?.selectedIndex) > -1) {
                 const selectedButton = this._possibilities[NumCast(this._inkToTextDoc?.selectedIndex)];
-                if (Cast(selectedButton?.proto?.onClick, ScriptField)?.script.run({ this: selectedButton }, console.log).success) {
+                if (selectedButton) {
+                    selectedButton.props.onClick();
                     scriptWorked = true;
-                    console.log("success");
-                };
-                console.log(Cast(selectedButton?.proto?.onClick, ScriptField)?.script);
+                }
             }
 
             if (!scriptWorked) {
@@ -337,6 +340,7 @@ export default class GestureOverlay extends Touchable {
                 });
             }
             this._strokes = [];
+            this._points = [];
             this._possibilities = [];
             document.removeEventListener("touchend", this.handleHandUp);
         }
@@ -398,12 +402,12 @@ export default class GestureOverlay extends Touchable {
     }
 
     @action
-    onPointerUp = async (e: PointerEvent) => {
+    onPointerUp = (e: PointerEvent) => {
         if (this._points.length > 1) {
             const B = this.svgBounds;
             const points = this._points.map(p => ({ X: p.X - B.left, Y: p.Y - B.top }));
 
-            const initialPoint = this._points[0];
+            const initialPoint = this._points[0.];
             const xInGlass = initialPoint.X > (this._thumbX ?? Number.MAX_SAFE_INTEGER) && initialPoint.X < (this._thumbX ?? Number.MAX_SAFE_INTEGER) + this.height;
             const yInGlass = initialPoint.Y > (this._thumbY ?? Number.MAX_SAFE_INTEGER) - this.height && initialPoint.Y < (this._thumbY ?? Number.MAX_SAFE_INTEGER);
 
@@ -414,28 +418,21 @@ export default class GestureOverlay extends Touchable {
                         document.removeEventListener("pointerup", this.onPointerUp);
                         this._strokes.push(new Array(...this._points));
                         this._points = [];
-                        const results = await CognitiveServices.Inking.Appliers.InterpretStrokes(this._strokes);
-                        const wordResults = results.filter((r: any) => r.category === "inkWord");
-                        const possibilities: string[] = [];
-                        if (wordResults[0]?.recognizedText) {
-                            possibilities.push(wordResults[0]?.recognizedText)
-                        }
-                        possibilities.push(...wordResults[0]?.alternates?.map((a: any) => a.recognizedString));
-                        const r = Math.max(this.svgBounds.right, ...this._strokes.map(s => this.getBounds(s).right));
-                        const l = Math.min(this.svgBounds.left, ...this._strokes.map(s => this.getBounds(s).left));
-                        const t = Math.min(this.svgBounds.top, ...this._strokes.map(s => this.getBounds(s).top));
-                        runInAction(() => {
-                            const buttons = possibilities.map(p => Docs.Create.ButtonDocument({
-                                _height: 25, _width: r - l, backgroundColor: "lightgrey", color: "rgb(34, 34, 34)", x: l, y: t, _viewType: CollectionViewType.Linear,
-                                isExpanded: true,
-                                title: p, fontSize: 10, letterSpacing: "0px", textTransform: "unset", boxShadow: ".5px .5px 0px rgb(34, 34, 34)",
-                                onClick: ScriptField.MakeScript('createText(this.proto.title, this.x, this.y)')
-                            }));
-                            if (this._inkToTextDoc && this._thumbDoc) {
-                                this._inkToTextDoc = this._thumbDoc.inkToTextDoc = Docs.Create.LinearDocument(buttons, { _width: 300, _height: 25, _autoHeight: true, _chromeStatus: "disabled", isExpanded: true, flexDirection: "column" });
-                                this._inkToTextDoc.selectedIndex = 0;
+                        CognitiveServices.Inking.Appliers.InterpretStrokes(this._strokes).then((results) => {
+                            const wordResults = results.filter((r: any) => r.category === "inkWord" || r.category === "paragraph");
+                            const possibilities: string[] = [];
+                            if (wordResults[0]?.recognizedText) {
+                                possibilities.push(wordResults[0]?.recognizedText)
                             }
-                            this._possibilities = buttons;
+                            possibilities.push(...wordResults[0]?.alternates?.map((a: any) => a.recognizedString));
+                            console.log(possibilities);
+                            const r = Math.max(this.svgBounds.right, ...this._strokes.map(s => this.getBounds(s).right));
+                            const l = Math.min(this.svgBounds.left, ...this._strokes.map(s => this.getBounds(s).left));
+                            const t = Math.min(this.svgBounds.top, ...this._strokes.map(s => this.getBounds(s).top));
+                            runInAction(() => {
+                                this._possibilities = possibilities.map(p =>
+                                    <TouchScrollableMenuItem text={p} onClick={() => GestureOverlay.Instance.dispatchGesture(GestureUtils.Gestures.Text, [{ X: l, Y: t }], p)} />);
+                            });
                         });
                         break;
                     case ToolglassTools.IgnoreGesture:
@@ -511,10 +508,9 @@ export default class GestureOverlay extends Touchable {
         return [
             this.props.children,
             this._palette,
-
             [this._strokes.map(l => {
                 const b = this.getBounds(l);
-                return <svg width={b.width} height={b.height} style={{ transform: `translate(${b.left}px, ${b.top}px)`, pointerEvents: "none", position: "absolute", zIndex: 30000 }}>
+                return <svg key={b.left} width={b.width} height={b.height} style={{ transform: `translate(${b.left}px, ${b.top}px)`, pointerEvents: "none", position: "absolute", zIndex: 30000 }}>
                     {InteractionUtils.CreatePolyline(l, b.left, b.top, GestureOverlay.Instance.Color, GestureOverlay.Instance.Width)}
                 </svg>;
             }),
@@ -558,58 +554,6 @@ export default class GestureOverlay extends Touchable {
         this._clipboardDoc = undefined;
     }
 
-    @computed
-    private get suggestionContent() {
-        const b = Math.max(this.svgBounds.bottom, ...this._strokes.map(s => this.getBounds(s).bottom));
-        const r = Math.max(this.svgBounds.right, ...this._strokes.map(s => this.getBounds(s).right));
-        const l = Math.min(this.svgBounds.left, ...this._strokes.map(s => this.getBounds(s).left));
-        const t = Math.min(this.svgBounds.top, ...this._strokes.map(s => this.getBounds(s).top));
-        return (
-            <div className="inkToTextDoc-cont" style={{
-                transform: `translate(${l}px, ${b}px)`,
-                width: r - l,
-                height: this._possibilities.length * 25
-            }}>
-                <div className="inkToTextDoc-scroller" style={{ transform: `translate(0, ${-NumCast(this._inkToTextDoc!.selectedIndex) * 25}px)` }}>
-                    <DocumentView
-                        Document={this._inkToTextDoc!}
-                        DataDoc={undefined}
-                        LibraryPath={emptyPath}
-                        addDocument={undefined}
-                        addDocTab={returnFalse}
-                        pinToPres={emptyFunction}
-                        onClick={undefined}
-                        removeDocument={undefined}
-                        ScreenToLocalTransform={() => new Transform(-l, -(b + NumCast(this._inkToTextDoc?.selectedIndex, 0) * 25), 1)}
-                        ContentScaling={returnOne}
-                        PanelWidth={() => 300}
-                        PanelHeight={() => 300}
-                        renderDepth={0}
-                        backgroundColor={returnEmptyString}
-                        focus={emptyFunction}
-                        parentActive={returnTrue}
-                        whenActiveChanged={emptyFunction}
-                        bringToFront={emptyFunction}
-                        ContainingCollectionView={undefined}
-                        ContainingCollectionDoc={undefined}
-                        zoomToScale={emptyFunction}
-                        getScale={returnOne}
-                    />;
-                    </div>
-                <div className="shadow" style={{ height: `calc(100% - 25px - ${NumCast(this._inkToTextDoc!.selectedIndex) * 25}px)` }}>
-                </div>
-            </div>)
-    }
-
-    private get inkToTextSuggestions() {
-        if (this._inkToTextDoc && this._possibilities.length) {
-            return (
-                this.suggestionContent
-            );
-        }
-        return null;
-    }
-
     render() {
         trace();
         return (
@@ -618,8 +562,8 @@ export default class GestureOverlay extends Touchable {
 
                 <div className="clipboardDoc-cont" style={{
                     transform: `translate(${this._thumbX}px, ${(this._thumbY ?? 0) - this.height}px)`,
-                    height: this.height,
-                    width: this.height,
+                    height: this.height * 2,
+                    width: this.height * 2,
                     pointerEvents: this._clipboardDoc ? "unset" : "none",
                     touchAction: this._clipboardDoc ? "unset" : "none",
                 }}>
@@ -627,14 +571,14 @@ export default class GestureOverlay extends Touchable {
                 </div>
                 <div className="filter-cont" style={{
                     transform: `translate(${this._thumbX}px, ${(this._thumbY ?? 0) - this.height}px)`,
-                    height: this.height,
-                    width: this.height,
+                    height: this.height * 2,
+                    width: this.height * 2,
                     pointerEvents: "none",
                     touchAction: "none",
                     display: this.showBounds ? "unset" : "none",
                 }}>
                 </div>
-                {this.inkToTextSuggestions}
+                <TouchScrollableMenu options={this._possibilities} bounds={this.svgBounds} selectedIndex={this._selectedIndex} x={this._menuX} y={this._menuY} />
             </div>);
     }
 }
@@ -666,6 +610,5 @@ Scripting.addGlobal(function resetPen() {
     });
 });
 Scripting.addGlobal(function createText(text: any, x: any, y: any) {
-    console.log("creating");
     GestureOverlay.Instance.dispatchGesture("text", [{ X: x, Y: y }], text);
 });
\ 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 17139d9d9..2b160f2ff 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -984,6 +984,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             </CollectionFreeFormViewPannableContents>
         </MarqueeView>;
     }
+
     @computed get contentScaling() {
         if (this.props.annotationsKey) return 0;
         const hscale = this.nativeHeight ? this.props.PanelHeight() / this.nativeHeight : 1;
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
index 71f265484..db4b674b5 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
@@ -11,6 +11,7 @@ export default class MarqueeOptionsMenu extends AntimodeMenu {
     public createCollection: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction;
     public delete: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction;
     public summarize: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction;
+    public inkToText: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction;
     public showMarquee: () => void = unimplementedFunction;
     public hideMarquee: () => void = unimplementedFunction;
 
@@ -43,6 +44,13 @@ export default class MarqueeOptionsMenu extends AntimodeMenu {
                 onPointerDown={this.delete}>
                 <FontAwesomeIcon icon="trash-alt" size="lg" />
             </button>,
+            <button
+                className="antimodeMenu-button"
+                title="Change to Text"
+                key="inkToText"
+                onPointerDown={this.inkToText}>
+                <FontAwesomeIcon icon="font" size="lg" />
+            </button>,
         ];
         return this.getElement(buttons);
     }
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index ef2fc2ad1..112df8f05 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -1,12 +1,12 @@
 import { action, computed, observable } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, DocListCast } from "../../../../new_fields/Doc";
+import { Doc, DocListCast, DataSym, WidthSym, HeightSym } from "../../../../new_fields/Doc";
 import { InkField } from "../../../../new_fields/InkField";
 import { List } from "../../../../new_fields/List";
 import { listSpec } from "../../../../new_fields/Schema";
 import { SchemaHeaderField } from "../../../../new_fields/SchemaHeaderField";
 import { ComputedField } from "../../../../new_fields/ScriptField";
-import { Cast, NumCast, StrCast } from "../../../../new_fields/Types";
+import { Cast, NumCast, StrCast, FieldValue } from "../../../../new_fields/Types";
 import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils";
 import { Utils } from "../../../../Utils";
 import { Docs } from "../../../documents/Documents";
@@ -19,6 +19,7 @@ import "./MarqueeView.scss";
 import React = require("react");
 import MarqueeOptionsMenu from "./MarqueeOptionsMenu";
 import { SubCollectionViewProps } from "../CollectionSubView";
+import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
 
 interface MarqueeViewProps {
     getContainerTransform: () => Transform;
@@ -204,6 +205,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
             MarqueeOptionsMenu.Instance.createCollection = this.collection;
             MarqueeOptionsMenu.Instance.delete = this.delete;
             MarqueeOptionsMenu.Instance.summarize = this.summary;
+            MarqueeOptionsMenu.Instance.inkToText = this.syntaxHighlight;
             MarqueeOptionsMenu.Instance.showMarquee = this.showMarquee;
             MarqueeOptionsMenu.Instance.hideMarquee = this.hideMarquee;
             MarqueeOptionsMenu.Instance.jumpTo(e.clientX, e.clientY);
@@ -361,6 +363,29 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         this.hideMarquee();
     }
 
+    @action
+    syntaxHighlight = (e: KeyboardEvent | React.PointerEvent | undefined) => {
+        const selected = this.marqueeSelect(false);
+        if (e instanceof KeyboardEvent ? e.key === "i" : true) {
+            const inks = selected.filter(s => s.proto?.type === "ink");
+            const sets = selected.filter(s => s.proto?.type === "text")
+            const inkFields = inks.map(i => FieldValue(Cast(i.data, InkField)));
+            CognitiveServices.Inking.Appliers.InterpretStrokes(inkFields.filter(i => i instanceof InkField).map(i => i!.inkData)).then((results) => {
+                const wordResults = results.filter((r: any) => r.category === "inkWord");
+                console.log(wordResults);
+                for (const word of wordResults) {
+                    const indices: number[] = word.strokeIds;
+                    const r = Math.floor(Math.random() * 256);
+                    const g = Math.floor(Math.random() * 256);
+                    const b = Math.floor(Math.random() * 256);
+                    indices.forEach(i => {
+                        inks[i].color = `rgb(${r}, ${g}, ${b})`;
+                    })
+                }
+            });
+        }
+    }
+
     @action
     summary = (e: KeyboardEvent | React.PointerEvent | undefined) => {
         const bounds = this.Bounds;
-- 
cgit v1.2.3-70-g09d2


From 593016303351f365d6ae13413b72d77495ea6a03 Mon Sep 17 00:00:00 2001
From: Stanley Yip <stanley_yip@brown.edu>
Date: Sat, 8 Feb 2020 14:35:20 -0500
Subject: syntax highlighting :)

---
 src/client/documents/Documents.ts                  |  1 +
 src/client/views/GestureOverlay.tsx                | 22 +++++--
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 74 +++++++++++++++++++++-
 .../collections/collectionFreeForm/MarqueeView.tsx | 24 +++++--
 src/pen-gestures/GestureUtils.ts                   |  2 +
 src/pen-gestures/ndollar.ts                        | 11 +++-
 6 files changed, 119 insertions(+), 15 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 1c13eb079..81a6ff802 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -139,6 +139,7 @@ export interface DocumentOptions {
     letterSpacing?: string; // is linear view expanded
     flexDirection?: "unset" | "row" | "column" | "row-reverse" | "column-reverse";
     selectedIndex?: number;
+    syntaxColor?: string; // can be applied to text for syntax highlighting all matches in the text
 }
 
 class EmptyBox {
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index e8262af1b..0b77b4b86 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -50,7 +50,7 @@ export default class GestureOverlay extends Touchable {
     @observable private _clipboardDoc?: JSX.Element;
     @observable private _possibilities: JSX.Element[] = [];
 
-    @computed private get height(): number { return Math.max(this._pointerY && this._thumbY ? this._thumbY - this._pointerY : 300, 300); }
+    @computed private get height(): number { return 2 * Math.max(this._pointerY && this._thumbY ? this._thumbY - this._pointerY : 300, 300); }
     @computed private get showBounds() { return this.Tool !== ToolglassTools.None; }
 
     private _d1: Doc | undefined;
@@ -408,8 +408,8 @@ export default class GestureOverlay extends Touchable {
             const points = this._points.map(p => ({ X: p.X - B.left, Y: p.Y - B.top }));
 
             const initialPoint = this._points[0.];
-            const xInGlass = initialPoint.X > (this._thumbX ?? Number.MAX_SAFE_INTEGER) && initialPoint.X < (this._thumbX ?? Number.MAX_SAFE_INTEGER) + this.height;
-            const yInGlass = initialPoint.Y > (this._thumbY ?? Number.MAX_SAFE_INTEGER) - this.height && initialPoint.Y < (this._thumbY ?? Number.MAX_SAFE_INTEGER);
+            const xInGlass = initialPoint.X > (this._thumbX ?? Number.MAX_SAFE_INTEGER) && initialPoint.X < (this._thumbX ?? Number.MAX_SAFE_INTEGER) + (this.height);
+            const yInGlass = initialPoint.Y > (this._thumbY ?? Number.MAX_SAFE_INTEGER) - (this.height) && initialPoint.Y < (this._thumbY ?? Number.MAX_SAFE_INTEGER);
 
             if (this.Tool !== ToolglassTools.None && xInGlass && yInGlass) {
                 switch (this.Tool) {
@@ -450,6 +450,14 @@ export default class GestureOverlay extends Touchable {
                             this.dispatchGesture(GestureUtils.Gestures.Box);
                             actionPerformed = true;
                             break;
+                        case GestureUtils.Gestures.StartBracket:
+                            this.dispatchGesture(GestureUtils.Gestures.StartBracket);
+                            actionPerformed = true;
+                            break;
+                        case GestureUtils.Gestures.EndBracket:
+                            this.dispatchGesture(GestureUtils.Gestures.EndBracket);
+                            actionPerformed = true;
+                            break;
                         case GestureUtils.Gestures.Line:
                             actionPerformed = this.handleLineGesture();
                             break;
@@ -562,8 +570,8 @@ export default class GestureOverlay extends Touchable {
 
                 <div className="clipboardDoc-cont" style={{
                     transform: `translate(${this._thumbX}px, ${(this._thumbY ?? 0) - this.height}px)`,
-                    height: this.height * 2,
-                    width: this.height * 2,
+                    height: this.height,
+                    width: this.height,
                     pointerEvents: this._clipboardDoc ? "unset" : "none",
                     touchAction: this._clipboardDoc ? "unset" : "none",
                 }}>
@@ -571,8 +579,8 @@ export default class GestureOverlay extends Touchable {
                 </div>
                 <div className="filter-cont" style={{
                     transform: `translate(${this._thumbX}px, ${(this._thumbY ?? 0) - this.height}px)`,
-                    height: this.height * 2,
-                    width: this.height * 2,
+                    height: this.height,
+                    width: this.height,
                     pointerEvents: "none",
                     touchAction: "none",
                     display: this.showBounds ? "unset" : "none",
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 2b160f2ff..7b73050ab 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -9,7 +9,7 @@ import { Id } from "../../../../new_fields/FieldSymbols";
 import { InkTool, InkField, InkData } from "../../../../new_fields/InkField";
 import { createSchema, makeInterface } from "../../../../new_fields/Schema";
 import { ScriptField } from "../../../../new_fields/ScriptField";
-import { BoolCast, Cast, DateCast, NumCast, StrCast, ScriptCast } from "../../../../new_fields/Types";
+import { BoolCast, Cast, DateCast, NumCast, StrCast, ScriptCast, FieldValue } from "../../../../new_fields/Types";
 import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils";
 import { aggregateBounds, emptyFunction, intersectRect, returnOne, Utils } from "../../../../Utils";
 import { DocServer } from "../../../DocServer";
@@ -42,6 +42,8 @@ import { computedFn } from "mobx-utils";
 import { TraceMobx } from "../../../../new_fields/util";
 import { GestureUtils } from "../../../../pen-gestures/GestureUtils";
 import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
+import { RichTextField } from "../../../../new_fields/RichTextField";
+import { List } from "../../../../new_fields/List";
 
 library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload);
 
@@ -70,6 +72,9 @@ const PanZoomDocument = makeInterface(panZoomSchema, documentSchema, positionSch
 export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     private _lastX: number = 0;
     private _lastY: number = 0;
+    private _inkToTextStartX: number | undefined;
+    private _inkToTextStartY: number | undefined;
+    private _wordPalette: Map<string, string> = new Map<string, string>();
     private _clusterDistance: number = 75;
     private _hitCluster = false;
     private _layoutComputeReaction: IReactionDisposer | undefined;
@@ -411,11 +416,78 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 });
                 this.addDocument(Docs.Create.FreeformDocument(sel, { title: "nested collection", x: bounds.x, y: bounds.y, _width: bWidth, _height: bHeight, _panX: 0, _panY: 0 }));
                 sel.forEach(d => this.props.removeDocument(d));
+                e.stopPropagation();
+                break;
+            case GestureUtils.Gestures.StartBracket:
+                const start = this.getTransform().transformPoint(Math.min(...ge.points.map(p => p.X)), Math.min(...ge.points.map(p => p.Y)));
+                this._inkToTextStartX = start[0];
+                this._inkToTextStartY = start[1];
+                console.log("start");
+                break;
+            case GestureUtils.Gestures.EndBracket:
+                console.log("end");
+                if (this._inkToTextStartX && this._inkToTextStartY) {
+                    const end = this.getTransform().transformPoint(Math.max(...ge.points.map(p => p.X)), Math.max(...ge.points.map(p => p.Y)));
+                    const setDocs = this.getActiveDocuments().filter(s => s.proto?.type === "text" && s.color);
+                    const sets = setDocs.map((sd) => {
+                        return Cast(sd.data, RichTextField)?.Text as string;
+                    });
+                    if (sets.length && sets[0]) {
+                        this._wordPalette.clear();
+                        const colors = setDocs.map(sd => FieldValue(sd.color) as string);
+                        sets.forEach((st: string, i: number) => {
+                            const words = st.split(",");
+                            words.forEach(word => {
+                                this._wordPalette.set(word, colors[i]);
+                            });
+                        });
+                    }
+                    const inks = this.getActiveDocuments().filter(doc => {
+                        if (doc.type === "ink") {
+                            const l = NumCast(doc.x);
+                            const r = l + doc[WidthSym]();
+                            const t = NumCast(doc.y);
+                            const b = t + doc[HeightSym]();
+                            const pass = !(this._inkToTextStartX! > r || end[0] < l || this._inkToTextStartY! > b || end[1] < t);
+                            return pass;
+                        }
+                        return false;
+                    });
+                    const inkFields = inks.map(i => Cast(i.data, InkField));
+                    CognitiveServices.Inking.Appliers.InterpretStrokes(inkFields.filter(i => i instanceof InkField).map(i => i!.inkData)).then((results) => {
+                        const wordResults = results.filter((r: any) => r.category === "inkWord");
+                        console.log(wordResults);
+                        for (const word of wordResults) {
+                            const indices: number[] = word.strokeIds;
+                            indices.forEach(i => {
+                                const otherInks: Doc[] = [];
+                                indices.forEach(i2 => i2 !== i && otherInks.push(inks[i2]));
+                                inks[i].relatedInks = new List<Doc>(otherInks);
+                                const uniqueColors: string[] = [];
+                                Array.from(this._wordPalette.values()).forEach(c => uniqueColors.indexOf(c) === -1 && uniqueColors.push(c));
+                                inks[i].alternativeColors = new List<string>(uniqueColors);
+                                if (this._wordPalette.has(word.recognizedText)) {
+                                    inks[i].color = this._wordPalette.get(word.recognizedText);
+                                }
+                                else {
+                                    for (const alt of word.alternates) {
+                                        if (this._wordPalette.has(alt.recognizedString)) {
+                                            inks[i].color = this._wordPalette.get(alt.recognizedString);
+                                            break;
+                                        }
+                                    }
+                                }
+                            });
+                        }
+                    });
+                    this._inkToTextStartX = end[0];
+                }
                 break;
             case GestureUtils.Gestures.Text:
                 if (ge.text) {
                     const B = this.getTransform().transformPoint(ge.points[0].X, ge.points[0].Y);
                     this.addDocument(Docs.Create.TextDocument(ge.text, { title: ge.text, x: B[0], y: B[1] }));
+                    e.stopPropagation();
                 }
         }
     }
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 112df8f05..19a71012a 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -20,6 +20,7 @@ import React = require("react");
 import MarqueeOptionsMenu from "./MarqueeOptionsMenu";
 import { SubCollectionViewProps } from "../CollectionSubView";
 import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
+import { RichTextField } from "../../../../new_fields/RichTextField";
 
 interface MarqueeViewProps {
     getContainerTransform: () => Transform;
@@ -368,18 +369,29 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         const selected = this.marqueeSelect(false);
         if (e instanceof KeyboardEvent ? e.key === "i" : true) {
             const inks = selected.filter(s => s.proto?.type === "ink");
-            const sets = selected.filter(s => s.proto?.type === "text")
-            const inkFields = inks.map(i => FieldValue(Cast(i.data, InkField)));
+            const setDocs = selected.filter(s => s.proto?.type === "text" && s.color);
+            const sets = setDocs.map((sd) => {
+                return Cast(sd.data, RichTextField)?.Text as string;
+            });
+            const colors = setDocs.map(sd => FieldValue(sd.color) as string);
+            const wordToColor = new Map<string, string>();
+            console.log(sets);
+            sets.forEach((st: string, i: number) => {
+                const words = st.split(",");
+                words.forEach(word => {
+                    wordToColor.set(word, colors[i]);
+                });
+            });
+            const inkFields = inks.map(i => Cast(i.data, InkField));
             CognitiveServices.Inking.Appliers.InterpretStrokes(inkFields.filter(i => i instanceof InkField).map(i => i!.inkData)).then((results) => {
                 const wordResults = results.filter((r: any) => r.category === "inkWord");
                 console.log(wordResults);
                 for (const word of wordResults) {
                     const indices: number[] = word.strokeIds;
-                    const r = Math.floor(Math.random() * 256);
-                    const g = Math.floor(Math.random() * 256);
-                    const b = Math.floor(Math.random() * 256);
                     indices.forEach(i => {
-                        inks[i].color = `rgb(${r}, ${g}, ${b})`;
+                        if (wordToColor.has(word.recognizedText)) {
+                            inks[i].color = wordToColor.get(word.recognizedText);
+                        }
                     })
                 }
             });
diff --git a/src/pen-gestures/GestureUtils.ts b/src/pen-gestures/GestureUtils.ts
index 4e3493c1c..f14c573c3 100644
--- a/src/pen-gestures/GestureUtils.ts
+++ b/src/pen-gestures/GestureUtils.ts
@@ -35,6 +35,8 @@ export namespace GestureUtils {
     export enum Gestures {
         Box = "box",
         Line = "line",
+        StartBracket = "startbracket",
+        EndBracket = "endbracket",
         Stroke = "stroke",
         Scribble = "scribble",
         Text = "text"
diff --git a/src/pen-gestures/ndollar.ts b/src/pen-gestures/ndollar.ts
index 9e15ada2d..643d58591 100644
--- a/src/pen-gestures/ndollar.ts
+++ b/src/pen-gestures/ndollar.ts
@@ -142,7 +142,7 @@ export class Result {
 //
 // NDollarRecognizer constants
 //
-const NumMultistrokes = 2;
+const NumMultistrokes = 4;
 const NumPoints = 96;
 const SquareSize = 250.0;
 const OneDThreshold = 0.25; // customize to desired gesture set (usually 0.20 - 0.35)
@@ -178,6 +178,15 @@ export class NDollarRecognizer {
         this.Multistrokes[1] = new Multistroke(GestureUtils.Gestures.Line, useBoundedRotationInvariance, new Array(
             new Array(new Point(12, 347), new Point(119, 347))
         ));
+        this.Multistrokes[2] = new Multistroke(GestureUtils.Gestures.StartBracket, useBoundedRotationInvariance, new Array(
+            // new Array(new Point(145, 20), new Point(30, 21), new Point(34, 150))
+            new Array(new Point(31, 25), new Point(145, 20), new Point(31, 25), new Point(34, 150))
+        ));
+        this.Multistrokes[3] = new Multistroke(GestureUtils.Gestures.EndBracket, useBoundedRotationInvariance, new Array(
+            // new Array(new Point(150, 21), new Point(149, 150), new Point(26, 152))
+            // new Array(new Point(150, 150), new Point(150, 0), new Point(150, 150), new Point(0, 150))
+            new Array(new Point(10, 100), new Point(50, 12), new Point(100, 103))
+        ));
 
         //
         // PREDEFINED STROKES
-- 
cgit v1.2.3-70-g09d2


From 79e1323acd0d0f95d08a09cefce908e35d0e7558 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Sat, 8 Feb 2020 17:32:33 -0500
Subject: initial commit, pending png read error bug fix

---
 .gitignore                                         |   2 +-
 src/client/documents/Documents.ts                  |  45 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   2 +-
 src/scraping/buxton/final/BuxtonImporter.ts        | 316 ++++++++++++++
 src/scraping/buxton/json/buxton.json               | 116 -----
 src/scraping/buxton/json/incomplete.json           | 468 ---------------------
 src/scraping/buxton/node_scraper.ts                | 256 -----------
 src/server/ApiManagers/UtilManager.ts              |  38 +-
 .../authentication/models/current_user_utils.ts    |   2 +-
 9 files changed, 374 insertions(+), 871 deletions(-)
 create mode 100644 src/scraping/buxton/final/BuxtonImporter.ts
 delete mode 100644 src/scraping/buxton/json/buxton.json
 delete mode 100644 src/scraping/buxton/json/incomplete.json
 delete mode 100644 src/scraping/buxton/node_scraper.ts

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

diff --git a/.gitignore b/.gitignore
index b88fed833..fbf8668ca 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,7 +6,7 @@ ClientUtils.ts
 solr-8.3.1/server/logs/
 solr-8.3.1/server/solr/dash/data/tlog/*
 solr-8.3.1/server/solr/dash/data/index/*
-src/scraping/buxton/source/
+src/scraping/buxton/final/source/
 src/server/public/files/
 src/scraping/acm/package-lock.json
 src/server/session_manager/logs/**/*.log
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index d647b34e6..f05bb3736 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -54,6 +54,7 @@ import { InkField } from "../../new_fields/InkField";
 import { InkingControl } from "../views/InkingControl";
 import { RichTextField } from "../../new_fields/RichTextField";
 import { Networking } from "../Network";
+import { extname } from "path";
 const requestImageSize = require('../util/request-image-size');
 const path = require('path');
 
@@ -341,8 +342,34 @@ export namespace Docs {
      */
     export namespace Create {
 
-        export async function Buxton() {
-            console.log(await Networking.FetchFromServer("/newBuxton"));
+        export function Buxton() {
+            const loading = new Doc;
+            loading.title = "Please wait for the import script...";
+            const parent = TreeDocument([loading], {
+                title: "The Buxton Collection",
+                _width: 400,
+                _height: 400
+            });
+            Networking.FetchFromServer("/buxton").then(response => {
+                parent.data = new List<Doc>();
+                const devices = JSON.parse(response);
+                if (!Array.isArray(devices)) {
+                    alert("Improper Buxton import formatting!");
+                    return;
+                }
+                devices.forEach(device => {
+                    const { __images } = device;
+                    delete device.__images;
+                    const { ImageDocument, StackingDocument } = Docs.Create;
+                    if (Array.isArray(__images)) {
+                        const deviceImages = __images.map((url, i) => ImageDocument(url, { title: `image${i}.${extname(url)}` }));
+                        const doc = StackingDocument(deviceImages, { title: device.title });
+                        Docs.Get.DocumentHierarchyFromJson(device, undefined, doc);
+                        Doc.AddDocToList(parent, "data", doc);
+                    }
+                });
+            });
+            return parent;
         }
 
         Scripting.addGlobal(Buxton);
@@ -628,7 +655,7 @@ export namespace Docs {
          * or the result of any JSON.parse() call.
          * @param title an optional title to give to the highest parent document in the hierarchy
          */
-        export function DocumentHierarchyFromJson(input: any, title?: string): Opt<Doc> {
+        export function DocumentHierarchyFromJson(input: any, title?: string, appendToTarget?: Doc): Opt<Doc> {
             if (input === undefined || input === null || ![...primitives, "object"].includes(typeof input)) {
                 return undefined;
             }
@@ -638,7 +665,7 @@ export namespace Docs {
             }
             let converted: Doc;
             if (typeof parsed === "object" && !(parsed instanceof Array)) {
-                converted = convertObject(parsed, title);
+                converted = convertObject(parsed, title, appendToTarget);
             } else {
                 (converted = new Doc).json = toField(parsed);
             }
@@ -653,12 +680,12 @@ export namespace Docs {
          * @returns the object mapped from JSON to field values, where each mapping 
          * might involve arbitrary recursion (since toField might itself call convertObject)
          */
-        const convertObject = (object: any, title?: string): Doc => {
-            const target = new Doc();
+        const convertObject = (object: any, title?: string, target?: Doc): Doc => {
+            const resolved = target ?? new Doc;
             let result: Opt<Field>;
-            Object.keys(object).map(key => (result = toField(object[key], key)) && (target[key] = result));
-            title && !target.title && (target.title = title);
-            return target;
+            Object.keys(object).map(key => (result = toField(object[key], key)) && (resolved[key] = result));
+            title && !resolved.title && (resolved.title = title);
+            return resolved;
         };
 
         /**
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 2518a4a55..66e4ef1b0 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -807,7 +807,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     doFreeformLayout(poolData: Map<string, any>) {
         const layoutDocs = this.childLayoutPairs.map(pair => pair.layout);
         const initResult = this.Document.arrangeInit && this.Document.arrangeInit.script.run({ docs: layoutDocs, collection: this.Document }, console.log);
-        let state = initResult && initResult.success ? initResult.result.scriptState : undefined;
+        const state = initResult && initResult.success ? initResult.result.scriptState : undefined;
         const elements = initResult && initResult.success ? this.viewDefsToJSX(initResult.result.views) : [];
 
         this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map((pair, i) => {
diff --git a/src/scraping/buxton/final/BuxtonImporter.ts b/src/scraping/buxton/final/BuxtonImporter.ts
new file mode 100644
index 000000000..804d65d74
--- /dev/null
+++ b/src/scraping/buxton/final/BuxtonImporter.ts
@@ -0,0 +1,316 @@
+import { readdirSync, writeFile, mkdirSync, createWriteStream, createReadStream, unlinkSync } from "fs";
+import * as path from "path";
+import { red, cyan, yellow, green } from "colors";
+import { Utils } from "../../../Utils";
+import rimraf = require("rimraf");
+const StreamZip = require('node-stream-zip');
+import * as sharp from 'sharp';
+import { SizeSuffix, DashUploadUtils, InjectSize } from "../../../server/DashUploadUtils";
+import { AcceptibleMedia } from "../../../server/SharedMediaTypes";
+
+export interface DeviceDocument {
+    title: string;
+    shortDescription: string;
+    longDescription: string;
+    company: string;
+    year: number;
+    originalPrice: number;
+    degreesOfFreedom: number;
+    dimensions: string;
+    primaryKey: string;
+    secondaryKey: string;
+}
+
+interface DocumentContents {
+    body: string;
+    images: string[];
+}
+
+interface AnalysisResult {
+    device?: DeviceDocument;
+    errors?: any;
+}
+
+type Converter<T> = (raw: string) => { transformed?: T, error?: string };
+
+interface Processor<T> {
+    exp: RegExp;
+    matchIndex?: number;
+    transformer?: Converter<T>;
+}
+
+namespace Utilities {
+
+    export function numberValue(raw: string) {
+        const transformed = Number(raw);
+        if (isNaN(transformed)) {
+            return { error: `${transformed} cannot be parsed to a numeric value.` };
+        }
+        return { transformed };
+    }
+
+    export function collectUniqueTokens(raw: string) {
+        return { transformed: Array.from(new Set(raw.replace(/,|\s+and\s+/g, " ").split(/\s+/).map(token => token.toLowerCase().trim()))).map(capitalize).sort() };
+    }
+
+    export function correctSentences(raw: string) {
+        raw = raw.replace(/\./g, ". ").replace(/\:/g, ": ").replace(/\,/g, ", ").replace(/\?/g, "? ").trimRight();
+        raw = raw.replace(/\s{2,}/g, " ");
+        return { transformed: raw };
+    }
+
+    export function tryGetValidCapture(matches: RegExpExecArray | null, matchIndex: number): string | undefined {
+        let captured: string;
+        if (!matches || !(captured = matches[matchIndex])) {
+            return undefined;
+        }
+        const lower = captured.toLowerCase();
+        if (/to come/.test(lower)) {
+            return undefined;
+        }
+        if (lower.includes("xxx")) {
+            return undefined;
+        }
+        if (!captured.toLowerCase().replace(/[….\s]+/g, "").length) {
+            return undefined;
+        }
+        return captured;
+    }
+
+    export function capitalize(word: string): string {
+        const clean = word.trim();
+        if (!clean.length) {
+            return word;
+        }
+        return word.charAt(0).toUpperCase() + word.slice(1);
+    }
+
+}
+
+const RegexMap = new Map<keyof DeviceDocument, Processor<any>>([
+    ["title", {
+        exp: /contact\s+(.*)Short Description:/
+    }],
+    ["company", {
+        exp: /Company:\s+([^\|]*)\s+\|/,
+        transformer: (raw: string) => ({ transformed: raw.replace(/\./g, "") })
+    }],
+    ["year", {
+        exp: /Year:\s+([^\|]*)\s+\|/,
+        transformer: Utilities.numberValue
+    }],
+    ["primaryKey", {
+        exp: /Primary:\s+(.*)(Secondary|Additional):/,
+        transformer: Utilities.collectUniqueTokens
+    }],
+    ["secondaryKey", {
+        exp: /(Secondary|Additional):\s+([^\{\}]*)Links/,
+        transformer: Utilities.collectUniqueTokens,
+        matchIndex: 2
+    }],
+    ["originalPrice", {
+        exp: /Original Price \(USD\)\:\s+\$([0-9\.]+)/,
+        transformer: Utilities.numberValue
+    }],
+    ["degreesOfFreedom", {
+        exp: /Degrees of Freedom:\s+([0-9]+)/,
+        transformer: Utilities.numberValue
+    }],
+    ["dimensions", {
+        exp: /Dimensions\s+\(L x W x H\):\s+([0-9\.]+\s+x\s+[0-9\.]+\s+x\s+[0-9\.]+\s\([A-Za-z]+\))/,
+        transformer: (raw: string) => {
+            const [length, width, group] = raw.split(" x ");
+            const [height, unit] = group.split(" ");
+            return {
+                transformed: {
+                    dim_length: Number(length),
+                    dim_width: Number(width),
+                    dim_height: Number(height),
+                    dim_unit: unit.replace(/[\(\)]+/g, "")
+                }
+            };
+        }
+    }],
+    ["shortDescription", {
+        exp: /Short Description:\s+(.*)Bill Buxton[’']s Notes/,
+        transformer: Utilities.correctSentences
+    }],
+    ["longDescription", {
+        exp: /Bill Buxton[’']s Notes(.*)Device Details/,
+        transformer: Utilities.correctSentences
+    }],
+]);
+
+const outDir = path.resolve(__dirname, "json");
+const imageDir = path.resolve(__dirname, "../../../server/public/files/images/buxton");
+const successOut = "buxton.json";
+const failOut = "incomplete.json";
+const deviceKeys = Array.from(RegexMap.keys());
+
+export default async function executeImport() {
+    [outDir, imageDir].forEach(dir => {
+        rimraf.sync(dir);
+        mkdirSync(dir);
+    });
+    return parseFiles();
+}
+
+async function parseFiles(): Promise<DeviceDocument[]> {
+    const sourceDirectory = path.resolve(`${__dirname}/source`);
+
+    const candidates = readdirSync(sourceDirectory).filter(file => file.endsWith(".doc") || file.endsWith(".docx")).map(file => `${sourceDirectory}/${file}`);
+    const imported = await Promise.all(candidates.map(async path => ({ path, body: await extractFileContents(path) })));
+    const data = imported.map(({ path, body }) => analyze(path, body));
+
+    const masterDevices: DeviceDocument[] = [];
+    const masterErrors: any[] = [];
+
+    data.forEach(({ device, errors }) => {
+        if (device) {
+            masterDevices.push(device);
+        } else {
+            masterErrors.push(errors);
+        }
+    });
+    const total = candidates.length;
+    if (masterDevices.length + masterErrors.length !== total) {
+        throw new Error(`Encountered a ${masterDevices.length} to ${masterErrors.length} mismatch in device / error split!`);
+    }
+
+    console.log();
+    await writeOutputFile(successOut, masterDevices, total, true);
+    await writeOutputFile(failOut, masterErrors, total, false);
+    console.log();
+
+    return masterDevices;
+}
+
+async function extractFileContents(pathToDocument: string): Promise<{ body: string, images: string[] }> {
+    const zip = new StreamZip({ file: pathToDocument, storeEntries: true });
+    const contents = await new Promise<string>((resolve, reject) => {
+        zip.on('ready', () => {
+            let body = "";
+            zip.stream("word/document.xml", (error: any, stream: any) => {
+                if (error) {
+                    reject(error);
+                }
+                stream.on('data', (chunk: any) => body += chunk.toString());
+                stream.on('end', () => resolve(body));
+            });
+        });
+    });
+    const images = (await writeImages(zip)).map(name => `http://localhost:1050/files/images/buxton/${name}`);
+    zip.close();
+    let body = "";
+    const components = contents.toString().split('<w:t');
+    for (const component of components) {
+        const tags = component.split('>');
+        const content = tags[1].replace(/<.*$/, "");
+        body += content;
+    }
+    return { body, images };
+}
+
+async function writeImages(zip: any): Promise<string[]> {
+    const entryNames = Object.values<any>(zip.entries()).map(({ name }) => name);
+    const resolved: { mediaPath: string, ext: string }[] = [];
+    let initialWritePath: string;
+    entryNames.forEach(name => {
+        const matches = /^word\/media\/\w+\.(jpeg|jpg|png|gif)/.exec(name);
+        matches && resolved.push({ mediaPath: name, ext: matches[1] });
+    });
+    return Promise.all(resolved.map(async ({ mediaPath, ext }) => {
+        const outName = `upload_${Utils.GenerateGuid()}.${ext}`;
+        const initialWrite = await new Promise<string>((resolve, reject) => {
+            zip.stream(mediaPath, (error: any, stream: any) => {
+                if (error) {
+                    console.error(error);
+                    return reject(error);
+                }
+                initialWritePath = `${imageDir}/${outName}`;
+                const writeStream = createWriteStream(initialWritePath);
+                stream.on('end', () => resolve(outName));
+                stream.on('error', reject);
+                stream.pipe(writeStream);
+            });
+        });
+        const resizers = [
+            { resizer: sharp().rotate(), suffix: SizeSuffix.Original },
+            ...Object.values(DashUploadUtils.Sizes).map(size => ({
+                resizer: sharp().resize(size.width, undefined, { withoutEnlargement: true }).rotate(),
+                suffix: size.suffix
+            }))
+        ];
+        const { pngs, jpgs } = AcceptibleMedia;
+        if (pngs.includes(ext)) {
+            resizers.forEach(element => element.resizer = element.resizer.png());
+        } else if (jpgs.includes(ext)) {
+            resizers.forEach(element => element.resizer = element.resizer.jpeg());
+        }
+        for (const { resizer, suffix } of resizers) {
+            await new Promise<void>(resolve => {
+                const filename = InjectSize(outName, suffix);
+                console.log(filename);
+                createReadStream(initialWritePath).pipe(resizer).pipe(createWriteStream(`${imageDir}/${filename}`))
+                    .on('close', resolve)
+                    .on('error', error => {
+                        console.log(red(error));
+                        resolve();
+                    });
+            });
+        }
+        unlinkSync(initialWritePath);
+        return initialWrite;
+    }));
+}
+
+function analyze(pathToDocument: string, { body, images }: DocumentContents): AnalysisResult {
+    const filename = path.basename(pathToDocument).replace("Bill_Notes_", "");
+    console.log(`Parsing ${filename}...`);
+
+    const device: any = {};
+    const errors: any = { filename };
+
+    for (const key of deviceKeys) {
+        const { exp, transformer, matchIndex } = RegexMap.get(key)!;
+        const matches = exp.exec(body);
+
+        let captured = Utilities.tryGetValidCapture(matches, matchIndex ?? 1);
+        if (!captured) {
+            errors[key] = `ERR__${key.toUpperCase()}__: outer match ${matches === null ? "wasn't" : "was"} captured.`;
+            continue;
+        }
+
+        captured = captured.replace(/\s{2,}/g, " ");
+        if (transformer) {
+            const { error, transformed } = transformer(captured);
+            if (error) {
+                errors[key] = `__ERR__${key.toUpperCase()}__TRANSFORM__: ${error}`;
+                continue;
+            }
+            captured = transformed;
+        }
+
+        device[key] = captured;
+    }
+
+    const errorKeys = Object.keys(errors);
+    if (errorKeys.length > 1) {
+        console.log(red(`\n@ ${cyan(filename.toUpperCase())}...`));
+        errorKeys.forEach(key => key !== "filename" && console.log(red(errors[key])));
+        return { errors };
+    }
+
+    device.__images = images;
+
+    return { device };
+}
+
+async function writeOutputFile(relativePath: string, data: any[], total: number, success: boolean) {
+    console.log(yellow(`Encountered ${data.length} ${success ? "valid" : "invalid"} documents out of ${total} candidates. Writing ${relativePath}...`));
+    return new Promise<void>((resolve, reject) => {
+        const destination = path.resolve(outDir, relativePath);
+        const contents = JSON.stringify(data, undefined, 4);
+        writeFile(destination, contents, err => err ? reject(err) : resolve());
+    });
+}
\ No newline at end of file
diff --git a/src/scraping/buxton/json/buxton.json b/src/scraping/buxton/json/buxton.json
deleted file mode 100644
index 8371f2cf2..000000000
--- a/src/scraping/buxton/json/buxton.json
+++ /dev/null
@@ -1,116 +0,0 @@
-[
-    {
-        "title": "3Dconnexion CadMan 3D Motion Controller",
-        "company": "3Dconnexion",
-        "year": 2003,
-        "primaryKey": [
-            "Joystick"
-        ],
-        "secondaryKey": [
-            "Isometric",
-            "Joystick"
-        ],
-        "originalPrice": 399,
-        "degreesOfFreedom": 6,
-        "dimensions": {
-            "length": 175,
-            "width": 122,
-            "height": 43,
-            "unit": "mm"
-        },
-        "shortDescription": "The CadMan is a 6 degree of freedom (DOF) joystick controller. It represented a significant step towards making this class of is controller affordable. It was mainly directed at 3D modelling and animation and was a “next generation” of the Magellan controller, which is also in the collection.",
-        "longDescription": "The CadMan is a 6 degree of freedom (DOF) joystick controller. It represented a significant step towards making this class of is controller more affordable. It was mainly directed at 3D modelling and animation and was a “next generation” of the Magellan/SpaceMouse controller, which is also in the collection. Like the Magellan, this is an isometric rate-control joystick. That is, it rests in a neutral central position, not sending and signal. When a force is applied to it, it emits a signal indicating the direction and strength of that force. This signal can then be mapped to a parameter of a selected object, such as a sphere, and – for example – cause that sphere to rotate for as long as, and as fast as, and in the direction determined by, the duration, force, and direction of the applied force. When released, it springs back to neutral position. Note that the force does not need to be directed along a single DOF. In fact, a core feature of the device is that one can simultaneously and independently apply force that asserts control over more than one DOF, and furthermore, vary those forces dynamically. As an aid to understanding, let me walk through some of the underlying concepts at play here by using a more familiar device: a computer mouse. If you move a mouse in a forward/backward direction, the mouse pointer on the screen moves between the screen’s top and bottom. If you think of the screen as a piece of graph paper, that corresponds to moving along the “Y” axis. That is one degree of freedom. On the other hand, you could move the mouse left and right, which causes the mouse to move between the left and right side of the screen. That would correspond to moving along the graph paper’s “X” axis – a second degree of freedom. Yet, you can also move the mouse diagonally. This is an example of independently controlling two degrees of freedom. Now imagine that if you lifted your mouse off your desktop, that your computer could dynamically sense its height as you did so. This would constitute a “flying mouse” (the literal translation of the German word for a “Bat”, which Canadian colleague, Colin Ware, applied to just such a mouse which he built in 1988). If you moved your Bat vertically up and down, perpendicular to the desktop, you would be controlling movement along the “Z” axis - a third degree of freedom. Having already seen that we can move a mouse diagonally, we have established that we need not be constrained to only moving along a single axis. That extends to the movement of our Bat and movement along the “Z” axis. We can control our hand movement in dependently in any or all directions in 3D space. But how does one reconcile the fact that we call the CadMan a “3D controller, and yet also describe it as having 6 degrees of freedom? After all, the example this far demonstrates that our Bat, as described thus far, has freedom on movement in 3 Dimensions. While true, we can extend our example to prove that that freedom to move in 3D is also highly constrained. To demonstrate this, move your hand in 3D space on and above your desktop. However, do so keeping your palm flat, parallel to the desktop with your fingers pointing directly forward. In so doing, you are still moving in 3D. Now, while moving, twist your wrist, while moving the hand, such that your palm is alternatively exposed to the left and right side. This constitutes rotation around the “Y” axis. A fourth DOF. Now add a waving motion to your hand, as if it were a paper airplane diving up and down, while also rocking left and right. But keep your fingers pointing forward. You have now added a fifth DOF, rotation around the “X” axis. Finally, add a twist to your wrist so that your fingers are no longer constrained to pointing forward. This is the sixth degree of freedom, rotation around the “Z” axis. Now don’t be fooled, this exercise could continue. We are not restricted to even six DOF. Imagine doing the above, but where the movement and rotations are measured relative to the Bat’s position and orientation, rather than to the holding/controlling hand, per se. One could imagine the Bat having a scroll wheel, like the one on most mice today. Furthermore, while flying your Bat around in 3D, that wheel could easily be rolled in either forward or backward, and thereby control the size of whatever was being controlled. Hence, with one hand we could assert simultaneous and independent control over 7 DOF in 3D space. This exercise has two intended take-aways. The first is a better working understanding between the notion of Degree of Freedom (DOF) and Dimension in space. Hopefully, the confusion frequently encountered when 3D and 6DOF are used in close context, can now be eliminated. Second, is that, with appropriate sensing, the human hand is capable of exercising control over far more degrees of freedom that six. And if we use the two hands together, the potential number of DOF that one can control goes even further. Finally, it is important to add one more take-away – one which both emerges from, and is frequently encountered when discussing, the previous two. That is, do not equate exercising simultaneous control over a high number of DOF with consciously doing the same number of different things all at once. The example that used to be thrown at me when I started talking about coordinated simultaneously bi-manual action went along the lines of, “Psychology tells us that we cannot do multiple things at once, for example, simultaneously tapping your head and rubbing your stomach. ”Well, first, I can tap my head with one hand while rubbing my stomach with the other. But that is not the point. The whole essence of skill – motor-sensory and cognitive – is “chunking” or task integration. When one appears to be doing many different things at once, if they are skilled, they are consciously doing only one thing. Playing a chord on the piano, for example, or skiing down the hill. Likewise, in flying your imaginary BAT in the previous exercise with the scroll wheel, were you doing 7 things at once, or one thing with 7 DOF? And if you had a Bat in each hand, does that mean you are now doing 14 things at once, or are you doing one thing with 14 DOF? Let me provide a different way of answering this question: if you have ever played air guitar, or “conducted” the orchestra that you are listening to on the radio, you are exercising control over more than 14 DOF. And you are doing exactly what I just said, “playing air guitar” or “conducting an orchestra”. One thing – at the conscious level, which is what matters – despite almost any one thing being able to be deconstructed into hundreds of sub-tasks. As I said the essence of skill: aggregation, or chunking. What is most important for both tool designers and users to be mindful of, is the overwhelming influence that our choice and design of tools impacts the degree to which such integration or chunking can take place. The degree to which the tool matches both the skills that we have already acquired through a lifetime of living in the everyday world, and the demands of the intended task, the more seamless that task can be performed, the more “natural” it will feel, and the less learning will be required. In my experience, it brought particular value when used bimanually, in combination with a mouse, where the preferred hand performed conventional pointing, selection and dragging tasks, while the non-preferred hand could manipulate the parameters of the thing being selected. First variation of the since the 2001 formation of 3Dconnextion. The CadMan came in 5 colours: smoke, orange, red, blue and green. See the notes for the LogiCad3D Magellan for more details on this class of device. It is the “parent” of the CadMan, and despite the change in company name, it comes from the same team."
-    },
-    {
-        "title": "Adesso ACK-540UB USB Mini-Touch Keyboard with Touchpad",
-        "company": "Adesso",
-        "year": 2005,
-        "primaryKey": [
-            "Keyboard"
-        ],
-        "secondaryKey": [
-            "Pad",
-            "Touch"
-        ],
-        "originalPrice": 59.95,
-        "degreesOfFreedom": 2,
-        "dimensions": {
-            "length": 287,
-            "width": 140,
-            "height": 35.5,
-            "unit": "mm"
-        },
-        "shortDescription": "The Mini-Touch Keyboard is a surprisingly rare device: a laptop-style, small-footprint keyboard with a centrally mounted touch-pad. .",
-        "longDescription": "First released in 2003 with a PS/2 connector (ACK-540PW &amp; ACK-540PB). USB version released in 2006 in either black (ACK-540UB) or white (ACK-540UW). Marketed under different brands, including SolidTek: http: //www. tigerdirect. com/applications/searchtools/item-details. asp? EdpNo=1472243https: //acecaddigital. com/index. php/products/keyboards/mini-keyboards/kb-540 Deltaco: https: //www. digitalimpuls. no/logitech/116652/deltaco-minitastatur-med-touchpad-usb"
-    },
-    {
-        "title": "Contour Design UniTrap ",
-        "company": "Contour Design",
-        "year": 1999,
-        "primaryKey": [
-            "Re-skin"
-        ],
-        "secondaryKey": [
-            "Mouse"
-        ],
-        "originalPrice": 14.99,
-        "degreesOfFreedom": 2,
-        "dimensions": {
-            "length": 130.5,
-            "width": 75.7,
-            "height": 43,
-            "unit": "mm"
-        },
-        "shortDescription": "This is a plastic shell within which the round Apple iMac G3 “Hockey Puck” mouse can be fit. While the G3 Mouse worked well mechanically, when gripped its round shape gave few cues as to its orientation. Hence, if you moved your hand up, the screen pointer may well have moved diagonally. By reskinning it with the inexpensive Contour UniTrap, the problem went away without the need to buy a whole new mouse.",
-        "longDescription": "Also add back pointers from devices re-skinned"
-    },
-    {
-        "title": "Depraz Swiss Mouse",
-        "company": "Depraz",
-        "year": 1980,
-        "primaryKey": [
-            "Mouse"
-        ],
-        "secondaryKey": [
-            "Ball",
-            "Chord",
-            "Keyboard",
-            "Mouse"
-        ],
-        "originalPrice": 295,
-        "degreesOfFreedom": 2,
-        "dimensions": {
-            "length": 50.8,
-            "width": 76.2,
-            "height": 114.3,
-            "unit": "mm"
-        },
-        "shortDescription": "This mouse is one of the first commercially available mice to be sold publicly. It is known as the Swiss mouse, and yes, the roller mechanism was designed by a Swiss watchmaker. Coincidentally, the company that made it, Depraz, is based in Apples, Switzerland. Their success in selling this mouse is what caused Logitech to switch from a software development shop to one of the world’s leading suppliers of mice and other input devices.",
-        "longDescription": "DePraz began manufacturing in 1980, but following design built in 1979. Logitech started selling it in 1982. It was one of the first mass produced mice, one of the first available ball mice, as well as to have an optical shaft encoder – thereby improving linearity. An interesting fact, given its Swiss heritage, is that its designer, André Guignard, was trained as a Swiss watch maker. Unlike most modern mice, the DePraz, or “Swiss” mouse had a quasi-hemispherical shape. Hence, it was held in a so-called “power-grip”, much as one would grip a horizontally held ball – the thumb and small finger applying pressure on each side, with added support from the weight/friction of the palm on the back of the mouse. In this posture, the three middle fingers naturally positioning themselves over the three buttons mounted at the lower edge of the front. Largely freed of grip pressure, by grace of thumb and little finger, the middle fingers had essentially freedom of motion to independently operate the buttons. Each having a dedicated finger, the buttons could be easily pushed independently or in any combination. Like the three valves on a trumpet, this ability to “chord” extended the three physical buttons to have the power of seven. The down-side of this “turtle shell” form factor is that it placed the hand in a posture in which mouse movement relied more of the larger muscle groups of the arm to wrist, rather than wrist to fingers – the latter being the approach taken in most subsequent mice. The original Swiss Mouse was developed at École Polytechnique Fédérale de Lausanne by a project led by Jean-Daniel Nicoud, who was also responsible for the development of its optical shaft encoder. To augment their revenue stream, Logitech, then a software and hardware consulting company for the publishing industry, acquired marketing rights for North America. Mouse revenue quickly overshadowed that from software. In 1983, Logitech acquired DePraz, named the Swiss Mouse the “P4”, and grew to become one of the largest input device manufacturer in the world. One curious coincidence is that they were founded in the town of Apples, Switzerland."
-    },
-    {
-        "title": "One Laptop Per Child (OLPC) XO-1",
-        "company": "One Laptop Per Child (OLPC)",
-        "year": 2007,
-        "primaryKey": [
-            "Computer"
-        ],
-        "secondaryKey": [
-            "Keyboard",
-            "Laptop",
-            "Pad",
-            "Slate",
-            "Touch"
-        ],
-        "originalPrice": 199,
-        "degreesOfFreedom": 2,
-        "dimensions": {
-            "length": 242,
-            "width": 228,
-            "height": 30,
-            "unit": "mm"
-        },
-        "shortDescription": "The OLPC XO-1 is very innovative device that nevertheless raises serious issues about technology and social responsibility. It is included in the collection primarily as a warning against technological hubris, and the fact that no technologies are neutral from a social-cultural perspective.",
-        "longDescription": "IntroductionI have this computer in my collection as a reminder of the delicate relationship between object and purpose, and how no matter how well one does on the former, it will likely have no impact on making a wanting concept achieve the stated (and even valid) purpose any better. I include it in the collection as a cautionary tale of how the object may help sell a concept, regardless how ill-conceived – even to those who should know better, had they applied the most basic critical thinking. For consumers, investors and designers, its story serves as a cautionary reminder to the importance of cultivating and retaining a critical mind and questioning perspective, regardless of how intrinsically seductive or well-intentioned a technology may be. From the perspective of hardware and software, what the One Laptop Per Child (OLPC) project was able to accomplish is impressive. In general, the team delivered a computer that could be produced at a remarkably low price – even if about double that which was targeted. Specifically, the display, for example, is innovative, and stands out due to its ability to work both in the bright sun (reflective) as well as in poorly lit spaces (emissive) – something that goes beyond pretty much anything else that is available on today’s (2017) slate computers or e-readers. In short, some excellent work went into this machine, something that is even more impressive, given the nature of the organization from which it emerged. The industrial design was equally impressive. Undertaken by Yves Behar’s FuseprojectUltimately, however, the machine was a means to an end, not the end itself. Rather than a device, the actual mission of the OLPC project was: … to empower the world's poorest children through education. Yet, as described by in their materials, the computer was intended to play a key role in this: With access to this type of tool [the computer], children are engaged in their own education, and learn, share, and create together. They become connected to each other, to the world and to a brighter future. Hence, making a suitable computer suitable to that purpose and the conditions where it would be used, at a price point that would enable broad distribution, was a key part of the project. The Underlying Belief System of the OLPC ProjectSince they are key to the thinking behind the OLPC project, I believe if fair to frame my discussion around the following four questions: Will giving computers to kids in the developing world improve their education? Will having a thus better-educated youth help bring a society out of poverty? Can that educational improvement be accomplished by giving the computers to the kids, with no special training for teachers? Should this be attempted on a global scale without any advance field trials or pilot studies? From the perspective of the OLPC project, the answer to every one of these questions is an unequivocal “yes”. In fact, as we shall see, any suggestion to the contrary is typically answered by condescension and/or mockery. The answers appear to be viewed as self-evident and not worth even questioning. Those who have not subscribed to this doctrine might call such a viewpoint hubris. What staggers me is how the project got so far without the basic assumptions being more broadly questioned, much less such questions being seriously addressed by the proponents. How did seemingly otherwise people commit to the project, through their labour or financial investment, given the apparently naïve and utopian approach that it took? Does the desire to do good cloud judgment that much? Are we that dazzled by a cool technology or big hairy audacious goal? Or by a charismatic personality? To explain my concern, and what this artifact represents to me, let me just touch on the four assumptions on which the project was founded. Will giving computers to kids in the developing world improve education? The literature on this question is, at best, mixed. What is clear is that one cannot make any assumption that such improvements will occur, regardless of whether one is talking about the developing world or suburban USA. For example, in January 2011, The World Bank published the following study: Can Computers Help Students Learn? From Evidence to Policy, January 2011, Number 4, The World Bank. A public-private partnership in Colombia, called Computers for Education, was created in 2002 to increase the availability of computers in public schools for use in education. Since starting, the program has installed more than 73, 000 computers in over 6, 300 public schools in more than 1, 000 municipalities. By 2008, over 2 million students and 83, 000 teachers had taken part. This document reports on a two-year study to determine the impact of the program on student performance. Students in schools that received the computers and teacher training did not do measurably better on tests than students in the control group. Nor was there a positive effect on other measures of learning. Researchers did not find any difference in test scores when they looked at specific components of math and language studies, such as algebra and geometry, and grammar and paraphrase ability in Spanish. But report also notes that results of such studies are mixed: Studies on the relationship between using computers in the classroom and improved test scores in developing countries give mixed results: A review of Israel’s Tomorrow-98 program in the mid-1990s, which put computers in schools across the country, did not find any impact on math and Hebrew language scores. But in India, a study of a computer-assisted learning program showed a significant positive impact on math scores. One thing researchers agree on, more work is needed in this field. Before moving on, a search of the literature will show that these results are consistent with those that were available in the literature at the time that the project was started. The point that I am making is not that the OLPC project could not be made to work; rather, that it was wrong to assume that it would do so without spending at least as much time designing the process to bring that about, as was expended designing the computer itself. Risk is fine, and something that can be mitigated. But diving in under the assumption that it would just work is not calculated risk, it is gambling - with other people’s lives, education and money. Will a better educated population help bring a society out of poverty? I am largely going to punt on this question. The fact is, I would be hard pressed to argue against education. But let us grant that improving education in the developing world is a good thing. The appropriate question is: is the approach of the OLPC project a reasonable or responsible way to disburse the limited resources that are available to address the educational challenges of the developing world? At the very least, I would suggest that this is a topic worthy of debate. An a priori assumption that giving computers is the right solution is akin to the, “If you build it they will come” approach seen in the movie, Field of Dreams. The problem here is that this is not a movie. There are real lives and futures that are at stake here – lives of those who cannot afford to see the movie, much less have precious resources spent on projects that are not well thought through. Can that improvement be accomplished by just giving the computers to the kids without training teachers? Remarkably, the OLPC Project’s answer is an explicit, “Yes”. In a TED talk filmed in December 2007, the founder of the OLPC initiative, Nicholas Negroponte states: “When people tell me, you know, who’s going to teach the teachers to teach the kids, I say to myself, “What planet do you come from? ” Okay, there’s not a person in this room [the TED Conference], I don’t care how techy you are, there’s not a person in this room that doesn’t give their laptop or cell phone to a kid to help them debug it. Okay, we all need help, even those of us who are very seasoned. ”Let us leave aside the naïvete of this statement stemming from the lack of distinction between ability to use applications and devices versus the ability to create and shape them. A failure of logic remains in that those unseasoned kids are part of “us”, as in “we all need help”. Where do the kids go for help? To other kids? What if they don’t know? Often they won’t. After all, the question may well have to do with a concept in calculus, rather than how to use the computer. What then? No answer is offered. Rather, those who dare raise the serious and legitimate concerns regarding teacher preparation are mockingly dismissed as coming from another planet! Well, perhaps they are. But in that case, there should at least be some debate as to who lives on which planet. Is it the people raising the question or the one dismissing the concern that lives in the real world of responsible thought and action? Can this all be accomplished without any advance field trials? Should one just immediately commit to international deployment of the program? As recently as September 2009, Negroponte took part in a panel discussion where he spoke on this matter. He states: I'd like you to imagine that I told you \"I have a technology that is going to change the quality of life. \" And then I tell you \"Really the right thing to do is to set up a pilot project to test my technology. And then the second thing to do is, once the pilot has been running for some period of time, is to go and measure very carefully the benefits of that technology. \"And then I am to tell you that what we are going to is very scientifically evaluate this technology, with control groups - giving it to some, giving it to others. And this all is very reasonable until I tell you the technology is electricity. And you say \"Wait, you don't have to do that!\"But you don't have to do that with laptops and learning either. And the fact that somebody in the room would say the impact is unclear is to me amazing - unbelievably amazing. There's not a person in this room who hasn't bought a laptop for their child, if they could afford it. And you don't know somebody who hasn't done it, if they can afford it. So there's only one question on the table and that's, “How to afford it? ” That's the only question. There is no other question - it's just the economics. And so, when One Laptop Per Child started, I didn't have the picture quite as clear as that, but we did focus on trying to get the price down. We did focus on those things. Unfortunately, Negroponte demonstrates his lack of understanding of both the history of electricity and education in this example. His historical mistake is this: yes, it was pretty obvious that electricity could bring many benefits to society. But what happened when Edison did exactly what Negroponte advocates? He almost lost his company due to his complete (but mistaken) conviction that DC, rather the AC was the correct technology to pursue. As with electricity, yes, it is rather obvious that education could bring significant benefits to the developing world. But in order to avoid making the same kind of expensive mistake that Edison did, perhaps one might want to do one’s best to make sure that the chosen technology is the AC, rather than DC, of education. A little more research, and a little less hubris might have put the investments in Edison and the OLPC to much better use. But the larger question is this: in what way is it responsible for the wealthy western world to advocate an untested and expensive (in every sense) technological solution on the poorest nations in the world? If history has taught us anything, it has taught us that just because our intentions are good, the same is not necessarily true for consequences of our actions. Later in his presentation, Negroponte states: … our problems are swimming against very naïve views of education. With this, I have to agree. It is just whose views on education are naïve, and how can such views emerge from MIT, no less, much less pass with so little critical scrutiny by the public, the press, participants, and funders? In an interview with Paul Marks, published in the New Scientist in December 2008, we see the how the techno-centric aspect of the project plays into the ostensible human centric purpose of the project. Negroponte’s retort regarding some of the initial skepticism that the project provoked was this: “When we first said we could build a laptop for $100 it was viewed as unrealistic and so 'anti-market' and so 'anti' the current laptops which at the time were around $1000 each, \" Negroponte said. \"It was viewed as pure bravado - but look what happened: the netbook market has developed in our wake. \" The project's demands for cheaper components such as keyboards, and processors nudged the industry into finding ways to cut costs, he says. \"What started off as a revolution became a culture. \"Surprise, yes, computers get smaller, faster, and cheaper over the course of time, and yes, one can even grant that the OLPC project may have accelerated that inevitable move. And, I have already stated my admiration and respect for the quality of the technology that was developed. But in the context of the overall objectives of the project, the best that one can say is, “Congratulations on meeting a milestone. ” However, by the same token, one might also legitimately question if starting with the hardware was not an instance of putting the cart before the horse. Yes, it is obviously necessary to have portable computers in the first place, before one can introduce them into the classroom, home, and donate them to children in the developing world. But it is also the case that small portable computers were already in existence and at the time that the project was initiated. While a factor of ten more expensive than the eventual target price, they were both available and adequate to support limited preliminary testing of the underlying premises of the project in an affordable manner. That is, before launching into a major - albeit well-intentioned – hardware development project, it may have been prudent to have tested the underlying premises of its motivation. Here we have to return to the raison d’être of the initiative: … to empower the world's poorest children through educationHence, the extent to which this is achieved from a given investment must be the primary metric of success, as well as the driving force of the project. Yet, that is clearly not what happened. Driven by a blind Edisonian belief in their un-tested premise, the project’s investments were overwhelmingly on the side of technology rather than pedagogy. Perhaps the nature and extent of the naïve (but well-meaning) utopian dream underlying the project is captured in the last part of the interview, above: Negroponte believes that empowering children and their parents with the educational resources offered by computers and the Internet will lead to informed decisions that improve democracy. Indeed, it has led to some gentle ribbing between himself and his brother: John Negroponte - currently deputy secretary of state in the outgoing Bush administration and the first ever director of national intelligence at the National Security Agency. \"I often joke with John that he can bring democracy his way - and I'll bring it mine, \" he says. Apparently providing inexpensive laptops to children in the developing world is not only going to raise educational standards, eradicate poverty, it is also going to bring democracy! All that, with no mention of the numerous poor non-democratic countries that have literacy levels equal to or higher than the USA (Cuba might be one reasonable example). The words naïve technological-utopianism come to mind. I began by admitting that I was conflicted in terms of this project. From the purely technological perspective, there is much to admire in the project’s accomplishments. Sadly, that was not the project’s primary objective. What appears to be missing throughout is an inability to distinguish between the technology and the purpose to which is was intended to serve. My concern in this regard is reflected in a paper by Warschauer &amp; Ames(2010). The analysis reveals that provision of individual laptops is a utopian vision for the children in the poorest countries, whose educational and social futures could be more effectively improved if the same investments were instead made on more sustainable and proven interventions. Middle- and high-income countries may have a stronger rationale for providing individual laptops to children, but will still want to eschew OLPC’s technocentric vision. In summary, OLPC represents the latest in a long line of technologically utopian development schemes that have unsuccessfully attempted to solve complex social problems with overly simplistic solutions. There is a delicate relationship between technology and society, culture, ethics, and values. What this case study reflects is the fact that technologies are not neutral. They never are. Hence, technological initiatives must be accompanied by appropriate social, cultural and ethical considerations – especially in projects such as this where the technologies are being introduced into particularly vulnerable societies. That did not happen here, The fact that this project got the support that it did, and has gone as far as it has, given the way it was approached, is why this reminder – in the form of this device – is included in the collection. And if anyone ever wonders why I am so vocal about the need for public discourse around technology, one need look no further than the OLPC project."
-    }
-]
\ No newline at end of file
diff --git a/src/scraping/buxton/json/incomplete.json b/src/scraping/buxton/json/incomplete.json
deleted file mode 100644
index 4b05a2a86..000000000
--- a/src/scraping/buxton/json/incomplete.json
+++ /dev/null
@@ -1,468 +0,0 @@
-[
-    {
-        "filename": "3DMag.docx",
-        "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
-        "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured."
-    },
-    {
-        "filename": "3DPlus.docx",
-        "year": "ERR__YEAR__: outer match was captured.",
-        "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
-        "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured.",
-        "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
-    },
-    {
-        "filename": "3DSpace.docx",
-        "year": "ERR__YEAR__: outer match was captured.",
-        "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
-        "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured.",
-        "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
-    },
-    {
-        "filename": "3Dconnexion_SpaceNavigator.docx",
-        "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured."
-    },
-    {
-        "filename": "3MErgo.docx",
-        "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
-        "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured.",
-        "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
-    },
-    {
-        "filename": "ADB2.docx",
-        "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
-        "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured.",
-        "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured."
-    },
-    {
-        "filename": "AWrock.docx",
-        "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
-        "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured.",
-        "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured."
-    },
-    {
-        "filename": "Abaton.docx",
-        "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
-        "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured."
-    },
-    {
-        "filename": "Active.docx",
-        "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
-        "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured.",
-        "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured.",
-        "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
-    },
-    {
-        "filename": "AlphaSmart_Pro.docx",
-        "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured."
-    },
-    {
-        "filename": "Apple_ADB_Mouse.docx",
-        "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured."
-    },
-    {
-        "filename": "Apple_Mac_Portable-Katy’s MacBook Air-2.docx",
-        "year": "__ERR__YEAR__TRANSFORM__: NaN cannot be parsed to a numeric value.",
-        "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
-    },
-    {
-        "filename": "Apple_Mac_Portable-Katy’s MacBook Air.docx",
-        "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
-    },
-    {
-        "filename": "Apple_Scroll_Mouse.docx",
-        "year": "__ERR__YEAR__TRANSFORM__: NaN cannot be parsed to a numeric value.",
-        "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured.",
-        "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured."
-    },
-    {
-        "filename": "Apple_iPhone.docx",
-        "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
-    },
-    {
-        "filename": "Brailler.docx",
-        "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured.",
-        "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
-        "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
-    },
-    {
-        "filename": "Brewster_Stereoscope.docx",
-        "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured.",
-        "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
-        "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured."
-    },
-    {
-        "filename": "CasioTC500.docx",
-        "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
-        "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
-        "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured."
-    },
-    {
-        "filename": "Citizen_LC_909.docx",
-        "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured.",
-        "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
-        "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured."
-    },
-    {
-        "filename": "Citizen_LC_913.docx",
-        "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured."
-    },
-    {
-        "filename": "Citizen_LCl_914.docx",
-        "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured.",
-        "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured."
-    },
-    {
-        "filename": "CoolPix.docx",
-        "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
-        "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured.",
-        "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
-        "shortDescription": "ERR__SHORTDESCRIPTION__: outer match was captured."
-    },
-    {
-        "filename": "Cross.docx",
-        "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
-        "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured.",
-        "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
-    },
-    {
-        "filename": "Dymo_MK-6.docx",
-        "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
-        "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured.",
-        "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
-        "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
-    },
-    {
-        "filename": "Emotiv.docx",
-        "primaryKey": "ERR__PRIMARYKEY__: outer match was captured.",
-        "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
-        "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
-        "shortDescription": "ERR__SHORTDESCRIPTION__: outer match was captured.",
-        "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
-    },
-    {
-        "filename": "Explorer.docx",
-        "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
-        "shortDescription": "ERR__SHORTDESCRIPTION__: outer match was captured.",
-        "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
-    },
-    {
-        "filename": "Falcon.docx",
-        "primaryKey": "ERR__PRIMARYKEY__: outer match was captured.",
-        "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
-        "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
-        "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured.",
-        "shortDescription": "ERR__SHORTDESCRIPTION__: outer match was captured."
-    },
-    {
-        "filename": "Freeboard.docx",
-        "secondaryKey": "ERR__SECONDARYKEY__: outer match was captured.",
-        "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured.",
-        "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
-        "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured."
-    },
-    {
-        "filename": "FujitsuPalm.docx",
-        "primaryKey": "ERR__PRIMARYKEY__: outer match was captured.",
-        "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
-        "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured.",
-        "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
-        "shortDescription": "ERR__SHORTDESCRIPTION__: outer match was captured.",
-        "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
-    },
-    {
-        "filename": "FujitsuTouch.docx",
-        "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
-        "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured.",
-        "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
-        "shortDescription": "ERR__SHORTDESCRIPTION__: outer match was captured."
-    },
-    {
-        "filename": "GRiD1550-Katy’s MacBook Air-2.docx",
-        "year": "__ERR__YEAR__TRANSFORM__: NaN cannot be parsed to a numeric value.",
-        "shortDescription": "ERR__SHORTDESCRIPTION__: outer match was captured."
-    },
-    {
-        "filename": "GRiD1550-Katy’s MacBook Air.docx",
-        "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
-        "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
-        "shortDescription": "ERR__SHORTDESCRIPTION__: outer match was captured."
-    },
-    {
-        "filename": "GRiD1550.docx",
-        "primaryKey": "ERR__PRIMARYKEY__: outer match was captured.",
-        "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
-        "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
-        "shortDescription": "ERR__SHORTDESCRIPTION__: outer match was captured."
-    },
-    {
-        "filename": "Genius_Ring_Mouse.docx",
-        "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured."
-    },
-    {
-        "filename": "HTC_Touch.docx",
-        "year": "__ERR__YEAR__TRANSFORM__: NaN cannot be parsed to a numeric value.",
-        "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured."
-    },
-    {
-        "filename": "Helios-Klimax.docx",
-        "primaryKey": "ERR__PRIMARYKEY__: outer match was captured.",
-        "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
-        "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured.",
-        "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
-        "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured.",
-        "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
-    },
-    {
-        "filename": "Honeywell_T86.docx",
-        "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured."
-    },
-    {
-        "filename": "IBMTrack.docx",
-        "year": "ERR__YEAR__: outer match was captured.",
-        "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
-        "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured.",
-        "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured."
-    },
-    {
-        "filename": "IBM_Convertable-Katy’s MacBook Air-2.docx",
-        "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
-        "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured."
-    },
-    {
-        "filename": "IBM_Convertable-Katy’s MacBook Air.docx",
-        "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
-        "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured."
-    },
-    {
-        "filename": "IBM_Convertable.docx",
-        "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
-        "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured."
-    },
-    {
-        "filename": "IBM_PS2_Mouse.docx",
-        "primaryKey": "ERR__PRIMARYKEY__: outer match was captured.",
-        "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
-        "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured.",
-        "shortDescription": "ERR__SHORTDESCRIPTION__: outer match was captured.",
-        "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
-    },
-    {
-        "filename": "IBM_Simon.docx",
-        "year": "__ERR__YEAR__TRANSFORM__: NaN cannot be parsed to a numeric value."
-    },
-    {
-        "filename": "IDEO.docx",
-        "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
-        "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
-        "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured.",
-        "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
-    },
-    {
-        "filename": "Joyboard.docx",
-        "primaryKey": "ERR__PRIMARYKEY__: outer match was captured.",
-        "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
-        "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
-        "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured.",
-        "shortDescription": "ERR__SHORTDESCRIPTION__: outer match was captured."
-    },
-    {
-        "filename": "Kensington_SB_TB-Mouse.docx",
-        "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
-        "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
-        "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured."
-    },
-    {
-        "filename": "Leatherman_Tread.docx",
-        "primaryKey": "ERR__PRIMARYKEY__: outer match was captured.",
-        "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
-        "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
-        "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured.",
-        "shortDescription": "ERR__SHORTDESCRIPTION__: outer match was captured.",
-        "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
-    },
-    {
-        "filename": "M1.docx",
-        "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured."
-    },
-    {
-        "filename": "MS-1_Stereoscope.docx",
-        "year": "__ERR__YEAR__TRANSFORM__: NaN cannot be parsed to a numeric value.",
-        "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured.",
-        "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured."
-    },
-    {
-        "filename": "MWB_Braille_Writer.docx",
-        "company": "ERR__COMPANY__: outer match wasn't captured.",
-        "year": "__ERR__YEAR__TRANSFORM__: NaN cannot be parsed to a numeric value.",
-        "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured.",
-        "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured."
-    },
-    {
-        "filename": "MaltronLH.docx",
-        "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
-        "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured."
-    },
-    {
-        "filename": "Marine_Band_Harmonica.docx",
-        "company": "ERR__COMPANY__: outer match was captured.",
-        "year": "ERR__YEAR__: outer match was captured.",
-        "primaryKey": "ERR__PRIMARYKEY__: outer match was captured.",
-        "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
-        "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured.",
-        "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
-        "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured.",
-        "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
-    },
-    {
-        "filename": "Matrox.docx",
-        "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured.",
-        "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured."
-    },
-    {
-        "filename": "Metaphor_Kbd.docx",
-        "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured.",
-        "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
-        "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured.",
-        "shortDescription": "ERR__SHORTDESCRIPTION__: outer match was captured."
-    },
-    {
-        "filename": "Metaphor_Mouse.docx",
-        "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured.",
-        "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
-    },
-    {
-        "filename": "Motorola_DynaTAC.docx",
-        "year": "__ERR__YEAR__TRANSFORM__: NaN cannot be parsed to a numeric value.",
-        "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
-        "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured.",
-        "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
-    },
-    {
-        "filename": "NewO.docx",
-        "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
-        "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured."
-    },
-    {
-        "filename": "Newton120.docx",
-        "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured.",
-        "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
-    },
-    {
-        "filename": "Nikon_Coolpix-100.docx",
-        "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
-        "shortDescription": "ERR__SHORTDESCRIPTION__: outer match was captured."
-    },
-    {
-        "filename": "Numonics_Mgr_Mouse.docx",
-        "company": "ERR__COMPANY__: outer match was captured.",
-        "year": "ERR__YEAR__: outer match was captured.",
-        "primaryKey": "ERR__PRIMARYKEY__: outer match was captured.",
-        "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
-        "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured.",
-        "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
-        "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured.",
-        "shortDescription": "ERR__SHORTDESCRIPTION__: outer match was captured.",
-        "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
-    },
-    {
-        "filename": "PadMouse.docx",
-        "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
-        "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured.",
-        "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured."
-    },
-    {
-        "filename": "PowerTrack.docx",
-        "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured.",
-        "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
-    },
-    {
-        "filename": "ProAgio.docx",
-        "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
-        "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
-        "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured."
-    },
-    {
-        "filename": "Pulsar_time_Computer.docx",
-        "primaryKey": "ERR__PRIMARYKEY__: outer match was captured.",
-        "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
-        "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured."
-    },
-    {
-        "filename": "Ring.docx",
-        "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
-        "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
-    },
-    {
-        "filename": "SafeType_Kbd.docx",
-        "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured."
-    },
-    {
-        "filename": "Samsung_SPH-A500.docx",
-        "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
-    },
-    {
-        "filename": "SurfMouse.docx",
-        "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured."
-    },
-    {
-        "filename": "TPARCtab.docx",
-        "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured.",
-        "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured."
-    },
-    {
-        "filename": "Thumbelina.docx",
-        "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
-        "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
-        "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured."
-    },
-    {
-        "filename": "adecm.docx",
-        "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
-        "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured.",
-        "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured.",
-        "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
-    },
-    {
-        "filename": "eMate.docx",
-        "primaryKey": "ERR__PRIMARYKEY__: outer match was captured.",
-        "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
-        "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
-        "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured.",
-        "shortDescription": "ERR__SHORTDESCRIPTION__: outer match was captured.",
-        "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
-    },
-    {
-        "filename": "gravis.docx",
-        "year": "ERR__YEAR__: outer match was captured.",
-        "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
-        "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured.",
-        "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured.",
-        "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
-    },
-    {
-        "filename": "iGesture.docx",
-        "year": "__ERR__YEAR__TRANSFORM__: NaN cannot be parsed to a numeric value.",
-        "primaryKey": "ERR__PRIMARYKEY__: outer match was captured.",
-        "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
-        "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
-        "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
-    },
-    {
-        "filename": "iGrip.docx",
-        "shortDescription": "ERR__SHORTDESCRIPTION__: outer match was captured.",
-        "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
-    },
-    {
-        "filename": "iLiad.docx",
-        "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
-        "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured."
-    },
-    {
-        "filename": "round.docx",
-        "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
-        "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured.",
-        "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured.",
-        "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
-    }
-]
\ No newline at end of file
diff --git a/src/scraping/buxton/node_scraper.ts b/src/scraping/buxton/node_scraper.ts
deleted file mode 100644
index ab6c9dcb2..000000000
--- a/src/scraping/buxton/node_scraper.ts
+++ /dev/null
@@ -1,256 +0,0 @@
-import { readdirSync, writeFile, existsSync, mkdirSync } from "fs";
-import * as path from "path";
-import { red, cyan, yellow, green } from "colors";
-import { Opt } from "../../new_fields/Doc";
-const StreamZip = require('node-stream-zip');
-
-export interface DeviceDocument {
-    title: string;
-    shortDescription: string;
-    longDescription: string;
-    company: string;
-    year: number;
-    originalPrice: number;
-    degreesOfFreedom: number;
-    dimensions: string;
-    primaryKey: string;
-    secondaryKey: string;
-}
-
-interface AnalysisResult {
-    device?: DeviceDocument;
-    errors?: any;
-}
-
-type Converter<T> = (raw: string) => { transformed?: T, error?: string };
-
-interface Processor<T> {
-    exp: RegExp;
-    matchIndex?: number;
-    transformer?: Converter<T>;
-}
-
-const RegexMap = new Map<keyof DeviceDocument, Processor<any>>([
-    ["title", {
-        exp: /contact\s+(.*)Short Description:/
-    }],
-    ["company", {
-        exp: /Company:\s+([^\|]*)\s+\|/,
-        transformer: (raw: string) => ({ transformed: raw.replace(/\./g, "") })
-    }],
-    ["year", {
-        exp: /Year:\s+([^\|]*)\s+\|/,
-        transformer: numberValue
-    }],
-    ["primaryKey", {
-        exp: /Primary:\s+(.*)(Secondary|Additional):/,
-        transformer: collectUniqueTokens
-    }],
-    ["secondaryKey", {
-        exp: /(Secondary|Additional):\s+([^\{\}]*)Links/,
-        transformer: collectUniqueTokens,
-        matchIndex: 2
-    }],
-    ["originalPrice", {
-        exp: /Original Price \(USD\)\:\s+\$([0-9\.]+)/,
-        transformer: numberValue
-    }],
-    ["degreesOfFreedom", {
-        exp: /Degrees of Freedom:\s+([0-9]+)/,
-        transformer: numberValue
-    }],
-    ["dimensions", {
-        exp: /Dimensions\s+\(L x W x H\):\s+([0-9\.]+\s+x\s+[0-9\.]+\s+x\s+[0-9\.]+\s\([A-Za-z]+\))/,
-        transformer: (raw: string) => {
-            const [length, width, group] = raw.split(" x ");
-            const [height, unit] = group.split(" ");
-            return {
-                transformed: {
-                    length: Number(length),
-                    width: Number(width),
-                    height: Number(height),
-                    unit: unit.replace(/[\(\)]+/g, "")
-                }
-            };
-        }
-    }],
-    ["shortDescription", {
-        exp: /Short Description:\s+(.*)Bill Buxton[’']s Notes/,
-        transformer: correctSentences
-    }],
-    ["longDescription", {
-        exp: /Bill Buxton[’']s Notes(.*)Device Details/,
-        transformer: correctSentences
-    }],
-]);
-
-function numberValue(raw: string) {
-    const transformed = Number(raw);
-    if (isNaN(transformed)) {
-        return { error: `${transformed} cannot be parsed to a numeric value.` };
-    }
-    return { transformed };
-}
-
-function collectUniqueTokens(raw: string) {
-    return { transformed: Array.from(new Set(raw.replace(/,|\s+and\s+/g, " ").split(/\s+/).map(token => token.toLowerCase().trim()))).map(capitalize).sort() };
-}
-
-function correctSentences(raw: string) {
-    raw = raw.replace(/\./g, ". ").replace(/\:/g, ": ").replace(/\,/g, ", ").replace(/\?/g, "? ").trimRight();
-    raw = raw.replace(/\s{2,}/g, " ");
-    return { transformed: raw };
-}
-
-const outDir = path.resolve(__dirname, "json");
-const successOut = "buxton.json";
-const failOut = "incomplete.json";
-const deviceKeys = Array.from(RegexMap.keys());
-
-function printEntries(zip: any) {
-    const { entriesCount } = zip;
-    console.log(`Recognized ${entriesCount} entr${entriesCount === 1 ? "y" : "ies"}.`);
-    for (const entry of Object.values<any>(zip.entries())) {
-        const desc = entry.isDirectory ? 'directory' : `${entry.size} bytes`;
-        console.log(`${entry.name}: ${desc}`);
-    }
-}
-
-async function wordToPlainText(pathToDocument: string): Promise<string> {
-    const zip = new StreamZip({ file: pathToDocument, storeEntries: true });
-    const contents = await new Promise<string>((resolve, reject) => {
-        zip.on('ready', () => {
-            let body = "";
-            zip.stream("word/document.xml", (error: any, stream: any) => {
-                if (error) {
-                    reject(error);
-                }
-                stream.on('data', (chunk: any) => body += chunk.toString());
-                stream.on('end', () => {
-                    resolve(body);
-                    zip.close();
-                });
-            });
-        });
-    });
-    let body = "";
-    const components = contents.toString().split('<w:t');
-    for (const component of components) {
-        const tags = component.split('>');
-        const content = tags[1].replace(/<.*$/, "");
-        body += content;
-    }
-    return body;
-}
-
-function tryGetValidCapture(matches: RegExpExecArray | null, matchIndex: number): Opt<string> {
-    let captured: string;
-    if (!matches || !(captured = matches[matchIndex])) {
-        return undefined;
-    }
-    const lower = captured.toLowerCase();
-    if (/to come/.test(lower)) {
-        return undefined;
-    }
-    if (lower.includes("xxx")) {
-        return undefined;
-    }
-    if (!captured.toLowerCase().replace(/[….\s]+/g, "").length) {
-        return undefined;
-    }
-    return captured;
-}
-
-function capitalize(word: string): string {
-    const clean = word.trim();
-    if (!clean.length) {
-        return word;
-    }
-    return word.charAt(0).toUpperCase() + word.slice(1);
-}
-
-function analyze(path: string, body: string): AnalysisResult {
-    const device: any = {};
-
-    const segments = path.split("/");
-    const filename = segments[segments.length - 1].replace("Bill_Notes_", "");
-
-    const errors: any = { filename };
-
-    for (const key of deviceKeys) {
-        const { exp, transformer, matchIndex } = RegexMap.get(key)!;
-        const matches = exp.exec(body);
-
-        let captured = tryGetValidCapture(matches, matchIndex ?? 1);
-        if (!captured) {
-            errors[key] = `ERR__${key.toUpperCase()}__: outer match ${matches === null ? "wasn't" : "was"} captured.`;
-            continue;
-        }
-
-        captured = captured.replace(/\s{2,}/g, " ");
-        if (transformer) {
-            const { error, transformed } = transformer(captured);
-            if (error) {
-                errors[key] = `__ERR__${key.toUpperCase()}__TRANSFORM__: ${error}`;
-                continue;
-            }
-            captured = transformed;
-        }
-
-        device[key] = captured;
-    }
-
-    const errorKeys = Object.keys(errors);
-    if (errorKeys.length > 1) {
-        console.log(red(`\n@ ${cyan(filename.toUpperCase())}...`));
-        errorKeys.forEach(key => key !== "filename" && console.log(red(errors[key])));
-        return { errors };
-    }
-
-    return { device };
-}
-
-async function parseFiles(): Promise<DeviceDocument[]> {
-    const sourceDirectory = path.resolve(`${__dirname}/source`);
-    const candidates = readdirSync(sourceDirectory).filter(file => file.endsWith(".doc") || file.endsWith(".docx")).map(file => `${sourceDirectory}/${file}`);
-    const imported = await Promise.all(candidates.map(async path => ({ path, body: await wordToPlainText(path) })));
-    // const imported = [{ path: candidates[10], body: await extract(candidates[10]) }];
-    const data = imported.map(({ path, body }) => analyze(path, body));
-    const masterDevices: DeviceDocument[] = [];
-    const masterErrors: any[] = [];
-    data.forEach(({ device, errors }) => {
-        if (device) {
-            masterDevices.push(device);
-        } else {
-            masterErrors.push(errors);
-        }
-    });
-    const total = candidates.length;
-    if (masterDevices.length + masterErrors.length !== total) {
-        throw new Error(`Encountered a ${masterDevices.length} to ${masterErrors.length} mismatch in device / error split!`);
-    }
-    console.log();
-    await writeOutputFile(successOut, masterDevices, total, true);
-    await writeOutputFile(failOut, masterErrors, total, false);
-    console.log();
-
-    return masterDevices;
-}
-
-async function writeOutputFile(relativePath: string, data: any[], total: number, success: boolean) {
-    console.log(yellow(`Encountered ${data.length} ${success ? "valid" : "invalid"} documents out of ${total} candidates. Writing ${relativePath}...`));
-    return new Promise<void>((resolve, reject) => {
-        const destination = path.resolve(outDir, relativePath);
-        const contents = JSON.stringify(data, undefined, 4);
-        writeFile(destination, contents, err => err ? reject(err) : resolve());
-    });
-}
-
-export async function main() {
-    if (!existsSync(outDir)) {
-        mkdirSync(outDir);
-    }
-    return parseFiles();
-}
-
-main();
\ No newline at end of file
diff --git a/src/server/ApiManagers/UtilManager.ts b/src/server/ApiManagers/UtilManager.ts
index dbf274e93..e590a5b85 100644
--- a/src/server/ApiManagers/UtilManager.ts
+++ b/src/server/ApiManagers/UtilManager.ts
@@ -4,7 +4,7 @@ import { exec } from 'child_process';
 import { command_line } from "../ActionUtilities";
 import RouteSubscriber from "../RouteSubscriber";
 import { red } from "colors";
-import { main } from "../../scraping/buxton/node_scraper";
+import executeImport from "../../scraping/buxton/final/BuxtonImporter";
 
 export default class UtilManager extends ApiManager {
 
@@ -40,29 +40,29 @@ export default class UtilManager extends ApiManager {
             }
         });
 
-        register({
-            method: Method.GET,
-            subscription: "/buxton",
-            secureHandler: async ({ res }) => {
-                const cwd = './src/scraping/buxton';
+        // register({
+        //     method: Method.GET,
+        //     subscription: "/buxton",
+        //     secureHandler: async ({ res }) => {
+        //         const cwd = './src/scraping/buxton';
 
-                const onResolved = (stdout: string) => { console.log(stdout); res.redirect("/"); };
-                const onRejected = (err: any) => { console.error(err.message); res.send(err); };
-                const tryPython3 = (reason: any) => {
-                    console.log("Initial scraper failed for the following reason:");
-                    console.log(red(reason.Error));
-                    console.log("Falling back to python3...");
-                    return command_line('python3 scraper.py', cwd).then(onResolved, onRejected);
-                };
+        //         const onResolved = (stdout: string) => { console.log(stdout); res.redirect("/"); };
+        //         const onRejected = (err: any) => { console.error(err.message); res.send(err); };
+        //         const tryPython3 = (reason: any) => {
+        //             console.log("Initial scraper failed for the following reason:");
+        //             console.log(red(reason.Error));
+        //             console.log("Falling back to python3...");
+        //             return command_line('python3 scraper.py', cwd).then(onResolved, onRejected);
+        //         };
 
-                return command_line('python scraper.py', cwd).then(onResolved, tryPython3);
-            },
-        });
+        //         return command_line('python scraper.py', cwd).then(onResolved, tryPython3);
+        //     },
+        // });
 
         register({
             method: Method.GET,
-            subscription: "/newBuxton",
-            secureHandler: async ({ res }) => res.send(await main())
+            subscription: "/buxton",
+            secureHandler: async ({ res }) => res.send(await executeImport())
         });
 
         register({
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 71775bed6..896b88631 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -58,7 +58,7 @@ export class CurrentUserUtils {
             { title: "todo item", icon: "check", ignoreClick: true, drag: 'getCopy(this.dragFactory, true)', dragFactory: notes[notes.length - 1] },
             { title: "web page", icon: "globe-asia", ignoreClick: true, drag: 'Docs.Create.WebDocument("https://en.wikipedia.org/wiki/Hedgehog", {_width: 300, _height: 300, title: "New Webpage" })' },
             { title: "cat image", icon: "cat", ignoreClick: true, drag: 'Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { _width: 200, title: "an image of a cat" })' },
-            { title: "buxton", icon: "faObjectGroup", ignoreClick: true, drag: "Docs.Create.Buxton()" },
+            { title: "buxton", icon: "cloud-upload-alt", ignoreClick: true, drag: "Docs.Create.Buxton()" },
             { title: "record", icon: "microphone", ignoreClick: true, drag: `Docs.Create.AudioDocument("${nullAudio}", { _width: 200, title: "ready to record audio" })` },
             { title: "clickable button", icon: "bolt", ignoreClick: true, drag: 'Docs.Create.ButtonDocument({ _width: 150, _height: 50, title: "Button" })' },
             { title: "presentation", icon: "tv", click: 'openOnRight(Doc.UserDoc().curPresentation = getCopy(this.dragFactory, true))', drag: `Doc.UserDoc().curPresentation = getCopy(this.dragFactory,true)`, dragFactory: emptyPresentation },
-- 
cgit v1.2.3-70-g09d2


From 05c429412d36531bed4e8ece889479fcf93faea6 Mon Sep 17 00:00:00 2001
From: kimdahey <claire_kim1@brown.edu>
Date: Sun, 9 Feb 2020 16:43:01 -0500
Subject: functioning

---
 package-lock.json                                  |  4 +-
 package.json                                       |  2 +-
 .../views/collections/CollectionDockingView.tsx    | 69 ++++++++++++++++++++++
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 40 ++++++++++++-
 4 files changed, 110 insertions(+), 5 deletions(-)

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

diff --git a/package-lock.json b/package-lock.json
index 833710fe7..049c91fcf 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -16239,7 +16239,7 @@
         },
         "string_decoder": {
             "version": "1.1.1",
-            "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+            "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
             "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
             "requires": {
                 "safe-buffer": "~5.1.0"
@@ -18560,7 +18560,7 @@
         },
         "wrap-ansi": {
             "version": "2.1.0",
-            "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
+            "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
             "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
             "requires": {
                 "string-width": "^1.0.1",
diff --git a/package.json b/package.json
index 67b9b1630..7e0adfba6 100644
--- a/package.json
+++ b/package.json
@@ -253,4 +253,4 @@
         "xoauth2": "^1.2.0",
         "youtube": "^0.1.0"
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 82cb3bc88..7a6d54ac2 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -249,6 +249,75 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
         return true;
     }
 
+
+    //
+    //  Creates a split on the any side of the docking view, based on the passed input pullSide and then adds the Document to the requested side
+    //
+    @undoBatch
+    @action
+    public static AddSplit(document: Doc, pullSide: string, dataDoc: Doc | undefined, libraryPath?: Doc[]) {
+        if (!CollectionDockingView.Instance) return false;
+        const instance = CollectionDockingView.Instance;
+        const newItemStackConfig = {
+            type: 'stack',
+            content: [CollectionDockingView.makeDocumentConfig(document, dataDoc, undefined, libraryPath)]
+        };
+
+        const newContentItem = instance._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, instance._goldenLayout);
+
+        if (instance._goldenLayout.root.contentItems.length === 0) { // if no rows / columns
+            instance._goldenLayout.root.addChild(newContentItem);
+        } else if (instance._goldenLayout.root.contentItems[0].isRow) { // if row
+            if (pullSide === "left") {
+                instance._goldenLayout.root.contentItems[0].addChild(newContentItem, 0);
+            } else if (pullSide === "right") {
+                instance._goldenLayout.root.contentItems[0].addChild(newContentItem);
+            } else if (pullSide === "top" || pullSide === "bottom") {
+                // if not going in a row layout, must add already existing content into column
+                const rowlayout = instance._goldenLayout.root.contentItems[0];
+                const newColumn = rowlayout.layoutManager.createContentItem({ type: "column" }, instance._goldenLayout);
+                rowlayout.parent.replaceChild(rowlayout, newColumn);
+                if (pullSide === "top") {
+                    newColumn.addChild(rowlayout, undefined, true);
+                    newColumn.addChild(newContentItem, 0, true);
+                } else if (pullSide === "bottom") {
+                    newColumn.addChild(newContentItem, undefined, true);
+                    newColumn.addChild(rowlayout, 0, true);
+                }
+
+                rowlayout.config.height = 50;
+                newContentItem.config.height = 50;
+            }
+        } else if (instance._goldenLayout.root.contentItems[0].isColumn) { // if column
+            if (pullSide === "top") {
+                instance._goldenLayout.root.contentItems[0].addChild(newContentItem, 0);
+            } else if (pullSide === "bottom") {
+                instance._goldenLayout.root.contentItems[0].addChild(newContentItem);
+            } else if (pullSide === "left" || pullSide === "right") {
+                // if not going in a row layout, must add already existing content into column
+                const collayout = instance._goldenLayout.root.contentItems[0];
+                const newRow = collayout.layoutManager.createContentItem({ type: "row" }, instance._goldenLayout);
+                collayout.parent.replaceChild(collayout, newRow);
+
+                if (pullSide === "left") {
+                    newRow.addChild(collayout, undefined, true);
+                    newRow.addChild(newContentItem, 0, true);
+                } else if (pullSide === "right") {
+                    newRow.addChild(newContentItem, undefined, true);
+                    newRow.addChild(collayout, 0, true);
+                }
+
+                collayout.config.width = 50;
+                newContentItem.config.width = 50;
+            }
+        }
+
+        newContentItem.callDownwards('_$init');
+        instance.layoutChanged();
+        return true;
+    }
+
+
     //
     //  Creates a vertical split on the right side of the docking view, and then adds the Document to that split
     //
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 53fe2b18c..30ef9eb77 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -45,6 +45,7 @@ import { CognitiveServices } from "../../../cognitive_services/CognitiveServices
 import { RichTextField } from "../../../../new_fields/RichTextField";
 import { List } from "../../../../new_fields/List";
 import { DocumentViewProps } from "../../nodes/DocumentView";
+import { CollectionDockingView } from "../CollectionDockingView";
 
 library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload);
 
@@ -80,6 +81,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     private _hitCluster = false;
     private _layoutComputeReaction: IReactionDisposer | undefined;
     private _layoutPoolData = new ObservableMap<string, any>();
+    private _pullCoords: number[] = [0, 0];
 
     public get displayName() { return "CollectionFreeFormView(" + this.props.Document.title?.toString() + ")"; } // this makes mobx trace() statements more descriptive
     @observable.shallow _layoutElements: ViewDefResult[] = []; // shallow because some layout items (eg pivot labels) are just generated 'divs' and can't be frozen as observables
@@ -596,7 +598,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true);
             const pt1 = myTouches[0];
             const pt2 = myTouches[1];
-            console.log(myTouches);
 
             if (this.prevPoints.size === 2) {
                 const oldPoint1 = this.prevPoints.get(pt1.identifier);
@@ -625,7 +626,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                         // use the centerx and centery as the "new mouse position"
                         const centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2;
                         const centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2;
-                        this.pan({ clientX: centerX, clientY: centerY });
+
+                        if (!this._pullCoords[0] && !this._pullCoords[1]) { // if we are not bezel movement
+                            this.pan({ clientX: centerX, clientY: centerY });
+                        }
+
                         this._lastX = centerX;
                         this._lastY = centerY;
                     }
@@ -650,6 +655,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 const centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2;
                 this._lastX = centerX;
                 this._lastY = centerY;
+
+                // determine if we are using a bezel movement
+                if ((this.props.PanelWidth() - this._lastX) < 100 || this._lastX < 100 || (this.props.PanelHeight() - this._lastY < 100) || this._lastY < 120) { // to account for header
+                    this._pullCoords = [this._lastX, this._lastY];
+                }
+
                 this.removeMoveListeners();
                 this.addMoveListeners();
                 this.removeEndListeners();
@@ -660,12 +671,37 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     }
 
     cleanUpInteractions = () => {
+
+        if (this._pullCoords[0] !== 0 && this._pullCoords[1] !== 0) { // if bezel mvmt was activated
+            const xDiff = this._pullCoords[0] - this._lastX;
+            const yDiff = this._pullCoords[1] - this._lastY;
+
+            console.log('went thru', this._pullCoords);
+            if ((this._lastX < this._pullCoords[0]) && (yDiff < xDiff)) { // pull from right
+                console.log('pulled from right');
+                // CollectionDockingView.AddRightSplit(this.Document, undefined);
+                CollectionDockingView.AddSplit(this.Document, "right", undefined);
+            } else if ((this._lastY > this._pullCoords[1]) && (yDiff < xDiff)) { // pull from top
+                console.log('pulled from top');
+                CollectionDockingView.AddSplit(this.Document, "top", undefined);
+            } else if ((this._lastY < this._pullCoords[1]) && (yDiff > xDiff)) { // pull from bottom
+                console.log('pulled from bottom');
+                CollectionDockingView.AddSplit(this.Document, "bottom", undefined);
+            } else if ((this._lastX > this._pullCoords[0]) && (yDiff > xDiff)) { // pull from left
+                console.log('pulled from left');
+                CollectionDockingView.AddSplit(this.Document, "left", undefined);
+            }
+        }
+
+        this._pullCoords = [0, 0];
+
         document.removeEventListener("pointermove", this.onPointerMove);
         document.removeEventListener("pointerup", this.onPointerUp);
         this.removeMoveListeners();
         this.removeEndListeners();
     }
 
+
     @action
     zoom = (pointX: number, pointY: number, deltaY: number): void => {
         let deltaScale = deltaY > 0 ? (1 / 1.1) : 1.1;
-- 
cgit v1.2.3-70-g09d2


From 96b2f5f5334fb475180a095905e19e45a0414233 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sun, 9 Feb 2020 17:02:43 -0500
Subject: from added range sliders

---
 package-lock.json                                  |  78 ++++---
 package.json                                       |   1 +
 src/client/documents/DocumentTypes.ts              |   1 +
 src/client/documents/Documents.ts                  |   8 +
 .../views/collections/CollectionTimeView.tsx       |  57 ++++-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  15 +-
 src/client/views/nodes/DocumentContentsView.tsx    |   4 +-
 src/client/views/nodes/FormattedTextBox.tsx        |   2 +-
 src/client/views/nodes/KeyValueBox.scss            |   4 +-
 src/client/views/nodes/SliderBox-components.tsx    | 256 +++++++++++++++++++++
 src/client/views/nodes/SliderBox-tooltip.css       |  33 +++
 src/client/views/nodes/SliderBox.scss              |   8 +
 src/client/views/nodes/SliderBox.tsx               | 128 +++++++++++
 src/new_fields/Doc.ts                              |  22 +-
 14 files changed, 564 insertions(+), 53 deletions(-)
 create mode 100644 src/client/views/nodes/SliderBox-components.tsx
 create mode 100644 src/client/views/nodes/SliderBox-tooltip.css
 create mode 100644 src/client/views/nodes/SliderBox.scss
 create mode 100644 src/client/views/nodes/SliderBox.tsx

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

diff --git a/package-lock.json b/package-lock.json
index 379cd3337..d1c169bed 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -647,7 +647,7 @@
         },
         "@types/passport": {
             "version": "1.0.2",
-            "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.2.tgz",
+            "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.0.tgz",
             "integrity": "sha512-Pf39AYKf8q+YoONym3150cEwfUD66dtwHJWvbeOzKxnA0GZZ/vAXhNWv9vMhKyRQBQZiQyWQnhYBEBlKW6G8wg==",
             "requires": {
                 "@types/express": "*"
@@ -3776,6 +3776,11 @@
                 "type": "^1.0.1"
             }
         },
+        "d3-array": {
+            "version": "1.2.4",
+            "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz",
+            "integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw=="
+        },
         "d3-format": {
             "version": "1.4.3",
             "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.3.tgz",
@@ -5391,8 +5396,7 @@
                 },
                 "ansi-regex": {
                     "version": "2.1.1",
-                    "bundled": true,
-                    "optional": true
+                    "bundled": true
                 },
                 "aproba": {
                     "version": "1.2.0",
@@ -5410,13 +5414,11 @@
                 },
                 "balanced-match": {
                     "version": "1.0.0",
-                    "bundled": true,
-                    "optional": true
+                    "bundled": true
                 },
                 "brace-expansion": {
                     "version": "1.1.11",
                     "bundled": true,
-                    "optional": true,
                     "requires": {
                         "balanced-match": "^1.0.0",
                         "concat-map": "0.0.1"
@@ -5429,18 +5431,15 @@
                 },
                 "code-point-at": {
                     "version": "1.1.0",
-                    "bundled": true,
-                    "optional": true
+                    "bundled": true
                 },
                 "concat-map": {
                     "version": "0.0.1",
-                    "bundled": true,
-                    "optional": true
+                    "bundled": true
                 },
                 "console-control-strings": {
                     "version": "1.1.0",
-                    "bundled": true,
-                    "optional": true
+                    "bundled": true
                 },
                 "core-util-is": {
                     "version": "1.0.2",
@@ -5543,8 +5542,7 @@
                 },
                 "inherits": {
                     "version": "2.0.4",
-                    "bundled": true,
-                    "optional": true
+                    "bundled": true
                 },
                 "ini": {
                     "version": "1.3.5",
@@ -5554,7 +5552,6 @@
                 "is-fullwidth-code-point": {
                     "version": "1.0.0",
                     "bundled": true,
-                    "optional": true,
                     "requires": {
                         "number-is-nan": "^1.0.0"
                     }
@@ -5567,20 +5564,17 @@
                 "minimatch": {
                     "version": "3.0.4",
                     "bundled": true,
-                    "optional": true,
                     "requires": {
                         "brace-expansion": "^1.1.7"
                     }
                 },
                 "minimist": {
                     "version": "0.0.8",
-                    "bundled": true,
-                    "optional": true
+                    "bundled": true
                 },
                 "minipass": {
                     "version": "2.9.0",
                     "bundled": true,
-                    "optional": true,
                     "requires": {
                         "safe-buffer": "^5.1.2",
                         "yallist": "^3.0.0"
@@ -5597,7 +5591,6 @@
                 "mkdirp": {
                     "version": "0.5.1",
                     "bundled": true,
-                    "optional": true,
                     "requires": {
                         "minimist": "0.0.8"
                     }
@@ -5678,8 +5671,7 @@
                 },
                 "number-is-nan": {
                     "version": "1.0.1",
-                    "bundled": true,
-                    "optional": true
+                    "bundled": true
                 },
                 "object-assign": {
                     "version": "4.1.1",
@@ -5689,7 +5681,6 @@
                 "once": {
                     "version": "1.4.0",
                     "bundled": true,
-                    "optional": true,
                     "requires": {
                         "wrappy": "1"
                     }
@@ -5765,8 +5756,7 @@
                 },
                 "safe-buffer": {
                     "version": "5.1.2",
-                    "bundled": true,
-                    "optional": true
+                    "bundled": true
                 },
                 "safer-buffer": {
                     "version": "2.1.2",
@@ -5796,7 +5786,6 @@
                 "string-width": {
                     "version": "1.0.2",
                     "bundled": true,
-                    "optional": true,
                     "requires": {
                         "code-point-at": "^1.0.0",
                         "is-fullwidth-code-point": "^1.0.0",
@@ -5814,7 +5803,6 @@
                 "strip-ansi": {
                     "version": "3.0.1",
                     "bundled": true,
-                    "optional": true,
                     "requires": {
                         "ansi-regex": "^2.0.0"
                     }
@@ -5853,13 +5841,11 @@
                 },
                 "wrappy": {
                     "version": "1.0.2",
-                    "bundled": true,
-                    "optional": true
+                    "bundled": true
                 },
                 "yallist": {
                     "version": "3.1.1",
-                    "bundled": true,
-                    "optional": true
+                    "bundled": true
                 }
             }
         },
@@ -13518,6 +13504,27 @@
                 "tinycolor2": "^1.4.1"
             }
         },
+        "react-compound-slider": {
+            "version": "2.5.0",
+            "resolved": "https://registry.npmjs.org/react-compound-slider/-/react-compound-slider-2.5.0.tgz",
+            "integrity": "sha512-T84FtSI0bkQPmH5GaaHbL+2McOyIR6M5sqS80dqw/bHc5r2UKLYY64BWTbsL+XO0jlx7REuJJnZUBqo4eSRl7g==",
+            "requires": {
+                "@babel/runtime": "^7.7.7",
+                "d3-array": "^1.2.4",
+                "prop-types": "^15.7.2",
+                "warning": "^3.0.0"
+            },
+            "dependencies": {
+                "@babel/runtime": {
+                    "version": "7.8.4",
+                    "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.4.tgz",
+                    "integrity": "sha512-neAp3zt80trRVBI1x0azq6c57aNBqYZH8KhMm3TaB7wEI5Q4A2SHfBHE8w9gOhI/lrqxtEbXZgQIrHP+wvSGwQ==",
+                    "requires": {
+                        "regenerator-runtime": "^0.13.2"
+                    }
+                }
+            }
+        },
         "react-dimensions": {
             "version": "1.3.1",
             "resolved": "https://registry.npmjs.org/react-dimensions/-/react-dimensions-1.3.1.tgz",
@@ -13804,7 +13811,7 @@
         },
         "readable-stream": {
             "version": "2.3.6",
-            "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+            "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
             "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
             "requires": {
                 "core-util-is": "~1.0.0",
@@ -14244,7 +14251,8 @@
                 "fsevents": {
                     "version": "2.1.2",
                     "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.2.tgz",
-                    "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA=="
+                    "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==",
+                    "optional": true
                 },
                 "get-caller-file": {
                     "version": "2.0.5",
@@ -15722,7 +15730,7 @@
         },
         "strip-ansi": {
             "version": "3.0.1",
-            "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+            "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
             "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
             "requires": {
                 "ansi-regex": "^2.0.0"
@@ -17949,7 +17957,7 @@
         },
         "wrap-ansi": {
             "version": "2.1.0",
-            "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
+            "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
             "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
             "requires": {
                 "string-width": "^1.0.1",
diff --git a/package.json b/package.json
index 3b87a9a47..c71d321f4 100644
--- a/package.json
+++ b/package.json
@@ -205,6 +205,7 @@
         "react-bootstrap": "^1.0.0-beta.16",
         "react-bootstrap-dropdown-menu": "^1.1.15",
         "react-color": "^2.18.0",
+        "react-compound-slider": "^2.5.0",
         "react-dimensions": "^1.3.1",
         "react-dom": "^16.12.0",
         "react-golden-layout": "^1.0.6",
diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts
index 8f96b2fa6..6ef23ef08 100644
--- a/src/client/documents/DocumentTypes.ts
+++ b/src/client/documents/DocumentTypes.ts
@@ -14,6 +14,7 @@ export enum DocumentType {
     LINK = "link",
     LINKDOC = "linkdoc",
     BUTTON = "button",
+    SLIDER = "slider",
     TEMPLATE = "template",
     EXTENSION = "extension",
     YOUTUBE = "youtube",
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index d647b34e6..3d43c6eb2 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -37,6 +37,7 @@ import { DocumentManager } from "../util/DocumentManager";
 import DirectoryImportBox from "../util/Import & Export/DirectoryImportBox";
 import { Scripting } from "../util/Scripting";
 import { ButtonBox } from "../views/nodes/ButtonBox";
+import { SliderBox } from "../views/nodes/SliderBox";
 import { FontIconBox } from "../views/nodes/FontIconBox";
 import { SchemaHeaderField } from "../../new_fields/SchemaHeaderField";
 import { PresBox } from "../views/nodes/PresBox";
@@ -230,6 +231,9 @@ export namespace Docs {
             [DocumentType.BUTTON, {
                 layout: { view: ButtonBox, dataField: data },
             }],
+            [DocumentType.SLIDER, {
+                layout: { view: SliderBox, dataField: data },
+            }],
             [DocumentType.PRES, {
                 layout: { view: PresBox, dataField: data },
                 options: {}
@@ -559,6 +563,10 @@ export namespace Docs {
             return InstanceFromProto(Prototypes.get(DocumentType.BUTTON), undefined, { ...(options || {}) });
         }
 
+        export function SliderDocument(options?: DocumentOptions) {
+            return InstanceFromProto(Prototypes.get(DocumentType.SLIDER), undefined, { ...(options || {}) });
+        }
+
 
         export function FontIconDocument(options?: DocumentOptions) {
             return InstanceFromProto(Prototypes.get(DocumentType.FONTICON), undefined, { ...(options || {}) });
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 4983acbc2..2a45c91dd 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -2,7 +2,6 @@ import { faEdit } from "@fortawesome/free-solid-svg-icons";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { action, computed, observable, trace, runInAction } from "mobx";
 import { observer } from "mobx-react";
-import { Set } from "typescript-collections";
 import { Doc, DocListCast, Field } from "../../../new_fields/Doc";
 import { List } from "../../../new_fields/List";
 import { RichTextField } from "../../../new_fields/RichTextField";
@@ -54,7 +53,7 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
         const facets = new Set<string>();
         this.childDocs.forEach(child => Object.keys(Doc.GetProto(child)).forEach(key => facets.add(key)));
         Doc.AreProtosEqual(this.dataDoc, this.props.Document) && this.childDocs.forEach(child => Object.keys(child).forEach(key => facets.add(key)));
-        return facets.toArray();
+        return Array.from(facets);
     }
 
     /**
@@ -73,12 +72,52 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
                         docFilter.splice(index, 3);
                     }
                 }
+                const docRangeFilters = Cast(this.props.Document._docRangeFilters, listSpec("string"));
+                if (docRangeFilters) {
+                    let index: number;
+                    while ((index = docRangeFilters.findIndex(item => item === facetHeader)) !== -1) {
+                        docRangeFilters.splice(index, 3);
+                    }
+                }
             } else {
-                const newFacet = Docs.Create.TreeDocument([], { title: facetHeader, treeViewOpen: true, isFacetFilter: true });
-                const capturedVariables = { layoutDoc: this.props.Document, dataDoc: this.dataDoc };
-                const params = { layoutDoc: Doc.name, dataDoc: Doc.name, };
-                newFacet.data = ComputedField.MakeFunction(`readFacetData(layoutDoc, dataDoc, "${this.props.fieldKey}", "${facetHeader}")`, params, capturedVariables);
-                Doc.AddDocToList(facetCollection, "data", newFacet);
+                const allCollectionDocs = DocListCast(this.dataDoc[this.props.fieldKey]);
+                const facetValues = Array.from(allCollectionDocs.reduce((set, child) =>
+                    set.add(Field.toString(child[facetHeader] as Field)), new Set<string>()));
+
+                let nonNumbers = 0;
+                let minVal = Number.MAX_VALUE, maxVal = -Number.MAX_VALUE;
+                facetValues.map(val => {
+                    const num = Number(val);
+                    if (Number.isNaN(num)) {
+                        nonNumbers++;
+                    } else {
+                        minVal = Math.min(num, minVal);
+                        maxVal = Math.max(num, maxVal);
+                    }
+                });
+                if (nonNumbers / allCollectionDocs.length < .1) {
+                    const newFacet = Docs.Create.SliderDocument({ title: facetHeader });
+                    newFacet.treeViewExpandedView = "layout";
+                    newFacet.treeViewOpen = true;
+                    newFacet._sliderMin = minVal;
+                    newFacet._sliderMax = maxVal;
+                    newFacet._sliderMinThumb = minVal;
+                    newFacet._sliderMaxThumb = maxVal;
+                    newFacet.target = this.props.Document;
+                    const scriptText = `setDocFilterRange(this.target, "${facetHeader}", range)`;
+                    newFacet.onThumbChanged = ScriptField.MakeScript(scriptText, { this: Doc.name, range: "number" });
+
+                    // const capturedVariables = { layoutDoc: this.props.Document, dataDoc: this.dataDoc };
+                    // const params = { layoutDoc: Doc.name, dataDoc: Doc.name, };
+                    // newFacet.data = ComputedField.MakeFunction(`readFacetData(layoutDoc, dataDoc, "${this.props.fieldKey}", "${facetHeader}")`, params, capturedVariables);
+                    Doc.AddDocToList(facetCollection, "data", newFacet);
+                } else {
+                    const newFacet = Docs.Create.TreeDocument([], { title: facetHeader, treeViewOpen: true, isFacetFilter: true });
+                    const capturedVariables = { layoutDoc: this.props.Document, dataDoc: this.dataDoc };
+                    const params = { layoutDoc: Doc.name, dataDoc: Doc.name, };
+                    newFacet.data = ComputedField.MakeFunction(`readFacetData(layoutDoc, dataDoc, "${this.props.fieldKey}", "${facetHeader}")`, params, capturedVariables);
+                    Doc.AddDocToList(facetCollection, "data", newFacet);
+                }
             }
         }
     }
@@ -120,7 +159,7 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
             pair.layout[fieldKey] instanceof RichTextField ||
             typeof (pair.layout[fieldKey]) === "number" ||
             typeof (pair.layout[fieldKey]) === "string").map(fieldKey => keySet.add(fieldKey)));
-        keySet.toArray().map(fieldKey =>
+        Array.from(keySet).map(fieldKey =>
             docItems.push({ description: ":" + fieldKey, event: () => this.props.Document._pivotField = fieldKey, icon: "compress-arrows-alt" }));
         docItems.push({ description: ":(null)", event: () => this.props.Document._pivotField = undefined, icon: "compress-arrows-alt" })
         ContextMenu.Instance.addItem({ description: "Pivot Fields ...", subitems: docItems, icon: "eye" });
@@ -227,7 +266,7 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
                 </Flyout>
             </div>
             <div className="collectionTimeView-tree" key="tree">
-                <CollectionTreeView {...this.props} Document={facetCollection} />
+                <CollectionTreeView {...this.props} PanelWidth={() => this._facetWidth} Document={facetCollection} />
             </div>
         </div>;
     }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 2518a4a55..799627969 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -828,6 +828,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
     @computed get filterDocs() {
         const docFilters = Cast(this.props.Document._docFilter, listSpec("string"), []);
+        const docRangeFilters = Cast(this.props.Document._docRangeFilters, listSpec("string"), []);
         const clusters: { [key: string]: { [value: string]: string } } = {};
         for (let i = 0; i < docFilters.length; i += 3) {
             const [key, value, modifiers] = docFilters.slice(i, i + 3);
@@ -853,7 +854,19 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             }
             return true;
         }) : this.childDocs;
-        return filteredDocs;
+        const rangeFilteredDocs = docRangeFilters.length ? filteredDocs.filter(d => {
+            for (let i = 0; i < docRangeFilters.length; i += 3) {
+                const key = docRangeFilters[i];
+                const min = Number(docRangeFilters[i + 1]);
+                const max = Number(docRangeFilters[i + 2]);
+                const val = Cast(d[key], "number", null);
+                if (val !== undefined && (val < min || val > max)) {
+                    return false;
+                }
+            }
+            return true;
+        }) : this.childDocs;
+        return rangeFilteredDocs;
     }
     get doLayoutComputation() {
         const { newPool, computedElementData } = this.doInternalLayoutComputation;
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 3b9015994..284600bb4 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -1,7 +1,6 @@
 import { computed } from "mobx";
 import { observer } from "mobx-react";
 import { Doc } from "../../../new_fields/Doc";
-import { ScriptField } from "../../../new_fields/ScriptField";
 import { Cast, StrCast } from "../../../new_fields/Types";
 import { OmitKeys, Without } from "../../../Utils";
 import { HistogramBox } from "../../northstar/dash-nodes/HistogramBox";
@@ -14,6 +13,7 @@ import { LinkFollowBox } from "../linking/LinkFollowBox";
 import { YoutubeBox } from "./../../apis/youtube/YoutubeBox";
 import { AudioBox } from "./AudioBox";
 import { ButtonBox } from "./ButtonBox";
+import { SliderBox } from "./SliderBox";
 import { DocumentBox } from "./DocumentBox";
 import { DocumentViewProps } from "./DocumentView";
 import "./DocumentView.scss";
@@ -104,7 +104,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
             <ObserverJsxParser
                 blacklistedAttrs={[]}
                 components={{
-                    FormattedTextBox, ImageBox, IconBox, DirectoryImportBox, FontIconBox: FontIconBox, ButtonBox, FieldView,
+                    FormattedTextBox, ImageBox, IconBox, DirectoryImportBox, FontIconBox: FontIconBox, ButtonBox, SliderBox, FieldView,
                     CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebBox, KeyValueBox,
                     PDFBox, VideoBox, AudioBox, HistogramBox, PresBox, YoutubeBox, LinkFollowBox, PresElementBox, QueryBox,
                     ColorBox, DocuLinkBox, InkingStroke, DocumentBox
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 9370d3745..449dca3a1 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -574,7 +574,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
                     if (ret.frag.size > 2 && ret.start >= 0) {
                         let selection = TextSelection.near(editor.state.doc.resolve(ret.start)); // default to near the start
                         if (ret.frag.firstChild) {
-                            selection = TextSelection.between(editor.state.doc.resolve(ret.start + 2), editor.state.doc.resolve(ret.start + ret.frag.firstChild.nodeSize)); // bcz: looks better to not have the target selected
+                            selection = TextSelection.between(editor.state.doc.resolve(ret.start), editor.state.doc.resolve(ret.start + ret.frag.firstChild.nodeSize)); // bcz: looks better to not have the target selected
                         }
                         editor.dispatch(editor.state.tr.setSelection(new TextSelection(selection.$from, selection.$from)).scrollIntoView());
                         const mark = editor.state.schema.mark(this._editorView.state.schema.marks.search_highlight);
diff --git a/src/client/views/nodes/KeyValueBox.scss b/src/client/views/nodes/KeyValueBox.scss
index 6e8a36c6a..a26880c9e 100644
--- a/src/client/views/nodes/KeyValueBox.scss
+++ b/src/client/views/nodes/KeyValueBox.scss
@@ -74,7 +74,7 @@ $header-height: 30px;
 
 .keyValueBox-evenRow {
     position: relative;
-    display: inline-block;
+    display: flex;
     width:100%;
     height:$header-height;
     background: $light-color;
@@ -114,7 +114,7 @@ $header-height: 30px;
 
 .keyValueBox-oddRow {
     position: relative;
-    display: inline-block;
+    display: flex;
     width:100%;
     height:30px;
     background: $light-color-secondary;
diff --git a/src/client/views/nodes/SliderBox-components.tsx b/src/client/views/nodes/SliderBox-components.tsx
new file mode 100644
index 000000000..a38cad459
--- /dev/null
+++ b/src/client/views/nodes/SliderBox-components.tsx
@@ -0,0 +1,256 @@
+import * as React from "react";
+import { SliderItem } from "react-compound-slider";
+import "./SliderBox-tooltip.css";
+
+const { Component, Fragment } = React;
+
+// *******************************************************
+// TOOLTIP RAIL
+// *******************************************************
+const railStyle: React.CSSProperties = {
+    position: "absolute",
+    width: "100%",
+    height: 40,
+    top: -13,
+    borderRadius: 7,
+    cursor: "pointer",
+    opacity: 0.3,
+    zIndex: 300,
+    border: "1px solid grey"
+};
+
+const railCenterStyle: React.CSSProperties = {
+    position: "absolute",
+    width: "100%",
+    height: 14,
+    borderRadius: 7,
+    cursor: "pointer",
+    pointerEvents: "none",
+    backgroundColor: "rgb(155,155,155)"
+};
+
+interface TooltipRailProps {
+    activeHandleID: string;
+    getRailProps: (props: object) => object;
+    getEventData: (e: Event) => object;
+}
+
+export class TooltipRail extends Component<TooltipRailProps> {
+    state = {
+        value: null,
+        percent: null
+    };
+
+    static defaultProps = {
+        disabled: false
+    };
+
+    onMouseEnter = () => {
+        document.addEventListener("mousemove", this.onMouseMove);
+    };
+
+    onMouseLeave = () => {
+        this.setState({ value: null, percent: null });
+        document.removeEventListener("mousemove", this.onMouseMove);
+    };
+
+    onMouseMove = (e: Event) => {
+        const { activeHandleID, getEventData } = this.props;
+
+        if (activeHandleID) {
+            this.setState({ value: null, percent: null });
+        } else {
+            this.setState(getEventData(e));
+        }
+    };
+
+    render() {
+        const { value, percent } = this.state;
+        const { activeHandleID, getRailProps } = this.props;
+
+        return (
+            <Fragment>
+                {!activeHandleID && value ? (
+                    <div
+                        style={{
+                            left: `${percent}%`,
+                            position: "absolute",
+                            marginLeft: "-11px",
+                            marginTop: "-35px"
+                        }}
+                    >
+                        <div className="tooltip">
+                            <span className="tooltiptext">Value: {value}</span>
+                        </div>
+                    </div>
+                ) : null}
+                <div
+                    style={railStyle}
+                    {...getRailProps({
+                        onMouseEnter: this.onMouseEnter,
+                        onMouseLeave: this.onMouseLeave
+                    })}
+                />
+                <div style={railCenterStyle} />
+            </Fragment>
+        );
+    }
+}
+
+// *******************************************************
+// HANDLE COMPONENT
+// *******************************************************
+interface HandleProps {
+    key: string;
+    handle: SliderItem;
+    isActive: Boolean;
+    disabled?: Boolean;
+    domain: number[];
+    getHandleProps: (id: string, config: object) => object;
+}
+
+export class Handle extends Component<HandleProps> {
+    static defaultProps = {
+        disabled: false
+    };
+
+    state = {
+        mouseOver: false
+    };
+
+    onMouseEnter = () => {
+        this.setState({ mouseOver: true });
+    };
+
+    onMouseLeave = () => {
+        this.setState({ mouseOver: false });
+    };
+
+    render() {
+        const {
+            domain: [min, max],
+            handle: { id, value, percent },
+            isActive,
+            disabled,
+            getHandleProps
+        } = this.props;
+        const { mouseOver } = this.state;
+
+        return (
+            <Fragment>
+                {(mouseOver || isActive) && !disabled ? (
+                    <div
+                        style={{
+                            left: `${percent}%`,
+                            position: "absolute",
+                            marginLeft: "-11px",
+                            marginTop: "-35px"
+                        }}
+                    >
+                        <div className="tooltip">
+                            <span className="tooltiptext">Value: {value}</span>
+                        </div>
+                    </div>
+                ) : null}
+                <div
+                    role="slider"
+                    aria-valuemin={min}
+                    aria-valuemax={max}
+                    aria-valuenow={value}
+                    style={{
+                        left: `${percent}%`,
+                        position: "absolute",
+                        marginLeft: "-11px",
+                        marginTop: "-6px",
+                        zIndex: 400,
+                        width: 24,
+                        height: 24,
+                        cursor: "pointer",
+                        border: 0,
+                        borderRadius: "50%",
+                        boxShadow: "1px 1px 1px 1px rgba(0, 0, 0, 0.4)",
+                        backgroundColor: disabled ? "#666" : "#3e1db3"
+                    }}
+                    {...getHandleProps(id, {
+                        onMouseEnter: this.onMouseEnter,
+                        onMouseLeave: this.onMouseLeave
+                    })}
+                />
+            </Fragment>
+        );
+    }
+}
+
+// *******************************************************
+// TRACK COMPONENT
+// *******************************************************
+interface TrackProps {
+    source: SliderItem;
+    target: SliderItem;
+    disabled: Boolean;
+    getTrackProps: () => object;
+}
+
+export function Track({
+    source,
+    target,
+    getTrackProps,
+    disabled = false
+}: TrackProps) {
+    return (
+        <div
+            style={{
+                position: "absolute",
+                height: 14,
+                zIndex: 1,
+                backgroundColor: disabled ? "#999" : "#3e1db3",
+                borderRadius: 7,
+                cursor: "pointer",
+                left: `${source.percent}%`,
+                width: `${target.percent - source.percent}%`
+            }}
+            {...getTrackProps()}
+        />
+    );
+}
+
+// *******************************************************
+// TICK COMPONENT
+// *******************************************************
+interface TickProps {
+    tick: SliderItem;
+    count: number;
+    format: (val: number) => string;
+}
+
+const defaultFormat = (d: number) => `d`;
+
+export function Tick({ tick, count, format = defaultFormat }: TickProps) {
+    return (
+        <div>
+            <div
+                style={{
+                    position: "absolute",
+                    marginTop: 17,
+                    width: 1,
+                    height: 5,
+                    backgroundColor: "rgb(200,200,200)",
+                    left: `${tick.percent}%`
+                }}
+            />
+            <div
+                style={{
+                    position: "absolute",
+                    marginTop: 25,
+                    fontSize: 10,
+                    textAlign: "center",
+                    marginLeft: `${-(100 / count) / 2}%`,
+                    width: `${100 / count}%`,
+                    left: `${tick.percent}%`
+                }}
+            >
+                {format(tick.value)}
+            </div>
+        </div>
+    );
+}
diff --git a/src/client/views/nodes/SliderBox-tooltip.css b/src/client/views/nodes/SliderBox-tooltip.css
new file mode 100644
index 000000000..8afde8eb5
--- /dev/null
+++ b/src/client/views/nodes/SliderBox-tooltip.css
@@ -0,0 +1,33 @@
+.tooltip {
+    position: relative;
+    display: inline-block;
+    border-bottom: 1px dotted #222;
+    margin-left: 22px;
+  }
+  
+  .tooltip .tooltiptext {
+    width: 100px;
+    background-color: #222;
+    color: #fff;
+    opacity: 0.8;
+    text-align: center;
+    border-radius: 6px;
+    padding: 5px 0;
+    position: absolute;
+    z-index: 1;
+    bottom: 150%;
+    left: 50%;
+    margin-left: -60px;
+  }
+  
+  .tooltip .tooltiptext::after {
+    content: "";
+    position: absolute;
+    top: 100%;
+    left: 50%;
+    margin-left: -5px;
+    border-width: 5px;
+    border-style: solid;
+    border-color: #222 transparent transparent transparent;
+  }
+  
\ No newline at end of file
diff --git a/src/client/views/nodes/SliderBox.scss b/src/client/views/nodes/SliderBox.scss
new file mode 100644
index 000000000..4ef277d8c
--- /dev/null
+++ b/src/client/views/nodes/SliderBox.scss
@@ -0,0 +1,8 @@
+.sliderBox-outerDiv {
+    width: 100%;
+    height: 100%;
+    pointer-events: all;
+    border-radius: inherit;
+    display: flex;
+    flex-direction: column;
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/SliderBox.tsx b/src/client/views/nodes/SliderBox.tsx
new file mode 100644
index 000000000..00d3baf7c
--- /dev/null
+++ b/src/client/views/nodes/SliderBox.tsx
@@ -0,0 +1,128 @@
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { faEdit } from '@fortawesome/free-regular-svg-icons';
+import { computed } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { Handles, Rail, Slider, Tracks, Ticks } from 'react-compound-slider';
+import { Doc } from '../../../new_fields/Doc';
+import { documentSchema } from '../../../new_fields/documentSchemas';
+import { createSchema, listSpec, makeInterface } from '../../../new_fields/Schema';
+import { ScriptField } from '../../../new_fields/ScriptField';
+import { BoolCast, FieldValue, StrCast, NumCast, Cast } from '../../../new_fields/Types';
+import { DragManager } from '../../util/DragManager';
+import { ContextMenu } from '../ContextMenu';
+import { ContextMenuProps } from '../ContextMenuItem';
+import { DocComponent } from '../DocComponent';
+import './SliderBox.scss';
+import { Handle, TooltipRail, Track, Tick } from './SliderBox-components';
+import { FieldView, FieldViewProps } from './FieldView';
+
+
+library.add(faEdit as any);
+
+const ButtonSchema = createSchema({
+    onClick: ScriptField,
+    buttonParams: listSpec("string"),
+    text: "string"
+});
+
+type SliderDocument = makeInterface<[typeof ButtonSchema, typeof documentSchema]>;
+const SliderDocument = makeInterface(ButtonSchema, documentSchema);
+
+@observer
+export class SliderBox extends DocComponent<FieldViewProps, SliderDocument>(SliderDocument) {
+    public static LayoutString(fieldKey: string) { return FieldView.LayoutString(SliderBox, fieldKey); }
+    private dropDisposer?: DragManager.DragDropDisposer;
+
+    @computed get dataDoc() {
+        return this.props.DataDoc &&
+            (this.Document.isTemplateForField || BoolCast(this.props.DataDoc.isTemplateForField) ||
+                this.props.DataDoc.layout === this.props.Document) ? this.props.DataDoc : Doc.GetProto(this.props.Document);
+    }
+
+    specificContextMenu = (e: React.MouseEvent): void => {
+        const funcs: ContextMenuProps[] = [];
+        funcs.push({
+            description: "Clear Script Params", event: () => {
+                const params = FieldValue(this.Document.buttonParams);
+                params && params.map(p => this.props.Document[p] = undefined);
+            }, icon: "trash"
+        });
+
+        ContextMenu.Instance.addItem({ description: "OnClick...", subitems: funcs, icon: "asterisk" });
+    }
+    onChange = (values: readonly number[]) => {
+        Cast(this.props.Document.onThumbChanged, ScriptField, null)?.script.run({ range: values, this: this.props.Document })
+    }
+
+    render() {
+        const domain = [NumCast(this.props.Document._sliderMin), NumCast(this.props.Document._sliderMax)]
+        const defaultValues = [NumCast(this.props.Document._sliderMinThumb), NumCast(this.props.Document._sliderMaxThumb)];
+        return (
+            <div className="sliderBox-outerDiv" onContextMenu={this.specificContextMenu} onPointerDown={e => e.stopPropagation()}
+                style={{ boxShadow: this.Document.opacity === 0 ? undefined : StrCast(this.Document.boxShadow, "") }}>
+                <div className="sliderBox-mainButton" style={{
+                    background: this.Document.backgroundColor, color: this.Document.color || "black",
+                    fontSize: this.Document.fontSize, letterSpacing: this.Document.letterSpacing || ""
+                }} >
+                    <Slider
+                        mode={2}
+                        step={1}
+                        domain={domain}
+                        rootStyle={{ position: "relative", width: "100%" }}
+                        // onUpdate={this.onUpdate}
+                        onChange={this.onChange}
+                        values={defaultValues}
+                    >
+
+                        <Rail>{railProps => <TooltipRail {...railProps} />}</Rail>
+                        <Handles>
+                            {({ handles, activeHandleID, getHandleProps }) => (
+                                <div className="slider-handles">
+                                    {handles.map(handle => (
+                                        <Handle
+                                            key={handle.id}
+                                            handle={handle}
+                                            domain={domain}
+                                            isActive={handle.id === activeHandleID}
+                                            getHandleProps={getHandleProps}
+                                        />
+                                    ))}
+                                </div>
+                            )}
+                        </Handles>
+                        <Tracks left={false} right={false}>
+                            {({ tracks, getTrackProps }) => (
+                                <div className="slider-tracks">
+                                    {tracks.map(({ id, source, target }) => (
+                                        <Track
+                                            key={id}
+                                            source={source}
+                                            target={target}
+                                            disabled={false}
+                                            getTrackProps={getTrackProps}
+                                        />
+                                    ))}
+                                </div>
+                            )}
+                        </Tracks>
+                        <Ticks count={5}>
+                            {({ ticks }) => (
+                                <div className="slider-tracks">
+                                    {ticks.map((tick) => (
+                                        <Tick
+                                            key={tick.id}
+                                            tick={tick}
+                                            count={ticks.length}
+                                            format={(val: number) => val.toString()}
+                                        />
+                                    ))}
+                                </div>
+                            )}
+                        </Ticks>
+                    </Slider>
+                </div>
+            </div>
+        );
+    }
+}
\ No newline at end of file
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index b1c1fda05..3ccf4c49b 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -804,7 +804,22 @@ export namespace Doc {
         if (StrCast(doc.title).endsWith("_" + prevLayout)) doc.title = StrCast(doc.title).replace("_" + prevLayout, "");
         doc.layoutKey = deiconify || "layout";
     }
-    export function setDocFilter(container: Doc, key: string, value: any, modifiers?: string) {
+    export function setDocFilterRange(container: Doc, key: string, range?: number[]) {
+        const docFilters = Cast(container._docRangeFilters, listSpec("string"), []);
+        for (let i = 0; i < docFilters.length; i += 3) {
+            if (docFilters[i] === key) {
+                docFilters.splice(i, 3);
+                break;
+            }
+        }
+        if (range !== undefined) {
+            docFilters.push(key);
+            docFilters.push(range[0].toString());
+            docFilters.push(range[1].toString());
+            container._docRangeFilters = new List<string>(docFilters);
+        }
+    }
+    export function setDocFilter(container: Doc, key: string, value: any, modifiers?: string | number) {
         const docFilters = Cast(container._docFilter, listSpec("string"), []);
         for (let i = 0; i < docFilters.length; i += 3) {
             if (docFilters[i] === key && docFilters[i + 1] === value) {
@@ -812,7 +827,7 @@ export namespace Doc {
                 break;
             }
         }
-        if (modifiers !== undefined) {
+        if (typeof modifiers === "string") {
             docFilters.push(key);
             docFilters.push(value);
             docFilters.push(modifiers);
@@ -843,4 +858,5 @@ Scripting.addGlobal(function selectedDocs(container: Doc, excludeCollections: bo
     const docs = DocListCast(Doc.UserDoc().SelectedDocs).filter(d => !Doc.AreProtosEqual(d, container) && !d.annotationOn && d.type !== DocumentType.DOCUMENT && d.type !== DocumentType.KVP && (!excludeCollections || !Cast(d.data, listSpec(Doc), null)));
     return docs.length ? new List(docs) : prevValue;
 });
-Scripting.addGlobal(function setDocFilter(container: Doc, key: string, value: any, modifiers?: string) { Doc.setDocFilter(container, key, value, modifiers); });
\ No newline at end of file
+Scripting.addGlobal(function setDocFilter(container: Doc, key: string, value: any, modifiers?: string) { Doc.setDocFilter(container, key, value, modifiers); });
+Scripting.addGlobal(function setDocFilterRange(container: Doc, key: string, min: number, max: number) { Doc.setDocFilterRange(container, key, min, max); });
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From 33556b484c54337427a81e8ac50d05996ec876b7 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 10 Feb 2020 01:11:56 -0500
Subject: fixed filtering bug with pivot view.  fixed flyout dismissing for
 facets.

---
 src/client/documents/Documents.ts                        |  1 +
 src/client/views/TemplateMenu.tsx                        | 10 +++-------
 src/client/views/collections/CollectionTimeView.tsx      | 16 +++++++++-------
 src/client/views/collections/CollectionTreeView.tsx      | 16 ++++++++--------
 .../collectionFreeForm/CollectionFreeFormView.tsx        |  4 ++--
 5 files changed, 23 insertions(+), 24 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 82112836b..2f12b50d0 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -132,6 +132,7 @@ export interface DocumentOptions {
     strokeWidth?: number;
     color?: string;
     treeViewHideTitle?: boolean; // whether to hide the title of a tree view
+    treeViewHideHeaderFields?: boolean; // whether to hide the drop down options for tree view items.
     treeViewOpen?: boolean; // whether this document is expanded in a tree view
     treeViewChecked?: ScriptField; // script to call when a tree view checkbox is checked
     isFacetFilter?: boolean; // whether document functions as a facet filter in a tree view
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index f61eb9cd0..e03e4aa04 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -4,14 +4,11 @@ import { SelectionManager } from "../util/SelectionManager";
 import { undoBatch } from "../util/UndoManager";
 import './TemplateMenu.scss';
 import { DocumentView } from "./nodes/DocumentView";
-import { Template, Templates } from "./Templates";
+import { Template } from "./Templates";
 import React = require("react");
 import { Doc, DocListCast } from "../../new_fields/Doc";
 import { StrCast, Cast } from "../../new_fields/Types";
 import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils";
-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 }> {
@@ -48,6 +45,8 @@ export interface TemplateMenuProps {
 
 @observer
 export class TemplateMenu extends React.Component<TemplateMenuProps> {
+    _addedKeys = new ObservableSet();
+    _customRef = React.createRef<HTMLInputElement>();
     @observable private _hidden: boolean = true;
 
     toggleLayout = (e: React.ChangeEvent<HTMLInputElement>, layout: string): void => {
@@ -62,7 +61,6 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
         DocumentView.FloatDoc(topDocView, ex, ey);
     }
 
-
     @undoBatch
     @action
     toggleTemplate = (event: React.ChangeEvent<HTMLInputElement>, template: Template): void => {
@@ -105,8 +103,6 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
         });
     }
 
-    _addedKeys = new ObservableSet();
-    _customRef = React.createRef<HTMLInputElement>();
     render() {
         const layout = Doc.Layout(this.props.docViews[0].Document);
         const templateMenu: Array<JSX.Element> = [];
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 487e98b33..4306a07a1 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -1,27 +1,29 @@
 import { faEdit } from "@fortawesome/free-solid-svg-icons";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed, observable, trace, runInAction } from "mobx";
+import { action, computed, observable, runInAction, trace } from "mobx";
 import { observer } from "mobx-react";
 import { Doc, DocListCast, Field } from "../../../new_fields/Doc";
 import { List } from "../../../new_fields/List";
+import { ObjectField } from "../../../new_fields/ObjectField";
 import { RichTextField } from "../../../new_fields/RichTextField";
 import { listSpec } from "../../../new_fields/Schema";
 import { ComputedField, ScriptField } from "../../../new_fields/ScriptField";
 import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
 import { Docs } from "../../documents/Documents";
+import { DocumentType } from "../../documents/DocumentTypes";
 import { Scripting } from "../../util/Scripting";
 import { ContextMenu } from "../ContextMenu";
 import { ContextMenuProps } from "../ContextMenuItem";
 import { EditableView } from "../EditableView";
-import { anchorPoints, Flyout } from "../TemplateMenu";
 import { ViewDefBounds } from "./collectionFreeForm/CollectionFreeFormLayoutEngines";
 import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView";
 import { CollectionSubView } from "./CollectionSubView";
 import "./CollectionTimeView.scss";
-import React = require("react");
 import { CollectionTreeView } from "./CollectionTreeView";
-import { ObjectField } from "../../../new_fields/ObjectField";
-import { DocumentType } from "../../documents/DocumentTypes";
+const higflyout = require("@hig/flyout");
+export const { anchorPoints } = higflyout;
+export const Flyout = higflyout.default;
+import React = require("react");
 
 @observer
 export class CollectionTimeView extends CollectionSubView(doc => doc) {
@@ -31,7 +33,7 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
     componentDidMount() {
         const childDetailed = this.props.Document.childDetailed; // bcz: needs to be here to make sure the childDetailed layout template has been loaded when the first item is clicked;
         if (!this.props.Document._facetCollection) {
-            const facetCollection = Docs.Create.TreeDocument([], { title: "facetFilters", _yMargin: 0, treeViewHideTitle: true });
+            const facetCollection = Docs.Create.TreeDocument([], { title: "facetFilters", _yMargin: 0, treeViewHideTitle: true, treeViewHideHeaderFields: true });
             facetCollection.target = this.props.Document;
             this.props.Document.excludeFields = new List<string>(["_facetCollection", "_docFilter"]);
 
@@ -248,7 +250,7 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
         trace();
         const facetCollection = Cast(this.props.Document?._facetCollection, Doc, null);
         const flyout = (
-            <div className="collectionTimeView-flyout" style={{ width: `${this._facetWidth}` }}>
+            <div className="collectionTimeView-flyout" style={{ width: `${this._facetWidth}`, display: "block" }} onWheel={e => e.stopPropagation()}>
                 {this._allFacets.map(facet => <label className="collectionTimeView-flyout-item" key={`${facet}`} onClick={e => this.facetClick(facet)}>
                     <input type="checkbox" onChange={e => { }} checked={DocListCast((this.props.Document._facetCollection as Doc)?.data).some(d => d.title === facet)} />
                     <span className="checkmark" />
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 001064b30..33417c73d 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -60,7 +60,7 @@ export interface TreeViewProps {
     treeViewId: Doc;
     parentKey: string;
     active: (outsideReaction?: boolean) => boolean;
-    hideHeaderFields: () => boolean;
+    treeViewHideHeaderFields: () => boolean;
     preventTreeViewOpen: boolean;
     renderedIds: string[];
     onCheckedClick?: ScriptField;
@@ -292,7 +292,7 @@ class TreeView extends React.Component<TreeViewProps> {
                 contentElement = TreeView.GetChildElements(contents instanceof Doc ? [contents] :
                     DocListCast(contents), this.props.treeViewId, doc, undefined, key, this.props.containingCollection, this.props.prevSibling, addDoc, remDoc, this.move,
                     this.props.dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active,
-                    this.props.panelWidth, this.props.ChromeHeight, this.props.renderDepth, this.props.hideHeaderFields, this.props.preventTreeViewOpen,
+                    this.props.panelWidth, this.props.ChromeHeight, this.props.renderDepth, this.props.treeViewHideHeaderFields, this.props.preventTreeViewOpen,
                     [...this.props.renderedIds, doc[Id]], this.props.libraryPath, this.props.onCheckedClick);
             } else {
                 contentElement = <EditableView
@@ -335,7 +335,7 @@ class TreeView extends React.Component<TreeViewProps> {
                     TreeView.GetChildElements(docs, this.props.treeViewId, Doc.Layout(this.props.document),
                         this.templateDataDoc, expandKey, this.props.containingCollection, this.props.prevSibling, addDoc, remDoc, this.move,
                         this.props.dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform,
-                        this.props.outerXf, this.props.active, this.props.panelWidth, this.props.ChromeHeight, this.props.renderDepth, this.props.hideHeaderFields, this.props.preventTreeViewOpen,
+                        this.props.outerXf, this.props.active, this.props.panelWidth, this.props.ChromeHeight, this.props.renderDepth, this.props.treeViewHideHeaderFields, this.props.preventTreeViewOpen,
                         [...this.props.renderedIds, this.props.document[Id]], this.props.libraryPath, this.props.onCheckedClick)}
             </ul >;
         } else if (this.treeViewExpandedView === "fields") {
@@ -425,7 +425,7 @@ class TreeView extends React.Component<TreeViewProps> {
                 }} >
                 {this.editableView("title")}
             </div >
-            {this.props.hideHeaderFields() ? (null) : headerElements}
+            {this.props.treeViewHideHeaderFields() ? (null) : headerElements}
             {openRight}
         </>;
     }
@@ -464,7 +464,7 @@ class TreeView extends React.Component<TreeViewProps> {
         panelWidth: () => number,
         ChromeHeight: undefined | (() => number),
         renderDepth: number,
-        hideHeaderFields: () => boolean,
+        treeViewHideHeaderFields: () => boolean,
         preventTreeViewOpen: boolean,
         renderedIds: string[],
         libraryPath: Doc[] | undefined,
@@ -574,7 +574,7 @@ class TreeView extends React.Component<TreeViewProps> {
                 outerXf={outerXf}
                 parentKey={key}
                 active={active}
-                hideHeaderFields={hideHeaderFields}
+                treeViewHideHeaderFields={treeViewHideHeaderFields}
                 preventTreeViewOpen={preventTreeViewOpen}
                 renderedIds={renderedIds} />;
         });
@@ -625,7 +625,7 @@ export class CollectionTreeView extends CollectionSubView(Document) {
         } else {
             const layoutItems: ContextMenuProps[] = [];
             layoutItems.push({ description: (this.props.Document.preventTreeViewOpen ? "Persist" : "Abandon") + "Treeview State", event: () => this.props.Document.preventTreeViewOpen = !this.props.Document.preventTreeViewOpen, icon: "paint-brush" });
-            layoutItems.push({ description: (this.props.Document.hideHeaderFields ? "Show" : "Hide") + " Header Fields", event: () => this.props.Document.hideHeaderFields = !this.props.Document.hideHeaderFields, icon: "paint-brush" });
+            layoutItems.push({ description: (this.props.Document.treeViewHideHeaderFields ? "Show" : "Hide") + " Header Fields", event: () => this.props.Document.treeViewHideHeaderFields = !this.props.Document.treeViewHideHeaderFields, icon: "paint-brush" });
             layoutItems.push({ description: (this.props.Document.treeViewHideTitle ? "Show" : "Hide") + " Title", event: () => this.props.Document.treeViewHideTitle = !this.props.Document.treeViewHideTitle, icon: "paint-brush" });
             ContextMenu.Instance.addItem({ description: "Treeview Options ...", subitems: layoutItems, icon: "eye" });
         }
@@ -732,7 +732,7 @@ export class CollectionTreeView extends CollectionSubView(Document) {
                     {
                         TreeView.GetChildElements(this.childDocs, this.props.Document, this.props.Document, this.props.DataDoc, this.props.fieldKey, this.props.ContainingCollectionDoc, undefined, addDoc, this.remove,
                             moveDoc, dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform,
-                            this.outerXf, this.props.active, this.props.PanelWidth, this.props.ChromeHeight, this.props.renderDepth, () => BoolCast(this.props.Document.hideHeaderFields),
+                            this.outerXf, this.props.active, this.props.PanelWidth, this.props.ChromeHeight, this.props.renderDepth, () => BoolCast(this.props.Document.treeViewHideHeaderFields),
                             BoolCast(this.props.Document.preventTreeViewOpen), [], this.props.LibraryPath, ScriptCast(this.props.Document.onCheckedClick))
                     }
                 </ul>
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 799627969..17d06f50c 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -854,7 +854,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             }
             return true;
         }) : this.childDocs;
-        const rangeFilteredDocs = docRangeFilters.length ? filteredDocs.filter(d => {
+        const rangeFilteredDocs = filteredDocs.filter(d => {
             for (let i = 0; i < docRangeFilters.length; i += 3) {
                 const key = docRangeFilters[i];
                 const min = Number(docRangeFilters[i + 1]);
@@ -865,7 +865,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 }
             }
             return true;
-        }) : this.childDocs;
+        });
         return rangeFilteredDocs;
     }
     get doLayoutComputation() {
-- 
cgit v1.2.3-70-g09d2


From a411e92f6e7b486108f5d06564a2cdbbe91ae5ad Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Mon, 10 Feb 2020 10:17:25 -0500
Subject: tweaks to pivot view layout and filtering.

---
 package-lock.json                                  | 7102 ++++++++++----------
 package.json                                       |    6 +-
 .../views/collections/CollectionTimeView.scss      |    1 -
 .../views/collections/CollectionTimeView.tsx       |   27 +-
 .../views/collections/CollectionTreeView.tsx       |    2 +-
 src/client/views/collections/CollectionView.tsx    |    4 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |    2 +-
 src/client/views/nodes/DocumentView.tsx            |    4 +-
 src/new_fields/Doc.ts                              |    4 +-
 9 files changed, 3594 insertions(+), 3558 deletions(-)

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

diff --git a/package-lock.json b/package-lock.json
index 4f8f786a8..d2db4f72d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,7 +9,7 @@
             "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz",
             "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==",
             "requires": {
-                "@babel/highlight": "^7.0.0"
+                "@babel/highlight": "7.5.0"
             }
         },
         "@babel/helper-module-imports": {
@@ -17,7 +17,7 @@
             "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.7.4.tgz",
             "integrity": "sha512-dGcrX6K9l8258WFjyDLJwuVKxR4XZfU0/vTUgOQYWEnRD8mgr+p4d6fCUMq/ys0h4CCt/S5JhbvtyErjWouAUQ==",
             "requires": {
-                "@babel/types": "^7.7.4"
+                "@babel/types": "7.7.4"
             }
         },
         "@babel/highlight": {
@@ -25,9 +25,9 @@
             "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.5.0.tgz",
             "integrity": "sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ==",
             "requires": {
-                "chalk": "^2.0.0",
-                "esutils": "^2.0.2",
-                "js-tokens": "^4.0.0"
+                "chalk": "2.4.2",
+                "esutils": "2.0.2",
+                "js-tokens": "4.0.0"
             },
             "dependencies": {
                 "ansi-styles": {
@@ -35,7 +35,7 @@
                     "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
                     "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
                     "requires": {
-                        "color-convert": "^1.9.0"
+                        "color-convert": "1.9.3"
                     }
                 },
                 "chalk": {
@@ -43,9 +43,9 @@
                     "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
                     "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
                     "requires": {
-                        "ansi-styles": "^3.2.1",
-                        "escape-string-regexp": "^1.0.5",
-                        "supports-color": "^5.3.0"
+                        "ansi-styles": "3.2.1",
+                        "escape-string-regexp": "1.0.5",
+                        "supports-color": "5.5.0"
                     }
                 },
                 "supports-color": {
@@ -53,7 +53,7 @@
                     "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
                     "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
                     "requires": {
-                        "has-flag": "^3.0.0"
+                        "has-flag": "3.0.0"
                     }
                 }
             }
@@ -63,7 +63,7 @@
             "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.5.5.tgz",
             "integrity": "sha512-28QvEGyQyNkB0/m2B4FU7IEZGK2NUrcMtT6BZEFALTguLk+AUT6ofsHtPk5QyjAdUkpMJ+/Em+quwz4HOt30AQ==",
             "requires": {
-                "regenerator-runtime": "^0.13.2"
+                "regenerator-runtime": "0.13.3"
             }
         },
         "@babel/types": {
@@ -71,9 +71,9 @@
             "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.4.tgz",
             "integrity": "sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==",
             "requires": {
-                "esutils": "^2.0.2",
-                "lodash": "^4.17.13",
-                "to-fast-properties": "^2.0.0"
+                "esutils": "2.0.2",
+                "lodash": "4.17.15",
+                "to-fast-properties": "2.0.0"
             }
         },
         "@emotion/cache": {
@@ -106,7 +106,7 @@
                 "@emotion/memoize": "0.7.3",
                 "@emotion/unitless": "0.7.4",
                 "@emotion/utils": "0.11.2",
-                "csstype": "^2.5.7"
+                "csstype": "2.6.6"
             }
         },
         "@emotion/sheet": {
@@ -144,7 +144,7 @@
             "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free-solid/-/fontawesome-free-solid-5.0.13.tgz",
             "integrity": "sha512-b+krVnqkdDt52Yfev0x0ZZgtxBQsLw00Zfa3uaVWIDzpNZVtrEXuxldUSUaN/ihgGhSNi8VpvDAdNPVgCKOSxw==",
             "requires": {
-                "@fortawesome/fontawesome-common-types": "^0.1.7"
+                "@fortawesome/fontawesome-common-types": "0.1.7"
             }
         },
         "@fortawesome/fontawesome-svg-core": {
@@ -152,7 +152,7 @@
             "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.26.tgz",
             "integrity": "sha512-3Dfd/v2IztP1TxKOxZiB5+4kaOZK9mNy0KU1vVK7nFlPWz3gzxrCWB+AloQhQUoJ8HhGqbzjliK89Vl7PExGbw==",
             "requires": {
-                "@fortawesome/fontawesome-common-types": "^0.2.26"
+                "@fortawesome/fontawesome-common-types": "0.2.26"
             },
             "dependencies": {
                 "@fortawesome/fontawesome-common-types": {
@@ -167,7 +167,7 @@
             "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-5.12.0.tgz",
             "integrity": "sha512-50uCFzVUki3wfmFmrMNLFhOt8dP6YZ53zwR4dK9FR7Lwq1IVHXnSBb8MtGLe3urLJ2sA+CSu7Pc7s3i6/zLxmA==",
             "requires": {
-                "@fortawesome/fontawesome-common-types": "^0.2.26"
+                "@fortawesome/fontawesome-common-types": "0.2.26"
             },
             "dependencies": {
                 "@fortawesome/fontawesome-common-types": {
@@ -182,7 +182,7 @@
             "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-5.12.0.tgz",
             "integrity": "sha512-FAvpmylTs0PosHwHrWPQX6/7ODc9M11kCE6AOAujFufDYzqTj2cPHT4yJO7zTEkKdAbbusJzbWpnOboMuyjeQA==",
             "requires": {
-                "@fortawesome/fontawesome-common-types": "^0.2.26"
+                "@fortawesome/fontawesome-common-types": "0.2.26"
             },
             "dependencies": {
                 "@fortawesome/fontawesome-common-types": {
@@ -197,7 +197,7 @@
             "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.12.0.tgz",
             "integrity": "sha512-CnpsWs6GhTs9ekNB3d8rcO5HYqRkXbYKf2YNiAlTWbj5eVlPqsd/XH1F9If8jkcR1aegryAbln/qYeKVZzpM0g==",
             "requires": {
-                "@fortawesome/fontawesome-common-types": "^0.2.26"
+                "@fortawesome/fontawesome-common-types": "0.2.26"
             },
             "dependencies": {
                 "@fortawesome/fontawesome-common-types": {
@@ -212,7 +212,7 @@
             "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.8.tgz",
             "integrity": "sha512-I5h9YQg/ePA3Br9ISS18fcwOYmzQYDSM1ftH03/8nHkiqIVHtUyQBw482+60dnzvlr82gHt3mGm+nDUp159FCw==",
             "requires": {
-                "prop-types": "^15.5.10"
+                "prop-types": "15.7.2"
             }
         },
         "@hig/flyout": {
@@ -220,10 +220,10 @@
             "resolved": "https://registry.npmjs.org/@hig/flyout/-/flyout-1.2.0.tgz",
             "integrity": "sha512-/maJI6COH1UyUX3LCa0muG2VmPiXBtacyz8RAdLLcDIgeTpmyvxpb0FFZI/ib2HUbO2iILBN7qnXiMJ4YNJ/bQ==",
             "requires": {
-                "@hig/utils": "^0.4.0",
-                "emotion": "^10.0.0",
-                "prop-types": "^15.7.1",
-                "react-transition-group": "^2.3.1"
+                "@hig/utils": "0.4.0",
+                "emotion": "10.0.23",
+                "prop-types": "15.7.2",
+                "react-transition-group": "2.9.0"
             }
         },
         "@hig/theme-context": {
@@ -231,8 +231,8 @@
             "resolved": "https://registry.npmjs.org/@hig/theme-context/-/theme-context-2.1.3.tgz",
             "integrity": "sha512-c0Ju+Z8C532ZZtjwOLzN+XeO+pL3kqUawu6ZG3J084MH5RM9W8JCKyMf4D9Qr38jFWoiX6u8yiSxxjV/mz9Sqw==",
             "requires": {
-                "create-react-context": "^0.2.3",
-                "prop-types": "^15.6.1"
+                "create-react-context": "0.2.3",
+                "prop-types": "15.7.2"
             }
         },
         "@hig/theme-data": {
@@ -240,7 +240,7 @@
             "resolved": "https://registry.npmjs.org/@hig/theme-data/-/theme-data-2.13.0.tgz",
             "integrity": "sha512-ZU6GWRdVpU1baeDb4F/1BGzdVkdamFSrr+1DuRpwD4TbhFa5nXTT+xfyX20ZUf3eO5KP68QSAKFMaMPYbfEGQA==",
             "requires": {
-                "tinycolor2": "^1.4.1"
+                "tinycolor2": "1.4.1"
             }
         },
         "@hig/utils": {
@@ -248,8 +248,8 @@
             "resolved": "https://registry.npmjs.org/@hig/utils/-/utils-0.4.0.tgz",
             "integrity": "sha512-EQnMGZKdPh9UJaBUKLKXp92sSoCo+PTpgrGNd8q+71uRFdD0udMu/+yeVekTEtNOJcCk1gnKfyg1rRvIbTcpRw==",
             "requires": {
-                "emotion": "^10.0.0",
-                "lodash.memoize": "^4.1.2"
+                "emotion": "10.0.23",
+                "lodash.memoize": "4.1.2"
             }
         },
         "@icons/material": {
@@ -277,8 +277,8 @@
             "resolved": "https://registry.npmjs.org/@trendmicro/react-buttons/-/react-buttons-1.3.1.tgz",
             "integrity": "sha512-9zvt/fdkqCb9kxUdZnvTZKmbmykM2wDQ3VEJFtztGcKAkm4Wkq4oZOQLJXKfUQ1vX3w+YDJob18LkNOzaHI1UQ==",
             "requires": {
-                "classnames": "^2.2.5",
-                "prop-types": "^15.5.8"
+                "classnames": "2.2.6",
+                "prop-types": "15.7.2"
             }
         },
         "@trendmicro/react-dropdown": {
@@ -286,13 +286,13 @@
             "resolved": "https://registry.npmjs.org/@trendmicro/react-dropdown/-/react-dropdown-1.3.0.tgz",
             "integrity": "sha512-KwL0ksEZPay7qNsiGcPQ3aGmyfJCcUuIjiD9HZs6L66ScwSRoFkDlAjMTlRVLFcYVNhpuyUH4pPiFlKQQzDHGQ==",
             "requires": {
-                "@trendmicro/react-buttons": "^1.3.0",
-                "chained-function": "^0.5.0",
-                "classnames": "^2.2.5",
-                "dom-helpers": "^3.3.1",
-                "prop-types": "^15.6.0",
-                "uncontrollable": "^5.0.0",
-                "warning": "^3.0.0"
+                "@trendmicro/react-buttons": "1.3.1",
+                "chained-function": "0.5.0",
+                "classnames": "2.2.6",
+                "dom-helpers": "3.4.0",
+                "prop-types": "15.7.2",
+                "uncontrollable": "5.1.0",
+                "warning": "3.0.0"
             }
         },
         "@types/adm-zip": {
@@ -300,7 +300,7 @@
             "resolved": "https://registry.npmjs.org/@types/adm-zip/-/adm-zip-0.4.32.tgz",
             "integrity": "sha512-hv1O7ySn+XvP5OeDQcJFWwVb2v+GFGO1A9aMTQ5B/bzxb7WW21O8iRhVdsKKr8QwuiagzGmPP+gsUAYZ6bRddQ==",
             "requires": {
-                "@types/node": "*"
+                "@types/node": "10.17.13"
             }
         },
         "@types/animejs": {
@@ -318,7 +318,7 @@
             "resolved": "https://registry.npmjs.org/@types/archiver/-/archiver-3.0.0.tgz",
             "integrity": "sha512-orghAMOF+//wSg4ru2znk6jt0eIPvKTtMVLH7XcYcjbcRyAXRClDlh27QVdqnAvVM37yu9xDP6Nh7egRhNr8tQ==",
             "requires": {
-                "@types/glob": "*"
+                "@types/glob": "7.1.1"
             }
         },
         "@types/async": {
@@ -336,7 +336,7 @@
             "resolved": "https://registry.npmjs.org/@types/babylon/-/babylon-6.16.5.tgz",
             "integrity": "sha512-xH2e58elpj1X4ynnKp9qSnWlsRTIs6n3tgLGNfwAGHwePw0mulHQllV34n0T25uYSu1k0hRKkWXF890B1yS47w==",
             "requires": {
-                "@types/babel-types": "*"
+                "@types/babel-types": "7.0.7"
             }
         },
         "@types/bcrypt-nodejs": {
@@ -354,8 +354,8 @@
             "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.17.1.tgz",
             "integrity": "sha512-RoX2EZjMiFMjZh9lmYrwgoP9RTpAjSHiJxdp4oidAQVO02T7HER3xj9UKue5534ULWeqVEkujhWcyvUce+d68w==",
             "requires": {
-                "@types/connect": "*",
-                "@types/node": "*"
+                "@types/connect": "3.4.32",
+                "@types/node": "10.17.13"
             }
         },
         "@types/bson": {
@@ -363,7 +363,7 @@
             "resolved": "https://registry.npmjs.org/@types/bson/-/bson-4.0.1.tgz",
             "integrity": "sha512-K6VAEdLVJFBxKp8m5cRTbUfeZpuSvOuLKJLrgw9ANIXo00RiyGzgH4BKWWR4F520gV4tWmxG7q9sKQRVDuzrBw==",
             "requires": {
-                "@types/node": "*"
+                "@types/node": "10.17.13"
             }
         },
         "@types/caseless": {
@@ -386,7 +386,7 @@
             "resolved": "https://registry.npmjs.org/@types/color/-/color-3.0.1.tgz",
             "integrity": "sha512-oeUWVaAwI+xINDUx+3F2vJkl/vVB03VChFF/Gl3iQCdbcakjuoJyMOba+3BXRtnBhxZ7uBYqQBi9EpLnvSoztA==",
             "requires": {
-                "@types/color-convert": "*"
+                "@types/color-convert": "1.9.0"
             }
         },
         "@types/color-convert": {
@@ -394,7 +394,7 @@
             "resolved": "https://registry.npmjs.org/@types/color-convert/-/color-convert-1.9.0.tgz",
             "integrity": "sha512-OKGEfULrvSL2VRbkl/gnjjgbbF7ycIlpSsX7Nkab4MOWi5XxmgBYvuiQ7lcCFY5cPDz7MUNaKgxte2VRmtr4Fg==",
             "requires": {
-                "@types/color-name": "*"
+                "@types/color-name": "1.1.1"
             }
         },
         "@types/color-name": {
@@ -407,7 +407,7 @@
             "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.32.tgz",
             "integrity": "sha512-4r8qa0quOvh7lGD0pre62CAb1oni1OO6ecJLGCezTmhQ8Fz50Arx9RUszryR8KlgK6avuSXvviL6yWyViQABOg==",
             "requires": {
-                "@types/node": "*"
+                "@types/node": "10.17.13"
             }
         },
         "@types/connect-flash": {
@@ -415,7 +415,7 @@
             "resolved": "https://registry.npmjs.org/@types/connect-flash/-/connect-flash-0.0.34.tgz",
             "integrity": "sha512-QC93TwnTZ0sk//bfT81o7U4GOedbOZAcgvqi0v1vJqCESC8tqIVnhzB1CHiAUBUWFjoxG5JQF0TYaNa6DMb6Ig==",
             "requires": {
-                "@types/express": "*"
+                "@types/express": "4.17.2"
             }
         },
         "@types/cookie-parser": {
@@ -423,7 +423,7 @@
             "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.2.tgz",
             "integrity": "sha512-uwcY8m6SDQqciHsqcKDGbo10GdasYsPCYkH3hVegj9qAah6pX5HivOnOuI3WYmyQMnOATV39zv/Ybs0bC/6iVg==",
             "requires": {
-                "@types/express": "*"
+                "@types/express": "4.17.2"
             }
         },
         "@types/cookie-session": {
@@ -431,8 +431,8 @@
             "resolved": "https://registry.npmjs.org/@types/cookie-session/-/cookie-session-2.0.37.tgz",
             "integrity": "sha512-h8uZLDGyfAgER6kHbHlYWm1g/P/7zCBMOW6yT5/fQydVJxByJD4tohSvHBzJrGoLVmQJefQdfwuNkKb23cq29Q==",
             "requires": {
-                "@types/express": "*",
-                "@types/keygrip": "*"
+                "@types/express": "4.17.2",
+                "@types/keygrip": "1.0.1"
             }
         },
         "@types/d3-format": {
@@ -445,7 +445,7 @@
             "resolved": "https://registry.npmjs.org/@types/dotenv/-/dotenv-6.1.1.tgz",
             "integrity": "sha512-ftQl3DtBvqHl9L16tpqqzA4YzCSXZfi7g8cQceTz5rOlYtk/IZbFjAv3mLOQlNIgOaylCQWQoBdDQHPgEBJPHg==",
             "requires": {
-                "@types/node": "*"
+                "@types/node": "10.17.13"
             }
         },
         "@types/events": {
@@ -458,7 +458,7 @@
             "resolved": "https://registry.npmjs.org/@types/exif/-/exif-0.6.0.tgz",
             "integrity": "sha512-TyXIoevHn10FjPnCbNfpFlgb44c5KPsCbdWaNf59T76fKOl6YWfBQTmlt84kI7GtY4VuG9aW0qlEEmMuNDldoQ==",
             "requires": {
-                "@types/node": "*"
+                "@types/node": "10.17.13"
             }
         },
         "@types/express": {
@@ -466,9 +466,9 @@
             "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.2.tgz",
             "integrity": "sha512-5mHFNyavtLoJmnusB8OKJ5bshSzw+qkMIBAobLrIM48HJvunFva9mOa6aBwh64lBFyNwBbs0xiEFuj4eU/NjCA==",
             "requires": {
-                "@types/body-parser": "*",
-                "@types/express-serve-static-core": "*",
-                "@types/serve-static": "*"
+                "@types/body-parser": "1.17.1",
+                "@types/express-serve-static-core": "4.17.1",
+                "@types/serve-static": "1.13.3"
             }
         },
         "@types/express-flash": {
@@ -476,8 +476,8 @@
             "resolved": "https://registry.npmjs.org/@types/express-flash/-/express-flash-0.0.0.tgz",
             "integrity": "sha512-zs1xXRIZOjghUBriJPSnhPmfDpqf/EQxT21ggi/9XZ9/RHYrUi+5vK2jnQrP2pD1abbuZvm7owLICiNCLBQzEQ==",
             "requires": {
-                "@types/connect-flash": "*",
-                "@types/express": "*"
+                "@types/connect-flash": "0.0.34",
+                "@types/express": "4.17.2"
             }
         },
         "@types/express-serve-static-core": {
@@ -485,8 +485,8 @@
             "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.1.tgz",
             "integrity": "sha512-9e7jj549ZI+RxY21Cl0t8uBnWyb22HzILupyHZjYEVK//5TT/1bZodU+yUbLnPdoYViBBnNWbxp4zYjGV0zUGw==",
             "requires": {
-                "@types/node": "*",
-                "@types/range-parser": "*"
+                "@types/node": "10.17.13",
+                "@types/range-parser": "1.2.3"
             }
         },
         "@types/express-session": {
@@ -494,8 +494,8 @@
             "resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.15.16.tgz",
             "integrity": "sha512-vWQpNt9t/zc4bTX+Ow5powZb9n3NwOM0SYsAJ7PYj5vliB6FA40ye5sW5fZTw8+ekbzJf/sgvtQocf7IryJBJw==",
             "requires": {
-                "@types/express": "*",
-                "@types/node": "*"
+                "@types/express": "4.17.2",
+                "@types/node": "10.17.13"
             }
         },
         "@types/express-validator": {
@@ -503,7 +503,7 @@
             "resolved": "https://registry.npmjs.org/@types/express-validator/-/express-validator-3.0.0.tgz",
             "integrity": "sha512-LusnB0YhTXpBT25PXyGPQlK7leE1e41Vezq1hHEUwjfkopM1Pkv2X2Ppxqh9c+w/HZ6Udzki8AJotKNjDTGdkQ==",
             "requires": {
-                "express-validator": "*"
+                "express-validator": "5.3.1"
             }
         },
         "@types/formidable": {
@@ -511,8 +511,8 @@
             "resolved": "https://registry.npmjs.org/@types/formidable/-/formidable-1.0.31.tgz",
             "integrity": "sha512-dIhM5t8lRP0oWe2HF8MuPvdd1TpPTjhDMAqemcq6oIZQCBQTovhBAdTQ5L5veJB4pdQChadmHuxtB0YzqvfU3Q==",
             "requires": {
-                "@types/events": "*",
-                "@types/node": "*"
+                "@types/events": "3.0.0",
+                "@types/node": "10.17.13"
             }
         },
         "@types/gapi": {
@@ -525,9 +525,9 @@
             "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz",
             "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==",
             "requires": {
-                "@types/events": "*",
-                "@types/minimatch": "*",
-                "@types/node": "*"
+                "@types/events": "3.0.0",
+                "@types/minimatch": "3.0.3",
+                "@types/node": "10.17.13"
             }
         },
         "@types/jquery": {
@@ -535,7 +535,7 @@
             "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.3.31.tgz",
             "integrity": "sha512-Lz4BAJihoFw5nRzKvg4nawXPzutkv7wmfQ5121avptaSIXlDNJCUuxZxX/G+9EVidZGuO0UBlk+YjKbwRKJigg==",
             "requires": {
-                "@types/sizzle": "*"
+                "@types/sizzle": "2.3.2"
             }
         },
         "@types/jquery-awesome-cursor": {
@@ -543,7 +543,7 @@
             "resolved": "https://registry.npmjs.org/@types/jquery-awesome-cursor/-/jquery-awesome-cursor-0.3.0.tgz",
             "integrity": "sha512-tNou39eBTgyQtQGzcynUblExZdZiDqg5xuorANsoIfwBRBZZpHOP8wT/iDSR/qSq2rsu1KuQEfoC8z2L9YSp8A==",
             "requires": {
-                "@types/jquery": "*"
+                "@types/jquery": "3.3.31"
             }
         },
         "@types/jsonwebtoken": {
@@ -551,7 +551,7 @@
             "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.3.7.tgz",
             "integrity": "sha512-B5SSifLkjB0ns7VXpOOtOUlynE78/hKcY8G8pOAhkLJZinwofIBYqz555nRj2W9iDWZqFhK5R+7NZDaRmKWAoQ==",
             "requires": {
-                "@types/node": "*"
+                "@types/node": "10.17.13"
             }
         },
         "@types/keygrip": {
@@ -570,7 +570,7 @@
             "integrity": "sha512-j5AcZo7dbMxHoOimcHEIh0JZe5e1b8q8AqGSpZJrYc7xOgCIP79cIjTdx5jSDLtySnQDwkDTqwlC7Xw7uXw7qg==",
             "dev": true,
             "requires": {
-                "@types/node": "*"
+                "@types/node": "10.17.13"
             }
         },
         "@types/mime": {
@@ -588,7 +588,7 @@
             "resolved": "https://registry.npmjs.org/@types/mobile-detect/-/mobile-detect-1.3.4.tgz",
             "integrity": "sha512-MGBTvT5c7aH8eX6szFYP3dWPryNLt5iGlo31XNaJtt8o6jsg6tjn99eEMq9l8T6cPZymsr+J4Jth8+/G/04ZDw==",
             "requires": {
-                "mobile-detect": "*"
+                "mobile-detect": "1.4.4"
             }
         },
         "@types/mocha": {
@@ -601,8 +601,8 @@
             "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.3.14.tgz",
             "integrity": "sha512-Ie0Fjoifm/TPY2rNOgixzhNSjDgxgR0dMKQk9XqUXHnkfuw26SpbMXjwECfxSnEdG1bH6bIlpLIK7HvGHQhzqg==",
             "requires": {
-                "@types/bson": "*",
-                "@types/node": "*"
+                "@types/bson": "4.0.1",
+                "@types/node": "10.17.13"
             }
         },
         "@types/mongoose": {
@@ -610,8 +610,8 @@
             "resolved": "https://registry.npmjs.org/@types/mongoose/-/mongoose-5.5.43.tgz",
             "integrity": "sha512-ZZ/KnIyl81zrTfjwcbLBgb8bR9dnYJDQfcaEpOAz2B7cv1bm8FyOPonKcnAmbDHCTWkAFjsF/J3JhnLtUdWZSg==",
             "requires": {
-                "@types/mongodb": "*",
-                "@types/node": "*"
+                "@types/mongodb": "3.3.14",
+                "@types/node": "10.17.13"
             }
         },
         "@types/node": {
@@ -624,7 +624,7 @@
             "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-4.6.8.tgz",
             "integrity": "sha512-IX1P3bxDP1VIdZf6/kIWYNmSejkYm9MOyMEtoDFi4DVzKjJ3kY4GhOcOAKs6lZRjqVVmF9UjPOZXuQczlpZThw==",
             "requires": {
-                "@types/node": "*"
+                "@types/node": "10.17.13"
             }
         },
         "@types/oauth": {
@@ -632,7 +632,7 @@
             "resolved": "https://registry.npmjs.org/@types/oauth/-/oauth-0.9.1.tgz",
             "integrity": "sha512-a1iY62/a3yhZ7qH7cNUsxoI3U/0Fe9+RnuFrpTKr+0WVOzbKlSLojShCKe20aOD1Sppv+i8Zlq0pLDuTJnwS4A==",
             "requires": {
-                "@types/node": "*"
+                "@types/node": "10.17.13"
             }
         },
         "@types/orderedmap": {
@@ -647,10 +647,10 @@
         },
         "@types/passport": {
             "version": "1.0.2",
-            "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.0.tgz",
+            "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.2.tgz",
             "integrity": "sha512-Pf39AYKf8q+YoONym3150cEwfUD66dtwHJWvbeOzKxnA0GZZ/vAXhNWv9vMhKyRQBQZiQyWQnhYBEBlKW6G8wg==",
             "requires": {
-                "@types/express": "*"
+                "@types/express": "4.17.2"
             }
         },
         "@types/passport-google-oauth20": {
@@ -658,9 +658,9 @@
             "resolved": "https://registry.npmjs.org/@types/passport-google-oauth20/-/passport-google-oauth20-2.0.3.tgz",
             "integrity": "sha512-6EUEGzEg4acwowvgR/yVZIj8S2Kkwc6JmlY2/wnM1wJHNz20o7s1TIGrxnah8ymLgJasYDpy95P3TMMqlmetPw==",
             "requires": {
-                "@types/express": "*",
-                "@types/passport": "*",
-                "@types/passport-oauth2": "*"
+                "@types/express": "4.17.2",
+                "@types/passport": "1.0.2",
+                "@types/passport-oauth2": "1.4.8"
             }
         },
         "@types/passport-local": {
@@ -668,9 +668,9 @@
             "resolved": "https://registry.npmjs.org/@types/passport-local/-/passport-local-1.0.33.tgz",
             "integrity": "sha512-+rn6ZIxje0jZ2+DAiWFI8vGG7ZFKB0hXx2cUdMmudSWsigSq6ES7Emso46r4HJk0qCgrZVfI8sJiM7HIYf4SbA==",
             "requires": {
-                "@types/express": "*",
-                "@types/passport": "*",
-                "@types/passport-strategy": "*"
+                "@types/express": "4.17.2",
+                "@types/passport": "1.0.2",
+                "@types/passport-strategy": "0.2.35"
             }
         },
         "@types/passport-oauth2": {
@@ -678,9 +678,9 @@
             "resolved": "https://registry.npmjs.org/@types/passport-oauth2/-/passport-oauth2-1.4.8.tgz",
             "integrity": "sha512-tlX16wyFE5YJR2pHpZ308dgB1MV9/Ra2wfQh71eWk+/umPoD1Rca2D4N5M27W7nZm1wqUNGTk1I864nHvEgiFA==",
             "requires": {
-                "@types/express": "*",
-                "@types/oauth": "*",
-                "@types/passport": "*"
+                "@types/express": "4.17.2",
+                "@types/oauth": "0.9.1",
+                "@types/passport": "1.0.2"
             }
         },
         "@types/passport-strategy": {
@@ -688,8 +688,8 @@
             "resolved": "https://registry.npmjs.org/@types/passport-strategy/-/passport-strategy-0.2.35.tgz",
             "integrity": "sha512-o5D19Jy2XPFoX2rKApykY15et3Apgax00RRLf0RUotPDUsYrQa7x4howLYr9El2mlUApHmCMv5CZ1IXqKFQ2+g==",
             "requires": {
-                "@types/express": "*",
-                "@types/passport": "*"
+                "@types/express": "4.17.2",
+                "@types/passport": "1.0.2"
             }
         },
         "@types/pdfjs-dist": {
@@ -707,9 +707,9 @@
             "resolved": "https://registry.npmjs.org/@types/prosemirror-commands/-/prosemirror-commands-1.0.1.tgz",
             "integrity": "sha512-GeE12m8VT9N1JrzoY//946IX8ZyQOLNmvryJ+BNQs/HvhmXW9EWOcWUE6OBRtxK7Y8SrzSOwx4XmqSgVmK3tGQ==",
             "requires": {
-                "@types/prosemirror-model": "*",
-                "@types/prosemirror-state": "*",
-                "@types/prosemirror-view": "*"
+                "@types/prosemirror-model": "1.7.2",
+                "@types/prosemirror-state": "1.2.3",
+                "@types/prosemirror-view": "1.11.2"
             }
         },
         "@types/prosemirror-history": {
@@ -717,8 +717,8 @@
             "resolved": "https://registry.npmjs.org/@types/prosemirror-history/-/prosemirror-history-1.0.1.tgz",
             "integrity": "sha512-BYyPJlWDo3VEnWS5X2DCHXrrAKEjdbCe1DUjGL6R/8hmwMFe3iMJGYdBkOXU1FfkTpw7Z+PlwY/pMyeelVydmg==",
             "requires": {
-                "@types/prosemirror-model": "*",
-                "@types/prosemirror-state": "*"
+                "@types/prosemirror-model": "1.7.2",
+                "@types/prosemirror-state": "1.2.3"
             }
         },
         "@types/prosemirror-inputrules": {
@@ -726,8 +726,8 @@
             "resolved": "https://registry.npmjs.org/@types/prosemirror-inputrules/-/prosemirror-inputrules-1.0.2.tgz",
             "integrity": "sha512-bKFneQUPnkZmzCJ1uoitpKH6PFW0hc4q55NsC7mFUCvX0eZl0GRKxyfV47jkJbsbyUQoO/QFv0WwLDz2bo15sA==",
             "requires": {
-                "@types/prosemirror-model": "*",
-                "@types/prosemirror-state": "*"
+                "@types/prosemirror-model": "1.7.2",
+                "@types/prosemirror-state": "1.2.3"
             }
         },
         "@types/prosemirror-keymap": {
@@ -735,8 +735,8 @@
             "resolved": "https://registry.npmjs.org/@types/prosemirror-keymap/-/prosemirror-keymap-1.0.1.tgz",
             "integrity": "sha512-8IjM8ySmoZps9Tn+aKfB4ZR6zoNOjeQfAc9YLQujYXHJB6tdGWV0cbTuoT4QmZOR1iecN1EJ6E9RiRUBk796kQ==",
             "requires": {
-                "@types/prosemirror-state": "*",
-                "@types/prosemirror-view": "*"
+                "@types/prosemirror-state": "1.2.3",
+                "@types/prosemirror-view": "1.11.2"
             }
         },
         "@types/prosemirror-menu": {
@@ -744,9 +744,9 @@
             "resolved": "https://registry.npmjs.org/@types/prosemirror-menu/-/prosemirror-menu-1.0.1.tgz",
             "integrity": "sha512-wVGc6G7uYRvjIuVwV0zKSLwntFH1wanFwM1fDkq2YcUrLhuj4zZ1i7IPe+yqSoPm7JfmjiDEgHXTpafmwLKJrA==",
             "requires": {
-                "@types/prosemirror-model": "*",
-                "@types/prosemirror-state": "*",
-                "@types/prosemirror-view": "*"
+                "@types/prosemirror-model": "1.7.2",
+                "@types/prosemirror-state": "1.2.3",
+                "@types/prosemirror-view": "1.11.2"
             }
         },
         "@types/prosemirror-model": {
@@ -754,7 +754,7 @@
             "resolved": "https://registry.npmjs.org/@types/prosemirror-model/-/prosemirror-model-1.7.2.tgz",
             "integrity": "sha512-2l+yXvidg3AUHN07mO4Jd8Q84fo6ksFsy7LHUurLYrZ74uTahBp2fzcO49AKZMzww2EulXJ40Kl/OFaQ/7A1fw==",
             "requires": {
-                "@types/orderedmap": "*"
+                "@types/orderedmap": "1.0.0"
             }
         },
         "@types/prosemirror-schema-basic": {
@@ -762,7 +762,7 @@
             "resolved": "https://registry.npmjs.org/@types/prosemirror-schema-basic/-/prosemirror-schema-basic-1.0.1.tgz",
             "integrity": "sha512-IOQAYf1urifbH+Zwbq5XfFOUMNCbEnvIqpuSAE8SUt00nDAoH62T/S8Qhu8LuF++KQbyXb7fdMp352zkPW9Hmw==",
             "requires": {
-                "@types/prosemirror-model": "*"
+                "@types/prosemirror-model": "1.7.2"
             }
         },
         "@types/prosemirror-schema-list": {
@@ -770,9 +770,9 @@
             "resolved": "https://registry.npmjs.org/@types/prosemirror-schema-list/-/prosemirror-schema-list-1.0.1.tgz",
             "integrity": "sha512-+iUYq+pj2wVHSThj0MjNDzkkGwq8aDQ6j0UJK8a0cNCL8v44Ftcx1noGPtBIEUJgitH960VnfBNoTWfQoQZfRA==",
             "requires": {
-                "@types/orderedmap": "*",
-                "@types/prosemirror-model": "*",
-                "@types/prosemirror-state": "*"
+                "@types/orderedmap": "1.0.0",
+                "@types/prosemirror-model": "1.7.2",
+                "@types/prosemirror-state": "1.2.3"
             }
         },
         "@types/prosemirror-state": {
@@ -780,9 +780,9 @@
             "resolved": "https://registry.npmjs.org/@types/prosemirror-state/-/prosemirror-state-1.2.3.tgz",
             "integrity": "sha512-6m433Hubix9bx+JgcLW7zzyiZuzwjq5mBdSMYY4Yi5c5ZpV2RiVmg7Cy6f9Thtts8vuztilw+PczJAgDm1Frfw==",
             "requires": {
-                "@types/prosemirror-model": "*",
-                "@types/prosemirror-transform": "*",
-                "@types/prosemirror-view": "*"
+                "@types/prosemirror-model": "1.7.2",
+                "@types/prosemirror-transform": "1.1.1",
+                "@types/prosemirror-view": "1.11.2"
             }
         },
         "@types/prosemirror-transform": {
@@ -790,7 +790,7 @@
             "resolved": "https://registry.npmjs.org/@types/prosemirror-transform/-/prosemirror-transform-1.1.1.tgz",
             "integrity": "sha512-yYCYSoiRH+Wcbl8GJc0PFCzeyMzNQ1vL2xrHHSXZuNcIlH75VoiKrZFeZ6BS9cl8mYXjZrlmdBe8YOxYvyKM6A==",
             "requires": {
-                "@types/prosemirror-model": "*"
+                "@types/prosemirror-model": "1.7.2"
             }
         },
         "@types/prosemirror-view": {
@@ -798,9 +798,9 @@
             "resolved": "https://registry.npmjs.org/@types/prosemirror-view/-/prosemirror-view-1.11.2.tgz",
             "integrity": "sha512-EKcQmR4KdkFZU13wS5pWrkSojRCPGqz/l/uzpZFfW5cgdr7fQsftf2/ttvIjpk1a94ISifEY4UZwflVJ+uL4Rg==",
             "requires": {
-                "@types/prosemirror-model": "*",
-                "@types/prosemirror-state": "*",
-                "@types/prosemirror-transform": "*"
+                "@types/prosemirror-model": "1.7.2",
+                "@types/prosemirror-state": "1.2.3",
+                "@types/prosemirror-transform": "1.1.1"
             }
         },
         "@types/pug": {
@@ -818,7 +818,7 @@
             "resolved": "https://registry.npmjs.org/@types/rc-switch/-/rc-switch-1.9.0.tgz",
             "integrity": "sha512-L3ZQ2/1GAaiKuQTMO/2sRdhnNNVtnnvdE0ZOL3/+v54j5LguOvYqTSOHPk9nLPi5va3pdRN1MTce3JkJvGZ65A==",
             "requires": {
-                "@types/react": "*"
+                "@types/react": "16.9.19"
             }
         },
         "@types/react": {
@@ -826,8 +826,8 @@
             "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.19.tgz",
             "integrity": "sha512-LJV97//H+zqKWMms0kvxaKYJDG05U2TtQB3chRLF8MPNs+MQh/H1aGlyDUxjaHvu08EAGerdX2z4LTBc7ns77A==",
             "requires": {
-                "@types/prop-types": "*",
-                "csstype": "^2.2.0"
+                "@types/prop-types": "15.7.3",
+                "csstype": "2.6.6"
             }
         },
         "@types/react-autosuggest": {
@@ -835,7 +835,7 @@
             "resolved": "https://registry.npmjs.org/@types/react-autosuggest/-/react-autosuggest-9.3.13.tgz",
             "integrity": "sha512-J1RB5KAIk6vOJ+TkIE+EqjCWBjXxjobzeA6D9KrS64kzrt6ixC6uSoKMFnG6ByeWoC+1g0756NILCpIpTV62dw==",
             "requires": {
-                "@types/react": "*"
+                "@types/react": "16.9.19"
             }
         },
         "@types/react-color": {
@@ -843,7 +843,7 @@
             "resolved": "https://registry.npmjs.org/@types/react-color/-/react-color-2.17.3.tgz",
             "integrity": "sha512-ewFUB9mNXuqT2UMbiYNqXiUWt857VinGaElhX0Gk+kT1BrXel0imzRp1FeWntHpr+uHkKAnJbr5e4Yc4vP1BRg==",
             "requires": {
-                "@types/react": "*"
+                "@types/react": "16.9.19"
             }
         },
         "@types/react-dom": {
@@ -852,7 +852,7 @@
             "integrity": "sha512-BX6RQ8s9D+2/gDhxrj8OW+YD4R+8hj7FEM/OJHGNR0KipE1h1mSsf39YeyC81qafkq+N3rU3h3RFbLSwE5VqUg==",
             "dev": true,
             "requires": {
-                "@types/react": "*"
+                "@types/react": "16.9.19"
             }
         },
         "@types/react-measure": {
@@ -860,7 +860,7 @@
             "resolved": "https://registry.npmjs.org/@types/react-measure/-/react-measure-2.0.6.tgz",
             "integrity": "sha512-FxAwgDVKvxm4SPXu24x9cwzsty8x33UueazHcpxM1UWZlGJI57yIHM2djE3xUJhYVxuzNzi4E8UL3kmCkdh+4A==",
             "requires": {
-                "@types/react": "*"
+                "@types/react": "16.9.19"
             }
         },
         "@types/react-table": {
@@ -868,7 +868,7 @@
             "resolved": "https://registry.npmjs.org/@types/react-table/-/react-table-6.8.6.tgz",
             "integrity": "sha512-mCgyjD1KiMfZMHixOEzv7qXh5TSfmvgNbOb8qKLcTJbnGgsVSalcEdrNqASyaMp79MvdbMzxAcFQ3lftzmH7MA==",
             "requires": {
-                "@types/react": "*"
+                "@types/react": "16.9.19"
             }
         },
         "@types/request": {
@@ -876,10 +876,10 @@
             "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.4.tgz",
             "integrity": "sha512-W1t1MTKYR8PxICH+A4HgEIPuAC3sbljoEVfyZbeFJJDbr30guDspJri2XOaM2E+Un7ZjrihaDi7cf6fPa2tbgw==",
             "requires": {
-                "@types/caseless": "*",
-                "@types/node": "*",
-                "@types/tough-cookie": "*",
-                "form-data": "^2.5.0"
+                "@types/caseless": "0.12.2",
+                "@types/node": "10.17.13",
+                "@types/tough-cookie": "2.3.6",
+                "form-data": "2.5.1"
             }
         },
         "@types/request-promise": {
@@ -887,8 +887,8 @@
             "resolved": "https://registry.npmjs.org/@types/request-promise/-/request-promise-4.1.45.tgz",
             "integrity": "sha512-KFagTY/a7CzAj86DkhaAtqP0ViYTNam+CfEokSwtPFUIuq9Qrq+Rq2X4nuaB6OJmM2s0xWeiS085Ro7vR0tt9Q==",
             "requires": {
-                "@types/bluebird": "*",
-                "@types/request": "*"
+                "@types/bluebird": "3.5.29",
+                "@types/request": "2.48.4"
             }
         },
         "@types/rimraf": {
@@ -896,8 +896,8 @@
             "resolved": "https://registry.npmjs.org/@types/rimraf/-/rimraf-2.0.3.tgz",
             "integrity": "sha512-dZfyfL/u9l/oi984hEXdmAjX3JHry7TLWw43u1HQ8HhPv6KtfxnrZ3T/bleJ0GEvnk9t5sM7eePkgMqz3yBcGg==",
             "requires": {
-                "@types/glob": "*",
-                "@types/node": "*"
+                "@types/glob": "7.1.1",
+                "@types/node": "10.17.13"
             }
         },
         "@types/serve-static": {
@@ -905,8 +905,8 @@
             "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.3.tgz",
             "integrity": "sha512-oprSwp094zOglVrXdlo/4bAHtKTAxX6VT8FOZlBKrmyLbNvE1zxZyJ6yikMVtHIvwP45+ZQGJn+FdXGKTozq0g==",
             "requires": {
-                "@types/express-serve-static-core": "*",
-                "@types/mime": "*"
+                "@types/express-serve-static-core": "4.17.1",
+                "@types/mime": "2.0.1"
             }
         },
         "@types/sharp": {
@@ -914,7 +914,7 @@
             "resolved": "https://registry.npmjs.org/@types/sharp/-/sharp-0.23.1.tgz",
             "integrity": "sha512-iBRM9RjRF9pkIkukk6imlxfaKMRuiRND8L0yYKl5PJu5uLvxuNzp5f0x8aoTG5VX85M8O//BwbttzFVZL1j/FQ==",
             "requires": {
-                "@types/node": "*"
+                "@types/node": "10.17.13"
             }
         },
         "@types/shelljs": {
@@ -922,8 +922,8 @@
             "resolved": "https://registry.npmjs.org/@types/shelljs/-/shelljs-0.8.6.tgz",
             "integrity": "sha512-svx2eQS268awlppL/P8wgDLBrsDXdKznABHJcuqXyWpSKJgE1s2clXlBvAwbO/lehTmG06NtEWJRkAk4tAgenA==",
             "requires": {
-                "@types/glob": "*",
-                "@types/node": "*"
+                "@types/glob": "7.1.1",
+                "@types/node": "10.17.13"
             }
         },
         "@types/sizzle": {
@@ -936,7 +936,7 @@
             "resolved": "https://registry.npmjs.org/@types/socket.io/-/socket.io-2.1.4.tgz",
             "integrity": "sha512-cI98INy7tYnweTsUlp8ocveVdAxENUThO0JsLSCs51cjOP2yV5Mqo5QszMDPckyRRA+PO6+wBgKvGvHUCc23TQ==",
             "requires": {
-                "@types/node": "*"
+                "@types/node": "10.17.13"
             }
         },
         "@types/socket.io-client": {
@@ -976,7 +976,7 @@
             "resolved": "https://registry.npmjs.org/@types/typescript/-/typescript-2.0.0.tgz",
             "integrity": "sha1-xDNTnJi64oaCswfqp6D9IRW4PCg=",
             "requires": {
-                "typescript": "*"
+                "typescript": "3.7.5"
             }
         },
         "@types/uglify-js": {
@@ -984,7 +984,7 @@
             "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.0.4.tgz",
             "integrity": "sha512-SudIN9TRJ+v8g5pTG8RRCqfqTMNqgWCKKd3vtynhGzkIIjxaicNAMuY5TRadJ6tzDu3Dotf3ngaMILtmOdmWEQ==",
             "requires": {
-                "source-map": "^0.6.1"
+                "source-map": "0.6.1"
             },
             "dependencies": {
                 "source-map": {
@@ -999,7 +999,7 @@
             "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-3.4.6.tgz",
             "integrity": "sha512-cCdlC/1kGEZdEglzOieLDYBxHsvEOIg7kp/2FYyVR9Pxakq+Qf/inL3RKQ+PA8gOlI/NnL+fXmQH12nwcGzsHw==",
             "requires": {
-                "@types/node": "*"
+                "@types/node": "10.17.13"
             }
         },
         "@types/webpack": {
@@ -1007,12 +1007,12 @@
             "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.3.tgz",
             "integrity": "sha512-dH+BZ6pHBZFrXpnif0YU/PbmUq3lQrvRPnqkxsciSIzvG/DE+Vm/Wrjn56T7V3+B5ryQa5fw0oGnHL8tk4ll6w==",
             "requires": {
-                "@types/anymatch": "*",
-                "@types/node": "*",
-                "@types/tapable": "*",
-                "@types/uglify-js": "*",
-                "@types/webpack-sources": "*",
-                "source-map": "^0.6.0"
+                "@types/anymatch": "1.3.1",
+                "@types/node": "10.17.13",
+                "@types/tapable": "1.0.5",
+                "@types/uglify-js": "3.0.4",
+                "@types/webpack-sources": "0.1.6",
+                "source-map": "0.6.1"
             },
             "dependencies": {
                 "source-map": {
@@ -1028,10 +1028,10 @@
             "integrity": "sha512-DzNJJ6ah/6t1n8sfAgQyEbZ/OMmFcF9j9P3aesnm7G6/iBFR/qiGin8K89J0RmaWIBzhTMdDg3I5PmKmSv7N9w==",
             "dev": true,
             "requires": {
-                "@types/connect": "*",
-                "@types/memory-fs": "*",
-                "@types/webpack": "*",
-                "loglevel": "^1.6.2"
+                "@types/connect": "3.4.32",
+                "@types/memory-fs": "0.3.2",
+                "@types/webpack": "4.41.3",
+                "loglevel": "1.6.3"
             }
         },
         "@types/webpack-hot-middleware": {
@@ -1040,8 +1040,8 @@
             "integrity": "sha512-J+Zk7c99pMnPcxWpPY7CEdc4jhPFX1SVPrqXTzinoF8ea+OmIiHa+oOdVI7leMQFGZ6G9e9Yo1uNPhLVr/R8Uw==",
             "dev": true,
             "requires": {
-                "@types/connect": "*",
-                "@types/webpack": "*"
+                "@types/connect": "3.4.32",
+                "@types/webpack": "4.41.3"
             }
         },
         "@types/webpack-sources": {
@@ -1049,9 +1049,9 @@
             "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-0.1.6.tgz",
             "integrity": "sha512-FtAWR7wR5ocJ9+nP137DV81tveD/ZgB1sadnJ/axUGM3BUVfRPx8oQNMtv3JNfTeHx3VP7cXiyfR/jmtEsVHsQ==",
             "requires": {
-                "@types/node": "*",
-                "@types/source-list-map": "*",
-                "source-map": "^0.6.1"
+                "@types/node": "10.17.13",
+                "@types/source-list-map": "0.1.2",
+                "source-map": "0.6.1"
             },
             "dependencies": {
                 "source-map": {
@@ -1122,7 +1122,7 @@
             "dev": true,
             "requires": {
                 "@webassemblyjs/ast": "1.8.5",
-                "mamacro": "^0.0.3"
+                "mamacro": "0.0.3"
             }
         },
         "@webassemblyjs/helper-wasm-bytecode": {
@@ -1149,7 +1149,7 @@
             "integrity": "sha512-aaCvQYrvKbY/n6wKHb/ylAJr27GglahUO89CcGXMItrOBqRarUMxWLJgxm9PJNuKULwN5n1csT9bYoMeZOGF3g==",
             "dev": true,
             "requires": {
-                "@xtuc/ieee754": "^1.2.0"
+                "@xtuc/ieee754": "1.2.0"
             }
         },
         "@webassemblyjs/leb128": {
@@ -1275,7 +1275,7 @@
             "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
             "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
             "requires": {
-                "event-target-shim": "^5.0.0"
+                "event-target-shim": "5.0.1"
             }
         },
         "accepts": {
@@ -1283,7 +1283,7 @@
             "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
             "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
             "requires": {
-                "mime-types": "~2.1.24",
+                "mime-types": "2.1.24",
                 "negotiator": "0.6.2"
             }
         },
@@ -1297,7 +1297,7 @@
             "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-3.1.0.tgz",
             "integrity": "sha1-/YJw9x+7SZawBPqIDuXUZXOnMb8=",
             "requires": {
-                "acorn": "^4.0.4"
+                "acorn": "4.0.13"
             },
             "dependencies": {
                 "acorn": {
@@ -1333,7 +1333,7 @@
             "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz",
             "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==",
             "requires": {
-                "es6-promisify": "^5.0.0"
+                "es6-promisify": "5.0.0"
             }
         },
         "ajv": {
@@ -1341,10 +1341,10 @@
             "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz",
             "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==",
             "requires": {
-                "fast-deep-equal": "^2.0.1",
-                "fast-json-stable-stringify": "^2.0.0",
-                "json-schema-traverse": "^0.4.1",
-                "uri-js": "^4.2.2"
+                "fast-deep-equal": "2.0.1",
+                "fast-json-stable-stringify": "2.0.0",
+                "json-schema-traverse": "0.4.1",
+                "uri-js": "4.2.2"
             }
         },
         "ajv-errors": {
@@ -1362,9 +1362,9 @@
             "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz",
             "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=",
             "requires": {
-                "kind-of": "^3.0.2",
-                "longest": "^1.0.1",
-                "repeat-string": "^1.5.2"
+                "kind-of": "3.2.2",
+                "longest": "1.0.1",
+                "repeat-string": "1.6.1"
             },
             "dependencies": {
                 "kind-of": {
@@ -1372,7 +1372,7 @@
                     "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
                     "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
                     "requires": {
-                        "is-buffer": "^1.1.5"
+                        "is-buffer": "1.1.6"
                     }
                 }
             }
@@ -1392,7 +1392,7 @@
             "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz",
             "integrity": "sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=",
             "requires": {
-                "string-width": "^2.0.0"
+                "string-width": "2.1.1"
             },
             "dependencies": {
                 "ansi-regex": {
@@ -1410,8 +1410,8 @@
                     "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
                     "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
                     "requires": {
-                        "is-fullwidth-code-point": "^2.0.0",
-                        "strip-ansi": "^4.0.0"
+                        "is-fullwidth-code-point": "2.0.0",
+                        "strip-ansi": "4.0.0"
                     }
                 },
                 "strip-ansi": {
@@ -1419,7 +1419,7 @@
                     "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
                     "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
                     "requires": {
-                        "ansi-regex": "^3.0.0"
+                        "ansi-regex": "3.0.0"
                     }
                 }
             }
@@ -1435,7 +1435,7 @@
             "resolved": "https://registry.npmjs.org/ansi-escape-sequences/-/ansi-escape-sequences-4.1.0.tgz",
             "integrity": "sha512-dzW9kHxH011uBsidTXd14JXgzye/YLb2LzeKZ4bsgl/Knwx8AtbSFkkGxagdNOoh0DlqHCmfiEjWKBaqjOanVw==",
             "requires": {
-                "array-back": "^3.0.1"
+                "array-back": "3.1.0"
             },
             "dependencies": {
                 "array-back": {
@@ -1471,8 +1471,8 @@
             "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz",
             "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==",
             "requires": {
-                "micromatch": "^3.1.4",
-                "normalize-path": "^2.1.1"
+                "micromatch": "3.1.10",
+                "normalize-path": "2.1.1"
             },
             "dependencies": {
                 "normalize-path": {
@@ -1480,7 +1480,7 @@
                     "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
                     "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=",
                     "requires": {
-                        "remove-trailing-separator": "^1.0.1"
+                        "remove-trailing-separator": "1.1.0"
                     }
                 }
             }
@@ -1495,13 +1495,13 @@
             "resolved": "https://registry.npmjs.org/archiver/-/archiver-3.1.1.tgz",
             "integrity": "sha512-5Hxxcig7gw5Jod/8Gq0OneVgLYET+oNHcxgWItq4TbhOzRLKNAFUb9edAftiMKXvXfCB0vbGrJdZDNq0dWMsxg==",
             "requires": {
-                "archiver-utils": "^2.1.0",
-                "async": "^2.6.3",
-                "buffer-crc32": "^0.2.1",
-                "glob": "^7.1.4",
-                "readable-stream": "^3.4.0",
-                "tar-stream": "^2.1.0",
-                "zip-stream": "^2.1.2"
+                "archiver-utils": "2.1.0",
+                "async": "2.6.3",
+                "buffer-crc32": "0.2.13",
+                "glob": "7.1.4",
+                "readable-stream": "3.4.0",
+                "tar-stream": "2.1.0",
+                "zip-stream": "2.1.2"
             },
             "dependencies": {
                 "bl": {
@@ -1509,7 +1509,7 @@
                     "resolved": "https://registry.npmjs.org/bl/-/bl-3.0.0.tgz",
                     "integrity": "sha512-EUAyP5UHU5hxF8BPT0LKW8gjYLhq1DQIcneOX/pL/m2Alo+OYDQAJlHq+yseMP50Os2nHXOSic6Ss3vSQeyf4A==",
                     "requires": {
-                        "readable-stream": "^3.0.1"
+                        "readable-stream": "3.4.0"
                     }
                 },
                 "readable-stream": {
@@ -1517,9 +1517,9 @@
                     "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz",
                     "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==",
                     "requires": {
-                        "inherits": "^2.0.3",
-                        "string_decoder": "^1.1.1",
-                        "util-deprecate": "^1.0.1"
+                        "inherits": "2.0.3",
+                        "string_decoder": "1.1.1",
+                        "util-deprecate": "1.0.2"
                     }
                 },
                 "tar-stream": {
@@ -1527,11 +1527,11 @@
                     "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.0.tgz",
                     "integrity": "sha512-+DAn4Nb4+gz6WZigRzKEZl1QuJVOLtAwwF+WUxy1fJ6X63CaGaUAxJRD2KEn1OMfcbCjySTYpNC6WmfQoIEOdw==",
                     "requires": {
-                        "bl": "^3.0.0",
-                        "end-of-stream": "^1.4.1",
-                        "fs-constants": "^1.0.0",
-                        "inherits": "^2.0.3",
-                        "readable-stream": "^3.1.1"
+                        "bl": "3.0.0",
+                        "end-of-stream": "1.4.1",
+                        "fs-constants": "1.0.0",
+                        "inherits": "2.0.3",
+                        "readable-stream": "3.4.0"
                     }
                 }
             }
@@ -1541,16 +1541,16 @@
             "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz",
             "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==",
             "requires": {
-                "glob": "^7.1.4",
-                "graceful-fs": "^4.2.0",
-                "lazystream": "^1.0.0",
-                "lodash.defaults": "^4.2.0",
-                "lodash.difference": "^4.5.0",
-                "lodash.flatten": "^4.4.0",
-                "lodash.isplainobject": "^4.0.6",
-                "lodash.union": "^4.6.0",
-                "normalize-path": "^3.0.0",
-                "readable-stream": "^2.0.0"
+                "glob": "7.1.4",
+                "graceful-fs": "4.2.0",
+                "lazystream": "1.0.0",
+                "lodash.defaults": "4.2.0",
+                "lodash.difference": "4.5.0",
+                "lodash.flatten": "4.4.0",
+                "lodash.isplainobject": "4.0.6",
+                "lodash.union": "4.6.0",
+                "normalize-path": "3.0.0",
+                "readable-stream": "2.3.6"
             }
         },
         "are-we-there-yet": {
@@ -1558,8 +1558,8 @@
             "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz",
             "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==",
             "requires": {
-                "delegates": "^1.0.0",
-                "readable-stream": "^2.0.6"
+                "delegates": "1.0.0",
+                "readable-stream": "2.3.6"
             }
         },
         "argparse": {
@@ -1567,7 +1567,7 @@
             "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
             "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
             "requires": {
-                "sprintf-js": "~1.0.2"
+                "sprintf-js": "1.0.3"
             }
         },
         "arr-diff": {
@@ -1590,7 +1590,7 @@
             "resolved": "https://registry.npmjs.org/array-back/-/array-back-2.0.0.tgz",
             "integrity": "sha512-eJv4pLLufP3g5kcZry0j6WXpIbzYw9GUB4mVJZno9wfwiBxbizTnHCw3VJb07cBihbFX48Y7oSrW9y+gt4glyw==",
             "requires": {
-                "typical": "^2.6.1"
+                "typical": "2.6.1"
             }
         },
         "array-batcher": {
@@ -1598,11 +1598,11 @@
             "resolved": "https://registry.npmjs.org/array-batcher/-/array-batcher-1.2.3.tgz",
             "integrity": "sha512-/IOrwn4ZJi7YqTZrs3k+wQN5nKhjtTqL5ZKkzB+sKJlPeJzpMnRc3o8T9yt8/ZJiSldd+PwTHjM+//UsaszOOw==",
             "requires": {
-                "@types/node": "^12.7.5",
-                "chai": "^4.2.0",
-                "mocha": "^6.2.0",
-                "request": "^2.88.0",
-                "request-promise": "^4.2.4"
+                "@types/node": "12.12.3",
+                "chai": "4.2.0",
+                "mocha": "6.2.2",
+                "request": "2.88.0",
+                "request-promise": "4.2.5"
             },
             "dependencies": {
                 "@types/node": {
@@ -1625,7 +1625,7 @@
                     "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
                     "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
                     "requires": {
-                        "color-convert": "^1.9.0"
+                        "color-convert": "1.9.3"
                     }
                 },
                 "assert-plus": {
@@ -1653,9 +1653,9 @@
                     "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
                     "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==",
                     "requires": {
-                        "string-width": "^3.1.0",
-                        "strip-ansi": "^5.2.0",
-                        "wrap-ansi": "^5.1.0"
+                        "string-width": "3.1.0",
+                        "strip-ansi": "5.2.0",
+                        "wrap-ansi": "5.1.0"
                     }
                 },
                 "debug": {
@@ -1663,7 +1663,7 @@
                     "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
                     "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
                     "requires": {
-                        "ms": "^2.1.1"
+                        "ms": "2.1.1"
                     }
                 },
                 "find-up": {
@@ -1671,7 +1671,7 @@
                     "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
                     "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
                     "requires": {
-                        "locate-path": "^3.0.0"
+                        "locate-path": "3.0.0"
                     }
                 },
                 "form-data": {
@@ -1679,9 +1679,9 @@
                     "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
                     "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
                     "requires": {
-                        "asynckit": "^0.4.0",
-                        "combined-stream": "^1.0.6",
-                        "mime-types": "^2.1.12"
+                        "asynckit": "0.4.0",
+                        "combined-stream": "1.0.8",
+                        "mime-types": "2.1.24"
                     }
                 },
                 "get-caller-file": {
@@ -1694,12 +1694,12 @@
                     "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz",
                     "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==",
                     "requires": {
-                        "fs.realpath": "^1.0.0",
-                        "inflight": "^1.0.4",
-                        "inherits": "2",
-                        "minimatch": "^3.0.4",
-                        "once": "^1.3.0",
-                        "path-is-absolute": "^1.0.0"
+                        "fs.realpath": "1.0.0",
+                        "inflight": "1.0.6",
+                        "inherits": "2.0.3",
+                        "minimatch": "3.0.4",
+                        "once": "1.4.0",
+                        "path-is-absolute": "1.0.1"
                     }
                 },
                 "har-validator": {
@@ -1707,8 +1707,8 @@
                     "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz",
                     "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==",
                     "requires": {
-                        "ajv": "^6.5.5",
-                        "har-schema": "^2.0.0"
+                        "ajv": "6.10.2",
+                        "har-schema": "2.0.0"
                     }
                 },
                 "he": {
@@ -1721,9 +1721,9 @@
                     "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
                     "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
                     "requires": {
-                        "assert-plus": "^1.0.0",
-                        "jsprim": "^1.2.2",
-                        "sshpk": "^1.7.0"
+                        "assert-plus": "1.0.0",
+                        "jsprim": "1.4.1",
+                        "sshpk": "1.16.1"
                     }
                 },
                 "is-fullwidth-code-point": {
@@ -1736,8 +1736,8 @@
                     "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
                     "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
                     "requires": {
-                        "p-locate": "^3.0.0",
-                        "path-exists": "^3.0.0"
+                        "p-locate": "3.0.0",
+                        "path-exists": "3.0.0"
                     }
                 },
                 "mocha": {
@@ -1785,7 +1785,7 @@
                     "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
                     "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
                     "requires": {
-                        "p-limit": "^2.0.0"
+                        "p-limit": "2.2.2"
                     }
                 },
                 "qs": {
@@ -1798,26 +1798,26 @@
                     "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz",
                     "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==",
                     "requires": {
-                        "aws-sign2": "~0.7.0",
-                        "aws4": "^1.8.0",
-                        "caseless": "~0.12.0",
-                        "combined-stream": "~1.0.6",
-                        "extend": "~3.0.2",
-                        "forever-agent": "~0.6.1",
-                        "form-data": "~2.3.2",
-                        "har-validator": "~5.1.0",
-                        "http-signature": "~1.2.0",
-                        "is-typedarray": "~1.0.0",
-                        "isstream": "~0.1.2",
-                        "json-stringify-safe": "~5.0.1",
-                        "mime-types": "~2.1.19",
-                        "oauth-sign": "~0.9.0",
-                        "performance-now": "^2.1.0",
-                        "qs": "~6.5.2",
-                        "safe-buffer": "^5.1.2",
-                        "tough-cookie": "~2.4.3",
-                        "tunnel-agent": "^0.6.0",
-                        "uuid": "^3.3.2"
+                        "aws-sign2": "0.7.0",
+                        "aws4": "1.9.0",
+                        "caseless": "0.12.0",
+                        "combined-stream": "1.0.8",
+                        "extend": "3.0.2",
+                        "forever-agent": "0.6.1",
+                        "form-data": "2.3.3",
+                        "har-validator": "5.1.3",
+                        "http-signature": "1.2.0",
+                        "is-typedarray": "1.0.0",
+                        "isstream": "0.1.2",
+                        "json-stringify-safe": "5.0.1",
+                        "mime-types": "2.1.24",
+                        "oauth-sign": "0.9.0",
+                        "performance-now": "2.1.0",
+                        "qs": "6.5.2",
+                        "safe-buffer": "5.1.2",
+                        "tough-cookie": "2.4.3",
+                        "tunnel-agent": "0.6.0",
+                        "uuid": "3.4.0"
                     }
                 },
                 "require-main-filename": {
@@ -1830,9 +1830,9 @@
                     "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
                     "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
                     "requires": {
-                        "emoji-regex": "^7.0.1",
-                        "is-fullwidth-code-point": "^2.0.0",
-                        "strip-ansi": "^5.1.0"
+                        "emoji-regex": "7.0.3",
+                        "is-fullwidth-code-point": "2.0.0",
+                        "strip-ansi": "5.2.0"
                     }
                 },
                 "strip-ansi": {
@@ -1840,7 +1840,7 @@
                     "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
                     "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
                     "requires": {
-                        "ansi-regex": "^4.1.0"
+                        "ansi-regex": "4.1.0"
                     }
                 },
                 "supports-color": {
@@ -1848,7 +1848,7 @@
                     "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz",
                     "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==",
                     "requires": {
-                        "has-flag": "^3.0.0"
+                        "has-flag": "3.0.0"
                     }
                 },
                 "which-module": {
@@ -1861,9 +1861,9 @@
                     "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
                     "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
                     "requires": {
-                        "ansi-styles": "^3.2.0",
-                        "string-width": "^3.0.0",
-                        "strip-ansi": "^5.0.0"
+                        "ansi-styles": "3.2.1",
+                        "string-width": "3.1.0",
+                        "strip-ansi": "5.2.0"
                     }
                 },
                 "y18n": {
@@ -1876,16 +1876,16 @@
                     "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.0.tgz",
                     "integrity": "sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA==",
                     "requires": {
-                        "cliui": "^5.0.0",
-                        "find-up": "^3.0.0",
-                        "get-caller-file": "^2.0.1",
-                        "require-directory": "^2.1.1",
-                        "require-main-filename": "^2.0.0",
-                        "set-blocking": "^2.0.0",
-                        "string-width": "^3.0.0",
-                        "which-module": "^2.0.0",
-                        "y18n": "^4.0.0",
-                        "yargs-parser": "^13.1.1"
+                        "cliui": "5.0.0",
+                        "find-up": "3.0.0",
+                        "get-caller-file": "2.0.5",
+                        "require-directory": "2.1.1",
+                        "require-main-filename": "2.0.0",
+                        "set-blocking": "2.0.0",
+                        "string-width": "3.1.0",
+                        "which-module": "2.0.0",
+                        "y18n": "4.0.0",
+                        "yargs-parser": "13.1.1"
                     }
                 },
                 "yargs-parser": {
@@ -1893,8 +1893,8 @@
                     "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz",
                     "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==",
                     "requires": {
-                        "camelcase": "^5.0.0",
-                        "decamelize": "^1.2.0"
+                        "camelcase": "5.3.1",
+                        "decamelize": "1.2.0"
                     }
                 }
             }
@@ -1921,7 +1921,7 @@
             "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=",
             "dev": true,
             "requires": {
-                "array-uniq": "^1.0.1"
+                "array-uniq": "1.0.3"
             }
         },
         "array-uniq": {
@@ -1956,7 +1956,7 @@
             "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
             "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==",
             "requires": {
-                "safer-buffer": "~2.1.0"
+                "safer-buffer": "2.1.2"
             }
         },
         "asn1.js": {
@@ -1964,9 +1964,9 @@
             "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz",
             "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==",
             "requires": {
-                "bn.js": "^4.0.0",
-                "inherits": "^2.0.1",
-                "minimalistic-assert": "^1.0.0"
+                "bn.js": "4.11.8",
+                "inherits": "2.0.3",
+                "minimalistic-assert": "1.0.1"
             }
         },
         "assert": {
@@ -1975,7 +1975,7 @@
             "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==",
             "dev": true,
             "requires": {
-                "object-assign": "^4.1.1",
+                "object-assign": "4.1.1",
                 "util": "0.10.3"
             },
             "dependencies": {
@@ -2021,7 +2021,7 @@
             "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
             "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
             "requires": {
-                "lodash": "^4.17.14"
+                "lodash": "4.17.15"
             }
         },
         "async-each": {
@@ -2055,14 +2055,14 @@
             "integrity": "sha512-slv66OAJB8orL+UUaTI3pKlLorwIvS4ARZzYR9iJJyGsEgOqueMfOMdKySWzZ73vIkEe3fcwFgsKMg4d8zyb1g==",
             "dev": true,
             "requires": {
-                "chalk": "^2.4.1",
-                "enhanced-resolve": "^4.0.0",
-                "loader-utils": "^1.1.0",
-                "lodash": "^4.17.5",
-                "micromatch": "^3.1.9",
-                "mkdirp": "^0.5.1",
-                "source-map-support": "^0.5.3",
-                "webpack-log": "^1.2.0"
+                "chalk": "2.4.2",
+                "enhanced-resolve": "4.1.0",
+                "loader-utils": "1.2.3",
+                "lodash": "4.17.15",
+                "micromatch": "3.1.10",
+                "mkdirp": "0.5.1",
+                "source-map-support": "0.5.12",
+                "webpack-log": "1.2.0"
             },
             "dependencies": {
                 "ansi-styles": {
@@ -2071,7 +2071,7 @@
                     "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
                     "dev": true,
                     "requires": {
-                        "color-convert": "^1.9.0"
+                        "color-convert": "1.9.3"
                     }
                 },
                 "chalk": {
@@ -2080,9 +2080,9 @@
                     "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
                     "dev": true,
                     "requires": {
-                        "ansi-styles": "^3.2.1",
-                        "escape-string-regexp": "^1.0.5",
-                        "supports-color": "^5.3.0"
+                        "ansi-styles": "3.2.1",
+                        "escape-string-regexp": "1.0.5",
+                        "supports-color": "5.5.0"
                     }
                 },
                 "supports-color": {
@@ -2091,7 +2091,7 @@
                     "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
                     "dev": true,
                     "requires": {
-                        "has-flag": "^3.0.0"
+                        "has-flag": "3.0.0"
                     }
                 }
             }
@@ -2112,9 +2112,9 @@
             "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=",
             "dev": true,
             "requires": {
-                "chalk": "^1.1.3",
-                "esutils": "^2.0.2",
-                "js-tokens": "^3.0.2"
+                "chalk": "1.1.3",
+                "esutils": "2.0.2",
+                "js-tokens": "3.0.2"
             },
             "dependencies": {
                 "js-tokens": {
@@ -2130,16 +2130,16 @@
             "resolved": "https://registry.npmjs.org/babel-plugin-emotion/-/babel-plugin-emotion-10.0.23.tgz",
             "integrity": "sha512-1JiCyXU0t5S2xCbItejCduLGGcKmF3POT0Ujbexog2MI4IlRcIn/kWjkYwCUZlxpON0O5FC635yPl/3slr7cKQ==",
             "requires": {
-                "@babel/helper-module-imports": "^7.0.0",
+                "@babel/helper-module-imports": "7.7.4",
                 "@emotion/hash": "0.7.3",
                 "@emotion/memoize": "0.7.3",
-                "@emotion/serialize": "^0.11.14",
-                "babel-plugin-macros": "^2.0.0",
-                "babel-plugin-syntax-jsx": "^6.18.0",
-                "convert-source-map": "^1.5.0",
-                "escape-string-regexp": "^1.0.5",
-                "find-root": "^1.1.0",
-                "source-map": "^0.5.7"
+                "@emotion/serialize": "0.11.14",
+                "babel-plugin-macros": "2.8.0",
+                "babel-plugin-syntax-jsx": "6.18.0",
+                "convert-source-map": "1.7.0",
+                "escape-string-regexp": "1.0.5",
+                "find-root": "1.1.0",
+                "source-map": "0.5.7"
             }
         },
         "babel-plugin-macros": {
@@ -2147,9 +2147,9 @@
             "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz",
             "integrity": "sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==",
             "requires": {
-                "@babel/runtime": "^7.7.2",
-                "cosmiconfig": "^6.0.0",
-                "resolve": "^1.12.0"
+                "@babel/runtime": "7.7.6",
+                "cosmiconfig": "6.0.0",
+                "resolve": "1.13.1"
             },
             "dependencies": {
                 "@babel/runtime": {
@@ -2157,7 +2157,7 @@
                     "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.7.6.tgz",
                     "integrity": "sha512-BWAJxpNVa0QlE5gZdWjSxXtemZyZ9RmrmVozxt3NUXeZhVIJ5ANyqmMc0JDrivBZyxUuQvFxlvH4OWWOogGfUw==",
                     "requires": {
-                        "regenerator-runtime": "^0.13.2"
+                        "regenerator-runtime": "0.13.3"
                     }
                 },
                 "resolve": {
@@ -2165,7 +2165,7 @@
                     "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.13.1.tgz",
                     "integrity": "sha512-CxqObCX8K8YtAhOBRg+lrcdn+LK+WYOS8tSjqSFbjtrI5PnS63QPhZl4+yKfrU9tdsbMu9Anr/amegT87M9Z6w==",
                     "requires": {
-                        "path-parse": "^1.0.6"
+                        "path-parse": "1.0.6"
                     }
                 }
             }
@@ -2180,8 +2180,8 @@
             "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
             "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=",
             "requires": {
-                "core-js": "^2.4.0",
-                "regenerator-runtime": "^0.11.0"
+                "core-js": "2.6.9",
+                "regenerator-runtime": "0.11.1"
             },
             "dependencies": {
                 "core-js": {
@@ -2201,10 +2201,10 @@
             "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz",
             "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=",
             "requires": {
-                "babel-runtime": "^6.26.0",
-                "esutils": "^2.0.2",
-                "lodash": "^4.17.4",
-                "to-fast-properties": "^1.0.3"
+                "babel-runtime": "6.26.0",
+                "esutils": "2.0.2",
+                "lodash": "4.17.15",
+                "to-fast-properties": "1.0.3"
             },
             "dependencies": {
                 "to-fast-properties": {
@@ -2234,13 +2234,13 @@
             "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz",
             "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==",
             "requires": {
-                "cache-base": "^1.0.1",
-                "class-utils": "^0.3.5",
-                "component-emitter": "^1.2.1",
-                "define-property": "^1.0.0",
-                "isobject": "^3.0.1",
-                "mixin-deep": "^1.2.0",
-                "pascalcase": "^0.1.1"
+                "cache-base": "1.0.1",
+                "class-utils": "0.3.6",
+                "component-emitter": "1.3.0",
+                "define-property": "1.0.0",
+                "isobject": "3.0.1",
+                "mixin-deep": "1.3.2",
+                "pascalcase": "0.1.1"
             },
             "dependencies": {
                 "define-property": {
@@ -2248,7 +2248,7 @@
                     "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
                     "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
                     "requires": {
-                        "is-descriptor": "^1.0.0"
+                        "is-descriptor": "1.0.2"
                     }
                 },
                 "is-accessor-descriptor": {
@@ -2256,7 +2256,7 @@
                     "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
                     "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
                     "requires": {
-                        "kind-of": "^6.0.0"
+                        "kind-of": "6.0.2"
                     }
                 },
                 "is-data-descriptor": {
@@ -2264,7 +2264,7 @@
                     "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
                     "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
                     "requires": {
-                        "kind-of": "^6.0.0"
+                        "kind-of": "6.0.2"
                     }
                 },
                 "is-descriptor": {
@@ -2272,9 +2272,9 @@
                     "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
                     "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
                     "requires": {
-                        "is-accessor-descriptor": "^1.0.0",
-                        "is-data-descriptor": "^1.0.0",
-                        "kind-of": "^6.0.2"
+                        "is-accessor-descriptor": "1.0.0",
+                        "is-data-descriptor": "1.0.0",
+                        "kind-of": "6.0.2"
                     }
                 }
             }
@@ -2320,7 +2320,7 @@
             "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
             "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
             "requires": {
-                "tweetnacl": "^0.14.3"
+                "tweetnacl": "0.14.5"
             }
         },
         "better-assert": {
@@ -2360,7 +2360,7 @@
             "resolved": "https://registry.npmjs.org/bl/-/bl-3.0.0.tgz",
             "integrity": "sha512-EUAyP5UHU5hxF8BPT0LKW8gjYLhq1DQIcneOX/pL/m2Alo+OYDQAJlHq+yseMP50Os2nHXOSic6Ss3vSQeyf4A==",
             "requires": {
-                "readable-stream": "^3.0.1"
+                "readable-stream": "3.4.0"
             },
             "dependencies": {
                 "readable-stream": {
@@ -2368,9 +2368,9 @@
                     "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz",
                     "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==",
                     "requires": {
-                        "inherits": "^2.0.3",
-                        "string_decoder": "^1.1.1",
-                        "util-deprecate": "^1.0.1"
+                        "inherits": "2.0.3",
+                        "string_decoder": "1.1.1",
+                        "util-deprecate": "1.0.2"
                     }
                 }
             }
@@ -2385,7 +2385,7 @@
             "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz",
             "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=",
             "requires": {
-                "inherits": "~2.0.0"
+                "inherits": "2.0.3"
             }
         },
         "bluebird": {
@@ -2409,15 +2409,15 @@
             "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==",
             "requires": {
                 "bytes": "3.1.0",
-                "content-type": "~1.0.4",
+                "content-type": "1.0.4",
                 "debug": "2.6.9",
-                "depd": "~1.1.2",
+                "depd": "1.1.2",
                 "http-errors": "1.7.2",
                 "iconv-lite": "0.4.24",
-                "on-finished": "~2.3.0",
+                "on-finished": "2.3.0",
                 "qs": "6.7.0",
                 "raw-body": "2.4.0",
-                "type-is": "~1.6.17"
+                "type-is": "1.6.18"
             }
         },
         "bonjour": {
@@ -2426,12 +2426,12 @@
             "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=",
             "dev": true,
             "requires": {
-                "array-flatten": "^2.1.0",
-                "deep-equal": "^1.0.1",
-                "dns-equal": "^1.0.0",
-                "dns-txt": "^2.0.2",
-                "multicast-dns": "^6.0.1",
-                "multicast-dns-service-types": "^1.1.0"
+                "array-flatten": "2.1.2",
+                "deep-equal": "1.1.1",
+                "dns-equal": "1.0.0",
+                "dns-txt": "2.0.2",
+                "multicast-dns": "6.2.3",
+                "multicast-dns-service-types": "1.1.0"
             },
             "dependencies": {
                 "array-flatten": {
@@ -2457,13 +2457,13 @@
             "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz",
             "integrity": "sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw==",
             "requires": {
-                "ansi-align": "^2.0.0",
-                "camelcase": "^4.0.0",
-                "chalk": "^2.0.1",
-                "cli-boxes": "^1.0.0",
-                "string-width": "^2.0.0",
-                "term-size": "^1.2.0",
-                "widest-line": "^2.0.0"
+                "ansi-align": "2.0.0",
+                "camelcase": "4.1.0",
+                "chalk": "2.4.2",
+                "cli-boxes": "1.0.0",
+                "string-width": "2.1.1",
+                "term-size": "1.2.0",
+                "widest-line": "2.0.1"
             },
             "dependencies": {
                 "ansi-regex": {
@@ -2476,7 +2476,7 @@
                     "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
                     "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
                     "requires": {
-                        "color-convert": "^1.9.0"
+                        "color-convert": "1.9.3"
                     }
                 },
                 "chalk": {
@@ -2484,9 +2484,9 @@
                     "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
                     "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
                     "requires": {
-                        "ansi-styles": "^3.2.1",
-                        "escape-string-regexp": "^1.0.5",
-                        "supports-color": "^5.3.0"
+                        "ansi-styles": "3.2.1",
+                        "escape-string-regexp": "1.0.5",
+                        "supports-color": "5.5.0"
                     }
                 },
                 "is-fullwidth-code-point": {
@@ -2499,8 +2499,8 @@
                     "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
                     "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
                     "requires": {
-                        "is-fullwidth-code-point": "^2.0.0",
-                        "strip-ansi": "^4.0.0"
+                        "is-fullwidth-code-point": "2.0.0",
+                        "strip-ansi": "4.0.0"
                     }
                 },
                 "strip-ansi": {
@@ -2508,7 +2508,7 @@
                     "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
                     "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
                     "requires": {
-                        "ansi-regex": "^3.0.0"
+                        "ansi-regex": "3.0.0"
                     }
                 },
                 "supports-color": {
@@ -2516,7 +2516,7 @@
                     "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
                     "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
                     "requires": {
-                        "has-flag": "^3.0.0"
+                        "has-flag": "3.0.0"
                     }
                 }
             }
@@ -2526,7 +2526,7 @@
             "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
             "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
             "requires": {
-                "balanced-match": "^1.0.0",
+                "balanced-match": "1.0.0",
                 "concat-map": "0.0.1"
             }
         },
@@ -2535,16 +2535,16 @@
             "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
             "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==",
             "requires": {
-                "arr-flatten": "^1.1.0",
-                "array-unique": "^0.3.2",
-                "extend-shallow": "^2.0.1",
-                "fill-range": "^4.0.0",
-                "isobject": "^3.0.1",
-                "repeat-element": "^1.1.2",
-                "snapdragon": "^0.8.1",
-                "snapdragon-node": "^2.0.1",
-                "split-string": "^3.0.2",
-                "to-regex": "^3.0.1"
+                "arr-flatten": "1.1.0",
+                "array-unique": "0.3.2",
+                "extend-shallow": "2.0.1",
+                "fill-range": "4.0.0",
+                "isobject": "3.0.1",
+                "repeat-element": "1.1.3",
+                "snapdragon": "0.8.2",
+                "snapdragon-node": "2.1.1",
+                "split-string": "3.1.0",
+                "to-regex": "3.0.2"
             },
             "dependencies": {
                 "extend-shallow": {
@@ -2552,7 +2552,7 @@
                     "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
                     "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
                     "requires": {
-                        "is-extendable": "^0.1.0"
+                        "is-extendable": "0.1.1"
                     }
                 }
             }
@@ -2578,12 +2578,12 @@
             "resolved": "http://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
             "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==",
             "requires": {
-                "buffer-xor": "^1.0.3",
-                "cipher-base": "^1.0.0",
-                "create-hash": "^1.1.0",
-                "evp_bytestokey": "^1.0.3",
-                "inherits": "^2.0.1",
-                "safe-buffer": "^5.0.1"
+                "buffer-xor": "1.0.3",
+                "cipher-base": "1.0.4",
+                "create-hash": "1.2.0",
+                "evp_bytestokey": "1.0.3",
+                "inherits": "2.0.3",
+                "safe-buffer": "5.1.2"
             }
         },
         "browserify-cipher": {
@@ -2591,9 +2591,9 @@
             "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz",
             "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==",
             "requires": {
-                "browserify-aes": "^1.0.4",
-                "browserify-des": "^1.0.0",
-                "evp_bytestokey": "^1.0.0"
+                "browserify-aes": "1.2.0",
+                "browserify-des": "1.0.2",
+                "evp_bytestokey": "1.0.3"
             }
         },
         "browserify-des": {
@@ -2601,10 +2601,10 @@
             "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz",
             "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==",
             "requires": {
-                "cipher-base": "^1.0.1",
-                "des.js": "^1.0.0",
-                "inherits": "^2.0.1",
-                "safe-buffer": "^5.1.2"
+                "cipher-base": "1.0.4",
+                "des.js": "1.0.0",
+                "inherits": "2.0.3",
+                "safe-buffer": "5.1.2"
             }
         },
         "browserify-rsa": {
@@ -2612,8 +2612,8 @@
             "resolved": "http://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
             "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=",
             "requires": {
-                "bn.js": "^4.1.0",
-                "randombytes": "^2.0.1"
+                "bn.js": "4.11.8",
+                "randombytes": "2.1.0"
             }
         },
         "browserify-sign": {
@@ -2621,13 +2621,13 @@
             "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz",
             "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=",
             "requires": {
-                "bn.js": "^4.1.1",
-                "browserify-rsa": "^4.0.0",
-                "create-hash": "^1.1.0",
-                "create-hmac": "^1.1.2",
-                "elliptic": "^6.0.0",
-                "inherits": "^2.0.1",
-                "parse-asn1": "^5.0.0"
+                "bn.js": "4.11.8",
+                "browserify-rsa": "4.0.1",
+                "create-hash": "1.2.0",
+                "create-hmac": "1.1.7",
+                "elliptic": "6.5.0",
+                "inherits": "2.0.3",
+                "parse-asn1": "5.1.4"
             }
         },
         "browserify-zlib": {
@@ -2636,7 +2636,7 @@
             "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==",
             "dev": true,
             "requires": {
-                "pako": "~1.0.5"
+                "pako": "1.0.10"
             }
         },
         "bson": {
@@ -2649,8 +2649,8 @@
             "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.4.3.tgz",
             "integrity": "sha512-zvj65TkFeIt3i6aj5bIvJDzjjQQGs4o/sNoezg1F1kYap9Nu2jcUdpwzRSJTHMMzG0H7bZkn4rNQpImhuxWX2A==",
             "requires": {
-                "base64-js": "^1.0.2",
-                "ieee754": "^1.1.4"
+                "base64-js": "1.3.0",
+                "ieee754": "1.1.13"
             }
         },
         "buffer-crc32": {
@@ -2708,19 +2708,19 @@
             "integrity": "sha512-Dph0MzuH+rTQzGPNT9fAnrPmMmjKfST6trxJeK7NQuHRaVw24VzPRWTmg9MpcwOVQZO0E1FBICUlFeNaKPIfHA==",
             "dev": true,
             "requires": {
-                "bluebird": "^3.5.1",
-                "chownr": "^1.0.1",
-                "glob": "^7.1.2",
-                "graceful-fs": "^4.1.11",
-                "lru-cache": "^4.1.1",
-                "mississippi": "^2.0.0",
-                "mkdirp": "^0.5.1",
-                "move-concurrently": "^1.0.1",
-                "promise-inflight": "^1.0.1",
-                "rimraf": "^2.6.2",
-                "ssri": "^5.2.4",
-                "unique-filename": "^1.1.0",
-                "y18n": "^4.0.0"
+                "bluebird": "3.7.2",
+                "chownr": "1.1.2",
+                "glob": "7.1.4",
+                "graceful-fs": "4.2.0",
+                "lru-cache": "4.1.5",
+                "mississippi": "2.0.0",
+                "mkdirp": "0.5.1",
+                "move-concurrently": "1.0.1",
+                "promise-inflight": "1.0.1",
+                "rimraf": "2.7.1",
+                "ssri": "5.3.0",
+                "unique-filename": "1.1.1",
+                "y18n": "4.0.0"
             },
             "dependencies": {
                 "rimraf": {
@@ -2729,7 +2729,7 @@
                     "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
                     "dev": true,
                     "requires": {
-                        "glob": "^7.1.3"
+                        "glob": "7.1.4"
                     }
                 },
                 "y18n": {
@@ -2745,15 +2745,15 @@
             "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz",
             "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==",
             "requires": {
-                "collection-visit": "^1.0.0",
-                "component-emitter": "^1.2.1",
-                "get-value": "^2.0.6",
-                "has-value": "^1.0.0",
-                "isobject": "^3.0.1",
-                "set-value": "^2.0.0",
-                "to-object-path": "^0.3.0",
-                "union-value": "^1.0.0",
-                "unset-value": "^1.0.0"
+                "collection-visit": "1.0.0",
+                "component-emitter": "1.3.0",
+                "get-value": "2.0.6",
+                "has-value": "1.0.0",
+                "isobject": "3.0.1",
+                "set-value": "2.0.1",
+                "to-object-path": "0.3.0",
+                "union-value": "1.0.1",
+                "unset-value": "1.0.0"
             }
         },
         "callsite": {
@@ -2776,8 +2776,8 @@
             "resolved": "http://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
             "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=",
             "requires": {
-                "camelcase": "^2.0.0",
-                "map-obj": "^1.0.0"
+                "camelcase": "2.1.1",
+                "map-obj": "1.0.1"
             },
             "dependencies": {
                 "camelcase": {
@@ -2792,9 +2792,9 @@
             "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.6.1.tgz",
             "integrity": "sha512-S98rKsPcuhfTcYbtF53UIJhcbgIAK533d1kJKMwsMwAIFgfd58MOyxRud3kktlzWiEkFliaJtvyZCBtud/XVEA==",
             "requires": {
-                "nan": "^2.14.0",
-                "node-pre-gyp": "^0.11.0",
-                "simple-get": "^3.0.3"
+                "nan": "2.14.0",
+                "node-pre-gyp": "0.11.0",
+                "simple-get": "3.0.3"
             }
         },
         "capture-stack-trace": {
@@ -2812,8 +2812,8 @@
             "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz",
             "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=",
             "requires": {
-                "align-text": "^0.1.3",
-                "lazy-cache": "^1.0.3"
+                "align-text": "0.1.4",
+                "lazy-cache": "1.0.4"
             }
         },
         "chai": {
@@ -2821,12 +2821,12 @@
             "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz",
             "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==",
             "requires": {
-                "assertion-error": "^1.1.0",
-                "check-error": "^1.0.2",
-                "deep-eql": "^3.0.1",
-                "get-func-name": "^2.0.0",
-                "pathval": "^1.1.0",
-                "type-detect": "^4.0.5"
+                "assertion-error": "1.1.0",
+                "check-error": "1.0.2",
+                "deep-eql": "3.0.1",
+                "get-func-name": "2.0.0",
+                "pathval": "1.1.0",
+                "type-detect": "4.0.8"
             }
         },
         "chained-function": {
@@ -2839,11 +2839,11 @@
             "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
             "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
             "requires": {
-                "ansi-styles": "^2.2.1",
-                "escape-string-regexp": "^1.0.2",
-                "has-ansi": "^2.0.0",
-                "strip-ansi": "^3.0.0",
-                "supports-color": "^2.0.0"
+                "ansi-styles": "2.2.1",
+                "escape-string-regexp": "1.0.5",
+                "has-ansi": "2.0.0",
+                "strip-ansi": "3.0.1",
+                "supports-color": "2.0.0"
             }
         },
         "character-parser": {
@@ -2851,7 +2851,7 @@
             "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz",
             "integrity": "sha1-x84o821LzZdE5f/CxfzeHHMmH8A=",
             "requires": {
-                "is-regex": "^1.0.3"
+                "is-regex": "1.0.4"
             }
         },
         "check-error": {
@@ -2864,12 +2864,12 @@
             "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.3.tgz",
             "integrity": "sha512-0td5ijfUPuubwLUu0OBoe98gZj8C/AA+RW3v67GPlGOrvxWjZmBXiBCRU+I8VEiNyJzjth40POfHiz2RB3gImA==",
             "requires": {
-                "css-select": "~1.2.0",
-                "dom-serializer": "~0.1.1",
-                "entities": "~1.1.1",
-                "htmlparser2": "^3.9.1",
-                "lodash": "^4.15.0",
-                "parse5": "^3.0.1"
+                "css-select": "1.2.0",
+                "dom-serializer": "0.1.1",
+                "entities": "1.1.2",
+                "htmlparser2": "3.10.1",
+                "lodash": "4.17.15",
+                "parse5": "3.0.3"
             },
             "dependencies": {
                 "parse5": {
@@ -2877,7 +2877,7 @@
                     "resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz",
                     "integrity": "sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==",
                     "requires": {
-                        "@types/node": "*"
+                        "@types/node": "10.17.13"
                     }
                 }
             }
@@ -2892,18 +2892,18 @@
             "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz",
             "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==",
             "requires": {
-                "anymatch": "^2.0.0",
-                "async-each": "^1.0.1",
-                "braces": "^2.3.2",
-                "fsevents": "^1.2.7",
-                "glob-parent": "^3.1.0",
-                "inherits": "^2.0.3",
-                "is-binary-path": "^1.0.0",
-                "is-glob": "^4.0.0",
-                "normalize-path": "^3.0.0",
-                "path-is-absolute": "^1.0.0",
-                "readdirp": "^2.2.1",
-                "upath": "^1.1.1"
+                "anymatch": "2.0.0",
+                "async-each": "1.0.3",
+                "braces": "2.3.2",
+                "fsevents": "1.2.11",
+                "glob-parent": "3.1.0",
+                "inherits": "2.0.3",
+                "is-binary-path": "1.0.1",
+                "is-glob": "4.0.1",
+                "normalize-path": "3.0.0",
+                "path-is-absolute": "1.0.1",
+                "readdirp": "2.2.1",
+                "upath": "1.2.0"
             }
         },
         "chownr": {
@@ -2917,7 +2917,7 @@
             "integrity": "sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==",
             "dev": true,
             "requires": {
-                "tslib": "^1.9.0"
+                "tslib": "1.10.0"
             }
         },
         "ci-info": {
@@ -2930,8 +2930,8 @@
             "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
             "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==",
             "requires": {
-                "inherits": "^2.0.1",
-                "safe-buffer": "^5.0.1"
+                "inherits": "2.0.3",
+                "safe-buffer": "5.1.2"
             }
         },
         "class-transformer": {
@@ -2944,10 +2944,10 @@
             "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz",
             "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==",
             "requires": {
-                "arr-union": "^3.1.0",
-                "define-property": "^0.2.5",
-                "isobject": "^3.0.0",
-                "static-extend": "^0.1.1"
+                "arr-union": "3.1.0",
+                "define-property": "0.2.5",
+                "isobject": "3.0.1",
+                "static-extend": "0.1.2"
             },
             "dependencies": {
                 "define-property": {
@@ -2955,7 +2955,7 @@
                     "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
                     "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
                     "requires": {
-                        "is-descriptor": "^0.1.0"
+                        "is-descriptor": "0.1.6"
                     }
                 }
             }
@@ -2970,7 +2970,7 @@
             "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz",
             "integrity": "sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g==",
             "requires": {
-                "source-map": "~0.6.0"
+                "source-map": "0.6.1"
             },
             "dependencies": {
                 "source-map": {
@@ -2990,14 +2990,14 @@
             "resolved": "https://registry.npmjs.org/cliss/-/cliss-0.0.2.tgz",
             "integrity": "sha512-6rj9pgdukjT994Md13JCUAgTk91abAKrygL9sAvmHY4F6AKMOV8ccGaxhUUfcBuyg3sundWnn3JE0Mc9W6ZYqw==",
             "requires": {
-                "command-line-usage": "^4.0.1",
-                "deepmerge": "^2.0.0",
-                "get-stdin": "^5.0.1",
+                "command-line-usage": "4.1.0",
+                "deepmerge": "2.2.1",
+                "get-stdin": "5.0.1",
                 "inspect-parameters-declaration": "0.0.9",
                 "object-to-arguments": "0.0.8",
-                "pipe-functions": "^1.3.0",
-                "strip-ansi": "^4.0.0",
-                "yargs-parser": "^7.0.0"
+                "pipe-functions": "1.3.0",
+                "strip-ansi": "4.0.0",
+                "yargs-parser": "7.0.0"
             },
             "dependencies": {
                 "ansi-regex": {
@@ -3010,7 +3010,7 @@
                     "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
                     "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
                     "requires": {
-                        "ansi-regex": "^3.0.0"
+                        "ansi-regex": "3.0.0"
                     }
                 }
             }
@@ -3020,9 +3020,9 @@
             "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz",
             "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=",
             "requires": {
-                "string-width": "^1.0.1",
-                "strip-ansi": "^3.0.1",
-                "wrap-ansi": "^2.0.0"
+                "string-width": "1.0.2",
+                "strip-ansi": "3.0.1",
+                "wrap-ansi": "2.1.0"
             }
         },
         "clj-fuzzy": {
@@ -3036,9 +3036,9 @@
             "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==",
             "dev": true,
             "requires": {
-                "is-plain-object": "^2.0.4",
-                "kind-of": "^6.0.2",
-                "shallow-clone": "^3.0.0"
+                "is-plain-object": "2.0.4",
+                "kind-of": "6.0.2",
+                "shallow-clone": "3.0.1"
             }
         },
         "code-point-at": {
@@ -3051,8 +3051,8 @@
             "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz",
             "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=",
             "requires": {
-                "map-visit": "^1.0.0",
-                "object-visit": "^1.0.0"
+                "map-visit": "1.0.0",
+                "object-visit": "1.0.1"
             }
         },
         "color": {
@@ -3060,8 +3060,8 @@
             "resolved": "https://registry.npmjs.org/color/-/color-3.1.2.tgz",
             "integrity": "sha512-vXTJhHebByxZn3lDvDJYw4lR5+uB3vuoHsuYA5AKuxRVn5wzzIfQKGLBmgdVRHKTJYeK5rvJcHnrd0Li49CFpg==",
             "requires": {
-                "color-convert": "^1.9.1",
-                "color-string": "^1.5.2"
+                "color-convert": "1.9.3",
+                "color-string": "1.5.3"
             }
         },
         "color-convert": {
@@ -3082,8 +3082,8 @@
             "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz",
             "integrity": "sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==",
             "requires": {
-                "color-name": "^1.0.0",
-                "simple-swizzle": "^0.2.2"
+                "color-name": "1.1.3",
+                "simple-swizzle": "0.2.2"
             }
         },
         "colors": {
@@ -3096,7 +3096,7 @@
             "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
             "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
             "requires": {
-                "delayed-stream": "~1.0.0"
+                "delayed-stream": "1.0.0"
             }
         },
         "command-line-usage": {
@@ -3104,10 +3104,10 @@
             "resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-4.1.0.tgz",
             "integrity": "sha512-MxS8Ad995KpdAC0Jopo/ovGIroV/m0KHwzKfXxKag6FHOkGsH8/lv5yjgablcRxCJJC0oJeUMuO/gmaq+Wq46g==",
             "requires": {
-                "ansi-escape-sequences": "^4.0.0",
-                "array-back": "^2.0.0",
-                "table-layout": "^0.4.2",
-                "typical": "^2.6.1"
+                "ansi-escape-sequences": "4.1.0",
+                "array-back": "2.0.0",
+                "table-layout": "0.4.5",
+                "typical": "2.6.1"
             }
         },
         "commander": {
@@ -3126,15 +3126,15 @@
             "resolved": "https://registry.npmjs.org/commoner/-/commoner-0.10.8.tgz",
             "integrity": "sha1-NPw2cs0kOT6LtH5wyqApOBH08sU=",
             "requires": {
-                "commander": "^2.5.0",
-                "detective": "^4.3.1",
-                "glob": "^5.0.15",
-                "graceful-fs": "^4.1.2",
-                "iconv-lite": "^0.4.5",
-                "mkdirp": "^0.5.0",
-                "private": "^0.1.6",
-                "q": "^1.1.2",
-                "recast": "^0.11.17"
+                "commander": "2.20.0",
+                "detective": "4.7.1",
+                "glob": "5.0.15",
+                "graceful-fs": "4.2.0",
+                "iconv-lite": "0.4.24",
+                "mkdirp": "0.5.1",
+                "private": "0.1.8",
+                "q": "1.5.1",
+                "recast": "0.11.23"
             },
             "dependencies": {
                 "glob": {
@@ -3142,11 +3142,11 @@
                     "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz",
                     "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=",
                     "requires": {
-                        "inflight": "^1.0.4",
-                        "inherits": "2",
-                        "minimatch": "2 || 3",
-                        "once": "^1.3.0",
-                        "path-is-absolute": "^1.0.0"
+                        "inflight": "1.0.6",
+                        "inherits": "2.0.3",
+                        "minimatch": "3.0.4",
+                        "once": "1.4.0",
+                        "path-is-absolute": "1.0.1"
                     }
                 }
             }
@@ -3171,10 +3171,10 @@
             "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-2.1.1.tgz",
             "integrity": "sha512-eVw6n7CnEMFzc3duyFVrQEuY1BlHR3rYsSztyG32ibGMW722i3C6IizEGMFmfMU+A+fALvBIwxN3czffTcdA+Q==",
             "requires": {
-                "buffer-crc32": "^0.2.13",
-                "crc32-stream": "^3.0.1",
-                "normalize-path": "^3.0.0",
-                "readable-stream": "^2.3.6"
+                "buffer-crc32": "0.2.13",
+                "crc32-stream": "3.0.1",
+                "normalize-path": "3.0.0",
+                "readable-stream": "2.3.6"
             }
         },
         "compressible": {
@@ -3183,7 +3183,7 @@
             "integrity": "sha512-BGHeLCK1GV7j1bSmQQAi26X+GgWcTjLr/0tzSvMCl3LH1w1IJ4PFSPoV5316b30cneTziC+B1a+3OjoSUcQYmw==",
             "dev": true,
             "requires": {
-                "mime-db": ">= 1.40.0 < 2"
+                "mime-db": "1.40.0"
             }
         },
         "compression": {
@@ -3192,13 +3192,13 @@
             "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==",
             "dev": true,
             "requires": {
-                "accepts": "~1.3.5",
+                "accepts": "1.3.7",
                 "bytes": "3.0.0",
-                "compressible": "~2.0.16",
+                "compressible": "2.0.17",
                 "debug": "2.6.9",
-                "on-headers": "~1.0.2",
+                "on-headers": "1.0.2",
                 "safe-buffer": "5.1.2",
-                "vary": "~1.1.2"
+                "vary": "1.1.2"
             },
             "dependencies": {
                 "bytes": {
@@ -3220,10 +3220,10 @@
             "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
             "dev": true,
             "requires": {
-                "buffer-from": "^1.0.0",
-                "inherits": "^2.0.3",
-                "readable-stream": "^2.2.2",
-                "typedarray": "^0.0.6"
+                "buffer-from": "1.1.1",
+                "inherits": "2.0.3",
+                "readable-stream": "2.3.6",
+                "typedarray": "0.0.6"
             }
         },
         "configstore": {
@@ -3231,12 +3231,12 @@
             "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.2.tgz",
             "integrity": "sha512-vtv5HtGjcYUgFrXc6Kx747B83MRRVS5R1VTEQoXvuP+kMI+if6uywV0nDGoiydJRy4yk7h9od5Og0kxx4zUXmw==",
             "requires": {
-                "dot-prop": "^4.1.0",
-                "graceful-fs": "^4.1.2",
-                "make-dir": "^1.0.0",
-                "unique-string": "^1.0.0",
-                "write-file-atomic": "^2.0.0",
-                "xdg-basedir": "^3.0.0"
+                "dot-prop": "4.2.0",
+                "graceful-fs": "4.2.0",
+                "make-dir": "1.3.0",
+                "unique-string": "1.0.0",
+                "write-file-atomic": "2.4.3",
+                "xdg-basedir": "3.0.0"
             }
         },
         "connect-flash": {
@@ -3255,7 +3255,7 @@
             "resolved": "https://registry.npmjs.org/connect-mongo/-/connect-mongo-2.0.3.tgz",
             "integrity": "sha512-Vs+QZ/6X6gbCrP1Ls7Oh/wlyY6pgpbPSrUKF5yRT+zd+4GZPNbjNquxquZ+Clv2+03HBXE7T4lVM0PUcaBhihg==",
             "requires": {
-                "mongodb": "^2.0.36"
+                "mongodb": "2.2.36"
             },
             "dependencies": {
                 "mongodb": {
@@ -3278,13 +3278,13 @@
                     "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.7.tgz",
                     "integrity": "sha1-BwV6y+JGeyIELTb5jFrVBwVOlbE=",
                     "requires": {
-                        "buffer-shims": "~1.0.0",
-                        "core-util-is": "~1.0.0",
-                        "inherits": "~2.0.1",
-                        "isarray": "~1.0.0",
-                        "process-nextick-args": "~1.0.6",
-                        "string_decoder": "~1.0.0",
-                        "util-deprecate": "~1.0.1"
+                        "buffer-shims": "1.0.0",
+                        "core-util-is": "1.0.2",
+                        "inherits": "2.0.3",
+                        "isarray": "1.0.0",
+                        "process-nextick-args": "1.0.7",
+                        "string_decoder": "1.0.3",
+                        "util-deprecate": "1.0.2"
                     }
                 },
                 "string_decoder": {
@@ -3292,7 +3292,7 @@
                     "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
                     "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
                     "requires": {
-                        "safe-buffer": "~5.1.0"
+                        "safe-buffer": "5.1.2"
                     }
                 }
             }
@@ -3313,10 +3313,10 @@
             "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-3.1.2.tgz",
             "integrity": "sha512-yePcBqEFhLOqSBtwYOGGS1exHo/s1xjekXiinh4itpNQGCu4KA1euPh1fg07N2wMITZXQkBz75Ntdt1ctGZouw==",
             "requires": {
-                "@types/babel-types": "^7.0.0",
-                "@types/babylon": "^6.16.2",
-                "babel-types": "^6.26.0",
-                "babylon": "^6.18.0"
+                "@types/babel-types": "7.0.7",
+                "@types/babylon": "6.16.5",
+                "babel-types": "6.26.0",
+                "babylon": "6.18.0"
             }
         },
         "constants-browserify": {
@@ -3343,7 +3343,7 @@
             "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz",
             "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==",
             "requires": {
-                "safe-buffer": "~5.1.1"
+                "safe-buffer": "5.1.2"
             }
         },
         "cookie": {
@@ -3367,7 +3367,7 @@
             "requires": {
                 "cookies": "0.8.0",
                 "debug": "3.2.6",
-                "on-headers": "~1.0.2",
+                "on-headers": "1.0.2",
                 "safe-buffer": "5.2.0"
             },
             "dependencies": {
@@ -3376,7 +3376,7 @@
                     "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
                     "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
                     "requires": {
-                        "ms": "^2.1.1"
+                        "ms": "2.1.2"
                     }
                 },
                 "ms": {
@@ -3401,8 +3401,8 @@
             "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.8.0.tgz",
             "integrity": "sha512-8aPsApQfebXnuI+537McwYsDtjVxGm8gTIzQI3FDW6t5t/DAhERxtnbEPN/8RX+uZthoz4eCOgloXaE5cYyNow==",
             "requires": {
-                "depd": "~2.0.0",
-                "keygrip": "~1.1.0"
+                "depd": "2.0.0",
+                "keygrip": "1.1.0"
             },
             "dependencies": {
                 "depd": {
@@ -3418,12 +3418,12 @@
             "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==",
             "dev": true,
             "requires": {
-                "aproba": "^1.1.1",
-                "fs-write-stream-atomic": "^1.0.8",
-                "iferr": "^0.1.5",
-                "mkdirp": "^0.5.1",
-                "rimraf": "^2.5.4",
-                "run-queue": "^1.0.0"
+                "aproba": "1.2.0",
+                "fs-write-stream-atomic": "1.0.10",
+                "iferr": "0.1.5",
+                "mkdirp": "0.5.1",
+                "rimraf": "2.7.1",
+                "run-queue": "1.0.3"
             },
             "dependencies": {
                 "rimraf": {
@@ -3432,7 +3432,7 @@
                     "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
                     "dev": true,
                     "requires": {
-                        "glob": "^7.1.3"
+                        "glob": "7.1.4"
                     }
                 }
             }
@@ -3448,14 +3448,14 @@
             "integrity": "sha512-Y+SQCF+0NoWQryez2zXn5J5knmr9z/9qSQt7fbL78u83rxmigOy8X5+BFn8CFSuX+nKT8gpYwJX68ekqtQt6ZA==",
             "dev": true,
             "requires": {
-                "cacache": "^10.0.4",
-                "find-cache-dir": "^1.0.0",
-                "globby": "^7.1.1",
-                "is-glob": "^4.0.0",
-                "loader-utils": "^1.1.0",
-                "minimatch": "^3.0.4",
-                "p-limit": "^1.0.0",
-                "serialize-javascript": "^1.4.0"
+                "cacache": "10.0.4",
+                "find-cache-dir": "1.0.0",
+                "globby": "7.1.1",
+                "is-glob": "4.0.1",
+                "loader-utils": "1.2.3",
+                "minimatch": "3.0.4",
+                "p-limit": "1.3.0",
+                "serialize-javascript": "1.7.0"
             },
             "dependencies": {
                 "p-limit": {
@@ -3464,7 +3464,7 @@
                     "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==",
                     "dev": true,
                     "requires": {
-                        "p-try": "^1.0.0"
+                        "p-try": "1.0.0"
                     }
                 }
             }
@@ -3484,8 +3484,8 @@
             "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
             "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
             "requires": {
-                "object-assign": "^4",
-                "vary": "^1"
+                "object-assign": "4.1.1",
+                "vary": "1.1.2"
             }
         },
         "cosmiconfig": {
@@ -3493,11 +3493,11 @@
             "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz",
             "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==",
             "requires": {
-                "@types/parse-json": "^4.0.0",
-                "import-fresh": "^3.1.0",
-                "parse-json": "^5.0.0",
-                "path-type": "^4.0.0",
-                "yaml": "^1.7.2"
+                "@types/parse-json": "4.0.0",
+                "import-fresh": "3.2.1",
+                "parse-json": "5.0.0",
+                "path-type": "4.0.0",
+                "yaml": "1.7.2"
             },
             "dependencies": {
                 "path-type": {
@@ -3512,7 +3512,7 @@
             "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz",
             "integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==",
             "requires": {
-                "buffer": "^5.1.0"
+                "buffer": "5.4.3"
             }
         },
         "crc32-stream": {
@@ -3520,8 +3520,8 @@
             "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-3.0.1.tgz",
             "integrity": "sha512-mctvpXlbzsvK+6z8kJwSJ5crm7yBwrQMTybJzMw1O4lLGJqjlDCXY2Zw7KheiA6XBEcBmfLx1D88mjRGVJtY9w==",
             "requires": {
-                "crc": "^3.4.4",
-                "readable-stream": "^3.4.0"
+                "crc": "3.8.0",
+                "readable-stream": "3.4.0"
             },
             "dependencies": {
                 "readable-stream": {
@@ -3529,9 +3529,9 @@
                     "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz",
                     "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==",
                     "requires": {
-                        "inherits": "^2.0.3",
-                        "string_decoder": "^1.1.1",
-                        "util-deprecate": "^1.0.1"
+                        "inherits": "2.0.3",
+                        "string_decoder": "1.1.1",
+                        "util-deprecate": "1.0.2"
                     }
                 }
             }
@@ -3541,8 +3541,8 @@
             "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz",
             "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==",
             "requires": {
-                "bn.js": "^4.1.0",
-                "elliptic": "^6.0.0"
+                "bn.js": "4.11.8",
+                "elliptic": "6.5.0"
             }
         },
         "create-emotion": {
@@ -3550,8 +3550,8 @@
             "resolved": "https://registry.npmjs.org/create-emotion/-/create-emotion-10.0.14.tgz",
             "integrity": "sha512-5G4naKMxokOur+94eDz7iPKBfwzy4wa/+0isnPhxXyosIQHBq7yvBy4jjdZw/nnRm7G3PM7P9Ug8mUmtoqcaHg==",
             "requires": {
-                "@emotion/cache": "^10.0.14",
-                "@emotion/serialize": "^0.11.8",
+                "@emotion/cache": "10.0.19",
+                "@emotion/serialize": "0.11.14",
                 "@emotion/sheet": "0.9.3",
                 "@emotion/utils": "0.11.2"
             }
@@ -3561,7 +3561,7 @@
             "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz",
             "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=",
             "requires": {
-                "capture-stack-trace": "^1.0.0"
+                "capture-stack-trace": "1.0.1"
             }
         },
         "create-hash": {
@@ -3569,11 +3569,11 @@
             "resolved": "http://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
             "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
             "requires": {
-                "cipher-base": "^1.0.1",
-                "inherits": "^2.0.1",
-                "md5.js": "^1.3.4",
-                "ripemd160": "^2.0.1",
-                "sha.js": "^2.4.0"
+                "cipher-base": "1.0.4",
+                "inherits": "2.0.3",
+                "md5.js": "1.3.5",
+                "ripemd160": "2.0.2",
+                "sha.js": "2.4.11"
             }
         },
         "create-hmac": {
@@ -3581,12 +3581,12 @@
             "resolved": "http://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
             "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==",
             "requires": {
-                "cipher-base": "^1.0.3",
-                "create-hash": "^1.1.0",
-                "inherits": "^2.0.1",
-                "ripemd160": "^2.0.0",
-                "safe-buffer": "^5.0.1",
-                "sha.js": "^2.4.8"
+                "cipher-base": "1.0.4",
+                "create-hash": "1.2.0",
+                "inherits": "2.0.3",
+                "ripemd160": "2.0.2",
+                "safe-buffer": "5.1.2",
+                "sha.js": "2.4.11"
             }
         },
         "create-react-context": {
@@ -3594,8 +3594,8 @@
             "resolved": "https://registry.npmjs.org/create-react-context/-/create-react-context-0.2.3.tgz",
             "integrity": "sha512-CQBmD0+QGgTaxDL3OX1IDXYqjkp2It4RIbcb99jS6AEg27Ga+a9G3JtK6SIu0HBwPLZlmwt9F7UwWA4Bn92Rag==",
             "requires": {
-                "fbjs": "^0.8.0",
-                "gud": "^1.0.0"
+                "fbjs": "0.8.17",
+                "gud": "1.0.0"
             }
         },
         "crel": {
@@ -3609,7 +3609,7 @@
             "integrity": "sha512-1yHhtcfAd1r4nwQgknowuUNfIT9E8dOMMspC36g45dN+iD1blloi7xp8X/xAIDnjHWyt1uQ8PHk2fkNaym7soQ==",
             "dev": true,
             "requires": {
-                "cross-spawn": "^6.0.5"
+                "cross-spawn": "6.0.5"
             }
         },
         "cross-fetch": {
@@ -3634,11 +3634,11 @@
             "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
             "dev": true,
             "requires": {
-                "nice-try": "^1.0.4",
-                "path-key": "^2.0.1",
-                "semver": "^5.5.0",
-                "shebang-command": "^1.2.0",
-                "which": "^1.2.9"
+                "nice-try": "1.0.5",
+                "path-key": "2.0.1",
+                "semver": "5.7.0",
+                "shebang-command": "1.2.0",
+                "which": "1.3.1"
             }
         },
         "crypto-browserify": {
@@ -3646,17 +3646,17 @@
             "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz",
             "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==",
             "requires": {
-                "browserify-cipher": "^1.0.0",
-                "browserify-sign": "^4.0.0",
-                "create-ecdh": "^4.0.0",
-                "create-hash": "^1.1.0",
-                "create-hmac": "^1.1.0",
-                "diffie-hellman": "^5.0.0",
-                "inherits": "^2.0.1",
-                "pbkdf2": "^3.0.3",
-                "public-encrypt": "^4.0.0",
-                "randombytes": "^2.0.0",
-                "randomfill": "^1.0.3"
+                "browserify-cipher": "1.0.1",
+                "browserify-sign": "4.0.4",
+                "create-ecdh": "4.0.3",
+                "create-hash": "1.2.0",
+                "create-hmac": "1.1.7",
+                "diffie-hellman": "5.0.3",
+                "inherits": "2.0.3",
+                "pbkdf2": "3.0.17",
+                "public-encrypt": "4.0.3",
+                "randombytes": "2.1.0",
+                "randomfill": "1.0.4"
             }
         },
         "crypto-random-string": {
@@ -3670,17 +3670,17 @@
             "integrity": "sha512-OcKJU/lt232vl1P9EEDamhoO9iKY3tIjY5GU+XDLblAykTdgs6Ux9P1hTHve8nFKy5KPpOXOsVI/hIwi3841+w==",
             "dev": true,
             "requires": {
-                "camelcase": "^5.2.0",
-                "icss-utils": "^4.1.0",
-                "loader-utils": "^1.2.3",
-                "normalize-path": "^3.0.0",
-                "postcss": "^7.0.14",
-                "postcss-modules-extract-imports": "^2.0.0",
-                "postcss-modules-local-by-default": "^2.0.6",
-                "postcss-modules-scope": "^2.1.0",
-                "postcss-modules-values": "^2.0.0",
-                "postcss-value-parser": "^3.3.0",
-                "schema-utils": "^1.0.0"
+                "camelcase": "5.3.1",
+                "icss-utils": "4.1.1",
+                "loader-utils": "1.2.3",
+                "normalize-path": "3.0.0",
+                "postcss": "7.0.17",
+                "postcss-modules-extract-imports": "2.0.0",
+                "postcss-modules-local-by-default": "2.0.6",
+                "postcss-modules-scope": "2.1.0",
+                "postcss-modules-values": "2.0.0",
+                "postcss-value-parser": "3.3.1",
+                "schema-utils": "1.0.0"
             },
             "dependencies": {
                 "camelcase": {
@@ -3695,9 +3695,9 @@
                     "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==",
                     "dev": true,
                     "requires": {
-                        "ajv": "^6.1.0",
-                        "ajv-errors": "^1.0.0",
-                        "ajv-keywords": "^3.1.0"
+                        "ajv": "6.10.2",
+                        "ajv-errors": "1.0.1",
+                        "ajv-keywords": "3.4.1"
                     }
                 }
             }
@@ -3707,10 +3707,10 @@
             "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz",
             "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=",
             "requires": {
-                "boolbase": "~1.0.0",
-                "css-what": "2.1",
+                "boolbase": "1.0.0",
+                "css-what": "2.1.3",
                 "domutils": "1.5.1",
-                "nth-check": "~1.0.1"
+                "nth-check": "1.0.2"
             }
         },
         "css-what": {
@@ -3736,7 +3736,7 @@
             "integrity": "sha512-QXSAu2WBsSRXCPjvI43Y40m6fMevvyRm8JVAuF9ksQz5jha4pWP1wpaK7Yu5oLFc6+XAY+hj8YhefyXcBB53gg==",
             "dev": true,
             "requires": {
-                "cssom": "~0.3.6"
+                "cssom": "0.3.8"
             },
             "dependencies": {
                 "cssom": {
@@ -3757,7 +3757,7 @@
             "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
             "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=",
             "requires": {
-                "array-find-index": "^1.0.1"
+                "array-find-index": "1.0.2"
             }
         },
         "cyclist": {
@@ -3772,8 +3772,8 @@
             "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==",
             "dev": true,
             "requires": {
-                "es5-ext": "^0.10.50",
-                "type": "^1.0.1"
+                "es5-ext": "0.10.50",
+                "type": "1.0.1"
             }
         },
         "d3-array": {
@@ -3791,7 +3791,7 @@
             "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
             "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
             "requires": {
-                "assert-plus": "^1.0.0"
+                "assert-plus": "1.0.0"
             },
             "dependencies": {
                 "assert-plus": {
@@ -3807,9 +3807,9 @@
             "integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==",
             "dev": true,
             "requires": {
-                "abab": "^2.0.0",
-                "whatwg-mimetype": "^2.2.0",
-                "whatwg-url": "^7.0.0"
+                "abab": "2.0.3",
+                "whatwg-mimetype": "2.3.0",
+                "whatwg-url": "7.1.0"
             }
         },
         "dateformat": {
@@ -3818,8 +3818,8 @@
             "integrity": "sha1-nxJLZ1lMk3/3BpMuSmQsyo27/uk=",
             "dev": true,
             "requires": {
-                "get-stdin": "^4.0.1",
-                "meow": "^3.3.0"
+                "get-stdin": "4.0.1",
+                "meow": "3.7.0"
             },
             "dependencies": {
                 "get-stdin": {
@@ -3859,7 +3859,7 @@
             "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz",
             "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=",
             "requires": {
-                "mimic-response": "^1.0.0"
+                "mimic-response": "1.0.1"
             }
         },
         "deep-eql": {
@@ -3867,7 +3867,7 @@
             "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz",
             "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==",
             "requires": {
-                "type-detect": "^4.0.0"
+                "type-detect": "4.0.8"
             }
         },
         "deep-equal": {
@@ -3876,12 +3876,12 @@
             "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==",
             "dev": true,
             "requires": {
-                "is-arguments": "^1.0.4",
-                "is-date-object": "^1.0.1",
-                "is-regex": "^1.0.4",
-                "object-is": "^1.0.1",
-                "object-keys": "^1.1.1",
-                "regexp.prototype.flags": "^1.2.0"
+                "is-arguments": "1.0.4",
+                "is-date-object": "1.0.1",
+                "is-regex": "1.0.4",
+                "object-is": "1.0.2",
+                "object-keys": "1.1.1",
+                "regexp.prototype.flags": "1.3.0"
             }
         },
         "deep-extend": {
@@ -3906,8 +3906,8 @@
             "integrity": "sha512-h6sMrVB1VMWVrW13mSc6ia/DwYYw5MN6+exNu1OaJeFac5aSAvwM7lZ0NVfTABuSkQelr4h5oebg3KB1XPdjgA==",
             "dev": true,
             "requires": {
-                "execa": "^1.0.0",
-                "ip-regex": "^2.1.0"
+                "execa": "1.0.0",
+                "ip-regex": "2.1.0"
             },
             "dependencies": {
                 "execa": {
@@ -3916,13 +3916,13 @@
                     "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==",
                     "dev": true,
                     "requires": {
-                        "cross-spawn": "^6.0.0",
-                        "get-stream": "^4.0.0",
-                        "is-stream": "^1.1.0",
-                        "npm-run-path": "^2.0.0",
-                        "p-finally": "^1.0.0",
-                        "signal-exit": "^3.0.0",
-                        "strip-eof": "^1.0.0"
+                        "cross-spawn": "6.0.5",
+                        "get-stream": "4.1.0",
+                        "is-stream": "1.1.0",
+                        "npm-run-path": "2.0.2",
+                        "p-finally": "1.0.0",
+                        "signal-exit": "3.0.2",
+                        "strip-eof": "1.0.0"
                     }
                 },
                 "get-stream": {
@@ -3931,7 +3931,7 @@
                     "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
                     "dev": true,
                     "requires": {
-                        "pump": "^3.0.0"
+                        "pump": "3.0.0"
                     }
                 },
                 "pump": {
@@ -3940,8 +3940,8 @@
                     "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
                     "dev": true,
                     "requires": {
-                        "end-of-stream": "^1.1.0",
-                        "once": "^1.3.1"
+                        "end-of-stream": "1.4.1",
+                        "once": "1.4.0"
                     }
                 }
             }
@@ -3951,7 +3951,7 @@
             "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
             "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
             "requires": {
-                "object-keys": "^1.0.12"
+                "object-keys": "1.1.1"
             }
         },
         "define-property": {
@@ -3959,8 +3959,8 @@
             "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz",
             "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==",
             "requires": {
-                "is-descriptor": "^1.0.2",
-                "isobject": "^3.0.1"
+                "is-descriptor": "1.0.2",
+                "isobject": "3.0.1"
             },
             "dependencies": {
                 "is-accessor-descriptor": {
@@ -3968,7 +3968,7 @@
                     "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
                     "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
                     "requires": {
-                        "kind-of": "^6.0.0"
+                        "kind-of": "6.0.2"
                     }
                 },
                 "is-data-descriptor": {
@@ -3976,7 +3976,7 @@
                     "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
                     "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
                     "requires": {
-                        "kind-of": "^6.0.0"
+                        "kind-of": "6.0.2"
                     }
                 },
                 "is-descriptor": {
@@ -3984,9 +3984,9 @@
                     "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
                     "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
                     "requires": {
-                        "is-accessor-descriptor": "^1.0.0",
-                        "is-data-descriptor": "^1.0.0",
-                        "kind-of": "^6.0.2"
+                        "is-accessor-descriptor": "1.0.0",
+                        "is-data-descriptor": "1.0.0",
+                        "kind-of": "6.0.2"
                     }
                 }
             }
@@ -4002,13 +4002,13 @@
             "integrity": "sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==",
             "dev": true,
             "requires": {
-                "@types/glob": "^7.1.1",
-                "globby": "^6.1.0",
-                "is-path-cwd": "^2.0.0",
-                "is-path-in-cwd": "^2.0.0",
-                "p-map": "^2.0.0",
-                "pify": "^4.0.1",
-                "rimraf": "^2.6.3"
+                "@types/glob": "7.1.1",
+                "globby": "6.1.0",
+                "is-path-cwd": "2.2.0",
+                "is-path-in-cwd": "2.1.0",
+                "p-map": "2.1.0",
+                "pify": "4.0.1",
+                "rimraf": "2.7.1"
             },
             "dependencies": {
                 "globby": {
@@ -4017,11 +4017,11 @@
                     "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=",
                     "dev": true,
                     "requires": {
-                        "array-union": "^1.0.1",
-                        "glob": "^7.0.3",
-                        "object-assign": "^4.0.1",
-                        "pify": "^2.0.0",
-                        "pinkie-promise": "^2.0.0"
+                        "array-union": "1.0.2",
+                        "glob": "7.1.4",
+                        "object-assign": "4.1.1",
+                        "pify": "2.3.0",
+                        "pinkie-promise": "2.0.1"
                     },
                     "dependencies": {
                         "pify": {
@@ -4044,7 +4044,7 @@
                     "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
                     "dev": true,
                     "requires": {
-                        "glob": "^7.1.3"
+                        "glob": "7.1.4"
                     }
                 }
             }
@@ -4074,8 +4074,8 @@
             "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz",
             "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=",
             "requires": {
-                "inherits": "^2.0.1",
-                "minimalistic-assert": "^1.0.0"
+                "inherits": "2.0.3",
+                "minimalistic-assert": "1.0.1"
             }
         },
         "destroy": {
@@ -4105,8 +4105,8 @@
             "resolved": "https://registry.npmjs.org/detective/-/detective-4.7.1.tgz",
             "integrity": "sha512-H6PmeeUcZloWtdt4DAkFyzFL94arpHr3NOwwmVILFiy+9Qd4JTxxXrzfyGk/lmct2qVGBwTSwSXagqu2BxmWig==",
             "requires": {
-                "acorn": "^5.2.1",
-                "defined": "^1.0.0"
+                "acorn": "5.7.3",
+                "defined": "1.0.0"
             }
         },
         "diff": {
@@ -4119,9 +4119,9 @@
             "resolved": "http://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
             "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==",
             "requires": {
-                "bn.js": "^4.1.0",
-                "miller-rabin": "^4.0.0",
-                "randombytes": "^2.0.0"
+                "bn.js": "4.11.8",
+                "miller-rabin": "4.0.1",
+                "randombytes": "2.1.0"
             }
         },
         "dir-glob": {
@@ -4130,7 +4130,7 @@
             "integrity": "sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==",
             "dev": true,
             "requires": {
-                "path-type": "^3.0.0"
+                "path-type": "3.0.0"
             },
             "dependencies": {
                 "path-type": {
@@ -4139,7 +4139,7 @@
                     "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==",
                     "dev": true,
                     "requires": {
-                        "pify": "^3.0.0"
+                        "pify": "3.0.0"
                     }
                 },
                 "pify": {
@@ -4162,8 +4162,8 @@
             "integrity": "sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg==",
             "dev": true,
             "requires": {
-                "ip": "^1.1.0",
-                "safe-buffer": "^5.0.1"
+                "ip": "1.1.5",
+                "safe-buffer": "5.1.2"
             }
         },
         "dns-txt": {
@@ -4172,7 +4172,7 @@
             "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=",
             "dev": true,
             "requires": {
-                "buffer-indexof": "^1.0.0"
+                "buffer-indexof": "1.1.1"
             }
         },
         "doctypes": {
@@ -4185,7 +4185,7 @@
             "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz",
             "integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==",
             "requires": {
-                "@babel/runtime": "^7.1.2"
+                "@babel/runtime": "7.5.5"
             }
         },
         "dom-serializer": {
@@ -4193,8 +4193,8 @@
             "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz",
             "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==",
             "requires": {
-                "domelementtype": "^1.3.0",
-                "entities": "^1.1.1"
+                "domelementtype": "1.3.1",
+                "entities": "1.1.2"
             }
         },
         "domain-browser": {
@@ -4214,7 +4214,7 @@
             "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==",
             "dev": true,
             "requires": {
-                "webidl-conversions": "^4.0.2"
+                "webidl-conversions": "4.0.2"
             }
         },
         "domhandler": {
@@ -4222,7 +4222,7 @@
             "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz",
             "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==",
             "requires": {
-                "domelementtype": "1"
+                "domelementtype": "1.3.1"
             }
         },
         "domutils": {
@@ -4230,8 +4230,8 @@
             "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz",
             "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=",
             "requires": {
-                "dom-serializer": "0",
-                "domelementtype": "1"
+                "dom-serializer": "0.1.1",
+                "domelementtype": "1.3.1"
             }
         },
         "dot-prop": {
@@ -4239,7 +4239,7 @@
             "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz",
             "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==",
             "requires": {
-                "is-obj": "^1.0.0"
+                "is-obj": "1.0.1"
             }
         },
         "dotenv": {
@@ -4263,10 +4263,10 @@
             "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==",
             "dev": true,
             "requires": {
-                "end-of-stream": "^1.0.0",
-                "inherits": "^2.0.1",
-                "readable-stream": "^2.0.0",
-                "stream-shift": "^1.0.0"
+                "end-of-stream": "1.4.1",
+                "inherits": "2.0.3",
+                "readable-stream": "2.3.6",
+                "stream-shift": "1.0.0"
             }
         },
         "dynamic-dedupe": {
@@ -4275,7 +4275,7 @@
             "integrity": "sha1-BuRMIj9eTpTXjvnbI6ZRXOL5YqE=",
             "dev": true,
             "requires": {
-                "xtend": "^4.0.0"
+                "xtend": "4.0.2"
             }
         },
         "ecc-jsbn": {
@@ -4283,8 +4283,8 @@
             "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
             "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
             "requires": {
-                "jsbn": "~0.1.0",
-                "safer-buffer": "^2.1.0"
+                "jsbn": "0.1.1",
+                "safer-buffer": "2.1.2"
             }
         },
         "ecdsa-sig-formatter": {
@@ -4292,7 +4292,7 @@
             "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
             "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
             "requires": {
-                "safe-buffer": "^5.0.1"
+                "safe-buffer": "5.1.2"
             }
         },
         "ee-first": {
@@ -4310,13 +4310,13 @@
             "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.0.tgz",
             "integrity": "sha512-eFOJTMyCYb7xtE/caJ6JJu+bhi67WCYNbkGSknu20pmM8Ke/bqOfdnZWxyoGN26JgfxTbXrsCkEw4KheCT/KGg==",
             "requires": {
-                "bn.js": "^4.4.0",
-                "brorand": "^1.0.1",
-                "hash.js": "^1.0.0",
-                "hmac-drbg": "^1.0.0",
-                "inherits": "^2.0.1",
-                "minimalistic-assert": "^1.0.0",
-                "minimalistic-crypto-utils": "^1.0.0"
+                "bn.js": "4.11.8",
+                "brorand": "1.1.0",
+                "hash.js": "1.1.7",
+                "hmac-drbg": "1.0.1",
+                "inherits": "2.0.3",
+                "minimalistic-assert": "1.0.1",
+                "minimalistic-crypto-utils": "1.0.1"
             }
         },
         "emoji-regex": {
@@ -4334,8 +4334,8 @@
             "resolved": "https://registry.npmjs.org/emotion/-/emotion-10.0.23.tgz",
             "integrity": "sha512-H/x+5rJUnSvI0rdYsAFyDfuQwE0poZgTMj5TQsKirLzyHVWqs6CiUponsdE86sisXw0vS60j91HAbidJJeDt1g==",
             "requires": {
-                "babel-plugin-emotion": "^10.0.23",
-                "create-emotion": "^10.0.14"
+                "babel-plugin-emotion": "10.0.23",
+                "create-emotion": "10.0.14"
             }
         },
         "encodeurl": {
@@ -4348,7 +4348,7 @@
             "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz",
             "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=",
             "requires": {
-                "iconv-lite": "~0.4.13"
+                "iconv-lite": "0.4.24"
             }
         },
         "end-of-stream": {
@@ -4356,7 +4356,7 @@
             "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz",
             "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==",
             "requires": {
-                "once": "^1.4.0"
+                "once": "1.4.0"
             }
         },
         "engine.io": {
@@ -4364,12 +4364,12 @@
             "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.4.0.tgz",
             "integrity": "sha512-XCyYVWzcHnK5cMz7G4VTu2W7zJS7SM1QkcelghyIk/FmobWBtXE7fwhBusEKvCSqc3bMh8fNFMlUkCKTFRxH2w==",
             "requires": {
-                "accepts": "~1.3.4",
+                "accepts": "1.3.7",
                 "base64id": "2.0.0",
                 "cookie": "0.3.1",
-                "debug": "~4.1.0",
-                "engine.io-parser": "~2.2.0",
-                "ws": "^7.1.2"
+                "debug": "4.1.1",
+                "engine.io-parser": "2.2.0",
+                "ws": "7.2.1"
             },
             "dependencies": {
                 "debug": {
@@ -4377,7 +4377,7 @@
                     "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
                     "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
                     "requires": {
-                        "ms": "^2.1.1"
+                        "ms": "2.1.2"
                     }
                 },
                 "ms": {
@@ -4394,14 +4394,14 @@
             "requires": {
                 "component-emitter": "1.2.1",
                 "component-inherit": "0.0.3",
-                "debug": "~4.1.0",
-                "engine.io-parser": "~2.2.0",
+                "debug": "4.1.1",
+                "engine.io-parser": "2.2.0",
                 "has-cors": "1.1.0",
                 "indexof": "0.0.1",
                 "parseqs": "0.0.5",
                 "parseuri": "0.0.5",
-                "ws": "~6.1.0",
-                "xmlhttprequest-ssl": "~1.5.4",
+                "ws": "6.1.4",
+                "xmlhttprequest-ssl": "1.5.5",
                 "yeast": "0.1.2"
             },
             "dependencies": {
@@ -4415,7 +4415,7 @@
                     "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
                     "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
                     "requires": {
-                        "ms": "^2.1.1"
+                        "ms": "2.1.2"
                     }
                 },
                 "ms": {
@@ -4428,7 +4428,7 @@
                     "resolved": "https://registry.npmjs.org/ws/-/ws-6.1.4.tgz",
                     "integrity": "sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA==",
                     "requires": {
-                        "async-limiter": "~1.0.0"
+                        "async-limiter": "1.0.1"
                     }
                 }
             }
@@ -4439,10 +4439,10 @@
             "integrity": "sha512-6I3qD9iUxotsC5HEMuuGsKA0cXerGz+4uGcXQEkfBidgKf0amsjrrtwcbwK/nzpZBxclXlV7gGl9dgWvu4LF6w==",
             "requires": {
                 "after": "0.8.2",
-                "arraybuffer.slice": "~0.0.7",
+                "arraybuffer.slice": "0.0.7",
                 "base64-arraybuffer": "0.1.5",
                 "blob": "0.0.5",
-                "has-binary2": "~1.0.2"
+                "has-binary2": "1.0.3"
             }
         },
         "enhanced-resolve": {
@@ -4451,9 +4451,9 @@
             "integrity": "sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng==",
             "dev": true,
             "requires": {
-                "graceful-fs": "^4.1.2",
-                "memory-fs": "^0.4.0",
-                "tapable": "^1.0.0"
+                "graceful-fs": "4.2.0",
+                "memory-fs": "0.4.1",
+                "tapable": "1.1.3"
             }
         },
         "entities": {
@@ -4466,8 +4466,8 @@
             "resolved": "https://registry.npmjs.org/envify/-/envify-3.4.1.tgz",
             "integrity": "sha1-1xIjKejfFoi6dxsSUBkXyc5cvOg=",
             "requires": {
-                "jstransform": "^11.0.3",
-                "through": "~2.3.4"
+                "jstransform": "11.0.3",
+                "through": "2.3.8"
             }
         },
         "errno": {
@@ -4476,7 +4476,7 @@
             "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==",
             "dev": true,
             "requires": {
-                "prr": "~1.0.1"
+                "prr": "1.0.1"
             }
         },
         "error-ex": {
@@ -4484,7 +4484,7 @@
             "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
             "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
             "requires": {
-                "is-arrayish": "^0.2.1"
+                "is-arrayish": "0.2.1"
             }
         },
         "es-abstract": {
@@ -4492,16 +4492,16 @@
             "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.16.0.tgz",
             "integrity": "sha512-xdQnfykZ9JMEiasTAJZJdMWCQ1Vm00NBw79/AWi7ELfZuuPCSOMDZbT9mkOfSctVtfhb+sAAzrm+j//GjjLHLg==",
             "requires": {
-                "es-to-primitive": "^1.2.0",
-                "function-bind": "^1.1.1",
-                "has": "^1.0.3",
-                "has-symbols": "^1.0.0",
-                "is-callable": "^1.1.4",
-                "is-regex": "^1.0.4",
-                "object-inspect": "^1.6.0",
-                "object-keys": "^1.1.1",
-                "string.prototype.trimleft": "^2.1.0",
-                "string.prototype.trimright": "^2.1.0"
+                "es-to-primitive": "1.2.0",
+                "function-bind": "1.1.1",
+                "has": "1.0.3",
+                "has-symbols": "1.0.0",
+                "is-callable": "1.1.4",
+                "is-regex": "1.0.4",
+                "object-inspect": "1.6.0",
+                "object-keys": "1.1.1",
+                "string.prototype.trimleft": "2.1.0",
+                "string.prototype.trimright": "2.1.0"
             }
         },
         "es-to-primitive": {
@@ -4509,9 +4509,9 @@
             "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz",
             "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==",
             "requires": {
-                "is-callable": "^1.1.4",
-                "is-date-object": "^1.0.1",
-                "is-symbol": "^1.0.2"
+                "is-callable": "1.1.4",
+                "is-date-object": "1.0.1",
+                "is-symbol": "1.0.2"
             }
         },
         "es5-ext": {
@@ -4520,9 +4520,9 @@
             "integrity": "sha512-KMzZTPBkeQV/JcSQhI5/z6d9VWJ3EnQ194USTUwIYZ2ZbpN8+SGXQKt1h68EX44+qt+Fzr8DO17vnxrw7c3agw==",
             "dev": true,
             "requires": {
-                "es6-iterator": "~2.0.3",
-                "es6-symbol": "~3.1.1",
-                "next-tick": "^1.0.0"
+                "es6-iterator": "2.0.3",
+                "es6-symbol": "3.1.1",
+                "next-tick": "1.0.0"
             }
         },
         "es6-iterator": {
@@ -4531,9 +4531,9 @@
             "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=",
             "dev": true,
             "requires": {
-                "d": "1",
-                "es5-ext": "^0.10.35",
-                "es6-symbol": "^3.1.1"
+                "d": "1.0.1",
+                "es5-ext": "0.10.50",
+                "es6-symbol": "3.1.1"
             }
         },
         "es6-promise": {
@@ -4546,7 +4546,7 @@
             "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz",
             "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=",
             "requires": {
-                "es6-promise": "^4.0.3"
+                "es6-promise": "4.2.8"
             },
             "dependencies": {
                 "es6-promise": {
@@ -4562,8 +4562,8 @@
             "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=",
             "dev": true,
             "requires": {
-                "d": "1",
-                "es5-ext": "~0.10.14"
+                "d": "1.0.1",
+                "es5-ext": "0.10.50"
             }
         },
         "escape-html": {
@@ -4582,11 +4582,11 @@
             "integrity": "sha512-Q8t2YZ+0e0pc7NRVj3B4tSQ9rim1oi4Fh46k2xhJ2qOiEwhQfdjyEQddWdj7ZFaKmU+5104vn1qrcjEPWq+bgQ==",
             "dev": true,
             "requires": {
-                "esprima": "^3.1.3",
-                "estraverse": "^4.2.0",
-                "esutils": "^2.0.2",
-                "optionator": "^0.8.1",
-                "source-map": "~0.6.1"
+                "esprima": "3.1.3",
+                "estraverse": "4.3.0",
+                "esutils": "2.0.2",
+                "optionator": "0.8.3",
+                "source-map": "0.6.1"
             },
             "dependencies": {
                 "esprima": {
@@ -4610,8 +4610,8 @@
             "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==",
             "dev": true,
             "requires": {
-                "esrecurse": "^4.1.0",
-                "estraverse": "^4.1.1"
+                "esrecurse": "4.2.1",
+                "estraverse": "4.3.0"
             }
         },
         "esprima": {
@@ -4625,7 +4625,7 @@
             "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==",
             "dev": true,
             "requires": {
-                "estraverse": "^4.1.0"
+                "estraverse": "4.3.0"
             }
         },
         "estraverse": {
@@ -4654,13 +4654,13 @@
             "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.5.tgz",
             "integrity": "sha512-vyibDcu5JL20Me1fP734QBH/kenBGLZap2n0+XXM7mvuUPzJ20Ydqj1aKcIeMdri1p+PU+4yAKugjN8KCVst+g==",
             "requires": {
-                "duplexer": "^0.1.1",
-                "from": "^0.1.7",
+                "duplexer": "0.1.1",
+                "from": "0.1.7",
                 "map-stream": "0.0.7",
-                "pause-stream": "^0.0.11",
-                "split": "^1.0.1",
-                "stream-combiner": "^0.2.2",
-                "through": "^2.3.8"
+                "pause-stream": "0.0.11",
+                "split": "1.0.1",
+                "stream-combiner": "0.2.2",
+                "through": "2.3.8"
             }
         },
         "event-target-shim": {
@@ -4685,7 +4685,7 @@
             "integrity": "sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ==",
             "dev": true,
             "requires": {
-                "original": "^1.0.0"
+                "original": "1.0.2"
             }
         },
         "evp_bytestokey": {
@@ -4693,8 +4693,8 @@
             "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz",
             "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==",
             "requires": {
-                "md5.js": "^1.3.4",
-                "safe-buffer": "^5.1.1"
+                "md5.js": "1.3.5",
+                "safe-buffer": "5.1.2"
             }
         },
         "execa": {
@@ -4702,13 +4702,13 @@
             "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz",
             "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=",
             "requires": {
-                "cross-spawn": "^5.0.1",
-                "get-stream": "^3.0.0",
-                "is-stream": "^1.1.0",
-                "npm-run-path": "^2.0.0",
-                "p-finally": "^1.0.0",
-                "signal-exit": "^3.0.0",
-                "strip-eof": "^1.0.0"
+                "cross-spawn": "5.1.0",
+                "get-stream": "3.0.0",
+                "is-stream": "1.1.0",
+                "npm-run-path": "2.0.2",
+                "p-finally": "1.0.0",
+                "signal-exit": "3.0.2",
+                "strip-eof": "1.0.0"
             },
             "dependencies": {
                 "cross-spawn": {
@@ -4716,9 +4716,9 @@
                     "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
                     "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=",
                     "requires": {
-                        "lru-cache": "^4.0.1",
-                        "shebang-command": "^1.2.0",
-                        "which": "^1.2.9"
+                        "lru-cache": "4.1.5",
+                        "shebang-command": "1.2.0",
+                        "which": "1.3.1"
                     }
                 }
             }
@@ -4733,7 +4733,7 @@
             "resolved": "https://registry.npmjs.org/exif/-/exif-0.6.0.tgz",
             "integrity": "sha1-YKYmaAdlQst+T1cZnUrG830sX0o=",
             "requires": {
-                "debug": "^2.2"
+                "debug": "2.6.9"
             }
         },
         "expand-brackets": {
@@ -4741,13 +4741,13 @@
             "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
             "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=",
             "requires": {
-                "debug": "^2.3.3",
-                "define-property": "^0.2.5",
-                "extend-shallow": "^2.0.1",
-                "posix-character-classes": "^0.1.0",
-                "regex-not": "^1.0.0",
-                "snapdragon": "^0.8.1",
-                "to-regex": "^3.0.1"
+                "debug": "2.6.9",
+                "define-property": "0.2.5",
+                "extend-shallow": "2.0.1",
+                "posix-character-classes": "0.1.1",
+                "regex-not": "1.0.2",
+                "snapdragon": "0.8.2",
+                "to-regex": "3.0.2"
             },
             "dependencies": {
                 "define-property": {
@@ -4755,7 +4755,7 @@
                     "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
                     "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
                     "requires": {
-                        "is-descriptor": "^0.1.0"
+                        "is-descriptor": "0.1.6"
                     }
                 },
                 "extend-shallow": {
@@ -4763,7 +4763,7 @@
                     "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
                     "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
                     "requires": {
-                        "is-extendable": "^0.1.0"
+                        "is-extendable": "0.1.1"
                     }
                 }
             }
@@ -4779,7 +4779,7 @@
             "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=",
             "dev": true,
             "requires": {
-                "homedir-polyfill": "^1.0.1"
+                "homedir-polyfill": "1.0.3"
             }
         },
         "express": {
@@ -4787,36 +4787,36 @@
             "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",
             "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==",
             "requires": {
-                "accepts": "~1.3.7",
+                "accepts": "1.3.7",
                 "array-flatten": "1.1.1",
                 "body-parser": "1.19.0",
                 "content-disposition": "0.5.3",
-                "content-type": "~1.0.4",
+                "content-type": "1.0.4",
                 "cookie": "0.4.0",
                 "cookie-signature": "1.0.6",
                 "debug": "2.6.9",
-                "depd": "~1.1.2",
-                "encodeurl": "~1.0.2",
-                "escape-html": "~1.0.3",
-                "etag": "~1.8.1",
-                "finalhandler": "~1.1.2",
+                "depd": "1.1.2",
+                "encodeurl": "1.0.2",
+                "escape-html": "1.0.3",
+                "etag": "1.8.1",
+                "finalhandler": "1.1.2",
                 "fresh": "0.5.2",
                 "merge-descriptors": "1.0.1",
-                "methods": "~1.1.2",
-                "on-finished": "~2.3.0",
-                "parseurl": "~1.3.3",
+                "methods": "1.1.2",
+                "on-finished": "2.3.0",
+                "parseurl": "1.3.3",
                 "path-to-regexp": "0.1.7",
-                "proxy-addr": "~2.0.5",
+                "proxy-addr": "2.0.5",
                 "qs": "6.7.0",
-                "range-parser": "~1.2.1",
+                "range-parser": "1.2.1",
                 "safe-buffer": "5.1.2",
                 "send": "0.17.1",
                 "serve-static": "1.14.1",
                 "setprototypeof": "1.1.1",
-                "statuses": "~1.5.0",
-                "type-is": "~1.6.18",
+                "statuses": "1.5.0",
+                "type-is": "1.6.18",
                 "utils-merge": "1.0.1",
-                "vary": "~1.1.2"
+                "vary": "1.1.2"
             },
             "dependencies": {
                 "cookie": {
@@ -4831,7 +4831,7 @@
             "resolved": "https://registry.npmjs.org/express-flash/-/express-flash-0.0.2.tgz",
             "integrity": "sha1-I9GovPP5DXB5KOSJ+Whp7K0KzaI=",
             "requires": {
-                "connect-flash": "0.1.x"
+                "connect-flash": "0.1.1"
             }
         },
         "express-session": {
@@ -4842,11 +4842,11 @@
                 "cookie": "0.4.0",
                 "cookie-signature": "1.0.6",
                 "debug": "2.6.9",
-                "depd": "~2.0.0",
-                "on-headers": "~1.0.2",
-                "parseurl": "~1.3.3",
+                "depd": "2.0.0",
+                "on-headers": "1.0.2",
+                "parseurl": "1.3.3",
                 "safe-buffer": "5.2.0",
-                "uid-safe": "~2.1.5"
+                "uid-safe": "2.1.5"
             },
             "dependencies": {
                 "cookie": {
@@ -4871,8 +4871,8 @@
             "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-5.3.1.tgz",
             "integrity": "sha512-g8xkipBF6VxHbO1+ksC7nxUU7+pWif0+OZXjZTybKJ/V0aTVhuCoHbyhIPgSYVldwQLocGExPtB2pE0DqK4jsw==",
             "requires": {
-                "lodash": "^4.17.10",
-                "validator": "^10.4.0"
+                "lodash": "4.17.15",
+                "validator": "10.11.0"
             }
         },
         "expressjs": {
@@ -4890,8 +4890,8 @@
             "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
             "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=",
             "requires": {
-                "assign-symbols": "^1.0.0",
-                "is-extendable": "^1.0.1"
+                "assign-symbols": "1.0.0",
+                "is-extendable": "1.0.1"
             },
             "dependencies": {
                 "is-extendable": {
@@ -4899,7 +4899,7 @@
                     "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
                     "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
                     "requires": {
-                        "is-plain-object": "^2.0.4"
+                        "is-plain-object": "2.0.4"
                     }
                 }
             }
@@ -4909,14 +4909,14 @@
             "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz",
             "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==",
             "requires": {
-                "array-unique": "^0.3.2",
-                "define-property": "^1.0.0",
-                "expand-brackets": "^2.1.4",
-                "extend-shallow": "^2.0.1",
-                "fragment-cache": "^0.2.1",
-                "regex-not": "^1.0.0",
-                "snapdragon": "^0.8.1",
-                "to-regex": "^3.0.1"
+                "array-unique": "0.3.2",
+                "define-property": "1.0.0",
+                "expand-brackets": "2.1.4",
+                "extend-shallow": "2.0.1",
+                "fragment-cache": "0.2.1",
+                "regex-not": "1.0.2",
+                "snapdragon": "0.8.2",
+                "to-regex": "3.0.2"
             },
             "dependencies": {
                 "define-property": {
@@ -4924,7 +4924,7 @@
                     "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
                     "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
                     "requires": {
-                        "is-descriptor": "^1.0.0"
+                        "is-descriptor": "1.0.2"
                     }
                 },
                 "extend-shallow": {
@@ -4932,7 +4932,7 @@
                     "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
                     "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
                     "requires": {
-                        "is-extendable": "^0.1.0"
+                        "is-extendable": "0.1.1"
                     }
                 },
                 "is-accessor-descriptor": {
@@ -4940,7 +4940,7 @@
                     "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
                     "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
                     "requires": {
-                        "kind-of": "^6.0.0"
+                        "kind-of": "6.0.2"
                     }
                 },
                 "is-data-descriptor": {
@@ -4948,7 +4948,7 @@
                     "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
                     "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
                     "requires": {
-                        "kind-of": "^6.0.0"
+                        "kind-of": "6.0.2"
                     }
                 },
                 "is-descriptor": {
@@ -4956,9 +4956,9 @@
                     "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
                     "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
                     "requires": {
-                        "is-accessor-descriptor": "^1.0.0",
-                        "is-data-descriptor": "^1.0.0",
-                        "kind-of": "^6.0.2"
+                        "is-accessor-descriptor": "1.0.0",
+                        "is-data-descriptor": "1.0.0",
+                        "kind-of": "6.0.2"
                     }
                 }
             }
@@ -4995,7 +4995,7 @@
             "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=",
             "dev": true,
             "requires": {
-                "websocket-driver": ">=0.5.1"
+                "websocket-driver": "0.7.3"
             }
         },
         "fbjs": {
@@ -5003,13 +5003,13 @@
             "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.17.tgz",
             "integrity": "sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90=",
             "requires": {
-                "core-js": "^1.0.0",
-                "isomorphic-fetch": "^2.1.1",
-                "loose-envify": "^1.0.0",
-                "object-assign": "^4.1.0",
-                "promise": "^7.1.1",
-                "setimmediate": "^1.0.5",
-                "ua-parser-js": "^0.7.18"
+                "core-js": "1.2.7",
+                "isomorphic-fetch": "2.2.1",
+                "loose-envify": "1.4.0",
+                "object-assign": "4.1.1",
+                "promise": "7.3.1",
+                "setimmediate": "1.0.5",
+                "ua-parser-js": "0.7.20"
             }
         },
         "figgy-pudding": {
@@ -5024,8 +5024,8 @@
             "integrity": "sha512-4sNIOXgtH/9WZq4NvlfU3Opn5ynUsqBwSLyM+I7UOwdGigTBYfVVQEwe/msZNX/j4pCJTIM14Fsw66Svo1oVrw==",
             "dev": true,
             "requires": {
-                "loader-utils": "^1.0.2",
-                "schema-utils": "^1.0.0"
+                "loader-utils": "1.2.3",
+                "schema-utils": "1.0.0"
             },
             "dependencies": {
                 "schema-utils": {
@@ -5034,9 +5034,9 @@
                     "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==",
                     "dev": true,
                     "requires": {
-                        "ajv": "^6.1.0",
-                        "ajv-errors": "^1.0.0",
-                        "ajv-keywords": "^3.1.0"
+                        "ajv": "6.10.2",
+                        "ajv-errors": "1.0.1",
+                        "ajv-keywords": "3.4.1"
                     }
                 }
             }
@@ -5053,7 +5053,7 @@
             "integrity": "sha1-9KGVc1Xdr0Q8zXiolfPVXiPIoDQ=",
             "dev": true,
             "requires": {
-                "debounce": "^1.0.0"
+                "debounce": "1.2.0"
             }
         },
         "fill-range": {
@@ -5061,10 +5061,10 @@
             "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
             "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=",
             "requires": {
-                "extend-shallow": "^2.0.1",
-                "is-number": "^3.0.0",
-                "repeat-string": "^1.6.1",
-                "to-regex-range": "^2.1.0"
+                "extend-shallow": "2.0.1",
+                "is-number": "3.0.0",
+                "repeat-string": "1.6.1",
+                "to-regex-range": "2.1.1"
             },
             "dependencies": {
                 "extend-shallow": {
@@ -5072,7 +5072,7 @@
                     "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
                     "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
                     "requires": {
-                        "is-extendable": "^0.1.0"
+                        "is-extendable": "0.1.1"
                     }
                 }
             }
@@ -5083,12 +5083,12 @@
             "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
             "requires": {
                 "debug": "2.6.9",
-                "encodeurl": "~1.0.2",
-                "escape-html": "~1.0.3",
-                "on-finished": "~2.3.0",
-                "parseurl": "~1.3.3",
-                "statuses": "~1.5.0",
-                "unpipe": "~1.0.0"
+                "encodeurl": "1.0.2",
+                "escape-html": "1.0.3",
+                "on-finished": "2.3.0",
+                "parseurl": "1.3.3",
+                "statuses": "1.5.0",
+                "unpipe": "1.0.0"
             }
         },
         "find": {
@@ -5096,7 +5096,7 @@
             "resolved": "https://registry.npmjs.org/find/-/find-0.1.7.tgz",
             "integrity": "sha1-yGyHrxqxjyIrvjjeyGy8dg0Wpvs=",
             "requires": {
-                "traverse-chain": "~0.1.0"
+                "traverse-chain": "0.1.0"
             }
         },
         "find-cache-dir": {
@@ -5105,9 +5105,9 @@
             "integrity": "sha1-kojj6ePMN0hxfTnq3hfPcfww7m8=",
             "dev": true,
             "requires": {
-                "commondir": "^1.0.1",
-                "make-dir": "^1.0.0",
-                "pkg-dir": "^2.0.0"
+                "commondir": "1.0.1",
+                "make-dir": "1.3.0",
+                "pkg-dir": "2.0.0"
             }
         },
         "find-in-files": {
@@ -5115,8 +5115,8 @@
             "resolved": "https://registry.npmjs.org/find-in-files/-/find-in-files-0.5.0.tgz",
             "integrity": "sha512-VraTc6HdtdSHmAp0yJpAy20yPttGKzyBWc7b7FPnnsX9TOgmKx0g9xajizpF/iuu4IvNK4TP0SpyBT9zAlwG+g==",
             "requires": {
-                "find": "^0.1.5",
-                "q": "^1.0.1"
+                "find": "0.1.7",
+                "q": "1.5.1"
             }
         },
         "find-root": {
@@ -5129,7 +5129,7 @@
             "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
             "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=",
             "requires": {
-                "locate-path": "^2.0.0"
+                "locate-path": "2.0.0"
             }
         },
         "findup-sync": {
@@ -5138,10 +5138,10 @@
             "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==",
             "dev": true,
             "requires": {
-                "detect-file": "^1.0.0",
-                "is-glob": "^4.0.0",
-                "micromatch": "^3.0.4",
-                "resolve-dir": "^1.0.1"
+                "detect-file": "1.0.0",
+                "is-glob": "4.0.1",
+                "micromatch": "3.1.10",
+                "resolve-dir": "1.0.1"
             }
         },
         "flat": {
@@ -5149,7 +5149,7 @@
             "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz",
             "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==",
             "requires": {
-                "is-buffer": "~2.0.3"
+                "is-buffer": "2.0.4"
             },
             "dependencies": {
                 "is-buffer": {
@@ -5170,8 +5170,8 @@
             "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==",
             "dev": true,
             "requires": {
-                "inherits": "^2.0.3",
-                "readable-stream": "^2.3.6"
+                "inherits": "2.0.3",
+                "readable-stream": "2.3.6"
             }
         },
         "follow-redirects": {
@@ -5180,7 +5180,7 @@
             "integrity": "sha512-CRcPzsSIbXyVDl0QI01muNDu69S8trU4jArW9LpOt2WtC6LyUJetcIrmfHsRBx7/Jb6GHJUiuqyYxPooFfNt6A==",
             "dev": true,
             "requires": {
-                "debug": "^3.0.0"
+                "debug": "3.2.6"
             },
             "dependencies": {
                 "debug": {
@@ -5189,7 +5189,7 @@
                     "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
                     "dev": true,
                     "requires": {
-                        "ms": "^2.1.1"
+                        "ms": "2.1.2"
                     }
                 },
                 "ms": {
@@ -5210,7 +5210,7 @@
             "resolved": "https://registry.npmjs.org/for-each-property/-/for-each-property-0.0.4.tgz",
             "integrity": "sha1-z6hXrsFCLh0Sb/CHhPz2Jim8g/Y=",
             "requires": {
-                "get-prototype-chain": "^1.0.1"
+                "get-prototype-chain": "1.0.1"
             }
         },
         "for-each-property-deep": {
@@ -5237,14 +5237,14 @@
             "integrity": "sha512-vqOY5gakcoon2s12V7MMe01OPwfgqulUWFzm+geQaPPOBKjW1I7aqqoBVlU0ECn97liMB0ECs16pRdIGe9qdRw==",
             "dev": true,
             "requires": {
-                "babel-code-frame": "^6.22.0",
-                "chalk": "^2.4.1",
-                "chokidar": "^2.0.4",
-                "micromatch": "^3.1.10",
-                "minimatch": "^3.0.4",
-                "semver": "^5.6.0",
-                "tapable": "^1.0.0",
-                "worker-rpc": "^0.1.0"
+                "babel-code-frame": "6.26.0",
+                "chalk": "2.4.2",
+                "chokidar": "2.1.8",
+                "micromatch": "3.1.10",
+                "minimatch": "3.0.4",
+                "semver": "5.7.0",
+                "tapable": "1.1.3",
+                "worker-rpc": "0.1.1"
             },
             "dependencies": {
                 "ansi-styles": {
@@ -5253,7 +5253,7 @@
                     "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
                     "dev": true,
                     "requires": {
-                        "color-convert": "^1.9.0"
+                        "color-convert": "1.9.3"
                     }
                 },
                 "chalk": {
@@ -5262,9 +5262,9 @@
                     "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
                     "dev": true,
                     "requires": {
-                        "ansi-styles": "^3.2.1",
-                        "escape-string-regexp": "^1.0.5",
-                        "supports-color": "^5.3.0"
+                        "ansi-styles": "3.2.1",
+                        "escape-string-regexp": "1.0.5",
+                        "supports-color": "5.5.0"
                     }
                 },
                 "supports-color": {
@@ -5273,7 +5273,7 @@
                     "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
                     "dev": true,
                     "requires": {
-                        "has-flag": "^3.0.0"
+                        "has-flag": "3.0.0"
                     }
                 }
             }
@@ -5283,9 +5283,9 @@
             "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz",
             "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==",
             "requires": {
-                "asynckit": "^0.4.0",
-                "combined-stream": "^1.0.6",
-                "mime-types": "^2.1.12"
+                "asynckit": "0.4.0",
+                "combined-stream": "1.0.8",
+                "mime-types": "2.1.24"
             }
         },
         "formidable": {
@@ -5303,7 +5303,7 @@
             "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz",
             "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=",
             "requires": {
-                "map-cache": "^0.2.2"
+                "map-cache": "0.2.2"
             }
         },
         "fresh": {
@@ -5322,8 +5322,8 @@
             "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=",
             "dev": true,
             "requires": {
-                "inherits": "^2.0.1",
-                "readable-stream": "^2.0.0"
+                "inherits": "2.0.3",
+                "readable-stream": "2.3.6"
             }
         },
         "fs-constants": {
@@ -5336,11 +5336,11 @@
             "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.26.7.tgz",
             "integrity": "sha1-muH92UiXeY7at20JGM9C0MMYT6k=",
             "requires": {
-                "graceful-fs": "^4.1.2",
-                "jsonfile": "^2.1.0",
-                "klaw": "^1.0.0",
-                "path-is-absolute": "^1.0.0",
-                "rimraf": "^2.2.8"
+                "graceful-fs": "4.2.0",
+                "jsonfile": "2.4.0",
+                "klaw": "1.3.1",
+                "path-is-absolute": "1.0.1",
+                "rimraf": "2.7.1"
             },
             "dependencies": {
                 "rimraf": {
@@ -5348,7 +5348,7 @@
                     "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
                     "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
                     "requires": {
-                        "glob": "^7.1.3"
+                        "glob": "7.1.4"
                     }
                 }
             }
@@ -5358,7 +5358,7 @@
             "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.6.tgz",
             "integrity": "sha512-crhvyXcMejjv3Z5d2Fa9sf5xLYVCF5O1c71QxbVnbLsmYMBEvDAftewesN/HhY03YRoA7zOMxjNGrF5svGaaeQ==",
             "requires": {
-                "minipass": "^2.2.1"
+                "minipass": "2.3.5"
             }
         },
         "fs-write-stream-atomic": {
@@ -5367,10 +5367,10 @@
             "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=",
             "dev": true,
             "requires": {
-                "graceful-fs": "^4.1.2",
-                "iferr": "^0.1.5",
-                "imurmurhash": "^0.1.4",
-                "readable-stream": "1 || 2"
+                "graceful-fs": "4.2.0",
+                "iferr": "0.1.5",
+                "imurmurhash": "0.1.4",
+                "readable-stream": "2.3.6"
             }
         },
         "fs.realpath": {
@@ -5384,9 +5384,9 @@
             "integrity": "sha512-+ux3lx6peh0BpvY0JebGyZoiR4D+oYzdPZMKJwkZ+sFkNJzpL7tXc/wehS49gUAxg3tmMHPHZkA8JU2rhhgDHw==",
             "optional": true,
             "requires": {
-                "bindings": "^1.5.0",
-                "nan": "^2.12.1",
-                "node-pre-gyp": "*"
+                "bindings": "1.5.0",
+                "nan": "2.14.0",
+                "node-pre-gyp": "0.14.0"
             },
             "dependencies": {
                 "abbrev": {
@@ -5408,8 +5408,8 @@
                     "bundled": true,
                     "optional": true,
                     "requires": {
-                        "delegates": "^1.0.0",
-                        "readable-stream": "^2.0.6"
+                        "delegates": "1.0.0",
+                        "readable-stream": "2.3.6"
                     }
                 },
                 "balanced-match": {
@@ -5420,7 +5420,7 @@
                     "version": "1.1.11",
                     "bundled": true,
                     "requires": {
-                        "balanced-match": "^1.0.0",
+                        "balanced-match": "1.0.0",
                         "concat-map": "0.0.1"
                     }
                 },
@@ -5451,7 +5451,7 @@
                     "bundled": true,
                     "optional": true,
                     "requires": {
-                        "ms": "^2.1.1"
+                        "ms": "2.1.2"
                     }
                 },
                 "deep-extend": {
@@ -5474,7 +5474,7 @@
                     "bundled": true,
                     "optional": true,
                     "requires": {
-                        "minipass": "^2.6.0"
+                        "minipass": "2.9.0"
                     }
                 },
                 "fs.realpath": {
@@ -5487,14 +5487,14 @@
                     "bundled": true,
                     "optional": true,
                     "requires": {
-                        "aproba": "^1.0.3",
-                        "console-control-strings": "^1.0.0",
-                        "has-unicode": "^2.0.0",
-                        "object-assign": "^4.1.0",
-                        "signal-exit": "^3.0.0",
-                        "string-width": "^1.0.1",
-                        "strip-ansi": "^3.0.1",
-                        "wide-align": "^1.1.0"
+                        "aproba": "1.2.0",
+                        "console-control-strings": "1.1.0",
+                        "has-unicode": "2.0.1",
+                        "object-assign": "4.1.1",
+                        "signal-exit": "3.0.2",
+                        "string-width": "1.0.2",
+                        "strip-ansi": "3.0.1",
+                        "wide-align": "1.1.3"
                     }
                 },
                 "glob": {
@@ -5502,12 +5502,12 @@
                     "bundled": true,
                     "optional": true,
                     "requires": {
-                        "fs.realpath": "^1.0.0",
-                        "inflight": "^1.0.4",
-                        "inherits": "2",
-                        "minimatch": "^3.0.4",
-                        "once": "^1.3.0",
-                        "path-is-absolute": "^1.0.0"
+                        "fs.realpath": "1.0.0",
+                        "inflight": "1.0.6",
+                        "inherits": "2.0.4",
+                        "minimatch": "3.0.4",
+                        "once": "1.4.0",
+                        "path-is-absolute": "1.0.1"
                     }
                 },
                 "has-unicode": {
@@ -5520,7 +5520,7 @@
                     "bundled": true,
                     "optional": true,
                     "requires": {
-                        "safer-buffer": ">= 2.1.2 < 3"
+                        "safer-buffer": "2.1.2"
                     }
                 },
                 "ignore-walk": {
@@ -5528,7 +5528,7 @@
                     "bundled": true,
                     "optional": true,
                     "requires": {
-                        "minimatch": "^3.0.4"
+                        "minimatch": "3.0.4"
                     }
                 },
                 "inflight": {
@@ -5536,8 +5536,8 @@
                     "bundled": true,
                     "optional": true,
                     "requires": {
-                        "once": "^1.3.0",
-                        "wrappy": "1"
+                        "once": "1.4.0",
+                        "wrappy": "1.0.2"
                     }
                 },
                 "inherits": {
@@ -5553,7 +5553,7 @@
                     "version": "1.0.0",
                     "bundled": true,
                     "requires": {
-                        "number-is-nan": "^1.0.0"
+                        "number-is-nan": "1.0.1"
                     }
                 },
                 "isarray": {
@@ -5565,7 +5565,7 @@
                     "version": "3.0.4",
                     "bundled": true,
                     "requires": {
-                        "brace-expansion": "^1.1.7"
+                        "brace-expansion": "1.1.11"
                     }
                 },
                 "minimist": {
@@ -5576,8 +5576,8 @@
                     "version": "2.9.0",
                     "bundled": true,
                     "requires": {
-                        "safe-buffer": "^5.1.2",
-                        "yallist": "^3.0.0"
+                        "safe-buffer": "5.1.2",
+                        "yallist": "3.1.1"
                     }
                 },
                 "minizlib": {
@@ -5585,7 +5585,7 @@
                     "bundled": true,
                     "optional": true,
                     "requires": {
-                        "minipass": "^2.9.0"
+                        "minipass": "2.9.0"
                     }
                 },
                 "mkdirp": {
@@ -5605,9 +5605,9 @@
                     "bundled": true,
                     "optional": true,
                     "requires": {
-                        "debug": "^3.2.6",
-                        "iconv-lite": "^0.4.4",
-                        "sax": "^1.2.4"
+                        "debug": "3.2.6",
+                        "iconv-lite": "0.4.24",
+                        "sax": "1.2.4"
                     }
                 },
                 "node-pre-gyp": {
@@ -5615,16 +5615,16 @@
                     "bundled": true,
                     "optional": true,
                     "requires": {
-                        "detect-libc": "^1.0.2",
-                        "mkdirp": "^0.5.1",
-                        "needle": "^2.2.1",
-                        "nopt": "^4.0.1",
-                        "npm-packlist": "^1.1.6",
-                        "npmlog": "^4.0.2",
-                        "rc": "^1.2.7",
-                        "rimraf": "^2.6.1",
-                        "semver": "^5.3.0",
-                        "tar": "^4.4.2"
+                        "detect-libc": "1.0.3",
+                        "mkdirp": "0.5.1",
+                        "needle": "2.4.0",
+                        "nopt": "4.0.1",
+                        "npm-packlist": "1.4.7",
+                        "npmlog": "4.1.2",
+                        "rc": "1.2.8",
+                        "rimraf": "2.7.1",
+                        "semver": "5.7.1",
+                        "tar": "4.4.13"
                     }
                 },
                 "nopt": {
@@ -5632,8 +5632,8 @@
                     "bundled": true,
                     "optional": true,
                     "requires": {
-                        "abbrev": "1",
-                        "osenv": "^0.1.4"
+                        "abbrev": "1.1.1",
+                        "osenv": "0.1.5"
                     }
                 },
                 "npm-bundled": {
@@ -5641,7 +5641,7 @@
                     "bundled": true,
                     "optional": true,
                     "requires": {
-                        "npm-normalize-package-bin": "^1.0.1"
+                        "npm-normalize-package-bin": "1.0.1"
                     }
                 },
                 "npm-normalize-package-bin": {
@@ -5654,8 +5654,8 @@
                     "bundled": true,
                     "optional": true,
                     "requires": {
-                        "ignore-walk": "^3.0.1",
-                        "npm-bundled": "^1.0.1"
+                        "ignore-walk": "3.0.3",
+                        "npm-bundled": "1.1.1"
                     }
                 },
                 "npmlog": {
@@ -5663,10 +5663,10 @@
                     "bundled": true,
                     "optional": true,
                     "requires": {
-                        "are-we-there-yet": "~1.1.2",
-                        "console-control-strings": "~1.1.0",
-                        "gauge": "~2.7.3",
-                        "set-blocking": "~2.0.0"
+                        "are-we-there-yet": "1.1.5",
+                        "console-control-strings": "1.1.0",
+                        "gauge": "2.7.4",
+                        "set-blocking": "2.0.0"
                     }
                 },
                 "number-is-nan": {
@@ -5682,7 +5682,7 @@
                     "version": "1.4.0",
                     "bundled": true,
                     "requires": {
-                        "wrappy": "1"
+                        "wrappy": "1.0.2"
                     }
                 },
                 "os-homedir": {
@@ -5700,8 +5700,8 @@
                     "bundled": true,
                     "optional": true,
                     "requires": {
-                        "os-homedir": "^1.0.0",
-                        "os-tmpdir": "^1.0.0"
+                        "os-homedir": "1.0.2",
+                        "os-tmpdir": "1.0.2"
                     }
                 },
                 "path-is-absolute": {
@@ -5719,10 +5719,10 @@
                     "bundled": true,
                     "optional": true,
                     "requires": {
-                        "deep-extend": "^0.6.0",
-                        "ini": "~1.3.0",
-                        "minimist": "^1.2.0",
-                        "strip-json-comments": "~2.0.1"
+                        "deep-extend": "0.6.0",
+                        "ini": "1.3.5",
+                        "minimist": "1.2.0",
+                        "strip-json-comments": "2.0.1"
                     },
                     "dependencies": {
                         "minimist": {
@@ -5737,13 +5737,13 @@
                     "bundled": true,
                     "optional": true,
                     "requires": {
-                        "core-util-is": "~1.0.0",
-                        "inherits": "~2.0.3",
-                        "isarray": "~1.0.0",
-                        "process-nextick-args": "~2.0.0",
-                        "safe-buffer": "~5.1.1",
-                        "string_decoder": "~1.1.1",
-                        "util-deprecate": "~1.0.1"
+                        "core-util-is": "1.0.2",
+                        "inherits": "2.0.4",
+                        "isarray": "1.0.0",
+                        "process-nextick-args": "2.0.1",
+                        "safe-buffer": "5.1.2",
+                        "string_decoder": "1.1.1",
+                        "util-deprecate": "1.0.2"
                     }
                 },
                 "rimraf": {
@@ -5751,7 +5751,7 @@
                     "bundled": true,
                     "optional": true,
                     "requires": {
-                        "glob": "^7.1.3"
+                        "glob": "7.1.6"
                     }
                 },
                 "safe-buffer": {
@@ -5787,9 +5787,9 @@
                     "version": "1.0.2",
                     "bundled": true,
                     "requires": {
-                        "code-point-at": "^1.0.0",
-                        "is-fullwidth-code-point": "^1.0.0",
-                        "strip-ansi": "^3.0.0"
+                        "code-point-at": "1.1.0",
+                        "is-fullwidth-code-point": "1.0.0",
+                        "strip-ansi": "3.0.1"
                     }
                 },
                 "string_decoder": {
@@ -5797,14 +5797,14 @@
                     "bundled": true,
                     "optional": true,
                     "requires": {
-                        "safe-buffer": "~5.1.0"
+                        "safe-buffer": "5.1.2"
                     }
                 },
                 "strip-ansi": {
                     "version": "3.0.1",
                     "bundled": true,
                     "requires": {
-                        "ansi-regex": "^2.0.0"
+                        "ansi-regex": "2.1.1"
                     }
                 },
                 "strip-json-comments": {
@@ -5817,13 +5817,13 @@
                     "bundled": true,
                     "optional": true,
                     "requires": {
-                        "chownr": "^1.1.1",
-                        "fs-minipass": "^1.2.5",
-                        "minipass": "^2.8.6",
-                        "minizlib": "^1.2.1",
-                        "mkdirp": "^0.5.0",
-                        "safe-buffer": "^5.1.2",
-                        "yallist": "^3.0.3"
+                        "chownr": "1.1.3",
+                        "fs-minipass": "1.2.7",
+                        "minipass": "2.9.0",
+                        "minizlib": "1.3.3",
+                        "mkdirp": "0.5.1",
+                        "safe-buffer": "5.1.2",
+                        "yallist": "3.1.1"
                     }
                 },
                 "util-deprecate": {
@@ -5836,7 +5836,7 @@
                     "bundled": true,
                     "optional": true,
                     "requires": {
-                        "string-width": "^1.0.2 || 2"
+                        "string-width": "1.0.2"
                     }
                 },
                 "wrappy": {
@@ -5854,10 +5854,10 @@
             "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz",
             "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==",
             "requires": {
-                "graceful-fs": "^4.1.2",
-                "inherits": "~2.0.0",
-                "mkdirp": ">=0.5 0",
-                "rimraf": "2"
+                "graceful-fs": "4.2.0",
+                "inherits": "2.0.3",
+                "mkdirp": "0.5.1",
+                "rimraf": "2.7.1"
             },
             "dependencies": {
                 "rimraf": {
@@ -5865,7 +5865,7 @@
                     "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
                     "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
                     "requires": {
-                        "glob": "^7.1.3"
+                        "glob": "7.1.4"
                     }
                 }
             }
@@ -5880,14 +5880,14 @@
             "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
             "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
             "requires": {
-                "aproba": "^1.0.3",
-                "console-control-strings": "^1.0.0",
-                "has-unicode": "^2.0.0",
-                "object-assign": "^4.1.0",
-                "signal-exit": "^3.0.0",
-                "string-width": "^1.0.1",
-                "strip-ansi": "^3.0.1",
-                "wide-align": "^1.1.0"
+                "aproba": "1.2.0",
+                "console-control-strings": "1.1.0",
+                "has-unicode": "2.0.1",
+                "object-assign": "4.1.1",
+                "signal-exit": "3.0.2",
+                "string-width": "1.0.2",
+                "strip-ansi": "3.0.1",
+                "wide-align": "1.1.3"
             }
         },
         "gaxios": {
@@ -5895,10 +5895,10 @@
             "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-2.0.1.tgz",
             "integrity": "sha512-c1NXovTxkgRJTIgB2FrFmOFg4YIV6N/bAa4f/FZ4jIw13Ql9ya/82x69CswvotJhbV3DiGnlTZwoq2NVXk2Irg==",
             "requires": {
-                "abort-controller": "^3.0.0",
-                "extend": "^3.0.2",
-                "https-proxy-agent": "^2.2.1",
-                "node-fetch": "^2.3.0"
+                "abort-controller": "3.0.0",
+                "extend": "3.0.2",
+                "https-proxy-agent": "2.2.2",
+                "node-fetch": "2.6.0"
             },
             "dependencies": {
                 "node-fetch": {
@@ -5913,7 +5913,7 @@
             "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz",
             "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==",
             "requires": {
-                "globule": "^1.0.0"
+                "globule": "1.3.0"
             }
         },
         "gcp-metadata": {
@@ -5921,8 +5921,8 @@
             "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-2.0.1.tgz",
             "integrity": "sha512-nrbLj5O1MurvpLC/doFwzdTfKnmYGDYXlY/v7eQ4tJNVIvQXbOK672J9UFbradbtmuTkyHzjpzD8HD0Djz0LWw==",
             "requires": {
-                "gaxios": "^2.0.0",
-                "json-bigint": "^0.3.0"
+                "gaxios": "2.0.1",
+                "json-bigint": "0.3.0"
             }
         },
         "get-caller-file": {
@@ -5965,7 +5965,7 @@
             "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
             "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
             "requires": {
-                "assert-plus": "^1.0.0"
+                "assert-plus": "1.0.0"
             },
             "dependencies": {
                 "assert-plus": {
@@ -5985,12 +5985,12 @@
             "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz",
             "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==",
             "requires": {
-                "fs.realpath": "^1.0.0",
-                "inflight": "^1.0.4",
-                "inherits": "2",
-                "minimatch": "^3.0.4",
-                "once": "^1.3.0",
-                "path-is-absolute": "^1.0.0"
+                "fs.realpath": "1.0.0",
+                "inflight": "1.0.6",
+                "inherits": "2.0.3",
+                "minimatch": "3.0.4",
+                "once": "1.4.0",
+                "path-is-absolute": "1.0.1"
             }
         },
         "glob-parent": {
@@ -5998,8 +5998,8 @@
             "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
             "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=",
             "requires": {
-                "is-glob": "^3.1.0",
-                "path-dirname": "^1.0.0"
+                "is-glob": "3.1.0",
+                "path-dirname": "1.0.2"
             },
             "dependencies": {
                 "is-glob": {
@@ -6007,7 +6007,7 @@
                     "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz",
                     "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=",
                     "requires": {
-                        "is-extglob": "^2.1.0"
+                        "is-extglob": "2.1.1"
                     }
                 }
             }
@@ -6017,7 +6017,7 @@
             "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz",
             "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=",
             "requires": {
-                "ini": "^1.3.4"
+                "ini": "1.3.5"
             }
         },
         "global-modules": {
@@ -6026,7 +6026,7 @@
             "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==",
             "dev": true,
             "requires": {
-                "global-prefix": "^3.0.0"
+                "global-prefix": "3.0.0"
             },
             "dependencies": {
                 "global-prefix": {
@@ -6035,9 +6035,9 @@
                     "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==",
                     "dev": true,
                     "requires": {
-                        "ini": "^1.3.5",
-                        "kind-of": "^6.0.2",
-                        "which": "^1.3.1"
+                        "ini": "1.3.5",
+                        "kind-of": "6.0.2",
+                        "which": "1.3.1"
                     }
                 }
             }
@@ -6048,11 +6048,11 @@
             "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=",
             "dev": true,
             "requires": {
-                "expand-tilde": "^2.0.2",
-                "homedir-polyfill": "^1.0.1",
-                "ini": "^1.3.4",
-                "is-windows": "^1.0.1",
-                "which": "^1.2.14"
+                "expand-tilde": "2.0.2",
+                "homedir-polyfill": "1.0.3",
+                "ini": "1.3.5",
+                "is-windows": "1.0.2",
+                "which": "1.3.1"
             }
         },
         "globby": {
@@ -6061,12 +6061,12 @@
             "integrity": "sha1-+yzP+UAfhgCUXfral0QMypcrhoA=",
             "dev": true,
             "requires": {
-                "array-union": "^1.0.1",
-                "dir-glob": "^2.0.0",
-                "glob": "^7.1.2",
-                "ignore": "^3.3.5",
-                "pify": "^3.0.0",
-                "slash": "^1.0.0"
+                "array-union": "1.0.2",
+                "dir-glob": "2.2.2",
+                "glob": "7.1.4",
+                "ignore": "3.3.10",
+                "pify": "3.0.0",
+                "slash": "1.0.0"
             },
             "dependencies": {
                 "pify": {
@@ -6082,9 +6082,9 @@
             "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.0.tgz",
             "integrity": "sha512-YlD4kdMqRCQHrhVdonet4TdRtv1/sZKepvoxNT4Nrhrp5HI8XFfc8kFlGlBn2myBo80aGp8Eft259mbcUJhgSg==",
             "requires": {
-                "glob": "~7.1.1",
-                "lodash": "~4.17.10",
-                "minimatch": "~3.0.2"
+                "glob": "7.1.4",
+                "lodash": "4.17.15",
+                "minimatch": "3.0.4"
             }
         },
         "golden-layout": {
@@ -6092,7 +6092,7 @@
             "resolved": "https://registry.npmjs.org/golden-layout/-/golden-layout-1.5.9.tgz",
             "integrity": "sha1-o5vB9qZ+b4hreXwBbdkk6UJrp38=",
             "requires": {
-                "jquery": "*"
+                "jquery": "3.4.1"
             }
         },
         "google-auth-library": {
@@ -6100,14 +6100,14 @@
             "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-4.2.6.tgz",
             "integrity": "sha512-oJ6tCA9rbsYeIVY+mcLPFHa2hatz3XO6idYIrlI/KhhlMxZrO3tKyU8O2Pxu5KnSBBP7Wj4HtbM1LLKngNFaFw==",
             "requires": {
-                "arrify": "^2.0.0",
-                "base64-js": "^1.3.0",
-                "fast-text-encoding": "^1.0.0",
-                "gaxios": "^2.0.0",
-                "gcp-metadata": "^2.0.0",
-                "gtoken": "^3.0.0",
-                "jws": "^3.1.5",
-                "lru-cache": "^5.0.0"
+                "arrify": "2.0.1",
+                "base64-js": "1.3.0",
+                "fast-text-encoding": "1.0.0",
+                "gaxios": "2.0.1",
+                "gcp-metadata": "2.0.1",
+                "gtoken": "3.0.2",
+                "jws": "3.2.2",
+                "lru-cache": "5.1.1"
             },
             "dependencies": {
                 "arrify": {
@@ -6120,7 +6120,7 @@
                     "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
                     "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
                     "requires": {
-                        "yallist": "^3.0.2"
+                        "yallist": "3.0.3"
                     }
                 }
             }
@@ -6130,7 +6130,7 @@
             "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-2.0.1.tgz",
             "integrity": "sha512-6h6x+eBX3k+IDSe/c8dVYmn8Mzr1mUcmKC9MdUSwaBkFAXlqBEnwFWmSFgGC+tcqtsLn73BDP/vUNWEehf1Rww==",
             "requires": {
-                "node-forge": "^0.8.0"
+                "node-forge": "0.8.5"
             },
             "dependencies": {
                 "node-forge": {
@@ -6145,8 +6145,8 @@
             "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-40.0.1.tgz",
             "integrity": "sha512-B6qZVCautOOspEhru9GZ814I+ztkGWyA4ZEUfaXwXHBruX/HAWqedbsuUEx1w3nCECywK/FLTNUdcbH9zpaMaw==",
             "requires": {
-                "google-auth-library": "^4.0.0",
-                "googleapis-common": "^2.0.2"
+                "google-auth-library": "4.2.6",
+                "googleapis-common": "2.0.4"
             }
         },
         "googleapis-common": {
@@ -6154,12 +6154,12 @@
             "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-2.0.4.tgz",
             "integrity": "sha512-8RRkxr24v1jIKCC1onFWA8RGnwFV55m3Qpil9DLX1yLc9e5qvOJsRoDOhhD2e7jFRONYEhT/BzT8vJZANqSr9w==",
             "requires": {
-                "extend": "^3.0.2",
-                "gaxios": "^2.0.1",
-                "google-auth-library": "^4.2.5",
-                "qs": "^6.7.0",
-                "url-template": "^2.0.8",
-                "uuid": "^3.3.2"
+                "extend": "3.0.2",
+                "gaxios": "2.0.1",
+                "google-auth-library": "4.2.6",
+                "qs": "6.7.0",
+                "url-template": "2.0.8",
+                "uuid": "3.4.0"
             }
         },
         "googlephotos": {
@@ -6167,9 +6167,9 @@
             "resolved": "https://registry.npmjs.org/googlephotos/-/googlephotos-0.2.5.tgz",
             "integrity": "sha512-XPmD3gu8aMVuwauKVzzahD2Vn8Cn8WtBRGgSF5J9A85Fn6N2GM0OToxWbEoTfyKahK+ryGHGcIYzDX98ndxE9g==",
             "requires": {
-                "lodash.chunk": "^4.2.0",
-                "request": "^2.86.0",
-                "request-promise": "^4.2.2"
+                "lodash.chunk": "4.2.0",
+                "request": "2.88.0",
+                "request-promise": "4.2.5"
             }
         },
         "got": {
@@ -6177,17 +6177,17 @@
             "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz",
             "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=",
             "requires": {
-                "create-error-class": "^3.0.0",
-                "duplexer3": "^0.1.4",
-                "get-stream": "^3.0.0",
-                "is-redirect": "^1.0.0",
-                "is-retry-allowed": "^1.0.0",
-                "is-stream": "^1.0.0",
-                "lowercase-keys": "^1.0.0",
-                "safe-buffer": "^5.0.1",
-                "timed-out": "^4.0.0",
-                "unzip-response": "^2.0.1",
-                "url-parse-lax": "^1.0.0"
+                "create-error-class": "3.0.2",
+                "duplexer3": "0.1.4",
+                "get-stream": "3.0.0",
+                "is-redirect": "1.0.0",
+                "is-retry-allowed": "1.2.0",
+                "is-stream": "1.1.0",
+                "lowercase-keys": "1.0.1",
+                "safe-buffer": "5.1.2",
+                "timed-out": "4.0.1",
+                "unzip-response": "2.0.1",
+                "url-parse-lax": "1.0.0"
             }
         },
         "graceful-fs": {
@@ -6211,10 +6211,10 @@
             "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-3.0.2.tgz",
             "integrity": "sha512-BOBi6Zz31JfxhSHRZBIDdbwIbOPyux10WxJHdx8wz/FMP1zyN1xFrsAWsgcLe5ww5v/OZu/MePUEZAjgJXSauA==",
             "requires": {
-                "gaxios": "^2.0.0",
-                "google-p12-pem": "^2.0.0",
-                "jws": "^3.1.5",
-                "mime": "^2.2.0"
+                "gaxios": "2.0.1",
+                "google-p12-pem": "2.0.1",
+                "jws": "3.2.2",
+                "mime": "2.4.4"
             },
             "dependencies": {
                 "mime": {
@@ -6245,8 +6245,8 @@
             "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz",
             "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==",
             "requires": {
-                "ajv": "^6.5.5",
-                "har-schema": "^2.0.0"
+                "ajv": "6.10.2",
+                "har-schema": "2.0.0"
             }
         },
         "has": {
@@ -6254,7 +6254,7 @@
             "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
             "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
             "requires": {
-                "function-bind": "^1.1.1"
+                "function-bind": "1.1.1"
             }
         },
         "has-ansi": {
@@ -6262,7 +6262,7 @@
             "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
             "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=",
             "requires": {
-                "ansi-regex": "^2.0.0"
+                "ansi-regex": "2.1.1"
             }
         },
         "has-binary2": {
@@ -6305,9 +6305,9 @@
             "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz",
             "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=",
             "requires": {
-                "get-value": "^2.0.6",
-                "has-values": "^1.0.0",
-                "isobject": "^3.0.0"
+                "get-value": "2.0.6",
+                "has-values": "1.0.0",
+                "isobject": "3.0.1"
             }
         },
         "has-values": {
@@ -6315,8 +6315,8 @@
             "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz",
             "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=",
             "requires": {
-                "is-number": "^3.0.0",
-                "kind-of": "^4.0.0"
+                "is-number": "3.0.0",
+                "kind-of": "4.0.0"
             },
             "dependencies": {
                 "kind-of": {
@@ -6324,7 +6324,7 @@
                     "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz",
                     "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=",
                     "requires": {
-                        "is-buffer": "^1.1.5"
+                        "is-buffer": "1.1.6"
                     }
                 }
             }
@@ -6334,8 +6334,8 @@
             "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz",
             "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=",
             "requires": {
-                "inherits": "^2.0.1",
-                "safe-buffer": "^5.0.1"
+                "inherits": "2.0.3",
+                "safe-buffer": "5.1.2"
             }
         },
         "hash.js": {
@@ -6343,8 +6343,8 @@
             "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
             "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==",
             "requires": {
-                "inherits": "^2.0.3",
-                "minimalistic-assert": "^1.0.1"
+                "inherits": "2.0.3",
+                "minimalistic-assert": "1.0.1"
             }
         },
         "he": {
@@ -6358,9 +6358,9 @@
             "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
             "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=",
             "requires": {
-                "hash.js": "^1.0.3",
-                "minimalistic-assert": "^1.0.0",
-                "minimalistic-crypto-utils": "^1.0.1"
+                "hash.js": "1.1.7",
+                "minimalistic-assert": "1.0.1",
+                "minimalistic-crypto-utils": "1.0.1"
             }
         },
         "hoist-non-react-statics": {
@@ -6368,7 +6368,7 @@
             "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.0.tgz",
             "integrity": "sha512-0XsbTXxgiaCDYDIWFcwkmerZPSwywfUqYmwT4jzewKTQSWoE6FCMoUVOeBJWK3E/CrWbxRG3m5GzY4lnIwGRBA==",
             "requires": {
-                "react-is": "^16.7.0"
+                "react-is": "16.8.6"
             }
         },
         "homedir-polyfill": {
@@ -6377,7 +6377,7 @@
             "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==",
             "dev": true,
             "requires": {
-                "parse-passwd": "^1.0.0"
+                "parse-passwd": "1.0.0"
             }
         },
         "hosted-git-info": {
@@ -6396,10 +6396,10 @@
             "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=",
             "dev": true,
             "requires": {
-                "inherits": "^2.0.1",
-                "obuf": "^1.0.0",
-                "readable-stream": "^2.0.1",
-                "wbuf": "^1.1.0"
+                "inherits": "2.0.3",
+                "obuf": "1.1.2",
+                "readable-stream": "2.3.6",
+                "wbuf": "1.7.3"
             }
         },
         "html-encoding-sniffer": {
@@ -6408,7 +6408,7 @@
             "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==",
             "dev": true,
             "requires": {
-                "whatwg-encoding": "^1.0.1"
+                "whatwg-encoding": "1.0.5"
             }
         },
         "html-entities": {
@@ -6427,12 +6427,12 @@
             "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz",
             "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==",
             "requires": {
-                "domelementtype": "^1.3.1",
-                "domhandler": "^2.3.0",
-                "domutils": "^1.5.1",
-                "entities": "^1.1.1",
-                "inherits": "^2.0.1",
-                "readable-stream": "^3.1.1"
+                "domelementtype": "1.3.1",
+                "domhandler": "2.4.2",
+                "domutils": "1.5.1",
+                "entities": "1.1.2",
+                "inherits": "2.0.3",
+                "readable-stream": "3.4.0"
             },
             "dependencies": {
                 "readable-stream": {
@@ -6440,9 +6440,9 @@
                     "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz",
                     "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==",
                     "requires": {
-                        "inherits": "^2.0.3",
-                        "string_decoder": "^1.1.1",
-                        "util-deprecate": "^1.0.1"
+                        "inherits": "2.0.3",
+                        "string_decoder": "1.1.1",
+                        "util-deprecate": "1.0.2"
                     }
                 }
             }
@@ -6458,10 +6458,10 @@
             "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
             "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
             "requires": {
-                "depd": "~1.1.2",
+                "depd": "1.1.2",
                 "inherits": "2.0.3",
                 "setprototypeof": "1.1.1",
-                "statuses": ">= 1.5.0 < 2",
+                "statuses": "1.5.0",
                 "toidentifier": "1.0.0"
             }
         },
@@ -6477,9 +6477,9 @@
             "integrity": "sha512-84I2iJM/n1d4Hdgc6y2+qY5mDaz2PUVjlg9znE9byl+q0uC3DeByqBGReQu5tpLK0TAqTIXScRUV+dg7+bUPpQ==",
             "dev": true,
             "requires": {
-                "eventemitter3": "^4.0.0",
-                "follow-redirects": "^1.0.0",
-                "requires-port": "^1.0.0"
+                "eventemitter3": "4.0.0",
+                "follow-redirects": "1.9.0",
+                "requires-port": "1.0.0"
             },
             "dependencies": {
                 "eventemitter3": {
@@ -6496,10 +6496,10 @@
             "integrity": "sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q==",
             "dev": true,
             "requires": {
-                "http-proxy": "^1.17.0",
-                "is-glob": "^4.0.0",
-                "lodash": "^4.17.11",
-                "micromatch": "^3.1.10"
+                "http-proxy": "1.18.0",
+                "is-glob": "4.0.1",
+                "lodash": "4.17.15",
+                "micromatch": "3.1.10"
             }
         },
         "http-signature": {
@@ -6507,9 +6507,9 @@
             "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
             "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
             "requires": {
-                "assert-plus": "^1.0.0",
-                "jsprim": "^1.2.2",
-                "sshpk": "^1.7.0"
+                "assert-plus": "1.0.0",
+                "jsprim": "1.4.1",
+                "sshpk": "1.16.1"
             }
         },
         "https-browserify": {
@@ -6523,8 +6523,8 @@
             "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.2.tgz",
             "integrity": "sha512-c8Ndjc9Bkpfx/vCJueCPy0jlP4ccCCSNDp8xwCZzPjKJUm+B+u9WX2x98Qx4n1PiMNTWo3D7KK5ifNV/yJyRzg==",
             "requires": {
-                "agent-base": "^4.3.0",
-                "debug": "^3.1.0"
+                "agent-base": "4.3.0",
+                "debug": "3.2.6"
             },
             "dependencies": {
                 "debug": {
@@ -6532,7 +6532,7 @@
                     "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
                     "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
                     "requires": {
-                        "ms": "^2.1.1"
+                        "ms": "2.1.2"
                     }
                 },
                 "ms": {
@@ -6552,7 +6552,7 @@
             "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
             "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
             "requires": {
-                "safer-buffer": ">= 2.1.2 < 3"
+                "safer-buffer": "2.1.2"
             }
         },
         "icss-replace-symbols": {
@@ -6567,7 +6567,7 @@
             "integrity": "sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA==",
             "dev": true,
             "requires": {
-                "postcss": "^7.0.14"
+                "postcss": "7.0.17"
             }
         },
         "ieee754": {
@@ -6597,7 +6597,7 @@
             "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz",
             "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==",
             "requires": {
-                "minimatch": "^3.0.4"
+                "minimatch": "3.0.4"
             }
         },
         "image-data-uri": {
@@ -6605,10 +6605,10 @@
             "resolved": "https://registry.npmjs.org/image-data-uri/-/image-data-uri-2.0.1.tgz",
             "integrity": "sha512-BZh721F2Q5TwBdwpiqrBrHEdj8daj8KuMZK/DOCyqQlz1CqFhhuZWbK5ZCUnAvFJr8LaKHTaWl9ja3/a3DC2Ew==",
             "requires": {
-                "fs-extra": "^0.26.7",
+                "fs-extra": "0.26.7",
                 "magicli": "0.0.8",
-                "mime-types": "^2.1.18",
-                "request": "^2.88.0"
+                "mime-types": "2.1.24",
+                "request": "2.88.0"
             }
         },
         "image-size": {
@@ -6621,7 +6621,7 @@
             "resolved": "https://registry.npmjs.org/imagesloaded/-/imagesloaded-4.1.4.tgz",
             "integrity": "sha512-ltiBVcYpc/TYTF5nolkMNsnREHW+ICvfQ3Yla2Sgr71YFwQ86bDwV9hgpFhFtrGPuwEx5+LqOHIrdXBdoWwwsA==",
             "requires": {
-                "ev-emitter": "^1.0.0"
+                "ev-emitter": "1.1.1"
             }
         },
         "immutable": {
@@ -6634,8 +6634,8 @@
             "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz",
             "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==",
             "requires": {
-                "parent-module": "^1.0.0",
-                "resolve-from": "^4.0.0"
+                "parent-module": "1.0.1",
+                "resolve-from": "4.0.0"
             },
             "dependencies": {
                 "resolve-from": {
@@ -6656,8 +6656,8 @@
             "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==",
             "dev": true,
             "requires": {
-                "pkg-dir": "^3.0.0",
-                "resolve-cwd": "^2.0.0"
+                "pkg-dir": "3.0.0",
+                "resolve-cwd": "2.0.0"
             },
             "dependencies": {
                 "find-up": {
@@ -6666,7 +6666,7 @@
                     "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
                     "dev": true,
                     "requires": {
-                        "locate-path": "^3.0.0"
+                        "locate-path": "3.0.0"
                     }
                 },
                 "locate-path": {
@@ -6675,8 +6675,8 @@
                     "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
                     "dev": true,
                     "requires": {
-                        "p-locate": "^3.0.0",
-                        "path-exists": "^3.0.0"
+                        "p-locate": "3.0.0",
+                        "path-exists": "3.0.0"
                     }
                 },
                 "p-locate": {
@@ -6685,7 +6685,7 @@
                     "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
                     "dev": true,
                     "requires": {
-                        "p-limit": "^2.0.0"
+                        "p-limit": "2.2.2"
                     }
                 },
                 "pkg-dir": {
@@ -6694,7 +6694,7 @@
                     "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==",
                     "dev": true,
                     "requires": {
-                        "find-up": "^3.0.0"
+                        "find-up": "3.0.0"
                     }
                 }
             }
@@ -6714,7 +6714,7 @@
             "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz",
             "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=",
             "requires": {
-                "repeating": "^2.0.0"
+                "repeating": "2.0.1"
             }
         },
         "indexes-of": {
@@ -6739,8 +6739,8 @@
             "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
             "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
             "requires": {
-                "once": "^1.3.0",
-                "wrappy": "1"
+                "once": "1.4.0",
+                "wrappy": "1.0.2"
             }
         },
         "infobox-parser": {
@@ -6748,7 +6748,7 @@
             "resolved": "https://registry.npmjs.org/infobox-parser/-/infobox-parser-3.3.1.tgz",
             "integrity": "sha512-Aj1uF/taawGhet8cazhXz2uEDFMOqH8hnuw720wvi7Zw6bJWmA45Ta2FI9xMG5wvvo4CB6GR9S1/RUFtC6EtAg==",
             "requires": {
-                "camelcase": "^4.1.0"
+                "camelcase": "4.1.0"
             }
         },
         "inherits": {
@@ -6793,10 +6793,10 @@
                     "resolved": "https://registry.npmjs.org/magicli/-/magicli-0.0.5.tgz",
                     "integrity": "sha1-zufQ+7THBRiqyxHsPrfiX/SaSSE=",
                     "requires": {
-                        "commander": "^2.9.0",
-                        "get-stdin": "^5.0.1",
-                        "inspect-function": "^0.2.1",
-                        "pipe-functions": "^1.2.0"
+                        "commander": "2.20.0",
+                        "get-stdin": "5.0.1",
+                        "inspect-function": "0.2.2",
+                        "pipe-functions": "1.3.0"
                     }
                 }
             }
@@ -6808,7 +6808,7 @@
             "requires": {
                 "for-each-property": "0.0.4",
                 "for-each-property-deep": "0.0.3",
-                "inspect-function": "^0.3.1"
+                "inspect-function": "0.3.4"
             },
             "dependencies": {
                 "inspect-function": {
@@ -6855,10 +6855,10 @@
                             "resolved": "https://registry.npmjs.org/magicli/-/magicli-0.0.5.tgz",
                             "integrity": "sha1-zufQ+7THBRiqyxHsPrfiX/SaSSE=",
                             "requires": {
-                                "commander": "^2.9.0",
-                                "get-stdin": "^5.0.1",
-                                "inspect-function": "^0.2.1",
-                                "pipe-functions": "^1.2.0"
+                                "commander": "2.20.0",
+                                "get-stdin": "5.0.1",
+                                "inspect-function": "0.2.2",
+                                "pipe-functions": "1.3.0"
                             }
                         },
                         "split-skip": {
@@ -6881,8 +6881,8 @@
             "integrity": "sha512-S1zBo1D6zcsyuC6PMmY5+55YMILQ9av8lotMx447Bq6SAgo/sDK6y6uUKmuYhW7eacnIhFfsPmCNYdDzsnnDCg==",
             "dev": true,
             "requires": {
-                "default-gateway": "^4.2.0",
-                "ipaddr.js": "^1.9.0"
+                "default-gateway": "4.2.0",
+                "ipaddr.js": "1.9.0"
             }
         },
         "interpret": {
@@ -6895,7 +6895,7 @@
             "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
             "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
             "requires": {
-                "loose-envify": "^1.0.0"
+                "loose-envify": "1.4.0"
             }
         },
         "invert-kv": {
@@ -6931,7 +6931,7 @@
             "resolved": "http://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
             "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=",
             "requires": {
-                "kind-of": "^3.0.2"
+                "kind-of": "3.2.2"
             },
             "dependencies": {
                 "kind-of": {
@@ -6939,7 +6939,7 @@
                     "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
                     "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
                     "requires": {
-                        "is-buffer": "^1.1.5"
+                        "is-buffer": "1.1.6"
                     }
                 }
             }
@@ -6960,7 +6960,7 @@
             "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz",
             "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=",
             "requires": {
-                "binary-extensions": "^1.0.0"
+                "binary-extensions": "1.13.1"
             }
         },
         "is-buffer": {
@@ -6978,7 +6978,7 @@
             "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz",
             "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==",
             "requires": {
-                "ci-info": "^1.5.0"
+                "ci-info": "1.6.0"
             }
         },
         "is-data-descriptor": {
@@ -6986,7 +6986,7 @@
             "resolved": "http://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
             "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=",
             "requires": {
-                "kind-of": "^3.0.2"
+                "kind-of": "3.2.2"
             },
             "dependencies": {
                 "kind-of": {
@@ -6994,7 +6994,7 @@
                     "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
                     "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
                     "requires": {
-                        "is-buffer": "^1.1.5"
+                        "is-buffer": "1.1.6"
                     }
                 }
             }
@@ -7009,9 +7009,9 @@
             "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz",
             "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==",
             "requires": {
-                "is-accessor-descriptor": "^0.1.6",
-                "is-data-descriptor": "^0.1.4",
-                "kind-of": "^5.0.0"
+                "is-accessor-descriptor": "0.1.6",
+                "is-data-descriptor": "0.1.4",
+                "kind-of": "5.1.0"
             },
             "dependencies": {
                 "kind-of": {
@@ -7026,8 +7026,8 @@
             "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-3.0.0.tgz",
             "integrity": "sha1-Oayqa+f9HzRx3ELHQW5hwkMXrJ8=",
             "requires": {
-                "acorn": "~4.0.2",
-                "object-assign": "^4.0.1"
+                "acorn": "4.0.13",
+                "object-assign": "4.1.1"
             },
             "dependencies": {
                 "acorn": {
@@ -7052,7 +7052,7 @@
             "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz",
             "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=",
             "requires": {
-                "number-is-nan": "^1.0.0"
+                "number-is-nan": "1.0.1"
             }
         },
         "is-fullwidth-code-point": {
@@ -7060,7 +7060,7 @@
             "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
             "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
             "requires": {
-                "number-is-nan": "^1.0.0"
+                "number-is-nan": "1.0.1"
             }
         },
         "is-glob": {
@@ -7068,7 +7068,7 @@
             "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
             "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
             "requires": {
-                "is-extglob": "^2.1.1"
+                "is-extglob": "2.1.1"
             }
         },
         "is-installed-globally": {
@@ -7076,8 +7076,8 @@
             "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.1.0.tgz",
             "integrity": "sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=",
             "requires": {
-                "global-dirs": "^0.1.0",
-                "is-path-inside": "^1.0.0"
+                "global-dirs": "0.1.1",
+                "is-path-inside": "1.0.1"
             }
         },
         "is-npm": {
@@ -7090,7 +7090,7 @@
             "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
             "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
             "requires": {
-                "kind-of": "^3.0.2"
+                "kind-of": "3.2.2"
             },
             "dependencies": {
                 "kind-of": {
@@ -7098,7 +7098,7 @@
                     "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
                     "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
                     "requires": {
-                        "is-buffer": "^1.1.5"
+                        "is-buffer": "1.1.6"
                     }
                 }
             }
@@ -7120,7 +7120,7 @@
             "integrity": "sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==",
             "dev": true,
             "requires": {
-                "is-path-inside": "^2.1.0"
+                "is-path-inside": "2.1.0"
             },
             "dependencies": {
                 "is-path-inside": {
@@ -7129,7 +7129,7 @@
                     "integrity": "sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==",
                     "dev": true,
                     "requires": {
-                        "path-is-inside": "^1.0.2"
+                        "path-is-inside": "1.0.2"
                     }
                 }
             }
@@ -7139,7 +7139,7 @@
             "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz",
             "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=",
             "requires": {
-                "path-is-inside": "^1.0.1"
+                "path-is-inside": "1.0.2"
             }
         },
         "is-plain-object": {
@@ -7147,7 +7147,7 @@
             "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
             "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
             "requires": {
-                "isobject": "^3.0.1"
+                "isobject": "3.0.1"
             }
         },
         "is-promise": {
@@ -7165,7 +7165,7 @@
             "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz",
             "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=",
             "requires": {
-                "has": "^1.0.1"
+                "has": "1.0.3"
             }
         },
         "is-retry-allowed": {
@@ -7183,7 +7183,7 @@
             "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz",
             "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==",
             "requires": {
-                "has-symbols": "^1.0.0"
+                "has-symbols": "1.0.0"
             }
         },
         "is-typedarray": {
@@ -7227,8 +7227,8 @@
             "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz",
             "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=",
             "requires": {
-                "node-fetch": "^1.0.1",
-                "whatwg-fetch": ">=0.10.0"
+                "node-fetch": "1.7.3",
+                "whatwg-fetch": "3.0.0"
             }
         },
         "isstream": {
@@ -7241,8 +7241,8 @@
             "resolved": "https://registry.npmjs.org/its-set/-/its-set-1.2.3.tgz",
             "integrity": "sha512-UQc+xLLn+0a8KKRXRj3OS2kERK8G7zcayPpPULqZnPwuJ1hGWEO8+j0T5eycu7DKXYjezw3pyF8oV1fJkJxV5w==",
             "requires": {
-                "babel-runtime": "6.x.x",
-                "lodash.get": "^4.4.2"
+                "babel-runtime": "6.26.0",
+                "lodash.get": "4.4.2"
             }
         },
         "jquery": {
@@ -7255,7 +7255,7 @@
             "resolved": "https://registry.npmjs.org/jquery-awesome-cursor/-/jquery-awesome-cursor-0.3.1.tgz",
             "integrity": "sha1-1pcaMrRiRhC868rAkDsAFWHQXso=",
             "requires": {
-                "font-awesome": "4.x"
+                "font-awesome": "4.7.0"
             }
         },
         "js-base64": {
@@ -7283,8 +7283,8 @@
             "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
             "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
             "requires": {
-                "argparse": "^1.0.7",
-                "esprima": "^4.0.0"
+                "argparse": "1.0.10",
+                "esprima": "4.0.1"
             }
         },
         "jsbn": {
@@ -7298,32 +7298,32 @@
             "integrity": "sha512-fAl1W0/7T2G5vURSyxBzrJ1LSdQn6Tr5UX/xD4PXDx/PDgwygedfW6El/KIj3xJ7FU61TTYnc/l/B7P49Eqt6g==",
             "dev": true,
             "requires": {
-                "abab": "^2.0.0",
-                "acorn": "^7.1.0",
-                "acorn-globals": "^4.3.2",
-                "array-equal": "^1.0.0",
-                "cssom": "^0.4.1",
-                "cssstyle": "^2.0.0",
-                "data-urls": "^1.1.0",
-                "domexception": "^1.0.1",
-                "escodegen": "^1.11.1",
-                "html-encoding-sniffer": "^1.0.2",
-                "nwsapi": "^2.2.0",
+                "abab": "2.0.3",
+                "acorn": "7.1.0",
+                "acorn-globals": "4.3.4",
+                "array-equal": "1.0.0",
+                "cssom": "0.4.4",
+                "cssstyle": "2.0.0",
+                "data-urls": "1.1.0",
+                "domexception": "1.0.1",
+                "escodegen": "1.12.1",
+                "html-encoding-sniffer": "1.0.2",
+                "nwsapi": "2.2.0",
                 "parse5": "5.1.0",
-                "pn": "^1.1.0",
-                "request": "^2.88.0",
-                "request-promise-native": "^1.0.7",
-                "saxes": "^3.1.9",
-                "symbol-tree": "^3.2.2",
-                "tough-cookie": "^3.0.1",
-                "w3c-hr-time": "^1.0.1",
-                "w3c-xmlserializer": "^1.1.2",
-                "webidl-conversions": "^4.0.2",
-                "whatwg-encoding": "^1.0.5",
-                "whatwg-mimetype": "^2.3.0",
-                "whatwg-url": "^7.0.0",
-                "ws": "^7.0.0",
-                "xml-name-validator": "^3.0.0"
+                "pn": "1.1.0",
+                "request": "2.88.0",
+                "request-promise-native": "1.0.8",
+                "saxes": "3.1.11",
+                "symbol-tree": "3.2.4",
+                "tough-cookie": "3.0.1",
+                "w3c-hr-time": "1.0.1",
+                "w3c-xmlserializer": "1.1.2",
+                "webidl-conversions": "4.0.2",
+                "whatwg-encoding": "1.0.5",
+                "whatwg-mimetype": "2.3.0",
+                "whatwg-url": "7.1.0",
+                "ws": "7.2.1",
+                "xml-name-validator": "3.0.0"
             },
             "dependencies": {
                 "acorn": {
@@ -7338,8 +7338,8 @@
                     "integrity": "sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A==",
                     "dev": true,
                     "requires": {
-                        "acorn": "^6.0.1",
-                        "acorn-walk": "^6.0.1"
+                        "acorn": "6.4.0",
+                        "acorn-walk": "6.2.0"
                     },
                     "dependencies": {
                         "acorn": {
@@ -7356,9 +7356,9 @@
                     "integrity": "sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==",
                     "dev": true,
                     "requires": {
-                        "ip-regex": "^2.1.0",
-                        "psl": "^1.1.28",
-                        "punycode": "^2.1.1"
+                        "ip-regex": "2.1.0",
+                        "psl": "1.2.0",
+                        "punycode": "2.1.1"
                     }
                 }
             }
@@ -7368,7 +7368,7 @@
             "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-0.3.0.tgz",
             "integrity": "sha1-DM2RLEuCcNBfBW+9E4FLU9OCWx4=",
             "requires": {
-                "bignumber.js": "^7.0.0"
+                "bignumber.js": "7.2.1"
             }
         },
         "json-parse-better-errors": {
@@ -7407,7 +7407,7 @@
             "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
             "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
             "requires": {
-                "minimist": "^1.2.0"
+                "minimist": "1.2.0"
             },
             "dependencies": {
                 "minimist": {
@@ -7422,7 +7422,7 @@
             "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz",
             "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=",
             "requires": {
-                "graceful-fs": "^4.1.6"
+                "graceful-fs": "4.2.0"
             }
         },
         "jsonschema": {
@@ -7435,16 +7435,16 @@
             "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz",
             "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==",
             "requires": {
-                "jws": "^3.2.2",
-                "lodash.includes": "^4.3.0",
-                "lodash.isboolean": "^3.0.3",
-                "lodash.isinteger": "^4.0.4",
-                "lodash.isnumber": "^3.0.3",
-                "lodash.isplainobject": "^4.0.6",
-                "lodash.isstring": "^4.0.1",
-                "lodash.once": "^4.0.0",
-                "ms": "^2.1.1",
-                "semver": "^5.6.0"
+                "jws": "3.2.2",
+                "lodash.includes": "4.3.0",
+                "lodash.isboolean": "3.0.3",
+                "lodash.isinteger": "4.0.4",
+                "lodash.isnumber": "3.0.3",
+                "lodash.isplainobject": "4.0.6",
+                "lodash.isstring": "4.0.1",
+                "lodash.once": "4.1.1",
+                "ms": "2.1.2",
+                "semver": "5.7.0"
             },
             "dependencies": {
                 "ms": {
@@ -7477,11 +7477,11 @@
             "resolved": "https://registry.npmjs.org/jstransform/-/jstransform-11.0.3.tgz",
             "integrity": "sha1-CaeJk+CuTU70SH9hVakfYZDLQiM=",
             "requires": {
-                "base62": "^1.1.0",
-                "commoner": "^0.10.1",
-                "esprima-fb": "^15001.1.0-dev-harmony-fb",
-                "object-assign": "^2.0.0",
-                "source-map": "^0.4.2"
+                "base62": "1.2.8",
+                "commoner": "0.10.8",
+                "esprima-fb": "15001.1.0-dev-harmony-fb",
+                "object-assign": "2.1.1",
+                "source-map": "0.4.4"
             },
             "dependencies": {
                 "esprima-fb": {
@@ -7499,7 +7499,7 @@
                     "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz",
                     "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=",
                     "requires": {
-                        "amdefine": ">=0.0.4"
+                        "amdefine": "1.0.1"
                     }
                 }
             }
@@ -7509,8 +7509,8 @@
             "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-1.0.0.tgz",
             "integrity": "sha1-7Yvwkh4vPx7U1cGkT2hwntJHIsM=",
             "requires": {
-                "is-promise": "^2.0.0",
-                "promise": "^7.0.1"
+                "is-promise": "2.1.0",
+                "promise": "7.3.1"
             }
         },
         "jsx-to-string": {
@@ -7518,9 +7518,9 @@
             "resolved": "https://registry.npmjs.org/jsx-to-string/-/jsx-to-string-1.4.0.tgz",
             "integrity": "sha1-Ztw013PaufQP6ZPP+ZQOXaZVtwU=",
             "requires": {
-                "immutable": "^4.0.0-rc.9",
-                "json-stringify-pretty-compact": "^1.0.1",
-                "react": "^0.14.0"
+                "immutable": "4.0.0-rc.12",
+                "json-stringify-pretty-compact": "1.2.0",
+                "react": "0.14.9"
             },
             "dependencies": {
                 "fbjs": {
@@ -7528,11 +7528,11 @@
                     "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.6.1.tgz",
                     "integrity": "sha1-lja3cF9bqWhNRLcveDISVK/IYPc=",
                     "requires": {
-                        "core-js": "^1.0.0",
-                        "loose-envify": "^1.0.0",
-                        "promise": "^7.0.3",
-                        "ua-parser-js": "^0.7.9",
-                        "whatwg-fetch": "^0.9.0"
+                        "core-js": "1.2.7",
+                        "loose-envify": "1.4.0",
+                        "promise": "7.3.1",
+                        "ua-parser-js": "0.7.20",
+                        "whatwg-fetch": "0.9.0"
                     }
                 },
                 "react": {
@@ -7540,8 +7540,8 @@
                     "resolved": "https://registry.npmjs.org/react/-/react-0.14.9.tgz",
                     "integrity": "sha1-kRCmSXxJ1EuhwO3TF67CnC4NkdE=",
                     "requires": {
-                        "envify": "^3.0.0",
-                        "fbjs": "^0.6.1"
+                        "envify": "3.4.1",
+                        "fbjs": "0.6.1"
                     }
                 },
                 "whatwg-fetch": {
@@ -7558,7 +7558,7 @@
             "requires": {
                 "buffer-equal-constant-time": "1.0.1",
                 "ecdsa-sig-formatter": "1.0.11",
-                "safe-buffer": "^5.0.1"
+                "safe-buffer": "5.1.2"
             }
         },
         "jws": {
@@ -7566,8 +7566,8 @@
             "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
             "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
             "requires": {
-                "jwa": "^1.4.1",
-                "safe-buffer": "^5.0.1"
+                "jwa": "1.4.1",
+                "safe-buffer": "5.1.2"
             }
         },
         "kareem": {
@@ -7604,7 +7604,7 @@
             "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz",
             "integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=",
             "requires": {
-                "graceful-fs": "^4.1.9"
+                "graceful-fs": "4.2.0"
             }
         },
         "latest-version": {
@@ -7612,7 +7612,7 @@
             "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz",
             "integrity": "sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU=",
             "requires": {
-                "package-json": "^4.0.0"
+                "package-json": "4.0.1"
             }
         },
         "lazy-cache": {
@@ -7625,7 +7625,7 @@
             "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz",
             "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=",
             "requires": {
-                "readable-stream": "^2.0.5"
+                "readable-stream": "2.3.6"
             }
         },
         "lcid": {
@@ -7633,7 +7633,7 @@
             "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz",
             "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=",
             "requires": {
-                "invert-kv": "^1.0.0"
+                "invert-kv": "1.0.0"
             }
         },
         "levn": {
@@ -7642,8 +7642,8 @@
             "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=",
             "dev": true,
             "requires": {
-                "prelude-ls": "~1.1.2",
-                "type-check": "~0.3.2"
+                "prelude-ls": "1.1.2",
+                "type-check": "0.3.2"
             }
         },
         "lines-and-columns": {
@@ -7656,11 +7656,11 @@
             "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
             "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
             "requires": {
-                "graceful-fs": "^4.1.2",
-                "parse-json": "^2.2.0",
-                "pify": "^2.0.0",
-                "pinkie-promise": "^2.0.0",
-                "strip-bom": "^2.0.0"
+                "graceful-fs": "4.2.0",
+                "parse-json": "2.2.0",
+                "pify": "2.3.0",
+                "pinkie-promise": "2.0.1",
+                "strip-bom": "2.0.0"
             },
             "dependencies": {
                 "parse-json": {
@@ -7668,7 +7668,7 @@
                     "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
                     "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=",
                     "requires": {
-                        "error-ex": "^1.2.0"
+                        "error-ex": "1.3.2"
                     }
                 }
             }
@@ -7684,9 +7684,9 @@
             "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz",
             "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==",
             "requires": {
-                "big.js": "^5.2.2",
-                "emojis-list": "^2.0.0",
-                "json5": "^1.0.1"
+                "big.js": "5.2.2",
+                "emojis-list": "2.1.0",
+                "json5": "1.0.1"
             }
         },
         "locate-path": {
@@ -7694,8 +7694,8 @@
             "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
             "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=",
             "requires": {
-                "p-locate": "^2.0.0",
-                "path-exists": "^3.0.0"
+                "p-locate": "2.0.0",
+                "path-exists": "3.0.0"
             }
         },
         "lodash": {
@@ -7794,7 +7794,7 @@
             "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz",
             "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==",
             "requires": {
-                "chalk": "^2.0.1"
+                "chalk": "2.4.2"
             },
             "dependencies": {
                 "ansi-styles": {
@@ -7802,7 +7802,7 @@
                     "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
                     "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
                     "requires": {
-                        "color-convert": "^1.9.0"
+                        "color-convert": "1.9.3"
                     }
                 },
                 "chalk": {
@@ -7810,9 +7810,9 @@
                     "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
                     "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
                     "requires": {
-                        "ansi-styles": "^3.2.1",
-                        "escape-string-regexp": "^1.0.5",
-                        "supports-color": "^5.3.0"
+                        "ansi-styles": "3.2.1",
+                        "escape-string-regexp": "1.0.5",
+                        "supports-color": "5.5.0"
                     }
                 },
                 "supports-color": {
@@ -7820,7 +7820,7 @@
                     "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
                     "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
                     "requires": {
-                        "has-flag": "^3.0.0"
+                        "has-flag": "3.0.0"
                     }
                 }
             }
@@ -7837,8 +7837,8 @@
             "integrity": "sha512-V/73qkPuJmx4BcBF19xPBr+0ZRVBhc4POxvZTZdMeXpJ4NItXSJ/MSwuFT0kQJlCbXvdlZoQQ/418bS1y9Jh6A==",
             "dev": true,
             "requires": {
-                "es6-symbol": "^3.1.1",
-                "object.assign": "^4.1.0"
+                "es6-symbol": "3.1.1",
+                "object.assign": "4.1.0"
             }
         },
         "longest": {
@@ -7851,7 +7851,7 @@
             "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
             "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
             "requires": {
-                "js-tokens": "^3.0.0 || ^4.0.0"
+                "js-tokens": "4.0.0"
             }
         },
         "loud-rejection": {
@@ -7859,8 +7859,8 @@
             "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz",
             "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=",
             "requires": {
-                "currently-unhandled": "^0.4.1",
-                "signal-exit": "^3.0.0"
+                "currently-unhandled": "0.4.1",
+                "signal-exit": "3.0.2"
             }
         },
         "lowercase-keys": {
@@ -7873,8 +7873,8 @@
             "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz",
             "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==",
             "requires": {
-                "pseudomap": "^1.0.2",
-                "yallist": "^2.1.2"
+                "pseudomap": "1.0.2",
+                "yallist": "2.1.2"
             },
             "dependencies": {
                 "yallist": {
@@ -7890,7 +7890,7 @@
             "integrity": "sha512-x/eBenweAHF+DsYy172sK4doRxZl0yrJnfxhLJiN7H6hPM3Ya0PfI6uBZshZ3ScFFSQD7HXgBqMdbnXKEZsO1g==",
             "requires": {
                 "cliss": "0.0.2",
-                "find-up": "^2.1.0",
+                "find-up": "2.1.0",
                 "for-each-property": "0.0.4",
                 "inspect-property": "0.0.6"
             }
@@ -7900,7 +7900,7 @@
             "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz",
             "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==",
             "requires": {
-                "pify": "^3.0.0"
+                "pify": "3.0.0"
             },
             "dependencies": {
                 "pify": {
@@ -7928,7 +7928,7 @@
             "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==",
             "dev": true,
             "requires": {
-                "p-defer": "^1.0.0"
+                "p-defer": "1.0.0"
             }
         },
         "map-cache": {
@@ -7951,7 +7951,7 @@
             "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz",
             "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=",
             "requires": {
-                "object-visit": "^1.0.0"
+                "object-visit": "1.0.1"
             }
         },
         "material-colors": {
@@ -7964,9 +7964,9 @@
             "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
             "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==",
             "requires": {
-                "hash-base": "^3.0.0",
-                "inherits": "^2.0.1",
-                "safe-buffer": "^5.1.2"
+                "hash-base": "3.0.4",
+                "inherits": "2.0.3",
+                "safe-buffer": "5.1.2"
             }
         },
         "media-typer": {
@@ -7980,9 +7980,9 @@
             "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==",
             "dev": true,
             "requires": {
-                "map-age-cleaner": "^0.1.1",
-                "mimic-fn": "^2.0.0",
-                "p-is-promise": "^2.0.0"
+                "map-age-cleaner": "0.1.3",
+                "mimic-fn": "2.1.0",
+                "p-is-promise": "2.1.0"
             }
         },
         "memory-fs": {
@@ -7991,8 +7991,8 @@
             "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=",
             "dev": true,
             "requires": {
-                "errno": "^0.1.3",
-                "readable-stream": "^2.0.1"
+                "errno": "0.1.7",
+                "readable-stream": "2.3.6"
             }
         },
         "memory-pager": {
@@ -8006,16 +8006,16 @@
             "resolved": "http://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
             "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=",
             "requires": {
-                "camelcase-keys": "^2.0.0",
-                "decamelize": "^1.1.2",
-                "loud-rejection": "^1.0.0",
-                "map-obj": "^1.0.1",
-                "minimist": "^1.1.3",
-                "normalize-package-data": "^2.3.4",
-                "object-assign": "^4.0.1",
-                "read-pkg-up": "^1.0.1",
-                "redent": "^1.0.0",
-                "trim-newlines": "^1.0.0"
+                "camelcase-keys": "2.1.0",
+                "decamelize": "1.2.0",
+                "loud-rejection": "1.6.0",
+                "map-obj": "1.0.1",
+                "minimist": "1.2.0",
+                "normalize-package-data": "2.5.0",
+                "object-assign": "4.1.1",
+                "read-pkg-up": "1.0.1",
+                "redent": "1.0.0",
+                "trim-newlines": "1.0.0"
             },
             "dependencies": {
                 "minimist": {
@@ -8046,19 +8046,19 @@
             "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
             "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==",
             "requires": {
-                "arr-diff": "^4.0.0",
-                "array-unique": "^0.3.2",
-                "braces": "^2.3.1",
-                "define-property": "^2.0.2",
-                "extend-shallow": "^3.0.2",
-                "extglob": "^2.0.4",
-                "fragment-cache": "^0.2.1",
-                "kind-of": "^6.0.2",
-                "nanomatch": "^1.2.9",
-                "object.pick": "^1.3.0",
-                "regex-not": "^1.0.0",
-                "snapdragon": "^0.8.1",
-                "to-regex": "^3.0.2"
+                "arr-diff": "4.0.0",
+                "array-unique": "0.3.2",
+                "braces": "2.3.2",
+                "define-property": "2.0.2",
+                "extend-shallow": "3.0.2",
+                "extglob": "2.0.4",
+                "fragment-cache": "0.2.1",
+                "kind-of": "6.0.2",
+                "nanomatch": "1.2.13",
+                "object.pick": "1.3.0",
+                "regex-not": "1.0.2",
+                "snapdragon": "0.8.2",
+                "to-regex": "3.0.2"
             }
         },
         "miller-rabin": {
@@ -8066,8 +8066,8 @@
             "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz",
             "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==",
             "requires": {
-                "bn.js": "^4.0.0",
-                "brorand": "^1.0.1"
+                "bn.js": "4.11.8",
+                "brorand": "1.1.0"
             }
         },
         "mime": {
@@ -8114,7 +8114,7 @@
             "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
             "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
             "requires": {
-                "brace-expansion": "^1.1.7"
+                "brace-expansion": "1.1.11"
             }
         },
         "minimist": {
@@ -8127,8 +8127,8 @@
             "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz",
             "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==",
             "requires": {
-                "safe-buffer": "^5.1.2",
-                "yallist": "^3.0.0"
+                "safe-buffer": "5.1.2",
+                "yallist": "3.0.3"
             }
         },
         "minizlib": {
@@ -8136,7 +8136,7 @@
             "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.2.1.tgz",
             "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==",
             "requires": {
-                "minipass": "^2.2.1"
+                "minipass": "2.3.5"
             }
         },
         "mississippi": {
@@ -8145,16 +8145,16 @@
             "integrity": "sha512-zHo8v+otD1J10j/tC+VNoGK9keCuByhKovAvdn74dmxJl9+mWHnx6EMsDN4lgRoMI/eYo2nchAxniIbUPb5onw==",
             "dev": true,
             "requires": {
-                "concat-stream": "^1.5.0",
-                "duplexify": "^3.4.2",
-                "end-of-stream": "^1.1.0",
-                "flush-write-stream": "^1.0.0",
-                "from2": "^2.1.0",
-                "parallel-transform": "^1.1.0",
-                "pump": "^2.0.1",
-                "pumpify": "^1.3.3",
-                "stream-each": "^1.1.0",
-                "through2": "^2.0.0"
+                "concat-stream": "1.6.2",
+                "duplexify": "3.7.1",
+                "end-of-stream": "1.4.1",
+                "flush-write-stream": "1.1.1",
+                "from2": "2.3.0",
+                "parallel-transform": "1.1.0",
+                "pump": "2.0.1",
+                "pumpify": "1.5.1",
+                "stream-each": "1.2.3",
+                "through2": "2.0.5"
             }
         },
         "mixin-deep": {
@@ -8162,8 +8162,8 @@
             "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz",
             "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==",
             "requires": {
-                "for-in": "^1.0.2",
-                "is-extendable": "^1.0.1"
+                "for-in": "1.0.2",
+                "is-extendable": "1.0.1"
             },
             "dependencies": {
                 "is-extendable": {
@@ -8171,7 +8171,7 @@
                     "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
                     "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
                     "requires": {
-                        "is-plain-object": "^2.0.4"
+                        "is-plain-object": "2.0.4"
                     }
                 }
             }
@@ -8199,8 +8199,8 @@
             "resolved": "https://registry.npmjs.org/mobx-react/-/mobx-react-5.4.4.tgz",
             "integrity": "sha512-2mTzpyEjVB/RGk2i6KbcmP4HWcAUFox5ZRCrGvSyz49w20I4C4qql63grPpYrS9E9GKwgydBHQlA4y665LuRCQ==",
             "requires": {
-                "hoist-non-react-statics": "^3.0.0",
-                "react-lifecycles-compat": "^3.0.2"
+                "hoist-non-react-statics": "3.3.0",
+                "react-lifecycles-compat": "3.0.4"
             }
         },
         "mobx-react-devtools": {
@@ -8259,12 +8259,12 @@
                     "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
                     "dev": true,
                     "requires": {
-                        "fs.realpath": "^1.0.0",
-                        "inflight": "^1.0.4",
-                        "inherits": "2",
-                        "minimatch": "^3.0.4",
-                        "once": "^1.3.0",
-                        "path-is-absolute": "^1.0.0"
+                        "fs.realpath": "1.0.0",
+                        "inflight": "1.0.6",
+                        "inherits": "2.0.3",
+                        "minimatch": "3.0.4",
+                        "once": "1.4.0",
+                        "path-is-absolute": "1.0.1"
                     }
                 },
                 "supports-color": {
@@ -8273,7 +8273,7 @@
                     "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==",
                     "dev": true,
                     "requires": {
-                        "has-flag": "^3.0.0"
+                        "has-flag": "3.0.0"
                     }
                 }
             }
@@ -8283,12 +8283,12 @@
             "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.5.2.tgz",
             "integrity": "sha512-Lxt4th2tK2MxmkDBR5cMik+xEnkvhwg0BC5kGcHm9RBwaNEsrIryvV5istGXOHbnif5KslMpY1FbX6YbGJ/Trg==",
             "requires": {
-                "bl": "^2.2.0",
-                "bson": "^1.1.1",
-                "denque": "^1.4.1",
-                "require_optional": "^1.0.1",
-                "safe-buffer": "^5.1.2",
-                "saslprep": "^1.0.0"
+                "bl": "2.2.0",
+                "bson": "1.1.3",
+                "denque": "1.4.1",
+                "require_optional": "1.0.1",
+                "safe-buffer": "5.1.2",
+                "saslprep": "1.0.3"
             },
             "dependencies": {
                 "bl": {
@@ -8296,8 +8296,8 @@
                     "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.0.tgz",
                     "integrity": "sha512-wbgvOpqopSr7uq6fJrLH8EsvYMJf9gzfo2jCsL2eTy75qXPukA4pCgHamOQkZtY5vmfVtjB+P3LNlMHW5CEZXA==",
                     "requires": {
-                        "readable-stream": "^2.3.5",
-                        "safe-buffer": "^5.1.1"
+                        "readable-stream": "2.3.6",
+                        "safe-buffer": "5.1.2"
                     }
                 },
                 "bson": {
@@ -8312,8 +8312,8 @@
             "resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-2.1.20.tgz",
             "integrity": "sha512-IN57CX5/Q1bhDq6ShAR6gIv4koFsZP7L8WOK1S0lR0pVDQaScffSMV5jxubLsmZ7J+UdqmykKw4r9hG3XQEGgQ==",
             "requires": {
-                "bson": "~1.0.4",
-                "require_optional": "~1.0.0"
+                "bson": "1.0.9",
+                "require_optional": "1.0.1"
             }
         },
         "mongoose": {
@@ -8321,7 +8321,7 @@
             "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.8.9.tgz",
             "integrity": "sha512-gRazoLTQ0yuv4bk2z+nZEarKCyJ7WilFBkgrRqpOczUZUhk3i/FCe0rp8Mjc87dGXaHx54j8AjPJ0UKqJDXWMA==",
             "requires": {
-                "bson": "~1.1.1",
+                "bson": "1.1.3",
                 "kareem": "2.3.1",
                 "mongodb": "3.4.1",
                 "mongoose-legacy-pluralize": "1.0.2",
@@ -8344,10 +8344,10 @@
                     "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.4.1.tgz",
                     "integrity": "sha512-juqt5/Z42J4DcE7tG7UdVaTKmUC6zinF4yioPfpeOSNBieWSK6qCY+0tfGQcHLKrauWPDdMZVROHJOa8q2pWsA==",
                     "requires": {
-                        "bson": "^1.1.1",
-                        "require_optional": "^1.0.1",
-                        "safe-buffer": "^5.1.2",
-                        "saslprep": "^1.0.0"
+                        "bson": "1.1.3",
+                        "require_optional": "1.0.1",
+                        "safe-buffer": "5.1.2",
+                        "saslprep": "1.0.3"
                     }
                 },
                 "ms": {
@@ -8368,12 +8368,12 @@
             "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=",
             "dev": true,
             "requires": {
-                "aproba": "^1.1.1",
-                "copy-concurrently": "^1.0.0",
-                "fs-write-stream-atomic": "^1.0.8",
-                "mkdirp": "^0.5.1",
-                "rimraf": "^2.5.4",
-                "run-queue": "^1.0.3"
+                "aproba": "1.2.0",
+                "copy-concurrently": "1.0.5",
+                "fs-write-stream-atomic": "1.0.10",
+                "mkdirp": "0.5.1",
+                "rimraf": "2.7.1",
+                "run-queue": "1.0.3"
             },
             "dependencies": {
                 "rimraf": {
@@ -8382,7 +8382,7 @@
                     "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
                     "dev": true,
                     "requires": {
-                        "glob": "^7.1.3"
+                        "glob": "7.1.4"
                     }
                 }
             }
@@ -8399,7 +8399,7 @@
             "requires": {
                 "bluebird": "3.5.1",
                 "debug": "3.1.0",
-                "regexp-clone": "^1.0.0",
+                "regexp-clone": "1.0.0",
                 "safe-buffer": "5.1.2",
                 "sliced": "1.0.1"
             },
@@ -8430,8 +8430,8 @@
             "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==",
             "dev": true,
             "requires": {
-                "dns-packet": "^1.3.1",
-                "thunky": "^1.0.2"
+                "dns-packet": "1.3.1",
+                "thunky": "1.1.0"
             }
         },
         "multicast-dns-service-types": {
@@ -8450,17 +8450,17 @@
             "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz",
             "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==",
             "requires": {
-                "arr-diff": "^4.0.0",
-                "array-unique": "^0.3.2",
-                "define-property": "^2.0.2",
-                "extend-shallow": "^3.0.2",
-                "fragment-cache": "^0.2.1",
-                "is-windows": "^1.0.2",
-                "kind-of": "^6.0.2",
-                "object.pick": "^1.3.0",
-                "regex-not": "^1.0.0",
-                "snapdragon": "^0.8.1",
-                "to-regex": "^3.0.1"
+                "arr-diff": "4.0.0",
+                "array-unique": "0.3.2",
+                "define-property": "2.0.2",
+                "extend-shallow": "3.0.2",
+                "fragment-cache": "0.2.1",
+                "is-windows": "1.0.2",
+                "kind-of": "6.0.2",
+                "object.pick": "1.3.0",
+                "regex-not": "1.0.2",
+                "snapdragon": "0.8.2",
+                "to-regex": "3.0.2"
             }
         },
         "napi-build-utils": {
@@ -8478,9 +8478,9 @@
             "resolved": "https://registry.npmjs.org/needle/-/needle-2.4.0.tgz",
             "integrity": "sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg==",
             "requires": {
-                "debug": "^3.2.6",
-                "iconv-lite": "^0.4.4",
-                "sax": "^1.2.4"
+                "debug": "3.2.6",
+                "iconv-lite": "0.4.24",
+                "sax": "1.2.4"
             },
             "dependencies": {
                 "debug": {
@@ -8488,7 +8488,7 @@
                     "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
                     "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
                     "requires": {
-                        "ms": "^2.1.1"
+                        "ms": "2.1.2"
                     }
                 },
                 "ms": {
@@ -8525,7 +8525,7 @@
             "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.13.0.tgz",
             "integrity": "sha512-9HrZGFVTR5SOu3PZAnAY2hLO36aW1wmA+FDsVkr85BTST32TLCA1H/AEcatVRAsWLyXS3bqUDYCAjq5/QGuSTA==",
             "requires": {
-                "semver": "^5.4.1"
+                "semver": "5.7.0"
             }
         },
         "node-ensure": {
@@ -8538,8 +8538,8 @@
             "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.5.tgz",
             "integrity": "sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ==",
             "requires": {
-                "object.getownpropertydescriptors": "^2.0.3",
-                "semver": "^5.7.0"
+                "object.getownpropertydescriptors": "2.0.3",
+                "semver": "5.7.0"
             }
         },
         "node-fetch": {
@@ -8547,8 +8547,8 @@
             "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz",
             "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==",
             "requires": {
-                "encoding": "^0.1.11",
-                "is-stream": "^1.0.1"
+                "encoding": "0.1.12",
+                "is-stream": "1.1.0"
             }
         },
         "node-forge": {
@@ -8562,18 +8562,18 @@
             "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.8.0.tgz",
             "integrity": "sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA==",
             "requires": {
-                "fstream": "^1.0.0",
-                "glob": "^7.0.3",
-                "graceful-fs": "^4.1.2",
-                "mkdirp": "^0.5.0",
-                "nopt": "2 || 3",
-                "npmlog": "0 || 1 || 2 || 3 || 4",
-                "osenv": "0",
-                "request": "^2.87.0",
-                "rimraf": "2",
-                "semver": "~5.3.0",
-                "tar": "^2.0.0",
-                "which": "1"
+                "fstream": "1.0.12",
+                "glob": "7.1.4",
+                "graceful-fs": "4.2.0",
+                "mkdirp": "0.5.1",
+                "nopt": "3.0.6",
+                "npmlog": "4.1.2",
+                "osenv": "0.1.5",
+                "request": "2.88.0",
+                "rimraf": "2.7.1",
+                "semver": "5.3.0",
+                "tar": "2.2.2",
+                "which": "1.3.1"
             },
             "dependencies": {
                 "nopt": {
@@ -8581,7 +8581,7 @@
                     "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz",
                     "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=",
                     "requires": {
-                        "abbrev": "1"
+                        "abbrev": "1.1.1"
                     }
                 },
                 "rimraf": {
@@ -8589,7 +8589,7 @@
                     "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
                     "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
                     "requires": {
-                        "glob": "^7.1.3"
+                        "glob": "7.1.4"
                     }
                 },
                 "semver": {
@@ -8602,9 +8602,9 @@
                     "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.2.tgz",
                     "integrity": "sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==",
                     "requires": {
-                        "block-stream": "*",
-                        "fstream": "^1.0.12",
-                        "inherits": "2"
+                        "block-stream": "0.0.9",
+                        "fstream": "1.0.12",
+                        "inherits": "2.0.3"
                     }
                 }
             }
@@ -8615,29 +8615,29 @@
             "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==",
             "dev": true,
             "requires": {
-                "assert": "^1.1.1",
-                "browserify-zlib": "^0.2.0",
-                "buffer": "^4.3.0",
-                "console-browserify": "^1.1.0",
-                "constants-browserify": "^1.0.0",
-                "crypto-browserify": "^3.11.0",
-                "domain-browser": "^1.1.1",
-                "events": "^3.0.0",
-                "https-browserify": "^1.0.0",
-                "os-browserify": "^0.3.0",
+                "assert": "1.5.0",
+                "browserify-zlib": "0.2.0",
+                "buffer": "4.9.2",
+                "console-browserify": "1.2.0",
+                "constants-browserify": "1.0.0",
+                "crypto-browserify": "3.12.0",
+                "domain-browser": "1.2.0",
+                "events": "3.0.0",
+                "https-browserify": "1.0.0",
+                "os-browserify": "0.3.0",
                 "path-browserify": "0.0.1",
-                "process": "^0.11.10",
-                "punycode": "^1.2.4",
-                "querystring-es3": "^0.2.0",
-                "readable-stream": "^2.3.3",
-                "stream-browserify": "^2.0.1",
-                "stream-http": "^2.7.2",
-                "string_decoder": "^1.0.0",
-                "timers-browserify": "^2.0.4",
+                "process": "0.11.10",
+                "punycode": "1.4.1",
+                "querystring-es3": "0.2.1",
+                "readable-stream": "2.3.6",
+                "stream-browserify": "2.0.2",
+                "stream-http": "2.8.3",
+                "string_decoder": "1.1.1",
+                "timers-browserify": "2.0.11",
                 "tty-browserify": "0.0.0",
-                "url": "^0.11.0",
-                "util": "^0.11.0",
-                "vm-browserify": "^1.0.1"
+                "url": "0.11.0",
+                "util": "0.11.1",
+                "vm-browserify": "1.1.2"
             },
             "dependencies": {
                 "buffer": {
@@ -8646,9 +8646,9 @@
                     "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==",
                     "dev": true,
                     "requires": {
-                        "base64-js": "^1.0.2",
-                        "ieee754": "^1.1.4",
-                        "isarray": "^1.0.0"
+                        "base64-js": "1.3.0",
+                        "ieee754": "1.1.13",
+                        "isarray": "1.0.0"
                     }
                 },
                 "punycode": {
@@ -8665,11 +8665,11 @@
             "integrity": "sha512-M4UBGcs4jeOK9CjTsYwkvH6/MzuUmGCyTW+kCY7uO+1ZVr0+FHGdPdIf5CCLqAaxnRrWidyoQlNkMIIVwbKB8Q==",
             "dev": true,
             "requires": {
-                "growly": "^1.3.0",
-                "is-wsl": "^1.1.0",
-                "semver": "^5.5.0",
-                "shellwords": "^0.1.1",
-                "which": "^1.3.0"
+                "growly": "1.3.0",
+                "is-wsl": "1.1.0",
+                "semver": "5.7.0",
+                "shellwords": "0.1.1",
+                "which": "1.3.1"
             }
         },
         "node-pre-gyp": {
@@ -8677,16 +8677,16 @@
             "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz",
             "integrity": "sha512-TwWAOZb0j7e9eGaf9esRx3ZcLaE5tQ2lvYy1pb5IAaG1a2e2Kv5Lms1Y4hpj+ciXJRofIxxlt5haeQ/2ANeE0Q==",
             "requires": {
-                "detect-libc": "^1.0.2",
-                "mkdirp": "^0.5.1",
-                "needle": "^2.2.1",
-                "nopt": "^4.0.1",
-                "npm-packlist": "^1.1.6",
-                "npmlog": "^4.0.2",
-                "rc": "^1.2.7",
-                "rimraf": "^2.6.1",
-                "semver": "^5.3.0",
-                "tar": "^4"
+                "detect-libc": "1.0.3",
+                "mkdirp": "0.5.1",
+                "needle": "2.4.0",
+                "nopt": "4.0.1",
+                "npm-packlist": "1.4.7",
+                "npmlog": "4.1.2",
+                "rc": "1.2.8",
+                "rimraf": "2.7.1",
+                "semver": "5.7.0",
+                "tar": "4.4.10"
             },
             "dependencies": {
                 "rimraf": {
@@ -8694,7 +8694,7 @@
                     "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
                     "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
                     "requires": {
-                        "glob": "^7.1.3"
+                        "glob": "7.1.4"
                     }
                 }
             }
@@ -8704,23 +8704,23 @@
             "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.13.1.tgz",
             "integrity": "sha512-TTWFx+ZhyDx1Biiez2nB0L3YrCZ/8oHagaDalbuBSlqXgUPsdkUSzJsVxeDO9LtPB49+Fh3WQl3slABo6AotNw==",
             "requires": {
-                "async-foreach": "^0.1.3",
-                "chalk": "^1.1.1",
-                "cross-spawn": "^3.0.0",
-                "gaze": "^1.0.0",
-                "get-stdin": "^4.0.1",
-                "glob": "^7.0.3",
-                "in-publish": "^2.0.0",
-                "lodash": "^4.17.15",
-                "meow": "^3.7.0",
-                "mkdirp": "^0.5.1",
-                "nan": "^2.13.2",
-                "node-gyp": "^3.8.0",
-                "npmlog": "^4.0.0",
-                "request": "^2.88.0",
-                "sass-graph": "^2.2.4",
-                "stdout-stream": "^1.4.0",
-                "true-case-path": "^1.0.2"
+                "async-foreach": "0.1.3",
+                "chalk": "1.1.3",
+                "cross-spawn": "3.0.1",
+                "gaze": "1.1.3",
+                "get-stdin": "4.0.1",
+                "glob": "7.1.4",
+                "in-publish": "2.0.0",
+                "lodash": "4.17.15",
+                "meow": "3.7.0",
+                "mkdirp": "0.5.1",
+                "nan": "2.14.0",
+                "node-gyp": "3.8.0",
+                "npmlog": "4.1.2",
+                "request": "2.88.0",
+                "sass-graph": "2.2.4",
+                "stdout-stream": "1.4.1",
+                "true-case-path": "1.0.3"
             },
             "dependencies": {
                 "cross-spawn": {
@@ -8728,8 +8728,8 @@
                     "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-3.0.1.tgz",
                     "integrity": "sha1-ElYDfsufDF9549bvE14wdwGEuYI=",
                     "requires": {
-                        "lru-cache": "^4.0.1",
-                        "which": "^1.2.9"
+                        "lru-cache": "4.1.5",
+                        "which": "1.3.1"
                     }
                 },
                 "get-stdin": {
@@ -8754,16 +8754,16 @@
             "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-1.19.4.tgz",
             "integrity": "sha512-VGPaqQBNk193lrJFotBU8nvWZPqEZY2eIzymy2jjY0fJ9qIsxA0sxQ8ATPl0gZC645gijYEc1jtZvpS8QWzJGQ==",
             "requires": {
-                "chokidar": "^2.1.8",
-                "debug": "^3.2.6",
-                "ignore-by-default": "^1.0.1",
-                "minimatch": "^3.0.4",
-                "pstree.remy": "^1.1.7",
-                "semver": "^5.7.1",
-                "supports-color": "^5.5.0",
-                "touch": "^3.1.0",
-                "undefsafe": "^2.0.2",
-                "update-notifier": "^2.5.0"
+                "chokidar": "2.1.8",
+                "debug": "3.2.6",
+                "ignore-by-default": "1.0.1",
+                "minimatch": "3.0.4",
+                "pstree.remy": "1.1.7",
+                "semver": "5.7.1",
+                "supports-color": "5.5.0",
+                "touch": "3.1.0",
+                "undefsafe": "2.0.2",
+                "update-notifier": "2.5.0"
             },
             "dependencies": {
                 "debug": {
@@ -8771,7 +8771,7 @@
                     "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
                     "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
                     "requires": {
-                        "ms": "^2.1.1"
+                        "ms": "2.1.2"
                     }
                 },
                 "ms": {
@@ -8789,7 +8789,7 @@
                     "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
                     "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
                     "requires": {
-                        "has-flag": "^3.0.0"
+                        "has-flag": "3.0.0"
                     }
                 }
             }
@@ -8804,8 +8804,8 @@
             "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz",
             "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=",
             "requires": {
-                "abbrev": "1",
-                "osenv": "^0.1.4"
+                "abbrev": "1.1.1",
+                "osenv": "0.1.5"
             }
         },
         "normalize-package-data": {
@@ -8813,10 +8813,10 @@
             "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
             "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
             "requires": {
-                "hosted-git-info": "^2.1.4",
-                "resolve": "^1.10.0",
-                "semver": "2 || 3 || 4 || 5",
-                "validate-npm-package-license": "^3.0.1"
+                "hosted-git-info": "2.8.5",
+                "resolve": "1.11.1",
+                "semver": "5.7.0",
+                "validate-npm-package-license": "3.0.4"
             }
         },
         "normalize-path": {
@@ -8830,141 +8830,141 @@
             "integrity": "sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg=="
         },
         "npm": {
-            "version": "6.13.6",
-            "resolved": "https://registry.npmjs.org/npm/-/npm-6.13.6.tgz",
-            "integrity": "sha512-NomC08kv7HIl1FOyLOe9Hp89kYsOsvx52huVIJ7i8hFW8Xp65lDwe/8wTIrh9q9SaQhA8hTrfXPh3BEL3TmMpw==",
-            "requires": {
-                "JSONStream": "^1.3.5",
-                "abbrev": "~1.1.1",
-                "ansicolors": "~0.3.2",
-                "ansistyles": "~0.1.3",
-                "aproba": "^2.0.0",
-                "archy": "~1.0.0",
-                "bin-links": "^1.1.6",
-                "bluebird": "^3.5.5",
-                "byte-size": "^5.0.1",
-                "cacache": "^12.0.3",
-                "call-limit": "^1.1.1",
-                "chownr": "^1.1.3",
-                "ci-info": "^2.0.0",
-                "cli-columns": "^3.1.2",
-                "cli-table3": "^0.5.1",
-                "cmd-shim": "^3.0.3",
-                "columnify": "~1.5.4",
-                "config-chain": "^1.1.12",
-                "debuglog": "*",
-                "detect-indent": "~5.0.0",
-                "detect-newline": "^2.1.0",
-                "dezalgo": "~1.0.3",
-                "editor": "~1.0.0",
-                "figgy-pudding": "^3.5.1",
-                "find-npm-prefix": "^1.0.2",
-                "fs-vacuum": "~1.2.10",
-                "fs-write-stream-atomic": "~1.0.10",
-                "gentle-fs": "^2.3.0",
-                "glob": "^7.1.4",
-                "graceful-fs": "^4.2.3",
-                "has-unicode": "~2.0.1",
-                "hosted-git-info": "^2.8.5",
-                "iferr": "^1.0.2",
-                "imurmurhash": "*",
-                "infer-owner": "^1.0.4",
-                "inflight": "~1.0.6",
-                "inherits": "^2.0.4",
-                "ini": "^1.3.5",
-                "init-package-json": "^1.10.3",
-                "is-cidr": "^3.0.0",
-                "json-parse-better-errors": "^1.0.2",
-                "lazy-property": "~1.0.0",
-                "libcipm": "^4.0.7",
-                "libnpm": "^3.0.1",
-                "libnpmaccess": "^3.0.2",
-                "libnpmhook": "^5.0.3",
-                "libnpmorg": "^1.0.1",
-                "libnpmsearch": "^2.0.2",
-                "libnpmteam": "^1.0.2",
-                "libnpx": "^10.2.0",
-                "lock-verify": "^2.1.0",
-                "lockfile": "^1.0.4",
-                "lodash._baseindexof": "*",
-                "lodash._baseuniq": "~4.6.0",
-                "lodash._bindcallback": "*",
-                "lodash._cacheindexof": "*",
-                "lodash._createcache": "*",
-                "lodash._getnative": "*",
-                "lodash.clonedeep": "~4.5.0",
-                "lodash.restparam": "*",
-                "lodash.union": "~4.6.0",
-                "lodash.uniq": "~4.5.0",
-                "lodash.without": "~4.4.0",
-                "lru-cache": "^5.1.1",
-                "meant": "~1.0.1",
-                "mississippi": "^3.0.0",
-                "mkdirp": "~0.5.1",
-                "move-concurrently": "^1.0.1",
-                "node-gyp": "^5.0.5",
-                "nopt": "~4.0.1",
-                "normalize-package-data": "^2.5.0",
-                "npm-audit-report": "^1.3.2",
-                "npm-cache-filename": "~1.0.2",
-                "npm-install-checks": "^3.0.2",
-                "npm-lifecycle": "^3.1.4",
-                "npm-package-arg": "^6.1.1",
-                "npm-packlist": "^1.4.7",
-                "npm-pick-manifest": "^3.0.2",
-                "npm-profile": "^4.0.2",
-                "npm-registry-fetch": "^4.0.2",
-                "npm-user-validate": "~1.0.0",
-                "npmlog": "~4.1.2",
-                "once": "~1.4.0",
-                "opener": "^1.5.1",
-                "osenv": "^0.1.5",
-                "pacote": "^9.5.12",
-                "path-is-inside": "~1.0.2",
-                "promise-inflight": "~1.0.1",
-                "qrcode-terminal": "^0.12.0",
-                "query-string": "^6.8.2",
-                "qw": "~1.0.1",
-                "read": "~1.0.7",
-                "read-cmd-shim": "^1.0.5",
-                "read-installed": "~4.0.3",
-                "read-package-json": "^2.1.1",
-                "read-package-tree": "^5.3.1",
-                "readable-stream": "^3.4.0",
-                "readdir-scoped-modules": "^1.1.0",
-                "request": "^2.88.0",
-                "retry": "^0.12.0",
-                "rimraf": "^2.6.3",
-                "safe-buffer": "^5.1.2",
-                "semver": "^5.7.1",
-                "sha": "^3.0.0",
-                "slide": "~1.1.6",
-                "sorted-object": "~2.0.1",
-                "sorted-union-stream": "~2.1.3",
-                "ssri": "^6.0.1",
-                "stringify-package": "^1.0.1",
-                "tar": "^4.4.13",
-                "text-table": "~0.2.0",
-                "tiny-relative-date": "^1.3.0",
+            "version": "6.13.7",
+            "resolved": "https://registry.npmjs.org/npm/-/npm-6.13.7.tgz",
+            "integrity": "sha512-X967EKTT407CvgrWFjXusnPh0VLERcmR9hZFSVgkEquOomZkvpwLJ5zrQ3qrG9SpPLKJE4bPLUu76exKQ4a3Cg==",
+            "requires": {
+                "JSONStream": "1.3.5",
+                "abbrev": "1.1.1",
+                "ansicolors": "0.3.2",
+                "ansistyles": "0.1.3",
+                "aproba": "2.0.0",
+                "archy": "1.0.0",
+                "bin-links": "1.1.7",
+                "bluebird": "3.5.5",
+                "byte-size": "5.0.1",
+                "cacache": "12.0.3",
+                "call-limit": "1.1.1",
+                "chownr": "1.1.3",
+                "ci-info": "2.0.0",
+                "cli-columns": "3.1.2",
+                "cli-table3": "0.5.1",
+                "cmd-shim": "3.0.3",
+                "columnify": "1.5.4",
+                "config-chain": "1.1.12",
+                "debuglog": "1.0.1",
+                "detect-indent": "5.0.0",
+                "detect-newline": "2.1.0",
+                "dezalgo": "1.0.3",
+                "editor": "1.0.0",
+                "figgy-pudding": "3.5.1",
+                "find-npm-prefix": "1.0.2",
+                "fs-vacuum": "1.2.10",
+                "fs-write-stream-atomic": "1.0.10",
+                "gentle-fs": "2.3.0",
+                "glob": "7.1.4",
+                "graceful-fs": "4.2.3",
+                "has-unicode": "2.0.1",
+                "hosted-git-info": "2.8.5",
+                "iferr": "1.0.2",
+                "imurmurhash": "0.1.4",
+                "infer-owner": "1.0.4",
+                "inflight": "1.0.6",
+                "inherits": "2.0.4",
+                "ini": "1.3.5",
+                "init-package-json": "1.10.3",
+                "is-cidr": "3.0.0",
+                "json-parse-better-errors": "1.0.2",
+                "lazy-property": "1.0.0",
+                "libcipm": "4.0.7",
+                "libnpm": "3.0.1",
+                "libnpmaccess": "3.0.2",
+                "libnpmhook": "5.0.3",
+                "libnpmorg": "1.0.1",
+                "libnpmsearch": "2.0.2",
+                "libnpmteam": "1.0.2",
+                "libnpx": "10.2.2",
+                "lock-verify": "2.1.0",
+                "lockfile": "1.0.4",
+                "lodash._baseindexof": "3.1.0",
+                "lodash._baseuniq": "4.6.0",
+                "lodash._bindcallback": "3.0.1",
+                "lodash._cacheindexof": "3.0.2",
+                "lodash._createcache": "3.1.2",
+                "lodash._getnative": "3.9.1",
+                "lodash.clonedeep": "4.5.0",
+                "lodash.restparam": "3.6.1",
+                "lodash.union": "4.6.0",
+                "lodash.uniq": "4.5.0",
+                "lodash.without": "4.4.0",
+                "lru-cache": "5.1.1",
+                "meant": "1.0.1",
+                "mississippi": "3.0.0",
+                "mkdirp": "0.5.1",
+                "move-concurrently": "1.0.1",
+                "node-gyp": "5.0.7",
+                "nopt": "4.0.1",
+                "normalize-package-data": "2.5.0",
+                "npm-audit-report": "1.3.2",
+                "npm-cache-filename": "1.0.2",
+                "npm-install-checks": "3.0.2",
+                "npm-lifecycle": "3.1.4",
+                "npm-package-arg": "6.1.1",
+                "npm-packlist": "1.4.7",
+                "npm-pick-manifest": "3.0.2",
+                "npm-profile": "4.0.2",
+                "npm-registry-fetch": "4.0.2",
+                "npm-user-validate": "1.0.0",
+                "npmlog": "4.1.2",
+                "once": "1.4.0",
+                "opener": "1.5.1",
+                "osenv": "0.1.5",
+                "pacote": "9.5.12",
+                "path-is-inside": "1.0.2",
+                "promise-inflight": "1.0.1",
+                "qrcode-terminal": "0.12.0",
+                "query-string": "6.8.2",
+                "qw": "1.0.1",
+                "read": "1.0.7",
+                "read-cmd-shim": "1.0.5",
+                "read-installed": "4.0.3",
+                "read-package-json": "2.1.1",
+                "read-package-tree": "5.3.1",
+                "readable-stream": "3.4.0",
+                "readdir-scoped-modules": "1.1.0",
+                "request": "2.88.0",
+                "retry": "0.12.0",
+                "rimraf": "2.6.3",
+                "safe-buffer": "5.1.2",
+                "semver": "5.7.1",
+                "sha": "3.0.0",
+                "slide": "1.1.6",
+                "sorted-object": "2.0.1",
+                "sorted-union-stream": "2.1.3",
+                "ssri": "6.0.1",
+                "stringify-package": "1.0.1",
+                "tar": "4.4.13",
+                "text-table": "0.2.0",
+                "tiny-relative-date": "1.3.0",
                 "uid-number": "0.0.6",
-                "umask": "~1.1.0",
-                "unique-filename": "^1.1.1",
-                "unpipe": "~1.0.0",
-                "update-notifier": "^2.5.0",
-                "uuid": "^3.3.3",
-                "validate-npm-package-license": "^3.0.4",
-                "validate-npm-package-name": "~3.0.0",
-                "which": "^1.3.1",
-                "worker-farm": "^1.7.0",
-                "write-file-atomic": "^2.4.3"
+                "umask": "1.1.0",
+                "unique-filename": "1.1.1",
+                "unpipe": "1.0.0",
+                "update-notifier": "2.5.0",
+                "uuid": "3.3.3",
+                "validate-npm-package-license": "3.0.4",
+                "validate-npm-package-name": "3.0.0",
+                "which": "1.3.1",
+                "worker-farm": "1.7.0",
+                "write-file-atomic": "2.4.3"
             },
             "dependencies": {
                 "JSONStream": {
                     "version": "1.3.5",
                     "bundled": true,
                     "requires": {
-                        "jsonparse": "^1.2.0",
-                        "through": ">=2.2.7 <3"
+                        "jsonparse": "1.3.1",
+                        "through": "2.3.8"
                     }
                 },
                 "abbrev": {
@@ -8975,31 +8975,31 @@
                     "version": "4.3.0",
                     "bundled": true,
                     "requires": {
-                        "es6-promisify": "^5.0.0"
+                        "es6-promisify": "5.0.0"
                     }
                 },
                 "agentkeepalive": {
                     "version": "3.5.2",
                     "bundled": true,
                     "requires": {
-                        "humanize-ms": "^1.2.1"
+                        "humanize-ms": "1.2.1"
                     }
                 },
                 "ajv": {
                     "version": "5.5.2",
                     "bundled": true,
                     "requires": {
-                        "co": "^4.6.0",
-                        "fast-deep-equal": "^1.0.0",
-                        "fast-json-stable-stringify": "^2.0.0",
-                        "json-schema-traverse": "^0.3.0"
+                        "co": "4.6.0",
+                        "fast-deep-equal": "1.1.0",
+                        "fast-json-stable-stringify": "2.0.0",
+                        "json-schema-traverse": "0.3.1"
                     }
                 },
                 "ansi-align": {
                     "version": "2.0.0",
                     "bundled": true,
                     "requires": {
-                        "string-width": "^2.0.0"
+                        "string-width": "2.1.1"
                     }
                 },
                 "ansi-regex": {
@@ -9010,7 +9010,7 @@
                     "version": "3.2.1",
                     "bundled": true,
                     "requires": {
-                        "color-convert": "^1.9.0"
+                        "color-convert": "1.9.1"
                     }
                 },
                 "ansicolors": {
@@ -9033,28 +9033,28 @@
                     "version": "1.1.4",
                     "bundled": true,
                     "requires": {
-                        "delegates": "^1.0.0",
-                        "readable-stream": "^2.0.6"
+                        "delegates": "1.0.0",
+                        "readable-stream": "2.3.6"
                     },
                     "dependencies": {
                         "readable-stream": {
                             "version": "2.3.6",
                             "bundled": true,
                             "requires": {
-                                "core-util-is": "~1.0.0",
-                                "inherits": "~2.0.3",
-                                "isarray": "~1.0.0",
-                                "process-nextick-args": "~2.0.0",
-                                "safe-buffer": "~5.1.1",
-                                "string_decoder": "~1.1.1",
-                                "util-deprecate": "~1.0.1"
+                                "core-util-is": "1.0.2",
+                                "inherits": "2.0.4",
+                                "isarray": "1.0.0",
+                                "process-nextick-args": "2.0.0",
+                                "safe-buffer": "5.1.2",
+                                "string_decoder": "1.1.1",
+                                "util-deprecate": "1.0.2"
                             }
                         },
                         "string_decoder": {
                             "version": "1.1.1",
                             "bundled": true,
                             "requires": {
-                                "safe-buffer": "~5.1.0"
+                                "safe-buffer": "5.1.2"
                             }
                         }
                     }
@@ -9067,7 +9067,7 @@
                     "version": "0.2.4",
                     "bundled": true,
                     "requires": {
-                        "safer-buffer": "~2.1.0"
+                        "safer-buffer": "2.1.2"
                     }
                 },
                 "assert-plus": {
@@ -9095,19 +9095,19 @@
                     "bundled": true,
                     "optional": true,
                     "requires": {
-                        "tweetnacl": "^0.14.3"
+                        "tweetnacl": "0.14.5"
                     }
                 },
                 "bin-links": {
-                    "version": "1.1.6",
+                    "version": "1.1.7",
                     "bundled": true,
                     "requires": {
-                        "bluebird": "^3.5.3",
-                        "cmd-shim": "^3.0.0",
-                        "gentle-fs": "^2.3.0",
-                        "graceful-fs": "^4.1.15",
-                        "npm-normalize-package-bin": "^1.0.0",
-                        "write-file-atomic": "^2.3.0"
+                        "bluebird": "3.5.5",
+                        "cmd-shim": "3.0.3",
+                        "gentle-fs": "2.3.0",
+                        "graceful-fs": "4.2.3",
+                        "npm-normalize-package-bin": "1.0.1",
+                        "write-file-atomic": "2.4.3"
                     }
                 },
                 "bluebird": {
@@ -9118,20 +9118,20 @@
                     "version": "1.3.0",
                     "bundled": true,
                     "requires": {
-                        "ansi-align": "^2.0.0",
-                        "camelcase": "^4.0.0",
-                        "chalk": "^2.0.1",
-                        "cli-boxes": "^1.0.0",
-                        "string-width": "^2.0.0",
-                        "term-size": "^1.2.0",
-                        "widest-line": "^2.0.0"
+                        "ansi-align": "2.0.0",
+                        "camelcase": "4.1.0",
+                        "chalk": "2.4.1",
+                        "cli-boxes": "1.0.0",
+                        "string-width": "2.1.1",
+                        "term-size": "1.2.0",
+                        "widest-line": "2.0.0"
                     }
                 },
                 "brace-expansion": {
                     "version": "1.1.11",
                     "bundled": true,
                     "requires": {
-                        "balanced-match": "^1.0.0",
+                        "balanced-match": "1.0.0",
                         "concat-map": "0.0.1"
                     }
                 },
@@ -9155,21 +9155,21 @@
                     "version": "12.0.3",
                     "bundled": true,
                     "requires": {
-                        "bluebird": "^3.5.5",
-                        "chownr": "^1.1.1",
-                        "figgy-pudding": "^3.5.1",
-                        "glob": "^7.1.4",
-                        "graceful-fs": "^4.1.15",
-                        "infer-owner": "^1.0.3",
-                        "lru-cache": "^5.1.1",
-                        "mississippi": "^3.0.0",
-                        "mkdirp": "^0.5.1",
-                        "move-concurrently": "^1.0.1",
-                        "promise-inflight": "^1.0.1",
-                        "rimraf": "^2.6.3",
-                        "ssri": "^6.0.1",
-                        "unique-filename": "^1.1.1",
-                        "y18n": "^4.0.0"
+                        "bluebird": "3.5.5",
+                        "chownr": "1.1.3",
+                        "figgy-pudding": "3.5.1",
+                        "glob": "7.1.4",
+                        "graceful-fs": "4.2.3",
+                        "infer-owner": "1.0.4",
+                        "lru-cache": "5.1.1",
+                        "mississippi": "3.0.0",
+                        "mkdirp": "0.5.1",
+                        "move-concurrently": "1.0.1",
+                        "promise-inflight": "1.0.1",
+                        "rimraf": "2.6.3",
+                        "ssri": "6.0.1",
+                        "unique-filename": "1.1.1",
+                        "y18n": "4.0.0"
                     }
                 },
                 "call-limit": {
@@ -9192,9 +9192,9 @@
                     "version": "2.4.1",
                     "bundled": true,
                     "requires": {
-                        "ansi-styles": "^3.2.1",
-                        "escape-string-regexp": "^1.0.5",
-                        "supports-color": "^5.3.0"
+                        "ansi-styles": "3.2.1",
+                        "escape-string-regexp": "1.0.5",
+                        "supports-color": "5.4.0"
                     }
                 },
                 "chownr": {
@@ -9209,7 +9209,7 @@
                     "version": "2.0.10",
                     "bundled": true,
                     "requires": {
-                        "ip-regex": "^2.1.0"
+                        "ip-regex": "2.1.0"
                     }
                 },
                 "cli-boxes": {
@@ -9220,26 +9220,26 @@
                     "version": "3.1.2",
                     "bundled": true,
                     "requires": {
-                        "string-width": "^2.0.0",
-                        "strip-ansi": "^3.0.1"
+                        "string-width": "2.1.1",
+                        "strip-ansi": "3.0.1"
                     }
                 },
                 "cli-table3": {
                     "version": "0.5.1",
                     "bundled": true,
                     "requires": {
-                        "colors": "^1.1.2",
-                        "object-assign": "^4.1.0",
-                        "string-width": "^2.1.1"
+                        "colors": "1.3.3",
+                        "object-assign": "4.1.1",
+                        "string-width": "2.1.1"
                     }
                 },
                 "cliui": {
                     "version": "4.1.0",
                     "bundled": true,
                     "requires": {
-                        "string-width": "^2.1.1",
-                        "strip-ansi": "^4.0.0",
-                        "wrap-ansi": "^2.0.0"
+                        "string-width": "2.1.1",
+                        "strip-ansi": "4.0.0",
+                        "wrap-ansi": "2.1.0"
                     },
                     "dependencies": {
                         "ansi-regex": {
@@ -9250,7 +9250,7 @@
                             "version": "4.0.0",
                             "bundled": true,
                             "requires": {
-                                "ansi-regex": "^3.0.0"
+                                "ansi-regex": "3.0.0"
                             }
                         }
                     }
@@ -9263,8 +9263,8 @@
                     "version": "3.0.3",
                     "bundled": true,
                     "requires": {
-                        "graceful-fs": "^4.1.2",
-                        "mkdirp": "~0.5.0"
+                        "graceful-fs": "4.2.3",
+                        "mkdirp": "0.5.1"
                     }
                 },
                 "co": {
@@ -9279,7 +9279,7 @@
                     "version": "1.9.1",
                     "bundled": true,
                     "requires": {
-                        "color-name": "^1.1.1"
+                        "color-name": "1.1.3"
                     }
                 },
                 "color-name": {
@@ -9295,15 +9295,15 @@
                     "version": "1.5.4",
                     "bundled": true,
                     "requires": {
-                        "strip-ansi": "^3.0.0",
-                        "wcwidth": "^1.0.0"
+                        "strip-ansi": "3.0.1",
+                        "wcwidth": "1.0.1"
                     }
                 },
                 "combined-stream": {
                     "version": "1.0.6",
                     "bundled": true,
                     "requires": {
-                        "delayed-stream": "~1.0.0"
+                        "delayed-stream": "1.0.0"
                     }
                 },
                 "concat-map": {
@@ -9314,30 +9314,30 @@
                     "version": "1.6.2",
                     "bundled": true,
                     "requires": {
-                        "buffer-from": "^1.0.0",
-                        "inherits": "^2.0.3",
-                        "readable-stream": "^2.2.2",
-                        "typedarray": "^0.0.6"
+                        "buffer-from": "1.0.0",
+                        "inherits": "2.0.4",
+                        "readable-stream": "2.3.6",
+                        "typedarray": "0.0.6"
                     },
                     "dependencies": {
                         "readable-stream": {
                             "version": "2.3.6",
                             "bundled": true,
                             "requires": {
-                                "core-util-is": "~1.0.0",
-                                "inherits": "~2.0.3",
-                                "isarray": "~1.0.0",
-                                "process-nextick-args": "~2.0.0",
-                                "safe-buffer": "~5.1.1",
-                                "string_decoder": "~1.1.1",
-                                "util-deprecate": "~1.0.1"
+                                "core-util-is": "1.0.2",
+                                "inherits": "2.0.4",
+                                "isarray": "1.0.0",
+                                "process-nextick-args": "2.0.0",
+                                "safe-buffer": "5.1.2",
+                                "string_decoder": "1.1.1",
+                                "util-deprecate": "1.0.2"
                             }
                         },
                         "string_decoder": {
                             "version": "1.1.1",
                             "bundled": true,
                             "requires": {
-                                "safe-buffer": "~5.1.0"
+                                "safe-buffer": "5.1.2"
                             }
                         }
                     }
@@ -9346,20 +9346,20 @@
                     "version": "1.1.12",
                     "bundled": true,
                     "requires": {
-                        "ini": "^1.3.4",
-                        "proto-list": "~1.2.1"
+                        "ini": "1.3.5",
+                        "proto-list": "1.2.4"
                     }
                 },
                 "configstore": {
                     "version": "3.1.2",
                     "bundled": true,
                     "requires": {
-                        "dot-prop": "^4.1.0",
-                        "graceful-fs": "^4.1.2",
-                        "make-dir": "^1.0.0",
-                        "unique-string": "^1.0.0",
-                        "write-file-atomic": "^2.0.0",
-                        "xdg-basedir": "^3.0.0"
+                        "dot-prop": "4.2.0",
+                        "graceful-fs": "4.2.3",
+                        "make-dir": "1.3.0",
+                        "unique-string": "1.0.0",
+                        "write-file-atomic": "2.4.3",
+                        "xdg-basedir": "3.0.0"
                     }
                 },
                 "console-control-strings": {
@@ -9370,12 +9370,12 @@
                     "version": "1.0.5",
                     "bundled": true,
                     "requires": {
-                        "aproba": "^1.1.1",
-                        "fs-write-stream-atomic": "^1.0.8",
-                        "iferr": "^0.1.5",
-                        "mkdirp": "^0.5.1",
-                        "rimraf": "^2.5.4",
-                        "run-queue": "^1.0.0"
+                        "aproba": "1.2.0",
+                        "fs-write-stream-atomic": "1.0.10",
+                        "iferr": "0.1.5",
+                        "mkdirp": "0.5.1",
+                        "rimraf": "2.6.3",
+                        "run-queue": "1.0.3"
                     },
                     "dependencies": {
                         "aproba": {
@@ -9396,24 +9396,24 @@
                     "version": "3.0.2",
                     "bundled": true,
                     "requires": {
-                        "capture-stack-trace": "^1.0.0"
+                        "capture-stack-trace": "1.0.0"
                     }
                 },
                 "cross-spawn": {
                     "version": "5.1.0",
                     "bundled": true,
                     "requires": {
-                        "lru-cache": "^4.0.1",
-                        "shebang-command": "^1.2.0",
-                        "which": "^1.2.9"
+                        "lru-cache": "4.1.5",
+                        "shebang-command": "1.2.0",
+                        "which": "1.3.1"
                     },
                     "dependencies": {
                         "lru-cache": {
                             "version": "4.1.5",
                             "bundled": true,
                             "requires": {
-                                "pseudomap": "^1.0.2",
-                                "yallist": "^2.1.2"
+                                "pseudomap": "1.0.2",
+                                "yallist": "2.1.2"
                             }
                         },
                         "yallist": {
@@ -9434,7 +9434,7 @@
                     "version": "1.14.1",
                     "bundled": true,
                     "requires": {
-                        "assert-plus": "^1.0.0"
+                        "assert-plus": "1.0.0"
                     }
                 },
                 "debug": {
@@ -9470,14 +9470,14 @@
                     "version": "1.0.3",
                     "bundled": true,
                     "requires": {
-                        "clone": "^1.0.2"
+                        "clone": "1.0.4"
                     }
                 },
                 "define-properties": {
                     "version": "1.1.3",
                     "bundled": true,
                     "requires": {
-                        "object-keys": "^1.0.12"
+                        "object-keys": "1.0.12"
                     }
                 },
                 "delayed-stream": {
@@ -9500,15 +9500,15 @@
                     "version": "1.0.3",
                     "bundled": true,
                     "requires": {
-                        "asap": "^2.0.0",
-                        "wrappy": "1"
+                        "asap": "2.0.6",
+                        "wrappy": "1.0.2"
                     }
                 },
                 "dot-prop": {
                     "version": "4.2.0",
                     "bundled": true,
                     "requires": {
-                        "is-obj": "^1.0.0"
+                        "is-obj": "1.0.1"
                     }
                 },
                 "dotenv": {
@@ -9523,30 +9523,30 @@
                     "version": "3.6.0",
                     "bundled": true,
                     "requires": {
-                        "end-of-stream": "^1.0.0",
-                        "inherits": "^2.0.1",
-                        "readable-stream": "^2.0.0",
-                        "stream-shift": "^1.0.0"
+                        "end-of-stream": "1.4.1",
+                        "inherits": "2.0.4",
+                        "readable-stream": "2.3.6",
+                        "stream-shift": "1.0.0"
                     },
                     "dependencies": {
                         "readable-stream": {
                             "version": "2.3.6",
                             "bundled": true,
                             "requires": {
-                                "core-util-is": "~1.0.0",
-                                "inherits": "~2.0.3",
-                                "isarray": "~1.0.0",
-                                "process-nextick-args": "~2.0.0",
-                                "safe-buffer": "~5.1.1",
-                                "string_decoder": "~1.1.1",
-                                "util-deprecate": "~1.0.1"
+                                "core-util-is": "1.0.2",
+                                "inherits": "2.0.4",
+                                "isarray": "1.0.0",
+                                "process-nextick-args": "2.0.0",
+                                "safe-buffer": "5.1.2",
+                                "string_decoder": "1.1.1",
+                                "util-deprecate": "1.0.2"
                             }
                         },
                         "string_decoder": {
                             "version": "1.1.1",
                             "bundled": true,
                             "requires": {
-                                "safe-buffer": "~5.1.0"
+                                "safe-buffer": "5.1.2"
                             }
                         }
                     }
@@ -9556,8 +9556,8 @@
                     "bundled": true,
                     "optional": true,
                     "requires": {
-                        "jsbn": "~0.1.0",
-                        "safer-buffer": "^2.1.0"
+                        "jsbn": "0.1.1",
+                        "safer-buffer": "2.1.2"
                     }
                 },
                 "editor": {
@@ -9568,18 +9568,18 @@
                     "version": "0.1.12",
                     "bundled": true,
                     "requires": {
-                        "iconv-lite": "~0.4.13"
+                        "iconv-lite": "0.4.23"
                     }
                 },
                 "end-of-stream": {
                     "version": "1.4.1",
                     "bundled": true,
                     "requires": {
-                        "once": "^1.4.0"
+                        "once": "1.4.0"
                     }
                 },
                 "env-paths": {
-                    "version": "1.0.0",
+                    "version": "2.2.0",
                     "bundled": true
                 },
                 "err-code": {
@@ -9590,27 +9590,27 @@
                     "version": "0.1.7",
                     "bundled": true,
                     "requires": {
-                        "prr": "~1.0.1"
+                        "prr": "1.0.1"
                     }
                 },
                 "es-abstract": {
                     "version": "1.12.0",
                     "bundled": true,
                     "requires": {
-                        "es-to-primitive": "^1.1.1",
-                        "function-bind": "^1.1.1",
-                        "has": "^1.0.1",
-                        "is-callable": "^1.1.3",
-                        "is-regex": "^1.0.4"
+                        "es-to-primitive": "1.2.0",
+                        "function-bind": "1.1.1",
+                        "has": "1.0.3",
+                        "is-callable": "1.1.4",
+                        "is-regex": "1.0.4"
                     }
                 },
                 "es-to-primitive": {
                     "version": "1.2.0",
                     "bundled": true,
                     "requires": {
-                        "is-callable": "^1.1.4",
-                        "is-date-object": "^1.0.1",
-                        "is-symbol": "^1.0.2"
+                        "is-callable": "1.1.4",
+                        "is-date-object": "1.0.1",
+                        "is-symbol": "1.0.2"
                     }
                 },
                 "es6-promise": {
@@ -9621,7 +9621,7 @@
                     "version": "5.0.0",
                     "bundled": true,
                     "requires": {
-                        "es6-promise": "^4.0.3"
+                        "es6-promise": "4.2.8"
                     }
                 },
                 "escape-string-regexp": {
@@ -9632,13 +9632,13 @@
                     "version": "0.7.0",
                     "bundled": true,
                     "requires": {
-                        "cross-spawn": "^5.0.1",
-                        "get-stream": "^3.0.0",
-                        "is-stream": "^1.1.0",
-                        "npm-run-path": "^2.0.0",
-                        "p-finally": "^1.0.0",
-                        "signal-exit": "^3.0.0",
-                        "strip-eof": "^1.0.0"
+                        "cross-spawn": "5.1.0",
+                        "get-stream": "3.0.0",
+                        "is-stream": "1.1.0",
+                        "npm-run-path": "2.0.2",
+                        "p-finally": "1.0.0",
+                        "signal-exit": "3.0.2",
+                        "strip-eof": "1.0.0"
                     },
                     "dependencies": {
                         "get-stream": {
@@ -9675,35 +9675,35 @@
                     "version": "2.1.0",
                     "bundled": true,
                     "requires": {
-                        "locate-path": "^2.0.0"
+                        "locate-path": "2.0.0"
                     }
                 },
                 "flush-write-stream": {
                     "version": "1.0.3",
                     "bundled": true,
                     "requires": {
-                        "inherits": "^2.0.1",
-                        "readable-stream": "^2.0.4"
+                        "inherits": "2.0.4",
+                        "readable-stream": "2.3.6"
                     },
                     "dependencies": {
                         "readable-stream": {
                             "version": "2.3.6",
                             "bundled": true,
                             "requires": {
-                                "core-util-is": "~1.0.0",
-                                "inherits": "~2.0.3",
-                                "isarray": "~1.0.0",
-                                "process-nextick-args": "~2.0.0",
-                                "safe-buffer": "~5.1.1",
-                                "string_decoder": "~1.1.1",
-                                "util-deprecate": "~1.0.1"
+                                "core-util-is": "1.0.2",
+                                "inherits": "2.0.4",
+                                "isarray": "1.0.0",
+                                "process-nextick-args": "2.0.0",
+                                "safe-buffer": "5.1.2",
+                                "string_decoder": "1.1.1",
+                                "util-deprecate": "1.0.2"
                             }
                         },
                         "string_decoder": {
                             "version": "1.1.1",
                             "bundled": true,
                             "requires": {
-                                "safe-buffer": "~5.1.0"
+                                "safe-buffer": "5.1.2"
                             }
                         }
                     }
@@ -9716,37 +9716,37 @@
                     "version": "2.3.2",
                     "bundled": true,
                     "requires": {
-                        "asynckit": "^0.4.0",
+                        "asynckit": "0.4.0",
                         "combined-stream": "1.0.6",
-                        "mime-types": "^2.1.12"
+                        "mime-types": "2.1.19"
                     }
                 },
                 "from2": {
                     "version": "2.3.0",
                     "bundled": true,
                     "requires": {
-                        "inherits": "^2.0.1",
-                        "readable-stream": "^2.0.0"
+                        "inherits": "2.0.4",
+                        "readable-stream": "2.3.6"
                     },
                     "dependencies": {
                         "readable-stream": {
                             "version": "2.3.6",
                             "bundled": true,
                             "requires": {
-                                "core-util-is": "~1.0.0",
-                                "inherits": "~2.0.3",
-                                "isarray": "~1.0.0",
-                                "process-nextick-args": "~2.0.0",
-                                "safe-buffer": "~5.1.1",
-                                "string_decoder": "~1.1.1",
-                                "util-deprecate": "~1.0.1"
+                                "core-util-is": "1.0.2",
+                                "inherits": "2.0.4",
+                                "isarray": "1.0.0",
+                                "process-nextick-args": "2.0.0",
+                                "safe-buffer": "5.1.2",
+                                "string_decoder": "1.1.1",
+                                "util-deprecate": "1.0.2"
                             }
                         },
                         "string_decoder": {
                             "version": "1.1.1",
                             "bundled": true,
                             "requires": {
-                                "safe-buffer": "~5.1.0"
+                                "safe-buffer": "5.1.2"
                             }
                         }
                     }
@@ -9755,15 +9755,15 @@
                     "version": "1.2.7",
                     "bundled": true,
                     "requires": {
-                        "minipass": "^2.6.0"
+                        "minipass": "2.9.0"
                     },
                     "dependencies": {
                         "minipass": {
                             "version": "2.9.0",
                             "bundled": true,
                             "requires": {
-                                "safe-buffer": "^5.1.2",
-                                "yallist": "^3.0.0"
+                                "safe-buffer": "5.1.2",
+                                "yallist": "3.0.3"
                             }
                         }
                     }
@@ -9772,19 +9772,19 @@
                     "version": "1.2.10",
                     "bundled": true,
                     "requires": {
-                        "graceful-fs": "^4.1.2",
-                        "path-is-inside": "^1.0.1",
-                        "rimraf": "^2.5.2"
+                        "graceful-fs": "4.2.3",
+                        "path-is-inside": "1.0.2",
+                        "rimraf": "2.6.3"
                     }
                 },
                 "fs-write-stream-atomic": {
                     "version": "1.0.10",
                     "bundled": true,
                     "requires": {
-                        "graceful-fs": "^4.1.2",
-                        "iferr": "^0.1.5",
-                        "imurmurhash": "^0.1.4",
-                        "readable-stream": "1 || 2"
+                        "graceful-fs": "4.2.3",
+                        "iferr": "0.1.5",
+                        "imurmurhash": "0.1.4",
+                        "readable-stream": "2.3.6"
                     },
                     "dependencies": {
                         "iferr": {
@@ -9795,20 +9795,20 @@
                             "version": "2.3.6",
                             "bundled": true,
                             "requires": {
-                                "core-util-is": "~1.0.0",
-                                "inherits": "~2.0.3",
-                                "isarray": "~1.0.0",
-                                "process-nextick-args": "~2.0.0",
-                                "safe-buffer": "~5.1.1",
-                                "string_decoder": "~1.1.1",
-                                "util-deprecate": "~1.0.1"
+                                "core-util-is": "1.0.2",
+                                "inherits": "2.0.4",
+                                "isarray": "1.0.0",
+                                "process-nextick-args": "2.0.0",
+                                "safe-buffer": "5.1.2",
+                                "string_decoder": "1.1.1",
+                                "util-deprecate": "1.0.2"
                             }
                         },
                         "string_decoder": {
                             "version": "1.1.1",
                             "bundled": true,
                             "requires": {
-                                "safe-buffer": "~5.1.0"
+                                "safe-buffer": "5.1.2"
                             }
                         }
                     }
@@ -9825,14 +9825,14 @@
                     "version": "2.7.4",
                     "bundled": true,
                     "requires": {
-                        "aproba": "^1.0.3",
-                        "console-control-strings": "^1.0.0",
-                        "has-unicode": "^2.0.0",
-                        "object-assign": "^4.1.0",
-                        "signal-exit": "^3.0.0",
-                        "string-width": "^1.0.1",
-                        "strip-ansi": "^3.0.1",
-                        "wide-align": "^1.1.0"
+                        "aproba": "1.2.0",
+                        "console-control-strings": "1.1.0",
+                        "has-unicode": "2.0.1",
+                        "object-assign": "4.1.1",
+                        "signal-exit": "3.0.2",
+                        "string-width": "1.0.2",
+                        "strip-ansi": "3.0.1",
+                        "wide-align": "1.1.2"
                     },
                     "dependencies": {
                         "aproba": {
@@ -9843,9 +9843,9 @@
                             "version": "1.0.2",
                             "bundled": true,
                             "requires": {
-                                "code-point-at": "^1.0.0",
-                                "is-fullwidth-code-point": "^1.0.0",
-                                "strip-ansi": "^3.0.0"
+                                "code-point-at": "1.1.0",
+                                "is-fullwidth-code-point": "1.0.0",
+                                "strip-ansi": "3.0.1"
                             }
                         }
                     }
@@ -9858,17 +9858,17 @@
                     "version": "2.3.0",
                     "bundled": true,
                     "requires": {
-                        "aproba": "^1.1.2",
-                        "chownr": "^1.1.2",
-                        "cmd-shim": "^3.0.3",
-                        "fs-vacuum": "^1.2.10",
-                        "graceful-fs": "^4.1.11",
-                        "iferr": "^0.1.5",
-                        "infer-owner": "^1.0.4",
-                        "mkdirp": "^0.5.1",
-                        "path-is-inside": "^1.0.2",
-                        "read-cmd-shim": "^1.0.1",
-                        "slide": "^1.1.6"
+                        "aproba": "1.2.0",
+                        "chownr": "1.1.3",
+                        "cmd-shim": "3.0.3",
+                        "fs-vacuum": "1.2.10",
+                        "graceful-fs": "4.2.3",
+                        "iferr": "0.1.5",
+                        "infer-owner": "1.0.4",
+                        "mkdirp": "0.5.1",
+                        "path-is-inside": "1.0.2",
+                        "read-cmd-shim": "1.0.5",
+                        "slide": "1.1.6"
                     },
                     "dependencies": {
                         "aproba": {
@@ -9882,57 +9882,57 @@
                     }
                 },
                 "get-caller-file": {
-                    "version": "1.0.2",
+                    "version": "1.0.3",
                     "bundled": true
                 },
                 "get-stream": {
                     "version": "4.1.0",
                     "bundled": true,
                     "requires": {
-                        "pump": "^3.0.0"
+                        "pump": "3.0.0"
                     }
                 },
                 "getpass": {
                     "version": "0.1.7",
                     "bundled": true,
                     "requires": {
-                        "assert-plus": "^1.0.0"
+                        "assert-plus": "1.0.0"
                     }
                 },
                 "glob": {
                     "version": "7.1.4",
                     "bundled": true,
                     "requires": {
-                        "fs.realpath": "^1.0.0",
-                        "inflight": "^1.0.4",
-                        "inherits": "2",
-                        "minimatch": "^3.0.4",
-                        "once": "^1.3.0",
-                        "path-is-absolute": "^1.0.0"
+                        "fs.realpath": "1.0.0",
+                        "inflight": "1.0.6",
+                        "inherits": "2.0.4",
+                        "minimatch": "3.0.4",
+                        "once": "1.4.0",
+                        "path-is-absolute": "1.0.1"
                     }
                 },
                 "global-dirs": {
                     "version": "0.1.1",
                     "bundled": true,
                     "requires": {
-                        "ini": "^1.3.4"
+                        "ini": "1.3.5"
                     }
                 },
                 "got": {
                     "version": "6.7.1",
                     "bundled": true,
                     "requires": {
-                        "create-error-class": "^3.0.0",
-                        "duplexer3": "^0.1.4",
-                        "get-stream": "^3.0.0",
-                        "is-redirect": "^1.0.0",
-                        "is-retry-allowed": "^1.0.0",
-                        "is-stream": "^1.0.0",
-                        "lowercase-keys": "^1.0.0",
-                        "safe-buffer": "^5.0.1",
-                        "timed-out": "^4.0.0",
-                        "unzip-response": "^2.0.1",
-                        "url-parse-lax": "^1.0.0"
+                        "create-error-class": "3.0.2",
+                        "duplexer3": "0.1.4",
+                        "get-stream": "3.0.0",
+                        "is-redirect": "1.0.0",
+                        "is-retry-allowed": "1.1.0",
+                        "is-stream": "1.1.0",
+                        "lowercase-keys": "1.0.1",
+                        "safe-buffer": "5.1.2",
+                        "timed-out": "4.0.1",
+                        "unzip-response": "2.0.1",
+                        "url-parse-lax": "1.0.0"
                     },
                     "dependencies": {
                         "get-stream": {
@@ -9953,15 +9953,15 @@
                     "version": "5.1.0",
                     "bundled": true,
                     "requires": {
-                        "ajv": "^5.3.0",
-                        "har-schema": "^2.0.0"
+                        "ajv": "5.5.2",
+                        "har-schema": "2.0.0"
                     }
                 },
                 "has": {
                     "version": "1.0.3",
                     "bundled": true,
                     "requires": {
-                        "function-bind": "^1.1.1"
+                        "function-bind": "1.1.1"
                     }
                 },
                 "has-flag": {
@@ -9988,7 +9988,7 @@
                     "version": "2.1.0",
                     "bundled": true,
                     "requires": {
-                        "agent-base": "4",
+                        "agent-base": "4.3.0",
                         "debug": "3.1.0"
                     }
                 },
@@ -9996,31 +9996,31 @@
                     "version": "1.2.0",
                     "bundled": true,
                     "requires": {
-                        "assert-plus": "^1.0.0",
-                        "jsprim": "^1.2.2",
-                        "sshpk": "^1.7.0"
+                        "assert-plus": "1.0.0",
+                        "jsprim": "1.4.1",
+                        "sshpk": "1.14.2"
                     }
                 },
                 "https-proxy-agent": {
                     "version": "2.2.4",
                     "bundled": true,
                     "requires": {
-                        "agent-base": "^4.3.0",
-                        "debug": "^3.1.0"
+                        "agent-base": "4.3.0",
+                        "debug": "3.1.0"
                     }
                 },
                 "humanize-ms": {
                     "version": "1.2.1",
                     "bundled": true,
                     "requires": {
-                        "ms": "^2.0.0"
+                        "ms": "2.1.1"
                     }
                 },
                 "iconv-lite": {
                     "version": "0.4.23",
                     "bundled": true,
                     "requires": {
-                        "safer-buffer": ">= 2.1.2 < 3"
+                        "safer-buffer": "2.1.2"
                     }
                 },
                 "iferr": {
@@ -10031,7 +10031,7 @@
                     "version": "3.0.3",
                     "bundled": true,
                     "requires": {
-                        "minimatch": "^3.0.4"
+                        "minimatch": "3.0.4"
                     }
                 },
                 "import-lazy": {
@@ -10050,8 +10050,8 @@
                     "version": "1.0.6",
                     "bundled": true,
                     "requires": {
-                        "once": "^1.3.0",
-                        "wrappy": "1"
+                        "once": "1.4.0",
+                        "wrappy": "1.0.2"
                     }
                 },
                 "inherits": {
@@ -10066,18 +10066,18 @@
                     "version": "1.10.3",
                     "bundled": true,
                     "requires": {
-                        "glob": "^7.1.1",
-                        "npm-package-arg": "^4.0.0 || ^5.0.0 || ^6.0.0",
-                        "promzard": "^0.3.0",
-                        "read": "~1.0.1",
-                        "read-package-json": "1 || 2",
-                        "semver": "2.x || 3.x || 4 || 5",
-                        "validate-npm-package-license": "^3.0.1",
-                        "validate-npm-package-name": "^3.0.0"
+                        "glob": "7.1.4",
+                        "npm-package-arg": "6.1.1",
+                        "promzard": "0.3.0",
+                        "read": "1.0.7",
+                        "read-package-json": "2.1.1",
+                        "semver": "5.7.1",
+                        "validate-npm-package-license": "3.0.4",
+                        "validate-npm-package-name": "3.0.0"
                     }
                 },
                 "invert-kv": {
-                    "version": "1.0.0",
+                    "version": "2.0.0",
                     "bundled": true
                 },
                 "ip": {
@@ -10096,7 +10096,7 @@
                     "version": "1.1.0",
                     "bundled": true,
                     "requires": {
-                        "ci-info": "^1.0.0"
+                        "ci-info": "1.6.0"
                     },
                     "dependencies": {
                         "ci-info": {
@@ -10109,7 +10109,7 @@
                     "version": "3.0.0",
                     "bundled": true,
                     "requires": {
-                        "cidr-regex": "^2.0.10"
+                        "cidr-regex": "2.0.10"
                     }
                 },
                 "is-date-object": {
@@ -10120,15 +10120,15 @@
                     "version": "1.0.0",
                     "bundled": true,
                     "requires": {
-                        "number-is-nan": "^1.0.0"
+                        "number-is-nan": "1.0.1"
                     }
                 },
                 "is-installed-globally": {
                     "version": "0.1.0",
                     "bundled": true,
                     "requires": {
-                        "global-dirs": "^0.1.0",
-                        "is-path-inside": "^1.0.0"
+                        "global-dirs": "0.1.1",
+                        "is-path-inside": "1.0.1"
                     }
                 },
                 "is-npm": {
@@ -10143,7 +10143,7 @@
                     "version": "1.0.1",
                     "bundled": true,
                     "requires": {
-                        "path-is-inside": "^1.0.1"
+                        "path-is-inside": "1.0.2"
                     }
                 },
                 "is-redirect": {
@@ -10154,7 +10154,7 @@
                     "version": "1.0.4",
                     "bundled": true,
                     "requires": {
-                        "has": "^1.0.1"
+                        "has": "1.0.3"
                     }
                 },
                 "is-retry-allowed": {
@@ -10169,7 +10169,7 @@
                     "version": "1.0.2",
                     "bundled": true,
                     "requires": {
-                        "has-symbols": "^1.0.0"
+                        "has-symbols": "1.0.0"
                     }
                 },
                 "is-typedarray": {
@@ -10227,7 +10227,7 @@
                     "version": "3.1.0",
                     "bundled": true,
                     "requires": {
-                        "package-json": "^4.0.0"
+                        "package-json": "4.0.1"
                     }
                 },
                 "lazy-property": {
@@ -10235,105 +10235,105 @@
                     "bundled": true
                 },
                 "lcid": {
-                    "version": "1.0.0",
+                    "version": "2.0.0",
                     "bundled": true,
                     "requires": {
-                        "invert-kv": "^1.0.0"
+                        "invert-kv": "2.0.0"
                     }
                 },
                 "libcipm": {
                     "version": "4.0.7",
                     "bundled": true,
                     "requires": {
-                        "bin-links": "^1.1.2",
-                        "bluebird": "^3.5.1",
-                        "figgy-pudding": "^3.5.1",
-                        "find-npm-prefix": "^1.0.2",
-                        "graceful-fs": "^4.1.11",
-                        "ini": "^1.3.5",
-                        "lock-verify": "^2.0.2",
-                        "mkdirp": "^0.5.1",
-                        "npm-lifecycle": "^3.0.0",
-                        "npm-logical-tree": "^1.2.1",
-                        "npm-package-arg": "^6.1.0",
-                        "pacote": "^9.1.0",
-                        "read-package-json": "^2.0.13",
-                        "rimraf": "^2.6.2",
-                        "worker-farm": "^1.6.0"
+                        "bin-links": "1.1.7",
+                        "bluebird": "3.5.5",
+                        "figgy-pudding": "3.5.1",
+                        "find-npm-prefix": "1.0.2",
+                        "graceful-fs": "4.2.3",
+                        "ini": "1.3.5",
+                        "lock-verify": "2.1.0",
+                        "mkdirp": "0.5.1",
+                        "npm-lifecycle": "3.1.4",
+                        "npm-logical-tree": "1.2.1",
+                        "npm-package-arg": "6.1.1",
+                        "pacote": "9.5.12",
+                        "read-package-json": "2.1.1",
+                        "rimraf": "2.6.3",
+                        "worker-farm": "1.7.0"
                     }
                 },
                 "libnpm": {
                     "version": "3.0.1",
                     "bundled": true,
                     "requires": {
-                        "bin-links": "^1.1.2",
-                        "bluebird": "^3.5.3",
-                        "find-npm-prefix": "^1.0.2",
-                        "libnpmaccess": "^3.0.2",
-                        "libnpmconfig": "^1.2.1",
-                        "libnpmhook": "^5.0.3",
-                        "libnpmorg": "^1.0.1",
-                        "libnpmpublish": "^1.1.2",
-                        "libnpmsearch": "^2.0.2",
-                        "libnpmteam": "^1.0.2",
-                        "lock-verify": "^2.0.2",
-                        "npm-lifecycle": "^3.0.0",
-                        "npm-logical-tree": "^1.2.1",
-                        "npm-package-arg": "^6.1.0",
-                        "npm-profile": "^4.0.2",
-                        "npm-registry-fetch": "^4.0.0",
-                        "npmlog": "^4.1.2",
-                        "pacote": "^9.5.3",
-                        "read-package-json": "^2.0.13",
-                        "stringify-package": "^1.0.0"
+                        "bin-links": "1.1.7",
+                        "bluebird": "3.5.5",
+                        "find-npm-prefix": "1.0.2",
+                        "libnpmaccess": "3.0.2",
+                        "libnpmconfig": "1.2.1",
+                        "libnpmhook": "5.0.3",
+                        "libnpmorg": "1.0.1",
+                        "libnpmpublish": "1.1.2",
+                        "libnpmsearch": "2.0.2",
+                        "libnpmteam": "1.0.2",
+                        "lock-verify": "2.1.0",
+                        "npm-lifecycle": "3.1.4",
+                        "npm-logical-tree": "1.2.1",
+                        "npm-package-arg": "6.1.1",
+                        "npm-profile": "4.0.2",
+                        "npm-registry-fetch": "4.0.2",
+                        "npmlog": "4.1.2",
+                        "pacote": "9.5.12",
+                        "read-package-json": "2.1.1",
+                        "stringify-package": "1.0.1"
                     }
                 },
                 "libnpmaccess": {
                     "version": "3.0.2",
                     "bundled": true,
                     "requires": {
-                        "aproba": "^2.0.0",
-                        "get-stream": "^4.0.0",
-                        "npm-package-arg": "^6.1.0",
-                        "npm-registry-fetch": "^4.0.0"
+                        "aproba": "2.0.0",
+                        "get-stream": "4.1.0",
+                        "npm-package-arg": "6.1.1",
+                        "npm-registry-fetch": "4.0.2"
                     }
                 },
                 "libnpmconfig": {
                     "version": "1.2.1",
                     "bundled": true,
                     "requires": {
-                        "figgy-pudding": "^3.5.1",
-                        "find-up": "^3.0.0",
-                        "ini": "^1.3.5"
+                        "figgy-pudding": "3.5.1",
+                        "find-up": "3.0.0",
+                        "ini": "1.3.5"
                     },
                     "dependencies": {
                         "find-up": {
                             "version": "3.0.0",
                             "bundled": true,
                             "requires": {
-                                "locate-path": "^3.0.0"
+                                "locate-path": "3.0.0"
                             }
                         },
                         "locate-path": {
                             "version": "3.0.0",
                             "bundled": true,
                             "requires": {
-                                "p-locate": "^3.0.0",
-                                "path-exists": "^3.0.0"
+                                "p-locate": "3.0.0",
+                                "path-exists": "3.0.0"
                             }
                         },
                         "p-limit": {
                             "version": "2.2.0",
                             "bundled": true,
                             "requires": {
-                                "p-try": "^2.0.0"
+                                "p-try": "2.2.0"
                             }
                         },
                         "p-locate": {
                             "version": "3.0.0",
                             "bundled": true,
                             "requires": {
-                                "p-limit": "^2.0.0"
+                                "p-limit": "2.2.0"
                             }
                         },
                         "p-try": {
@@ -10346,91 +10346,91 @@
                     "version": "5.0.3",
                     "bundled": true,
                     "requires": {
-                        "aproba": "^2.0.0",
-                        "figgy-pudding": "^3.4.1",
-                        "get-stream": "^4.0.0",
-                        "npm-registry-fetch": "^4.0.0"
+                        "aproba": "2.0.0",
+                        "figgy-pudding": "3.5.1",
+                        "get-stream": "4.1.0",
+                        "npm-registry-fetch": "4.0.2"
                     }
                 },
                 "libnpmorg": {
                     "version": "1.0.1",
                     "bundled": true,
                     "requires": {
-                        "aproba": "^2.0.0",
-                        "figgy-pudding": "^3.4.1",
-                        "get-stream": "^4.0.0",
-                        "npm-registry-fetch": "^4.0.0"
+                        "aproba": "2.0.0",
+                        "figgy-pudding": "3.5.1",
+                        "get-stream": "4.1.0",
+                        "npm-registry-fetch": "4.0.2"
                     }
                 },
                 "libnpmpublish": {
                     "version": "1.1.2",
                     "bundled": true,
                     "requires": {
-                        "aproba": "^2.0.0",
-                        "figgy-pudding": "^3.5.1",
-                        "get-stream": "^4.0.0",
-                        "lodash.clonedeep": "^4.5.0",
-                        "normalize-package-data": "^2.4.0",
-                        "npm-package-arg": "^6.1.0",
-                        "npm-registry-fetch": "^4.0.0",
-                        "semver": "^5.5.1",
-                        "ssri": "^6.0.1"
+                        "aproba": "2.0.0",
+                        "figgy-pudding": "3.5.1",
+                        "get-stream": "4.1.0",
+                        "lodash.clonedeep": "4.5.0",
+                        "normalize-package-data": "2.5.0",
+                        "npm-package-arg": "6.1.1",
+                        "npm-registry-fetch": "4.0.2",
+                        "semver": "5.7.1",
+                        "ssri": "6.0.1"
                     }
                 },
                 "libnpmsearch": {
                     "version": "2.0.2",
                     "bundled": true,
                     "requires": {
-                        "figgy-pudding": "^3.5.1",
-                        "get-stream": "^4.0.0",
-                        "npm-registry-fetch": "^4.0.0"
+                        "figgy-pudding": "3.5.1",
+                        "get-stream": "4.1.0",
+                        "npm-registry-fetch": "4.0.2"
                     }
                 },
                 "libnpmteam": {
                     "version": "1.0.2",
                     "bundled": true,
                     "requires": {
-                        "aproba": "^2.0.0",
-                        "figgy-pudding": "^3.4.1",
-                        "get-stream": "^4.0.0",
-                        "npm-registry-fetch": "^4.0.0"
+                        "aproba": "2.0.0",
+                        "figgy-pudding": "3.5.1",
+                        "get-stream": "4.1.0",
+                        "npm-registry-fetch": "4.0.2"
                     }
                 },
                 "libnpx": {
-                    "version": "10.2.0",
+                    "version": "10.2.2",
                     "bundled": true,
                     "requires": {
-                        "dotenv": "^5.0.1",
-                        "npm-package-arg": "^6.0.0",
-                        "rimraf": "^2.6.2",
-                        "safe-buffer": "^5.1.0",
-                        "update-notifier": "^2.3.0",
-                        "which": "^1.3.0",
-                        "y18n": "^4.0.0",
-                        "yargs": "^11.0.0"
+                        "dotenv": "5.0.1",
+                        "npm-package-arg": "6.1.1",
+                        "rimraf": "2.6.3",
+                        "safe-buffer": "5.1.2",
+                        "update-notifier": "2.5.0",
+                        "which": "1.3.1",
+                        "y18n": "4.0.0",
+                        "yargs": "11.1.1"
                     }
                 },
                 "locate-path": {
                     "version": "2.0.0",
                     "bundled": true,
                     "requires": {
-                        "p-locate": "^2.0.0",
-                        "path-exists": "^3.0.0"
+                        "p-locate": "2.0.0",
+                        "path-exists": "3.0.0"
                     }
                 },
                 "lock-verify": {
                     "version": "2.1.0",
                     "bundled": true,
                     "requires": {
-                        "npm-package-arg": "^6.1.0",
-                        "semver": "^5.4.1"
+                        "npm-package-arg": "6.1.1",
+                        "semver": "5.7.1"
                     }
                 },
                 "lockfile": {
                     "version": "1.0.4",
                     "bundled": true,
                     "requires": {
-                        "signal-exit": "^3.0.2"
+                        "signal-exit": "3.0.2"
                     }
                 },
                 "lodash._baseindexof": {
@@ -10441,8 +10441,8 @@
                     "version": "4.6.0",
                     "bundled": true,
                     "requires": {
-                        "lodash._createset": "~4.0.0",
-                        "lodash._root": "~3.0.0"
+                        "lodash._createset": "4.0.3",
+                        "lodash._root": "3.0.1"
                     }
                 },
                 "lodash._bindcallback": {
@@ -10457,7 +10457,7 @@
                     "version": "3.1.2",
                     "bundled": true,
                     "requires": {
-                        "lodash._getnative": "^3.0.0"
+                        "lodash._getnative": "3.9.1"
                     }
                 },
                 "lodash._createset": {
@@ -10500,31 +10500,38 @@
                     "version": "5.1.1",
                     "bundled": true,
                     "requires": {
-                        "yallist": "^3.0.2"
+                        "yallist": "3.0.3"
                     }
                 },
                 "make-dir": {
                     "version": "1.3.0",
                     "bundled": true,
                     "requires": {
-                        "pify": "^3.0.0"
+                        "pify": "3.0.0"
                     }
                 },
                 "make-fetch-happen": {
                     "version": "5.0.2",
                     "bundled": true,
                     "requires": {
-                        "agentkeepalive": "^3.4.1",
-                        "cacache": "^12.0.0",
-                        "http-cache-semantics": "^3.8.1",
-                        "http-proxy-agent": "^2.1.0",
-                        "https-proxy-agent": "^2.2.3",
-                        "lru-cache": "^5.1.1",
-                        "mississippi": "^3.0.0",
-                        "node-fetch-npm": "^2.0.2",
-                        "promise-retry": "^1.1.1",
-                        "socks-proxy-agent": "^4.0.0",
-                        "ssri": "^6.0.0"
+                        "agentkeepalive": "3.5.2",
+                        "cacache": "12.0.3",
+                        "http-cache-semantics": "3.8.1",
+                        "http-proxy-agent": "2.1.0",
+                        "https-proxy-agent": "2.2.4",
+                        "lru-cache": "5.1.1",
+                        "mississippi": "3.0.0",
+                        "node-fetch-npm": "2.0.2",
+                        "promise-retry": "1.1.1",
+                        "socks-proxy-agent": "4.0.2",
+                        "ssri": "6.0.1"
+                    }
+                },
+                "map-age-cleaner": {
+                    "version": "0.1.3",
+                    "bundled": true,
+                    "requires": {
+                        "p-defer": "1.0.0"
                     }
                 },
                 "meant": {
@@ -10532,10 +10539,18 @@
                     "bundled": true
                 },
                 "mem": {
-                    "version": "1.1.0",
+                    "version": "4.3.0",
                     "bundled": true,
                     "requires": {
-                        "mimic-fn": "^1.0.0"
+                        "map-age-cleaner": "0.1.3",
+                        "mimic-fn": "2.1.0",
+                        "p-is-promise": "2.1.0"
+                    },
+                    "dependencies": {
+                        "mimic-fn": {
+                            "version": "2.1.0",
+                            "bundled": true
+                        }
                     }
                 },
                 "mime-db": {
@@ -10546,18 +10561,14 @@
                     "version": "2.1.19",
                     "bundled": true,
                     "requires": {
-                        "mime-db": "~1.35.0"
+                        "mime-db": "1.35.0"
                     }
                 },
-                "mimic-fn": {
-                    "version": "1.2.0",
-                    "bundled": true
-                },
                 "minimatch": {
                     "version": "3.0.4",
                     "bundled": true,
                     "requires": {
-                        "brace-expansion": "^1.1.7"
+                        "brace-expansion": "1.1.11"
                     }
                 },
                 "minimist": {
@@ -10568,15 +10579,15 @@
                     "version": "1.3.3",
                     "bundled": true,
                     "requires": {
-                        "minipass": "^2.9.0"
+                        "minipass": "2.9.0"
                     },
                     "dependencies": {
                         "minipass": {
                             "version": "2.9.0",
                             "bundled": true,
                             "requires": {
-                                "safe-buffer": "^5.1.2",
-                                "yallist": "^3.0.0"
+                                "safe-buffer": "5.1.2",
+                                "yallist": "3.0.3"
                             }
                         }
                     }
@@ -10585,16 +10596,16 @@
                     "version": "3.0.0",
                     "bundled": true,
                     "requires": {
-                        "concat-stream": "^1.5.0",
-                        "duplexify": "^3.4.2",
-                        "end-of-stream": "^1.1.0",
-                        "flush-write-stream": "^1.0.0",
-                        "from2": "^2.1.0",
-                        "parallel-transform": "^1.1.0",
-                        "pump": "^3.0.0",
-                        "pumpify": "^1.3.3",
-                        "stream-each": "^1.1.0",
-                        "through2": "^2.0.0"
+                        "concat-stream": "1.6.2",
+                        "duplexify": "3.6.0",
+                        "end-of-stream": "1.4.1",
+                        "flush-write-stream": "1.0.3",
+                        "from2": "2.3.0",
+                        "parallel-transform": "1.1.0",
+                        "pump": "3.0.0",
+                        "pumpify": "1.5.1",
+                        "stream-each": "1.2.2",
+                        "through2": "2.0.3"
                     }
                 },
                 "mkdirp": {
@@ -10608,12 +10619,12 @@
                     "version": "1.0.1",
                     "bundled": true,
                     "requires": {
-                        "aproba": "^1.1.1",
-                        "copy-concurrently": "^1.0.0",
-                        "fs-write-stream-atomic": "^1.0.8",
-                        "mkdirp": "^0.5.1",
-                        "rimraf": "^2.5.4",
-                        "run-queue": "^1.0.3"
+                        "aproba": "1.2.0",
+                        "copy-concurrently": "1.0.5",
+                        "fs-write-stream-atomic": "1.0.10",
+                        "mkdirp": "0.5.1",
+                        "rimraf": "2.6.3",
+                        "run-queue": "1.0.3"
                     },
                     "dependencies": {
                         "aproba": {
@@ -10630,68 +10641,59 @@
                     "version": "0.0.7",
                     "bundled": true
                 },
+                "nice-try": {
+                    "version": "1.0.5",
+                    "bundled": true
+                },
                 "node-fetch-npm": {
                     "version": "2.0.2",
                     "bundled": true,
                     "requires": {
-                        "encoding": "^0.1.11",
-                        "json-parse-better-errors": "^1.0.0",
-                        "safe-buffer": "^5.1.1"
+                        "encoding": "0.1.12",
+                        "json-parse-better-errors": "1.0.2",
+                        "safe-buffer": "5.1.2"
                     }
                 },
                 "node-gyp": {
-                    "version": "5.0.5",
+                    "version": "5.0.7",
                     "bundled": true,
                     "requires": {
-                        "env-paths": "^1.0.0",
-                        "glob": "^7.0.3",
-                        "graceful-fs": "^4.1.2",
-                        "mkdirp": "^0.5.0",
-                        "nopt": "2 || 3",
-                        "npmlog": "0 || 1 || 2 || 3 || 4",
-                        "request": "^2.87.0",
-                        "rimraf": "2",
-                        "semver": "~5.3.0",
-                        "tar": "^4.4.12",
-                        "which": "1"
-                    },
-                    "dependencies": {
-                        "nopt": {
-                            "version": "3.0.6",
-                            "bundled": true,
-                            "requires": {
-                                "abbrev": "1"
-                            }
-                        },
-                        "semver": {
-                            "version": "5.3.0",
-                            "bundled": true
-                        }
+                        "env-paths": "2.2.0",
+                        "glob": "7.1.4",
+                        "graceful-fs": "4.2.3",
+                        "mkdirp": "0.5.1",
+                        "nopt": "4.0.1",
+                        "npmlog": "4.1.2",
+                        "request": "2.88.0",
+                        "rimraf": "2.6.3",
+                        "semver": "5.7.1",
+                        "tar": "4.4.13",
+                        "which": "1.3.1"
                     }
                 },
                 "nopt": {
                     "version": "4.0.1",
                     "bundled": true,
                     "requires": {
-                        "abbrev": "1",
-                        "osenv": "^0.1.4"
+                        "abbrev": "1.1.1",
+                        "osenv": "0.1.5"
                     }
                 },
                 "normalize-package-data": {
                     "version": "2.5.0",
                     "bundled": true,
                     "requires": {
-                        "hosted-git-info": "^2.1.4",
-                        "resolve": "^1.10.0",
-                        "semver": "2 || 3 || 4 || 5",
-                        "validate-npm-package-license": "^3.0.1"
+                        "hosted-git-info": "2.8.5",
+                        "resolve": "1.10.0",
+                        "semver": "5.7.1",
+                        "validate-npm-package-license": "3.0.4"
                     },
                     "dependencies": {
                         "resolve": {
                             "version": "1.10.0",
                             "bundled": true,
                             "requires": {
-                                "path-parse": "^1.0.6"
+                                "path-parse": "1.0.6"
                             }
                         }
                     }
@@ -10700,15 +10702,15 @@
                     "version": "1.3.2",
                     "bundled": true,
                     "requires": {
-                        "cli-table3": "^0.5.0",
-                        "console-control-strings": "^1.1.0"
+                        "cli-table3": "0.5.1",
+                        "console-control-strings": "1.1.0"
                     }
                 },
                 "npm-bundled": {
                     "version": "1.1.1",
                     "bundled": true,
                     "requires": {
-                        "npm-normalize-package-bin": "^1.0.1"
+                        "npm-normalize-package-bin": "1.0.1"
                     }
                 },
                 "npm-cache-filename": {
@@ -10719,21 +10721,21 @@
                     "version": "3.0.2",
                     "bundled": true,
                     "requires": {
-                        "semver": "^2.3.0 || 3.x || 4 || 5"
+                        "semver": "5.7.1"
                     }
                 },
                 "npm-lifecycle": {
                     "version": "3.1.4",
                     "bundled": true,
                     "requires": {
-                        "byline": "^5.0.0",
-                        "graceful-fs": "^4.1.15",
-                        "node-gyp": "^5.0.2",
-                        "resolve-from": "^4.0.0",
-                        "slide": "^1.1.6",
+                        "byline": "5.0.0",
+                        "graceful-fs": "4.2.3",
+                        "node-gyp": "5.0.7",
+                        "resolve-from": "4.0.0",
+                        "slide": "1.1.6",
                         "uid-number": "0.0.6",
-                        "umask": "^1.1.0",
-                        "which": "^1.3.1"
+                        "umask": "1.1.0",
+                        "which": "1.3.1"
                     }
                 },
                 "npm-logical-tree": {
@@ -10748,49 +10750,49 @@
                     "version": "6.1.1",
                     "bundled": true,
                     "requires": {
-                        "hosted-git-info": "^2.7.1",
-                        "osenv": "^0.1.5",
-                        "semver": "^5.6.0",
-                        "validate-npm-package-name": "^3.0.0"
+                        "hosted-git-info": "2.8.5",
+                        "osenv": "0.1.5",
+                        "semver": "5.7.1",
+                        "validate-npm-package-name": "3.0.0"
                     }
                 },
                 "npm-packlist": {
                     "version": "1.4.7",
                     "bundled": true,
                     "requires": {
-                        "ignore-walk": "^3.0.1",
-                        "npm-bundled": "^1.0.1"
+                        "ignore-walk": "3.0.3",
+                        "npm-bundled": "1.1.1"
                     }
                 },
                 "npm-pick-manifest": {
                     "version": "3.0.2",
                     "bundled": true,
                     "requires": {
-                        "figgy-pudding": "^3.5.1",
-                        "npm-package-arg": "^6.0.0",
-                        "semver": "^5.4.1"
+                        "figgy-pudding": "3.5.1",
+                        "npm-package-arg": "6.1.1",
+                        "semver": "5.7.1"
                     }
                 },
                 "npm-profile": {
                     "version": "4.0.2",
                     "bundled": true,
                     "requires": {
-                        "aproba": "^1.1.2 || 2",
-                        "figgy-pudding": "^3.4.1",
-                        "npm-registry-fetch": "^4.0.0"
+                        "aproba": "2.0.0",
+                        "figgy-pudding": "3.5.1",
+                        "npm-registry-fetch": "4.0.2"
                     }
                 },
                 "npm-registry-fetch": {
                     "version": "4.0.2",
                     "bundled": true,
                     "requires": {
-                        "JSONStream": "^1.3.4",
-                        "bluebird": "^3.5.1",
-                        "figgy-pudding": "^3.4.1",
-                        "lru-cache": "^5.1.1",
-                        "make-fetch-happen": "^5.0.0",
-                        "npm-package-arg": "^6.1.0",
-                        "safe-buffer": "^5.2.0"
+                        "JSONStream": "1.3.5",
+                        "bluebird": "3.5.5",
+                        "figgy-pudding": "3.5.1",
+                        "lru-cache": "5.1.1",
+                        "make-fetch-happen": "5.0.2",
+                        "npm-package-arg": "6.1.1",
+                        "safe-buffer": "5.2.0"
                     },
                     "dependencies": {
                         "safe-buffer": {
@@ -10803,7 +10805,7 @@
                     "version": "2.0.2",
                     "bundled": true,
                     "requires": {
-                        "path-key": "^2.0.0"
+                        "path-key": "2.0.1"
                     }
                 },
                 "npm-user-validate": {
@@ -10814,10 +10816,10 @@
                     "version": "4.1.2",
                     "bundled": true,
                     "requires": {
-                        "are-we-there-yet": "~1.1.2",
-                        "console-control-strings": "~1.1.0",
-                        "gauge": "~2.7.3",
-                        "set-blocking": "~2.0.0"
+                        "are-we-there-yet": "1.1.4",
+                        "console-control-strings": "1.1.0",
+                        "gauge": "2.7.4",
+                        "set-blocking": "2.0.0"
                     }
                 },
                 "number-is-nan": {
@@ -10840,15 +10842,15 @@
                     "version": "2.0.3",
                     "bundled": true,
                     "requires": {
-                        "define-properties": "^1.1.2",
-                        "es-abstract": "^1.5.1"
+                        "define-properties": "1.1.3",
+                        "es-abstract": "1.12.0"
                     }
                 },
                 "once": {
                     "version": "1.4.0",
                     "bundled": true,
                     "requires": {
-                        "wrappy": "1"
+                        "wrappy": "1.0.2"
                     }
                 },
                 "opener": {
@@ -10860,12 +10862,38 @@
                     "bundled": true
                 },
                 "os-locale": {
-                    "version": "2.1.0",
+                    "version": "3.1.0",
                     "bundled": true,
                     "requires": {
-                        "execa": "^0.7.0",
-                        "lcid": "^1.0.0",
-                        "mem": "^1.1.0"
+                        "execa": "1.0.0",
+                        "lcid": "2.0.0",
+                        "mem": "4.3.0"
+                    },
+                    "dependencies": {
+                        "cross-spawn": {
+                            "version": "6.0.5",
+                            "bundled": true,
+                            "requires": {
+                                "nice-try": "1.0.5",
+                                "path-key": "2.0.1",
+                                "semver": "5.7.1",
+                                "shebang-command": "1.2.0",
+                                "which": "1.3.1"
+                            }
+                        },
+                        "execa": {
+                            "version": "1.0.0",
+                            "bundled": true,
+                            "requires": {
+                                "cross-spawn": "6.0.5",
+                                "get-stream": "4.1.0",
+                                "is-stream": "1.1.0",
+                                "npm-run-path": "2.0.2",
+                                "p-finally": "1.0.0",
+                                "signal-exit": "3.0.2",
+                                "strip-eof": "1.0.0"
+                            }
+                        }
                     }
                 },
                 "os-tmpdir": {
@@ -10876,26 +10904,34 @@
                     "version": "0.1.5",
                     "bundled": true,
                     "requires": {
-                        "os-homedir": "^1.0.0",
-                        "os-tmpdir": "^1.0.0"
+                        "os-homedir": "1.0.2",
+                        "os-tmpdir": "1.0.2"
                     }
                 },
+                "p-defer": {
+                    "version": "1.0.0",
+                    "bundled": true
+                },
                 "p-finally": {
                     "version": "1.0.0",
                     "bundled": true
                 },
+                "p-is-promise": {
+                    "version": "2.1.0",
+                    "bundled": true
+                },
                 "p-limit": {
                     "version": "1.2.0",
                     "bundled": true,
                     "requires": {
-                        "p-try": "^1.0.0"
+                        "p-try": "1.0.0"
                     }
                 },
                 "p-locate": {
                     "version": "2.0.0",
                     "bundled": true,
                     "requires": {
-                        "p-limit": "^1.1.0"
+                        "p-limit": "1.2.0"
                     }
                 },
                 "p-try": {
@@ -10906,54 +10942,54 @@
                     "version": "4.0.1",
                     "bundled": true,
                     "requires": {
-                        "got": "^6.7.1",
-                        "registry-auth-token": "^3.0.1",
-                        "registry-url": "^3.0.3",
-                        "semver": "^5.1.0"
+                        "got": "6.7.1",
+                        "registry-auth-token": "3.3.2",
+                        "registry-url": "3.1.0",
+                        "semver": "5.7.1"
                     }
                 },
                 "pacote": {
                     "version": "9.5.12",
                     "bundled": true,
                     "requires": {
-                        "bluebird": "^3.5.3",
-                        "cacache": "^12.0.2",
-                        "chownr": "^1.1.2",
-                        "figgy-pudding": "^3.5.1",
-                        "get-stream": "^4.1.0",
-                        "glob": "^7.1.3",
-                        "infer-owner": "^1.0.4",
-                        "lru-cache": "^5.1.1",
-                        "make-fetch-happen": "^5.0.0",
-                        "minimatch": "^3.0.4",
-                        "minipass": "^2.3.5",
-                        "mississippi": "^3.0.0",
-                        "mkdirp": "^0.5.1",
-                        "normalize-package-data": "^2.4.0",
-                        "npm-normalize-package-bin": "^1.0.0",
-                        "npm-package-arg": "^6.1.0",
-                        "npm-packlist": "^1.1.12",
-                        "npm-pick-manifest": "^3.0.0",
-                        "npm-registry-fetch": "^4.0.0",
-                        "osenv": "^0.1.5",
-                        "promise-inflight": "^1.0.1",
-                        "promise-retry": "^1.1.1",
-                        "protoduck": "^5.0.1",
-                        "rimraf": "^2.6.2",
-                        "safe-buffer": "^5.1.2",
-                        "semver": "^5.6.0",
-                        "ssri": "^6.0.1",
-                        "tar": "^4.4.10",
-                        "unique-filename": "^1.1.1",
-                        "which": "^1.3.1"
+                        "bluebird": "3.5.5",
+                        "cacache": "12.0.3",
+                        "chownr": "1.1.3",
+                        "figgy-pudding": "3.5.1",
+                        "get-stream": "4.1.0",
+                        "glob": "7.1.4",
+                        "infer-owner": "1.0.4",
+                        "lru-cache": "5.1.1",
+                        "make-fetch-happen": "5.0.2",
+                        "minimatch": "3.0.4",
+                        "minipass": "2.9.0",
+                        "mississippi": "3.0.0",
+                        "mkdirp": "0.5.1",
+                        "normalize-package-data": "2.5.0",
+                        "npm-normalize-package-bin": "1.0.1",
+                        "npm-package-arg": "6.1.1",
+                        "npm-packlist": "1.4.7",
+                        "npm-pick-manifest": "3.0.2",
+                        "npm-registry-fetch": "4.0.2",
+                        "osenv": "0.1.5",
+                        "promise-inflight": "1.0.1",
+                        "promise-retry": "1.1.1",
+                        "protoduck": "5.0.1",
+                        "rimraf": "2.6.3",
+                        "safe-buffer": "5.1.2",
+                        "semver": "5.7.1",
+                        "ssri": "6.0.1",
+                        "tar": "4.4.13",
+                        "unique-filename": "1.1.1",
+                        "which": "1.3.1"
                     },
                     "dependencies": {
                         "minipass": {
                             "version": "2.9.0",
                             "bundled": true,
                             "requires": {
-                                "safe-buffer": "^5.1.2",
-                                "yallist": "^3.0.0"
+                                "safe-buffer": "5.1.2",
+                                "yallist": "3.0.3"
                             }
                         }
                     }
@@ -10962,29 +10998,29 @@
                     "version": "1.1.0",
                     "bundled": true,
                     "requires": {
-                        "cyclist": "~0.2.2",
-                        "inherits": "^2.0.3",
-                        "readable-stream": "^2.1.5"
+                        "cyclist": "0.2.2",
+                        "inherits": "2.0.4",
+                        "readable-stream": "2.3.6"
                     },
                     "dependencies": {
                         "readable-stream": {
                             "version": "2.3.6",
                             "bundled": true,
                             "requires": {
-                                "core-util-is": "~1.0.0",
-                                "inherits": "~2.0.3",
-                                "isarray": "~1.0.0",
-                                "process-nextick-args": "~2.0.0",
-                                "safe-buffer": "~5.1.1",
-                                "string_decoder": "~1.1.1",
-                                "util-deprecate": "~1.0.1"
+                                "core-util-is": "1.0.2",
+                                "inherits": "2.0.4",
+                                "isarray": "1.0.0",
+                                "process-nextick-args": "2.0.0",
+                                "safe-buffer": "5.1.2",
+                                "string_decoder": "1.1.1",
+                                "util-deprecate": "1.0.2"
                             }
                         },
                         "string_decoder": {
                             "version": "1.1.1",
                             "bundled": true,
                             "requires": {
-                                "safe-buffer": "~5.1.0"
+                                "safe-buffer": "5.1.2"
                             }
                         }
                     }
@@ -11033,8 +11069,8 @@
                     "version": "1.1.1",
                     "bundled": true,
                     "requires": {
-                        "err-code": "^1.0.0",
-                        "retry": "^0.10.0"
+                        "err-code": "1.1.2",
+                        "retry": "0.10.1"
                     },
                     "dependencies": {
                         "retry": {
@@ -11047,7 +11083,7 @@
                     "version": "0.3.0",
                     "bundled": true,
                     "requires": {
-                        "read": "1"
+                        "read": "1.0.7"
                     }
                 },
                 "proto-list": {
@@ -11058,7 +11094,7 @@
                     "version": "5.0.1",
                     "bundled": true,
                     "requires": {
-                        "genfun": "^5.0.0"
+                        "genfun": "5.0.0"
                     }
                 },
                 "prr": {
@@ -11077,25 +11113,25 @@
                     "version": "3.0.0",
                     "bundled": true,
                     "requires": {
-                        "end-of-stream": "^1.1.0",
-                        "once": "^1.3.1"
+                        "end-of-stream": "1.4.1",
+                        "once": "1.4.0"
                     }
                 },
                 "pumpify": {
                     "version": "1.5.1",
                     "bundled": true,
                     "requires": {
-                        "duplexify": "^3.6.0",
-                        "inherits": "^2.0.3",
-                        "pump": "^2.0.0"
+                        "duplexify": "3.6.0",
+                        "inherits": "2.0.4",
+                        "pump": "2.0.1"
                     },
                     "dependencies": {
                         "pump": {
                             "version": "2.0.1",
                             "bundled": true,
                             "requires": {
-                                "end-of-stream": "^1.1.0",
-                                "once": "^1.3.1"
+                                "end-of-stream": "1.4.1",
+                                "once": "1.4.0"
                             }
                         }
                     }
@@ -11116,9 +11152,9 @@
                     "version": "6.8.2",
                     "bundled": true,
                     "requires": {
-                        "decode-uri-component": "^0.2.0",
-                        "split-on-first": "^1.0.0",
-                        "strict-uri-encode": "^2.0.0"
+                        "decode-uri-component": "0.2.0",
+                        "split-on-first": "1.1.0",
+                        "strict-uri-encode": "2.0.0"
                     }
                 },
                 "qw": {
@@ -11129,10 +11165,10 @@
                     "version": "1.2.7",
                     "bundled": true,
                     "requires": {
-                        "deep-extend": "^0.5.1",
-                        "ini": "~1.3.0",
-                        "minimist": "^1.2.0",
-                        "strip-json-comments": "~2.0.1"
+                        "deep-extend": "0.5.1",
+                        "ini": "1.3.5",
+                        "minimist": "1.2.0",
+                        "strip-json-comments": "2.0.1"
                     },
                     "dependencies": {
                         "minimist": {
@@ -11145,107 +11181,107 @@
                     "version": "1.0.7",
                     "bundled": true,
                     "requires": {
-                        "mute-stream": "~0.0.4"
+                        "mute-stream": "0.0.7"
                     }
                 },
                 "read-cmd-shim": {
                     "version": "1.0.5",
                     "bundled": true,
                     "requires": {
-                        "graceful-fs": "^4.1.2"
+                        "graceful-fs": "4.2.3"
                     }
                 },
                 "read-installed": {
                     "version": "4.0.3",
                     "bundled": true,
                     "requires": {
-                        "debuglog": "^1.0.1",
-                        "graceful-fs": "^4.1.2",
-                        "read-package-json": "^2.0.0",
-                        "readdir-scoped-modules": "^1.0.0",
-                        "semver": "2 || 3 || 4 || 5",
-                        "slide": "~1.1.3",
-                        "util-extend": "^1.0.1"
+                        "debuglog": "1.0.1",
+                        "graceful-fs": "4.2.3",
+                        "read-package-json": "2.1.1",
+                        "readdir-scoped-modules": "1.1.0",
+                        "semver": "5.7.1",
+                        "slide": "1.1.6",
+                        "util-extend": "1.0.3"
                     }
                 },
                 "read-package-json": {
                     "version": "2.1.1",
                     "bundled": true,
                     "requires": {
-                        "glob": "^7.1.1",
-                        "graceful-fs": "^4.1.2",
-                        "json-parse-better-errors": "^1.0.1",
-                        "normalize-package-data": "^2.0.0",
-                        "npm-normalize-package-bin": "^1.0.0"
+                        "glob": "7.1.4",
+                        "graceful-fs": "4.2.3",
+                        "json-parse-better-errors": "1.0.2",
+                        "normalize-package-data": "2.5.0",
+                        "npm-normalize-package-bin": "1.0.1"
                     }
                 },
                 "read-package-tree": {
                     "version": "5.3.1",
                     "bundled": true,
                     "requires": {
-                        "read-package-json": "^2.0.0",
-                        "readdir-scoped-modules": "^1.0.0",
-                        "util-promisify": "^2.1.0"
+                        "read-package-json": "2.1.1",
+                        "readdir-scoped-modules": "1.1.0",
+                        "util-promisify": "2.1.0"
                     }
                 },
                 "readable-stream": {
                     "version": "3.4.0",
                     "bundled": true,
                     "requires": {
-                        "inherits": "^2.0.3",
-                        "string_decoder": "^1.1.1",
-                        "util-deprecate": "^1.0.1"
+                        "inherits": "2.0.4",
+                        "string_decoder": "1.2.0",
+                        "util-deprecate": "1.0.2"
                     }
                 },
                 "readdir-scoped-modules": {
                     "version": "1.1.0",
                     "bundled": true,
                     "requires": {
-                        "debuglog": "^1.0.1",
-                        "dezalgo": "^1.0.0",
-                        "graceful-fs": "^4.1.2",
-                        "once": "^1.3.0"
+                        "debuglog": "1.0.1",
+                        "dezalgo": "1.0.3",
+                        "graceful-fs": "4.2.3",
+                        "once": "1.4.0"
                     }
                 },
                 "registry-auth-token": {
                     "version": "3.3.2",
                     "bundled": true,
                     "requires": {
-                        "rc": "^1.1.6",
-                        "safe-buffer": "^5.0.1"
+                        "rc": "1.2.7",
+                        "safe-buffer": "5.1.2"
                     }
                 },
                 "registry-url": {
                     "version": "3.1.0",
                     "bundled": true,
                     "requires": {
-                        "rc": "^1.0.1"
+                        "rc": "1.2.7"
                     }
                 },
                 "request": {
                     "version": "2.88.0",
                     "bundled": true,
                     "requires": {
-                        "aws-sign2": "~0.7.0",
-                        "aws4": "^1.8.0",
-                        "caseless": "~0.12.0",
-                        "combined-stream": "~1.0.6",
-                        "extend": "~3.0.2",
-                        "forever-agent": "~0.6.1",
-                        "form-data": "~2.3.2",
-                        "har-validator": "~5.1.0",
-                        "http-signature": "~1.2.0",
-                        "is-typedarray": "~1.0.0",
-                        "isstream": "~0.1.2",
-                        "json-stringify-safe": "~5.0.1",
-                        "mime-types": "~2.1.19",
-                        "oauth-sign": "~0.9.0",
-                        "performance-now": "^2.1.0",
-                        "qs": "~6.5.2",
-                        "safe-buffer": "^5.1.2",
-                        "tough-cookie": "~2.4.3",
-                        "tunnel-agent": "^0.6.0",
-                        "uuid": "^3.3.2"
+                        "aws-sign2": "0.7.0",
+                        "aws4": "1.8.0",
+                        "caseless": "0.12.0",
+                        "combined-stream": "1.0.6",
+                        "extend": "3.0.2",
+                        "forever-agent": "0.6.1",
+                        "form-data": "2.3.2",
+                        "har-validator": "5.1.0",
+                        "http-signature": "1.2.0",
+                        "is-typedarray": "1.0.0",
+                        "isstream": "0.1.2",
+                        "json-stringify-safe": "5.0.1",
+                        "mime-types": "2.1.19",
+                        "oauth-sign": "0.9.0",
+                        "performance-now": "2.1.0",
+                        "qs": "6.5.2",
+                        "safe-buffer": "5.1.2",
+                        "tough-cookie": "2.4.3",
+                        "tunnel-agent": "0.6.0",
+                        "uuid": "3.3.3"
                     }
                 },
                 "require-directory": {
@@ -11268,14 +11304,14 @@
                     "version": "2.6.3",
                     "bundled": true,
                     "requires": {
-                        "glob": "^7.1.3"
+                        "glob": "7.1.4"
                     }
                 },
                 "run-queue": {
                     "version": "1.0.3",
                     "bundled": true,
                     "requires": {
-                        "aproba": "^1.1.1"
+                        "aproba": "1.2.0"
                     },
                     "dependencies": {
                         "aproba": {
@@ -11300,7 +11336,7 @@
                     "version": "2.1.0",
                     "bundled": true,
                     "requires": {
-                        "semver": "^5.0.3"
+                        "semver": "5.7.1"
                     }
                 },
                 "set-blocking": {
@@ -11311,14 +11347,14 @@
                     "version": "3.0.0",
                     "bundled": true,
                     "requires": {
-                        "graceful-fs": "^4.1.2"
+                        "graceful-fs": "4.2.3"
                     }
                 },
                 "shebang-command": {
                     "version": "1.2.0",
                     "bundled": true,
                     "requires": {
-                        "shebang-regex": "^1.0.0"
+                        "shebang-regex": "1.0.0"
                     }
                 },
                 "shebang-regex": {
@@ -11342,22 +11378,22 @@
                     "bundled": true,
                     "requires": {
                         "ip": "1.1.5",
-                        "smart-buffer": "^4.1.0"
+                        "smart-buffer": "4.1.0"
                     }
                 },
                 "socks-proxy-agent": {
                     "version": "4.0.2",
                     "bundled": true,
                     "requires": {
-                        "agent-base": "~4.2.1",
-                        "socks": "~2.3.2"
+                        "agent-base": "4.2.1",
+                        "socks": "2.3.3"
                     },
                     "dependencies": {
                         "agent-base": {
                             "version": "4.2.1",
                             "bundled": true,
                             "requires": {
-                                "es6-promisify": "^5.0.0"
+                                "es6-promisify": "5.0.0"
                             }
                         }
                     }
@@ -11370,16 +11406,16 @@
                     "version": "2.1.3",
                     "bundled": true,
                     "requires": {
-                        "from2": "^1.3.0",
-                        "stream-iterate": "^1.1.0"
+                        "from2": "1.3.0",
+                        "stream-iterate": "1.2.0"
                     },
                     "dependencies": {
                         "from2": {
                             "version": "1.3.0",
                             "bundled": true,
                             "requires": {
-                                "inherits": "~2.0.1",
-                                "readable-stream": "~1.1.10"
+                                "inherits": "2.0.4",
+                                "readable-stream": "1.1.14"
                             }
                         },
                         "isarray": {
@@ -11390,10 +11426,10 @@
                             "version": "1.1.14",
                             "bundled": true,
                             "requires": {
-                                "core-util-is": "~1.0.0",
-                                "inherits": "~2.0.1",
+                                "core-util-is": "1.0.2",
+                                "inherits": "2.0.4",
                                 "isarray": "0.0.1",
-                                "string_decoder": "~0.10.x"
+                                "string_decoder": "0.10.31"
                             }
                         },
                         "string_decoder": {
@@ -11406,8 +11442,8 @@
                     "version": "3.0.0",
                     "bundled": true,
                     "requires": {
-                        "spdx-expression-parse": "^3.0.0",
-                        "spdx-license-ids": "^3.0.0"
+                        "spdx-expression-parse": "3.0.0",
+                        "spdx-license-ids": "3.0.3"
                     }
                 },
                 "spdx-exceptions": {
@@ -11418,8 +11454,8 @@
                     "version": "3.0.0",
                     "bundled": true,
                     "requires": {
-                        "spdx-exceptions": "^2.1.0",
-                        "spdx-license-ids": "^3.0.0"
+                        "spdx-exceptions": "2.1.0",
+                        "spdx-license-ids": "3.0.3"
                     }
                 },
                 "spdx-license-ids": {
@@ -11434,58 +11470,58 @@
                     "version": "1.14.2",
                     "bundled": true,
                     "requires": {
-                        "asn1": "~0.2.3",
-                        "assert-plus": "^1.0.0",
-                        "bcrypt-pbkdf": "^1.0.0",
-                        "dashdash": "^1.12.0",
-                        "ecc-jsbn": "~0.1.1",
-                        "getpass": "^0.1.1",
-                        "jsbn": "~0.1.0",
-                        "safer-buffer": "^2.0.2",
-                        "tweetnacl": "~0.14.0"
+                        "asn1": "0.2.4",
+                        "assert-plus": "1.0.0",
+                        "bcrypt-pbkdf": "1.0.2",
+                        "dashdash": "1.14.1",
+                        "ecc-jsbn": "0.1.2",
+                        "getpass": "0.1.7",
+                        "jsbn": "0.1.1",
+                        "safer-buffer": "2.1.2",
+                        "tweetnacl": "0.14.5"
                     }
                 },
                 "ssri": {
                     "version": "6.0.1",
                     "bundled": true,
                     "requires": {
-                        "figgy-pudding": "^3.5.1"
+                        "figgy-pudding": "3.5.1"
                     }
                 },
                 "stream-each": {
                     "version": "1.2.2",
                     "bundled": true,
                     "requires": {
-                        "end-of-stream": "^1.1.0",
-                        "stream-shift": "^1.0.0"
+                        "end-of-stream": "1.4.1",
+                        "stream-shift": "1.0.0"
                     }
                 },
                 "stream-iterate": {
                     "version": "1.2.0",
                     "bundled": true,
                     "requires": {
-                        "readable-stream": "^2.1.5",
-                        "stream-shift": "^1.0.0"
+                        "readable-stream": "2.3.6",
+                        "stream-shift": "1.0.0"
                     },
                     "dependencies": {
                         "readable-stream": {
                             "version": "2.3.6",
                             "bundled": true,
                             "requires": {
-                                "core-util-is": "~1.0.0",
-                                "inherits": "~2.0.3",
-                                "isarray": "~1.0.0",
-                                "process-nextick-args": "~2.0.0",
-                                "safe-buffer": "~5.1.1",
-                                "string_decoder": "~1.1.1",
-                                "util-deprecate": "~1.0.1"
+                                "core-util-is": "1.0.2",
+                                "inherits": "2.0.4",
+                                "isarray": "1.0.0",
+                                "process-nextick-args": "2.0.0",
+                                "safe-buffer": "5.1.2",
+                                "string_decoder": "1.1.1",
+                                "util-deprecate": "1.0.2"
                             }
                         },
                         "string_decoder": {
                             "version": "1.1.1",
                             "bundled": true,
                             "requires": {
-                                "safe-buffer": "~5.1.0"
+                                "safe-buffer": "5.1.2"
                             }
                         }
                     }
@@ -11502,8 +11538,8 @@
                     "version": "2.1.1",
                     "bundled": true,
                     "requires": {
-                        "is-fullwidth-code-point": "^2.0.0",
-                        "strip-ansi": "^4.0.0"
+                        "is-fullwidth-code-point": "2.0.0",
+                        "strip-ansi": "4.0.0"
                     },
                     "dependencies": {
                         "ansi-regex": {
@@ -11518,7 +11554,7 @@
                             "version": "4.0.0",
                             "bundled": true,
                             "requires": {
-                                "ansi-regex": "^3.0.0"
+                                "ansi-regex": "3.0.0"
                             }
                         }
                     }
@@ -11527,7 +11563,7 @@
                     "version": "1.2.0",
                     "bundled": true,
                     "requires": {
-                        "safe-buffer": "~5.1.0"
+                        "safe-buffer": "5.1.2"
                     }
                 },
                 "stringify-package": {
@@ -11538,7 +11574,7 @@
                     "version": "3.0.1",
                     "bundled": true,
                     "requires": {
-                        "ansi-regex": "^2.0.0"
+                        "ansi-regex": "2.1.1"
                     }
                 },
                 "strip-eof": {
@@ -11553,28 +11589,28 @@
                     "version": "5.4.0",
                     "bundled": true,
                     "requires": {
-                        "has-flag": "^3.0.0"
+                        "has-flag": "3.0.0"
                     }
                 },
                 "tar": {
                     "version": "4.4.13",
                     "bundled": true,
                     "requires": {
-                        "chownr": "^1.1.1",
-                        "fs-minipass": "^1.2.5",
-                        "minipass": "^2.8.6",
-                        "minizlib": "^1.2.1",
-                        "mkdirp": "^0.5.0",
-                        "safe-buffer": "^5.1.2",
-                        "yallist": "^3.0.3"
+                        "chownr": "1.1.3",
+                        "fs-minipass": "1.2.7",
+                        "minipass": "2.9.0",
+                        "minizlib": "1.3.3",
+                        "mkdirp": "0.5.1",
+                        "safe-buffer": "5.1.2",
+                        "yallist": "3.0.3"
                     },
                     "dependencies": {
                         "minipass": {
                             "version": "2.9.0",
                             "bundled": true,
                             "requires": {
-                                "safe-buffer": "^5.1.2",
-                                "yallist": "^3.0.0"
+                                "safe-buffer": "5.1.2",
+                                "yallist": "3.0.3"
                             }
                         }
                     }
@@ -11583,7 +11619,7 @@
                     "version": "1.2.0",
                     "bundled": true,
                     "requires": {
-                        "execa": "^0.7.0"
+                        "execa": "0.7.0"
                     }
                 },
                 "text-table": {
@@ -11598,28 +11634,28 @@
                     "version": "2.0.3",
                     "bundled": true,
                     "requires": {
-                        "readable-stream": "^2.1.5",
-                        "xtend": "~4.0.1"
+                        "readable-stream": "2.3.6",
+                        "xtend": "4.0.1"
                     },
                     "dependencies": {
                         "readable-stream": {
                             "version": "2.3.6",
                             "bundled": true,
                             "requires": {
-                                "core-util-is": "~1.0.0",
-                                "inherits": "~2.0.3",
-                                "isarray": "~1.0.0",
-                                "process-nextick-args": "~2.0.0",
-                                "safe-buffer": "~5.1.1",
-                                "string_decoder": "~1.1.1",
-                                "util-deprecate": "~1.0.1"
+                                "core-util-is": "1.0.2",
+                                "inherits": "2.0.4",
+                                "isarray": "1.0.0",
+                                "process-nextick-args": "2.0.0",
+                                "safe-buffer": "5.1.2",
+                                "string_decoder": "1.1.1",
+                                "util-deprecate": "1.0.2"
                             }
                         },
                         "string_decoder": {
                             "version": "1.1.1",
                             "bundled": true,
                             "requires": {
-                                "safe-buffer": "~5.1.0"
+                                "safe-buffer": "5.1.2"
                             }
                         }
                     }
@@ -11636,15 +11672,15 @@
                     "version": "2.4.3",
                     "bundled": true,
                     "requires": {
-                        "psl": "^1.1.24",
-                        "punycode": "^1.4.1"
+                        "psl": "1.1.29",
+                        "punycode": "1.4.1"
                     }
                 },
                 "tunnel-agent": {
                     "version": "0.6.0",
                     "bundled": true,
                     "requires": {
-                        "safe-buffer": "^5.0.1"
+                        "safe-buffer": "5.1.2"
                     }
                 },
                 "tweetnacl": {
@@ -11668,21 +11704,21 @@
                     "version": "1.1.1",
                     "bundled": true,
                     "requires": {
-                        "unique-slug": "^2.0.0"
+                        "unique-slug": "2.0.0"
                     }
                 },
                 "unique-slug": {
                     "version": "2.0.0",
                     "bundled": true,
                     "requires": {
-                        "imurmurhash": "^0.1.4"
+                        "imurmurhash": "0.1.4"
                     }
                 },
                 "unique-string": {
                     "version": "1.0.0",
                     "bundled": true,
                     "requires": {
-                        "crypto-random-string": "^1.0.0"
+                        "crypto-random-string": "1.0.0"
                     }
                 },
                 "unpipe": {
@@ -11697,23 +11733,23 @@
                     "version": "2.5.0",
                     "bundled": true,
                     "requires": {
-                        "boxen": "^1.2.1",
-                        "chalk": "^2.0.1",
-                        "configstore": "^3.0.0",
-                        "import-lazy": "^2.1.0",
-                        "is-ci": "^1.0.10",
-                        "is-installed-globally": "^0.1.0",
-                        "is-npm": "^1.0.0",
-                        "latest-version": "^3.0.0",
-                        "semver-diff": "^2.0.0",
-                        "xdg-basedir": "^3.0.0"
+                        "boxen": "1.3.0",
+                        "chalk": "2.4.1",
+                        "configstore": "3.1.2",
+                        "import-lazy": "2.1.0",
+                        "is-ci": "1.1.0",
+                        "is-installed-globally": "0.1.0",
+                        "is-npm": "1.0.0",
+                        "latest-version": "3.1.0",
+                        "semver-diff": "2.1.0",
+                        "xdg-basedir": "3.0.0"
                     }
                 },
                 "url-parse-lax": {
                     "version": "1.0.0",
                     "bundled": true,
                     "requires": {
-                        "prepend-http": "^1.0.1"
+                        "prepend-http": "1.0.4"
                     }
                 },
                 "util-deprecate": {
@@ -11728,7 +11764,7 @@
                     "version": "2.1.0",
                     "bundled": true,
                     "requires": {
-                        "object.getownpropertydescriptors": "^2.0.3"
+                        "object.getownpropertydescriptors": "2.0.3"
                     }
                 },
                 "uuid": {
@@ -11739,38 +11775,38 @@
                     "version": "3.0.4",
                     "bundled": true,
                     "requires": {
-                        "spdx-correct": "^3.0.0",
-                        "spdx-expression-parse": "^3.0.0"
+                        "spdx-correct": "3.0.0",
+                        "spdx-expression-parse": "3.0.0"
                     }
                 },
                 "validate-npm-package-name": {
                     "version": "3.0.0",
                     "bundled": true,
                     "requires": {
-                        "builtins": "^1.0.3"
+                        "builtins": "1.0.3"
                     }
                 },
                 "verror": {
                     "version": "1.10.0",
                     "bundled": true,
                     "requires": {
-                        "assert-plus": "^1.0.0",
+                        "assert-plus": "1.0.0",
                         "core-util-is": "1.0.2",
-                        "extsprintf": "^1.2.0"
+                        "extsprintf": "1.3.0"
                     }
                 },
                 "wcwidth": {
                     "version": "1.0.1",
                     "bundled": true,
                     "requires": {
-                        "defaults": "^1.0.3"
+                        "defaults": "1.0.3"
                     }
                 },
                 "which": {
                     "version": "1.3.1",
                     "bundled": true,
                     "requires": {
-                        "isexe": "^2.0.0"
+                        "isexe": "2.0.0"
                     }
                 },
                 "which-module": {
@@ -11781,16 +11817,16 @@
                     "version": "1.1.2",
                     "bundled": true,
                     "requires": {
-                        "string-width": "^1.0.2"
+                        "string-width": "1.0.2"
                     },
                     "dependencies": {
                         "string-width": {
                             "version": "1.0.2",
                             "bundled": true,
                             "requires": {
-                                "code-point-at": "^1.0.0",
-                                "is-fullwidth-code-point": "^1.0.0",
-                                "strip-ansi": "^3.0.0"
+                                "code-point-at": "1.1.0",
+                                "is-fullwidth-code-point": "1.0.0",
+                                "strip-ansi": "3.0.1"
                             }
                         }
                     }
@@ -11799,31 +11835,31 @@
                     "version": "2.0.0",
                     "bundled": true,
                     "requires": {
-                        "string-width": "^2.1.1"
+                        "string-width": "2.1.1"
                     }
                 },
                 "worker-farm": {
                     "version": "1.7.0",
                     "bundled": true,
                     "requires": {
-                        "errno": "~0.1.7"
+                        "errno": "0.1.7"
                     }
                 },
                 "wrap-ansi": {
                     "version": "2.1.0",
                     "bundled": true,
                     "requires": {
-                        "string-width": "^1.0.1",
-                        "strip-ansi": "^3.0.1"
+                        "string-width": "1.0.2",
+                        "strip-ansi": "3.0.1"
                     },
                     "dependencies": {
                         "string-width": {
                             "version": "1.0.2",
                             "bundled": true,
                             "requires": {
-                                "code-point-at": "^1.0.0",
-                                "is-fullwidth-code-point": "^1.0.0",
-                                "strip-ansi": "^3.0.0"
+                                "code-point-at": "1.1.0",
+                                "is-fullwidth-code-point": "1.0.0",
+                                "strip-ansi": "3.0.1"
                             }
                         }
                     }
@@ -11836,9 +11872,9 @@
                     "version": "2.4.3",
                     "bundled": true,
                     "requires": {
-                        "graceful-fs": "^4.1.11",
-                        "imurmurhash": "^0.1.4",
-                        "signal-exit": "^3.0.2"
+                        "graceful-fs": "4.2.3",
+                        "imurmurhash": "0.1.4",
+                        "signal-exit": "3.0.2"
                     }
                 },
                 "xdg-basedir": {
@@ -11858,21 +11894,21 @@
                     "bundled": true
                 },
                 "yargs": {
-                    "version": "11.0.0",
+                    "version": "11.1.1",
                     "bundled": true,
                     "requires": {
-                        "cliui": "^4.0.0",
-                        "decamelize": "^1.1.1",
-                        "find-up": "^2.1.0",
-                        "get-caller-file": "^1.0.1",
-                        "os-locale": "^2.0.0",
-                        "require-directory": "^2.1.1",
-                        "require-main-filename": "^1.0.1",
-                        "set-blocking": "^2.0.0",
-                        "string-width": "^2.0.0",
-                        "which-module": "^2.0.0",
-                        "y18n": "^3.2.1",
-                        "yargs-parser": "^9.0.2"
+                        "cliui": "4.1.0",
+                        "decamelize": "1.2.0",
+                        "find-up": "2.1.0",
+                        "get-caller-file": "1.0.3",
+                        "os-locale": "3.1.0",
+                        "require-directory": "2.1.1",
+                        "require-main-filename": "1.0.1",
+                        "set-blocking": "2.0.0",
+                        "string-width": "2.1.1",
+                        "which-module": "2.0.0",
+                        "y18n": "3.2.1",
+                        "yargs-parser": "9.0.2"
                     },
                     "dependencies": {
                         "y18n": {
@@ -11885,7 +11921,7 @@
                     "version": "9.0.2",
                     "bundled": true,
                     "requires": {
-                        "camelcase": "^4.1.0"
+                        "camelcase": "4.1.0"
                     }
                 }
             }
@@ -11895,7 +11931,7 @@
             "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz",
             "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==",
             "requires": {
-                "npm-normalize-package-bin": "^1.0.1"
+                "npm-normalize-package-bin": "1.0.1"
             }
         },
         "npm-normalize-package-bin": {
@@ -11908,8 +11944,8 @@
             "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.7.tgz",
             "integrity": "sha512-vAj7dIkp5NhieaGZxBJB8fF4R0078rqsmhJcAfXZ6O7JJhjhPK96n5Ry1oZcfLXgfun0GWTZPOxaEyqv8GBykQ==",
             "requires": {
-                "ignore-walk": "^3.0.1",
-                "npm-bundled": "^1.0.1"
+                "ignore-walk": "3.0.3",
+                "npm-bundled": "1.1.1"
             }
         },
         "npm-run-path": {
@@ -11917,7 +11953,7 @@
             "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
             "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=",
             "requires": {
-                "path-key": "^2.0.0"
+                "path-key": "2.0.1"
             }
         },
         "npmlog": {
@@ -11925,10 +11961,10 @@
             "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
             "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
             "requires": {
-                "are-we-there-yet": "~1.1.2",
-                "console-control-strings": "~1.1.0",
-                "gauge": "~2.7.3",
-                "set-blocking": "~2.0.0"
+                "are-we-there-yet": "1.1.5",
+                "console-control-strings": "1.1.0",
+                "gauge": "2.7.4",
+                "set-blocking": "2.0.0"
             }
         },
         "nth-check": {
@@ -11936,7 +11972,7 @@
             "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz",
             "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==",
             "requires": {
-                "boolbase": "~1.0.0"
+                "boolbase": "1.0.0"
             }
         },
         "number-is-nan": {
@@ -11975,9 +12011,9 @@
             "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz",
             "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=",
             "requires": {
-                "copy-descriptor": "^0.1.0",
-                "define-property": "^0.2.5",
-                "kind-of": "^3.0.3"
+                "copy-descriptor": "0.1.1",
+                "define-property": "0.2.5",
+                "kind-of": "3.2.2"
             },
             "dependencies": {
                 "define-property": {
@@ -11985,7 +12021,7 @@
                     "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
                     "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
                     "requires": {
-                        "is-descriptor": "^0.1.0"
+                        "is-descriptor": "0.1.6"
                     }
                 },
                 "kind-of": {
@@ -11993,7 +12029,7 @@
                     "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
                     "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
                     "requires": {
-                        "is-buffer": "^1.1.5"
+                        "is-buffer": "1.1.6"
                     }
                 }
             }
@@ -12042,10 +12078,10 @@
                     "resolved": "https://registry.npmjs.org/magicli/-/magicli-0.0.5.tgz",
                     "integrity": "sha1-zufQ+7THBRiqyxHsPrfiX/SaSSE=",
                     "requires": {
-                        "commander": "^2.9.0",
-                        "get-stdin": "^5.0.1",
-                        "inspect-function": "^0.2.1",
-                        "pipe-functions": "^1.2.0"
+                        "commander": "2.20.0",
+                        "get-stdin": "5.0.1",
+                        "inspect-function": "0.2.2",
+                        "pipe-functions": "1.3.0"
                     }
                 }
             }
@@ -12055,7 +12091,7 @@
             "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz",
             "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=",
             "requires": {
-                "isobject": "^3.0.0"
+                "isobject": "3.0.1"
             }
         },
         "object.assign": {
@@ -12063,10 +12099,10 @@
             "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz",
             "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==",
             "requires": {
-                "define-properties": "^1.1.2",
-                "function-bind": "^1.1.1",
-                "has-symbols": "^1.0.0",
-                "object-keys": "^1.0.11"
+                "define-properties": "1.1.3",
+                "function-bind": "1.1.1",
+                "has-symbols": "1.0.0",
+                "object-keys": "1.1.1"
             }
         },
         "object.getownpropertydescriptors": {
@@ -12074,8 +12110,8 @@
             "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz",
             "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=",
             "requires": {
-                "define-properties": "^1.1.2",
-                "es-abstract": "^1.5.1"
+                "define-properties": "1.1.3",
+                "es-abstract": "1.16.0"
             }
         },
         "object.pick": {
@@ -12083,7 +12119,7 @@
             "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz",
             "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=",
             "requires": {
-                "isobject": "^3.0.1"
+                "isobject": "3.0.1"
             }
         },
         "obuf": {
@@ -12110,7 +12146,7 @@
             "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
             "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
             "requires": {
-                "wrappy": "1"
+                "wrappy": "1.0.2"
             }
         },
         "opn": {
@@ -12119,7 +12155,7 @@
             "integrity": "sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==",
             "dev": true,
             "requires": {
-                "is-wsl": "^1.1.0"
+                "is-wsl": "1.1.0"
             }
         },
         "optionator": {
@@ -12128,12 +12164,12 @@
             "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==",
             "dev": true,
             "requires": {
-                "deep-is": "~0.1.3",
-                "fast-levenshtein": "~2.0.6",
-                "levn": "~0.3.0",
-                "prelude-ls": "~1.1.2",
-                "type-check": "~0.3.2",
-                "word-wrap": "~1.2.3"
+                "deep-is": "0.1.3",
+                "fast-levenshtein": "2.0.6",
+                "levn": "0.3.0",
+                "prelude-ls": "1.1.2",
+                "type-check": "0.3.2",
+                "word-wrap": "1.2.3"
             }
         },
         "orderedmap": {
@@ -12147,7 +12183,7 @@
             "integrity": "sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==",
             "dev": true,
             "requires": {
-                "url-parse": "^1.4.3"
+                "url-parse": "1.4.7"
             }
         },
         "os-browserify": {
@@ -12166,7 +12202,7 @@
             "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz",
             "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=",
             "requires": {
-                "lcid": "^1.0.0"
+                "lcid": "1.0.0"
             }
         },
         "os-tmpdir": {
@@ -12179,8 +12215,8 @@
             "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz",
             "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==",
             "requires": {
-                "os-homedir": "^1.0.0",
-                "os-tmpdir": "^1.0.0"
+                "os-homedir": "1.0.2",
+                "os-tmpdir": "1.0.2"
             }
         },
         "p-defer": {
@@ -12205,7 +12241,7 @@
             "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz",
             "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==",
             "requires": {
-                "p-try": "^2.0.0"
+                "p-try": "2.2.0"
             },
             "dependencies": {
                 "p-try": {
@@ -12220,7 +12256,7 @@
             "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz",
             "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=",
             "requires": {
-                "p-limit": "^1.1.0"
+                "p-limit": "1.3.0"
             },
             "dependencies": {
                 "p-limit": {
@@ -12228,7 +12264,7 @@
                     "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz",
                     "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==",
                     "requires": {
-                        "p-try": "^1.0.0"
+                        "p-try": "1.0.0"
                     }
                 }
             }
@@ -12245,7 +12281,7 @@
             "integrity": "sha512-XE6G4+YTTkT2a0UWb2kjZe8xNwf8bIbnqpc/IS/idOBVhyves0mK5OJgeocjx7q5pvX/6m23xuzVPYT1uGM73w==",
             "dev": true,
             "requires": {
-                "retry": "^0.12.0"
+                "retry": "0.12.0"
             }
         },
         "p-try": {
@@ -12258,10 +12294,10 @@
             "resolved": "https://registry.npmjs.org/package-json/-/package-json-4.0.1.tgz",
             "integrity": "sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=",
             "requires": {
-                "got": "^6.7.1",
-                "registry-auth-token": "^3.0.1",
-                "registry-url": "^3.0.3",
-                "semver": "^5.1.0"
+                "got": "6.7.1",
+                "registry-auth-token": "3.4.0",
+                "registry-url": "3.1.0",
+                "semver": "5.7.0"
             }
         },
         "pako": {
@@ -12276,9 +12312,9 @@
             "integrity": "sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY=",
             "dev": true,
             "requires": {
-                "cyclist": "~0.2.2",
-                "inherits": "^2.0.3",
-                "readable-stream": "^2.1.5"
+                "cyclist": "0.2.2",
+                "inherits": "2.0.3",
+                "readable-stream": "2.3.6"
             }
         },
         "parent-module": {
@@ -12286,7 +12322,7 @@
             "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
             "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
             "requires": {
-                "callsites": "^3.0.0"
+                "callsites": "3.1.0"
             }
         },
         "parse-asn1": {
@@ -12294,12 +12330,12 @@
             "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.4.tgz",
             "integrity": "sha512-Qs5duJcuvNExRfFZ99HDD3z4mAi3r9Wl/FOjEOijlxwCZs7E7mW2vjTpgQ4J8LpTF8x5v+1Vn5UQFejmWT11aw==",
             "requires": {
-                "asn1.js": "^4.0.0",
-                "browserify-aes": "^1.0.0",
-                "create-hash": "^1.1.0",
-                "evp_bytestokey": "^1.0.0",
-                "pbkdf2": "^3.0.3",
-                "safe-buffer": "^5.1.1"
+                "asn1.js": "4.10.1",
+                "browserify-aes": "1.2.0",
+                "create-hash": "1.2.0",
+                "evp_bytestokey": "1.0.3",
+                "pbkdf2": "3.0.17",
+                "safe-buffer": "5.1.2"
             }
         },
         "parse-json": {
@@ -12307,10 +12343,10 @@
             "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz",
             "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==",
             "requires": {
-                "@babel/code-frame": "^7.0.0",
-                "error-ex": "^1.3.1",
-                "json-parse-better-errors": "^1.0.1",
-                "lines-and-columns": "^1.1.6"
+                "@babel/code-frame": "7.5.5",
+                "error-ex": "1.3.2",
+                "json-parse-better-errors": "1.0.2",
+                "lines-and-columns": "1.1.6"
             }
         },
         "parse-passwd": {
@@ -12330,7 +12366,7 @@
             "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz",
             "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=",
             "requires": {
-                "better-assert": "~1.0.0"
+                "better-assert": "1.0.2"
             }
         },
         "parseuri": {
@@ -12338,7 +12374,7 @@
             "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz",
             "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=",
             "requires": {
-                "better-assert": "~1.0.0"
+                "better-assert": "1.0.2"
             }
         },
         "parseurl": {
@@ -12356,7 +12392,7 @@
             "resolved": "https://registry.npmjs.org/passport/-/passport-0.4.1.tgz",
             "integrity": "sha512-IxXgZZs8d7uFSt3eqNjM9NQ3g3uQCW5avD8mRNoXV99Yig50vjuaez6dQK2qC0kVWPRTujxY0dWgGfT09adjYg==",
             "requires": {
-                "passport-strategy": "1.x.x",
+                "passport-strategy": "1.0.0",
                 "pause": "0.0.1"
             }
         },
@@ -12365,7 +12401,7 @@
             "resolved": "https://registry.npmjs.org/passport-google-oauth20/-/passport-google-oauth20-2.0.0.tgz",
             "integrity": "sha512-KSk6IJ15RoxuGq7D1UKK/8qKhNfzbLeLrG3gkLZ7p4A6DBCcv7xpyQwuXtWdpyR0+E0mwkpjY1VfPOhxQrKzdQ==",
             "requires": {
-                "passport-oauth2": "1.x.x"
+                "passport-oauth2": "1.5.0"
             }
         },
         "passport-local": {
@@ -12373,7 +12409,7 @@
             "resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz",
             "integrity": "sha1-H+YyaMkudWBmJkN+O5BmYsFbpu4=",
             "requires": {
-                "passport-strategy": "1.x.x"
+                "passport-strategy": "1.0.0"
             }
         },
         "passport-oauth2": {
@@ -12381,11 +12417,11 @@
             "resolved": "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.5.0.tgz",
             "integrity": "sha512-kqBt6vR/5VlCK8iCx1/KpY42kQ+NEHZwsSyt4Y6STiNjU+wWICG1i8ucc1FapXDGO15C5O5VZz7+7vRzrDPXXQ==",
             "requires": {
-                "base64url": "3.x.x",
-                "oauth": "0.9.x",
-                "passport-strategy": "1.x.x",
-                "uid2": "0.0.x",
-                "utils-merge": "1.x.x"
+                "base64url": "3.0.1",
+                "oauth": "0.9.15",
+                "passport-strategy": "1.0.0",
+                "uid2": "0.0.3",
+                "utils-merge": "1.0.1"
             }
         },
         "passport-strategy": {
@@ -12439,9 +12475,9 @@
             "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz",
             "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=",
             "requires": {
-                "graceful-fs": "^4.1.2",
-                "pify": "^2.0.0",
-                "pinkie-promise": "^2.0.0"
+                "graceful-fs": "4.2.0",
+                "pify": "2.3.0",
+                "pinkie-promise": "2.0.1"
             }
         },
         "pathval": {
@@ -12459,7 +12495,7 @@
             "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz",
             "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=",
             "requires": {
-                "through": "~2.3"
+                "through": "2.3.8"
             }
         },
         "pbkdf2": {
@@ -12467,11 +12503,11 @@
             "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz",
             "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==",
             "requires": {
-                "create-hash": "^1.1.2",
-                "create-hmac": "^1.1.4",
-                "ripemd160": "^2.0.1",
-                "safe-buffer": "^5.0.1",
-                "sha.js": "^2.4.8"
+                "create-hash": "1.2.0",
+                "create-hmac": "1.1.7",
+                "ripemd160": "2.0.2",
+                "safe-buffer": "5.1.2",
+                "sha.js": "2.4.11"
             }
         },
         "pdf-parse": {
@@ -12479,8 +12515,8 @@
             "resolved": "https://registry.npmjs.org/pdf-parse/-/pdf-parse-1.1.1.tgz",
             "integrity": "sha512-v6ZJ/efsBpGrGGknjtq9J/oC8tZWq0KWL5vQrk2GlzLEQPUDB1ex+13Rmidl1neNN358Jn9EHZw5y07FFtaC7A==",
             "requires": {
-                "debug": "^3.1.0",
-                "node-ensure": "^0.0.0"
+                "debug": "3.2.6",
+                "node-ensure": "0.0.0"
             },
             "dependencies": {
                 "debug": {
@@ -12488,7 +12524,7 @@
                     "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
                     "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
                     "requires": {
-                        "ms": "^2.1.1"
+                        "ms": "2.1.2"
                     }
                 },
                 "ms": {
@@ -12503,8 +12539,8 @@
             "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-2.3.200.tgz",
             "integrity": "sha512-+8wBjU5h8LPZOIvR9X2uCrp/8xWQG1DRDKMLg5lzGN1qyIAZlYUxA0KQyy12Nw5jN7ozulC6v97PMaDcLgAcFg==",
             "requires": {
-                "node-ensure": "^0.0.0",
-                "worker-loader": "^2.0.0"
+                "node-ensure": "0.0.0",
+                "worker-loader": "2.0.0"
             }
         },
         "performance-now": {
@@ -12532,7 +12568,7 @@
             "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
             "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
             "requires": {
-                "pinkie": "^2.0.0"
+                "pinkie": "2.0.4"
             }
         },
         "pipe-functions": {
@@ -12546,7 +12582,7 @@
             "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=",
             "dev": true,
             "requires": {
-                "find-up": "^2.1.0"
+                "find-up": "2.1.0"
             }
         },
         "pn": {
@@ -12566,9 +12602,9 @@
             "integrity": "sha512-6ElJnHBbxVA1XSLgBp7G1FiCkQdlqGzuF7DswL5tcea+E8UpuvPU7beVAjjRwCioTS9ZluNbu+ZyRvgTsmqEBg==",
             "dev": true,
             "requires": {
-                "async": "^2.6.2",
-                "debug": "^3.1.1",
-                "mkdirp": "^0.5.1"
+                "async": "2.6.3",
+                "debug": "3.2.6",
+                "mkdirp": "0.5.1"
             },
             "dependencies": {
                 "debug": {
@@ -12577,7 +12613,7 @@
                     "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
                     "dev": true,
                     "requires": {
-                        "ms": "^2.1.1"
+                        "ms": "2.1.2"
                     }
                 },
                 "ms": {
@@ -12599,9 +12635,9 @@
             "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==",
             "dev": true,
             "requires": {
-                "chalk": "^2.4.2",
-                "source-map": "^0.6.1",
-                "supports-color": "^6.1.0"
+                "chalk": "2.4.2",
+                "source-map": "0.6.1",
+                "supports-color": "6.1.0"
             },
             "dependencies": {
                 "ansi-styles": {
@@ -12610,7 +12646,7 @@
                     "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
                     "dev": true,
                     "requires": {
-                        "color-convert": "^1.9.0"
+                        "color-convert": "1.9.3"
                     }
                 },
                 "chalk": {
@@ -12619,9 +12655,9 @@
                     "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
                     "dev": true,
                     "requires": {
-                        "ansi-styles": "^3.2.1",
-                        "escape-string-regexp": "^1.0.5",
-                        "supports-color": "^5.3.0"
+                        "ansi-styles": "3.2.1",
+                        "escape-string-regexp": "1.0.5",
+                        "supports-color": "5.5.0"
                     },
                     "dependencies": {
                         "supports-color": {
@@ -12630,7 +12666,7 @@
                             "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
                             "dev": true,
                             "requires": {
-                                "has-flag": "^3.0.0"
+                                "has-flag": "3.0.0"
                             }
                         }
                     }
@@ -12647,7 +12683,7 @@
                     "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
                     "dev": true,
                     "requires": {
-                        "has-flag": "^3.0.0"
+                        "has-flag": "3.0.0"
                     }
                 }
             }
@@ -12658,7 +12694,7 @@
             "integrity": "sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ==",
             "dev": true,
             "requires": {
-                "postcss": "^7.0.5"
+                "postcss": "7.0.17"
             }
         },
         "postcss-modules-local-by-default": {
@@ -12667,9 +12703,9 @@
             "integrity": "sha512-oLUV5YNkeIBa0yQl7EYnxMgy4N6noxmiwZStaEJUSe2xPMcdNc8WmBQuQCx18H5psYbVxz8zoHk0RAAYZXP9gA==",
             "dev": true,
             "requires": {
-                "postcss": "^7.0.6",
-                "postcss-selector-parser": "^6.0.0",
-                "postcss-value-parser": "^3.3.1"
+                "postcss": "7.0.17",
+                "postcss-selector-parser": "6.0.2",
+                "postcss-value-parser": "3.3.1"
             }
         },
         "postcss-modules-scope": {
@@ -12678,8 +12714,8 @@
             "integrity": "sha512-91Rjps0JnmtUB0cujlc8KIKCsJXWjzuxGeT/+Q2i2HXKZ7nBUeF9YQTZZTNvHVoNYj1AthsjnGLtqDUE0Op79A==",
             "dev": true,
             "requires": {
-                "postcss": "^7.0.6",
-                "postcss-selector-parser": "^6.0.0"
+                "postcss": "7.0.17",
+                "postcss-selector-parser": "6.0.2"
             }
         },
         "postcss-modules-values": {
@@ -12688,8 +12724,8 @@
             "integrity": "sha512-Ki7JZa7ff1N3EIMlPnGTZfUMe69FFwiQPnVSXC9mnn3jozCRBYIxiZd44yJOV2AmabOo4qFf8s0dC/+lweG7+w==",
             "dev": true,
             "requires": {
-                "icss-replace-symbols": "^1.1.0",
-                "postcss": "^7.0.6"
+                "icss-replace-symbols": "1.1.0",
+                "postcss": "7.0.17"
             }
         },
         "postcss-selector-parser": {
@@ -12698,9 +12734,9 @@
             "integrity": "sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg==",
             "dev": true,
             "requires": {
-                "cssesc": "^3.0.0",
-                "indexes-of": "^1.0.1",
-                "uniq": "^1.0.1"
+                "cssesc": "3.0.0",
+                "indexes-of": "1.0.1",
+                "uniq": "1.0.1"
             }
         },
         "postcss-value-parser": {
@@ -12714,21 +12750,21 @@
             "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.3.3.tgz",
             "integrity": "sha512-GV+nsUXuPW2p8Zy7SarF/2W/oiK8bFQgJcncoJ0d7kRpekEA0ftChjfEaF9/Y+QJEc/wFR7RAEa8lYByuUIe2g==",
             "requires": {
-                "detect-libc": "^1.0.3",
-                "expand-template": "^2.0.3",
+                "detect-libc": "1.0.3",
+                "expand-template": "2.0.3",
                 "github-from-package": "0.0.0",
-                "minimist": "^1.2.0",
-                "mkdirp": "^0.5.1",
-                "napi-build-utils": "^1.0.1",
-                "node-abi": "^2.7.0",
-                "noop-logger": "^0.1.1",
-                "npmlog": "^4.0.1",
-                "pump": "^3.0.0",
-                "rc": "^1.2.7",
-                "simple-get": "^3.0.3",
-                "tar-fs": "^2.0.0",
-                "tunnel-agent": "^0.6.0",
-                "which-pm-runs": "^1.0.0"
+                "minimist": "1.2.0",
+                "mkdirp": "0.5.1",
+                "napi-build-utils": "1.0.1",
+                "node-abi": "2.13.0",
+                "noop-logger": "0.1.1",
+                "npmlog": "4.1.2",
+                "pump": "3.0.0",
+                "rc": "1.2.8",
+                "simple-get": "3.0.3",
+                "tar-fs": "2.0.0",
+                "tunnel-agent": "0.6.0",
+                "which-pm-runs": "1.0.0"
             },
             "dependencies": {
                 "minimist": {
@@ -12741,8 +12777,8 @@
                     "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
                     "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
                     "requires": {
-                        "end-of-stream": "^1.1.0",
-                        "once": "^1.3.1"
+                        "end-of-stream": "1.4.1",
+                        "once": "1.4.0"
                     }
                 }
             }
@@ -12768,12 +12804,12 @@
             "resolved": "https://registry.npmjs.org/probe-image-size/-/probe-image-size-4.1.1.tgz",
             "integrity": "sha512-42LqKZqTLxH/UvAZ2/cKhAsR4G/Y6B7i7fI2qtQu9hRBK4YjS6gqO+QRtwTjvojUx4+/+JuOMzLoFyRecT9qRw==",
             "requires": {
-                "any-promise": "^1.3.0",
-                "deepmerge": "^4.0.0",
-                "inherits": "^2.0.3",
-                "next-tick": "^1.0.0",
-                "request": "^2.83.0",
-                "stream-parser": "~0.3.1"
+                "any-promise": "1.3.0",
+                "deepmerge": "4.0.0",
+                "inherits": "2.0.3",
+                "next-tick": "1.0.0",
+                "request": "2.88.0",
+                "stream-parser": "0.3.1"
             },
             "dependencies": {
                 "assert-plus": {
@@ -12801,9 +12837,9 @@
                     "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
                     "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
                     "requires": {
-                        "asynckit": "^0.4.0",
-                        "combined-stream": "^1.0.6",
-                        "mime-types": "^2.1.12"
+                        "asynckit": "0.4.0",
+                        "combined-stream": "1.0.8",
+                        "mime-types": "2.1.24"
                     }
                 },
                 "har-validator": {
@@ -12811,8 +12847,8 @@
                     "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz",
                     "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==",
                     "requires": {
-                        "ajv": "^6.5.5",
-                        "har-schema": "^2.0.0"
+                        "ajv": "6.10.2",
+                        "har-schema": "2.0.0"
                     }
                 },
                 "http-signature": {
@@ -12820,9 +12856,9 @@
                     "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
                     "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
                     "requires": {
-                        "assert-plus": "^1.0.0",
-                        "jsprim": "^1.2.2",
-                        "sshpk": "^1.7.0"
+                        "assert-plus": "1.0.0",
+                        "jsprim": "1.4.1",
+                        "sshpk": "1.16.1"
                     }
                 },
                 "oauth-sign": {
@@ -12840,26 +12876,26 @@
                     "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz",
                     "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==",
                     "requires": {
-                        "aws-sign2": "~0.7.0",
-                        "aws4": "^1.8.0",
-                        "caseless": "~0.12.0",
-                        "combined-stream": "~1.0.6",
-                        "extend": "~3.0.2",
-                        "forever-agent": "~0.6.1",
-                        "form-data": "~2.3.2",
-                        "har-validator": "~5.1.0",
-                        "http-signature": "~1.2.0",
-                        "is-typedarray": "~1.0.0",
-                        "isstream": "~0.1.2",
-                        "json-stringify-safe": "~5.0.1",
-                        "mime-types": "~2.1.19",
-                        "oauth-sign": "~0.9.0",
-                        "performance-now": "^2.1.0",
-                        "qs": "~6.5.2",
-                        "safe-buffer": "^5.1.2",
-                        "tough-cookie": "~2.4.3",
-                        "tunnel-agent": "^0.6.0",
-                        "uuid": "^3.3.2"
+                        "aws-sign2": "0.7.0",
+                        "aws4": "1.9.0",
+                        "caseless": "0.12.0",
+                        "combined-stream": "1.0.8",
+                        "extend": "3.0.2",
+                        "forever-agent": "0.6.1",
+                        "form-data": "2.3.3",
+                        "har-validator": "5.1.3",
+                        "http-signature": "1.2.0",
+                        "is-typedarray": "1.0.0",
+                        "isstream": "0.1.2",
+                        "json-stringify-safe": "5.0.1",
+                        "mime-types": "2.1.24",
+                        "oauth-sign": "0.9.0",
+                        "performance-now": "2.1.0",
+                        "qs": "6.5.2",
+                        "safe-buffer": "5.1.2",
+                        "tough-cookie": "2.4.3",
+                        "tunnel-agent": "0.6.0",
+                        "uuid": "3.4.0"
                     }
                 }
             }
@@ -12880,7 +12916,7 @@
             "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
             "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
             "requires": {
-                "asap": "~2.0.3"
+                "asap": "2.0.6"
             }
         },
         "promise-inflight": {
@@ -12894,9 +12930,9 @@
             "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
             "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
             "requires": {
-                "loose-envify": "^1.4.0",
-                "object-assign": "^4.1.1",
-                "react-is": "^16.8.1"
+                "loose-envify": "1.4.0",
+                "object-assign": "4.1.1",
+                "react-is": "16.8.6"
             }
         },
         "prop-types-extra": {
@@ -12904,8 +12940,8 @@
             "resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.0.tgz",
             "integrity": "sha512-QFyuDxvMipmIVKD2TwxLVPzMnO4e5oOf1vr3tJIomL8E7d0lr6phTHd5nkPhFIzTD1idBLLEPeylL9g+rrTzRg==",
             "requires": {
-                "react-is": "^16.3.2",
-                "warning": "^3.0.0"
+                "react-is": "16.8.6",
+                "warning": "3.0.0"
             }
         },
         "prosemirror-commands": {
@@ -12913,9 +12949,9 @@
             "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.1.3.tgz",
             "integrity": "sha512-YVbKwTR4likoyhuwIUC9egbzHvnFrFUNbiesB0DB/HZ8hBcopQ42Tb4KGlYrS3n+pNDTFObN73CLFY6mYLN2IQ==",
             "requires": {
-                "prosemirror-model": "^1.0.0",
-                "prosemirror-state": "^1.0.0",
-                "prosemirror-transform": "^1.0.0"
+                "prosemirror-model": "1.9.1",
+                "prosemirror-state": "1.3.2",
+                "prosemirror-transform": "1.2.3"
             }
         },
         "prosemirror-dropcursor": {
@@ -12923,9 +12959,9 @@
             "resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.3.2.tgz",
             "integrity": "sha512-4c94OUGyobGnwcQI70OXyMhE/9T4aTgjU+CHxkd5c7D+jH/J0mKM/lk+jneFVKt7+E4/M0D9HzRPifu8U28Thw==",
             "requires": {
-                "prosemirror-state": "^1.0.0",
-                "prosemirror-transform": "^1.1.0",
-                "prosemirror-view": "^1.1.0"
+                "prosemirror-state": "1.3.2",
+                "prosemirror-transform": "1.2.3",
+                "prosemirror-view": "1.13.8"
             }
         },
         "prosemirror-example-setup": {
@@ -12933,15 +12969,15 @@
             "resolved": "https://registry.npmjs.org/prosemirror-example-setup/-/prosemirror-example-setup-1.1.2.tgz",
             "integrity": "sha512-MTpIMyqk08jFnzxeRMCinCEMtVSTUtxKgQBGxfCbVe9C6zIOqp9qZZJz5Ojaad1GETySyuj8+OIHHvQsIaaaGQ==",
             "requires": {
-                "prosemirror-commands": "^1.0.0",
-                "prosemirror-dropcursor": "^1.0.0",
-                "prosemirror-gapcursor": "^1.0.0",
-                "prosemirror-history": "^1.0.0",
-                "prosemirror-inputrules": "^1.0.0",
-                "prosemirror-keymap": "^1.0.0",
-                "prosemirror-menu": "^1.0.0",
-                "prosemirror-schema-list": "^1.0.0",
-                "prosemirror-state": "^1.0.0"
+                "prosemirror-commands": "1.1.3",
+                "prosemirror-dropcursor": "1.3.2",
+                "prosemirror-gapcursor": "1.1.2",
+                "prosemirror-history": "1.1.3",
+                "prosemirror-inputrules": "1.1.2",
+                "prosemirror-keymap": "1.1.3",
+                "prosemirror-menu": "1.1.2",
+                "prosemirror-schema-list": "1.1.2",
+                "prosemirror-state": "1.3.2"
             }
         },
         "prosemirror-find-replace": {
@@ -12954,10 +12990,10 @@
             "resolved": "https://registry.npmjs.org/prosemirror-gapcursor/-/prosemirror-gapcursor-1.1.2.tgz",
             "integrity": "sha512-Z+eqk6RysZVxidGWN5aWoSTbn5bTHf1XZ+nQJVwUSdwdBVkfQMFdTHgfrXA8W5MhHHdNg/EEEYG3z3Zi/vE2QQ==",
             "requires": {
-                "prosemirror-keymap": "^1.0.0",
-                "prosemirror-model": "^1.0.0",
-                "prosemirror-state": "^1.0.0",
-                "prosemirror-view": "^1.0.0"
+                "prosemirror-keymap": "1.1.3",
+                "prosemirror-model": "1.9.1",
+                "prosemirror-state": "1.3.2",
+                "prosemirror-view": "1.13.8"
             }
         },
         "prosemirror-history": {
@@ -12965,9 +13001,9 @@
             "resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.1.3.tgz",
             "integrity": "sha512-zGDotijea+vnfnyyUGyiy1wfOQhf0B/b6zYcCouBV8yo6JmrE9X23M5q7Nf/nATywEZbgRLG70R4DmfSTC+gfg==",
             "requires": {
-                "prosemirror-state": "^1.2.2",
-                "prosemirror-transform": "^1.0.0",
-                "rope-sequence": "^1.3.0"
+                "prosemirror-state": "1.3.2",
+                "prosemirror-transform": "1.2.3",
+                "rope-sequence": "1.3.2"
             }
         },
         "prosemirror-inputrules": {
@@ -12975,8 +13011,8 @@
             "resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.1.2.tgz",
             "integrity": "sha512-Ja5Z3BWestlHYGvtSGqyvxMeB8QEuBjlHM8YnKtLGUXMDp965qdDV4goV8lJb17kIWHk7e7JNj6Catuoa3302g==",
             "requires": {
-                "prosemirror-state": "^1.0.0",
-                "prosemirror-transform": "^1.0.0"
+                "prosemirror-state": "1.3.2",
+                "prosemirror-transform": "1.2.3"
             }
         },
         "prosemirror-keymap": {
@@ -12984,8 +13020,8 @@
             "resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.1.3.tgz",
             "integrity": "sha512-PRA4NzkUMzV/NFf5pyQ6tmlIHiW/qjQ1kGWUlV2rF/dvlOxtpGpTEjIMhWgLuMf+HiDEFnUEP7uhYXu+t+491g==",
             "requires": {
-                "prosemirror-state": "^1.0.0",
-                "w3c-keyname": "^2.2.0"
+                "prosemirror-state": "1.3.2",
+                "w3c-keyname": "2.2.2"
             }
         },
         "prosemirror-menu": {
@@ -12993,10 +13029,10 @@
             "resolved": "https://registry.npmjs.org/prosemirror-menu/-/prosemirror-menu-1.1.2.tgz",
             "integrity": "sha512-iAPBMnxaj0AXzqgzxrJPrjo5njIqUaDQjyS17R/vb6zIBnEtH1ZDPanrmZnYkBEFvvM4fBtzDZSQct5iJNTcDQ==",
             "requires": {
-                "crel": "^3.1.0",
-                "prosemirror-commands": "^1.0.0",
-                "prosemirror-history": "^1.0.0",
-                "prosemirror-state": "^1.0.0"
+                "crel": "3.1.0",
+                "prosemirror-commands": "1.1.3",
+                "prosemirror-history": "1.1.3",
+                "prosemirror-state": "1.3.2"
             }
         },
         "prosemirror-model": {
@@ -13004,7 +13040,7 @@
             "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.9.1.tgz",
             "integrity": "sha512-Qblh8pm1c7Ll64sYLauwwzjimo/tFg1zW3Q3IWhKRhvfOEgRKqa6dC5pRrAa+XHOIjBFEYrqbi52J5bqA2dV8Q==",
             "requires": {
-                "orderedmap": "^1.1.0"
+                "orderedmap": "1.1.1"
             }
         },
         "prosemirror-schema-basic": {
@@ -13012,7 +13048,7 @@
             "resolved": "https://registry.npmjs.org/prosemirror-schema-basic/-/prosemirror-schema-basic-1.1.2.tgz",
             "integrity": "sha512-G4q8WflNsR1Q33QAV4MQO0xWrHLOJ+BQcKswGXMy626wlQj6c/1n1v4eC9ns+h2y1r/fJHZEgSZnsNhm9lbrDw==",
             "requires": {
-                "prosemirror-model": "^1.2.0"
+                "prosemirror-model": "1.9.1"
             }
         },
         "prosemirror-schema-list": {
@@ -13020,8 +13056,8 @@
             "resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.1.2.tgz",
             "integrity": "sha512-dgM9PwtM4twa5WsgSYMB+J8bwjnR43DAD3L9MsR9rKm/nZR5Y85xcjB7gusVMSsbQ2NomMZF03RE6No6mTnclQ==",
             "requires": {
-                "prosemirror-model": "^1.0.0",
-                "prosemirror-transform": "^1.0.0"
+                "prosemirror-model": "1.9.1",
+                "prosemirror-transform": "1.2.3"
             }
         },
         "prosemirror-state": {
@@ -13029,8 +13065,8 @@
             "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.3.2.tgz",
             "integrity": "sha512-t/JqE3aR0SV9QrzFVkAXsQwsgrQBNs/BDbcFH20RssW0xauqNNdjTXxy/J/kM7F+0zYi6+BRmz7cMMQQFU3mwQ==",
             "requires": {
-                "prosemirror-model": "^1.0.0",
-                "prosemirror-transform": "^1.0.0"
+                "prosemirror-model": "1.9.1",
+                "prosemirror-transform": "1.2.3"
             }
         },
         "prosemirror-transform": {
@@ -13038,7 +13074,7 @@
             "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.2.3.tgz",
             "integrity": "sha512-PUfayeskQfuUBXktvL6207ZWRwHBFNPNPiek4fR+LgCPnBofuEb2+L0FfbNtrAwffHVs6M3DaFvJB1W2VQdV0A==",
             "requires": {
-                "prosemirror-model": "^1.0.0"
+                "prosemirror-model": "1.9.1"
             }
         },
         "prosemirror-view": {
@@ -13046,9 +13082,9 @@
             "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.13.8.tgz",
             "integrity": "sha512-gz3msc9ocWo0m7PxCeNTLxaBZ8VSD9J+A/x16GwXnROAtyKalEMvZInwk+lH2kIEIM3tC/m90xe5U/vf+vM9IA==",
             "requires": {
-                "prosemirror-model": "^1.1.0",
-                "prosemirror-state": "^1.0.0",
-                "prosemirror-transform": "^1.1.0"
+                "prosemirror-model": "1.9.1",
+                "prosemirror-state": "1.3.2",
+                "prosemirror-transform": "1.2.3"
             }
         },
         "proxy-addr": {
@@ -13056,7 +13092,7 @@
             "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz",
             "integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==",
             "requires": {
-                "forwarded": "~0.1.2",
+                "forwarded": "0.1.2",
                 "ipaddr.js": "1.9.0"
             }
         },
@@ -13086,12 +13122,12 @@
             "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz",
             "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==",
             "requires": {
-                "bn.js": "^4.1.0",
-                "browserify-rsa": "^4.0.0",
-                "create-hash": "^1.1.0",
-                "parse-asn1": "^5.0.0",
-                "randombytes": "^2.0.1",
-                "safe-buffer": "^5.1.2"
+                "bn.js": "4.11.8",
+                "browserify-rsa": "4.0.1",
+                "create-hash": "1.2.0",
+                "parse-asn1": "5.1.4",
+                "randombytes": "2.1.0",
+                "safe-buffer": "5.1.2"
             }
         },
         "pug": {
@@ -13099,14 +13135,14 @@
             "resolved": "https://registry.npmjs.org/pug/-/pug-2.0.4.tgz",
             "integrity": "sha512-XhoaDlvi6NIzL49nu094R2NA6P37ijtgMDuWE+ofekDChvfKnzFal60bhSdiy8y2PBO6fmz3oMEIcfpBVRUdvw==",
             "requires": {
-                "pug-code-gen": "^2.0.2",
-                "pug-filters": "^3.1.1",
-                "pug-lexer": "^4.1.0",
-                "pug-linker": "^3.0.6",
-                "pug-load": "^2.0.12",
-                "pug-parser": "^5.0.1",
-                "pug-runtime": "^2.0.5",
-                "pug-strip-comments": "^1.0.4"
+                "pug-code-gen": "2.0.2",
+                "pug-filters": "3.1.1",
+                "pug-lexer": "4.1.0",
+                "pug-linker": "3.0.6",
+                "pug-load": "2.0.12",
+                "pug-parser": "5.0.1",
+                "pug-runtime": "2.0.5",
+                "pug-strip-comments": "1.0.4"
             }
         },
         "pug-attrs": {
@@ -13114,9 +13150,9 @@
             "resolved": "https://registry.npmjs.org/pug-attrs/-/pug-attrs-2.0.4.tgz",
             "integrity": "sha512-TaZ4Z2TWUPDJcV3wjU3RtUXMrd3kM4Wzjbe3EWnSsZPsJ3LDI0F3yCnf2/W7PPFF+edUFQ0HgDL1IoxSz5K8EQ==",
             "requires": {
-                "constantinople": "^3.0.1",
-                "js-stringify": "^1.0.1",
-                "pug-runtime": "^2.0.5"
+                "constantinople": "3.1.2",
+                "js-stringify": "1.0.2",
+                "pug-runtime": "2.0.5"
             }
         },
         "pug-code-gen": {
@@ -13124,14 +13160,14 @@
             "resolved": "https://registry.npmjs.org/pug-code-gen/-/pug-code-gen-2.0.2.tgz",
             "integrity": "sha512-kROFWv/AHx/9CRgoGJeRSm+4mLWchbgpRzTEn8XCiwwOy6Vh0gAClS8Vh5TEJ9DBjaP8wCjS3J6HKsEsYdvaCw==",
             "requires": {
-                "constantinople": "^3.1.2",
-                "doctypes": "^1.1.0",
-                "js-stringify": "^1.0.1",
-                "pug-attrs": "^2.0.4",
-                "pug-error": "^1.3.3",
-                "pug-runtime": "^2.0.5",
-                "void-elements": "^2.0.1",
-                "with": "^5.0.0"
+                "constantinople": "3.1.2",
+                "doctypes": "1.1.0",
+                "js-stringify": "1.0.2",
+                "pug-attrs": "2.0.4",
+                "pug-error": "1.3.3",
+                "pug-runtime": "2.0.5",
+                "void-elements": "2.0.1",
+                "with": "5.1.1"
             }
         },
         "pug-error": {
@@ -13144,13 +13180,13 @@
             "resolved": "https://registry.npmjs.org/pug-filters/-/pug-filters-3.1.1.tgz",
             "integrity": "sha512-lFfjNyGEyVWC4BwX0WyvkoWLapI5xHSM3xZJFUhx4JM4XyyRdO8Aucc6pCygnqV2uSgJFaJWW3Ft1wCWSoQkQg==",
             "requires": {
-                "clean-css": "^4.1.11",
-                "constantinople": "^3.0.1",
+                "clean-css": "4.2.1",
+                "constantinople": "3.1.2",
                 "jstransformer": "1.0.0",
-                "pug-error": "^1.3.3",
-                "pug-walk": "^1.1.8",
-                "resolve": "^1.1.6",
-                "uglify-js": "^2.6.1"
+                "pug-error": "1.3.3",
+                "pug-walk": "1.1.8",
+                "resolve": "1.11.1",
+                "uglify-js": "2.8.29"
             }
         },
         "pug-lexer": {
@@ -13158,9 +13194,9 @@
             "resolved": "https://registry.npmjs.org/pug-lexer/-/pug-lexer-4.1.0.tgz",
             "integrity": "sha512-i55yzEBtjm0mlplW4LoANq7k3S8gDdfC6+LThGEvsK4FuobcKfDAwt6V4jKPH9RtiE3a2Akfg5UpafZ1OksaPA==",
             "requires": {
-                "character-parser": "^2.1.1",
-                "is-expression": "^3.0.0",
-                "pug-error": "^1.3.3"
+                "character-parser": "2.2.0",
+                "is-expression": "3.0.0",
+                "pug-error": "1.3.3"
             }
         },
         "pug-linker": {
@@ -13168,8 +13204,8 @@
             "resolved": "https://registry.npmjs.org/pug-linker/-/pug-linker-3.0.6.tgz",
             "integrity": "sha512-bagfuHttfQOpANGy1Y6NJ+0mNb7dD2MswFG2ZKj22s8g0wVsojpRlqveEQHmgXXcfROB2RT6oqbPYr9EN2ZWzg==",
             "requires": {
-                "pug-error": "^1.3.3",
-                "pug-walk": "^1.1.8"
+                "pug-error": "1.3.3",
+                "pug-walk": "1.1.8"
             }
         },
         "pug-load": {
@@ -13177,8 +13213,8 @@
             "resolved": "https://registry.npmjs.org/pug-load/-/pug-load-2.0.12.tgz",
             "integrity": "sha512-UqpgGpyyXRYgJs/X60sE6SIf8UBsmcHYKNaOccyVLEuT6OPBIMo6xMPhoJnqtB3Q3BbO4Z3Bjz5qDsUWh4rXsg==",
             "requires": {
-                "object-assign": "^4.1.0",
-                "pug-walk": "^1.1.8"
+                "object-assign": "4.1.1",
+                "pug-walk": "1.1.8"
             }
         },
         "pug-parser": {
@@ -13186,7 +13222,7 @@
             "resolved": "https://registry.npmjs.org/pug-parser/-/pug-parser-5.0.1.tgz",
             "integrity": "sha512-nGHqK+w07p5/PsPIyzkTQfzlYfuqoiGjaoqHv1LjOv2ZLXmGX1O+4Vcvps+P4LhxZ3drYSljjq4b+Naid126wA==",
             "requires": {
-                "pug-error": "^1.3.3",
+                "pug-error": "1.3.3",
                 "token-stream": "0.0.1"
             }
         },
@@ -13200,7 +13236,7 @@
             "resolved": "https://registry.npmjs.org/pug-strip-comments/-/pug-strip-comments-1.0.4.tgz",
             "integrity": "sha512-i5j/9CS4yFhSxHp5iKPHwigaig/VV9g+FgReLJWWHEHbvKsbqL0oP/K5ubuLco6Wu3Kan5p7u7qk8A4oLLh6vw==",
             "requires": {
-                "pug-error": "^1.3.3"
+                "pug-error": "1.3.3"
             }
         },
         "pug-walk": {
@@ -13214,8 +13250,8 @@
             "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==",
             "dev": true,
             "requires": {
-                "end-of-stream": "^1.1.0",
-                "once": "^1.3.1"
+                "end-of-stream": "1.4.1",
+                "once": "1.4.0"
             }
         },
         "pumpify": {
@@ -13224,9 +13260,9 @@
             "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==",
             "dev": true,
             "requires": {
-                "duplexify": "^3.6.0",
-                "inherits": "^2.0.3",
-                "pump": "^2.0.0"
+                "duplexify": "3.7.1",
+                "inherits": "2.0.3",
+                "pump": "2.0.1"
             }
         },
         "punycode": {
@@ -13249,9 +13285,9 @@
             "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.10.1.tgz",
             "integrity": "sha512-SHTUV6gDlgMXg/AQUuLpTiBtW/etZ9JT6k6RCtCyqADquApLX0Aq5oK/s5UeTUAWBG50IExjIr587GqfXRfM4A==",
             "requires": {
-                "decode-uri-component": "^0.2.0",
-                "split-on-first": "^1.0.0",
-                "strict-uri-encode": "^2.0.0"
+                "decode-uri-component": "0.2.0",
+                "split-on-first": "1.1.0",
+                "strict-uri-encode": "2.0.0"
             }
         },
         "querystring": {
@@ -13282,7 +13318,7 @@
             "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
             "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
             "requires": {
-                "safe-buffer": "^5.1.0"
+                "safe-buffer": "5.1.2"
             }
         },
         "randomfill": {
@@ -13290,8 +13326,8 @@
             "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz",
             "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==",
             "requires": {
-                "randombytes": "^2.0.5",
-                "safe-buffer": "^5.1.0"
+                "randombytes": "2.1.0",
+                "safe-buffer": "5.1.2"
             }
         },
         "range-parser": {
@@ -13315,8 +13351,8 @@
             "resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-1.0.0.tgz",
             "integrity": "sha512-Uqy5AqELpytJTRxYT4fhltcKPj0TyaEpzJDcGz7DFJi+pQOOi3GjR/DOdxTkTsF+NzhnldIoG6TORaBlInUuqA==",
             "requires": {
-                "loader-utils": "^1.1.0",
-                "schema-utils": "^1.0.0"
+                "loader-utils": "1.2.3",
+                "schema-utils": "1.0.0"
             },
             "dependencies": {
                 "schema-utils": {
@@ -13324,9 +13360,9 @@
                     "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz",
                     "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==",
                     "requires": {
-                        "ajv": "^6.1.0",
-                        "ajv-errors": "^1.0.0",
-                        "ajv-keywords": "^3.1.0"
+                        "ajv": "6.10.2",
+                        "ajv-errors": "1.0.1",
+                        "ajv-keywords": "3.4.1"
                     }
                 }
             }
@@ -13336,10 +13372,10 @@
             "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
             "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
             "requires": {
-                "deep-extend": "^0.6.0",
-                "ini": "~1.3.0",
-                "minimist": "^1.2.0",
-                "strip-json-comments": "~2.0.1"
+                "deep-extend": "0.6.0",
+                "ini": "1.3.5",
+                "minimist": "1.2.0",
+                "strip-json-comments": "2.0.1"
             },
             "dependencies": {
                 "minimist": {
@@ -13354,9 +13390,9 @@
             "resolved": "https://registry.npmjs.org/rc-switch/-/rc-switch-1.9.0.tgz",
             "integrity": "sha512-Isas+egaK6qSk64jaEw4GgPStY4umYDbT7ZY93bZF1Af+b/JEsKsJdNOU2qG3WI0Z6tXo2DDq0kJCv8Yhu0zww==",
             "requires": {
-                "classnames": "^2.2.1",
-                "prop-types": "^15.5.6",
-                "react-lifecycles-compat": "^3.0.4"
+                "classnames": "2.2.6",
+                "prop-types": "15.7.2",
+                "react-lifecycles-compat": "3.0.4"
             }
         },
         "react": {
@@ -13364,9 +13400,9 @@
             "resolved": "https://registry.npmjs.org/react/-/react-16.12.0.tgz",
             "integrity": "sha512-fglqy3k5E+81pA8s+7K0/T3DBCF0ZDOher1elBFzF7O6arXJgzyu/FW+COxFvAWXJoJN9KIZbT2LXlukwphYTA==",
             "requires": {
-                "loose-envify": "^1.1.0",
-                "object-assign": "^4.1.1",
-                "prop-types": "^15.6.2"
+                "loose-envify": "1.4.0",
+                "object-assign": "4.1.1",
+                "prop-types": "15.7.2"
             }
         },
         "react-anime": {
@@ -13374,8 +13410,8 @@
             "resolved": "https://registry.npmjs.org/react-anime/-/react-anime-2.3.2.tgz",
             "integrity": "sha512-Wh9p1EdU5NTc2lyemHlaGG0QKuUYhpeGbh1Auw14Be+vukIkbCIaBMOyFya0Q7SIjSPzTNNpIgnD3oIv8TXWiw==",
             "requires": {
-                "animejs": "^3.1.0",
-                "lodash.isequal": "^4.5.0"
+                "animejs": "3.1.0",
+                "lodash.isequal": "4.5.0"
             }
         },
         "react-autosuggest": {
@@ -13383,9 +13419,9 @@
             "resolved": "https://registry.npmjs.org/react-autosuggest/-/react-autosuggest-9.4.3.tgz",
             "integrity": "sha512-wFbp5QpgFQRfw9cwKvcgLR8theikOUkv8PFsuLYqI2PUgVlx186Cz8MYt5bLxculi+jxGGUUVt+h0esaBZZouw==",
             "requires": {
-                "prop-types": "^15.5.10",
-                "react-autowhatever": "^10.1.2",
-                "shallow-equal": "^1.0.0"
+                "prop-types": "15.7.2",
+                "react-autowhatever": "10.2.0",
+                "shallow-equal": "1.2.0"
             }
         },
         "react-autowhatever": {
@@ -13393,9 +13429,9 @@
             "resolved": "https://registry.npmjs.org/react-autowhatever/-/react-autowhatever-10.2.0.tgz",
             "integrity": "sha512-dqHH4uqiJldPMbL8hl/i2HV4E8FMTDEdVlOIbRqYnJi0kTpWseF9fJslk/KS9pGDnm80JkYzVI+nzFjnOG/u+g==",
             "requires": {
-                "prop-types": "^15.5.8",
-                "react-themeable": "^1.1.0",
-                "section-iterator": "^2.0.0"
+                "prop-types": "15.7.2",
+                "react-themeable": "1.1.0",
+                "section-iterator": "2.0.0"
             }
         },
         "react-bootstrap": {
@@ -13403,21 +13439,21 @@
             "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-1.0.0-beta.16.tgz",
             "integrity": "sha512-wjb+3CwviDWAaz4O3gQpd2XMDNqbOiqOOzpLm5aLPcp1wTsQsVRhyM+rTPmO3hYU8auA2eNpTYLz08/fAcMqDA==",
             "requires": {
-                "@babel/runtime": "^7.4.2",
-                "@restart/context": "^2.1.4",
-                "@restart/hooks": "^0.3.11",
-                "@types/react": "^16.8.23",
-                "classnames": "^2.2.6",
-                "dom-helpers": "^5.1.2",
-                "invariant": "^2.2.4",
-                "keycode": "^2.2.0",
-                "popper.js": "^1.16.0",
-                "prop-types": "^15.7.2",
-                "prop-types-extra": "^1.1.0",
-                "react-overlays": "^2.1.0",
-                "react-transition-group": "^4.0.0",
-                "uncontrollable": "^7.0.0",
-                "warning": "^4.0.3"
+                "@babel/runtime": "7.5.5",
+                "@restart/context": "2.1.4",
+                "@restart/hooks": "0.3.19",
+                "@types/react": "16.9.19",
+                "classnames": "2.2.6",
+                "dom-helpers": "5.1.3",
+                "invariant": "2.2.4",
+                "keycode": "2.2.0",
+                "popper.js": "1.16.0",
+                "prop-types": "15.7.2",
+                "prop-types-extra": "1.1.0",
+                "react-overlays": "2.1.0",
+                "react-transition-group": "4.3.0",
+                "uncontrollable": "7.1.1",
+                "warning": "4.0.3"
             },
             "dependencies": {
                 "csstype": {
@@ -13430,8 +13466,8 @@
                     "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.1.3.tgz",
                     "integrity": "sha512-nZD1OtwfWGRBWlpANxacBEZrEuLa16o1nh7YopFWeoF68Zt8GGEmzHu6Xv4F3XaFIC+YXtTLrzgqKxFgLEe4jw==",
                     "requires": {
-                        "@babel/runtime": "^7.6.3",
-                        "csstype": "^2.6.7"
+                        "@babel/runtime": "7.7.7",
+                        "csstype": "2.6.8"
                     },
                     "dependencies": {
                         "@babel/runtime": {
@@ -13439,7 +13475,7 @@
                             "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.7.7.tgz",
                             "integrity": "sha512-uCnC2JEVAu8AKB5do1WRIsvrdJ0flYx/A/9f/6chdacnEZ7LmavjdsDXr5ksYBegxtuTPR5Va9/+13QF/kFkCA==",
                             "requires": {
-                                "regenerator-runtime": "^0.13.2"
+                                "regenerator-runtime": "0.13.3"
                             }
                         }
                     }
@@ -13449,10 +13485,10 @@
                     "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.3.0.tgz",
                     "integrity": "sha512-1qRV1ZuVSdxPlPf4O8t7inxUGpdyO5zG9IoNfJxSO0ImU2A1YWkEQvFPuIPZmMLkg5hYs7vv5mMOyfgSkvAwvw==",
                     "requires": {
-                        "@babel/runtime": "^7.5.5",
-                        "dom-helpers": "^5.0.1",
-                        "loose-envify": "^1.4.0",
-                        "prop-types": "^15.6.2"
+                        "@babel/runtime": "7.5.5",
+                        "dom-helpers": "5.1.3",
+                        "loose-envify": "1.4.0",
+                        "prop-types": "15.7.2"
                     }
                 },
                 "uncontrollable": {
@@ -13460,10 +13496,10 @@
                     "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.1.1.tgz",
                     "integrity": "sha512-EcPYhot3uWTS3w00R32R2+vS8Vr53tttrvMj/yA1uYRhf8hbTG2GyugGqWDY0qIskxn0uTTojVd6wPYW9ZEf8Q==",
                     "requires": {
-                        "@babel/runtime": "^7.6.3",
-                        "@types/react": "^16.9.11",
-                        "invariant": "^2.2.4",
-                        "react-lifecycles-compat": "^3.0.4"
+                        "@babel/runtime": "7.7.7",
+                        "@types/react": "16.9.19",
+                        "invariant": "2.2.4",
+                        "react-lifecycles-compat": "3.0.4"
                     },
                     "dependencies": {
                         "@babel/runtime": {
@@ -13471,7 +13507,7 @@
                             "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.7.7.tgz",
                             "integrity": "sha512-uCnC2JEVAu8AKB5do1WRIsvrdJ0flYx/A/9f/6chdacnEZ7LmavjdsDXr5ksYBegxtuTPR5Va9/+13QF/kFkCA==",
                             "requires": {
-                                "regenerator-runtime": "^0.13.2"
+                                "regenerator-runtime": "0.13.3"
                             }
                         }
                     }
@@ -13481,7 +13517,7 @@
                     "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
                     "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
                     "requires": {
-                        "loose-envify": "^1.0.0"
+                        "loose-envify": "1.4.0"
                     }
                 }
             }
@@ -13496,12 +13532,12 @@
             "resolved": "https://registry.npmjs.org/react-color/-/react-color-2.18.0.tgz",
             "integrity": "sha512-FyVeU1kQiSokWc8NPz22azl1ezLpJdUyTbWL0LPUpcuuYDrZ/Y1veOk9rRK5B3pMlyDGvTk4f4KJhlkIQNRjEA==",
             "requires": {
-                "@icons/material": "^0.2.4",
-                "lodash": "^4.17.11",
-                "material-colors": "^1.2.1",
-                "prop-types": "^15.5.10",
-                "reactcss": "^1.2.0",
-                "tinycolor2": "^1.4.1"
+                "@icons/material": "0.2.4",
+                "lodash": "4.17.15",
+                "material-colors": "1.2.6",
+                "prop-types": "15.7.2",
+                "reactcss": "1.2.3",
+                "tinycolor2": "1.4.1"
             }
         },
         "react-compound-slider": {
@@ -13509,10 +13545,10 @@
             "resolved": "https://registry.npmjs.org/react-compound-slider/-/react-compound-slider-2.5.0.tgz",
             "integrity": "sha512-T84FtSI0bkQPmH5GaaHbL+2McOyIR6M5sqS80dqw/bHc5r2UKLYY64BWTbsL+XO0jlx7REuJJnZUBqo4eSRl7g==",
             "requires": {
-                "@babel/runtime": "^7.7.7",
-                "d3-array": "^1.2.4",
-                "prop-types": "^15.7.2",
-                "warning": "^3.0.0"
+                "@babel/runtime": "7.8.4",
+                "d3-array": "1.2.4",
+                "prop-types": "15.7.2",
+                "warning": "3.0.0"
             },
             "dependencies": {
                 "@babel/runtime": {
@@ -13520,7 +13556,7 @@
                     "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.4.tgz",
                     "integrity": "sha512-neAp3zt80trRVBI1x0azq6c57aNBqYZH8KhMm3TaB7wEI5Q4A2SHfBHE8w9gOhI/lrqxtEbXZgQIrHP+wvSGwQ==",
                     "requires": {
-                        "regenerator-runtime": "^0.13.2"
+                        "regenerator-runtime": "0.13.3"
                     }
                 }
             }
@@ -13530,7 +13566,7 @@
             "resolved": "https://registry.npmjs.org/react-dimensions/-/react-dimensions-1.3.1.tgz",
             "integrity": "sha512-go5vMuGUxaB5PiTSIk+ZfAxLbHwcIgIfLhkBZ2SIMQjaCgnpttxa30z5ijEzfDjeOCTGRpxvkzcmE4Vt4Ppvyw==",
             "requires": {
-                "element-resize-event": "^2.0.4"
+                "element-resize-event": "2.0.9"
             }
         },
         "react-dom": {
@@ -13538,10 +13574,10 @@
             "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.12.0.tgz",
             "integrity": "sha512-LMxFfAGrcS3kETtQaCkTKjMiifahaMySFDn71fZUNpPHZQEzmk/GiAeIT8JSOrHB23fnuCOMruL2a8NYlw+8Gw==",
             "requires": {
-                "loose-envify": "^1.1.0",
-                "object-assign": "^4.1.1",
-                "prop-types": "^15.6.2",
-                "scheduler": "^0.18.0"
+                "loose-envify": "1.4.0",
+                "object-assign": "4.1.1",
+                "prop-types": "15.7.2",
+                "scheduler": "0.18.0"
             }
         },
         "react-golden-layout": {
@@ -13549,9 +13585,9 @@
             "resolved": "https://registry.npmjs.org/react-golden-layout/-/react-golden-layout-1.0.6.tgz",
             "integrity": "sha512-KZQ17Bnd+LfyCqe2scVMznrGKTciX3VwoT3y4xn3Qok9hknCvVXZfXe2RSX5zNG7FlLJzWt0VWqy8qZBHpQVuQ==",
             "requires": {
-                "golden-layout": "^1.5.9",
-                "react": "^16.3.0",
-                "react-dom": "^16.3.0"
+                "golden-layout": "1.5.9",
+                "react": "16.12.0",
+                "react-dom": "16.12.0"
             }
         },
         "react-image-lightbox-with-rotate": {
@@ -13559,9 +13595,9 @@
             "resolved": "https://registry.npmjs.org/react-image-lightbox-with-rotate/-/react-image-lightbox-with-rotate-5.1.1.tgz",
             "integrity": "sha512-5ZubUQefKSDGIiAwK4lkfmGr/bgIfNDHXqC+Fm6nbNwTVYuYOZ1RJjULOniEB4fxb3Vm0z/x0oNhi1lbP1aMtg==",
             "requires": {
-                "blueimp-load-image": "^2.19.0",
-                "prop-types": "^15.6.1",
-                "react-modal": "^3.4.4"
+                "blueimp-load-image": "2.24.0",
+                "prop-types": "15.7.2",
+                "react-modal": "3.9.1"
             }
         },
         "react-is": {
@@ -13574,8 +13610,8 @@
             "resolved": "https://registry.npmjs.org/react-jsx-parser/-/react-jsx-parser-1.21.0.tgz",
             "integrity": "sha512-3ZYS0+Fiz1hx1owMDvA6skoYnISdsdthKEMLmcoqQ2CF+LzBKnYK/GtMJ5lKKz0AfZiNpsBq8qK7Av2ZE6vEVQ==",
             "requires": {
-                "acorn": "^7.1.0",
-                "acorn-jsx": "^5.1.0"
+                "acorn": "7.1.0",
+                "acorn-jsx": "5.1.0"
             },
             "dependencies": {
                 "acorn": {
@@ -13595,10 +13631,10 @@
             "resolved": "https://registry.npmjs.org/react-measure/-/react-measure-2.3.0.tgz",
             "integrity": "sha512-dwAvmiOeblj5Dvpnk8Jm7Q8B4THF/f1l1HtKVi0XDecsG6LXwGvzV5R1H32kq3TW6RW64OAf5aoQxpIgLa4z8A==",
             "requires": {
-                "@babel/runtime": "^7.2.0",
-                "get-node-dimensions": "^1.2.1",
-                "prop-types": "^15.6.2",
-                "resize-observer-polyfill": "^1.5.0"
+                "@babel/runtime": "7.5.5",
+                "get-node-dimensions": "1.2.1",
+                "prop-types": "15.7.2",
+                "resize-observer-polyfill": "1.5.1"
             }
         },
         "react-modal": {
@@ -13606,10 +13642,10 @@
             "resolved": "https://registry.npmjs.org/react-modal/-/react-modal-3.9.1.tgz",
             "integrity": "sha512-k+TUkhGWpIVHLsEyjNmlyOYL0Uz03fNZvlkhCImd1h+6fhNgTi6H6jexVXPVhD2LMMDzJyfugxMN+APN/em+eQ==",
             "requires": {
-                "exenv": "^1.2.0",
-                "prop-types": "^15.5.10",
-                "react-lifecycles-compat": "^3.0.0",
-                "warning": "^4.0.3"
+                "exenv": "1.2.2",
+                "prop-types": "15.7.2",
+                "react-lifecycles-compat": "3.0.4",
+                "warning": "4.0.3"
             },
             "dependencies": {
                 "warning": {
@@ -13617,7 +13653,7 @@
                     "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
                     "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
                     "requires": {
-                        "loose-envify": "^1.0.0"
+                        "loose-envify": "1.4.0"
                     }
                 }
             }
@@ -13627,8 +13663,8 @@
             "resolved": "https://registry.npmjs.org/react-mosaic/-/react-mosaic-0.0.20.tgz",
             "integrity": "sha1-pSSr8uzyi5r2sh1NNQ/veCLvMJ4=",
             "requires": {
-                "prop-types": "^15.6.0",
-                "threads": "^0.8.0"
+                "prop-types": "15.7.2",
+                "threads": "0.8.1"
             }
         },
         "react-overlays": {
@@ -13636,13 +13672,13 @@
             "resolved": "https://registry.npmjs.org/react-overlays/-/react-overlays-2.1.0.tgz",
             "integrity": "sha512-tHPGTZosbQSo82yb9x4YCsmJJtspKvAPL5kXVnyoB2Z5UoAU3VetIuh2VblfVT408us5nLJd9uDtwI3xWDHS6w==",
             "requires": {
-                "@babel/runtime": "^7.4.5",
-                "@restart/hooks": "^0.3.12",
-                "dom-helpers": "^5.1.0",
-                "popper.js": "^1.15.0",
-                "prop-types": "^15.7.2",
-                "uncontrollable": "^7.0.0",
-                "warning": "^4.0.3"
+                "@babel/runtime": "7.5.5",
+                "@restart/hooks": "0.3.19",
+                "dom-helpers": "5.1.3",
+                "popper.js": "1.16.0",
+                "prop-types": "15.7.2",
+                "uncontrollable": "7.1.1",
+                "warning": "4.0.3"
             },
             "dependencies": {
                 "csstype": {
@@ -13655,8 +13691,8 @@
                     "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.1.3.tgz",
                     "integrity": "sha512-nZD1OtwfWGRBWlpANxacBEZrEuLa16o1nh7YopFWeoF68Zt8GGEmzHu6Xv4F3XaFIC+YXtTLrzgqKxFgLEe4jw==",
                     "requires": {
-                        "@babel/runtime": "^7.6.3",
-                        "csstype": "^2.6.7"
+                        "@babel/runtime": "7.7.7",
+                        "csstype": "2.6.8"
                     },
                     "dependencies": {
                         "@babel/runtime": {
@@ -13664,7 +13700,7 @@
                             "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.7.7.tgz",
                             "integrity": "sha512-uCnC2JEVAu8AKB5do1WRIsvrdJ0flYx/A/9f/6chdacnEZ7LmavjdsDXr5ksYBegxtuTPR5Va9/+13QF/kFkCA==",
                             "requires": {
-                                "regenerator-runtime": "^0.13.2"
+                                "regenerator-runtime": "0.13.3"
                             }
                         }
                     }
@@ -13674,10 +13710,10 @@
                     "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.1.1.tgz",
                     "integrity": "sha512-EcPYhot3uWTS3w00R32R2+vS8Vr53tttrvMj/yA1uYRhf8hbTG2GyugGqWDY0qIskxn0uTTojVd6wPYW9ZEf8Q==",
                     "requires": {
-                        "@babel/runtime": "^7.6.3",
-                        "@types/react": "^16.9.11",
-                        "invariant": "^2.2.4",
-                        "react-lifecycles-compat": "^3.0.4"
+                        "@babel/runtime": "7.7.7",
+                        "@types/react": "16.9.19",
+                        "invariant": "2.2.4",
+                        "react-lifecycles-compat": "3.0.4"
                     },
                     "dependencies": {
                         "@babel/runtime": {
@@ -13685,7 +13721,7 @@
                             "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.7.7.tgz",
                             "integrity": "sha512-uCnC2JEVAu8AKB5do1WRIsvrdJ0flYx/A/9f/6chdacnEZ7LmavjdsDXr5ksYBegxtuTPR5Va9/+13QF/kFkCA==",
                             "requires": {
-                                "regenerator-runtime": "^0.13.2"
+                                "regenerator-runtime": "0.13.3"
                             }
                         }
                     }
@@ -13695,7 +13731,7 @@
                     "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
                     "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
                     "requires": {
-                        "loose-envify": "^1.0.0"
+                        "loose-envify": "1.4.0"
                     }
                 }
             }
@@ -13705,8 +13741,8 @@
             "resolved": "https://registry.npmjs.org/react-simple-dropdown/-/react-simple-dropdown-3.2.3.tgz",
             "integrity": "sha512-NmyyvA0D4wph5ctzkn8U4wmblOacavJMl9gTOhQR3v8I997mc1FL1NFKkj3Mx+HNysBKRD/HI+kpxXCAgXumPw==",
             "requires": {
-                "classnames": "^2.1.2",
-                "prop-types": "^15.5.8"
+                "classnames": "2.2.6",
+                "prop-types": "15.7.2"
             }
         },
         "react-split-pane": {
@@ -13714,9 +13750,9 @@
             "resolved": "https://registry.npmjs.org/react-split-pane/-/react-split-pane-0.1.89.tgz",
             "integrity": "sha512-bGEiOevi6nBE1evEJOsZjd5A7plLboFAU4+HGASWWVm94XUg7QdsuPInGOB+5Ym4RtY3TZCpmUvLe6qQmrZUOg==",
             "requires": {
-                "prop-types": "^15.5.10",
-                "react-lifecycles-compat": "^3.0.4",
-                "react-style-proptype": "^3.0.0"
+                "prop-types": "15.7.2",
+                "react-lifecycles-compat": "3.0.4",
+                "react-style-proptype": "3.2.2"
             }
         },
         "react-style-proptype": {
@@ -13724,7 +13760,7 @@
             "resolved": "https://registry.npmjs.org/react-style-proptype/-/react-style-proptype-3.2.2.tgz",
             "integrity": "sha512-ywYLSjNkxKHiZOqNlso9PZByNEY+FTyh3C+7uuziK0xFXu9xzdyfHwg4S9iyiRRoPCR4k2LqaBBsWVmSBwCWYQ==",
             "requires": {
-                "prop-types": "^15.5.4"
+                "prop-types": "15.7.2"
             }
         },
         "react-table": {
@@ -13732,9 +13768,9 @@
             "resolved": "https://registry.npmjs.org/react-table/-/react-table-6.11.5.tgz",
             "integrity": "sha512-LM+AS9v//7Y7lAlgTWW/cW6Sn5VOb3EsSkKQfQTzOW8FngB1FUskLLNEVkAYsTX9LjOWR3QlGjykJqCE6eXT/g==",
             "requires": {
-                "@types/react-table": "^6.8.5",
-                "classnames": "^2.2.5",
-                "react-is": "^16.8.1"
+                "@types/react-table": "6.8.6",
+                "classnames": "2.2.6",
+                "react-is": "16.8.6"
             }
         },
         "react-themeable": {
@@ -13742,7 +13778,7 @@
             "resolved": "https://registry.npmjs.org/react-themeable/-/react-themeable-1.1.0.tgz",
             "integrity": "sha1-fURm3ZsrX6dQWHJ4JenxUro3mg4=",
             "requires": {
-                "object-assign": "^3.0.0"
+                "object-assign": "3.0.0"
             },
             "dependencies": {
                 "object-assign": {
@@ -13757,10 +13793,10 @@
             "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz",
             "integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==",
             "requires": {
-                "dom-helpers": "^3.4.0",
-                "loose-envify": "^1.4.0",
-                "prop-types": "^15.6.2",
-                "react-lifecycles-compat": "^3.0.4"
+                "dom-helpers": "3.4.0",
+                "loose-envify": "1.4.0",
+                "prop-types": "15.7.2",
+                "react-lifecycles-compat": "3.0.4"
             }
         },
         "reactcss": {
@@ -13768,7 +13804,7 @@
             "resolved": "https://registry.npmjs.org/reactcss/-/reactcss-1.2.3.tgz",
             "integrity": "sha512-KiwVUcFu1RErkI97ywr8nvx8dNOpT03rbnma0SSalTYjkrPYaEajR4a/MRt6DZ46K6arDRbWMNHF+xH7G7n/8A==",
             "requires": {
-                "lodash": "^4.0.1"
+                "lodash": "4.17.15"
             }
         },
         "read-pkg": {
@@ -13776,9 +13812,9 @@
             "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
             "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=",
             "requires": {
-                "load-json-file": "^1.0.0",
-                "normalize-package-data": "^2.3.2",
-                "path-type": "^1.0.0"
+                "load-json-file": "1.1.0",
+                "normalize-package-data": "2.5.0",
+                "path-type": "1.1.0"
             }
         },
         "read-pkg-up": {
@@ -13786,8 +13822,8 @@
             "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz",
             "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=",
             "requires": {
-                "find-up": "^1.0.0",
-                "read-pkg": "^1.0.0"
+                "find-up": "1.1.2",
+                "read-pkg": "1.1.0"
             },
             "dependencies": {
                 "find-up": {
@@ -13795,8 +13831,8 @@
                     "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz",
                     "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=",
                     "requires": {
-                        "path-exists": "^2.0.0",
-                        "pinkie-promise": "^2.0.0"
+                        "path-exists": "2.1.0",
+                        "pinkie-promise": "2.0.1"
                     }
                 },
                 "path-exists": {
@@ -13804,23 +13840,23 @@
                     "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz",
                     "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=",
                     "requires": {
-                        "pinkie-promise": "^2.0.0"
+                        "pinkie-promise": "2.0.1"
                     }
                 }
             }
         },
         "readable-stream": {
             "version": "2.3.6",
-            "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+            "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
             "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
             "requires": {
-                "core-util-is": "~1.0.0",
-                "inherits": "~2.0.3",
-                "isarray": "~1.0.0",
-                "process-nextick-args": "~2.0.0",
-                "safe-buffer": "~5.1.1",
-                "string_decoder": "~1.1.1",
-                "util-deprecate": "~1.0.1"
+                "core-util-is": "1.0.2",
+                "inherits": "2.0.3",
+                "isarray": "1.0.0",
+                "process-nextick-args": "2.0.1",
+                "safe-buffer": "5.1.2",
+                "string_decoder": "1.1.1",
+                "util-deprecate": "1.0.2"
             }
         },
         "readdirp": {
@@ -13828,9 +13864,9 @@
             "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz",
             "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==",
             "requires": {
-                "graceful-fs": "^4.1.11",
-                "micromatch": "^3.1.10",
-                "readable-stream": "^2.0.2"
+                "graceful-fs": "4.2.0",
+                "micromatch": "3.1.10",
+                "readable-stream": "2.3.6"
             }
         },
         "readline": {
@@ -13844,9 +13880,9 @@
             "integrity": "sha1-RR/TAEqx5N+bTktmN2sqIZEkYtM=",
             "requires": {
                 "ast-types": "0.9.6",
-                "esprima": "~3.1.0",
-                "private": "~0.1.5",
-                "source-map": "~0.5.0"
+                "esprima": "3.1.3",
+                "private": "0.1.8",
+                "source-map": "0.5.7"
             },
             "dependencies": {
                 "esprima": {
@@ -13861,7 +13897,7 @@
             "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz",
             "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=",
             "requires": {
-                "resolve": "^1.1.6"
+                "resolve": "1.11.1"
             }
         },
         "redent": {
@@ -13869,8 +13905,8 @@
             "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz",
             "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=",
             "requires": {
-                "indent-string": "^2.1.0",
-                "strip-indent": "^1.0.1"
+                "indent-string": "2.1.0",
+                "strip-indent": "1.0.1"
             }
         },
         "reduce-flatten": {
@@ -13888,8 +13924,8 @@
             "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz",
             "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==",
             "requires": {
-                "extend-shallow": "^3.0.2",
-                "safe-regex": "^1.1.0"
+                "extend-shallow": "3.0.2",
+                "safe-regex": "1.1.0"
             }
         },
         "regexp-clone": {
@@ -13903,8 +13939,8 @@
             "integrity": "sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==",
             "dev": true,
             "requires": {
-                "define-properties": "^1.1.3",
-                "es-abstract": "^1.17.0-next.1"
+                "define-properties": "1.1.3",
+                "es-abstract": "1.17.0"
             },
             "dependencies": {
                 "es-abstract": {
@@ -13913,17 +13949,17 @@
                     "integrity": "sha512-yYkE07YF+6SIBmg1MsJ9dlub5L48Ek7X0qz+c/CPCHS9EBXfESorzng4cJQjJW5/pB6vDF41u7F8vUhLVDqIug==",
                     "dev": true,
                     "requires": {
-                        "es-to-primitive": "^1.2.1",
-                        "function-bind": "^1.1.1",
-                        "has": "^1.0.3",
-                        "has-symbols": "^1.0.1",
-                        "is-callable": "^1.1.5",
-                        "is-regex": "^1.0.5",
-                        "object-inspect": "^1.7.0",
-                        "object-keys": "^1.1.1",
-                        "object.assign": "^4.1.0",
-                        "string.prototype.trimleft": "^2.1.1",
-                        "string.prototype.trimright": "^2.1.1"
+                        "es-to-primitive": "1.2.1",
+                        "function-bind": "1.1.1",
+                        "has": "1.0.3",
+                        "has-symbols": "1.0.1",
+                        "is-callable": "1.1.5",
+                        "is-regex": "1.0.5",
+                        "object-inspect": "1.7.0",
+                        "object-keys": "1.1.1",
+                        "object.assign": "4.1.0",
+                        "string.prototype.trimleft": "2.1.1",
+                        "string.prototype.trimright": "2.1.1"
                     }
                 },
                 "es-to-primitive": {
@@ -13932,9 +13968,9 @@
                     "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==",
                     "dev": true,
                     "requires": {
-                        "is-callable": "^1.1.4",
-                        "is-date-object": "^1.0.1",
-                        "is-symbol": "^1.0.2"
+                        "is-callable": "1.1.5",
+                        "is-date-object": "1.0.1",
+                        "is-symbol": "1.0.2"
                     }
                 },
                 "has-symbols": {
@@ -13955,7 +13991,7 @@
                     "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==",
                     "dev": true,
                     "requires": {
-                        "has": "^1.0.3"
+                        "has": "1.0.3"
                     }
                 },
                 "object-inspect": {
@@ -13970,8 +14006,8 @@
                     "integrity": "sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag==",
                     "dev": true,
                     "requires": {
-                        "define-properties": "^1.1.3",
-                        "function-bind": "^1.1.1"
+                        "define-properties": "1.1.3",
+                        "function-bind": "1.1.1"
                     }
                 },
                 "string.prototype.trimright": {
@@ -13980,8 +14016,8 @@
                     "integrity": "sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g==",
                     "dev": true,
                     "requires": {
-                        "define-properties": "^1.1.3",
-                        "function-bind": "^1.1.1"
+                        "define-properties": "1.1.3",
+                        "function-bind": "1.1.1"
                     }
                 }
             }
@@ -13991,8 +14027,8 @@
             "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.4.0.tgz",
             "integrity": "sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A==",
             "requires": {
-                "rc": "^1.1.6",
-                "safe-buffer": "^5.0.1"
+                "rc": "1.2.8",
+                "safe-buffer": "5.1.2"
             }
         },
         "registry-url": {
@@ -14000,7 +14036,7 @@
             "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz",
             "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=",
             "requires": {
-                "rc": "^1.0.1"
+                "rc": "1.2.8"
             }
         },
         "remove-trailing-separator": {
@@ -14023,7 +14059,7 @@
             "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz",
             "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=",
             "requires": {
-                "is-finite": "^1.0.0"
+                "is-finite": "1.0.2"
             }
         },
         "request": {
@@ -14031,26 +14067,26 @@
             "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz",
             "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==",
             "requires": {
-                "aws-sign2": "~0.7.0",
-                "aws4": "^1.8.0",
-                "caseless": "~0.12.0",
-                "combined-stream": "~1.0.6",
-                "extend": "~3.0.2",
-                "forever-agent": "~0.6.1",
-                "form-data": "~2.3.2",
-                "har-validator": "~5.1.0",
-                "http-signature": "~1.2.0",
-                "is-typedarray": "~1.0.0",
-                "isstream": "~0.1.2",
-                "json-stringify-safe": "~5.0.1",
-                "mime-types": "~2.1.19",
-                "oauth-sign": "~0.9.0",
-                "performance-now": "^2.1.0",
-                "qs": "~6.5.2",
-                "safe-buffer": "^5.1.2",
-                "tough-cookie": "~2.4.3",
-                "tunnel-agent": "^0.6.0",
-                "uuid": "^3.3.2"
+                "aws-sign2": "0.7.0",
+                "aws4": "1.9.0",
+                "caseless": "0.12.0",
+                "combined-stream": "1.0.8",
+                "extend": "3.0.2",
+                "forever-agent": "0.6.1",
+                "form-data": "2.3.3",
+                "har-validator": "5.1.3",
+                "http-signature": "1.2.0",
+                "is-typedarray": "1.0.0",
+                "isstream": "0.1.2",
+                "json-stringify-safe": "5.0.1",
+                "mime-types": "2.1.24",
+                "oauth-sign": "0.9.0",
+                "performance-now": "2.1.0",
+                "qs": "6.5.2",
+                "safe-buffer": "5.1.2",
+                "tough-cookie": "2.4.3",
+                "tunnel-agent": "0.6.0",
+                "uuid": "3.4.0"
             },
             "dependencies": {
                 "form-data": {
@@ -14058,9 +14094,9 @@
                     "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
                     "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
                     "requires": {
-                        "asynckit": "^0.4.0",
-                        "combined-stream": "^1.0.6",
-                        "mime-types": "^2.1.12"
+                        "asynckit": "0.4.0",
+                        "combined-stream": "1.0.8",
+                        "mime-types": "2.1.24"
                     }
                 },
                 "qs": {
@@ -14075,10 +14111,10 @@
             "resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.5.tgz",
             "integrity": "sha512-ZgnepCykFdmpq86fKGwqntyTiUrHycALuGggpyCZwMvGaZWgxW6yagT0FHkgo5LzYvOaCNvxYwWYIjevSH1EDg==",
             "requires": {
-                "bluebird": "^3.5.0",
+                "bluebird": "3.7.2",
                 "request-promise-core": "1.1.3",
-                "stealthy-require": "^1.1.1",
-                "tough-cookie": "^2.3.3"
+                "stealthy-require": "1.1.1",
+                "tough-cookie": "2.4.3"
             }
         },
         "request-promise-core": {
@@ -14086,7 +14122,7 @@
             "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.3.tgz",
             "integrity": "sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ==",
             "requires": {
-                "lodash": "^4.17.15"
+                "lodash": "4.17.15"
             }
         },
         "request-promise-native": {
@@ -14096,8 +14132,8 @@
             "dev": true,
             "requires": {
                 "request-promise-core": "1.1.3",
-                "stealthy-require": "^1.1.1",
-                "tough-cookie": "^2.3.3"
+                "stealthy-require": "1.1.1",
+                "tough-cookie": "2.4.3"
             }
         },
         "require-directory": {
@@ -14115,8 +14151,8 @@
             "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz",
             "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==",
             "requires": {
-                "resolve-from": "^2.0.0",
-                "semver": "^5.1.0"
+                "resolve-from": "2.0.0",
+                "semver": "5.7.0"
             },
             "dependencies": {
                 "resolve-from": {
@@ -14137,21 +14173,21 @@
             "resolved": "https://registry.npmjs.org/resilient-server-session/-/resilient-server-session-1.1.9.tgz",
             "integrity": "sha512-XSVujTyJOQMACllXUvWOSHY4GK4JI6aECjCrQR0UBvd2+hdjM1euffspn2b+7M0fepo+bJ71YrAOA9M34ChBZw==",
             "requires": {
-                "@types/chai": "^4.2.7",
-                "@types/express": "^4.17.2",
-                "@types/mocha": "^5.2.7",
-                "@types/node": "^10.12.30",
-                "@types/request-promise": "^4.1.42",
-                "@types/uuid": "^3.4.6",
-                "chai": "^4.2.0",
-                "colors": "^1.4.0",
-                "express": "^4.17.1",
-                "jsonschema": "^1.2.5",
-                "mocha": "^7.0.0",
-                "request": "^2.88.0",
-                "request-promise": "^4.2.5",
-                "typescript": "^3.7.4",
-                "uuid": "^3.3.3"
+                "@types/chai": "4.2.7",
+                "@types/express": "4.17.2",
+                "@types/mocha": "5.2.7",
+                "@types/node": "10.17.13",
+                "@types/request-promise": "4.1.45",
+                "@types/uuid": "3.4.6",
+                "chai": "4.2.0",
+                "colors": "1.4.0",
+                "express": "4.17.1",
+                "jsonschema": "1.2.5",
+                "mocha": "7.0.1",
+                "request": "2.88.0",
+                "request-promise": "4.2.5",
+                "typescript": "3.7.5",
+                "uuid": "3.4.0"
             },
             "dependencies": {
                 "ansi-colors": {
@@ -14169,7 +14205,7 @@
                     "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
                     "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
                     "requires": {
-                        "color-convert": "^1.9.0"
+                        "color-convert": "1.9.3"
                     }
                 },
                 "anymatch": {
@@ -14177,8 +14213,8 @@
                     "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz",
                     "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==",
                     "requires": {
-                        "normalize-path": "^3.0.0",
-                        "picomatch": "^2.0.4"
+                        "normalize-path": "3.0.0",
+                        "picomatch": "2.2.1"
                     }
                 },
                 "binary-extensions": {
@@ -14191,7 +14227,7 @@
                     "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
                     "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
                     "requires": {
-                        "fill-range": "^7.0.1"
+                        "fill-range": "7.0.1"
                     }
                 },
                 "camelcase": {
@@ -14204,14 +14240,14 @@
                     "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.0.tgz",
                     "integrity": "sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==",
                     "requires": {
-                        "anymatch": "~3.1.1",
-                        "braces": "~3.0.2",
-                        "fsevents": "~2.1.1",
-                        "glob-parent": "~5.1.0",
-                        "is-binary-path": "~2.1.0",
-                        "is-glob": "~4.0.1",
-                        "normalize-path": "~3.0.0",
-                        "readdirp": "~3.2.0"
+                        "anymatch": "3.1.1",
+                        "braces": "3.0.2",
+                        "fsevents": "2.1.2",
+                        "glob-parent": "5.1.0",
+                        "is-binary-path": "2.1.0",
+                        "is-glob": "4.0.1",
+                        "normalize-path": "3.0.0",
+                        "readdirp": "3.2.0"
                     }
                 },
                 "cliui": {
@@ -14219,9 +14255,9 @@
                     "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
                     "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==",
                     "requires": {
-                        "string-width": "^3.1.0",
-                        "strip-ansi": "^5.2.0",
-                        "wrap-ansi": "^5.1.0"
+                        "string-width": "3.1.0",
+                        "strip-ansi": "5.2.0",
+                        "wrap-ansi": "5.1.0"
                     }
                 },
                 "debug": {
@@ -14229,7 +14265,7 @@
                     "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
                     "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
                     "requires": {
-                        "ms": "^2.1.1"
+                        "ms": "2.1.1"
                     }
                 },
                 "fill-range": {
@@ -14237,7 +14273,7 @@
                     "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
                     "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
                     "requires": {
-                        "to-regex-range": "^5.0.1"
+                        "to-regex-range": "5.0.1"
                     }
                 },
                 "find-up": {
@@ -14245,7 +14281,7 @@
                     "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
                     "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
                     "requires": {
-                        "locate-path": "^3.0.0"
+                        "locate-path": "3.0.0"
                     }
                 },
                 "fsevents": {
@@ -14264,12 +14300,12 @@
                     "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz",
                     "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==",
                     "requires": {
-                        "fs.realpath": "^1.0.0",
-                        "inflight": "^1.0.4",
-                        "inherits": "2",
-                        "minimatch": "^3.0.4",
-                        "once": "^1.3.0",
-                        "path-is-absolute": "^1.0.0"
+                        "fs.realpath": "1.0.0",
+                        "inflight": "1.0.6",
+                        "inherits": "2.0.3",
+                        "minimatch": "3.0.4",
+                        "once": "1.4.0",
+                        "path-is-absolute": "1.0.1"
                     }
                 },
                 "glob-parent": {
@@ -14277,7 +14313,7 @@
                     "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz",
                     "integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==",
                     "requires": {
-                        "is-glob": "^4.0.1"
+                        "is-glob": "4.0.1"
                     }
                 },
                 "he": {
@@ -14290,7 +14326,7 @@
                     "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
                     "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
                     "requires": {
-                        "binary-extensions": "^2.0.0"
+                        "binary-extensions": "2.0.0"
                     }
                 },
                 "is-fullwidth-code-point": {
@@ -14308,8 +14344,8 @@
                     "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
                     "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
                     "requires": {
-                        "p-locate": "^3.0.0",
-                        "path-exists": "^3.0.0"
+                        "p-locate": "3.0.0",
+                        "path-exists": "3.0.0"
                     }
                 },
                 "mocha": {
@@ -14353,8 +14389,8 @@
                     "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz",
                     "integrity": "sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw==",
                     "requires": {
-                        "object.getownpropertydescriptors": "^2.0.3",
-                        "semver": "^5.7.0"
+                        "object.getownpropertydescriptors": "2.0.3",
+                        "semver": "5.7.0"
                     }
                 },
                 "p-locate": {
@@ -14362,7 +14398,7 @@
                     "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
                     "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
                     "requires": {
-                        "p-limit": "^2.0.0"
+                        "p-limit": "2.2.2"
                     }
                 },
                 "readdirp": {
@@ -14370,7 +14406,7 @@
                     "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.2.0.tgz",
                     "integrity": "sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==",
                     "requires": {
-                        "picomatch": "^2.0.4"
+                        "picomatch": "2.2.1"
                     }
                 },
                 "require-main-filename": {
@@ -14383,9 +14419,9 @@
                     "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
                     "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
                     "requires": {
-                        "emoji-regex": "^7.0.1",
-                        "is-fullwidth-code-point": "^2.0.0",
-                        "strip-ansi": "^5.1.0"
+                        "emoji-regex": "7.0.3",
+                        "is-fullwidth-code-point": "2.0.0",
+                        "strip-ansi": "5.2.0"
                     }
                 },
                 "strip-ansi": {
@@ -14393,7 +14429,7 @@
                     "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
                     "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
                     "requires": {
-                        "ansi-regex": "^4.1.0"
+                        "ansi-regex": "4.1.0"
                     }
                 },
                 "supports-color": {
@@ -14401,7 +14437,7 @@
                     "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz",
                     "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==",
                     "requires": {
-                        "has-flag": "^3.0.0"
+                        "has-flag": "3.0.0"
                     }
                 },
                 "to-regex-range": {
@@ -14409,7 +14445,7 @@
                     "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
                     "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
                     "requires": {
-                        "is-number": "^7.0.0"
+                        "is-number": "7.0.0"
                     }
                 },
                 "which-module": {
@@ -14422,9 +14458,9 @@
                     "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
                     "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
                     "requires": {
-                        "ansi-styles": "^3.2.0",
-                        "string-width": "^3.0.0",
-                        "strip-ansi": "^5.0.0"
+                        "ansi-styles": "3.2.1",
+                        "string-width": "3.1.0",
+                        "strip-ansi": "5.2.0"
                     }
                 },
                 "y18n": {
@@ -14437,16 +14473,16 @@
                     "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.0.tgz",
                     "integrity": "sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA==",
                     "requires": {
-                        "cliui": "^5.0.0",
-                        "find-up": "^3.0.0",
-                        "get-caller-file": "^2.0.1",
-                        "require-directory": "^2.1.1",
-                        "require-main-filename": "^2.0.0",
-                        "set-blocking": "^2.0.0",
-                        "string-width": "^3.0.0",
-                        "which-module": "^2.0.0",
-                        "y18n": "^4.0.0",
-                        "yargs-parser": "^13.1.1"
+                        "cliui": "5.0.0",
+                        "find-up": "3.0.0",
+                        "get-caller-file": "2.0.5",
+                        "require-directory": "2.1.1",
+                        "require-main-filename": "2.0.0",
+                        "set-blocking": "2.0.0",
+                        "string-width": "3.1.0",
+                        "which-module": "2.0.0",
+                        "y18n": "4.0.0",
+                        "yargs-parser": "13.1.1"
                     }
                 },
                 "yargs-parser": {
@@ -14454,8 +14490,8 @@
                     "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz",
                     "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==",
                     "requires": {
-                        "camelcase": "^5.0.0",
-                        "decamelize": "^1.2.0"
+                        "camelcase": "5.3.1",
+                        "decamelize": "1.2.0"
                     }
                 }
             }
@@ -14470,7 +14506,7 @@
             "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.1.tgz",
             "integrity": "sha512-vIpgF6wfuJOZI7KKKSP+HmiKggadPQAdsp5HiC1mvqnfp0gF1vdwgBWZIdrVft9pgqoMFQN+R7BSWZiBxx+BBw==",
             "requires": {
-                "path-parse": "^1.0.6"
+                "path-parse": "1.0.6"
             }
         },
         "resolve-cwd": {
@@ -14479,7 +14515,7 @@
             "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=",
             "dev": true,
             "requires": {
-                "resolve-from": "^3.0.0"
+                "resolve-from": "3.0.0"
             }
         },
         "resolve-dir": {
@@ -14488,8 +14524,8 @@
             "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=",
             "dev": true,
             "requires": {
-                "expand-tilde": "^2.0.0",
-                "global-modules": "^1.0.0"
+                "expand-tilde": "2.0.2",
+                "global-modules": "1.0.0"
             },
             "dependencies": {
                 "global-modules": {
@@ -14498,9 +14534,9 @@
                     "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==",
                     "dev": true,
                     "requires": {
-                        "global-prefix": "^1.0.1",
-                        "is-windows": "^1.0.1",
-                        "resolve-dir": "^1.0.0"
+                        "global-prefix": "1.0.2",
+                        "is-windows": "1.0.2",
+                        "resolve-dir": "1.0.1"
                     }
                 }
             }
@@ -14532,7 +14568,7 @@
             "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz",
             "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=",
             "requires": {
-                "align-text": "^0.1.1"
+                "align-text": "0.1.4"
             }
         },
         "rimraf": {
@@ -14540,7 +14576,7 @@
             "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.0.tgz",
             "integrity": "sha512-NDGVxTsjqfunkds7CqsOiEnxln4Bo7Nddl3XhS4pXg5OzwkLqJ971ZVAAnB+DDLnF76N+VnDEiBHaVV8I06SUg==",
             "requires": {
-                "glob": "^7.1.3"
+                "glob": "7.1.4"
             }
         },
         "ripemd160": {
@@ -14548,8 +14584,8 @@
             "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz",
             "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==",
             "requires": {
-                "hash-base": "^3.0.0",
-                "inherits": "^2.0.1"
+                "hash-base": "3.0.4",
+                "inherits": "2.0.3"
             }
         },
         "rope-sequence": {
@@ -14562,7 +14598,7 @@
             "resolved": "https://registry.npmjs.org/rtcpeerconnection-shim/-/rtcpeerconnection-shim-1.2.15.tgz",
             "integrity": "sha512-C6DxhXt7bssQ1nHb154lqeL0SXz5Dx4RczXZu2Aa/L1NJFnEVDxFwCBo3fqtuljhHIGceg5JKBV4XJ0gW5JKyw==",
             "requires": {
-                "sdp": "^2.6.0"
+                "sdp": "2.12.0"
             }
         },
         "run-queue": {
@@ -14571,7 +14607,7 @@
             "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=",
             "dev": true,
             "requires": {
-                "aproba": "^1.1.1"
+                "aproba": "1.2.0"
             }
         },
         "safe-buffer": {
@@ -14584,7 +14620,7 @@
             "resolved": "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
             "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=",
             "requires": {
-                "ret": "~0.1.10"
+                "ret": "0.1.15"
             }
         },
         "safer-buffer": {
@@ -14598,7 +14634,7 @@
             "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==",
             "optional": true,
             "requires": {
-                "sparse-bitfield": "^3.0.3"
+                "sparse-bitfield": "3.0.3"
             }
         },
         "sass-graph": {
@@ -14606,10 +14642,10 @@
             "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.4.tgz",
             "integrity": "sha1-E/vWPNHK8JCLn9k0dq1DpR0eC0k=",
             "requires": {
-                "glob": "^7.0.0",
-                "lodash": "^4.0.0",
-                "scss-tokenizer": "^0.2.3",
-                "yargs": "^7.0.0"
+                "glob": "7.1.4",
+                "lodash": "4.17.15",
+                "scss-tokenizer": "0.2.3",
+                "yargs": "7.1.0"
             }
         },
         "sass-loader": {
@@ -14618,11 +14654,11 @@
             "integrity": "sha512-tuU7+zm0pTCynKYHpdqaPpe+MMTQ76I9TPZ7i4/5dZsigE350shQWe5EZNl5dBidM49TPET75tNqRbcsUZWeNA==",
             "dev": true,
             "requires": {
-                "clone-deep": "^4.0.1",
-                "loader-utils": "^1.0.1",
-                "neo-async": "^2.5.0",
-                "pify": "^4.0.1",
-                "semver": "^6.3.0"
+                "clone-deep": "4.0.1",
+                "loader-utils": "1.2.3",
+                "neo-async": "2.6.1",
+                "pify": "4.0.1",
+                "semver": "6.3.0"
             },
             "dependencies": {
                 "pify": {
@@ -14650,7 +14686,7 @@
             "integrity": "sha512-Ydydq3zC+WYDJK1+gRxRapLIED9PWeSuuS41wqyoRmzvhhh9nc+QQrVMKJYzJFULazeGhzSV0QleN2wD3boh2g==",
             "dev": true,
             "requires": {
-                "xmlchars": "^2.1.1"
+                "xmlchars": "2.2.0"
             }
         },
         "scheduler": {
@@ -14658,8 +14694,8 @@
             "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.18.0.tgz",
             "integrity": "sha512-agTSHR1Nbfi6ulI0kYNK0203joW2Y5W4po4l+v03tOoiJKpTBbxpNhWDvqc/4IcOw+KLmSiQLTasZ4cab2/UWQ==",
             "requires": {
-                "loose-envify": "^1.1.0",
-                "object-assign": "^4.1.1"
+                "loose-envify": "1.4.0",
+                "object-assign": "4.1.1"
             }
         },
         "schema-utils": {
@@ -14667,8 +14703,8 @@
             "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.7.tgz",
             "integrity": "sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ==",
             "requires": {
-                "ajv": "^6.1.0",
-                "ajv-keywords": "^3.1.0"
+                "ajv": "6.10.2",
+                "ajv-keywords": "3.4.1"
             }
         },
         "scss-loader": {
@@ -14682,8 +14718,8 @@
             "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz",
             "integrity": "sha1-jrBtualyMzOCTT9VMGQRSYR85dE=",
             "requires": {
-                "js-base64": "^2.1.8",
-                "source-map": "^0.4.2"
+                "js-base64": "2.5.1",
+                "source-map": "0.4.4"
             },
             "dependencies": {
                 "source-map": {
@@ -14691,7 +14727,7 @@
                     "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz",
                     "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=",
                     "requires": {
-                        "amdefine": ">=0.0.4"
+                        "amdefine": "1.0.1"
                     }
                 }
             }
@@ -14731,7 +14767,7 @@
             "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz",
             "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=",
             "requires": {
-                "semver": "^5.0.3"
+                "semver": "5.7.0"
             }
         },
         "send": {
@@ -14740,18 +14776,18 @@
             "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==",
             "requires": {
                 "debug": "2.6.9",
-                "depd": "~1.1.2",
-                "destroy": "~1.0.4",
-                "encodeurl": "~1.0.2",
-                "escape-html": "~1.0.3",
-                "etag": "~1.8.1",
+                "depd": "1.1.2",
+                "destroy": "1.0.4",
+                "encodeurl": "1.0.2",
+                "escape-html": "1.0.3",
+                "etag": "1.8.1",
                 "fresh": "0.5.2",
-                "http-errors": "~1.7.2",
+                "http-errors": "1.7.2",
                 "mime": "1.6.0",
                 "ms": "2.1.1",
-                "on-finished": "~2.3.0",
-                "range-parser": "~1.2.1",
-                "statuses": "~1.5.0"
+                "on-finished": "2.3.0",
+                "range-parser": "1.2.1",
+                "statuses": "1.5.0"
             },
             "dependencies": {
                 "ms": {
@@ -14778,13 +14814,13 @@
             "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=",
             "dev": true,
             "requires": {
-                "accepts": "~1.3.4",
+                "accepts": "1.3.7",
                 "batch": "0.6.1",
                 "debug": "2.6.9",
-                "escape-html": "~1.0.3",
-                "http-errors": "~1.6.2",
-                "mime-types": "~2.1.17",
-                "parseurl": "~1.3.2"
+                "escape-html": "1.0.3",
+                "http-errors": "1.6.3",
+                "mime-types": "2.1.24",
+                "parseurl": "1.3.3"
             },
             "dependencies": {
                 "http-errors": {
@@ -14793,10 +14829,10 @@
                     "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=",
                     "dev": true,
                     "requires": {
-                        "depd": "~1.1.2",
+                        "depd": "1.1.2",
                         "inherits": "2.0.3",
                         "setprototypeof": "1.1.0",
-                        "statuses": ">= 1.4.0 < 2"
+                        "statuses": "1.5.0"
                     }
                 },
                 "setprototypeof": {
@@ -14812,9 +14848,9 @@
             "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz",
             "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==",
             "requires": {
-                "encodeurl": "~1.0.2",
-                "escape-html": "~1.0.3",
-                "parseurl": "~1.3.3",
+                "encodeurl": "1.0.2",
+                "escape-html": "1.0.3",
+                "parseurl": "1.3.3",
                 "send": "0.17.1"
             }
         },
@@ -14828,10 +14864,10 @@
             "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz",
             "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==",
             "requires": {
-                "extend-shallow": "^2.0.1",
-                "is-extendable": "^0.1.1",
-                "is-plain-object": "^2.0.3",
-                "split-string": "^3.0.1"
+                "extend-shallow": "2.0.1",
+                "is-extendable": "0.1.1",
+                "is-plain-object": "2.0.4",
+                "split-string": "3.1.0"
             },
             "dependencies": {
                 "extend-shallow": {
@@ -14839,7 +14875,7 @@
                     "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
                     "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
                     "requires": {
-                        "is-extendable": "^0.1.0"
+                        "is-extendable": "0.1.1"
                     }
                 }
             }
@@ -14859,8 +14895,8 @@
             "resolved": "http://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
             "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
             "requires": {
-                "inherits": "^2.0.1",
-                "safe-buffer": "^5.0.1"
+                "inherits": "2.0.3",
+                "safe-buffer": "5.1.2"
             }
         },
         "shallow-clone": {
@@ -14869,7 +14905,7 @@
             "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==",
             "dev": true,
             "requires": {
-                "kind-of": "^6.0.2"
+                "kind-of": "6.0.2"
             }
         },
         "shallow-equal": {
@@ -14882,15 +14918,15 @@
             "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.23.4.tgz",
             "integrity": "sha512-fJMagt6cT0UDy9XCsgyLi0eiwWWhQRxbwGmqQT6sY8Av4s0SVsT/deg8fobBQCTDU5iXRgz0rAeXoE2LBZ8g+Q==",
             "requires": {
-                "color": "^3.1.2",
-                "detect-libc": "^1.0.3",
-                "nan": "^2.14.0",
-                "npmlog": "^4.1.2",
-                "prebuild-install": "^5.3.3",
-                "semver": "^6.3.0",
-                "simple-get": "^3.1.0",
-                "tar": "^5.0.5",
-                "tunnel-agent": "^0.6.0"
+                "color": "3.1.2",
+                "detect-libc": "1.0.3",
+                "nan": "2.14.0",
+                "npmlog": "4.1.2",
+                "prebuild-install": "5.3.3",
+                "semver": "6.3.0",
+                "simple-get": "3.1.0",
+                "tar": "5.0.5",
+                "tunnel-agent": "0.6.0"
             },
             "dependencies": {
                 "chownr": {
@@ -14903,7 +14939,7 @@
                     "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz",
                     "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==",
                     "requires": {
-                        "mimic-response": "^2.0.0"
+                        "mimic-response": "2.0.0"
                     }
                 },
                 "fs-minipass": {
@@ -14911,7 +14947,7 @@
                     "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.0.0.tgz",
                     "integrity": "sha512-40Qz+LFXmd9tzYVnnBmZvFfvAADfUA14TXPK1s7IfElJTIZ97rA8w4Kin7Wt5JBrC3ShnnFJO/5vPjPEeJIq9A==",
                     "requires": {
-                        "minipass": "^3.0.0"
+                        "minipass": "3.1.1"
                     }
                 },
                 "mimic-response": {
@@ -14924,7 +14960,7 @@
                     "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.1.tgz",
                     "integrity": "sha512-UFqVihv6PQgwj8/yTGvl9kPz7xIAY+R5z6XYjRInD3Gk3qx6QGSD6zEcpeG4Dy/lQnv1J6zv8ejV90hyYIKf3w==",
                     "requires": {
-                        "yallist": "^4.0.0"
+                        "yallist": "4.0.0"
                     }
                 },
                 "minizlib": {
@@ -14932,8 +14968,8 @@
                     "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.0.tgz",
                     "integrity": "sha512-EzTZN/fjSvifSX0SlqUERCN39o6T40AMarPbv0MrarSFtIITCBh7bi+dU8nxGFHuqs9jdIAeoYoKuQAAASsPPA==",
                     "requires": {
-                        "minipass": "^3.0.0",
-                        "yallist": "^4.0.0"
+                        "minipass": "3.1.1",
+                        "yallist": "4.0.0"
                     }
                 },
                 "semver": {
@@ -14946,9 +14982,9 @@
                     "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.0.tgz",
                     "integrity": "sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==",
                     "requires": {
-                        "decompress-response": "^4.2.0",
-                        "once": "^1.3.1",
-                        "simple-concat": "^1.0.0"
+                        "decompress-response": "4.2.1",
+                        "once": "1.4.0",
+                        "simple-concat": "1.0.0"
                     }
                 },
                 "tar": {
@@ -14956,12 +14992,12 @@
                     "resolved": "https://registry.npmjs.org/tar/-/tar-5.0.5.tgz",
                     "integrity": "sha512-MNIgJddrV2TkuwChwcSNds/5E9VijOiw7kAc1y5hTNJoLDSuIyid2QtLYiCYNnICebpuvjhPQZsXwUL0O3l7OQ==",
                     "requires": {
-                        "chownr": "^1.1.3",
-                        "fs-minipass": "^2.0.0",
-                        "minipass": "^3.0.0",
-                        "minizlib": "^2.1.0",
-                        "mkdirp": "^0.5.0",
-                        "yallist": "^4.0.0"
+                        "chownr": "1.1.3",
+                        "fs-minipass": "2.0.0",
+                        "minipass": "3.1.1",
+                        "minizlib": "2.1.0",
+                        "mkdirp": "0.5.1",
+                        "yallist": "4.0.0"
                     }
                 },
                 "yallist": {
@@ -14976,7 +15012,7 @@
             "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
             "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
             "requires": {
-                "shebang-regex": "^1.0.0"
+                "shebang-regex": "1.0.0"
             }
         },
         "shebang-regex": {
@@ -14989,9 +15025,9 @@
             "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.3.tgz",
             "integrity": "sha512-fc0BKlAWiLpwZljmOvAOTE/gXawtCoNrP5oaY7KIaQbbyHeQVg01pSEuEGvGh3HEdBU4baCD7wQBwADmM/7f7A==",
             "requires": {
-                "glob": "^7.0.0",
-                "interpret": "^1.0.0",
-                "rechoir": "^0.6.2"
+                "glob": "7.1.4",
+                "interpret": "1.2.0",
+                "rechoir": "0.6.2"
             }
         },
         "shellwords": {
@@ -15020,9 +15056,9 @@
             "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.0.3.tgz",
             "integrity": "sha512-Wvre/Jq5vgoz31Z9stYWPLn0PqRqmBDpFSdypAnHu5AvRVCYPRYGnvryNLiXu8GOBNDH82J2FRHUGMjjHUpXFw==",
             "requires": {
-                "decompress-response": "^3.3.0",
-                "once": "^1.3.1",
-                "simple-concat": "^1.0.0"
+                "decompress-response": "3.3.0",
+                "once": "1.4.0",
+                "simple-concat": "1.0.0"
             }
         },
         "simple-swizzle": {
@@ -15030,7 +15066,7 @@
             "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
             "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=",
             "requires": {
-                "is-arrayish": "^0.3.1"
+                "is-arrayish": "0.3.2"
             },
             "dependencies": {
                 "is-arrayish": {
@@ -15056,14 +15092,14 @@
             "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz",
             "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==",
             "requires": {
-                "base": "^0.11.1",
-                "debug": "^2.2.0",
-                "define-property": "^0.2.5",
-                "extend-shallow": "^2.0.1",
-                "map-cache": "^0.2.2",
-                "source-map": "^0.5.6",
-                "source-map-resolve": "^0.5.0",
-                "use": "^3.1.0"
+                "base": "0.11.2",
+                "debug": "2.6.9",
+                "define-property": "0.2.5",
+                "extend-shallow": "2.0.1",
+                "map-cache": "0.2.2",
+                "source-map": "0.5.7",
+                "source-map-resolve": "0.5.2",
+                "use": "3.1.1"
             },
             "dependencies": {
                 "define-property": {
@@ -15071,7 +15107,7 @@
                     "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
                     "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
                     "requires": {
-                        "is-descriptor": "^0.1.0"
+                        "is-descriptor": "0.1.6"
                     }
                 },
                 "extend-shallow": {
@@ -15079,7 +15115,7 @@
                     "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
                     "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
                     "requires": {
-                        "is-extendable": "^0.1.0"
+                        "is-extendable": "0.1.1"
                     }
                 }
             }
@@ -15089,9 +15125,9 @@
             "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz",
             "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==",
             "requires": {
-                "define-property": "^1.0.0",
-                "isobject": "^3.0.0",
-                "snapdragon-util": "^3.0.1"
+                "define-property": "1.0.0",
+                "isobject": "3.0.1",
+                "snapdragon-util": "3.0.1"
             },
             "dependencies": {
                 "define-property": {
@@ -15099,7 +15135,7 @@
                     "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
                     "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
                     "requires": {
-                        "is-descriptor": "^1.0.0"
+                        "is-descriptor": "1.0.2"
                     }
                 },
                 "is-accessor-descriptor": {
@@ -15107,7 +15143,7 @@
                     "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
                     "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
                     "requires": {
-                        "kind-of": "^6.0.0"
+                        "kind-of": "6.0.2"
                     }
                 },
                 "is-data-descriptor": {
@@ -15115,7 +15151,7 @@
                     "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
                     "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
                     "requires": {
-                        "kind-of": "^6.0.0"
+                        "kind-of": "6.0.2"
                     }
                 },
                 "is-descriptor": {
@@ -15123,9 +15159,9 @@
                     "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
                     "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
                     "requires": {
-                        "is-accessor-descriptor": "^1.0.0",
-                        "is-data-descriptor": "^1.0.0",
-                        "kind-of": "^6.0.2"
+                        "is-accessor-descriptor": "1.0.0",
+                        "is-data-descriptor": "1.0.0",
+                        "kind-of": "6.0.2"
                     }
                 }
             }
@@ -15135,7 +15171,7 @@
             "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz",
             "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==",
             "requires": {
-                "kind-of": "^3.2.0"
+                "kind-of": "3.2.2"
             },
             "dependencies": {
                 "kind-of": {
@@ -15143,7 +15179,7 @@
                     "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
                     "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
                     "requires": {
-                        "is-buffer": "^1.1.5"
+                        "is-buffer": "1.1.6"
                     }
                 }
             }
@@ -15153,12 +15189,12 @@
             "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.3.0.tgz",
             "integrity": "sha512-2A892lrj0GcgR/9Qk81EaY2gYhCBxurV0PfmmESO6p27QPrUK1J3zdns+5QPqvUYK2q657nSj0guoIil9+7eFg==",
             "requires": {
-                "debug": "~4.1.0",
-                "engine.io": "~3.4.0",
-                "has-binary2": "~1.0.2",
-                "socket.io-adapter": "~1.1.0",
+                "debug": "4.1.1",
+                "engine.io": "3.4.0",
+                "has-binary2": "1.0.3",
+                "socket.io-adapter": "1.1.2",
                 "socket.io-client": "2.3.0",
-                "socket.io-parser": "~3.4.0"
+                "socket.io-parser": "3.4.0"
             },
             "dependencies": {
                 "debug": {
@@ -15166,7 +15202,7 @@
                     "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
                     "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
                     "requires": {
-                        "ms": "^2.1.1"
+                        "ms": "2.1.2"
                     }
                 },
                 "ms": {
@@ -15190,15 +15226,15 @@
                 "base64-arraybuffer": "0.1.5",
                 "component-bind": "1.0.0",
                 "component-emitter": "1.2.1",
-                "debug": "~4.1.0",
-                "engine.io-client": "~3.4.0",
-                "has-binary2": "~1.0.2",
+                "debug": "4.1.1",
+                "engine.io-client": "3.4.0",
+                "has-binary2": "1.0.3",
                 "has-cors": "1.1.0",
                 "indexof": "0.0.1",
                 "object-component": "0.0.3",
                 "parseqs": "0.0.5",
                 "parseuri": "0.0.5",
-                "socket.io-parser": "~3.3.0",
+                "socket.io-parser": "3.3.0",
                 "to-array": "0.1.4"
             },
             "dependencies": {
@@ -15212,7 +15248,7 @@
                     "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
                     "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
                     "requires": {
-                        "ms": "^2.1.1"
+                        "ms": "2.1.2"
                     }
                 },
                 "isarray": {
@@ -15231,7 +15267,7 @@
                     "integrity": "sha512-hczmV6bDgdaEbVqhAeVMM/jfUfzuEZHsQg6eOmLgJht6G3mPKMxYm75w2+qhAQZ+4X+1+ATZ+QFKeOZD5riHng==",
                     "requires": {
                         "component-emitter": "1.2.1",
-                        "debug": "~3.1.0",
+                        "debug": "3.1.0",
                         "isarray": "2.0.1"
                     },
                     "dependencies": {
@@ -15258,7 +15294,7 @@
             "integrity": "sha512-/G/VOI+3DBp0+DJKW4KesGnQkQPFmUCbA/oO2QGT6CWxU7hLGWqU3tyuzeSK/dqcyeHsQg1vTe9jiZI8GU9SCQ==",
             "requires": {
                 "component-emitter": "1.2.1",
-                "debug": "~4.1.0",
+                "debug": "4.1.1",
                 "isarray": "2.0.1"
             },
             "dependencies": {
@@ -15272,7 +15308,7 @@
                     "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
                     "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
                     "requires": {
-                        "ms": "^2.1.1"
+                        "ms": "2.1.2"
                     }
                 },
                 "isarray": {
@@ -15293,8 +15329,8 @@
             "integrity": "sha512-V48klKZl8T6MzatbLlzzRNhMepEys9Y4oGFpypBFFn1gLI/QQ9HtLLyWJNbPlwGLelOVOEijUbTTJeLLI59jLw==",
             "dev": true,
             "requires": {
-                "faye-websocket": "^0.10.0",
-                "uuid": "^3.0.1"
+                "faye-websocket": "0.10.0",
+                "uuid": "3.4.0"
             }
         },
         "sockjs-client": {
@@ -15303,12 +15339,12 @@
             "integrity": "sha512-5zaLyO8/nri5cua0VtOrFXBPK1jbL4+1cebT/mmKA1E1ZXOvJrII75bPu0l0k843G/+iAbhEqzyKr0w/eCCj7g==",
             "dev": true,
             "requires": {
-                "debug": "^3.2.5",
-                "eventsource": "^1.0.7",
-                "faye-websocket": "~0.11.1",
-                "inherits": "^2.0.3",
-                "json3": "^3.3.2",
-                "url-parse": "^1.4.3"
+                "debug": "3.2.6",
+                "eventsource": "1.0.7",
+                "faye-websocket": "0.11.3",
+                "inherits": "2.0.3",
+                "json3": "3.3.3",
+                "url-parse": "1.4.7"
             },
             "dependencies": {
                 "debug": {
@@ -15317,7 +15353,7 @@
                     "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
                     "dev": true,
                     "requires": {
-                        "ms": "^2.1.1"
+                        "ms": "2.1.2"
                     }
                 },
                 "faye-websocket": {
@@ -15326,7 +15362,7 @@
                     "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==",
                     "dev": true,
                     "requires": {
-                        "websocket-driver": ">=0.5.1"
+                        "websocket-driver": "0.7.3"
                     }
                 },
                 "ms": {
@@ -15342,9 +15378,9 @@
             "resolved": "https://registry.npmjs.org/solr-node/-/solr-node-1.2.1.tgz",
             "integrity": "sha512-DN3+FSBgpJEgGTNddzS8tNb+ILSn5MLcsWf15G9rGxi/sROHbpcevdRSVx6s5/nz56c/5AnBTBZWak7IXWX97A==",
             "requires": {
-                "@log4js-node/log4js-api": "^1.0.2",
-                "node-fetch": "^2.3.0",
-                "underscore": "^1.8.3"
+                "@log4js-node/log4js-api": "1.0.2",
+                "node-fetch": "2.6.0",
+                "underscore": "1.9.1"
             },
             "dependencies": {
                 "node-fetch": {
@@ -15370,11 +15406,11 @@
             "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz",
             "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==",
             "requires": {
-                "atob": "^2.1.1",
-                "decode-uri-component": "^0.2.0",
-                "resolve-url": "^0.2.1",
-                "source-map-url": "^0.4.0",
-                "urix": "^0.1.0"
+                "atob": "2.1.2",
+                "decode-uri-component": "0.2.0",
+                "resolve-url": "0.2.1",
+                "source-map-url": "0.4.0",
+                "urix": "0.1.0"
             }
         },
         "source-map-support": {
@@ -15383,8 +15419,8 @@
             "integrity": "sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ==",
             "dev": true,
             "requires": {
-                "buffer-from": "^1.0.0",
-                "source-map": "^0.6.0"
+                "buffer-from": "1.1.1",
+                "source-map": "0.6.1"
             },
             "dependencies": {
                 "source-map": {
@@ -15406,7 +15442,7 @@
             "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=",
             "optional": true,
             "requires": {
-                "memory-pager": "^1.0.2"
+                "memory-pager": "1.5.0"
             }
         },
         "spdx-correct": {
@@ -15414,8 +15450,8 @@
             "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz",
             "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==",
             "requires": {
-                "spdx-expression-parse": "^3.0.0",
-                "spdx-license-ids": "^3.0.0"
+                "spdx-expression-parse": "3.0.0",
+                "spdx-license-ids": "3.0.5"
             }
         },
         "spdx-exceptions": {
@@ -15428,8 +15464,8 @@
             "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz",
             "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==",
             "requires": {
-                "spdx-exceptions": "^2.1.0",
-                "spdx-license-ids": "^3.0.0"
+                "spdx-exceptions": "2.2.0",
+                "spdx-license-ids": "3.0.5"
             }
         },
         "spdx-license-ids": {
@@ -15443,11 +15479,11 @@
             "integrity": "sha512-HeZS3PBdMA+sZSu0qwpCxl3DeALD5ASx8pAX0jZdKXSpPWbQ6SYGnlg3BBmYLx5LtiZrmkAZfErCm2oECBcioA==",
             "dev": true,
             "requires": {
-                "debug": "^4.1.0",
-                "handle-thing": "^2.0.0",
-                "http-deceiver": "^1.2.7",
-                "select-hose": "^2.0.0",
-                "spdy-transport": "^3.0.0"
+                "debug": "4.1.1",
+                "handle-thing": "2.0.0",
+                "http-deceiver": "1.2.7",
+                "select-hose": "2.0.0",
+                "spdy-transport": "3.0.0"
             },
             "dependencies": {
                 "debug": {
@@ -15456,7 +15492,7 @@
                     "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
                     "dev": true,
                     "requires": {
-                        "ms": "^2.1.1"
+                        "ms": "2.1.2"
                     }
                 },
                 "ms": {
@@ -15473,12 +15509,12 @@
             "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==",
             "dev": true,
             "requires": {
-                "debug": "^4.1.0",
-                "detect-node": "^2.0.4",
-                "hpack.js": "^2.1.6",
-                "obuf": "^1.1.2",
-                "readable-stream": "^3.0.6",
-                "wbuf": "^1.7.3"
+                "debug": "4.1.1",
+                "detect-node": "2.0.4",
+                "hpack.js": "2.1.6",
+                "obuf": "1.1.2",
+                "readable-stream": "3.4.0",
+                "wbuf": "1.7.3"
             },
             "dependencies": {
                 "debug": {
@@ -15487,7 +15523,7 @@
                     "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
                     "dev": true,
                     "requires": {
-                        "ms": "^2.1.1"
+                        "ms": "2.1.2"
                     }
                 },
                 "ms": {
@@ -15502,9 +15538,9 @@
                     "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==",
                     "dev": true,
                     "requires": {
-                        "inherits": "^2.0.3",
-                        "string_decoder": "^1.1.1",
-                        "util-deprecate": "^1.0.1"
+                        "inherits": "2.0.3",
+                        "string_decoder": "1.1.1",
+                        "util-deprecate": "1.0.2"
                     }
                 }
             }
@@ -15514,7 +15550,7 @@
             "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz",
             "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==",
             "requires": {
-                "through": "2"
+                "through": "2.3.8"
             }
         },
         "split-on-first": {
@@ -15532,7 +15568,7 @@
             "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
             "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==",
             "requires": {
-                "extend-shallow": "^3.0.0"
+                "extend-shallow": "3.0.2"
             }
         },
         "sprintf-js": {
@@ -15545,15 +15581,15 @@
             "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz",
             "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==",
             "requires": {
-                "asn1": "~0.2.3",
-                "assert-plus": "^1.0.0",
-                "bcrypt-pbkdf": "^1.0.0",
-                "dashdash": "^1.12.0",
-                "ecc-jsbn": "~0.1.1",
-                "getpass": "^0.1.1",
-                "jsbn": "~0.1.0",
-                "safer-buffer": "^2.0.2",
-                "tweetnacl": "~0.14.0"
+                "asn1": "0.2.4",
+                "assert-plus": "1.0.0",
+                "bcrypt-pbkdf": "1.0.2",
+                "dashdash": "1.14.1",
+                "ecc-jsbn": "0.1.2",
+                "getpass": "0.1.7",
+                "jsbn": "0.1.1",
+                "safer-buffer": "2.1.2",
+                "tweetnacl": "0.14.5"
             },
             "dependencies": {
                 "assert-plus": {
@@ -15569,7 +15605,7 @@
             "integrity": "sha512-XRSIPqLij52MtgoQavH/x/dU1qVKtWUAAZeOHsR9c2Ddi4XerFy3mc1alf+dLJKl9EUIm/Ht+EowFkTUOA6GAQ==",
             "dev": true,
             "requires": {
-                "safe-buffer": "^5.1.1"
+                "safe-buffer": "5.1.2"
             }
         },
         "standard-error": {
@@ -15582,7 +15618,7 @@
             "resolved": "https://registry.npmjs.org/standard-http-error/-/standard-http-error-2.0.1.tgz",
             "integrity": "sha1-+K6RcuPO+cs40ucIShkl9Xp8NL0=",
             "requires": {
-                "standard-error": ">= 1.1.0 < 2"
+                "standard-error": "1.1.0"
             }
         },
         "static-extend": {
@@ -15590,8 +15626,8 @@
             "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz",
             "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=",
             "requires": {
-                "define-property": "^0.2.5",
-                "object-copy": "^0.1.0"
+                "define-property": "0.2.5",
+                "object-copy": "0.1.0"
             },
             "dependencies": {
                 "define-property": {
@@ -15599,7 +15635,7 @@
                     "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
                     "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
                     "requires": {
-                        "is-descriptor": "^0.1.0"
+                        "is-descriptor": "0.1.6"
                     }
                 }
             }
@@ -15614,7 +15650,7 @@
             "resolved": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.1.tgz",
             "integrity": "sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA==",
             "requires": {
-                "readable-stream": "^2.0.1"
+                "readable-stream": "2.3.6"
             }
         },
         "stealthy-require": {
@@ -15628,8 +15664,8 @@
             "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==",
             "dev": true,
             "requires": {
-                "inherits": "~2.0.1",
-                "readable-stream": "^2.0.2"
+                "inherits": "2.0.3",
+                "readable-stream": "2.3.6"
             }
         },
         "stream-combiner": {
@@ -15637,8 +15673,8 @@
             "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.2.2.tgz",
             "integrity": "sha1-rsjLrBd7Vrb0+kec7YwZEs7lKFg=",
             "requires": {
-                "duplexer": "~0.1.1",
-                "through": "~2.3.4"
+                "duplexer": "0.1.1",
+                "through": "2.3.8"
             }
         },
         "stream-each": {
@@ -15647,8 +15683,8 @@
             "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==",
             "dev": true,
             "requires": {
-                "end-of-stream": "^1.1.0",
-                "stream-shift": "^1.0.0"
+                "end-of-stream": "1.4.1",
+                "stream-shift": "1.0.0"
             }
         },
         "stream-http": {
@@ -15657,11 +15693,11 @@
             "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==",
             "dev": true,
             "requires": {
-                "builtin-status-codes": "^3.0.0",
-                "inherits": "^2.0.1",
-                "readable-stream": "^2.3.6",
-                "to-arraybuffer": "^1.0.0",
-                "xtend": "^4.0.0"
+                "builtin-status-codes": "3.0.0",
+                "inherits": "2.0.3",
+                "readable-stream": "2.3.6",
+                "to-arraybuffer": "1.0.1",
+                "xtend": "4.0.2"
             }
         },
         "stream-parser": {
@@ -15669,7 +15705,7 @@
             "resolved": "https://registry.npmjs.org/stream-parser/-/stream-parser-0.3.1.tgz",
             "integrity": "sha1-FhhUhpRCACGhGC/wrxkRwSl2F3M=",
             "requires": {
-                "debug": "2"
+                "debug": "2.6.9"
             }
         },
         "stream-shift": {
@@ -15688,9 +15724,9 @@
             "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
             "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
             "requires": {
-                "code-point-at": "^1.0.0",
-                "is-fullwidth-code-point": "^1.0.0",
-                "strip-ansi": "^3.0.0"
+                "code-point-at": "1.1.0",
+                "is-fullwidth-code-point": "1.0.0",
+                "strip-ansi": "3.0.1"
             }
         },
         "string.prototype.trimleft": {
@@ -15698,8 +15734,8 @@
             "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.0.tgz",
             "integrity": "sha512-FJ6b7EgdKxxbDxc79cOlok6Afd++TTs5szo+zJTUyow3ycrRfJVE2pq3vcN53XexvKZu/DJMDfeI/qMiZTrjTw==",
             "requires": {
-                "define-properties": "^1.1.3",
-                "function-bind": "^1.1.1"
+                "define-properties": "1.1.3",
+                "function-bind": "1.1.1"
             }
         },
         "string.prototype.trimright": {
@@ -15707,8 +15743,8 @@
             "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.0.tgz",
             "integrity": "sha512-fXZTSV55dNBwv16uw+hh5jkghxSnc5oHq+5K/gXgizHwAvMetdAJlHqqoFC1FSDVPYWLkAKl2cxpUT41sV7nSg==",
             "requires": {
-                "define-properties": "^1.1.3",
-                "function-bind": "^1.1.1"
+                "define-properties": "1.1.3",
+                "function-bind": "1.1.1"
             }
         },
         "string_decoder": {
@@ -15716,7 +15752,7 @@
             "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
             "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
             "requires": {
-                "safe-buffer": "~5.1.0"
+                "safe-buffer": "5.1.2"
             }
         },
         "stringify-parameters": {
@@ -15733,20 +15769,20 @@
                     "resolved": "https://registry.npmjs.org/magicli/-/magicli-0.0.5.tgz",
                     "integrity": "sha1-zufQ+7THBRiqyxHsPrfiX/SaSSE=",
                     "requires": {
-                        "commander": "^2.9.0",
-                        "get-stdin": "^5.0.1",
-                        "inspect-function": "^0.2.1",
-                        "pipe-functions": "^1.2.0"
+                        "commander": "2.20.0",
+                        "get-stdin": "5.0.1",
+                        "inspect-function": "0.2.2",
+                        "pipe-functions": "1.3.0"
                     }
                 }
             }
         },
         "strip-ansi": {
             "version": "3.0.1",
-            "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+            "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
             "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
             "requires": {
-                "ansi-regex": "^2.0.0"
+                "ansi-regex": "2.1.1"
             }
         },
         "strip-bom": {
@@ -15754,7 +15790,7 @@
             "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
             "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=",
             "requires": {
-                "is-utf8": "^0.2.0"
+                "is-utf8": "0.2.1"
             }
         },
         "strip-eof": {
@@ -15767,7 +15803,7 @@
             "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz",
             "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=",
             "requires": {
-                "get-stdin": "^4.0.1"
+                "get-stdin": "4.0.1"
             },
             "dependencies": {
                 "get-stdin": {
@@ -15788,8 +15824,8 @@
             "integrity": "sha512-XK+uv9kWwhZMZ1y7mysB+zoihsEj4wneFWAS5qoiLwzW0WzSqMrrsIy+a3zkQJq0ipFtBpX5W3MqyRIBF/WFGg==",
             "dev": true,
             "requires": {
-                "loader-utils": "^1.1.0",
-                "schema-utils": "^1.0.0"
+                "loader-utils": "1.2.3",
+                "schema-utils": "1.0.0"
             },
             "dependencies": {
                 "schema-utils": {
@@ -15798,9 +15834,9 @@
                     "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==",
                     "dev": true,
                     "requires": {
-                        "ajv": "^6.1.0",
-                        "ajv-errors": "^1.0.0",
-                        "ajv-keywords": "^3.1.0"
+                        "ajv": "6.10.2",
+                        "ajv-errors": "1.0.1",
+                        "ajv-keywords": "3.4.1"
                     }
                 }
             }
@@ -15821,11 +15857,11 @@
             "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-0.4.5.tgz",
             "integrity": "sha512-zTvf0mcggrGeTe/2jJ6ECkJHAQPIYEwDoqsiqBjI24mvRmQbInK5jq33fyypaCBxX08hMkfmdOqj6haT33EqWw==",
             "requires": {
-                "array-back": "^2.0.0",
-                "deep-extend": "~0.6.0",
-                "lodash.padend": "^4.6.1",
-                "typical": "^2.6.1",
-                "wordwrapjs": "^3.0.0"
+                "array-back": "2.0.0",
+                "deep-extend": "0.6.0",
+                "lodash.padend": "4.6.1",
+                "typical": "2.6.1",
+                "wordwrapjs": "3.0.0"
             }
         },
         "tapable": {
@@ -15839,13 +15875,13 @@
             "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.10.tgz",
             "integrity": "sha512-g2SVs5QIxvo6OLp0GudTqEf05maawKUxXru104iaayWA09551tFCTI8f1Asb4lPfkBr91k07iL4c11XO3/b0tA==",
             "requires": {
-                "chownr": "^1.1.1",
-                "fs-minipass": "^1.2.5",
-                "minipass": "^2.3.5",
-                "minizlib": "^1.2.1",
-                "mkdirp": "^0.5.0",
-                "safe-buffer": "^5.1.2",
-                "yallist": "^3.0.3"
+                "chownr": "1.1.2",
+                "fs-minipass": "1.2.6",
+                "minipass": "2.3.5",
+                "minizlib": "1.2.1",
+                "mkdirp": "0.5.1",
+                "safe-buffer": "5.1.2",
+                "yallist": "3.0.3"
             }
         },
         "tar-fs": {
@@ -15853,10 +15889,10 @@
             "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.0.tgz",
             "integrity": "sha512-vaY0obB6Om/fso8a8vakQBzwholQ7v5+uy+tF3Ozvxv1KNezmVQAiWtcNmMHFSFPqL3dJA8ha6gdtFbfX9mcxA==",
             "requires": {
-                "chownr": "^1.1.1",
-                "mkdirp": "^0.5.1",
-                "pump": "^3.0.0",
-                "tar-stream": "^2.0.0"
+                "chownr": "1.1.2",
+                "mkdirp": "0.5.1",
+                "pump": "3.0.0",
+                "tar-stream": "2.1.0"
             },
             "dependencies": {
                 "pump": {
@@ -15864,8 +15900,8 @@
                     "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
                     "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
                     "requires": {
-                        "end-of-stream": "^1.1.0",
-                        "once": "^1.3.1"
+                        "end-of-stream": "1.4.1",
+                        "once": "1.4.0"
                     }
                 }
             }
@@ -15875,11 +15911,11 @@
             "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.0.tgz",
             "integrity": "sha512-+DAn4Nb4+gz6WZigRzKEZl1QuJVOLtAwwF+WUxy1fJ6X63CaGaUAxJRD2KEn1OMfcbCjySTYpNC6WmfQoIEOdw==",
             "requires": {
-                "bl": "^3.0.0",
-                "end-of-stream": "^1.4.1",
-                "fs-constants": "^1.0.0",
-                "inherits": "^2.0.3",
-                "readable-stream": "^3.1.1"
+                "bl": "3.0.0",
+                "end-of-stream": "1.4.1",
+                "fs-constants": "1.0.0",
+                "inherits": "2.0.3",
+                "readable-stream": "3.4.0"
             },
             "dependencies": {
                 "readable-stream": {
@@ -15887,9 +15923,9 @@
                     "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz",
                     "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==",
                     "requires": {
-                        "inherits": "^2.0.3",
-                        "string_decoder": "^1.1.1",
-                        "util-deprecate": "^1.0.1"
+                        "inherits": "2.0.3",
+                        "string_decoder": "1.1.1",
+                        "util-deprecate": "1.0.2"
                     }
                 }
             }
@@ -15899,7 +15935,7 @@
             "resolved": "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz",
             "integrity": "sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=",
             "requires": {
-                "execa": "^0.7.0"
+                "execa": "0.7.0"
             }
         },
         "terser": {
@@ -15908,9 +15944,9 @@
             "integrity": "sha512-lH9zLIbX8PRBEFCTvfHGCy0s9HEKnNso1Dx9swSopF3VUnFLB8DpQ61tHxoofovNC/sG0spajJM3EIIRSTByiQ==",
             "dev": true,
             "requires": {
-                "commander": "^2.20.0",
-                "source-map": "~0.6.1",
-                "source-map-support": "~0.5.12"
+                "commander": "2.20.0",
+                "source-map": "0.6.1",
+                "source-map-support": "0.5.12"
             },
             "dependencies": {
                 "source-map": {
@@ -15927,15 +15963,15 @@
             "integrity": "sha512-QMxecFz/gHQwteWwSo5nTc6UaICqN1bMedC5sMtUc7y3Ha3Q8y6ZO0iCR8pq4RJC8Hjf0FEPEHZqcMB/+DFCrA==",
             "dev": true,
             "requires": {
-                "cacache": "^12.0.2",
-                "find-cache-dir": "^2.1.0",
-                "is-wsl": "^1.1.0",
-                "schema-utils": "^1.0.0",
-                "serialize-javascript": "^2.1.2",
-                "source-map": "^0.6.1",
-                "terser": "^4.1.2",
-                "webpack-sources": "^1.4.0",
-                "worker-farm": "^1.7.0"
+                "cacache": "12.0.3",
+                "find-cache-dir": "2.1.0",
+                "is-wsl": "1.1.0",
+                "schema-utils": "1.0.0",
+                "serialize-javascript": "2.1.2",
+                "source-map": "0.6.1",
+                "terser": "4.5.1",
+                "webpack-sources": "1.4.3",
+                "worker-farm": "1.7.0"
             },
             "dependencies": {
                 "cacache": {
@@ -15944,21 +15980,21 @@
                     "integrity": "sha512-kqdmfXEGFepesTuROHMs3MpFLWrPkSSpRqOw80RCflZXy/khxaArvFrQ7uJxSUduzAufc6G0g1VUCOZXxWavPw==",
                     "dev": true,
                     "requires": {
-                        "bluebird": "^3.5.5",
-                        "chownr": "^1.1.1",
-                        "figgy-pudding": "^3.5.1",
-                        "glob": "^7.1.4",
-                        "graceful-fs": "^4.1.15",
-                        "infer-owner": "^1.0.3",
-                        "lru-cache": "^5.1.1",
-                        "mississippi": "^3.0.0",
-                        "mkdirp": "^0.5.1",
-                        "move-concurrently": "^1.0.1",
-                        "promise-inflight": "^1.0.1",
-                        "rimraf": "^2.6.3",
-                        "ssri": "^6.0.1",
-                        "unique-filename": "^1.1.1",
-                        "y18n": "^4.0.0"
+                        "bluebird": "3.7.2",
+                        "chownr": "1.1.2",
+                        "figgy-pudding": "3.5.1",
+                        "glob": "7.1.4",
+                        "graceful-fs": "4.2.0",
+                        "infer-owner": "1.0.4",
+                        "lru-cache": "5.1.1",
+                        "mississippi": "3.0.0",
+                        "mkdirp": "0.5.1",
+                        "move-concurrently": "1.0.1",
+                        "promise-inflight": "1.0.1",
+                        "rimraf": "2.7.1",
+                        "ssri": "6.0.1",
+                        "unique-filename": "1.1.1",
+                        "y18n": "4.0.0"
                     }
                 },
                 "find-cache-dir": {
@@ -15967,9 +16003,9 @@
                     "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==",
                     "dev": true,
                     "requires": {
-                        "commondir": "^1.0.1",
-                        "make-dir": "^2.0.0",
-                        "pkg-dir": "^3.0.0"
+                        "commondir": "1.0.1",
+                        "make-dir": "2.1.0",
+                        "pkg-dir": "3.0.0"
                     }
                 },
                 "find-up": {
@@ -15978,7 +16014,7 @@
                     "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
                     "dev": true,
                     "requires": {
-                        "locate-path": "^3.0.0"
+                        "locate-path": "3.0.0"
                     }
                 },
                 "locate-path": {
@@ -15987,8 +16023,8 @@
                     "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
                     "dev": true,
                     "requires": {
-                        "p-locate": "^3.0.0",
-                        "path-exists": "^3.0.0"
+                        "p-locate": "3.0.0",
+                        "path-exists": "3.0.0"
                     }
                 },
                 "lru-cache": {
@@ -15997,7 +16033,7 @@
                     "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
                     "dev": true,
                     "requires": {
-                        "yallist": "^3.0.2"
+                        "yallist": "3.0.3"
                     }
                 },
                 "make-dir": {
@@ -16006,8 +16042,8 @@
                     "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
                     "dev": true,
                     "requires": {
-                        "pify": "^4.0.1",
-                        "semver": "^5.6.0"
+                        "pify": "4.0.1",
+                        "semver": "5.7.0"
                     }
                 },
                 "mississippi": {
@@ -16016,16 +16052,16 @@
                     "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==",
                     "dev": true,
                     "requires": {
-                        "concat-stream": "^1.5.0",
-                        "duplexify": "^3.4.2",
-                        "end-of-stream": "^1.1.0",
-                        "flush-write-stream": "^1.0.0",
-                        "from2": "^2.1.0",
-                        "parallel-transform": "^1.1.0",
-                        "pump": "^3.0.0",
-                        "pumpify": "^1.3.3",
-                        "stream-each": "^1.1.0",
-                        "through2": "^2.0.0"
+                        "concat-stream": "1.6.2",
+                        "duplexify": "3.7.1",
+                        "end-of-stream": "1.4.1",
+                        "flush-write-stream": "1.1.1",
+                        "from2": "2.3.0",
+                        "parallel-transform": "1.1.0",
+                        "pump": "3.0.0",
+                        "pumpify": "1.5.1",
+                        "stream-each": "1.2.3",
+                        "through2": "2.0.5"
                     }
                 },
                 "p-locate": {
@@ -16034,7 +16070,7 @@
                     "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
                     "dev": true,
                     "requires": {
-                        "p-limit": "^2.0.0"
+                        "p-limit": "2.2.2"
                     }
                 },
                 "pify": {
@@ -16049,7 +16085,7 @@
                     "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==",
                     "dev": true,
                     "requires": {
-                        "find-up": "^3.0.0"
+                        "find-up": "3.0.0"
                     }
                 },
                 "pump": {
@@ -16058,8 +16094,8 @@
                     "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
                     "dev": true,
                     "requires": {
-                        "end-of-stream": "^1.1.0",
-                        "once": "^1.3.1"
+                        "end-of-stream": "1.4.1",
+                        "once": "1.4.0"
                     }
                 },
                 "rimraf": {
@@ -16068,7 +16104,7 @@
                     "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
                     "dev": true,
                     "requires": {
-                        "glob": "^7.1.3"
+                        "glob": "7.1.4"
                     }
                 },
                 "schema-utils": {
@@ -16077,9 +16113,9 @@
                     "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==",
                     "dev": true,
                     "requires": {
-                        "ajv": "^6.1.0",
-                        "ajv-errors": "^1.0.0",
-                        "ajv-keywords": "^3.1.0"
+                        "ajv": "6.10.2",
+                        "ajv-errors": "1.0.1",
+                        "ajv-keywords": "3.4.1"
                     }
                 },
                 "serialize-javascript": {
@@ -16100,7 +16136,7 @@
                     "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==",
                     "dev": true,
                     "requires": {
-                        "figgy-pudding": "^3.5.1"
+                        "figgy-pudding": "3.5.1"
                     }
                 },
                 "y18n": {
@@ -16116,8 +16152,8 @@
             "resolved": "https://registry.npmjs.org/threads/-/threads-0.8.1.tgz",
             "integrity": "sha1-40ARW1lHMW0vfuMSPEwsW/nHbXI=",
             "requires": {
-                "eventemitter3": "^2.0.2",
-                "native-promise-only": "^0.8.1"
+                "eventemitter3": "2.0.3",
+                "native-promise-only": "0.8.1"
             }
         },
         "through": {
@@ -16131,8 +16167,8 @@
             "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==",
             "dev": true,
             "requires": {
-                "readable-stream": "~2.3.6",
-                "xtend": "~4.0.1"
+                "readable-stream": "2.3.6",
+                "xtend": "4.0.2"
             }
         },
         "thunky": {
@@ -16152,7 +16188,7 @@
             "integrity": "sha512-60aV6sgJ5YEbzUdn9c8kYGIqOubPoUdqQCul3SBAsRCZ40s6Y5cMcrW4dt3/k/EsbLVJNl9n6Vz3fTc+k2GeKQ==",
             "dev": true,
             "requires": {
-                "setimmediate": "^1.0.4"
+                "setimmediate": "1.0.5"
             }
         },
         "tinycolor2": {
@@ -16181,7 +16217,7 @@
             "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz",
             "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=",
             "requires": {
-                "kind-of": "^3.0.2"
+                "kind-of": "3.2.2"
             },
             "dependencies": {
                 "kind-of": {
@@ -16189,7 +16225,7 @@
                     "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
                     "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
                     "requires": {
-                        "is-buffer": "^1.1.5"
+                        "is-buffer": "1.1.6"
                     }
                 }
             }
@@ -16199,10 +16235,10 @@
             "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz",
             "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==",
             "requires": {
-                "define-property": "^2.0.2",
-                "extend-shallow": "^3.0.2",
-                "regex-not": "^1.0.2",
-                "safe-regex": "^1.1.0"
+                "define-property": "2.0.2",
+                "extend-shallow": "3.0.2",
+                "regex-not": "1.0.2",
+                "safe-regex": "1.1.0"
             }
         },
         "to-regex-range": {
@@ -16210,8 +16246,8 @@
             "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz",
             "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=",
             "requires": {
-                "is-number": "^3.0.0",
-                "repeat-string": "^1.6.1"
+                "is-number": "3.0.0",
+                "repeat-string": "1.6.1"
             }
         },
         "toidentifier": {
@@ -16229,7 +16265,7 @@
             "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz",
             "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==",
             "requires": {
-                "nopt": "~1.0.10"
+                "nopt": "1.0.10"
             },
             "dependencies": {
                 "nopt": {
@@ -16237,7 +16273,7 @@
                     "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz",
                     "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=",
                     "requires": {
-                        "abbrev": "1"
+                        "abbrev": "1.1.1"
                     }
                 }
             }
@@ -16247,8 +16283,8 @@
             "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
             "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==",
             "requires": {
-                "psl": "^1.1.24",
-                "punycode": "^1.4.1"
+                "psl": "1.2.0",
+                "punycode": "1.4.1"
             },
             "dependencies": {
                 "punycode": {
@@ -16264,7 +16300,7 @@
             "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=",
             "dev": true,
             "requires": {
-                "punycode": "^2.1.0"
+                "punycode": "2.1.1"
             }
         },
         "traverse-chain": {
@@ -16288,7 +16324,7 @@
             "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-1.0.3.tgz",
             "integrity": "sha512-m6s2OdQe5wgpFMC+pAJ+q9djG82O2jcHPOI6RNg1yy9rCYR+WD6Nbpl32fDpfC56nirdRy+opFa/Vk7HYhqaew==",
             "requires": {
-                "glob": "^7.1.2"
+                "glob": "7.1.4"
             }
         },
         "ts-loader": {
@@ -16297,11 +16333,11 @@
             "integrity": "sha512-XYsjfnRQCBum9AMRZpk2rTYSVpdZBpZK+kDh0TeT3kxmQNBDVIeUjdPjY5RZry4eIAb8XHc4gYSUiUWPYvzSRw==",
             "dev": true,
             "requires": {
-                "chalk": "^2.3.0",
-                "enhanced-resolve": "^4.0.0",
-                "loader-utils": "^1.0.2",
-                "micromatch": "^3.1.4",
-                "semver": "^5.0.1"
+                "chalk": "2.4.2",
+                "enhanced-resolve": "4.1.0",
+                "loader-utils": "1.2.3",
+                "micromatch": "3.1.10",
+                "semver": "5.7.0"
             },
             "dependencies": {
                 "ansi-styles": {
@@ -16310,7 +16346,7 @@
                     "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
                     "dev": true,
                     "requires": {
-                        "color-convert": "^1.9.0"
+                        "color-convert": "1.9.3"
                     }
                 },
                 "chalk": {
@@ -16319,9 +16355,9 @@
                     "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
                     "dev": true,
                     "requires": {
-                        "ansi-styles": "^3.2.1",
-                        "escape-string-regexp": "^1.0.5",
-                        "supports-color": "^5.3.0"
+                        "ansi-styles": "3.2.1",
+                        "escape-string-regexp": "1.0.5",
+                        "supports-color": "5.5.0"
                     }
                 },
                 "supports-color": {
@@ -16330,7 +16366,7 @@
                     "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
                     "dev": true,
                     "requires": {
-                        "has-flag": "^3.0.0"
+                        "has-flag": "3.0.0"
                     }
                 }
             }
@@ -16341,14 +16377,14 @@
             "integrity": "sha512-BVwVbPJRspzNh2yfslyT1PSbl5uIk03EZlb493RKHN4qej/D06n1cEhjlOJG69oFsE7OT8XjpTUcYf6pKTLMhw==",
             "dev": true,
             "requires": {
-                "arrify": "^1.0.0",
-                "buffer-from": "^1.1.0",
-                "diff": "^3.1.0",
-                "make-error": "^1.1.1",
-                "minimist": "^1.2.0",
-                "mkdirp": "^0.5.1",
-                "source-map-support": "^0.5.6",
-                "yn": "^2.0.0"
+                "arrify": "1.0.1",
+                "buffer-from": "1.1.1",
+                "diff": "3.5.0",
+                "make-error": "1.3.5",
+                "minimist": "1.2.0",
+                "mkdirp": "0.5.1",
+                "source-map-support": "0.5.12",
+                "yn": "2.0.0"
             },
             "dependencies": {
                 "diff": {
@@ -16371,18 +16407,18 @@
             "integrity": "sha512-M5ZwvB6FU3jtc70i5lFth86/6Qj5XR5nMMBwVxZF4cZhpO7XcbWw6tbNiJo22Zx0KfjEj9py5DANhwLOkPPufw==",
             "dev": true,
             "requires": {
-                "dateformat": "~1.0.4-1.2.3",
-                "dynamic-dedupe": "^0.3.0",
-                "filewatcher": "~3.0.0",
-                "minimist": "^1.1.3",
-                "mkdirp": "^0.5.1",
-                "node-notifier": "^5.4.0",
-                "resolve": "^1.0.0",
-                "rimraf": "^2.6.1",
-                "source-map-support": "^0.5.12",
-                "tree-kill": "^1.2.1",
-                "ts-node": "*",
-                "tsconfig": "^7.0.0"
+                "dateformat": "1.0.12",
+                "dynamic-dedupe": "0.3.0",
+                "filewatcher": "3.0.1",
+                "minimist": "1.2.0",
+                "mkdirp": "0.5.1",
+                "node-notifier": "5.4.3",
+                "resolve": "1.11.1",
+                "rimraf": "2.7.1",
+                "source-map-support": "0.5.12",
+                "tree-kill": "1.2.2",
+                "ts-node": "7.0.1",
+                "tsconfig": "7.0.0"
             },
             "dependencies": {
                 "minimist": {
@@ -16397,7 +16433,7 @@
                     "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
                     "dev": true,
                     "requires": {
-                        "glob": "^7.1.3"
+                        "glob": "7.1.4"
                     }
                 }
             }
@@ -16408,10 +16444,10 @@
             "integrity": "sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==",
             "dev": true,
             "requires": {
-                "@types/strip-bom": "^3.0.0",
+                "@types/strip-bom": "3.0.0",
                 "@types/strip-json-comments": "0.0.30",
-                "strip-bom": "^3.0.0",
-                "strip-json-comments": "^2.0.0"
+                "strip-bom": "3.0.0",
+                "strip-json-comments": "2.0.1"
             },
             "dependencies": {
                 "strip-bom": {
@@ -16434,19 +16470,19 @@
             "integrity": "sha512-EcMxhzCFt8k+/UP5r8waCf/lzmeSyVlqxqMEDQE7rWYiQky8KpIBz1JAoYXfROHrPZ1XXd43q8yQnULOLiBRQg==",
             "dev": true,
             "requires": {
-                "@babel/code-frame": "^7.0.0",
-                "builtin-modules": "^1.1.1",
-                "chalk": "^2.3.0",
-                "commander": "^2.12.1",
-                "diff": "^4.0.1",
-                "glob": "^7.1.1",
-                "js-yaml": "^3.13.1",
-                "minimatch": "^3.0.4",
-                "mkdirp": "^0.5.1",
-                "resolve": "^1.3.2",
-                "semver": "^5.3.0",
-                "tslib": "^1.8.0",
-                "tsutils": "^2.29.0"
+                "@babel/code-frame": "7.5.5",
+                "builtin-modules": "1.1.1",
+                "chalk": "2.4.2",
+                "commander": "2.20.0",
+                "diff": "4.0.1",
+                "glob": "7.1.4",
+                "js-yaml": "3.13.1",
+                "minimatch": "3.0.4",
+                "mkdirp": "0.5.1",
+                "resolve": "1.11.1",
+                "semver": "5.7.0",
+                "tslib": "1.10.0",
+                "tsutils": "2.29.0"
             },
             "dependencies": {
                 "ansi-styles": {
@@ -16455,7 +16491,7 @@
                     "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
                     "dev": true,
                     "requires": {
-                        "color-convert": "^1.9.0"
+                        "color-convert": "1.9.3"
                     }
                 },
                 "chalk": {
@@ -16464,9 +16500,9 @@
                     "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
                     "dev": true,
                     "requires": {
-                        "ansi-styles": "^3.2.1",
-                        "escape-string-regexp": "^1.0.5",
-                        "supports-color": "^5.3.0"
+                        "ansi-styles": "3.2.1",
+                        "escape-string-regexp": "1.0.5",
+                        "supports-color": "5.5.0"
                     }
                 },
                 "diff": {
@@ -16481,7 +16517,7 @@
                     "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
                     "dev": true,
                     "requires": {
-                        "has-flag": "^3.0.0"
+                        "has-flag": "3.0.0"
                     }
                 }
             }
@@ -16492,11 +16528,11 @@
             "integrity": "sha512-Me9Qf/87BOfCY8uJJw+J7VMF4U8WiMXKLhKKKugMydF0xMhMOt9wo2mjYTNhwbF9H7SHh8PAIwRG8roisTNekQ==",
             "dev": true,
             "requires": {
-                "loader-utils": "^1.0.2",
-                "mkdirp": "^0.5.1",
-                "object-assign": "^4.1.1",
-                "rimraf": "^2.4.4",
-                "semver": "^5.3.0"
+                "loader-utils": "1.2.3",
+                "mkdirp": "0.5.1",
+                "object-assign": "4.1.1",
+                "rimraf": "2.7.1",
+                "semver": "5.7.0"
             },
             "dependencies": {
                 "rimraf": {
@@ -16505,7 +16541,7 @@
                     "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
                     "dev": true,
                     "requires": {
-                        "glob": "^7.1.3"
+                        "glob": "7.1.4"
                     }
                 }
             }
@@ -16521,7 +16557,7 @@
             "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==",
             "dev": true,
             "requires": {
-                "tslib": "^1.8.1"
+                "tslib": "1.10.0"
             }
         },
         "tty-browserify": {
@@ -16535,7 +16571,7 @@
             "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
             "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
             "requires": {
-                "safe-buffer": "^5.0.1"
+                "safe-buffer": "5.1.2"
             }
         },
         "tweetnacl": {
@@ -16555,7 +16591,7 @@
             "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=",
             "dev": true,
             "requires": {
-                "prelude-ls": "~1.1.2"
+                "prelude-ls": "1.1.2"
             }
         },
         "type-detect": {
@@ -16569,7 +16605,7 @@
             "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
             "requires": {
                 "media-typer": "0.3.0",
-                "mime-types": "~2.1.24"
+                "mime-types": "2.1.24"
             }
         },
         "typedarray": {
@@ -16603,9 +16639,9 @@
             "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz",
             "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=",
             "requires": {
-                "source-map": "~0.5.1",
-                "uglify-to-browserify": "~1.0.0",
-                "yargs": "~3.10.0"
+                "source-map": "0.5.7",
+                "uglify-to-browserify": "1.0.2",
+                "yargs": "3.10.0"
             },
             "dependencies": {
                 "camelcase": {
@@ -16618,8 +16654,8 @@
                     "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz",
                     "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=",
                     "requires": {
-                        "center-align": "^0.1.1",
-                        "right-align": "^0.1.1",
+                        "center-align": "0.1.3",
+                        "right-align": "0.1.3",
                         "wordwrap": "0.0.2"
                     }
                 },
@@ -16628,9 +16664,9 @@
                     "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz",
                     "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=",
                     "requires": {
-                        "camelcase": "^1.0.2",
-                        "cliui": "^2.1.0",
-                        "decamelize": "^1.0.0",
+                        "camelcase": "1.2.1",
+                        "cliui": "2.1.0",
+                        "decamelize": "1.2.0",
                         "window-size": "0.1.0"
                     }
                 }
@@ -16647,7 +16683,7 @@
             "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
             "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==",
             "requires": {
-                "random-bytes": "~1.0.0"
+                "random-bytes": "1.0.0"
             }
         },
         "uid2": {
@@ -16660,7 +16696,7 @@
             "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-5.1.0.tgz",
             "integrity": "sha512-5FXYaFANKaafg4IVZXUNtGyzsnYEvqlr9wQ3WpZxFpEUxl29A3H6Q4G1Dnnorvq9TGOGATBApWR4YpLAh+F5hw==",
             "requires": {
-                "invariant": "^2.2.4"
+                "invariant": "2.2.4"
             }
         },
         "undefsafe": {
@@ -16668,7 +16704,7 @@
             "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.2.tgz",
             "integrity": "sha1-Il9rngM3Zj4Njnz9aG/Cg2zKznY=",
             "requires": {
-                "debug": "^2.2.0"
+                "debug": "2.6.9"
             }
         },
         "underscore": {
@@ -16681,10 +16717,10 @@
             "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz",
             "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==",
             "requires": {
-                "arr-union": "^3.1.0",
-                "get-value": "^2.0.6",
-                "is-extendable": "^0.1.1",
-                "set-value": "^2.0.1"
+                "arr-union": "3.1.0",
+                "get-value": "2.0.6",
+                "is-extendable": "0.1.1",
+                "set-value": "2.0.1"
             }
         },
         "uniq": {
@@ -16699,7 +16735,7 @@
             "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==",
             "dev": true,
             "requires": {
-                "unique-slug": "^2.0.0"
+                "unique-slug": "2.0.2"
             }
         },
         "unique-slug": {
@@ -16708,7 +16744,7 @@
             "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==",
             "dev": true,
             "requires": {
-                "imurmurhash": "^0.1.4"
+                "imurmurhash": "0.1.4"
             }
         },
         "unique-string": {
@@ -16716,7 +16752,7 @@
             "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz",
             "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=",
             "requires": {
-                "crypto-random-string": "^1.0.0"
+                "crypto-random-string": "1.0.0"
             }
         },
         "unpack-string": {
@@ -16734,8 +16770,8 @@
             "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz",
             "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=",
             "requires": {
-                "has-value": "^0.3.1",
-                "isobject": "^3.0.0"
+                "has-value": "0.3.1",
+                "isobject": "3.0.1"
             },
             "dependencies": {
                 "has-value": {
@@ -16743,9 +16779,9 @@
                     "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz",
                     "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=",
                     "requires": {
-                        "get-value": "^2.0.3",
-                        "has-values": "^0.1.4",
-                        "isobject": "^2.0.0"
+                        "get-value": "2.0.6",
+                        "has-values": "0.1.4",
+                        "isobject": "2.1.0"
                     },
                     "dependencies": {
                         "isobject": {
@@ -16780,16 +16816,16 @@
             "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-2.5.0.tgz",
             "integrity": "sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw==",
             "requires": {
-                "boxen": "^1.2.1",
-                "chalk": "^2.0.1",
-                "configstore": "^3.0.0",
-                "import-lazy": "^2.1.0",
-                "is-ci": "^1.0.10",
-                "is-installed-globally": "^0.1.0",
-                "is-npm": "^1.0.0",
-                "latest-version": "^3.0.0",
-                "semver-diff": "^2.0.0",
-                "xdg-basedir": "^3.0.0"
+                "boxen": "1.3.0",
+                "chalk": "2.4.2",
+                "configstore": "3.1.2",
+                "import-lazy": "2.1.0",
+                "is-ci": "1.2.1",
+                "is-installed-globally": "0.1.0",
+                "is-npm": "1.0.0",
+                "latest-version": "3.1.0",
+                "semver-diff": "2.1.0",
+                "xdg-basedir": "3.0.0"
             },
             "dependencies": {
                 "ansi-styles": {
@@ -16797,7 +16833,7 @@
                     "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
                     "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
                     "requires": {
-                        "color-convert": "^1.9.0"
+                        "color-convert": "1.9.3"
                     }
                 },
                 "chalk": {
@@ -16805,9 +16841,9 @@
                     "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
                     "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
                     "requires": {
-                        "ansi-styles": "^3.2.1",
-                        "escape-string-regexp": "^1.0.5",
-                        "supports-color": "^5.3.0"
+                        "ansi-styles": "3.2.1",
+                        "escape-string-regexp": "1.0.5",
+                        "supports-color": "5.5.0"
                     }
                 },
                 "supports-color": {
@@ -16815,7 +16851,7 @@
                     "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
                     "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
                     "requires": {
-                        "has-flag": "^3.0.0"
+                        "has-flag": "3.0.0"
                     }
                 }
             }
@@ -16825,7 +16861,7 @@
             "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
             "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==",
             "requires": {
-                "punycode": "^2.1.0"
+                "punycode": "2.1.1"
             }
         },
         "urix": {
@@ -16856,9 +16892,9 @@
             "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-1.1.2.tgz",
             "integrity": "sha512-dXHkKmw8FhPqu8asTc1puBfe3TehOCo2+RmOOev5suNCIYBcT626kxiWg1NBVkwc4rO8BGa7gP70W7VXuqHrjg==",
             "requires": {
-                "loader-utils": "^1.1.0",
-                "mime": "^2.0.3",
-                "schema-utils": "^1.0.0"
+                "loader-utils": "1.2.3",
+                "mime": "2.4.4",
+                "schema-utils": "1.0.0"
             },
             "dependencies": {
                 "mime": {
@@ -16871,9 +16907,9 @@
                     "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz",
                     "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==",
                     "requires": {
-                        "ajv": "^6.1.0",
-                        "ajv-errors": "^1.0.0",
-                        "ajv-keywords": "^3.1.0"
+                        "ajv": "6.10.2",
+                        "ajv-errors": "1.0.1",
+                        "ajv-keywords": "3.4.1"
                     }
                 }
             }
@@ -16884,8 +16920,8 @@
             "integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==",
             "dev": true,
             "requires": {
-                "querystringify": "^2.1.1",
-                "requires-port": "^1.0.0"
+                "querystringify": "2.1.1",
+                "requires-port": "1.0.0"
             }
         },
         "url-parse-lax": {
@@ -16893,7 +16929,7 @@
             "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz",
             "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=",
             "requires": {
-                "prepend-http": "^1.0.1"
+                "prepend-http": "1.0.4"
             }
         },
         "url-template": {
@@ -16941,8 +16977,8 @@
             "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
             "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
             "requires": {
-                "spdx-correct": "^3.0.0",
-                "spdx-expression-parse": "^3.0.0"
+                "spdx-correct": "3.1.0",
+                "spdx-expression-parse": "3.0.0"
             }
         },
         "validator": {
@@ -16960,9 +16996,9 @@
             "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
             "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
             "requires": {
-                "assert-plus": "^1.0.0",
+                "assert-plus": "1.0.0",
                 "core-util-is": "1.0.2",
-                "extsprintf": "^1.2.0"
+                "extsprintf": "1.3.0"
             },
             "dependencies": {
                 "assert-plus": {
@@ -16989,7 +17025,7 @@
             "integrity": "sha1-gqwr/2PZUOqeMYmlimViX+3xkEU=",
             "dev": true,
             "requires": {
-                "browser-process-hrtime": "^0.1.2"
+                "browser-process-hrtime": "0.1.3"
             }
         },
         "w3c-keyname": {
@@ -17003,9 +17039,9 @@
             "integrity": "sha512-p10l/ayESzrBMYWRID6xbuCKh2Fp77+sA0doRuGn4tTIMrrZVeqfpKjXHY+oDh3K4nLdPgNwMTVP6Vp4pvqbNg==",
             "dev": true,
             "requires": {
-                "domexception": "^1.0.1",
-                "webidl-conversions": "^4.0.2",
-                "xml-name-validator": "^3.0.0"
+                "domexception": "1.0.1",
+                "webidl-conversions": "4.0.2",
+                "xml-name-validator": "3.0.0"
             }
         },
         "warning": {
@@ -17013,7 +17049,7 @@
             "resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz",
             "integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=",
             "requires": {
-                "loose-envify": "^1.0.0"
+                "loose-envify": "1.4.0"
             }
         },
         "watchpack": {
@@ -17022,9 +17058,9 @@
             "integrity": "sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA==",
             "dev": true,
             "requires": {
-                "chokidar": "^2.0.2",
-                "graceful-fs": "^4.1.2",
-                "neo-async": "^2.5.0"
+                "chokidar": "2.1.8",
+                "graceful-fs": "4.2.0",
+                "neo-async": "2.6.1"
             }
         },
         "wbuf": {
@@ -17033,7 +17069,7 @@
             "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==",
             "dev": true,
             "requires": {
-                "minimalistic-assert": "^1.0.0"
+                "minimalistic-assert": "1.0.1"
             }
         },
         "webidl-conversions": {
@@ -17052,25 +17088,25 @@
                 "@webassemblyjs/helper-module-context": "1.8.5",
                 "@webassemblyjs/wasm-edit": "1.8.5",
                 "@webassemblyjs/wasm-parser": "1.8.5",
-                "acorn": "^6.2.1",
-                "ajv": "^6.10.2",
-                "ajv-keywords": "^3.4.1",
-                "chrome-trace-event": "^1.0.2",
-                "enhanced-resolve": "^4.1.0",
-                "eslint-scope": "^4.0.3",
-                "json-parse-better-errors": "^1.0.2",
-                "loader-runner": "^2.4.0",
-                "loader-utils": "^1.2.3",
-                "memory-fs": "^0.4.1",
-                "micromatch": "^3.1.10",
-                "mkdirp": "^0.5.1",
-                "neo-async": "^2.6.1",
-                "node-libs-browser": "^2.2.1",
-                "schema-utils": "^1.0.0",
-                "tapable": "^1.1.3",
-                "terser-webpack-plugin": "^1.4.3",
-                "watchpack": "^1.6.0",
-                "webpack-sources": "^1.4.1"
+                "acorn": "6.4.0",
+                "ajv": "6.10.2",
+                "ajv-keywords": "3.4.1",
+                "chrome-trace-event": "1.0.2",
+                "enhanced-resolve": "4.1.0",
+                "eslint-scope": "4.0.3",
+                "json-parse-better-errors": "1.0.2",
+                "loader-runner": "2.4.0",
+                "loader-utils": "1.2.3",
+                "memory-fs": "0.4.1",
+                "micromatch": "3.1.10",
+                "mkdirp": "0.5.1",
+                "neo-async": "2.6.1",
+                "node-libs-browser": "2.2.1",
+                "schema-utils": "1.0.0",
+                "tapable": "1.1.3",
+                "terser-webpack-plugin": "1.4.3",
+                "watchpack": "1.6.0",
+                "webpack-sources": "1.4.3"
             },
             "dependencies": {
                 "acorn": {
@@ -17085,9 +17121,9 @@
                     "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==",
                     "dev": true,
                     "requires": {
-                        "ajv": "^6.1.0",
-                        "ajv-errors": "^1.0.0",
-                        "ajv-keywords": "^3.1.0"
+                        "ajv": "6.10.2",
+                        "ajv-errors": "1.0.1",
+                        "ajv-keywords": "3.4.1"
                     }
                 }
             }
@@ -17123,7 +17159,7 @@
                     "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
                     "dev": true,
                     "requires": {
-                        "color-convert": "^1.9.0"
+                        "color-convert": "1.9.3"
                     }
                 },
                 "camelcase": {
@@ -17138,9 +17174,9 @@
                     "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
                     "dev": true,
                     "requires": {
-                        "ansi-styles": "^3.2.1",
-                        "escape-string-regexp": "^1.0.5",
-                        "supports-color": "^5.3.0"
+                        "ansi-styles": "3.2.1",
+                        "escape-string-regexp": "1.0.5",
+                        "supports-color": "5.5.0"
                     },
                     "dependencies": {
                         "supports-color": {
@@ -17149,7 +17185,7 @@
                             "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
                             "dev": true,
                             "requires": {
-                                "has-flag": "^3.0.0"
+                                "has-flag": "3.0.0"
                             }
                         }
                     }
@@ -17160,9 +17196,9 @@
                     "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==",
                     "dev": true,
                     "requires": {
-                        "string-width": "^3.1.0",
-                        "strip-ansi": "^5.2.0",
-                        "wrap-ansi": "^5.1.0"
+                        "string-width": "3.1.0",
+                        "strip-ansi": "5.2.0",
+                        "wrap-ansi": "5.1.0"
                     }
                 },
                 "execa": {
@@ -17171,13 +17207,13 @@
                     "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==",
                     "dev": true,
                     "requires": {
-                        "cross-spawn": "^6.0.0",
-                        "get-stream": "^4.0.0",
-                        "is-stream": "^1.1.0",
-                        "npm-run-path": "^2.0.0",
-                        "p-finally": "^1.0.0",
-                        "signal-exit": "^3.0.0",
-                        "strip-eof": "^1.0.0"
+                        "cross-spawn": "6.0.5",
+                        "get-stream": "4.1.0",
+                        "is-stream": "1.1.0",
+                        "npm-run-path": "2.0.2",
+                        "p-finally": "1.0.0",
+                        "signal-exit": "3.0.2",
+                        "strip-eof": "1.0.0"
                     }
                 },
                 "find-up": {
@@ -17186,7 +17222,7 @@
                     "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
                     "dev": true,
                     "requires": {
-                        "locate-path": "^3.0.0"
+                        "locate-path": "3.0.0"
                     }
                 },
                 "get-caller-file": {
@@ -17201,7 +17237,7 @@
                     "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
                     "dev": true,
                     "requires": {
-                        "pump": "^3.0.0"
+                        "pump": "3.0.0"
                     }
                 },
                 "invert-kv": {
@@ -17222,7 +17258,7 @@
                     "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==",
                     "dev": true,
                     "requires": {
-                        "invert-kv": "^2.0.0"
+                        "invert-kv": "2.0.0"
                     }
                 },
                 "locate-path": {
@@ -17231,8 +17267,8 @@
                     "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
                     "dev": true,
                     "requires": {
-                        "p-locate": "^3.0.0",
-                        "path-exists": "^3.0.0"
+                        "p-locate": "3.0.0",
+                        "path-exists": "3.0.0"
                     }
                 },
                 "os-locale": {
@@ -17241,9 +17277,9 @@
                     "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==",
                     "dev": true,
                     "requires": {
-                        "execa": "^1.0.0",
-                        "lcid": "^2.0.0",
-                        "mem": "^4.0.0"
+                        "execa": "1.0.0",
+                        "lcid": "2.0.0",
+                        "mem": "4.3.0"
                     }
                 },
                 "p-locate": {
@@ -17252,7 +17288,7 @@
                     "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
                     "dev": true,
                     "requires": {
-                        "p-limit": "^2.0.0"
+                        "p-limit": "2.2.2"
                     }
                 },
                 "pump": {
@@ -17261,8 +17297,8 @@
                     "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
                     "dev": true,
                     "requires": {
-                        "end-of-stream": "^1.1.0",
-                        "once": "^1.3.1"
+                        "end-of-stream": "1.4.1",
+                        "once": "1.4.0"
                     }
                 },
                 "require-main-filename": {
@@ -17277,9 +17313,9 @@
                     "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
                     "dev": true,
                     "requires": {
-                        "emoji-regex": "^7.0.1",
-                        "is-fullwidth-code-point": "^2.0.0",
-                        "strip-ansi": "^5.1.0"
+                        "emoji-regex": "7.0.3",
+                        "is-fullwidth-code-point": "2.0.0",
+                        "strip-ansi": "5.2.0"
                     }
                 },
                 "strip-ansi": {
@@ -17288,7 +17324,7 @@
                     "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
                     "dev": true,
                     "requires": {
-                        "ansi-regex": "^4.1.0"
+                        "ansi-regex": "4.1.0"
                     }
                 },
                 "supports-color": {
@@ -17297,7 +17333,7 @@
                     "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
                     "dev": true,
                     "requires": {
-                        "has-flag": "^3.0.0"
+                        "has-flag": "3.0.0"
                     }
                 },
                 "which-module": {
@@ -17312,9 +17348,9 @@
                     "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
                     "dev": true,
                     "requires": {
-                        "ansi-styles": "^3.2.0",
-                        "string-width": "^3.0.0",
-                        "strip-ansi": "^5.0.0"
+                        "ansi-styles": "3.2.1",
+                        "string-width": "3.1.0",
+                        "strip-ansi": "5.2.0"
                     }
                 },
                 "y18n": {
@@ -17329,17 +17365,17 @@
                     "integrity": "sha512-HG/DWAJa1PAnHT9JAhNa8AbAv3FPaiLzioSjCcmuXXhP8MlpHO5vwls4g4j6n30Z74GVQj8Xa62dWVx1QCGklg==",
                     "dev": true,
                     "requires": {
-                        "cliui": "^5.0.0",
-                        "find-up": "^3.0.0",
-                        "get-caller-file": "^2.0.1",
-                        "os-locale": "^3.1.0",
-                        "require-directory": "^2.1.1",
-                        "require-main-filename": "^2.0.0",
-                        "set-blocking": "^2.0.0",
-                        "string-width": "^3.0.0",
-                        "which-module": "^2.0.0",
-                        "y18n": "^4.0.0",
-                        "yargs-parser": "^13.1.0"
+                        "cliui": "5.0.0",
+                        "find-up": "3.0.0",
+                        "get-caller-file": "2.0.5",
+                        "os-locale": "3.1.0",
+                        "require-directory": "2.1.1",
+                        "require-main-filename": "2.0.0",
+                        "set-blocking": "2.0.0",
+                        "string-width": "3.1.0",
+                        "which-module": "2.0.0",
+                        "y18n": "4.0.0",
+                        "yargs-parser": "13.1.1"
                     }
                 },
                 "yargs-parser": {
@@ -17348,8 +17384,8 @@
                     "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==",
                     "dev": true,
                     "requires": {
-                        "camelcase": "^5.0.0",
-                        "decamelize": "^1.2.0"
+                        "camelcase": "5.3.1",
+                        "decamelize": "1.2.0"
                     }
                 }
             }
@@ -17360,11 +17396,11 @@
             "integrity": "sha512-1xC42LxbYoqLNAhV6YzTYacicgMZQTqRd27Sim9wn5hJrX3I5nxYy1SxSd4+gjUFsz1dQFj+yEe6zEVmSkeJjw==",
             "dev": true,
             "requires": {
-                "memory-fs": "^0.4.1",
-                "mime": "^2.4.4",
-                "mkdirp": "^0.5.1",
-                "range-parser": "^1.2.1",
-                "webpack-log": "^2.0.0"
+                "memory-fs": "0.4.1",
+                "mime": "2.4.4",
+                "mkdirp": "0.5.1",
+                "range-parser": "1.2.1",
+                "webpack-log": "2.0.0"
             },
             "dependencies": {
                 "mime": {
@@ -17379,8 +17415,8 @@
                     "integrity": "sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg==",
                     "dev": true,
                     "requires": {
-                        "ansi-colors": "^3.0.0",
-                        "uuid": "^3.3.2"
+                        "ansi-colors": "3.2.4",
+                        "uuid": "3.4.0"
                     }
                 }
             }
@@ -17392,37 +17428,37 @@
             "dev": true,
             "requires": {
                 "ansi-html": "0.0.7",
-                "bonjour": "^3.5.0",
-                "chokidar": "^2.1.8",
-                "compression": "^1.7.4",
-                "connect-history-api-fallback": "^1.6.0",
-                "debug": "^4.1.1",
-                "del": "^4.1.1",
-                "express": "^4.17.1",
-                "html-entities": "^1.2.1",
+                "bonjour": "3.5.0",
+                "chokidar": "2.1.8",
+                "compression": "1.7.4",
+                "connect-history-api-fallback": "1.6.0",
+                "debug": "4.1.1",
+                "del": "4.1.1",
+                "express": "4.17.1",
+                "html-entities": "1.2.1",
                 "http-proxy-middleware": "0.19.1",
-                "import-local": "^2.0.0",
-                "internal-ip": "^4.3.0",
-                "ip": "^1.1.5",
-                "is-absolute-url": "^3.0.3",
-                "killable": "^1.0.1",
-                "loglevel": "^1.6.6",
-                "opn": "^5.5.0",
-                "p-retry": "^3.0.1",
-                "portfinder": "^1.0.25",
-                "schema-utils": "^1.0.0",
-                "selfsigned": "^1.10.7",
-                "semver": "^6.3.0",
-                "serve-index": "^1.9.1",
+                "import-local": "2.0.0",
+                "internal-ip": "4.3.0",
+                "ip": "1.1.5",
+                "is-absolute-url": "3.0.3",
+                "killable": "1.0.1",
+                "loglevel": "1.6.6",
+                "opn": "5.5.0",
+                "p-retry": "3.0.1",
+                "portfinder": "1.0.25",
+                "schema-utils": "1.0.0",
+                "selfsigned": "1.10.7",
+                "semver": "6.3.0",
+                "serve-index": "1.9.1",
                 "sockjs": "0.3.19",
                 "sockjs-client": "1.4.0",
-                "spdy": "^4.0.1",
-                "strip-ansi": "^3.0.1",
-                "supports-color": "^6.1.0",
-                "url": "^0.11.0",
-                "webpack-dev-middleware": "^3.7.2",
-                "webpack-log": "^2.0.0",
-                "ws": "^6.2.1",
+                "spdy": "4.0.1",
+                "strip-ansi": "3.0.1",
+                "supports-color": "6.1.0",
+                "url": "0.11.0",
+                "webpack-dev-middleware": "3.7.2",
+                "webpack-log": "2.0.0",
+                "ws": "6.2.1",
                 "yargs": "12.0.5"
             },
             "dependencies": {
@@ -17444,9 +17480,9 @@
                     "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==",
                     "dev": true,
                     "requires": {
-                        "string-width": "^2.1.1",
-                        "strip-ansi": "^4.0.0",
-                        "wrap-ansi": "^2.0.0"
+                        "string-width": "2.1.1",
+                        "strip-ansi": "4.0.0",
+                        "wrap-ansi": "2.1.0"
                     },
                     "dependencies": {
                         "strip-ansi": {
@@ -17455,7 +17491,7 @@
                             "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
                             "dev": true,
                             "requires": {
-                                "ansi-regex": "^3.0.0"
+                                "ansi-regex": "3.0.0"
                             }
                         }
                     }
@@ -17466,7 +17502,7 @@
                     "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
                     "dev": true,
                     "requires": {
-                        "ms": "^2.1.1"
+                        "ms": "2.1.2"
                     }
                 },
                 "execa": {
@@ -17475,13 +17511,13 @@
                     "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==",
                     "dev": true,
                     "requires": {
-                        "cross-spawn": "^6.0.0",
-                        "get-stream": "^4.0.0",
-                        "is-stream": "^1.1.0",
-                        "npm-run-path": "^2.0.0",
-                        "p-finally": "^1.0.0",
-                        "signal-exit": "^3.0.0",
-                        "strip-eof": "^1.0.0"
+                        "cross-spawn": "6.0.5",
+                        "get-stream": "4.1.0",
+                        "is-stream": "1.1.0",
+                        "npm-run-path": "2.0.2",
+                        "p-finally": "1.0.0",
+                        "signal-exit": "3.0.2",
+                        "strip-eof": "1.0.0"
                     }
                 },
                 "find-up": {
@@ -17490,7 +17526,7 @@
                     "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
                     "dev": true,
                     "requires": {
-                        "locate-path": "^3.0.0"
+                        "locate-path": "3.0.0"
                     }
                 },
                 "get-stream": {
@@ -17499,7 +17535,7 @@
                     "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
                     "dev": true,
                     "requires": {
-                        "pump": "^3.0.0"
+                        "pump": "3.0.0"
                     }
                 },
                 "invert-kv": {
@@ -17520,7 +17556,7 @@
                     "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==",
                     "dev": true,
                     "requires": {
-                        "invert-kv": "^2.0.0"
+                        "invert-kv": "2.0.0"
                     }
                 },
                 "locate-path": {
@@ -17529,8 +17565,8 @@
                     "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
                     "dev": true,
                     "requires": {
-                        "p-locate": "^3.0.0",
-                        "path-exists": "^3.0.0"
+                        "p-locate": "3.0.0",
+                        "path-exists": "3.0.0"
                     }
                 },
                 "loglevel": {
@@ -17551,9 +17587,9 @@
                     "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==",
                     "dev": true,
                     "requires": {
-                        "execa": "^1.0.0",
-                        "lcid": "^2.0.0",
-                        "mem": "^4.0.0"
+                        "execa": "1.0.0",
+                        "lcid": "2.0.0",
+                        "mem": "4.3.0"
                     }
                 },
                 "p-locate": {
@@ -17562,7 +17598,7 @@
                     "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
                     "dev": true,
                     "requires": {
-                        "p-limit": "^2.0.0"
+                        "p-limit": "2.2.2"
                     }
                 },
                 "pump": {
@@ -17571,8 +17607,8 @@
                     "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
                     "dev": true,
                     "requires": {
-                        "end-of-stream": "^1.1.0",
-                        "once": "^1.3.1"
+                        "end-of-stream": "1.4.1",
+                        "once": "1.4.0"
                     }
                 },
                 "schema-utils": {
@@ -17581,9 +17617,9 @@
                     "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==",
                     "dev": true,
                     "requires": {
-                        "ajv": "^6.1.0",
-                        "ajv-errors": "^1.0.0",
-                        "ajv-keywords": "^3.1.0"
+                        "ajv": "6.10.2",
+                        "ajv-errors": "1.0.1",
+                        "ajv-keywords": "3.4.1"
                     }
                 },
                 "semver": {
@@ -17598,8 +17634,8 @@
                     "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
                     "dev": true,
                     "requires": {
-                        "is-fullwidth-code-point": "^2.0.0",
-                        "strip-ansi": "^4.0.0"
+                        "is-fullwidth-code-point": "2.0.0",
+                        "strip-ansi": "4.0.0"
                     },
                     "dependencies": {
                         "strip-ansi": {
@@ -17608,7 +17644,7 @@
                             "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
                             "dev": true,
                             "requires": {
-                                "ansi-regex": "^3.0.0"
+                                "ansi-regex": "3.0.0"
                             }
                         }
                     }
@@ -17619,7 +17655,7 @@
                     "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
                     "dev": true,
                     "requires": {
-                        "has-flag": "^3.0.0"
+                        "has-flag": "3.0.0"
                     }
                 },
                 "webpack-log": {
@@ -17628,8 +17664,8 @@
                     "integrity": "sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg==",
                     "dev": true,
                     "requires": {
-                        "ansi-colors": "^3.0.0",
-                        "uuid": "^3.3.2"
+                        "ansi-colors": "3.2.4",
+                        "uuid": "3.4.0"
                     }
                 },
                 "which-module": {
@@ -17644,7 +17680,7 @@
                     "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==",
                     "dev": true,
                     "requires": {
-                        "async-limiter": "~1.0.0"
+                        "async-limiter": "1.0.1"
                     }
                 },
                 "yargs": {
@@ -17653,18 +17689,18 @@
                     "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==",
                     "dev": true,
                     "requires": {
-                        "cliui": "^4.0.0",
-                        "decamelize": "^1.2.0",
-                        "find-up": "^3.0.0",
-                        "get-caller-file": "^1.0.1",
-                        "os-locale": "^3.0.0",
-                        "require-directory": "^2.1.1",
-                        "require-main-filename": "^1.0.1",
-                        "set-blocking": "^2.0.0",
-                        "string-width": "^2.0.0",
-                        "which-module": "^2.0.0",
-                        "y18n": "^3.2.1 || ^4.0.0",
-                        "yargs-parser": "^11.1.1"
+                        "cliui": "4.1.0",
+                        "decamelize": "1.2.0",
+                        "find-up": "3.0.0",
+                        "get-caller-file": "1.0.3",
+                        "os-locale": "3.1.0",
+                        "require-directory": "2.1.1",
+                        "require-main-filename": "1.0.1",
+                        "set-blocking": "2.0.0",
+                        "string-width": "2.1.1",
+                        "which-module": "2.0.0",
+                        "y18n": "3.2.1",
+                        "yargs-parser": "11.1.1"
                     }
                 },
                 "yargs-parser": {
@@ -17673,8 +17709,8 @@
                     "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==",
                     "dev": true,
                     "requires": {
-                        "camelcase": "^5.0.0",
-                        "decamelize": "^1.2.0"
+                        "camelcase": "5.3.1",
+                        "decamelize": "1.2.0"
                     }
                 }
             }
@@ -17686,9 +17722,9 @@
             "dev": true,
             "requires": {
                 "ansi-html": "0.0.7",
-                "html-entities": "^1.2.0",
-                "querystring": "^0.2.0",
-                "strip-ansi": "^3.0.0"
+                "html-entities": "1.2.1",
+                "querystring": "0.2.0",
+                "strip-ansi": "3.0.1"
             }
         },
         "webpack-log": {
@@ -17697,10 +17733,10 @@
             "integrity": "sha512-U9AnICnu50HXtiqiDxuli5gLB5PGBo7VvcHx36jRZHwK4vzOYLbImqT4lwWwoMHdQWwEKw736fCHEekokTEKHA==",
             "dev": true,
             "requires": {
-                "chalk": "^2.1.0",
-                "log-symbols": "^2.1.0",
-                "loglevelnext": "^1.0.1",
-                "uuid": "^3.1.0"
+                "chalk": "2.4.2",
+                "log-symbols": "2.2.0",
+                "loglevelnext": "1.0.5",
+                "uuid": "3.4.0"
             },
             "dependencies": {
                 "ansi-styles": {
@@ -17709,7 +17745,7 @@
                     "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
                     "dev": true,
                     "requires": {
-                        "color-convert": "^1.9.0"
+                        "color-convert": "1.9.3"
                     }
                 },
                 "chalk": {
@@ -17718,9 +17754,9 @@
                     "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
                     "dev": true,
                     "requires": {
-                        "ansi-styles": "^3.2.1",
-                        "escape-string-regexp": "^1.0.5",
-                        "supports-color": "^5.3.0"
+                        "ansi-styles": "3.2.1",
+                        "escape-string-regexp": "1.0.5",
+                        "supports-color": "5.5.0"
                     }
                 },
                 "supports-color": {
@@ -17729,7 +17765,7 @@
                     "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
                     "dev": true,
                     "requires": {
-                        "has-flag": "^3.0.0"
+                        "has-flag": "3.0.0"
                     }
                 }
             }
@@ -17740,8 +17776,8 @@
             "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==",
             "dev": true,
             "requires": {
-                "source-list-map": "^2.0.0",
-                "source-map": "~0.6.1"
+                "source-list-map": "2.0.1",
+                "source-map": "0.6.1"
             },
             "dependencies": {
                 "source-map": {
@@ -17757,8 +17793,8 @@
             "resolved": "https://registry.npmjs.org/webrtc-adapter/-/webrtc-adapter-7.4.0.tgz",
             "integrity": "sha512-YeflMTsqnQ6/7satrJzjzN9RjpkDDsEdoRuEkIhk+oOmWgDL1ocdWhZ1lPdB21ZXXY/AmEih4cHgKoW3SYw20A==",
             "requires": {
-                "rtcpeerconnection-shim": "^1.2.15",
-                "sdp": "^2.12.0"
+                "rtcpeerconnection-shim": "1.2.15",
+                "sdp": "2.12.0"
             }
         },
         "websocket-driver": {
@@ -17767,9 +17803,9 @@
             "integrity": "sha512-bpxWlvbbB459Mlipc5GBzzZwhoZgGEZLuqPaR0INBGnPAY1vdBX6hPnoFXiw+3yWxDuHyQjO2oXTMyS8A5haFg==",
             "dev": true,
             "requires": {
-                "http-parser-js": ">=0.4.0 <0.4.11",
-                "safe-buffer": ">=5.1.0",
-                "websocket-extensions": ">=0.1.1"
+                "http-parser-js": "0.4.10",
+                "safe-buffer": "5.1.2",
+                "websocket-extensions": "0.1.3"
             }
         },
         "websocket-extensions": {
@@ -17804,9 +17840,9 @@
             "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==",
             "dev": true,
             "requires": {
-                "lodash.sortby": "^4.7.0",
-                "tr46": "^1.0.1",
-                "webidl-conversions": "^4.0.2"
+                "lodash.sortby": "4.7.0",
+                "tr46": "1.0.1",
+                "webidl-conversions": "4.0.2"
             }
         },
         "which": {
@@ -17814,7 +17850,7 @@
             "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
             "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
             "requires": {
-                "isexe": "^2.0.0"
+                "isexe": "2.0.0"
             }
         },
         "which-module": {
@@ -17832,7 +17868,7 @@
             "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
             "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
             "requires": {
-                "string-width": "^1.0.2 || 2"
+                "string-width": "1.0.2"
             }
         },
         "widest-line": {
@@ -17840,7 +17876,7 @@
             "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz",
             "integrity": "sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==",
             "requires": {
-                "string-width": "^2.1.1"
+                "string-width": "2.1.1"
             },
             "dependencies": {
                 "ansi-regex": {
@@ -17858,8 +17894,8 @@
                     "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
                     "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
                     "requires": {
-                        "is-fullwidth-code-point": "^2.0.0",
-                        "strip-ansi": "^4.0.0"
+                        "is-fullwidth-code-point": "2.0.0",
+                        "strip-ansi": "4.0.0"
                     }
                 },
                 "strip-ansi": {
@@ -17867,7 +17903,7 @@
                     "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
                     "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
                     "requires": {
-                        "ansi-regex": "^3.0.0"
+                        "ansi-regex": "3.0.0"
                     }
                 }
             }
@@ -17877,8 +17913,8 @@
             "resolved": "https://registry.npmjs.org/wikijs/-/wikijs-6.0.1.tgz",
             "integrity": "sha512-67ZtXyVPspYM5/B5ci0NIwvPJyG23HPk33QQLgLbCcORQ6N0I3Mhxd/KsPRh3xyly87KDs/bh1xuIG6PVTCKGw==",
             "requires": {
-                "cheerio": "^1.0.0-rc.3",
-                "cross-fetch": "^3.0.2",
+                "cheerio": "1.0.0-rc.3",
+                "cross-fetch": "3.0.4",
                 "infobox-parser": "3.3.1"
             }
         },
@@ -17892,8 +17928,8 @@
             "resolved": "https://registry.npmjs.org/with/-/with-5.1.1.tgz",
             "integrity": "sha1-+k2qktrzLE6pTtRTyB8EaGtXXf4=",
             "requires": {
-                "acorn": "^3.1.0",
-                "acorn-globals": "^3.0.0"
+                "acorn": "3.3.0",
+                "acorn-globals": "3.1.0"
             },
             "dependencies": {
                 "acorn": {
@@ -17908,9 +17944,9 @@
             "resolved": "https://registry.npmjs.org/word-extractor/-/word-extractor-0.3.0.tgz",
             "integrity": "sha512-gFRsCojw2sijgoHXAa11b9K2OeEvajw4iQp89doDlseVYklR56Z22AlzDKeP1bo4N5wT/AJWptbcPQPstXVlsQ==",
             "requires": {
-                "async": "^1.5.2",
-                "event-stream": "^3.3.4",
-                "underscore": "^1.8.3"
+                "async": "1.5.2",
+                "event-stream": "3.3.5",
+                "underscore": "1.9.1"
             },
             "dependencies": {
                 "async": {
@@ -17931,9 +17967,9 @@
             "resolved": "https://registry.npmjs.org/words-to-numbers/-/words-to-numbers-1.5.1.tgz",
             "integrity": "sha512-uvz7zSCKmmA7o5f5zp4Z5l24RQhy6HSNu10URhNxQWv1I82RsFaZX3qD07RLFUMJsCV38oAuaca13AvhO+9yGw==",
             "requires": {
-                "babel-runtime": "6.x.x",
-                "clj-fuzzy": "^0.3.2",
-                "its-set": "^1.1.5"
+                "babel-runtime": "6.26.0",
+                "clj-fuzzy": "0.3.3",
+                "its-set": "1.2.3"
             }
         },
         "wordwrap": {
@@ -17946,8 +17982,8 @@
             "resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-3.0.0.tgz",
             "integrity": "sha512-mO8XtqyPvykVCsrwj5MlOVWvSnCdT+C+QVbm6blradR7JExAhbkZ7hZ9A+9NUtwzSqrlUo9a67ws0EiILrvRpw==",
             "requires": {
-                "reduce-flatten": "^1.0.1",
-                "typical": "^2.6.1"
+                "reduce-flatten": "1.0.1",
+                "typical": "2.6.1"
             }
         },
         "worker-farm": {
@@ -17956,7 +17992,7 @@
             "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==",
             "dev": true,
             "requires": {
-                "errno": "~0.1.7"
+                "errno": "0.1.7"
             }
         },
         "worker-loader": {
@@ -17964,8 +18000,8 @@
             "resolved": "https://registry.npmjs.org/worker-loader/-/worker-loader-2.0.0.tgz",
             "integrity": "sha512-tnvNp4K3KQOpfRnD20m8xltE3eWh89Ye+5oj7wXEEHKac1P4oZ6p9oTj8/8ExqoSBnk9nu5Pr4nKfQ1hn2APJw==",
             "requires": {
-                "loader-utils": "^1.0.0",
-                "schema-utils": "^0.4.0"
+                "loader-utils": "1.2.3",
+                "schema-utils": "0.4.7"
             }
         },
         "worker-rpc": {
@@ -17974,16 +18010,16 @@
             "integrity": "sha512-P1WjMrUB3qgJNI9jfmpZ/htmBEjFh//6l/5y8SD9hg1Ef5zTTVVoRjTrTEzPrNBQvmhMxkoTsjOXN10GWU7aCg==",
             "dev": true,
             "requires": {
-                "microevent.ts": "~0.1.1"
+                "microevent.ts": "0.1.1"
             }
         },
         "wrap-ansi": {
             "version": "2.1.0",
-            "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
+            "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
             "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
             "requires": {
-                "string-width": "^1.0.1",
-                "strip-ansi": "^3.0.1"
+                "string-width": "1.0.2",
+                "strip-ansi": "3.0.1"
             }
         },
         "wrappy": {
@@ -17996,9 +18032,9 @@
             "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz",
             "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==",
             "requires": {
-                "graceful-fs": "^4.1.11",
-                "imurmurhash": "^0.1.4",
-                "signal-exit": "^3.0.2"
+                "graceful-fs": "4.2.0",
+                "imurmurhash": "0.1.4",
+                "signal-exit": "3.0.2"
             }
         },
         "ws": {
@@ -18022,7 +18058,7 @@
             "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.1.9.tgz",
             "integrity": "sha1-wm/Qgm4Bor5xEHSKNPD4OFvkWfE=",
             "requires": {
-                "sax": ">=0.1.1"
+                "sax": "1.2.4"
             }
         },
         "xmlchars": {
@@ -18062,7 +18098,7 @@
             "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.7.2.tgz",
             "integrity": "sha512-qXROVp90sb83XtAoqE8bP9RwAkTTZbugRUTm5YeFCBfNRPEp2YzTeqWiz7m5OORHzEvrA/qcGS8hp/E+MMROYw==",
             "requires": {
-                "@babel/runtime": "^7.6.3"
+                "@babel/runtime": "7.7.6"
             },
             "dependencies": {
                 "@babel/runtime": {
@@ -18070,7 +18106,7 @@
                     "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.7.6.tgz",
                     "integrity": "sha512-BWAJxpNVa0QlE5gZdWjSxXtemZyZ9RmrmVozxt3NUXeZhVIJ5ANyqmMc0JDrivBZyxUuQvFxlvH4OWWOogGfUw==",
                     "requires": {
-                        "regenerator-runtime": "^0.13.2"
+                        "regenerator-runtime": "0.13.3"
                     }
                 }
             }
@@ -18080,19 +18116,19 @@
             "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz",
             "integrity": "sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=",
             "requires": {
-                "camelcase": "^3.0.0",
-                "cliui": "^3.2.0",
-                "decamelize": "^1.1.1",
-                "get-caller-file": "^1.0.1",
-                "os-locale": "^1.4.0",
-                "read-pkg-up": "^1.0.1",
-                "require-directory": "^2.1.1",
-                "require-main-filename": "^1.0.1",
-                "set-blocking": "^2.0.0",
-                "string-width": "^1.0.2",
-                "which-module": "^1.0.0",
-                "y18n": "^3.2.1",
-                "yargs-parser": "^5.0.0"
+                "camelcase": "3.0.0",
+                "cliui": "3.2.0",
+                "decamelize": "1.2.0",
+                "get-caller-file": "1.0.3",
+                "os-locale": "1.4.0",
+                "read-pkg-up": "1.0.1",
+                "require-directory": "2.1.1",
+                "require-main-filename": "1.0.1",
+                "set-blocking": "2.0.0",
+                "string-width": "1.0.2",
+                "which-module": "1.0.0",
+                "y18n": "3.2.1",
+                "yargs-parser": "5.0.0"
             },
             "dependencies": {
                 "camelcase": {
@@ -18105,7 +18141,7 @@
                     "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0.tgz",
                     "integrity": "sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=",
                     "requires": {
-                        "camelcase": "^3.0.0"
+                        "camelcase": "3.0.0"
                     }
                 }
             }
@@ -18115,7 +18151,7 @@
             "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-7.0.0.tgz",
             "integrity": "sha1-jQrELxbqVd69MyyvTEA4s+P139k=",
             "requires": {
-                "camelcase": "^4.1.0"
+                "camelcase": "4.1.0"
             }
         },
         "yargs-unparser": {
@@ -18123,9 +18159,9 @@
             "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz",
             "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==",
             "requires": {
-                "flat": "^4.1.0",
-                "lodash": "^4.17.15",
-                "yargs": "^13.3.0"
+                "flat": "4.1.0",
+                "lodash": "4.17.15",
+                "yargs": "13.3.0"
             },
             "dependencies": {
                 "ansi-regex": {
@@ -18138,7 +18174,7 @@
                     "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
                     "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
                     "requires": {
-                        "color-convert": "^1.9.0"
+                        "color-convert": "1.9.3"
                     }
                 },
                 "camelcase": {
@@ -18151,9 +18187,9 @@
                     "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
                     "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==",
                     "requires": {
-                        "string-width": "^3.1.0",
-                        "strip-ansi": "^5.2.0",
-                        "wrap-ansi": "^5.1.0"
+                        "string-width": "3.1.0",
+                        "strip-ansi": "5.2.0",
+                        "wrap-ansi": "5.1.0"
                     }
                 },
                 "find-up": {
@@ -18161,7 +18197,7 @@
                     "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
                     "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
                     "requires": {
-                        "locate-path": "^3.0.0"
+                        "locate-path": "3.0.0"
                     }
                 },
                 "get-caller-file": {
@@ -18179,8 +18215,8 @@
                     "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
                     "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
                     "requires": {
-                        "p-locate": "^3.0.0",
-                        "path-exists": "^3.0.0"
+                        "p-locate": "3.0.0",
+                        "path-exists": "3.0.0"
                     }
                 },
                 "p-locate": {
@@ -18188,7 +18224,7 @@
                     "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
                     "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
                     "requires": {
-                        "p-limit": "^2.0.0"
+                        "p-limit": "2.2.2"
                     }
                 },
                 "require-main-filename": {
@@ -18201,9 +18237,9 @@
                     "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
                     "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
                     "requires": {
-                        "emoji-regex": "^7.0.1",
-                        "is-fullwidth-code-point": "^2.0.0",
-                        "strip-ansi": "^5.1.0"
+                        "emoji-regex": "7.0.3",
+                        "is-fullwidth-code-point": "2.0.0",
+                        "strip-ansi": "5.2.0"
                     }
                 },
                 "strip-ansi": {
@@ -18211,7 +18247,7 @@
                     "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
                     "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
                     "requires": {
-                        "ansi-regex": "^4.1.0"
+                        "ansi-regex": "4.1.0"
                     }
                 },
                 "which-module": {
@@ -18224,9 +18260,9 @@
                     "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
                     "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
                     "requires": {
-                        "ansi-styles": "^3.2.0",
-                        "string-width": "^3.0.0",
-                        "strip-ansi": "^5.0.0"
+                        "ansi-styles": "3.2.1",
+                        "string-width": "3.1.0",
+                        "strip-ansi": "5.2.0"
                     }
                 },
                 "y18n": {
@@ -18239,16 +18275,16 @@
                     "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.0.tgz",
                     "integrity": "sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA==",
                     "requires": {
-                        "cliui": "^5.0.0",
-                        "find-up": "^3.0.0",
-                        "get-caller-file": "^2.0.1",
-                        "require-directory": "^2.1.1",
-                        "require-main-filename": "^2.0.0",
-                        "set-blocking": "^2.0.0",
-                        "string-width": "^3.0.0",
-                        "which-module": "^2.0.0",
-                        "y18n": "^4.0.0",
-                        "yargs-parser": "^13.1.1"
+                        "cliui": "5.0.0",
+                        "find-up": "3.0.0",
+                        "get-caller-file": "2.0.5",
+                        "require-directory": "2.1.1",
+                        "require-main-filename": "2.0.0",
+                        "set-blocking": "2.0.0",
+                        "string-width": "3.1.0",
+                        "which-module": "2.0.0",
+                        "y18n": "4.0.0",
+                        "yargs-parser": "13.1.1"
                     }
                 },
                 "yargs-parser": {
@@ -18256,8 +18292,8 @@
                     "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz",
                     "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==",
                     "requires": {
-                        "camelcase": "^5.0.0",
-                        "decamelize": "^1.2.0"
+                        "camelcase": "5.3.1",
+                        "decamelize": "1.2.0"
                     }
                 }
             }
@@ -18306,9 +18342,9 @@
             "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-2.1.2.tgz",
             "integrity": "sha512-ykebHGa2+uzth/R4HZLkZh3XFJzivhVsjJt8bN3GvBzLaqqrUdRacu+c4QtnUgjkkQfsOuNE1JgLKMCPNmkKgg==",
             "requires": {
-                "archiver-utils": "^2.1.0",
-                "compress-commons": "^2.1.1",
-                "readable-stream": "^3.4.0"
+                "archiver-utils": "2.1.0",
+                "compress-commons": "2.1.1",
+                "readable-stream": "3.4.0"
             },
             "dependencies": {
                 "readable-stream": {
@@ -18316,9 +18352,9 @@
                     "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz",
                     "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==",
                     "requires": {
-                        "inherits": "^2.0.3",
-                        "string_decoder": "^1.1.1",
-                        "util-deprecate": "^1.0.1"
+                        "inherits": "2.0.3",
+                        "string_decoder": "1.1.1",
+                        "util-deprecate": "1.0.2"
                     }
                 }
             }
diff --git a/package.json b/package.json
index 8acb38812..da9f8705f 100644
--- a/package.json
+++ b/package.json
@@ -176,7 +176,7 @@
         "nodemailer": "^5.1.1",
         "nodemon": "^1.19.4",
         "normalize.css": "^8.0.1",
-        "npm": "^6.13.6",
+        "npm": "^6.13.7",
         "p-limit": "^2.2.0",
         "passport": "^0.4.0",
         "passport-google-oauth20": "^2.0.0",
@@ -230,12 +230,12 @@
         "standard-http-error": "^2.0.1",
         "typescript-collections": "^1.3.3",
         "url-loader": "^1.1.2",
-        "webrtc-adapter": "^7.3.0",
         "uuid": "^3.4.0",
+        "webrtc-adapter": "^7.3.0",
         "wikijs": "^6.0.1",
         "word-extractor": "^0.3.0",
         "words-to-numbers": "^1.5.1",
         "xoauth2": "^1.2.0",
         "youtube": "^0.1.0"
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/collections/CollectionTimeView.scss b/src/client/views/collections/CollectionTimeView.scss
index a5ce73a92..02ef4e2d2 100644
--- a/src/client/views/collections/CollectionTimeView.scss
+++ b/src/client/views/collections/CollectionTimeView.scss
@@ -40,7 +40,6 @@
 
     .collectionTimeView-flyout {
         width: 400px;
-        height: 300px;
         display: inline-block;
 
         .collectionTimeView-flyout-item {
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 4306a07a1..6058f4e1d 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -35,7 +35,7 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
         if (!this.props.Document._facetCollection) {
             const facetCollection = Docs.Create.TreeDocument([], { title: "facetFilters", _yMargin: 0, treeViewHideTitle: true, treeViewHideHeaderFields: true });
             facetCollection.target = this.props.Document;
-            this.props.Document.excludeFields = new List<string>(["_facetCollection", "_docFilter"]);
+            this.props.Document.excludeFields = new List<string>(["_facetCollection", "_docFilters"]);
 
             const scriptText = "setDocFilter(containingTreeView.target, heading, this.title, checked)";
             const childText = "const alias = getAlias(this); Doc.ApplyTemplateTo(containingCollection.childDetailed, alias, 'layout_detailView'); alias.dropAction='alias'; alias.removeDropProperties=new List<string>(['dropAction']);  useRightSplit(alias, shiftKey); ";
@@ -68,7 +68,7 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
             const found = DocListCast(facetCollection.data).findIndex(doc => doc.title === facetHeader);
             if (found !== -1) {
                 (facetCollection.data as List<Doc>).splice(found, 1);
-                const docFilter = Cast(this.props.Document._docFilter, listSpec("string"));
+                const docFilter = Cast(this.props.Document._docFilters, listSpec("string"));
                 if (docFilter) {
                     let index: number;
                     while ((index = docFilter.findIndex(item => item === facetHeader)) !== -1) {
@@ -250,7 +250,7 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
         trace();
         const facetCollection = Cast(this.props.Document?._facetCollection, Doc, null);
         const flyout = (
-            <div className="collectionTimeView-flyout" style={{ width: `${this._facetWidth}`, display: "block" }} onWheel={e => e.stopPropagation()}>
+            <div className="collectionTimeView-flyout" style={{ width: `${this._facetWidth}`, height: this.props.PanelHeight() - 30, display: "block" }} onWheel={e => e.stopPropagation()}>
                 {this._allFacets.map(facet => <label className="collectionTimeView-flyout-item" key={`${facet}`} onClick={e => this.facetClick(facet)}>
                     <input type="checkbox" onChange={e => { }} checked={DocListCast((this.props.Document._facetCollection as Doc)?.data).some(d => d.title === facet)} />
                     <span className="checkmark" />
@@ -332,12 +332,14 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
                 <div className={"pivotKeyEntry"}>
                     <button className="collectionTimeView-backBtn" style={{ width: 50, height: 20, background: "green" }}
                         onClick={action(() => {
-                            let pfilterIndex = NumCast(this.props.Document._pfilterIndex);
-                            if (pfilterIndex > 0) {
-                                this.props.Document._docFilter = ObjectField.MakeCopy(this.props.Document["_pfilter" + --pfilterIndex] as ObjectField);
-                                this.props.Document._pfilterIndex = pfilterIndex;
+                            let prevFilterIndex = NumCast(this.props.Document._prevFilterIndex);
+                            if (prevFilterIndex > 0) {
+                                prevFilterIndex--;
+                                this.props.Document._docFilters = ObjectField.MakeCopy(this.props.Document["_prevDocFilter" + prevFilterIndex] as ObjectField);
+                                this.props.Document._docRangeFilters = ObjectField.MakeCopy(this.props.Document["_prevDocRangeFilters" + prevFilterIndex] as ObjectField);
+                                this.props.Document._prevFilterIndex = prevFilterIndex;
                             } else {
-                                this.props.Document._docFilter = new List([]);
+                                this.props.Document._docFilters = new List([]);
                             }
                         })}>
                         back
@@ -361,11 +363,12 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
 }
 
 Scripting.addGlobal(function pivotColumnClick(pivotDoc: Doc, bounds: ViewDefBounds) {
-    let pfilterIndex = NumCast(pivotDoc._pfilterIndex);
-    pivotDoc["_pfilter" + pfilterIndex] = ObjectField.MakeCopy(pivotDoc._docFilter as ObjectField);
-    pivotDoc._pfilterIndex = ++pfilterIndex;
+    let prevFilterIndex = NumCast(pivotDoc._prevFilterIndex);
+    pivotDoc["_prevDocFilter" + prevFilterIndex] = ObjectField.MakeCopy(pivotDoc._docFilters as ObjectField);
+    pivotDoc["_prevDocRangeFilters" + prevFilterIndex] = ObjectField.MakeCopy(pivotDoc._docRangeFilters as ObjectField);
+    pivotDoc._prevFilterIndex = ++prevFilterIndex;
     runInAction(() => {
-        pivotDoc._docFilter = new List();
+        pivotDoc._docFilters = new List();
         (bounds.payload as string[]).map(filterVal =>
             Doc.setDocFilter(pivotDoc, StrCast(pivotDoc._pivotField), filterVal, "check"));
     });
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 33417c73d..ab4804cdd 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -764,7 +764,7 @@ Scripting.addGlobal(function readFacetData(layoutDoc: Doc, dataDoc: Doc, dataKey
 });
 
 Scripting.addGlobal(function determineCheckedState(layoutDoc: Doc, facetHeader: string, facetValue: string) {
-    const docFilters = Cast(layoutDoc._docFilter, listSpec("string"), []);
+    const docFilters = Cast(layoutDoc._docFilters, listSpec("string"), []);
     for (let i = 0; i < docFilters.length; i += 3) {
         const [header, value, state] = docFilters.slice(i, i + 3);
         if (header === facetHeader && value === facetValue) {
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index bdd908807..fb4d1c1ad 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -133,7 +133,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
 
     @action.bound
     addDocument(doc: Doc): boolean {
-        const targetDataDoc = Doc.GetProto(this.props.DataDoc || this.props.Document);
+        const targetDataDoc = Doc.Layout(this.props.Document);
         Doc.AddDocToList(targetDataDoc, this.props.fieldKey, doc);
         targetDataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()));
         Doc.GetProto(doc).lastOpened = new DateField;
@@ -144,7 +144,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
     removeDocument(doc: Doc): boolean {
         const docView = DocumentManager.Instance.getDocumentView(doc, this.props.ContainingCollectionView);
         docView && SelectionManager.DeselectDoc(docView);
-        const value = Cast((this.props.DataDoc || this.props.Document)[this.props.fieldKey], listSpec(Doc), []);
+        const value = Cast(Doc.Layout(this.props.Document)[this.props.fieldKey], listSpec(Doc), []);
         let index = value.reduce((p, v, i) => (v instanceof Doc && v === doc) ? i : p, -1);
         index = index !== -1 ? index : value.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, doc)) ? i : p, -1);
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 17d06f50c..8c7e841fd 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -827,7 +827,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     }
 
     @computed get filterDocs() {
-        const docFilters = Cast(this.props.Document._docFilter, listSpec("string"), []);
+        const docFilters = Cast(this.props.Document._docFilters, listSpec("string"), []);
         const docRangeFilters = Cast(this.props.Document._docRangeFilters, listSpec("string"), []);
         const clusters: { [key: string]: { [value: string]: string } } = {};
         for (let i = 0; i < docFilters.length; i += 3) {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index dfc56c642..1199ed7ee 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -264,12 +264,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 SelectionManager.DeselectAll();
                 Doc.UnBrushDoc(this.props.Document);
             } else if (this.onClickHandler && this.onClickHandler.script) {
+                SelectionManager.DeselectAll();
                 this.onClickHandler.script.run({ this: this.Document.isTemplateForField && this.props.DataDoc ? this.props.DataDoc : this.props.Document, containingCollection: this.props.ContainingCollectionDoc, shiftKey: e.shiftKey }, console.log);
             } else if (this.Document.type === DocumentType.BUTTON) {
                 ScriptBox.EditButtonScript("On Button Clicked ...", this.props.Document, "onClick", e.clientX, e.clientY);
-            } else if (this.props.Document.isButton === "Selector") {  // this should be moved to an OnClick script
-                FormattedTextBoxComment.Hide();
-                this.Document.links?.[0] instanceof Doc && (Doc.UserDoc().SelectedDocs = new List([Doc.LinkOtherAnchor(this.Document.links[0], this.props.Document)]));
             } else if (this.Document.isButton) {
                 SelectionManager.SelectDoc(this, e.ctrlKey); // don't think this should happen if a button action is actually triggered.
                 this.buttonClick(e.altKey, e.ctrlKey);
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 80553f334..3baab119f 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -820,7 +820,7 @@ export namespace Doc {
         }
     }
     export function setDocFilter(container: Doc, key: string, value: any, modifiers?: string | number) {
-        const docFilters = Cast(container._docFilter, listSpec("string"), []);
+        const docFilters = Cast(container._docFilters, listSpec("string"), []);
         for (let i = 0; i < docFilters.length; i += 3) {
             if (docFilters[i] === key && docFilters[i + 1] === value) {
                 docFilters.splice(i, 3);
@@ -831,7 +831,7 @@ export namespace Doc {
             docFilters.push(key);
             docFilters.push(value);
             docFilters.push(modifiers);
-            container._docFilter = new List<string>(docFilters);
+            container._docFilters = new List<string>(docFilters);
         }
     }
     export function readDocRangeFilter(doc: Doc, key: string) {
-- 
cgit v1.2.3-70-g09d2


From 60a008f635177acccaa9dacb4006491725c93702 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Mon, 10 Feb 2020 15:05:30 -0500
Subject: small cleanups to adding stacking views to contentfittingboxes (like
 time/pivot).  needs to have nativeWidth/Height set.

---
 src/client/documents/Documents.ts                  |  4 +++
 src/client/util/RichTextRules.ts                   | 19 ++++++------
 src/client/util/RichTextSchema.tsx                 | 18 +++++------
 .../views/collections/CollectionStackingView.tsx   | 35 +++++++++-------------
 src/client/views/collections/CollectionSubView.tsx |  5 +++-
 .../views/collections/CollectionTimeView.tsx       |  3 +-
 src/client/views/collections/CollectionView.tsx    |  6 ++--
 .../CollectionFreeFormLayoutEngines.tsx            |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 23 +++++++-------
 .../CollectionMulticolumnView.scss                 |  2 +-
 .../CollectionMultirowView.scss                    |  2 +-
 src/client/views/nodes/DocumentView.tsx            | 20 ++++++++-----
 src/new_fields/Doc.ts                              | 17 +++++++++--
 .../authentication/models/current_user_utils.ts    |  6 ++--
 14 files changed, 87 insertions(+), 75 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 6775d2302..29f253115 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -594,6 +594,10 @@ export namespace Docs {
         export function MulticolumnDocument(documents: Array<Doc>, options: DocumentOptions) {
             return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, _viewType: CollectionViewType.Multicolumn });
         }
+        export function MultirowDocument(documents: Array<Doc>, options: DocumentOptions) {
+            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, _viewType: CollectionViewType.Multirow });
+        }
+
 
         export function MasonryDocument(documents: Array<Doc>, options: DocumentOptions) {
             return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, _viewType: CollectionViewType.Masonry });
diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts
index 3b30b5b3f..1b67b4fe4 100644
--- a/src/client/util/RichTextRules.ts
+++ b/src/client/util/RichTextRules.ts
@@ -1,17 +1,16 @@
-import { textblockTypeInputRule, smartQuotes, emDash, ellipsis, InputRule } from "prosemirror-inputrules";
-import { schema } from "./RichTextSchema";
-import { wrappingInputRule } from "./prosemirrorPatches";
+import { ellipsis, emDash, InputRule, smartQuotes, textblockTypeInputRule } from "prosemirror-inputrules";
 import { NodeSelection, TextSelection } from "prosemirror-state";
-import { StrCast, Cast, NumCast } from "../../new_fields/Types";
-import { Doc, DataSym } from "../../new_fields/Doc";
-import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
-import { Docs, DocUtils } from "../documents/Documents";
+import { DataSym, Doc } from "../../new_fields/Doc";
 import { Id } from "../../new_fields/FieldSymbols";
-import { DocServer } from "../DocServer";
+import { ComputedField } from "../../new_fields/ScriptField";
+import { Cast, NumCast } from "../../new_fields/Types";
 import { returnFalse, Utils } from "../../Utils";
+import { DocServer } from "../DocServer";
+import { Docs, DocUtils } from "../documents/Documents";
+import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
+import { wrappingInputRule } from "./prosemirrorPatches";
 import RichTextMenu from "./RichTextMenu";
-import { RichTextField } from "../../new_fields/RichTextField";
-import { ComputedField } from "../../new_fields/ScriptField";
+import { schema } from "./RichTextSchema";
 
 export const inpRules = {
     rules: [
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index c07ebe2ed..043b277de 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -1,28 +1,26 @@
-import { reaction, IReactionDisposer, observable, runInAction } from "mobx";
+import { IReactionDisposer, observable, reaction, runInAction } from "mobx";
 import { baseKeymap, toggleMark } from "prosemirror-commands";
 import { redo, undo } from "prosemirror-history";
 import { keymap } from "prosemirror-keymap";
 import { DOMOutputSpecArray, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model";
 import { bulletList, listItem, orderedList } from 'prosemirror-schema-list';
-import { EditorState, NodeSelection, TextSelection, Plugin } from "prosemirror-state";
+import { EditorState, NodeSelection, Plugin, TextSelection } from "prosemirror-state";
 import { StepMap } from "prosemirror-transform";
 import { EditorView } from "prosemirror-view";
 import * as ReactDOM from 'react-dom';
-import { Doc, WidthSym, HeightSym, DataSym, Field } from "../../new_fields/Doc";
+import { Doc, Field, HeightSym, WidthSym } from "../../new_fields/Doc";
+import { Id } from "../../new_fields/FieldSymbols";
+import { ObjectField } from "../../new_fields/ObjectField";
+import { ComputedField } from "../../new_fields/ScriptField";
+import { BoolCast, NumCast, StrCast } from "../../new_fields/Types";
 import { emptyFunction, returnEmptyString, returnFalse, returnOne, Utils } from "../../Utils";
 import { DocServer } from "../DocServer";
 import { DocumentView } from "../views/nodes/DocumentView";
+import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
 import { DocumentManager } from "./DocumentManager";
 import ParagraphNodeSpec from "./ParagraphNodeSpec";
 import { Transform } from "./Transform";
 import React = require("react");
-import { BoolCast, NumCast, StrCast } from "../../new_fields/Types";
-import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
-import { ObjectField } from "../../new_fields/ObjectField";
-import { ComputedField } from "../../new_fields/ScriptField";
-import { observer } from "mobx-react";
-import { Id } from "../../new_fields/FieldSymbols";
-import { OnChangeHandler } from "react-color/lib/components/common/ColorWrap";
 
 const blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"],
     preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0];
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index d772ace23..21c34d047 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -4,25 +4,25 @@ import { CursorProperty } from "csstype";
 import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import Switch from 'rc-switch';
-import { Doc, HeightSym, WidthSym, DataSym } from "../../../new_fields/Doc";
+import { Doc, HeightSym, WidthSym } from "../../../new_fields/Doc";
 import { Id } from "../../../new_fields/FieldSymbols";
 import { List } from "../../../new_fields/List";
 import { listSpec } from "../../../new_fields/Schema";
 import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
-import { BoolCast, Cast, NumCast, StrCast, ScriptCast } from "../../../new_fields/Types";
-import { emptyFunction, Utils } from "../../../Utils";
+import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../../new_fields/Types";
+import { TraceMobx } from "../../../new_fields/util";
+import { Utils } from "../../../Utils";
 import { DragManager } from "../../util/DragManager";
 import { Transform } from "../../util/Transform";
 import { undoBatch } from "../../util/UndoManager";
+import { ContextMenu } from "../ContextMenu";
+import { ContextMenuProps } from "../ContextMenuItem";
 import { EditableView } from "../EditableView";
 import { ContentFittingDocumentView } from "../nodes/ContentFittingDocumentView";
+import { CollectionMasonryViewFieldRow } from "./CollectionMasonryViewFieldRow";
 import "./CollectionStackingView.scss";
 import { CollectionStackingViewFieldColumn } from "./CollectionStackingViewFieldColumn";
 import { CollectionSubView } from "./CollectionSubView";
-import { ContextMenu } from "../ContextMenu";
-import { ContextMenuProps } from "../ContextMenuItem";
-import { CollectionMasonryViewFieldRow } from "./CollectionMasonryViewFieldRow";
-import { TraceMobx } from "../../../new_fields/util";
 import { CollectionViewType } from "./CollectionView";
 
 @observer
@@ -55,7 +55,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         this._docXfs.length = 0;
         return docs.map((d, i) => {
             const height = () => this.getDocHeight(d);
-            const width = () => this.widthScale * Math.min(d._nativeWidth && !d.ignoreAspect && !this.props.Document.fillColumn ? d[WidthSym]() : Number.MAX_VALUE, this.columnWidth / this.numGroupColumns);
+            const width = () => Math.min(d._nativeWidth && !d.ignoreAspect && !this.props.Document.fillColumn ? d[WidthSym]() : Number.MAX_VALUE, this.columnWidth / this.numGroupColumns);
             const dref = React.createRef<HTMLDivElement>();
             const dxf = () => this.getDocTransform(d, dref.current!);
             this._docXfs.push({ dxf: dxf, width: width, height: height });
@@ -376,16 +376,9 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         }
         return sections.map(section => this.isStackingView ? this.sectionStacking(section[0], section[1]) : this.sectionMasonry(section[0], section[1]));
     }
-    @computed get contentScale() {
-        const heightExtra = this.heightPercent > 1 ? this.heightPercent : 1;
-        return Math.min(this.props.Document[WidthSym]() / this.props.PanelWidth(), heightExtra * this.props.Document[HeightSym]() / this.props.PanelHeight());
-    }
-    @computed get widthScale() {
-        return this.heightPercent < 1 ? Math.max(1, this.contentScale) : 1;
-    }
-    @computed get heightPercent() {
-        return this.props.PanelHeight() / this.layoutDoc[HeightSym]();
-    }
+
+    @computed get scaling() { return !this.props.Document._nativeWidth ? 1 : this.props.PanelHeight() / NumCast(this.props.Document._nativeHeight); }
+
     render() {
         TraceMobx();
         const editableViewProps = {
@@ -399,9 +392,9 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
                     ref={this.createRef}
                     style={{
                         overflowY: this.props.active() ? "auto" : "hidden",
-                        transform: `scale(${Math.min(1, this.heightPercent)})`,
-                        height: `${100 * Math.max(1, this.contentScale)}%`,
-                        width: `${100 * this.widthScale}%`,
+                        transform: `scale(${this.scaling}`,
+                        height: `${1 / this.scaling * 100}%`,
+                        width: `${1 / this.scaling * 100}%`,
                         transformOrigin: "top left",
                     }}
                     onScroll={action((e: React.UIEvent<HTMLDivElement>) => this._scroll = e.currentTarget.scrollTop)}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 62b9e8380..d71bad647 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -1,7 +1,7 @@
 import { action, computed, IReactionDisposer, reaction, trace } from "mobx";
 import * as rp from 'request-promise';
 import CursorField from "../../../new_fields/CursorField";
-import { Doc, DocListCast, Opt } from "../../../new_fields/Doc";
+import { Doc, DocListCast, Opt, WidthSym, HeightSym } from "../../../new_fields/Doc";
 import { Id } from "../../../new_fields/FieldSymbols";
 import { List } from "../../../new_fields/List";
 import { listSpec } from "../../../new_fields/Schema";
@@ -167,6 +167,9 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
                     return true;
                 }
                 let added = false;
+                if (this.props.Document._freezeOnDrop) {
+                    de.complete.docDragData?.droppedDocuments.forEach(drop => Doc.freezeNativeDimensions(drop, drop[WidthSym](), drop[HeightSym]()));
+                }
                 if (docDragData.dropAction || docDragData.userDropAction) {
                     added = docDragData.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d) || added, false);
                 } else if (docDragData.moveDocument) {
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 6058f4e1d..db176d0bc 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -2,7 +2,7 @@ import { faEdit } from "@fortawesome/free-solid-svg-icons";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { action, computed, observable, runInAction, trace } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, DocListCast, Field } from "../../../new_fields/Doc";
+import { Doc, DocListCast, Field, WidthSym, HeightSym } from "../../../new_fields/Doc";
 import { List } from "../../../new_fields/List";
 import { ObjectField } from "../../../new_fields/ObjectField";
 import { RichTextField } from "../../../new_fields/RichTextField";
@@ -31,6 +31,7 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
     @observable _layoutEngine = "pivot";
 
     componentDidMount() {
+        this.props.Document._freezeOnDrop = true;
         const childDetailed = this.props.Document.childDetailed; // bcz: needs to be here to make sure the childDetailed layout template has been loaded when the first item is clicked;
         if (!this.props.Document._facetCollection) {
             const facetCollection = Docs.Create.TreeDocument([], { title: "facetFilters", _yMargin: 0, treeViewHideTitle: true, treeViewHideHeaderFields: true });
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 2f27e5273..c081649cc 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -7,7 +7,7 @@ import * as React from 'react';
 import Lightbox from 'react-image-lightbox-with-rotate';
 import 'react-image-lightbox-with-rotate/style.css'; // This only needs to be imported once in your app
 import { DateField } from '../../../new_fields/DateField';
-import { Doc, DocListCast } from '../../../new_fields/Doc';
+import { Doc, DocListCast, DataSym } from '../../../new_fields/Doc';
 import { Id } from '../../../new_fields/FieldSymbols';
 import { listSpec } from '../../../new_fields/Schema';
 import { BoolCast, Cast, StrCast, NumCast } from '../../../new_fields/Types';
@@ -133,7 +133,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
 
     @action.bound
     addDocument(doc: Doc): boolean {
-        const targetDataDoc = Doc.GetProto(this.props.DataDoc || this.props.Document); // bcz: shouldn't this be Doc.Layout(this.props.Document)?  Right now, that causes problems with Buxton layout & adding things to a SLideView
+        const targetDataDoc = this.props.Document[DataSym];
         Doc.AddDocToList(targetDataDoc, this.props.fieldKey, doc);
         targetDataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()));
         Doc.GetProto(doc).lastOpened = new DateField;
@@ -144,7 +144,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
     removeDocument(doc: Doc): boolean {
         const docView = DocumentManager.Instance.getDocumentView(doc, this.props.ContainingCollectionView);
         docView && SelectionManager.DeselectDoc(docView);
-        const value = Cast(Doc.GetProto(this.props.DataDoc || this.props.Document)[this.props.fieldKey], listSpec(Doc), []);
+        const value = Cast(this.props.Document[DataSym][this.props.fieldKey], listSpec(Doc), []);
         let index = value.reduce((p, v, i) => (v instanceof Doc && v === doc) ? i : p, -1);
         index = index !== -1 ? index : value.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, doc)) ? i : p, -1);
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index baf09fe5b..da4dc0270 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -186,7 +186,7 @@ export function computePivotLayout(
 
     const maxColHeight = pivotAxisWidth * expander * Math.ceil(maxInColumn / numCols);
     const dividers = sortedPivotKeys.map((key, i) =>
-        ({ type: "div", color: "lightGray", x: i * pivotAxisWidth * (numCols * expander + gap), y: -maxColHeight + pivotAxisWidth, width: pivotAxisWidth * numCols * expander, height: maxColHeight, payload: pivotColumnGroups.get(key)!.filters }));
+        ({ type: "div", color: "lightGray", x: i * pivotAxisWidth * (numCols * expander + gap) - pivotAxisWidth * (expander - 1) / 2, y: -maxColHeight + pivotAxisWidth, width: pivotAxisWidth * numCols * expander, height: maxColHeight, payload: pivotColumnGroups.get(key)!.filters }));
     groupNames.push(...dividers);
     return normalizeResults(panelDim, max_text, childPairs, docMap, poolData, viewDefsToJSX, groupNames, 0, [], childDocs.filter(c => !filterDocs.includes(c)));
 }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index c78a2a2cf..07a5a2c7b 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,20 +1,22 @@
 import { library } from "@fortawesome/fontawesome-svg-core";
 import { faEye } from "@fortawesome/free-regular-svg-icons";
 import { faBraille, faChalkboard, faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faFileUpload, faPaintBrush, faTable, faUpload } from "@fortawesome/free-solid-svg-icons";
-import { action, computed, observable, ObservableMap, reaction, runInAction, IReactionDisposer, trace } from "mobx";
+import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, DocListCast, HeightSym, Opt, WidthSym, DocListCastAsync, Field } from "../../../../new_fields/Doc";
+import { computedFn } from "mobx-utils";
+import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../../new_fields/Doc";
 import { documentSchema, positionSchema } from "../../../../new_fields/documentSchemas";
 import { Id } from "../../../../new_fields/FieldSymbols";
-import { InkTool, InkField, InkData } from "../../../../new_fields/InkField";
-import { createSchema, makeInterface, listSpec } from "../../../../new_fields/Schema";
+import { InkTool } from "../../../../new_fields/InkField";
+import { createSchema, listSpec, makeInterface } from "../../../../new_fields/Schema";
 import { ScriptField } from "../../../../new_fields/ScriptField";
-import { BoolCast, Cast, DateCast, NumCast, StrCast, ScriptCast } from "../../../../new_fields/Types";
+import { Cast, NumCast, ScriptCast, StrCast } from "../../../../new_fields/Types";
+import { TraceMobx } from "../../../../new_fields/util";
+import { GestureUtils } from "../../../../pen-gestures/GestureUtils";
 import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils";
-import { aggregateBounds, emptyFunction, intersectRect, returnOne, Utils } from "../../../../Utils";
+import { aggregateBounds, intersectRect, returnOne, Utils } from "../../../../Utils";
 import { DocServer } from "../../../DocServer";
-import { Docs, DocUtils } from "../../../documents/Documents";
-import { DocumentType } from "../../../documents/DocumentTypes";
+import { Docs } from "../../../documents/Documents";
 import { DocumentManager } from "../../../util/DocumentManager";
 import { DragManager } from "../../../util/DragManager";
 import { HistoryUtil } from "../../../util/History";
@@ -32,15 +34,12 @@ import { FormattedTextBox } from "../../nodes/FormattedTextBox";
 import { pageSchema } from "../../nodes/ImageBox";
 import PDFMenu from "../../pdf/PDFMenu";
 import { CollectionSubView } from "../CollectionSubView";
-import { computePivotLayout, ViewDefResult, computeTimelineLayout, PoolData, ViewDefBounds } from "./CollectionFreeFormLayoutEngines";
+import { computePivotLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult } from "./CollectionFreeFormLayoutEngines";
 import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors";
 import "./CollectionFreeFormView.scss";
 import MarqueeOptionsMenu from "./MarqueeOptionsMenu";
 import { MarqueeView } from "./MarqueeView";
 import React = require("react");
-import { computedFn } from "mobx-utils";
-import { TraceMobx } from "../../../../new_fields/util";
-import { GestureUtils } from "../../../../pen-gestures/GestureUtils";
 
 library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload);
 
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss
index 0c74b8ddb..821c8d804 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss
@@ -1,8 +1,8 @@
 .collectionMulticolumnView_contents {
     display: flex;
+    overflow: hidden;
     width: 100%;
     height: 100%;
-    overflow: hidden;
 
     .document-wrapper {
         display: flex;
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss
index 64f607680..a7e2c5707 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss
@@ -1,8 +1,8 @@
 .collectionMultirowView_contents {
     display: flex;
+    overflow: hidden;
     width: 100%;
     height: 100%;
-    overflow: hidden;
     flex-direction: column;
 
     .document-wrapper {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 1199ed7ee..080b01df2 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -610,13 +610,17 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     }
 
     @undoBatch
-    @action
-    freezeNativeDimensions = (): void => {
-        this.layoutDoc._autoHeight = false;
-        this.layoutDoc.ignoreAspect = !this.layoutDoc.ignoreAspect;
-        if (!this.layoutDoc.ignoreAspect && !this.layoutDoc._nativeWidth) {
-            this.layoutDoc._nativeWidth = this.props.PanelWidth();
-            this.layoutDoc._nativeHeight = this.props.PanelHeight();
+    public static unfreezeNativeDimensions = action((layoutDoc: Doc): void => {
+        layoutDoc._nativeWidth = undefined;
+        layoutDoc._nativeHeight = undefined;
+    });
+
+    toggleNativeDimensions = () => {
+        if (this.Document._nativeWidth || this.Document._nativeHeight) {
+            DocumentView.unfreezeNativeDimensions(this.layoutDoc);
+        }
+        else {
+            Doc.freezeNativeDimensions(this.layoutDoc, this.props.PanelWidth(), this.props.PanelHeight());
         }
     }
 
@@ -730,7 +734,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
         layoutItems.push({ description: `${this.Document._chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.Document._chromeStatus = (this.Document._chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" });
         layoutItems.push({ description: `${this.Document._autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" });
-        layoutItems.push({ description: this.Document.ignoreAspect || !this.Document._nativeWidth || !this.Document._nativeHeight ? "Freeze" : "Unfreeze", event: this.freezeNativeDimensions, icon: "snowflake" });
+        layoutItems.push({ description: !this.Document._nativeWidth || !this.Document._nativeHeight ? "Freeze" : "Unfreeze", event: this.toggleNativeDimensions, icon: "snowflake" });
         layoutItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" });
         layoutItems.push({ description: this.Document.lockedTransform ? "Unlock Transform" : "Lock Transform", event: this.toggleLockTransform, icon: BoolCast(this.Document.lockedTransform) ? "unlock" : "lock" });
         layoutItems.push({ description: "Center View", event: () => this.props.focus(this.props.Document, false), icon: "crosshairs" });
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 3baab119f..a28c6f58f 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -14,7 +14,7 @@ import { ComputedField, ScriptField } from "./ScriptField";
 import { BoolCast, Cast, FieldValue, NumCast, StrCast, ToConstructor } from "./Types";
 import { deleteProperty, getField, getter, makeEditable, makeReadOnly, setter, updateFunction } from "./util";
 import { intersectRect } from "../Utils";
-import { UndoManager } from "../client/util/UndoManager";
+import { UndoManager, undoBatch } from "../client/util/UndoManager";
 import { computedFn } from "mobx-utils";
 import { RichTextField } from "./RichTextField";
 import { Script } from "vm";
@@ -178,7 +178,7 @@ export class Doc extends RefField {
     private [SelfProxy]: any;
     public [WidthSym] = () => NumCast(this[SelfProxy]._width);
     public [HeightSym] = () => NumCast(this[SelfProxy]._height);
-    public get [DataSym]() { return Cast(this[SelfProxy].resolvedDataDoc, Doc, null) || this[SelfProxy]; }
+    public get [DataSym]() { return Cast(Doc.Layout(this[SelfProxy]).resolvedDataDoc, Doc, null) || this[SelfProxy]; }
     public get [LayoutSym]() { return this[SelfProxy].__LAYOUT__; }
     @computed get __LAYOUT__() {
         const templateLayoutDoc = Cast(Doc.LayoutField(this[SelfProxy]), Doc, null);
@@ -842,6 +842,17 @@ export namespace Doc {
             }
         }
     }
+
+    @undoBatch
+    @action
+    export function freezeNativeDimensions(layoutDoc: Doc, width: number, height: number): void {
+        layoutDoc._autoHeight = false;
+        layoutDoc.ignoreAspect = false;
+        if (!layoutDoc.ignoreAspect && !layoutDoc._nativeWidth) {
+            layoutDoc._nativeWidth = NumCast(layoutDoc._width, width);
+            layoutDoc._nativeHeight = NumCast(layoutDoc._height, height);
+        }
+    }
 }
 
 Scripting.addGlobal(function renameAlias(doc: any, n: any) { return StrCast(Doc.GetProto(doc).title).replace(/\([0-9]*\)/, "") + `(${n})`; });
@@ -867,4 +878,4 @@ Scripting.addGlobal(function selectedDocs(container: Doc, excludeCollections: bo
     return docs.length ? new List(docs) : prevValue;
 });
 Scripting.addGlobal(function setDocFilter(container: Doc, key: string, value: any, modifiers?: string) { Doc.setDocFilter(container, key, value, modifiers); });
-Scripting.addGlobal(function setDocFilterRange(container: Doc, key: string, range: number) { Doc.setDocFilterRange(container, key, range); });
\ No newline at end of file
+Scripting.addGlobal(function setDocFilterRange(container: Doc, key: string, range: number[]) { Doc.setDocFilterRange(container, key, range); });
\ 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 efee42f63..ea7a4999c 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -234,12 +234,12 @@ export class CurrentUserUtils {
 
     /// sets up the default list of buttons to be shown in the expanding button menu at the bottom of the Dash window
     static setupExpandingButtons(doc: Doc) {
-        const slideTemplate = Docs.Create.StackingDocument(
+        const slideTemplate = Docs.Create.MultirowDocument(
             [
-                Docs.Create.MulticolumnDocument([], { title: "images", _height: 200, _xMargin: 10, _yMargin: 10 }),
+                Docs.Create.MulticolumnDocument([], { title: "images", _height: 200 }),
                 Docs.Create.TextDocument("", { title: "contents", _height: 100 })
             ],
-            { _width: 400, _height: 300, title: "slide", _chromeStatus: "disabled", backgroundColor: "lightGray", _autoHeight: true });
+            { _width: 400, _height: 300, title: "slideView", _chromeStatus: "disabled", _xMargin: 3, _yMargin: 3, backgroundColor: "lightGray", _autoHeight: true });
         slideTemplate.isTemplateDoc = makeTemplate(slideTemplate);
 
         const iconDoc = Docs.Create.TextDocument("", { title: "icon", _width: 150, _height: 30, isTemplateDoc: true, onClick: ScriptField.MakeScript("setNativeView(this)") });
-- 
cgit v1.2.3-70-g09d2


From dc6453e27375a1b3d614a74b7fd1d83695f130d7 Mon Sep 17 00:00:00 2001
From: Stanley Yip <stanley_yip@brown.edu>
Date: Mon, 10 Feb 2020 20:02:43 -0500
Subject: some final stuff

---
 src/client/views/GestureOverlay.scss               | 15 ++++++++++++++
 src/client/views/GestureOverlay.tsx                | 23 +++++++++++++---------
 src/client/views/Palette.scss                      | 13 ++++++++++--
 src/client/views/Palette.tsx                       |  1 +
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  9 ++++-----
 .../collections/collectionFreeForm/MarqueeView.tsx | 14 ++++++++++---
 src/pen-gestures/ndollar.ts                        |  2 +-
 src/server/Search.ts                               |  2 +-
 8 files changed, 58 insertions(+), 21 deletions(-)

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

diff --git a/src/client/views/GestureOverlay.scss b/src/client/views/GestureOverlay.scss
index 7474ca839..107077792 100644
--- a/src/client/views/GestureOverlay.scss
+++ b/src/client/views/GestureOverlay.scss
@@ -5,6 +5,21 @@
     top: 0;
     left: 0;
     touch-action: none;
+
+    .pointerBubbles {
+        width: 100%;
+        height: 100%;
+        position: absolute;
+        pointer-events: none;
+
+        .bubble {
+            position: absolute;
+            width: 15px;
+            height: 15px;
+            border-radius: 100%;
+            border: .5px solid grey;
+        }
+    }
 }
 
 .clipboardDoc-cont {
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 4e9b87eea..5c4dd0c0a 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -32,8 +32,8 @@ import TouchScrollableMenu, { TouchScrollableMenuItem } from "./TouchScrollableM
 export default class GestureOverlay extends Touchable {
     static Instance: GestureOverlay;
 
-    @observable public Color: string = "rgb(244, 67, 54)";
-    @observable public Width: number = 5;
+    @observable public Color: string = "rgb(0, 0, 0)";
+    @observable public Width: number = 2;
     @observable public SavedColor?: string;
     @observable public SavedWidth?: number;
     @observable public Tool: ToolglassTools = ToolglassTools.None;
@@ -394,9 +394,9 @@ export default class GestureOverlay extends Touchable {
             if (pt && pt.identifier === this.thumbIdentifier && this._thumbY) {
                 if (this._thumbX && this._thumbY) {
                     const yOverX = Math.abs(pt.clientX - this._thumbX) < Math.abs(pt.clientY - this._thumbY);
-                    if ((yOverX && this._inkToTextDoc) || this._selectedIndex > 0) {
+                    if ((yOverX && this._inkToTextDoc) || this._selectedIndex > -1) {
                         if (Math.abs(pt.clientY - this._thumbY) > (10 * window.devicePixelRatio)) {
-                            this._selectedIndex = Math.min(Math.max(-1, -Math.ceil((pt.clientY - this._thumbY) / 20)), this._possibilities.length - 1);
+                            this._selectedIndex = Math.min(Math.max(-1, (-Math.ceil((pt.clientY - this._thumbY) / (10 * window.devicePixelRatio)) - 1)), this._possibilities.length - 1);
                         }
                     }
                     else if (this._thumbDoc) {
@@ -436,7 +436,7 @@ export default class GestureOverlay extends Touchable {
 
             let scriptWorked = false;
             if (NumCast(this._inkToTextDoc?.selectedIndex) > -1) {
-                const selectedButton = this._possibilities[NumCast(this._inkToTextDoc?.selectedIndex)];
+                const selectedButton = this._possibilities[this._selectedIndex];
                 if (selectedButton) {
                     selectedButton.props.onClick();
                     scriptWorked = true;
@@ -507,8 +507,10 @@ export default class GestureOverlay extends Touchable {
                 this._d1 = doc;
             }
             else if (this._d1 !== doc && !LinkManager.Instance.doesLinkExist(this._d1, doc)) {
-                DocUtils.MakeLink({ doc: this._d1 }, { doc: doc });
-                actionPerformed = true;
+                if (this._d1.type !== "ink" && doc.type !== "ink") {
+                    DocUtils.MakeLink({ doc: this._d1 }, { doc: doc });
+                    actionPerformed = true;
+                }
             }
         };
         const ge = new CustomEvent<GestureUtils.GestureEvent>("dashOnGesture",
@@ -716,6 +718,9 @@ export default class GestureOverlay extends Touchable {
                 }}>
                 </div>
                 <TouchScrollableMenu options={this._possibilities} bounds={this.svgBounds} selectedIndex={this._selectedIndex} x={this._menuX} y={this._menuY} />
+                {/* <div className="pointerBubbles">
+                {this._pointers.map(p => <div className="bubble" style={{ translate: `transform(${p.clientX}px, ${p.clientY}px)` }}></div>)}
+                </div> */}
             </div>);
     }
 }
@@ -743,8 +748,8 @@ Scripting.addGlobal(function setPen(width: any, color: any) {
 });
 Scripting.addGlobal(function resetPen() {
     runInAction(() => {
-        GestureOverlay.Instance.Color = GestureOverlay.Instance.SavedColor ?? "rgb(244, 67, 54)";
-        GestureOverlay.Instance.Width = GestureOverlay.Instance.SavedWidth ?? 5;
+        GestureOverlay.Instance.Color = GestureOverlay.Instance.SavedColor ?? "rgb(0, 0, 0)";
+        GestureOverlay.Instance.Width = GestureOverlay.Instance.SavedWidth ?? 2;
     });
 });
 Scripting.addGlobal(function createText(text: any, x: any, y: any) {
diff --git a/src/client/views/Palette.scss b/src/client/views/Palette.scss
index 4513de2b0..0ec879288 100644
--- a/src/client/views/Palette.scss
+++ b/src/client/views/Palette.scss
@@ -1,13 +1,14 @@
 .palette-container {
     .palette-thumb {
         touch-action: pan-x;
-        overflow: scroll;
         position: absolute;
-        width: 90px;
         height: 70px;
+        overflow: hidden;
 
         .palette-thumbContent {
             transition: transform .3s;
+            width: max-content;
+            overflow: hidden;
 
             .collectionView {
                 overflow: visible;
@@ -17,5 +18,13 @@
                 }
             }
         }
+
+        .palette-cover {
+            width: 50px;
+            height: 50px;
+            position: absolute;
+            bottom: 0;
+            border: 1px solid black;
+        }
     }
 }
\ No newline at end of file
diff --git a/src/client/views/Palette.tsx b/src/client/views/Palette.tsx
index 10aac96a0..e04f814d1 100644
--- a/src/client/views/Palette.tsx
+++ b/src/client/views/Palette.tsx
@@ -72,6 +72,7 @@ export default class Palette extends React.Component<PaletteProps> {
                             zoomToScale={emptyFunction}
                             getScale={returnOne}>
                         </DocumentView>
+                        <div className="palette-cover" style={{ transform: `translate(${Math.max(0, this._selectedIndex) * 50.75 + 23}px, 0px)` }}></div>
                     </div>
                 </div>
             </div>
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 53fe2b18c..4b1bb09a9 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -457,7 +457,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                     const inkFields = inks.map(i => Cast(i.data, InkField));
                     CognitiveServices.Inking.Appliers.InterpretStrokes(inkFields.filter(i => i instanceof InkField).map(i => i!.inkData)).then((results) => {
                         const wordResults = results.filter((r: any) => r.category === "inkWord");
-                        console.log(wordResults);
                         for (const word of wordResults) {
                             const indices: number[] = word.strokeIds;
                             indices.forEach(i => {
@@ -467,13 +466,13 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                                 const uniqueColors: string[] = [];
                                 Array.from(this._wordPalette.values()).forEach(c => uniqueColors.indexOf(c) === -1 && uniqueColors.push(c));
                                 inks[i].alternativeColors = new List<string>(uniqueColors);
-                                if (this._wordPalette.has(word.recognizedText)) {
-                                    inks[i].color = this._wordPalette.get(word.recognizedText);
+                                if (this._wordPalette.has(word.recognizedText.toLowerCase())) {
+                                    inks[i].color = this._wordPalette.get(word.recognizedText.toLowerCase());
                                 }
                                 else {
                                     for (const alt of word.alternates) {
-                                        if (this._wordPalette.has(alt.recognizedString)) {
-                                            inks[i].color = this._wordPalette.get(alt.recognizedString);
+                                        if (this._wordPalette.has(alt.recognizedString.toLowerCase())) {
+                                            inks[i].color = this._wordPalette.get(alt.recognizedString.toLowerCase());
                                             break;
                                         }
                                     }
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 19a71012a..8591144c0 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -375,7 +375,6 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
             });
             const colors = setDocs.map(sd => FieldValue(sd.color) as string);
             const wordToColor = new Map<string, string>();
-            console.log(sets);
             sets.forEach((st: string, i: number) => {
                 const words = st.split(",");
                 words.forEach(word => {
@@ -386,11 +385,20 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
             CognitiveServices.Inking.Appliers.InterpretStrokes(inkFields.filter(i => i instanceof InkField).map(i => i!.inkData)).then((results) => {
                 const wordResults = results.filter((r: any) => r.category === "inkWord");
                 console.log(wordResults);
+                console.log(results);
                 for (const word of wordResults) {
                     const indices: number[] = word.strokeIds;
                     indices.forEach(i => {
-                        if (wordToColor.has(word.recognizedText)) {
-                            inks[i].color = wordToColor.get(word.recognizedText);
+                        if (wordToColor.has(word.recognizedText.toLowerCase())) {
+                            inks[i].color = wordToColor.get(word.recognizedText.toLowerCase());
+                        }
+                        else {
+                            for (const alt of word.alternates) {
+                                if (wordToColor.has(alt.recognizedString.toLowerCase())) {
+                                    inks[i].color = wordToColor.get(alt.recognizedString.toLowerCase());
+                                    break;
+                                }
+                            }
                         }
                     })
                 }
diff --git a/src/pen-gestures/ndollar.ts b/src/pen-gestures/ndollar.ts
index 643d58591..365896197 100644
--- a/src/pen-gestures/ndollar.ts
+++ b/src/pen-gestures/ndollar.ts
@@ -185,7 +185,7 @@ export class NDollarRecognizer {
         this.Multistrokes[3] = new Multistroke(GestureUtils.Gestures.EndBracket, useBoundedRotationInvariance, new Array(
             // new Array(new Point(150, 21), new Point(149, 150), new Point(26, 152))
             // new Array(new Point(150, 150), new Point(150, 0), new Point(150, 150), new Point(0, 150))
-            new Array(new Point(10, 100), new Point(50, 12), new Point(100, 103))
+            new Array(new Point(10, 100), new Point(50, 100), new Point(100, 12), new Point(150, 103), new Point(190, 100))
         ));
 
         //
diff --git a/src/server/Search.ts b/src/server/Search.ts
index 43aa7e49c..21064e520 100644
--- a/src/server/Search.ts
+++ b/src/server/Search.ts
@@ -13,7 +13,7 @@ export namespace Search {
             });
             return res;
         } catch (e) {
-            console.warn("Search error: " + e + document);
+            // console.warn("Search error: " + e + document);
         }
     }
 
-- 
cgit v1.2.3-70-g09d2


From 4b09b59dc31b089eada895fa0eabac95fd3931bd Mon Sep 17 00:00:00 2001
From: kimdahey <claire_kim1@brown.edu>
Date: Mon, 10 Feb 2020 22:48:54 -0500
Subject: finished

---
 .../collectionFreeForm/CollectionFreeFormView.scss |  7 +++
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 72 ++++++++++++++++------
 2 files changed, 60 insertions(+), 19 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index 58fb81453..b70697e9a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -34,6 +34,7 @@
         height: 100%;
         display: flex;
         align-items: center;
+
         .collectionfreeformview-placeholderSpan {
             font-size: 32;
             display: flex;
@@ -97,4 +98,10 @@
 
 #prevCursor {
     animation: blink 1s infinite;
+}
+
+.pullpane-indicator {
+    z-index: 999;
+    background-color: rgba($color: #000000, $alpha: .4);
+    position: absolute;
 }
\ 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 30ef9eb77..85349b6e3 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -81,7 +81,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     private _hitCluster = false;
     private _layoutComputeReaction: IReactionDisposer | undefined;
     private _layoutPoolData = new ObservableMap<string, any>();
-    private _pullCoords: number[] = [0, 0];
+    @observable private _pullCoords: number[] = [0, 0];
+    @observable private _pullDirection: string = "";
 
     public get displayName() { return "CollectionFreeFormView(" + this.props.Document.title?.toString() + ")"; } // this makes mobx trace() statements more descriptive
     @observable.shallow _layoutElements: ViewDefResult[] = []; // shallow because some layout items (eg pivot labels) are just generated 'divs' and can't be frozen as observables
@@ -627,8 +628,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                         const centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2;
                         const centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2;
 
-                        if (!this._pullCoords[0] && !this._pullCoords[1]) { // if we are not bezel movement
+                        if (!this._pullDirection) { // if we are not bezel movement
                             this.pan({ clientX: centerX, clientY: centerY });
+                        } else {
+                            this._pullCoords = [centerX, centerY];
+                            console.log(this.layoutDoc._width);
                         }
 
                         this._lastX = centerX;
@@ -657,10 +661,21 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 this._lastY = centerY;
 
                 // determine if we are using a bezel movement
-                if ((this.props.PanelWidth() - this._lastX) < 100 || this._lastX < 100 || (this.props.PanelHeight() - this._lastY < 100) || this._lastY < 120) { // to account for header
+                if ((this.props.PanelWidth() - this._lastX) < 100) {
                     this._pullCoords = [this._lastX, this._lastY];
+                    this._pullDirection = "right";
+                } else if (this._lastX < 100) {
+                    this._pullCoords = [this._lastX, this._lastY];
+                    this._pullDirection = "left";
+                } else if (this.props.PanelHeight() - this._lastY < 100) {
+                    this._pullCoords = [this._lastX, this._lastY];
+                    this._pullDirection = "bottom";
+                } else if (this._lastY < 120) { // to account for header
+                    this._pullCoords = [this._lastX, this._lastY];
+                    this._pullDirection = "top";
                 }
 
+
                 this.removeMoveListeners();
                 this.addMoveListeners();
                 this.removeEndListeners();
@@ -672,27 +687,29 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
     cleanUpInteractions = () => {
 
-        if (this._pullCoords[0] !== 0 && this._pullCoords[1] !== 0) { // if bezel mvmt was activated
-            const xDiff = this._pullCoords[0] - this._lastX;
-            const yDiff = this._pullCoords[1] - this._lastY;
+        switch (this._pullDirection) {
 
-            console.log('went thru', this._pullCoords);
-            if ((this._lastX < this._pullCoords[0]) && (yDiff < xDiff)) { // pull from right
+            case "left":
+                console.log('pulled from left');
+                CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([], { title: "New Collection" }), "left", undefined);
+                break;
+            case "right":
                 console.log('pulled from right');
-                // CollectionDockingView.AddRightSplit(this.Document, undefined);
-                CollectionDockingView.AddSplit(this.Document, "right", undefined);
-            } else if ((this._lastY > this._pullCoords[1]) && (yDiff < xDiff)) { // pull from top
+                CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([], { title: "New Collection" }), "right", undefined);
+                break;
+            case "top":
                 console.log('pulled from top');
-                CollectionDockingView.AddSplit(this.Document, "top", undefined);
-            } else if ((this._lastY < this._pullCoords[1]) && (yDiff > xDiff)) { // pull from bottom
+                CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([], { title: "New Collection" }), "top", undefined);
+                break;
+            case "bottom":
                 console.log('pulled from bottom');
-                CollectionDockingView.AddSplit(this.Document, "bottom", undefined);
-            } else if ((this._lastX > this._pullCoords[0]) && (yDiff > xDiff)) { // pull from left
-                console.log('pulled from left');
-                CollectionDockingView.AddSplit(this.Document, "left", undefined);
-            }
+                CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([], { title: "New Collection" }), "bottom", undefined);
+                break;
+            default:
+                break;
         }
 
+        this._pullDirection = "";
         this._pullCoords = [0, 0];
 
         document.removeEventListener("pointermove", this.onPointerMove);
@@ -1125,7 +1142,24 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             {!this.Document._LODdisable && !this.props.active() && !this.props.isAnnotationOverlay && !this.props.annotationsKey && this.props.renderDepth > 0 ? // && this.props.CollectionView && lodarea < NumCast(this.Document.LODarea, 100000) ?
                 this.placeholder : this.marqueeView}
             <CollectionFreeFormOverlayView elements={this.elementFunc} />
-        </div>;
+
+            <div className={"pullpane-indicator"}
+                style={{
+                    display: this._pullDirection ? "block" : "none",
+                    // width: this._pullDirection === "left" || this._pullDirection === "right" ? Math.abs(this.props.PanelWidth() - this._pullCoords[0]) : this.props.PanelWidth(),
+                    // height: this._pullDirection === "top" || this._pullDirection === "bottom" ? Math.abs(this.props.PanelHeight() - this._pullCoords[1]) : this.props.PanelHeight(),
+                    // top: this._pullDirection === "bottom" ? this._pullCoords[0] : 0,
+                    // left: this._pullDirection === "right" ? this._pullCoords[1] : 0
+                    width: this._pullDirection === "left" ? this._pullCoords[0] : this._pullDirection === "right" ? this.props.PanelWidth() - this._pullCoords[0] : this.props.PanelWidth(),
+                    height: this._pullDirection === "top" ? this._pullCoords[1] : this._pullDirection === "bottom" ? this.props.PanelHeight() - this._pullCoords[1] : this.props.PanelHeight(),
+                    left: this._pullDirection === "right" ? undefined : 0,
+                    right: this._pullDirection === "right" ? 0 : undefined,
+                    top: this._pullDirection === "bottom" ? undefined : 0,
+                    bottom: this._pullDirection === "bottom" ? 0 : undefined
+                }}>
+            </div>
+
+        </div >;
     }
 }
 
-- 
cgit v1.2.3-70-g09d2


From 5a32a80e6f3026009522fdc3de614e85930efff2 Mon Sep 17 00:00:00 2001
From: kimdahey <claire_kim1@brown.edu>
Date: Mon, 10 Feb 2020 23:08:17 -0500
Subject: made slight qol improvements

---
 .../collections/collectionFreeForm/CollectionFreeFormView.tsx      | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 1f2cdaea4..8f555f315 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -46,6 +46,7 @@ import { RichTextField } from "../../../../new_fields/RichTextField";
 import { List } from "../../../../new_fields/List";
 import { DocumentViewProps } from "../../nodes/DocumentView";
 import { CollectionDockingView } from "../CollectionDockingView";
+import { MainView } from "../../MainView";
 
 library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload);
 
@@ -631,7 +632,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                             this.pan({ clientX: centerX, clientY: centerY });
                         } else {
                             this._pullCoords = [centerX, centerY];
-                            console.log(this.layoutDoc._width);
+                            console.log(MainView.Instance.flyoutWidth);
                         }
 
                         this._lastX = centerX;
@@ -1149,8 +1150,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                     // height: this._pullDirection === "top" || this._pullDirection === "bottom" ? Math.abs(this.props.PanelHeight() - this._pullCoords[1]) : this.props.PanelHeight(),
                     // top: this._pullDirection === "bottom" ? this._pullCoords[0] : 0,
                     // left: this._pullDirection === "right" ? this._pullCoords[1] : 0
-                    width: this._pullDirection === "left" ? this._pullCoords[0] : this._pullDirection === "right" ? this.props.PanelWidth() - this._pullCoords[0] : this.props.PanelWidth(),
-                    height: this._pullDirection === "top" ? this._pullCoords[1] : this._pullDirection === "bottom" ? this.props.PanelHeight() - this._pullCoords[1] : this.props.PanelHeight(),
+                    width: this._pullDirection === "left" ? this._pullCoords[0] : this._pullDirection === "right" ? MainView.Instance.getPWidth() - this._pullCoords[0] + MainView.Instance.flyoutWidth : MainView.Instance.getPWidth(),
+                    height: this._pullDirection === "top" ? this._pullCoords[1] : this._pullDirection === "bottom" ? MainView.Instance.getPHeight() - this._pullCoords[1] : MainView.Instance.getPHeight(),
                     left: this._pullDirection === "right" ? undefined : 0,
                     right: this._pullDirection === "right" ? 0 : undefined,
                     top: this._pullDirection === "bottom" ? undefined : 0,
-- 
cgit v1.2.3-70-g09d2


From f689b3ae048941f23d67750ba488ca90bba578ee Mon Sep 17 00:00:00 2001
From: Stanley Yip <stanley_yip@brown.edu>
Date: Tue, 11 Feb 2020 18:04:05 -0500
Subject: some more fixes

---
 src/client/util/InteractionUtils.tsx                   |  1 +
 .../collectionFreeForm/CollectionFreeFormView.tsx      | 18 ++++++++++++++++--
 .../collections/collectionFreeForm/MarqueeView.tsx     |  1 +
 src/pen-gestures/ndollar.ts                            |  2 +-
 4 files changed, 19 insertions(+), 3 deletions(-)

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

diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx
index 8fc5e8098..184e37ba5 100644
--- a/src/client/util/InteractionUtils.tsx
+++ b/src/client/util/InteractionUtils.tsx
@@ -91,6 +91,7 @@ export namespace InteractionUtils {
     }
 
     export function IsType(e: PointerEvent | React.PointerEvent, type: string): boolean {
+        console.log(e.button);
         switch (type) {
             // pen and eraser are both pointer type 'pen', but pen is button 0 and eraser is button 5. -syip2
             case PENTYPE:
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 4b1bb09a9..bb34f3d27 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -454,8 +454,22 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                         }
                         return false;
                     });
-                    const inkFields = inks.map(i => Cast(i.data, InkField));
-                    CognitiveServices.Inking.Appliers.InterpretStrokes(inkFields.filter(i => i instanceof InkField).map(i => i!.inkData)).then((results) => {
+                    // const inkFields = inks.map(i => Cast(i.data, InkField));
+                    const strokes: InkData[] = [];
+                    inks.forEach(i => {
+                        const d = Cast(i.data, InkField);
+                        const x = NumCast(i.x);
+                        const y = NumCast(i.y);
+                        const left = Math.min(...d?.inkData.map(pd => pd.X) ?? [0]);
+                        const top = Math.min(...d?.inkData.map(pd => pd.Y) ?? [0]);
+                        if (d) {
+                            strokes.push(d.inkData.map(pd => ({ X: pd.X + x - left, Y: pd.Y + y - top })));
+                        }
+                    });
+                    console.log(strokes)
+
+                    CognitiveServices.Inking.Appliers.InterpretStrokes(strokes).then((results) => {
+                        console.log(results);
                         const wordResults = results.filter((r: any) => r.category === "inkWord");
                         for (const word of wordResults) {
                             const indices: number[] = word.strokeIds;
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 8591144c0..e82ca6bf2 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -21,6 +21,7 @@ import MarqueeOptionsMenu from "./MarqueeOptionsMenu";
 import { SubCollectionViewProps } from "../CollectionSubView";
 import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
 import { RichTextField } from "../../../../new_fields/RichTextField";
+import { InteractionUtils } from "../../../util/InteractionUtils";
 
 interface MarqueeViewProps {
     getContainerTransform: () => Transform;
diff --git a/src/pen-gestures/ndollar.ts b/src/pen-gestures/ndollar.ts
index 365896197..bb92f62e1 100644
--- a/src/pen-gestures/ndollar.ts
+++ b/src/pen-gestures/ndollar.ts
@@ -185,7 +185,7 @@ export class NDollarRecognizer {
         this.Multistrokes[3] = new Multistroke(GestureUtils.Gestures.EndBracket, useBoundedRotationInvariance, new Array(
             // new Array(new Point(150, 21), new Point(149, 150), new Point(26, 152))
             // new Array(new Point(150, 150), new Point(150, 0), new Point(150, 150), new Point(0, 150))
-            new Array(new Point(10, 100), new Point(50, 100), new Point(100, 12), new Point(150, 103), new Point(190, 100))
+            new Array(new Point(10, 100), new Point(100, 100), new Point(150, 12), new Point(200, 103), new Point(300, 100))
         ));
 
         //
-- 
cgit v1.2.3-70-g09d2


From 2131c829b8632a87b8ba52bc311d3b0346eed3f7 Mon Sep 17 00:00:00 2001
From: kimdahey <claire_kim1@brown.edu>
Date: Tue, 11 Feb 2020 21:36:20 -0500
Subject: did the maths :')

---
 src/client/views/collections/CollectionSubView.tsx |  2 +
 .../collectionFreeForm/CollectionFreeFormView.scss |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 74 +++++++++++++++-------
 3 files changed, 53 insertions(+), 25 deletions(-)

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

diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 8679c8bd1..12906ee83 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -51,11 +51,13 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
         private gestureDisposer?: GestureUtils.GestureEventDisposer;
         protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
         private _childLayoutDisposer?: IReactionDisposer;
+        protected _mainCont?: HTMLDivElement;
         protected createDashEventsTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
             this.dropDisposer?.();
             this.gestureDisposer?.();
             this.multiTouchDisposer?.();
             if (ele) {
+                this._mainCont = ele;
                 this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
                 this.gestureDisposer = GestureUtils.MakeGestureTarget(ele, this.onGesture.bind(this));
                 this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(ele, this.onTouchStart.bind(this));
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index b70697e9a..2213b7882 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -101,7 +101,7 @@
 }
 
 .pullpane-indicator {
-    z-index: 999;
+    z-index: 99999;
     background-color: rgba($color: #000000, $alpha: .4);
     position: absolute;
 }
\ 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 8f555f315..bf517e7c2 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,6 +1,6 @@
 import { library } from "@fortawesome/fontawesome-svg-core";
 import { faEye } from "@fortawesome/free-regular-svg-icons";
-import { faBraille, faChalkboard, faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faFileUpload, faPaintBrush, faTable, faUpload } from "@fortawesome/free-solid-svg-icons";
+import { faBraille, faChalkboard, faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faFileUpload, faPaintBrush, faTable, faUpload, faTextHeight } from "@fortawesome/free-solid-svg-icons";
 import { action, computed, observable, ObservableMap, reaction, runInAction, IReactionDisposer } from "mobx";
 import { observer } from "mobx-react";
 import { Doc, DocListCast, HeightSym, Opt, WidthSym, DocListCastAsync, Field } from "../../../../new_fields/Doc";
@@ -47,6 +47,7 @@ import { List } from "../../../../new_fields/List";
 import { DocumentViewProps } from "../../nodes/DocumentView";
 import { CollectionDockingView } from "../CollectionDockingView";
 import { MainView } from "../../MainView";
+import { TouchScrollableMenuItem } from "../../TouchScrollableMenu";
 
 library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload);
 
@@ -627,12 +628,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                         // use the centerx and centery as the "new mouse position"
                         const centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2;
                         const centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2;
+                        // const transformed = this.getTransform().inverse().transformPoint(centerX, centerY);
 
                         if (!this._pullDirection) { // if we are not bezel movement
                             this.pan({ clientX: centerX, clientY: centerY });
                         } else {
                             this._pullCoords = [centerX, centerY];
-                            console.log(MainView.Instance.flyoutWidth);
                         }
 
                         this._lastX = centerX;
@@ -657,22 +658,28 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             if (pt1 && pt2) {
                 const centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2;
                 const centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2;
+                // const screenPoint = this.getTransform().inverse().transformPoint(centerX, centerY);
                 this._lastX = centerX;
                 this._lastY = centerY;
+                const screenBox = this._mainCont?.getBoundingClientRect();
 
+
+                // console.log(this.props.PanelWidth(), transformed[0]);
                 // determine if we are using a bezel movement
-                if ((this.props.PanelWidth() - this._lastX) < 100) {
-                    this._pullCoords = [this._lastX, this._lastY];
-                    this._pullDirection = "right";
-                } else if (this._lastX < 100) {
-                    this._pullCoords = [this._lastX, this._lastY];
-                    this._pullDirection = "left";
-                } else if (this.props.PanelHeight() - this._lastY < 100) {
-                    this._pullCoords = [this._lastX, this._lastY];
-                    this._pullDirection = "bottom";
-                } else if (this._lastY < 120) { // to account for header
-                    this._pullCoords = [this._lastX, this._lastY];
-                    this._pullDirection = "top";
+                if (screenBox) {
+                    if ((screenBox.right - centerX) < 100) {
+                        this._pullCoords = [centerX, centerY];
+                        this._pullDirection = "right";
+                    } else if (centerX - screenBox.left < 100) {
+                        this._pullCoords = [centerX, centerY];
+                        this._pullDirection = "left";
+                    } else if (screenBox.bottom - centerY < 100) {
+                        this._pullCoords = [centerX, centerY];
+                        this._pullDirection = "bottom";
+                    } else if (centerY - screenBox.top < 100) {
+                        this._pullCoords = [centerX, centerY];
+                        this._pullDirection = "top";
+                    }
                 }
 
 
@@ -708,6 +715,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             default:
                 break;
         }
+        console.log("");
 
         this._pullDirection = "";
         this._pullCoords = [0, 0];
@@ -1120,6 +1128,14 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     }
     render() {
         TraceMobx();
+        const clientRect = this._mainCont?.getBoundingClientRect();
+        // console.log('clientrect has upd8ed', clientRect, this._pullCoords);
+        console.log(
+            'left', clientRect ? this._pullDirection === "right" ? this._pullCoords[0] - MainView.Instance.flyoutWidth : clientRect.x - MainView.Instance.flyoutWidth : "auto",
+            'top indicator', clientRect ? this._pullDirection === "bottom" ? this._pullCoords[1] - clientRect.y : clientRect.y - 20 : "auto",
+            'width', clientRect ? this._pullDirection === "left" ? this._pullCoords[0] - clientRect.left : this._pullDirection === "right" ? clientRect.right - this._pullCoords[0] : clientRect.width : 0,
+            'height', clientRect ? this._pullDirection === "top" ? this._pullCoords[1] - clientRect.top : this._pullDirection === "bottom" ? clientRect.bottom - this._pullCoords[1] : clientRect.height : 0);
+        console.log(clientRect);
         // update the actual dimensions of the collection so that they can inquired (e.g., by a minimap)
         // this.Document.fitX = this.contentBounds && this.contentBounds.x;
         // this.Document.fitY = this.contentBounds && this.contentBounds.y;
@@ -1146,16 +1162,26 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             <div className={"pullpane-indicator"}
                 style={{
                     display: this._pullDirection ? "block" : "none",
-                    // width: this._pullDirection === "left" || this._pullDirection === "right" ? Math.abs(this.props.PanelWidth() - this._pullCoords[0]) : this.props.PanelWidth(),
-                    // height: this._pullDirection === "top" || this._pullDirection === "bottom" ? Math.abs(this.props.PanelHeight() - this._pullCoords[1]) : this.props.PanelHeight(),
-                    // top: this._pullDirection === "bottom" ? this._pullCoords[0] : 0,
-                    // left: this._pullDirection === "right" ? this._pullCoords[1] : 0
-                    width: this._pullDirection === "left" ? this._pullCoords[0] : this._pullDirection === "right" ? MainView.Instance.getPWidth() - this._pullCoords[0] + MainView.Instance.flyoutWidth : MainView.Instance.getPWidth(),
-                    height: this._pullDirection === "top" ? this._pullCoords[1] : this._pullDirection === "bottom" ? MainView.Instance.getPHeight() - this._pullCoords[1] : MainView.Instance.getPHeight(),
-                    left: this._pullDirection === "right" ? undefined : 0,
-                    right: this._pullDirection === "right" ? 0 : undefined,
-                    top: this._pullDirection === "bottom" ? undefined : 0,
-                    bottom: this._pullDirection === "bottom" ? 0 : undefined
+                    // width: clientRect ? this._pullDirection === "left" ? this._pullCoords[0] - clientRect.left : this._pullDirection === "right" ? clientRect.right - this._pullCoords[0] : clientRect.width : 0,
+                    // height: clientRect ? this._pullDirection === "top" ? this._pullCoords[0] - clientRect.top : this._pullDirection === "bottom" ? clientRect.bottom - this._pullCoords[0] : clientRect.height : 0,
+                    // left: clientRect ? this._pullDirection === "right" ? undefined : clientRect.left : 0,
+                    // right: clientRect ? this._pullDirection === "left" ? undefined : clientRect.right : 0,
+                    // top: clientRect ? this._pullDirection === "bottom" ? undefined : clientRect.top : 0,
+                    // bottom: clientRect ? this._pullDirection === "top" ? undefined : clientRect.bottom : 0,
+
+
+
+                    // hahahahahahahhahahahaa
+
+                    width: clientRect ? this._pullDirection === "left" ? this._pullCoords[0] - clientRect.left : this._pullDirection === "right" ? clientRect.right - this._pullCoords[0] : clientRect.width : 0,
+                    height: clientRect ? this._pullDirection === "top" ? this._pullCoords[1] - clientRect.top : this._pullDirection === "bottom" ? clientRect.bottom - this._pullCoords[1] : clientRect.height : 0,
+                    // left: clientRect ? this._pullDirection === "top" || this._pullDirection === "left" || this._pullDirection === "bottom" ? clientRect.left - MainView.Instance.flyoutWidth : this._pullCoords[0] - clientRect.left : undefined,
+                    // top: clientRect ? this._pullDirection === "top" || this._pullDirection === "left" ? clientRect.top - 20 : this._pullCoords[1] - clientRect.top : undefined,
+
+                    // left: x-axis, top: y-axis
+                    left: clientRect ? this._pullDirection === "right" ? this._pullCoords[0] - clientRect.x - MainView.Instance.flyoutWidth : 0 : "auto",
+                    top: clientRect ? this._pullDirection === "bottom" ? this._pullCoords[1] - clientRect.y : 0 : "auto",
+
                 }}>
             </div>
 
-- 
cgit v1.2.3-70-g09d2


From ff268c1440cc4c5553d19524150d6f2d00cbd14c Mon Sep 17 00:00:00 2001
From: kimdahey <claire_kim1@brown.edu>
Date: Tue, 11 Feb 2020 21:41:32 -0500
Subject: removed console logs + comments

---
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 32 ++--------------------
 1 file changed, 2 insertions(+), 30 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index bf517e7c2..5fe5895a8 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -658,13 +658,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             if (pt1 && pt2) {
                 const centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2;
                 const centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2;
-                // const screenPoint = this.getTransform().inverse().transformPoint(centerX, centerY);
                 this._lastX = centerX;
                 this._lastY = centerY;
                 const screenBox = this._mainCont?.getBoundingClientRect();
 
 
-                // console.log(this.props.PanelWidth(), transformed[0]);
                 // determine if we are using a bezel movement
                 if (screenBox) {
                     if ((screenBox.right - centerX) < 100) {
@@ -697,19 +695,15 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         switch (this._pullDirection) {
 
             case "left":
-                console.log('pulled from left');
                 CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([], { title: "New Collection" }), "left", undefined);
                 break;
             case "right":
-                console.log('pulled from right');
                 CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([], { title: "New Collection" }), "right", undefined);
                 break;
             case "top":
-                console.log('pulled from top');
                 CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([], { title: "New Collection" }), "top", undefined);
                 break;
             case "bottom":
-                console.log('pulled from bottom');
                 CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([], { title: "New Collection" }), "bottom", undefined);
                 break;
             default:
@@ -1129,13 +1123,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     render() {
         TraceMobx();
         const clientRect = this._mainCont?.getBoundingClientRect();
-        // console.log('clientrect has upd8ed', clientRect, this._pullCoords);
-        console.log(
-            'left', clientRect ? this._pullDirection === "right" ? this._pullCoords[0] - MainView.Instance.flyoutWidth : clientRect.x - MainView.Instance.flyoutWidth : "auto",
-            'top indicator', clientRect ? this._pullDirection === "bottom" ? this._pullCoords[1] - clientRect.y : clientRect.y - 20 : "auto",
-            'width', clientRect ? this._pullDirection === "left" ? this._pullCoords[0] - clientRect.left : this._pullDirection === "right" ? clientRect.right - this._pullCoords[0] : clientRect.width : 0,
-            'height', clientRect ? this._pullDirection === "top" ? this._pullCoords[1] - clientRect.top : this._pullDirection === "bottom" ? clientRect.bottom - this._pullCoords[1] : clientRect.height : 0);
-        console.log(clientRect);
         // update the actual dimensions of the collection so that they can inquired (e.g., by a minimap)
         // this.Document.fitX = this.contentBounds && this.contentBounds.x;
         // this.Document.fitY = this.contentBounds && this.contentBounds.y;
@@ -1162,25 +1149,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             <div className={"pullpane-indicator"}
                 style={{
                     display: this._pullDirection ? "block" : "none",
-                    // width: clientRect ? this._pullDirection === "left" ? this._pullCoords[0] - clientRect.left : this._pullDirection === "right" ? clientRect.right - this._pullCoords[0] : clientRect.width : 0,
-                    // height: clientRect ? this._pullDirection === "top" ? this._pullCoords[0] - clientRect.top : this._pullDirection === "bottom" ? clientRect.bottom - this._pullCoords[0] : clientRect.height : 0,
-                    // left: clientRect ? this._pullDirection === "right" ? undefined : clientRect.left : 0,
-                    // right: clientRect ? this._pullDirection === "left" ? undefined : clientRect.right : 0,
-                    // top: clientRect ? this._pullDirection === "bottom" ? undefined : clientRect.top : 0,
-                    // bottom: clientRect ? this._pullDirection === "top" ? undefined : clientRect.bottom : 0,
-
-
-
-                    // hahahahahahahhahahahaa
-
+                    top: clientRect ? this._pullDirection === "bottom" ? this._pullCoords[1] - clientRect.y : 0 : "auto",
+                    left: clientRect ? this._pullDirection === "right" ? this._pullCoords[0] - clientRect.x - MainView.Instance.flyoutWidth : 0 : "auto",
                     width: clientRect ? this._pullDirection === "left" ? this._pullCoords[0] - clientRect.left : this._pullDirection === "right" ? clientRect.right - this._pullCoords[0] : clientRect.width : 0,
                     height: clientRect ? this._pullDirection === "top" ? this._pullCoords[1] - clientRect.top : this._pullDirection === "bottom" ? clientRect.bottom - this._pullCoords[1] : clientRect.height : 0,
-                    // left: clientRect ? this._pullDirection === "top" || this._pullDirection === "left" || this._pullDirection === "bottom" ? clientRect.left - MainView.Instance.flyoutWidth : this._pullCoords[0] - clientRect.left : undefined,
-                    // top: clientRect ? this._pullDirection === "top" || this._pullDirection === "left" ? clientRect.top - 20 : this._pullCoords[1] - clientRect.top : undefined,
-
-                    // left: x-axis, top: y-axis
-                    left: clientRect ? this._pullDirection === "right" ? this._pullCoords[0] - clientRect.x - MainView.Instance.flyoutWidth : 0 : "auto",
-                    top: clientRect ? this._pullDirection === "bottom" ? this._pullCoords[1] - clientRect.y : 0 : "auto",
 
                 }}>
             </div>
-- 
cgit v1.2.3-70-g09d2


From 7dbcd2011ec08476151a08c113191a1676e757a2 Mon Sep 17 00:00:00 2001
From: Stanley Yip <stanley_yip@brown.edu>
Date: Tue, 11 Feb 2020 22:36:54 -0500
Subject: some small stuff

---
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  3 +--
 .../collections/collectionFreeForm/MarqueeView.tsx | 31 +++++++++++++++++++---
 2 files changed, 28 insertions(+), 6 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index cc6a2f4a5..41a22c558 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -470,7 +470,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                             strokes.push(d.inkData.map(pd => ({ X: pd.X + x - left, Y: pd.Y + y - top })));
                         }
                     });
-                    console.log(strokes)
 
                     CognitiveServices.Inking.Appliers.InterpretStrokes(strokes).then((results) => {
                         console.log(results);
@@ -487,7 +486,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                                 if (this._wordPalette.has(word.recognizedText.toLowerCase())) {
                                     inks[i].color = this._wordPalette.get(word.recognizedText.toLowerCase());
                                 }
-                                else {
+                                else if (word.alternates) {
                                     for (const alt of word.alternates) {
                                         if (this._wordPalette.has(alt.recognizedString.toLowerCase())) {
                                             inks[i].color = this._wordPalette.get(alt.recognizedString.toLowerCase());
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index e82ca6bf2..d4faa4dc1 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -384,16 +384,39 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
             });
             const inkFields = inks.map(i => Cast(i.data, InkField));
             CognitiveServices.Inking.Appliers.InterpretStrokes(inkFields.filter(i => i instanceof InkField).map(i => i!.inkData)).then((results) => {
+                // const wordResults = results.filter((r: any) => r.category === "inkWord");
+                // console.log(wordResults);
+                // console.log(results);
+                // for (const word of wordResults) {
+                //     const indices: number[] = word.strokeIds;
+                //     indices.forEach(i => {
+                //         if (wordToColor.has(word.recognizedText.toLowerCase())) {
+                //             inks[i].color = wordToColor.get(word.recognizedText.toLowerCase());
+                //         }
+                //         else {
+                //             for (const alt of word.alternates) {
+                //                 if (wordToColor.has(alt.recognizedString.toLowerCase())) {
+                //                     inks[i].color = wordToColor.get(alt.recognizedString.toLowerCase());
+                //                     break;
+                //                 }
+                //             }
+                //         }
+                //     })
+                // }
                 const wordResults = results.filter((r: any) => r.category === "inkWord");
-                console.log(wordResults);
-                console.log(results);
                 for (const word of wordResults) {
                     const indices: number[] = word.strokeIds;
                     indices.forEach(i => {
+                        const otherInks: Doc[] = [];
+                        indices.forEach(i2 => i2 !== i && otherInks.push(inks[i2]));
+                        inks[i].relatedInks = new List<Doc>(otherInks);
+                        const uniqueColors: string[] = [];
+                        Array.from(wordToColor.values()).forEach(c => uniqueColors.indexOf(c) === -1 && uniqueColors.push(c));
+                        inks[i].alternativeColors = new List<string>(uniqueColors);
                         if (wordToColor.has(word.recognizedText.toLowerCase())) {
                             inks[i].color = wordToColor.get(word.recognizedText.toLowerCase());
                         }
-                        else {
+                        else if (word.alternates) {
                             for (const alt of word.alternates) {
                                 if (wordToColor.has(alt.recognizedString.toLowerCase())) {
                                     inks[i].color = wordToColor.get(alt.recognizedString.toLowerCase());
@@ -401,7 +424,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
                                 }
                             }
                         }
-                    })
+                    });
                 }
             });
         }
-- 
cgit v1.2.3-70-g09d2


From 33d5a12af14e1ed50e5c3164b363fbbc253506a0 Mon Sep 17 00:00:00 2001
From: Stanley Yip <stanley_yip@brown.edu>
Date: Tue, 11 Feb 2020 22:45:13 -0500
Subject: small fix

---
 .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx    | 3 ++-
 src/client/views/nodes/DocumentView.tsx                                | 2 --
 2 files changed, 2 insertions(+), 3 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index ec01fc7a5..4b2ac94c1 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1163,7 +1163,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 style={{
                     display: this._pullDirection ? "block" : "none",
                     top: clientRect ? this._pullDirection === "bottom" ? this._pullCoords[1] - clientRect.y : 0 : "auto",
-                    left: clientRect ? this._pullDirection === "right" ? this._pullCoords[0] - clientRect.x - MainView.Instance.flyoutWidth : 0 : "auto",
+                    // left: clientRect ? this._pullDirection === "right" ? this._pullCoords[0] - clientRect.x - MainView.Instance.flyoutWidth : 0 : "auto",
+                    left: clientRect ? this._pullDirection === "right" ? this._pullCoords[0] - clientRect.x : 0 : "auto",
                     width: clientRect ? this._pullDirection === "left" ? this._pullCoords[0] - clientRect.left : this._pullDirection === "right" ? clientRect.right - this._pullCoords[0] : clientRect.width : 0,
                     height: clientRect ? this._pullDirection === "top" ? this._pullCoords[1] - clientRect.top : this._pullDirection === "bottom" ? clientRect.bottom - this._pullCoords[1] : clientRect.height : 0,
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index aa07db2a0..a69de60df 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -339,8 +339,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         SelectionManager.DeselectAll();
         if (this.Document.onPointerDown) return;
         const touch = me.touchEvent.changedTouches.item(0);
-        console.log("DOWN", SelectionManager.SelectedDocuments());
-        console.log("down");
         if (touch) {
             this._downX = touch.clientX;
             this._downY = touch.clientY;
-- 
cgit v1.2.3-70-g09d2


From fc533121302306e2be0cdf1957f7d60a6bc65097 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 11 Feb 2020 23:14:07 -0500
Subject: made default zoom to doc take up 3/4 of screen instead of 1/2

---
 .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx     | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 07a5a2c7b..8f0486b5b 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -697,7 +697,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
     }
 
-    setScaleToZoom = (doc: Doc, scale: number = 0.5) => {
+    setScaleToZoom = (doc: Doc, scale: number = 0.75) => {
         this.Document.scale = scale * Math.min(this.props.PanelWidth() / NumCast(doc._width), this.props.PanelHeight() / NumCast(doc._height));
     }
 
-- 
cgit v1.2.3-70-g09d2


From 5ed99ec401a403f8539d26d719c0c344cde9657a Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Wed, 12 Feb 2020 21:55:15 -0500
Subject: added childLayoutTemplate to freeform views.  fixed height of
 timeline docs.

---
 .../collectionFreeForm/CollectionFreeFormLayoutEngines.tsx            | 2 +-
 .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx   | 4 +++-
 2 files changed, 4 insertions(+), 2 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 050ca8347..ce2bce052 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -303,7 +303,7 @@ export function computeTimelineLayout(
             docMap.set(doc, {
                 type: "doc",
                 x: x, y: -Math.sqrt(stack) * pivotAxisWidth / 2 - pivotAxisWidth + (pivotAxisWidth - hgt) / 2,
-                zIndex: (curTime === key ? 1000 : zind++), highlight: curTime === key, width: wid / (Math.max(stack, 1)), height: hgt, payload: undefined
+                zIndex: (curTime === key ? 1000 : zind++), highlight: curTime === key, width: wid / (Math.max(stack, 1)), height: hgt / (Math.max(stack, 1)), payload: undefined
             });
             stacking[stack] = x + pivotAxisWidth;
         });
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 8f0486b5b..7f1817a15 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -4,7 +4,7 @@ import { faBraille, faChalkboard, faCompass, faCompressArrowsAlt, faExpandArrows
 import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import { computedFn } from "mobx-utils";
-import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../../new_fields/Doc";
+import { Doc, DocListCast, HeightSym, Opt, WidthSym, DocCastAsync } from "../../../../new_fields/Doc";
 import { documentSchema, positionSchema } from "../../../../new_fields/documentSchemas";
 import { Id } from "../../../../new_fields/FieldSymbols";
 import { InkTool } from "../../../../new_fields/InkField";
@@ -867,6 +867,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         });
         return rangeFilteredDocs;
     }
+    childLayoutDocFunc = () => this.props.childLayoutTemplate?.() || Cast(this.props.Document.childLayoutTemplate, Doc, null) as Doc;
     get doLayoutComputation() {
         const { newPool, computedElementData } = this.doInternalLayoutComputation;
         runInAction(() =>
@@ -883,6 +884,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             computedElementData.elements.push({
                 ele: <CollectionFreeFormDocumentView key={pair.layout[Id]}  {...this.getChildDocumentViewProps(pair.layout, pair.data)}
                     dataProvider={this.childDataProvider}
+                    LayoutDoc={this.childLayoutDocFunc}
                     jitterRotation={NumCast(this.props.Document.jitterRotation)}
                     fitToBox={this.props.fitToBox || this.props.layoutEngine !== undefined} />,
                 bounds: this.childDataProvider(pair.layout)
-- 
cgit v1.2.3-70-g09d2


From 553fb2a11aa638c35192a1a2f7da99729867e542 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 13 Feb 2020 22:30:32 -0500
Subject: starting a broad cleanup of code.  trying to make standard doc field
 names be more regular

---
 src/client/documents/Documents.ts                  | 15 ++--
 src/client/util/DragManager.ts                     |  1 -
 src/client/util/RichTextRules.ts                   |  3 +-
 src/client/views/MainView.tsx                      |  7 +-
 .../views/collections/CollectionCarouselView.scss  |  1 +
 .../views/collections/CollectionCarouselView.tsx   |  1 -
 .../views/collections/CollectionLinearView.tsx     | 10 +--
 src/client/views/collections/CollectionSubView.tsx |  5 +-
 .../views/collections/CollectionTreeView.tsx       | 35 ++++-----
 src/client/views/collections/CollectionView.tsx    |  5 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  3 +-
 .../views/nodes/ContentFittingDocumentView.tsx     | 24 +-----
 src/client/views/nodes/DocumentView.tsx            | 36 ++-------
 src/client/views/nodes/FormattedTextBox.tsx        | 11 ---
 src/client/views/nodes/ImageBox.tsx                |  9 ++-
 src/new_fields/Doc.ts                              | 90 +++++++++-------------
 src/new_fields/documentSchemas.ts                  | 12 ++-
 .../authentication/models/current_user_utils.ts    |  8 +-
 18 files changed, 96 insertions(+), 180 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 8304c2a7f..adcdf260d 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -22,7 +22,6 @@ import { ImageField, VideoField, AudioField, PdfField, WebField, YoutubeField }
 import { HtmlField } from "../../new_fields/HtmlField";
 import { List } from "../../new_fields/List";
 import { Cast, NumCast } from "../../new_fields/Types";
-import { IconField } from "../../new_fields/IconField";
 import { listSpec } from "../../new_fields/Schema";
 import { DocServer } from "../DocServer";
 import { dropActionType } from "../util/DragManager";
@@ -53,7 +52,6 @@ import { InkingStroke } from "../views/InkingStroke";
 import { InkField } from "../../new_fields/InkField";
 import { InkingControl } from "../views/InkingControl";
 import { RichTextField } from "../../new_fields/RichTextField";
-import { Networking } from "../Network";
 import { extname } from "path";
 import { MessageStore } from "../../server/Message";
 const requestImageSize = require('../util/request-image-size');
@@ -92,7 +90,6 @@ export interface DocumentOptions {
     scale?: number;
     isDisplayPanel?: boolean; // whether the panel functions as GoldenLayout "stack" used to display documents
     forceActive?: boolean;
-    preventTreeViewOpen?: boolean; // ignores the treeViewOpen Doc flag which allows a treeViewItem's expande/collapse state to be independent of other views of the same document in the tree view
     layout?: string | Doc;
     hideHeadings?: boolean; // whether stacking view column headings should be hidden
     isTemplateForField?: string; // the field key for which the containing document is a rendering template
@@ -109,7 +106,6 @@ export interface DocumentOptions {
     curPage?: number;
     currentTimecode?: number; // the current timecode of a time-based document (e.g., current time of a video)  value is in seconds
     displayTimecode?: number; // the time that a document should be displayed (e.g., time an annotation should be displayed on a video)
-    documentText?: string;
     borderRounding?: string;
     boxShadow?: string;
     sectionFilter?: string; // field key used to determine headings for sections in stacking and masonry views
@@ -124,15 +120,16 @@ export interface DocumentOptions {
     onChildClick?: ScriptField; // script given to children of a collection to execute when they are clicked
     onPointerDown?: ScriptField;
     onPointerUp?: ScriptField;
+    dropConverter?: ScriptField; // script to run when documents are dropped on this Document.
     dragFactory?: Doc; // document to create when dragging with a suitable onDragStart script
     onDragStart?: ScriptField; //script to execute at start of drag operation --  e.g., when a "creator" button is dragged this script generates a different document to drop
-    clipboard?: Doc; //script to execute at start of drag operation --  e.g., when a "creator" button is dragged this script generates a different document to drop
+    clipboard?: Doc;
     icon?: string;
     sourcePanel?: Doc; // panel to display in 'targetContainer' as the result of a button onClick script
     targetContainer?: Doc; // document whose proto will be set to 'panel' as the result of a onClick click script
-    dropConverter?: ScriptField; // script to run when documents are dropped on this Document.
     strokeWidth?: number;
     color?: string;
+    treeViewPreventOpen?: boolean; // ignores the treeViewOpen Doc flag which allows a treeViewItem's expand/collapse state to be independent of other views of the same document in the tree view
     treeViewHideTitle?: boolean; // whether to hide the title of a tree view
     treeViewHideHeaderFields?: boolean; // whether to hide the drop down options for tree view items.
     treeViewOpen?: boolean; // whether this document is expanded in a tree view
@@ -141,9 +138,9 @@ export interface DocumentOptions {
     limitHeight?: number; // maximum height for newly created (eg, from pasting) text documents
     // [key: string]: Opt<Field>;
     pointerHack?: boolean; // for buttons, allows onClick handler to fire onPointerDown
-    isExpanded?: boolean; // is linear view expanded
-    textTransform?: string; // is linear view expanded
-    letterSpacing?: string; // is linear view expanded
+    linearViewIsExpanded?: boolean; // is linear view expanded
+    textTransform?: string;
+    letterSpacing?: string;
 }
 
 class EmptyBox {
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index e572f0fcb..2877d5fd7 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -134,7 +134,6 @@ export namespace DragManager {
         embedDoc?: boolean;
         moveDocument?: MoveFunction;
         isSelectionMove?: boolean; // indicates that an explicitly selected Document is being dragged.  this will suppress onDragStart scripts
-        applyAsTemplate?: boolean;
     }
     export class LinkDragData {
         constructor(linkSourceDoc: Doc) {
diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts
index 6b8762a5e..de0f46202 100644
--- a/src/client/util/RichTextRules.ts
+++ b/src/client/util/RichTextRules.ts
@@ -121,8 +121,7 @@ export class RichTextRules {
             new InputRule(
                 new RegExp(/##$/),
                 (state, match, start, end) => {
-                    const schemaDoc = Doc.GetDataDoc(this.Document);
-                    const textDoc = Doc.GetProto(Cast(schemaDoc[DataSym], Doc, null)!);
+                    const textDoc = this.Document[DataSym];
                     const numInlines = NumCast(textDoc.inlineTextCount);
                     textDoc.inlineTextCount = numInlines + 1;
                     const inlineFieldKey = "inline" + numInlines; // which field on the text document this annotation will write to
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 401a4b15c..192f3b8fb 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -482,12 +482,13 @@ export class MainView extends React.Component {
         return new Transform(-translateX, -translateY, 1 / scale);
     }
     @computed get docButtons() {
-        if (CurrentUserUtils.UserDocument?.expandingButtons instanceof Doc) {
+        const expandingBtns = Doc.UserDoc()?.expandingButtons;
+        if (expandingBtns instanceof Doc) {
             return <div className="mainView-docButtons" ref={this._docBtnRef}
-                style={{ height: !CurrentUserUtils.UserDocument.expandingButtons.isExpanded ? "42px" : undefined }} >
+                style={{ height: !expandingBtns.linearViewIsExpanded ? "42px" : undefined }} >
                 <MainViewNotifs />
                 <CollectionLinearView
-                    Document={CurrentUserUtils.UserDocument.expandingButtons}
+                    Document={expandingBtns}
                     DataDoc={undefined}
                     LibraryPath={emptyPath}
                     fieldKey={"data"}
diff --git a/src/client/views/collections/CollectionCarouselView.scss b/src/client/views/collections/CollectionCarouselView.scss
index ad369bbff..fd1296286 100644
--- a/src/client/views/collections/CollectionCarouselView.scss
+++ b/src/client/views/collections/CollectionCarouselView.scss
@@ -1,6 +1,7 @@
 
 .collectionCarouselView-outer {
     background: gray;
+    height : 100%;
     .collectionCarouselView-caption {
         margin-left: 10%;
         margin-right: 10%;
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index f462e20b4..3c86bd799 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -12,7 +12,6 @@ import { CollectionSubView } from './CollectionSubView';
 import { faCaretLeft, faCaretRight } from '@fortawesome/free-solid-svg-icons';
 import { Doc } from '../../../new_fields/Doc';
 import { FormattedTextBox } from '../nodes/FormattedTextBox';
-import { ContextMenuProps } from '../ContextMenuItem';
 import { ContextMenu } from '../ContextMenu';
 import { ObjectField } from '../../../new_fields/ObjectField';
 
diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx
index 67062ae41..7eb316cf0 100644
--- a/src/client/views/collections/CollectionLinearView.tsx
+++ b/src/client/views/collections/CollectionLinearView.tsx
@@ -38,8 +38,8 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
 
     componentDidMount() {
         // is there any reason this needs to exist? -syip.  yes, it handles autoHeight for stacking views (masonry isn't yet supported).
-        this._widthDisposer = reaction(() => this.props.Document[HeightSym]() + this.childDocs.length + (this.props.Document.isExpanded ? 1 : 0),
-            () => this.props.Document._width = 5 + (this.props.Document.isExpanded ? this.childDocs.length * (this.props.Document[HeightSym]()) : 10),
+        this._widthDisposer = reaction(() => this.props.Document[HeightSym]() + this.childDocs.length + (this.props.Document.linearViewIsExpanded ? 1 : 0),
+            () => this.props.Document._width = 5 + (this.props.Document.linearViewIsExpanded ? this.childDocs.length * (this.props.Document[HeightSym]()) : 10),
             { fireImmediately: true }
         );
 
@@ -84,8 +84,8 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
         const guid = Utils.GenerateGuid();
         return <div className="collectionLinearView-outer">
             <div className="collectionLinearView" ref={this.createDashEventsTarget} >
-                <input id={`${guid}`} type="checkbox" checked={BoolCast(this.props.Document.isExpanded)} ref={this.addMenuToggle}
-                    onChange={action((e: any) => this.props.Document.isExpanded = this.addMenuToggle.current!.checked)} />
+                <input id={`${guid}`} type="checkbox" checked={BoolCast(this.props.Document.linearViewIsExpanded)} ref={this.addMenuToggle}
+                    onChange={action((e: any) => this.props.Document.linearViewIsExpanded = this.addMenuToggle.current!.checked)} />
                 <label htmlFor={`${guid}`} style={{ marginTop: "auto", marginBottom: "auto", background: StrCast(this.props.Document.backgroundColor, "black") === StrCast(this.props.Document.color, "white") ? "black" : StrCast(this.props.Document.backgroundColor, "black") }} title="Close Menu"><p>+</p></label>
 
                 <div className="collectionLinearView-content" style={{ height: this.dimension(), width: NumCast(this.props.Document._width, 25) }}>
@@ -97,7 +97,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
                         return <div className={`collectionLinearView-docBtn` + (pair.layout.onClick || pair.layout.onDragStart ? "-scalable" : "")} key={pair.layout[Id]} ref={dref}
                             style={{
                                 width: nested ? pair.layout[WidthSym]() : this.dimension() - deltaSize,
-                                height: nested && pair.layout.isExpanded ? pair.layout[HeightSym]() : this.dimension() - deltaSize,
+                                height: nested && pair.layout.linearViewIsExpanded ? pair.layout[HeightSym]() : this.dimension() - deltaSize,
                             }}  >
                             <DocumentView
                                 Document={pair.layout}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 343be1b58..0963e1ea6 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -159,7 +159,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
             const docDragData = de.complete.docDragData;
             (this.props.Document.dropConverter instanceof ScriptField) &&
                 this.props.Document.dropConverter.script.run({ dragData: docDragData }); /// bcz: check this 
-            if (docDragData && !docDragData.applyAsTemplate) {
+            if (docDragData) {
                 if (de.altKey && docDragData.draggedDocuments.length) {
                     this.childDocs.map(doc => {
                         doc.layout_fromParent = docDragData.draggedDocuments[0];
@@ -253,7 +253,8 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
                             }
                         });
                     } else {
-                        const htmlDoc = Docs.Create.HtmlDocument(html, { ...options, title: "-web page-", _width: 300, _height: 300, documentText: text });
+                        const htmlDoc = Docs.Create.HtmlDocument(html, { ...options, title: "-web page-", _width: 300, _height: 300 });
+                        Doc.GetProto(htmlDoc)["data-text"] = text;
                         this.props.addDocument(htmlDoc);
                     }
                     return;
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index e91c3cc49..a160dcc62 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -61,7 +61,7 @@ export interface TreeViewProps {
     parentKey: string;
     active: (outsideReaction?: boolean) => boolean;
     treeViewHideHeaderFields: () => boolean;
-    preventTreeViewOpen: boolean;
+    treeViewPreventOpen: boolean;
     renderedIds: string[];
     onCheckedClick?: ScriptField;
 }
@@ -84,7 +84,7 @@ library.add(faPlus, faMinus);
  * 
  * special fields:
  * treeViewOpen : flag denoting whether the documents sub-tree (contents) is visible or hidden
- * preventTreeViewOpen : ignores the treeViewOpen flag (for allowing a view to not be slaved to other views of the document)
+ * treeViewPreventOpen : ignores the treeViewOpen flag (for allowing a view to not be slaved to other views of the document)
  * treeViewExpandedView : name of field whose contents are being displayed as the document's subtree
  */
 class TreeView extends React.Component<TreeViewProps> {
@@ -96,8 +96,8 @@ class TreeView extends React.Component<TreeViewProps> {
 
     get defaultExpandedView() { return this.childDocs ? this.fieldKey : StrCast(this.props.document.defaultExpandedView, "fields"); }
     @observable _overrideTreeViewOpen = false; // override of the treeViewOpen field allowing the display state to be independent of the document's state
-    set treeViewOpen(c: boolean) { if (this.props.preventTreeViewOpen) this._overrideTreeViewOpen = c; else this.props.document.treeViewOpen = this._overrideTreeViewOpen = c; }
-    @computed get treeViewOpen() { return (!this.props.preventTreeViewOpen && BoolCast(this.props.document.treeViewOpen)) || this._overrideTreeViewOpen; }
+    set treeViewOpen(c: boolean) { if (this.props.treeViewPreventOpen) this._overrideTreeViewOpen = c; else this.props.document.treeViewOpen = this._overrideTreeViewOpen = c; }
+    @computed get treeViewOpen() { return (!this.props.treeViewPreventOpen && BoolCast(this.props.document.treeViewOpen)) || this._overrideTreeViewOpen; }
     @computed get treeViewExpandedView() { return StrCast(this.props.document.treeViewExpandedView, this.defaultExpandedView); }
     @computed get MAX_EMBED_HEIGHT() { return NumCast(this.props.document.maxEmbedHeight, 300); }
     @computed get dataDoc() { return this.templateDataDoc ? this.templateDataDoc : this.props.document; }
@@ -179,8 +179,7 @@ class TreeView extends React.Component<TreeViewProps> {
         SetValue={undoBatch((value: string) => Doc.SetInPlace(this.props.document, key, value, false) || true)}
         OnFillDown={undoBatch((value: string) => {
             Doc.SetInPlace(this.props.document, key, value, false);
-            const layoutDoc = this.props.document.layout_custom instanceof Doc ? Doc.ApplyTemplate(Doc.GetProto(this.props.document.layout_custom)) : undefined;
-            const doc = layoutDoc || Docs.Create.FreeformDocument([], { title: "", x: 0, y: 0, _width: 100, _height: 25, templates: new List<string>([Templates.Title.Layout]) });
+            const doc = Docs.Create.FreeformDocument([], { title: "", x: 0, y: 0, _width: 100, _height: 25, templates: new List<string>([Templates.Title.Layout]) });
             EditableView.loadId = doc[Id];
             return this.props.addDocument(doc);
         })}
@@ -291,7 +290,7 @@ class TreeView extends React.Component<TreeViewProps> {
                 contentElement = TreeView.GetChildElements(contents instanceof Doc ? [contents] :
                     DocListCast(contents), this.props.treeViewId, doc, undefined, key, this.props.containingCollection, this.props.prevSibling, addDoc, remDoc, this.move,
                     this.props.dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active,
-                    this.props.panelWidth, this.props.ChromeHeight, this.props.renderDepth, this.props.treeViewHideHeaderFields, this.props.preventTreeViewOpen,
+                    this.props.panelWidth, this.props.ChromeHeight, this.props.renderDepth, this.props.treeViewHideHeaderFields, this.props.treeViewPreventOpen,
                     [...this.props.renderedIds, doc[Id]], this.props.libraryPath, this.props.onCheckedClick);
             } else {
                 contentElement = <EditableView
@@ -334,7 +333,7 @@ class TreeView extends React.Component<TreeViewProps> {
                     TreeView.GetChildElements(docs, this.props.treeViewId, Doc.Layout(this.props.document),
                         this.templateDataDoc, expandKey, this.props.containingCollection, this.props.prevSibling, addDoc, remDoc, this.move,
                         this.props.dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform,
-                        this.props.outerXf, this.props.active, this.props.panelWidth, this.props.ChromeHeight, this.props.renderDepth, this.props.treeViewHideHeaderFields, this.props.preventTreeViewOpen,
+                        this.props.outerXf, this.props.active, this.props.panelWidth, this.props.ChromeHeight, this.props.renderDepth, this.props.treeViewHideHeaderFields, this.props.treeViewPreventOpen,
                         [...this.props.renderedIds, this.props.document[Id]], this.props.libraryPath, this.props.onCheckedClick)}
             </ul >;
         } else if (this.treeViewExpandedView === "fields") {
@@ -464,7 +463,7 @@ class TreeView extends React.Component<TreeViewProps> {
         ChromeHeight: undefined | (() => number),
         renderDepth: number,
         treeViewHideHeaderFields: () => boolean,
-        preventTreeViewOpen: boolean,
+        treeViewPreventOpen: boolean,
         renderedIds: string[],
         libraryPath: Doc[] | undefined,
         onCheckedClick: ScriptField | undefined
@@ -574,7 +573,7 @@ class TreeView extends React.Component<TreeViewProps> {
                 parentKey={key}
                 active={active}
                 treeViewHideHeaderFields={treeViewHideHeaderFields}
-                preventTreeViewOpen={preventTreeViewOpen}
+                treeViewPreventOpen={treeViewPreventOpen}
                 renderedIds={renderedIds} />;
         });
     }
@@ -601,8 +600,7 @@ export class CollectionTreeView extends CollectionSubView(Document) {
 
     @action
     remove = (document: Document): boolean => {
-        const targetDataDoc = this.props.Document && !this.props.Document.isTemplateForField ? this.props.Document : Doc.GetProto(this.props.Document[DataSym] as Doc);
-        const children = Cast(targetDataDoc[this.props.fieldKey], listSpec(Doc), []);
+        const children = Cast(this.props.Document[DataSym][this.props.fieldKey], listSpec(Doc), []);
         if (children.indexOf(document) !== -1) {
             children.splice(children.indexOf(document), 1);
             return true;
@@ -611,10 +609,8 @@ export class CollectionTreeView extends CollectionSubView(Document) {
     }
     @action
     addDoc = (doc: Document, relativeTo: Opt<Doc>, before?: boolean): boolean => {
-        const doAddDoc = () => {
-            const targetDataDoc = this.props.Document && !this.props.Document.isTemplateForField ? this.props.Document : Doc.GetProto(this.props.Document[DataSym] as Doc);
-            Doc.AddDocToList(targetDataDoc, this.props.fieldKey, doc, relativeTo, before, false, false, false);
-        };
+        const doAddDoc = () =>
+            Doc.AddDocToList(this.props.Document[DataSym], this.props.fieldKey, doc, relativeTo, before, false, false, false);
         if (this.props.Document.resolvedDataDoc instanceof Promise) {
             this.props.Document.resolvedDataDoc.then(resolved => doAddDoc());
         } else {
@@ -637,7 +633,7 @@ export class CollectionTreeView extends CollectionSubView(Document) {
             ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
         } else {
             const layoutItems: ContextMenuProps[] = [];
-            layoutItems.push({ description: (this.props.Document.preventTreeViewOpen ? "Persist" : "Abandon") + "Treeview State", event: () => this.props.Document.preventTreeViewOpen = !this.props.Document.preventTreeViewOpen, icon: "paint-brush" });
+            layoutItems.push({ description: (this.props.Document.treeViewPreventOpen ? "Persist" : "Abandon") + "Treeview State", event: () => this.props.Document.treeViewPreventOpen = !this.props.Document.treeViewPreventOpen, icon: "paint-brush" });
             layoutItems.push({ description: (this.props.Document.treeViewHideHeaderFields ? "Show" : "Hide") + " Header Fields", event: () => this.props.Document.treeViewHideHeaderFields = !this.props.Document.treeViewHideHeaderFields, icon: "paint-brush" });
             layoutItems.push({ description: (this.props.Document.treeViewHideTitle ? "Show" : "Hide") + " Title", event: () => this.props.Document.treeViewHideTitle = !this.props.Document.treeViewHideTitle, icon: "paint-brush" });
             ContextMenu.Instance.addItem({ description: "Treeview Options ...", subitems: layoutItems, icon: "eye" });
@@ -733,8 +729,7 @@ export class CollectionTreeView extends CollectionSubView(Document) {
                     SetValue={undoBatch((value: string) => Doc.SetInPlace(this.dataDoc, "title", value, false) || true)}
                     OnFillDown={undoBatch((value: string) => {
                         Doc.SetInPlace(this.dataDoc, "title", value, false);
-                        const layoutDoc = this.props.Document.layout_custom instanceof Doc ? Doc.ApplyTemplate(Doc.GetProto(this.props.Document.layout_custom)) : undefined;
-                        const doc = layoutDoc || Docs.Create.FreeformDocument([], { title: "", x: 0, y: 0, _width: 100, _height: 25, templates: new List<string>([Templates.Title.Layout]) });
+                        const doc = Docs.Create.FreeformDocument([], { title: "", x: 0, y: 0, _width: 100, _height: 25, templates: new List<string>([Templates.Title.Layout]) });
                         EditableView.loadId = doc[Id];
                         this.addDoc(doc, this.childDocs.length ? this.childDocs[0] : undefined, true);
                     })} />)}
@@ -744,7 +739,7 @@ export class CollectionTreeView extends CollectionSubView(Document) {
                         TreeView.GetChildElements(this.childDocs, this.props.Document, this.props.Document, this.props.DataDoc, this.props.fieldKey, this.props.ContainingCollectionDoc, undefined, addDoc, this.remove,
                             moveDoc, dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform,
                             this.outerXf, this.props.active, this.props.PanelWidth, this.props.ChromeHeight, this.props.renderDepth, () => BoolCast(this.props.Document.treeViewHideHeaderFields),
-                            BoolCast(this.props.Document.preventTreeViewOpen), [], this.props.LibraryPath, ScriptCast(this.props.Document.onCheckedClick))
+                            BoolCast(this.props.Document.treeViewPreventOpen), [], this.props.LibraryPath, ScriptCast(this.props.Document.onCheckedClick))
                     }
                 </ul>
             </div >
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index ad39b69d8..39ce4d6e8 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -133,7 +133,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
 
     @action.bound
     addDocument(doc: Doc): boolean {
-        const targetDataDoc = this.props.Document.resolvedDataDoc && !this.props.Document.isTemplateForField ? this.props.Document : Doc.GetProto(this.props.Document[DataSym]);
+        const targetDataDoc = this.props.Document[DataSym];
         targetDataDoc[this.props.fieldKey] = new List<Doc>([...DocListCast(targetDataDoc[this.props.fieldKey]), doc]);  // DocAddToList may write to targetdataDoc's parent ... we don't want this. should really change GetProto to GetDataDoc and test for resolvedDataDoc there
         // Doc.AddDocToList(targetDataDoc, this.props.fieldKey, doc);
         targetDataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()));
@@ -145,8 +145,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
     removeDocument(doc: Doc): boolean {
         const docView = DocumentManager.Instance.getDocumentView(doc, this.props.ContainingCollectionView);
         docView && SelectionManager.DeselectDoc(docView);
-        const targetDataDoc = this.props.Document.resolvedDataDoc && !this.props.Document.isTemplateForField ? this.props.Document : Doc.GetProto(this.props.Document[DataSym]);
-        const value = Cast(targetDataDoc[this.props.fieldKey], listSpec(Doc), []);
+        const value = Cast(this.props.Document[DataSym][this.props.fieldKey], listSpec(Doc), []);
         let index = value.reduce((p, v, i) => (v instanceof Doc && v === doc) ? i : p, -1);
         index = index !== -1 ? index : value.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, doc)) ? i : p, -1);
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 7f1817a15..e1854fc2d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1074,7 +1074,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         // this.Document.fitH = this.contentBounds && (this.contentBounds.b - this.contentBounds.y);
         // if isAnnotationOverlay is set, then children will be stored in the extension document for the fieldKey.
         // otherwise, they are stored in fieldKey.  All annotations to this document are stored in the extension document
-        // let lodarea = this.Document[WidthSym]() * this.Document[HeightSym]() / this.props.ScreenToLocalTransform().Scale / this.props.ScreenToLocalTransform().Scale;
         return <div className={"collectionfreeformview-container"}
             ref={this.createDashEventsTarget}
             onWheel={this.onPointerWheel}//pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined,
@@ -1086,7 +1085,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 width: this.contentScaling ? `${100 / this.contentScaling}%` : "",
                 height: this.contentScaling ? `${100 / this.contentScaling}%` : this.isAnnotationOverlay ? (this.props.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight()
             }}>
-            {!this.Document._LODdisable && !this.props.active() && !this.props.isAnnotationOverlay && !this.props.annotationsKey && this.props.renderDepth > 0 ? // && this.props.CollectionView && lodarea < NumCast(this.Document.LODarea, 100000) ?
+            {!this.Document._LODdisable && !this.props.active() && !this.props.isAnnotationOverlay && !this.props.annotationsKey && this.props.renderDepth > 0 ?
                 this.placeholder : this.marqueeView}
             <CollectionFreeFormOverlayView elements={this.elementFunc} />
         </div>;
diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx
index 671f5b96e..5c449026e 100644
--- a/src/client/views/nodes/ContentFittingDocumentView.tsx
+++ b/src/client/views/nodes/ContentFittingDocumentView.tsx
@@ -1,19 +1,17 @@
 import React = require("react");
-import { action, computed } from "mobx";
+import { computed } from "mobx";
 import { observer } from "mobx-react";
 import "react-table/react-table.css";
 import { Doc, Opt } from "../../../new_fields/Doc";
-import { ComputedField, ScriptField } from "../../../new_fields/ScriptField";
+import { ScriptField } from "../../../new_fields/ScriptField";
 import { NumCast, StrCast } from "../../../new_fields/Types";
+import { TraceMobx } from "../../../new_fields/util";
 import { emptyFunction, returnEmptyString, returnOne } from "../../../Utils";
-import { DragManager } from "../../util/DragManager";
 import { Transform } from "../../util/Transform";
-import { undoBatch } from "../../util/UndoManager";
+import { CollectionView } from "../collections/CollectionView";
 import '../DocumentDecorations.scss';
 import { DocumentView } from "../nodes/DocumentView";
 import "./ContentFittingDocumentView.scss";
-import { CollectionView } from "../collections/CollectionView";
-import { TraceMobx } from "../../../new_fields/util";
 
 interface ContentFittingDocumentViewProps {
     Document?: Doc;
@@ -55,20 +53,6 @@ export class ContentFittingDocumentView extends React.Component<ContentFittingDo
     }
     private contentScaling = () => this.scaling;
 
-    @undoBatch
-    @action
-    drop = (e: Event, de: DragManager.DropEvent) => {
-        const docDragData = de.complete.docDragData;
-        if (docDragData) {
-            this.props.childDocs && this.props.childDocs.map(otherdoc => {
-                const target = Doc.GetProto(otherdoc);
-                target.layout = ComputedField.MakeFunction("this.image_data[0]");
-                target.layout_custom = Doc.MakeDelegate(docDragData.draggedDocuments[0]);
-            });
-            e.stopPropagation();
-        }
-        return true;
-    }
     private PanelWidth = () => this.panelWidth;
     private PanelHeight = () => this.panelHeight;;
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 48df1ea33..e9b4f9f7f 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -92,7 +92,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     private _downY: number = 0;
     private _lastTap: number = 0;
     private _doubleTap = false;
-    private _hitTemplateDrag = false;
     private _mainCont = React.createRef<HTMLDivElement>();
     private _dropDisposer?: DragManager.DragDropDisposer;
     private _gestureEventDisposer?: GestureUtils.GestureEventDisposer;
@@ -196,14 +195,13 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         !this.props.dontRegisterView && DocumentManager.Instance.DocumentViews.splice(DocumentManager.Instance.DocumentViews.indexOf(this), 1);
     }
 
-    startDragging(x: number, y: number, dropAction: dropActionType, applyAsTemplate?: boolean) {
+    startDragging(x: number, y: number, dropAction: dropActionType) {
         if (this._mainCont.current) {
             const dragData = new DragManager.DocumentDragData([this.props.Document]);
             const [left, top] = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint(0, 0);
             dragData.offset = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).transformDirection(x - left, y - top);
             dragData.dropAction = dropAction;
             dragData.moveDocument = this.props.moveDocument;//  this.Document.onDragStart ? undefined : this.props.moveDocument;
-            dragData.applyAsTemplate = applyAsTemplate;
             dragData.dragDivName = this.props.dragDivName;
             this.props.Document.sourceContext = this.props.ContainingCollectionDoc; // bcz: !! shouldn't need this ... use search find the document's context dynamically
             DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, { hideSource: !dropAction && !this.Document.onDragStart });
@@ -257,8 +255,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             let preventDefault = true;
             if (this._doubleTap && this.props.renderDepth && !this.onClickHandler?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click
                 const fullScreenAlias = Doc.MakeAlias(this.props.Document);
-                if (StrCast(fullScreenAlias.layoutKey) !== "layout_custom" && fullScreenAlias.layout_custom !== undefined) {
-                    fullScreenAlias.layoutKey = "layout_custom";
+                if (StrCast(fullScreenAlias.layoutKey) !== "layout_fullScreen" && fullScreenAlias.layout_fullScreen) {
+                    fullScreenAlias.layoutKey = "layout_fullScreen";
                 }
                 this.props.addDocTab(fullScreenAlias, undefined, "inTab");
                 SelectionManager.DeselectAll();
@@ -323,7 +321,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             if (Math.abs(this._downX - touch.clientX) > 3 || Math.abs(this._downY - touch.clientY) > 3) {
                 if (!e.altKey && (!this.topMost || this.Document.onDragStart || this.Document.onClick)) {
                     this.cleanUpInteractions();
-                    this.startDragging(this._downX, this._downY, this.Document.dropAction ? this.Document.dropAction as any : e.ctrlKey || e.altKey ? "alias" : undefined, this._hitTemplateDrag);
+                    this.startDragging(this._downX, this._downY, this.Document.dropAction ? this.Document.dropAction as any : e.ctrlKey || e.altKey ? "alias" : undefined);
                 }
             }
             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
@@ -434,14 +432,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         if (!e.nativeEvent.cancelBubble || this.onClickHandler || this.Document.onDragStart) {
             this._downX = e.clientX;
             this._downY = e.clientY;
-            this._hitTemplateDrag = false;
-            // this whole section needs to move somewhere else.  We're trying to initiate a special "template" drag where
-            // this document is the template and we apply it to whatever we drop it on.
-            for (let element = (e.target as any); element && !this._hitTemplateDrag; element = element.parentElement) {
-                if (element.className && element.className.toString() === "collectionViewBaseChrome-collapse") {
-                    this._hitTemplateDrag = true;
-                }
-            }
             if ((this.active || this.Document.onDragStart || this.onClickHandler) &&
                 !e.ctrlKey &&
                 (e.button === 0 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) &&
@@ -470,7 +460,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 if (!e.altKey && (!this.topMost || this.Document.onDragStart || this.onClickHandler) && (e.buttons === 1 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE))) {
                     document.removeEventListener("pointermove", this.onPointerMove);
                     document.removeEventListener("pointerup", this.onPointerUp);
-                    this.startDragging(this._downX, this._downY, this.props.ContainingCollectionDoc?.childDropAction ? this.props.ContainingCollectionDoc?.childDropAction : this.Document.dropAction ? this.Document.dropAction as any : e.ctrlKey || e.altKey ? "alias" : undefined, this._hitTemplateDrag);
+                    this.startDragging(this._downX, this._downY, this.props.ContainingCollectionDoc?.childDropAction ? this.props.ContainingCollectionDoc?.childDropAction : this.Document.dropAction ? this.Document.dropAction as any : e.ctrlKey || e.altKey ? "alias" : undefined);
                 }
             }
             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
@@ -556,17 +546,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         }
     }
 
-    @undoBatch
-    makeSelBtnClicked = (): void => {
-        if (this.Document.isButton || this.Document.onClick || this.Document.ignoreClick) {
-            this.Document.isButton = false;
-            this.Document.ignoreClick = false;
-            this.Document.onClick = undefined;
-        } else {
-            this.props.Document.isButton = "Selector";
-        }
-    }
-
     @undoBatch
     @action
     drop = async (e: Event, de: DragManager.DropEvent) => {
@@ -578,10 +557,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             DocUtils.MakeLink({ doc: de.complete.annoDragData.annotationDocument }, { doc: this.props.Document, ctx: this.props.ContainingCollectionDoc },
                 `Link from ${StrCast(de.complete.annoDragData.annotationDocument.title)}`);
         }
-        if (de.complete.docDragData && de.complete.docDragData.applyAsTemplate) {
-            Doc.ApplyTemplateTo(de.complete.docDragData.draggedDocuments[0], this.props.Document, "layout_custom", undefined);
-            e.stopPropagation();
-        }
         if (de.complete.linkDragData) {
             e.stopPropagation();
             // const docs = await SearchUtil.Search(`data_l:"${destDoc[Id]}"`, true);
@@ -697,7 +672,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         onClicks.push({ description: "Toggle Detail", event: () => this.Document.onClick = ScriptField.MakeScript(`toggleDetail(this, "${this.props.Document.layoutKey}")`), icon: "window-restore" });
         onClicks.push({ description: this.Document.ignoreClick ? "Select" : "Do Nothing", event: () => this.Document.ignoreClick = !this.Document.ignoreClick, icon: this.Document.ignoreClick ? "unlock" : "lock" });
         onClicks.push({ description: this.Document.isButton || this.Document.onClick ? "Remove Click Behavior" : "Follow Link", event: this.makeBtnClicked, icon: "concierge-bell" });
-        onClicks.push({ description: this.props.Document.isButton ? "Remove Select Link Behavior" : "Select Link", event: this.makeSelBtnClicked, icon: "concierge-bell" });
         onClicks.push({ description: "Edit onClick Script", icon: "edit", event: (obj: any) => ScriptBox.EditButtonScript("On Button Clicked ...", this.props.Document, "onClick", obj.x, obj.y) });
         !existingOnClick && cm.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
 
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 7fbee8881..2fb61f9db 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -250,17 +250,6 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
                     Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new RichTextField(draggedDoc.data.Data, draggedDoc.data.Text);
                     e.stopPropagation();
                 }
-                // apply as template when dragging with Meta
-            } else if (draggedDoc && draggedDoc.type === DocumentType.TEXT && !Doc.AreProtosEqual(draggedDoc, this.props.Document) && de.metaKey) {
-                draggedDoc.isTemplateDoc = true;
-                let newLayout = Doc.Layout(draggedDoc);
-                if (typeof (draggedDoc.layout) === "string") {
-                    newLayout = Doc.MakeDelegate(draggedDoc);
-                    newLayout.layout = StrCast(newLayout.layout).replace(/fieldKey={'[^']*'}/, `fieldKey={'${this.props.fieldKey}'}`);
-                }
-                this.Document.layout_custom = newLayout;
-                this.Document.layoutKey = "layout_custom";
-                e.stopPropagation();
                 // embed document when dragging with a userDropAction or an embedDoc flag set
             } else if (de.complete.docDragData.userDropAction || de.complete.docDragData.embedDoc) {
                 const target = de.complete.docDragData.droppedDocuments[0];
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index c0e102195..364bce7a8 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -92,10 +92,11 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
             } else if (de.altKey || !this.dataDoc[this.props.fieldKey]) {
                 const layoutDoc = de.complete.docDragData?.draggedDocuments[0];
                 const targetField = Doc.LayoutFieldKey(layoutDoc);
-                if (layoutDoc?.[DataSym][targetField] instanceof ImageField) {
-                    this.dataDoc[this.props.fieldKey] = ObjectField.MakeCopy(layoutDoc[DataSym][targetField] as ImageField);
-                    this.dataDoc[this.props.fieldKey + "-nativeWidth"] = NumCast(layoutDoc[DataSym][targetField + "-nativeWidth"]);
-                    this.dataDoc[this.props.fieldKey + "-nativeHeight"] = NumCast(layoutDoc[DataSym][targetField + "-nativeHeight"]);
+                const targetDoc = layoutDoc[DataSym];
+                if (targetDoc[targetField] instanceof ImageField) {
+                    this.dataDoc[this.props.fieldKey] = ObjectField.MakeCopy(targetDoc[targetField] as ImageField);
+                    this.dataDoc[this.props.fieldKey + "-nativeWidth"] = NumCast(targetDoc[targetField + "-nativeWidth"]);
+                    this.dataDoc[this.props.fieldKey + "-nativeHeight"] = NumCast(targeDoc[targetField + "-nativeHeight"]);
                     e.stopPropagation();
                 }
             }
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 2305f2dce..55c0660c0 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -17,7 +17,6 @@ import { intersectRect } from "../Utils";
 import { UndoManager, undoBatch } from "../client/util/UndoManager";
 import { computedFn } from "mobx-utils";
 import { RichTextField } from "./RichTextField";
-import { Script } from "vm";
 
 export namespace Field {
     export function toKeyValueString(doc: Doc, key: string): string {
@@ -112,7 +111,12 @@ export class Doc extends RefField {
             get: getter,
             // getPrototypeOf: (target) => Cast(target[SelfProxy].proto, Doc) || null, // TODO this might be able to replace the proto logic in getter
             has: (target, key) => key in target.__fields,
-            ownKeys: target => Object.keys(target.__allfields),
+            ownKeys: target => {
+                let obj = {} as any;
+                Object.assign(obj, target.___fields);
+                runInAction(() => obj.__LAYOUT__ = target.__LAYOUT__);
+                return Object.keys(obj)
+            },
             getOwnPropertyDescriptor: (target, prop) => {
                 if (prop.toString() === "__LAYOUT__") {
                     return Reflect.getOwnPropertyDescriptor(target, prop);
@@ -140,17 +144,7 @@ export class Doc extends RefField {
     [key: string]: FieldResult;
 
     @serializable(alias("fields", map(autoObject(), { afterDeserialize: afterDocDeserialize })))
-    private get __fields() {
-        return this.___fields;
-    }
-    private get __allfields() {
-        let obj = {} as any;
-        Object.assign(obj, this.___fields);
-        runInAction(() => obj.__LAYOUT__ = this.__LAYOUT__);
-        return obj;
-    }
-
-
+    private get __fields() { return this.___fields; }
     private set __fields(value) {
         this.___fields = value;
         for (const key in value) {
@@ -168,18 +162,19 @@ export class Doc extends RefField {
     private [UpdatingFromServer]: boolean = false;
 
     private [Update] = (diff: any) => {
-        if (this[UpdatingFromServer]) {
-            return;
-        }
-        DocServer.UpdateField(this[Id], diff);
+        !this[UpdatingFromServer] && DocServer.UpdateField(this[Id], diff);
     }
 
     private [Self] = this;
     private [SelfProxy]: any;
     public [WidthSym] = () => NumCast(this[SelfProxy]._width);
     public [HeightSym] = () => NumCast(this[SelfProxy]._height);
-    public get [DataSym]() { return Cast(Doc.Layout(this[SelfProxy]).resolvedDataDoc, Doc, null) || this[SelfProxy]; }
     public get [LayoutSym]() { return this[SelfProxy].__LAYOUT__; }
+    public get [DataSym]() {
+        const self = this[SelfProxy];
+        return self.resolvedDataDoc && !self.isTemplateForField ? self :
+            Doc.GetProto(Cast(Doc.Layout(self).resolvedDataDoc, Doc, null) || self);
+    }
     @computed get __LAYOUT__() {
         const templateLayoutDoc = Cast(Doc.LayoutField(this[SelfProxy]), Doc, null);
         if (templateLayoutDoc) {
@@ -195,12 +190,8 @@ export class Doc extends RefField {
         return undefined;
     }
 
-    [ToScriptString]() {
-        return "invalid";
-    }
-    [ToString]() {
-        return "Doc";
-    }
+    [ToScriptString]() { return "invalid"; }
+    [ToString]() { return "Doc"; }
 
     private [CachedUpdates]: { [key: string]: () => void | Promise<any> } = {};
     public static CurrentUserEmail: string = "";
@@ -287,8 +278,7 @@ export namespace Doc {
 
     export function Get(doc: Doc, key: string, ignoreProto: boolean = false): FieldResult {
         try {
-            const self = doc[Self];
-            return getField(self, key, ignoreProto);
+            return getField(doc[Self], key, ignoreProto);
         } catch  {
             return doc;
         }
@@ -357,13 +347,12 @@ export namespace Doc {
         return r || r2 || r3 || r4;
     }
 
-    // gets the document's prototype or returns the document if it is a prototype
-    export function GetProto(doc: Doc) {
-        return doc && (Doc.GetT(doc, "isPrototype", "boolean", true) ? doc : (doc.proto || doc));
-    }
-    export function GetDataDoc(doc: Doc): Doc {
-        const proto = Doc.GetProto(doc);
-        return proto === doc ? proto : Doc.GetDataDoc(proto);
+    // Gets the data document for the document.  Note: this is mis-named -- it does not specifically
+    // return the doc's proto, but rather recursively searches through the proto inheritance chain 
+    // and returns the document who's proto is undefined or whose proto is marked as a base prototype ('isPrototype').
+    export function GetProto(doc: Doc): Doc {
+        const proto = doc && (Doc.GetT(doc, "isPrototype", "boolean", true) ? doc : (doc.proto || doc));
+        return proto === doc ? proto : Doc.GetProto(proto);
     }
 
     export function allKeys(doc: Doc): string[] {
@@ -443,11 +432,6 @@ export namespace Doc {
         return bounds;
     }
 
-    export function MakeTitled(title: string) {
-        const doc = new Doc();
-        doc.title = title;
-        return doc;
-    }
     export function MakeAlias(doc: Doc, id?: string) {
         const alias = !GetT(doc, "isPrototype", "boolean", true) ? Doc.MakeCopy(doc, undefined, id) : Doc.MakeDelegate(doc, id);
         const layout = Doc.LayoutField(alias);
@@ -488,7 +472,7 @@ export namespace Doc {
         if (templateLayoutDoc.resolvedDataDoc instanceof Promise) {
 
             expandedTemplateLayout = undefined;
-        } else if (templateLayoutDoc.resolvedDataDoc === Doc.GetDataDoc(targetDoc)) {
+        } else if (templateLayoutDoc.resolvedDataDoc === Doc.GetProto(targetDoc)) {
             expandedTemplateLayout = templateLayoutDoc;
         } else if (expandedTemplateLayout === undefined) {
             setTimeout(action(() => {
@@ -497,7 +481,7 @@ export namespace Doc {
                     newLayoutDoc.lockedPosition = true;
                     newLayoutDoc.expandedTemplate = targetDoc;
                     targetDoc[expandedLayoutFieldKey] = newLayoutDoc;
-                    const dataDoc = Doc.GetDataDoc(targetDoc);
+                    const dataDoc = Doc.GetProto(targetDoc);
                     newLayoutDoc.resolvedDataDoc = dataDoc;
                     if (dataDoc[templateField] === undefined && templateLayoutDoc[templateField] instanceof List && Cast(templateLayoutDoc[templateField], listSpec(Doc), []).length) {
                         dataDoc[templateField] = ComputedField.MakeFunction(`ObjectField.MakeCopy(templateLayoutDoc["${templateField}"] as List)`, { templateLayoutDoc: Doc.name }, { templateLayoutDoc: templateLayoutDoc });
@@ -660,10 +644,6 @@ export namespace Doc {
         // assign the template field doc a delegate of any extension document that was previously used to render the template field (since extension doc's carry rendering informatino)
         Doc.Layout(templateField)[metadataFieldKey + "_ext"] = Doc.MakeDelegate(templateField[templateFieldLayoutString?.split("'")[1] + "_ext"] as Doc);
 
-        if (templateField.backgroundColor !== templateDoc?.defaultBackgroundColor) {
-            templateField.defaultBackgroundColor = templateField.backgroundColor;
-        }
-
         return true;
     }
 
@@ -716,26 +696,26 @@ export namespace Doc {
     export function SetUserDoc(doc: Doc) { manager._user_doc = doc; }
     export function IsBrushed(doc: Doc) {
         return computedFn(function IsBrushed(doc: Doc) {
-            return brushManager.BrushedDoc.has(doc) || brushManager.BrushedDoc.has(Doc.GetDataDoc(doc));
+            return brushManager.BrushedDoc.has(doc) || brushManager.BrushedDoc.has(Doc.GetProto(doc));
         })(doc);
     }
     // don't bother memoizing (caching) the result if called from a non-reactive context. (plus this avoids a warning message)
     export function IsBrushedDegreeUnmemoized(doc: Doc) {
-        return brushManager.BrushedDoc.has(doc) ? 2 : brushManager.BrushedDoc.has(Doc.GetDataDoc(doc)) ? 1 : 0;
+        return brushManager.BrushedDoc.has(doc) ? 2 : brushManager.BrushedDoc.has(Doc.GetProto(doc)) ? 1 : 0;
     }
     export function IsBrushedDegree(doc: Doc) {
         return computedFn(function IsBrushDegree(doc: Doc) {
-            return brushManager.BrushedDoc.has(doc) ? 2 : brushManager.BrushedDoc.has(Doc.GetDataDoc(doc)) ? 1 : 0;
+            return Doc.IsBrushedDegreeUnmemoized(doc);
         })(doc);
     }
     export function BrushDoc(doc: Doc) {
         brushManager.BrushedDoc.set(doc, true);
-        brushManager.BrushedDoc.set(Doc.GetDataDoc(doc), true);
+        brushManager.BrushedDoc.set(Doc.GetProto(doc), true);
         return doc;
     }
     export function UnBrushDoc(doc: Doc) {
         brushManager.BrushedDoc.delete(doc);
-        brushManager.BrushedDoc.delete(Doc.GetDataDoc(doc));
+        brushManager.BrushedDoc.delete(Doc.GetProto(doc));
         return doc;
     }
 
@@ -748,14 +728,14 @@ export namespace Doc {
         document.removeEventListener("pointerdown", linkFollowUnhighlight);
     }
 
-    let dt = 0;
+    let _lastDate = 0;
     export function linkFollowHighlight(destDoc: Doc, dataAndDisplayDocs = true) {
         linkFollowUnhighlight();
         Doc.HighlightDoc(destDoc, dataAndDisplayDocs);
         document.removeEventListener("pointerdown", linkFollowUnhighlight);
         document.addEventListener("pointerdown", linkFollowUnhighlight);
-        const x = dt = Date.now();
-        window.setTimeout(() => dt === x && linkFollowUnhighlight(), 5000);
+        const lastDate = _lastDate = Date.now();
+        window.setTimeout(() => _lastDate === lastDate && linkFollowUnhighlight(), 5000);
     }
 
     export class HighlightBrush {
@@ -763,18 +743,18 @@ export namespace Doc {
     }
     const highlightManager = new HighlightBrush();
     export function IsHighlighted(doc: Doc) {
-        return highlightManager.HighlightedDoc.get(doc) || highlightManager.HighlightedDoc.get(Doc.GetDataDoc(doc));
+        return highlightManager.HighlightedDoc.get(doc) || highlightManager.HighlightedDoc.get(Doc.GetProto(doc));
     }
     export function HighlightDoc(doc: Doc, dataAndDisplayDocs = true) {
         runInAction(() => {
             highlightManager.HighlightedDoc.set(doc, true);
-            dataAndDisplayDocs && highlightManager.HighlightedDoc.set(Doc.GetDataDoc(doc), true);
+            dataAndDisplayDocs && highlightManager.HighlightedDoc.set(Doc.GetProto(doc), true);
         });
     }
     export function UnHighlightDoc(doc: Doc) {
         runInAction(() => {
             highlightManager.HighlightedDoc.set(doc, false);
-            highlightManager.HighlightedDoc.set(Doc.GetDataDoc(doc), false);
+            highlightManager.HighlightedDoc.set(Doc.GetProto(doc), false);
         });
     }
     export function UnhighlightAll() {
diff --git a/src/new_fields/documentSchemas.ts b/src/new_fields/documentSchemas.ts
index 81f073855..3cc05d3d5 100644
--- a/src/new_fields/documentSchemas.ts
+++ b/src/new_fields/documentSchemas.ts
@@ -4,12 +4,13 @@ import { Doc } from "./Doc";
 import { DateField } from "./DateField";
 
 export const documentSchema = createSchema({
-    layout: "string",           // this is the native layout string for the document.  templates can be added using other fields and setting layoutKey below (see layout_custom as an example)
+    type: "string",             // enumerated type of document -- should be template-specific (ie, start with an '_')
+    layout: "string",           // this is the native layout string for the document.  templates can be added using other fields and setting layoutKey below
     layoutKey: "string",        // holds the field key for the field that actually holds the current lyoat
-    layout_custom: Doc,         // used to hold a custom layout (there's nothing special about this field .. any field could hold a custom layout that can be selected by setting 'layoutKey')
     title: "string",            // document title (can be on either data document or layout)
     dropAction: "string",       // override specifying what should happen when this document is dropped (can be "alias" or "copy")
     childDropAction: "string",  // specify the override for what should happen when the child of a collection is dragged from it and dropped (can be "alias" or "copy")
+    _autoHeight: "boolean",     // whether the height of the document should be computed automatically based on its contents
     _nativeWidth: "number",     // native width of document which determines how much document contents are scaled when the document's width is set
     _nativeHeight: "number",    // "
     _width: "number",           // width of document in its container's coordinate system
@@ -27,20 +28,18 @@ export const documentSchema = createSchema({
     opacity: "number",          // opacity of document
     creationDate: DateField,    // when the document was created
     links: listSpec(Doc),       // computed (readonly) list of links associated with this document
-    removeDropProperties: listSpec("string"), // properties that should be removed from the alias/copy/etc of this document when it is dropped
     onClick: ScriptField,       // script to run when document is clicked (can be overriden by an onClick prop)
     onPointerDown: ScriptField, // script to run when document is clicked (can be overriden by an onClick prop)
     onPointerUp: ScriptField,   // script to run when document is clicked (can be overriden by an onClick prop)
     onDragStart: ScriptField,   // script to run when document is dragged (without being selected).  the script should return the Doc to be dropped.
     dragFactory: Doc,           // the document that serves as the "template" for the onDragStart script.  ie, to drag out copies of the dragFactory document.
+    removeDropProperties: listSpec("string"), // properties that should be removed from the alias/copy/etc of this document when it is dropped
     ignoreAspect: "boolean",    // whether aspect ratio should be ignored when laying out or manipulating the document
-    autoHeight: "boolean",      // whether the height of the document should be computed automatically based on its contents
     isTemplateForField: "string",// when specifies a field key, then the containing document is a template that renders the specified field
     isBackground: "boolean",    // whether document is a background element and ignores input events (can only selet with marquee)
-    type: "string",             // enumerated type of document
     treeViewOpen: "boolean",    //  flag denoting whether the documents sub-tree (contents) is visible or hidden
     treeViewExpandedView: "string", // name of field whose contents are being displayed as the document's subtree
-    preventTreeViewOpen: "boolean", // ignores the treeViewOpen flag (for allowing a view to not be slaved to other views of the document)
+    treeViewPreventOpen: "boolean", // ignores the treeViewOpen flag (for allowing a view to not be slaved to other views of the document)
     currentTimecode: "number",  // current play back time of a temporal document (video / audio)
     maximizeLocation: "string", // flag for where to place content when following a click interaction (e.g., onRight, inPlace, inTab) 
     lockedPosition: "boolean",  // whether the document can be moved (dragged)
@@ -57,7 +56,6 @@ export const documentSchema = createSchema({
     fitToBox: "boolean",        // whether freeform view contents should be zoomed/panned to fill the area of the document view
     xPadding: "number",         // pixels of padding on left/right of collectionfreeformview contents when fitToBox is set
     yPadding: "number",         // pixels of padding on left/right of collectionfreeformview contents when fitToBox is set
-    LODarea: "number",          // area (width*height) where CollectionFreeFormViews switch from a label to rendering contents
     letterSpacing: "string",
     textTransform: "string"
 });
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 45875d455..32c375bd7 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -139,7 +139,7 @@ export class CurrentUserUtils {
     static setupThumbDoc(userDoc: Doc) {
         if (!userDoc.thumbDoc) {
             userDoc.thumbDoc = Docs.Create.LinearDocument(CurrentUserUtils.setupThumbButtons(userDoc), {
-                _width: 100, _height: 50, ignoreClick: true, lockedPosition: true, _chromeStatus: "disabled", title: "buttons", _autoHeight: true, _yMargin: 5, isExpanded: true, backgroundColor: "white"
+                _width: 100, _height: 50, ignoreClick: true, lockedPosition: true, _chromeStatus: "disabled", title: "buttons", _autoHeight: true, _yMargin: 5, linearViewIsExpanded: true, backgroundColor: "white"
             });
         }
         return userDoc.thumbDoc;
@@ -181,12 +181,12 @@ export class CurrentUserUtils {
         });
 
         doc.documents = Docs.Create.TreeDocument([], {
-            title: "DOCUMENTS", _height: 42, forceActive: true, boxShadow: "0 0", preventTreeViewOpen: true, lockedPosition: true, backgroundColor: "#eeeeee"
+            title: "DOCUMENTS", _height: 42, forceActive: true, boxShadow: "0 0", treeViewPreventOpen: true, lockedPosition: true, backgroundColor: "#eeeeee"
         });
 
         // setup Recently Closed library item
         doc.recentlyClosed = Docs.Create.TreeDocument([], {
-            title: "RECENTLY CLOSED", _height: 75, forceActive: true, boxShadow: "0 0", preventTreeViewOpen: true, lockedPosition: true, backgroundColor: "#eeeeee"
+            title: "RECENTLY CLOSED", _height: 75, forceActive: true, boxShadow: "0 0", treeViewPreventOpen: true, lockedPosition: true, backgroundColor: "#eeeeee"
         });
 
         return Docs.Create.ButtonDocument({
@@ -255,7 +255,7 @@ export class CurrentUserUtils {
             { _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, dropAction: "alias", onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'), dragFactory: slideTemplate, removeDropProperties: new List<string>(["dropAction"]), title: "presentation slide", icon: "sticky-note" });
         doc.expandingButtons = Docs.Create.LinearDocument([doc.undoBtn as Doc, doc.redoBtn as Doc, doc.slidesBtn as Doc], {
             title: "expanding buttons", _gridGap: 5, _xMargin: 5, _yMargin: 5, _height: 42, _width: 100, boxShadow: "0 0",
-            backgroundColor: "black", preventTreeViewOpen: true, forceActive: true, lockedPosition: true,
+            backgroundColor: "black", treeViewPreventOpen: true, forceActive: true, lockedPosition: true,
 
             dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name })
         });
-- 
cgit v1.2.3-70-g09d2


From 5aecc6d75868dcafd0b1bb560ad5a4ad8ab976fa Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 14 Feb 2020 14:25:53 -0500
Subject: cleaned up backgroundColor settings and cluster overrides.  added a
 darkScheme

---
 src/client/documents/Documents.ts                  |  2 +-
 src/client/views/DocumentButtonBar.tsx             |  2 +-
 src/client/views/MainView.tsx                      | 27 ++++++++++++-----
 .../views/collections/CollectionCarouselView.tsx   |  1 +
 .../views/collections/CollectionDockingView.tsx    |  3 +-
 .../views/collections/CollectionSchemaCells.tsx    |  3 +-
 .../views/collections/CollectionStackingView.tsx   |  1 +
 .../views/collections/CollectionTreeView.scss      |  9 ++++--
 .../views/collections/CollectionTreeView.tsx       | 16 ++++++----
 src/client/views/collections/CollectionView.tsx    |  3 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 12 ++++----
 .../collections/collectionFreeForm/MarqueeView.tsx |  3 +-
 .../CollectionMulticolumnView.tsx                  |  1 +
 .../CollectionMultirowView.tsx                     |  1 +
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  7 ++---
 .../views/nodes/ContentFittingDocumentView.tsx     |  5 ++--
 src/client/views/nodes/DocumentView.tsx            | 34 +++++-----------------
 src/client/views/nodes/FieldView.tsx               |  6 ++--
 src/client/views/nodes/FormattedTextBox.tsx        |  3 +-
 19 files changed, 72 insertions(+), 67 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index adcdf260d..53671707e 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -168,7 +168,7 @@ export namespace Docs {
         const TemplateMap: TemplateMap = new Map([
             [DocumentType.TEXT, {
                 layout: { view: FormattedTextBox, dataField: data },
-                options: { _height: 150, backgroundColor: "#f1efeb", defaultBackgroundColor: "#f1efeb" }
+                options: { _height: 150 }
             }],
             [DocumentType.HIST, {
                 layout: { view: HistogramBox, dataField: data },
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index ff0d2718c..2201fe710 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -301,7 +301,7 @@ export class DocumentButtonBar extends React.Component<{ views: (DocumentView |
         const view0 = this.view0;
         const templates: Map<Template, boolean> = new Map();
         Array.from(Object.values(Templates.TemplateList)).map(template =>
-            templates.set(template, this.props.views.reduce((checked, doc) => checked || doc?.getLayoutPropStr("_show" + template.Name) ? true : false, false as boolean)));
+            templates.set(template, this.props.views.reduce((checked, doc) => checked || doc?.props.Document["_show" + template.Name] ? true : false, false as boolean)));
         return !view0 ? (null) : <div title="Customize layout" className="documentButtonBar-linkFlyout" ref={this._dragRef}>
             <Flyout anchorPoint={anchorPoints.LEFT_TOP}
                 content={<TemplateMenu docViews={this.props.views.filter(v => v).map(v => v as DocumentView)} templates={templates} />}>
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 192f3b8fb..ba49a2b53 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -13,7 +13,7 @@ import { Doc, DocListCast, Field, FieldResult, Opt } from '../../new_fields/Doc'
 import { Id } from '../../new_fields/FieldSymbols';
 import { List } from '../../new_fields/List';
 import { listSpec } from '../../new_fields/Schema';
-import { Cast, FieldValue, StrCast } from '../../new_fields/Types';
+import { Cast, FieldValue, StrCast, BoolCast } from '../../new_fields/Types';
 import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils';
 import { emptyFunction, returnEmptyString, returnFalse, returnOne, returnTrue, Utils, emptyPath } from '../../Utils';
 import GoogleAuthenticationManager from '../apis/GoogleAuthenticationManager';
@@ -43,6 +43,7 @@ import SettingsManager from '../util/SettingsManager';
 import { TraceMobx } from '../../new_fields/util';
 import { RadialMenu } from './nodes/RadialMenu';
 import RichTextMenu from '../util/RichTextMenu';
+import { DocumentType } from '../documents/DocumentTypes';
 
 @observer
 export class MainView extends React.Component {
@@ -56,8 +57,9 @@ export class MainView extends React.Component {
     @observable private _panelHeight: number = 0;
     @observable private _flyoutTranslate: boolean = true;
     @observable public flyoutWidth: number = 250;
+    private get darkScheme() { return BoolCast(Cast(this.userDoc.activeWorkspace, Doc, null)?.darkScheme); }
 
-    @computed private get userDoc() { return CurrentUserUtils.UserDocument; }
+    @computed private get userDoc() { return Doc.UserDoc(); }
     @computed private get mainContainer() { return this.userDoc ? FieldValue(Cast(this.userDoc.activeWorkspace, Doc)) : CurrentUserUtils.GuestWorkspace; }
     @computed public get mainFreeform(): Opt<Doc> { return (docs => (docs && docs.length > 1) ? docs[1] : undefined)(DocListCast(this.mainContainer!.data)); }
     @computed public get sidebarButtonsDoc() { return Cast(CurrentUserUtils.UserDocument.sidebarButtons, Doc) as Doc; }
@@ -207,7 +209,6 @@ export class MainView extends React.Component {
             _width: this._panelWidth * .7,
             _height: this._panelHeight,
             title: "Collection " + workspaceCount,
-            backgroundColor: "white"
         };
         const freeformDoc = CurrentUserUtils.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions);
         Doc.AddDocToList(Doc.GetProto(CurrentUserUtils.UserDocument.documents as Doc), "data", freeformDoc);
@@ -273,6 +274,16 @@ export class MainView extends React.Component {
     getPHeight = () => this._panelHeight;
     getContentsHeight = () => this._panelHeight - this._buttonBarHeight;
 
+    childBackgroundColor = (doc: Doc) => {
+        if (this.darkScheme) {
+            return doc.type === DocumentType.TEXT ? "#112423" : "black";
+        }
+        return doc.type === DocumentType.TEXT ? "#f1efeb" :
+            doc.type === DocumentType.COL && doc._viewType === CollectionViewType.Tree ? "lightgray" : "white";
+    }
+    sidebarBackgroundColor = (doc: Doc) => {
+        return this.childBackgroundColor(doc);
+    }
     @computed get mainDocView() {
         return <DocumentView Document={this.mainContainer!}
             DataDoc={undefined}
@@ -281,13 +292,13 @@ export class MainView extends React.Component {
             addDocTab={this.addDocTabFunc}
             pinToPres={emptyFunction}
             onClick={undefined}
+            backgroundColor={this.childBackgroundColor}
             removeDocument={undefined}
             ScreenToLocalTransform={Transform.Identity}
             ContentScaling={returnOne}
             PanelWidth={this.getPWidth}
             PanelHeight={this.getPHeight}
             renderDepth={0}
-            backgroundColor={returnEmptyString}
             focus={emptyFunction}
             parentActive={returnTrue}
             whenActiveChanged={emptyFunction}
@@ -366,7 +377,7 @@ export class MainView extends React.Component {
     mainContainerXf = () => new Transform(0, -this._buttonBarHeight, 1);
 
     @computed get flyout() {
-        const sidebarContent = this.userDoc && this.userDoc.sidebarContainer;
+        const sidebarContent = this.userDoc?.sidebarContainer;
         if (!(sidebarContent instanceof Doc)) {
             return (null);
         }
@@ -388,7 +399,7 @@ export class MainView extends React.Component {
                     PanelHeight={this.getPHeight}
                     renderDepth={0}
                     focus={emptyFunction}
-                    backgroundColor={returnEmptyString}
+                    backgroundColor={this.sidebarBackgroundColor}
                     parentActive={returnTrue}
                     whenActiveChanged={emptyFunction}
                     bringToFront={emptyFunction}
@@ -414,7 +425,7 @@ export class MainView extends React.Component {
                     PanelHeight={this.getContentsHeight}
                     renderDepth={0}
                     focus={emptyFunction}
-                    backgroundColor={returnEmptyString}
+                    backgroundColor={this.sidebarBackgroundColor}
                     parentActive={returnTrue}
                     whenActiveChanged={emptyFunction}
                     bringToFront={emptyFunction}
@@ -437,7 +448,7 @@ export class MainView extends React.Component {
     @computed get mainContent() {
         const sidebar = this.userDoc && this.userDoc.sidebarContainer;
         return !this.userDoc || !(sidebar instanceof Doc) ? (null) : (
-            <div className="mainView-mainContent" >
+            <div className="mainView-mainContent" style={{ color: this.darkScheme ? "lightGray" : "black" }} >
                 <div className="mainView-flyoutContainer" onPointerLeave={this.pointerLeaveDragger} style={{ width: this.flyoutWidth }}>
                     <div className="mainView-libraryHandle" onPointerDown={this.onPointerDown} onPointerOver={this.pointerOverDragger}
                         style={{ backgroundColor: `${StrCast(sidebar.backgroundColor, "lightGray")}` }} >
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index 3c86bd799..226a1c813 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -52,6 +52,7 @@ export class CollectionCarouselView extends CollectionSubView(CarouselDocument)
             <div>
                 <div className="collectionCarouselView-image">
                     <ContentFittingDocumentView {...this.props}
+                        backgroundColor={this.props.backgroundColor}
                         Document={this.childLayoutPairs[index].layout}
                         DataDocument={this.childLayoutPairs[index].data}
                         PanelHeight={this.panelHeight}
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index cb413b3e3..0b7dbea7c 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -599,6 +599,7 @@ interface DockedFrameProps {
     dataDocumentId: FieldId;
     glContainer: any;
     libraryPath: (FieldId[]);
+    backgroundColor?: (doc:Doc) => string| undefined;
     //collectionDockingView: CollectionDockingView
 }
 @observer
@@ -756,7 +757,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
             parentActive={returnTrue}
             whenActiveChanged={emptyFunction}
             focus={emptyFunction}
-            backgroundColor={returnEmptyString}
+            backgroundColor={CollectionDockingView.Instance.props.backgroundColor}
             addDocTab={this.addDocTab}
             pinToPres={DockedFrameRenderer.PinDoc}
             ContainingCollectionView={undefined}
diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx
index caffa7eb1..a3b1b5ec0 100644
--- a/src/client/views/collections/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/CollectionSchemaCells.tsx
@@ -37,7 +37,8 @@ export interface CellProps {
     renderDepth: number;
     addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => boolean;
     pinToPres: (document: Doc) => void;
-    moveDocument: (document: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => boolean;
+    moveDocument: (document: Doc, targetCollection: Doc | undefined, 
+    addDocument: (document: Doc) => boolean) => boolean;
     isFocused: boolean;
     changeFocusedCellByIndex: (row: number, col: number) => void;
     setIsEditing: (isEditing: boolean) => void;
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 7d1f2c284..055035b3e 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -159,6 +159,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         return <ContentFittingDocumentView
             Document={doc}
             DataDocument={dataDoc}
+            backgroundColor={this.props.backgroundColor}
             LayoutDoc={this.props.childLayoutTemplate}
             LibraryPath={this.props.LibraryPath}
             renderDepth={this.props.renderDepth + 1}
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index 2fa6813d7..6ebe81545 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -63,7 +63,9 @@
     font-size: 8pt;
     margin-left: 3px;
     display: none;
-    background: lightgray;
+}
+.collectionTreeView-keyHeader:hover {
+    background: #797777;
 }
 
 .collectionTreeView-subtitle {
@@ -84,9 +86,11 @@
 .treeViewItem-openRight {
     display: none;
     height: 17px;
-    background: gray;
     width: 15px;
 }
+.treeViewItem-openRight:hover {
+    background: #797777;
+}
 
 .treeViewItem-border {
     display: inherit;
@@ -101,7 +105,6 @@
     .treeViewItem-openRight {
         display: inline-block;
         height: 17px;
-        background: #a8a7a7;
         width: 15px;
 
         // display: inline;
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index a160dcc62..e2b3cc425 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -56,6 +56,7 @@ export interface TreeViewProps {
     indentDocument?: () => void;
     outdentDocument?: () => void;
     ScreenToLocalTransform: () => Transform;
+    backgroundColor?: (doc: Doc) => string | undefined;
     outerXf: () => { translateX: number, translateY: number };
     treeViewId: Doc;
     parentKey: string;
@@ -289,7 +290,7 @@ class TreeView extends React.Component<TreeViewProps> {
                 const addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true);
                 contentElement = TreeView.GetChildElements(contents instanceof Doc ? [contents] :
                     DocListCast(contents), this.props.treeViewId, doc, undefined, key, this.props.containingCollection, this.props.prevSibling, addDoc, remDoc, this.move,
-                    this.props.dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active,
+                    this.props.dropAction, this.props.addDocTab, this.props.pinToPres, this.props.backgroundColor, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active,
                     this.props.panelWidth, this.props.ChromeHeight, this.props.renderDepth, this.props.treeViewHideHeaderFields, this.props.treeViewPreventOpen,
                     [...this.props.renderedIds, doc[Id]], this.props.libraryPath, this.props.onCheckedClick);
             } else {
@@ -332,7 +333,7 @@ class TreeView extends React.Component<TreeViewProps> {
                 {!docs ? (null) :
                     TreeView.GetChildElements(docs, this.props.treeViewId, Doc.Layout(this.props.document),
                         this.templateDataDoc, expandKey, this.props.containingCollection, this.props.prevSibling, addDoc, remDoc, this.move,
-                        this.props.dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform,
+                        this.props.dropAction, this.props.addDocTab, this.props.pinToPres, this.props.backgroundColor, this.props.ScreenToLocalTransform,
                         this.props.outerXf, this.props.active, this.props.panelWidth, this.props.ChromeHeight, this.props.renderDepth, this.props.treeViewHideHeaderFields, this.props.treeViewPreventOpen,
                         [...this.props.renderedIds, this.props.document[Id]], this.props.libraryPath, this.props.onCheckedClick)}
             </ul >;
@@ -348,6 +349,7 @@ class TreeView extends React.Component<TreeViewProps> {
                     DataDocument={this.templateDataDoc}
                     LibraryPath={emptyPath}
                     renderDepth={this.props.renderDepth + 1}
+                    backgroundColor={this.props.backgroundColor}
                     fitToBox={this.boundsOfCollectionDocument !== undefined}
                     PanelWidth={this.docWidth}
                     PanelHeight={this.docHeight}
@@ -384,7 +386,7 @@ class TreeView extends React.Component<TreeViewProps> {
     @computed
     get renderBullet() {
         const checked = this.props.document.type === DocumentType.COL ? undefined : this.props.onCheckedClick ? (this.props.document.treeViewChecked ? this.props.document.treeViewChecked : "unchecked") : undefined;
-        return <div className="bullet" title="view inline" onClick={this.bulletClick} style={{ color: StrCast(this.props.document.color, checked === "unchecked" ? "white" : "black"), opacity: 0.4 }}>
+        return <div className="bullet" title="view inline" onClick={this.bulletClick} style={{ color: StrCast(this.props.document.color, checked === "unchecked" ? "white" : "inherit"), opacity: 0.4 }}>
             {<FontAwesomeIcon icon={checked === "check" ? "check" : (checked === "x" ? "times" : checked === "unchecked" ? "square" : !this.treeViewOpen ? (this.childDocs ? "caret-square-right" : "caret-right") : (this.childDocs ? "caret-square-down" : "caret-down"))} />}
         </div>;
     }
@@ -415,7 +417,7 @@ class TreeView extends React.Component<TreeViewProps> {
         return <>
             <div className="docContainer" title="click to edit title" id={`docContainer-${this.props.parentKey}`} ref={reference} onPointerDown={onItemDown}
                 style={{
-                    color: this.props.document.isMinimized ? "red" : "black",
+                    color: this.props.document.isMinimized ? "red" : "inherit",
                     background: Doc.IsHighlighted(this.props.document) ? "orange" : Doc.IsBrushed(this.props.document) ? "#06121212" : "0",
                     fontWeight: this.props.document.searchMatch ? "bold" : undefined,
                     outline: BoolCast(this.props.document.workspaceBrush) ? "dashed 1px #06123232" : undefined,
@@ -456,6 +458,7 @@ class TreeView extends React.Component<TreeViewProps> {
         dropAction: dropActionType,
         addDocTab: (doc: Doc, dataDoc: Doc | undefined, where: string) => boolean,
         pinToPres: (document: Doc) => void,
+        backgroundColor: undefined | ((document: Doc) => string | undefined),
         screenToLocalXf: () => Transform,
         outerXf: () => { translateX: number, translateY: number },
         active: (outsideReaction?: boolean) => boolean,
@@ -561,6 +564,7 @@ class TreeView extends React.Component<TreeViewProps> {
                 renderDepth={renderDepth}
                 deleteDoc={remove}
                 addDocument={addDocument}
+                backgroundColor={backgroundColor}
                 panelWidth={rowWidth}
                 panelHeight={rowHeight}
                 ChromeHeight={ChromeHeight}
@@ -714,7 +718,7 @@ export class CollectionTreeView extends CollectionSubView(Document) {
         const moveDoc = (d: Doc, target: Doc | undefined, addDoc: (doc: Doc) => boolean) => this.props.moveDocument(d, target, addDoc);
         return !this.childDocs ? (null) : (
             <div className="collectionTreeView-dropTarget" id="body"
-                style={{ background: StrCast(this.props.Document.backgroundColor, "lightgray"), paddingTop: `${NumCast(this.props.Document._yMargin, 20)}px` }}
+                style={{ background: this.props.backgroundColor?.(this.props.Document), paddingTop: `${NumCast(this.props.Document._yMargin, 20)}px` }}
                 onContextMenu={this.onContextMenu}
                 onWheel={(e: React.WheelEvent) => this._mainEle && this._mainEle.scrollHeight > this._mainEle.clientHeight && e.stopPropagation()}
                 onDrop={this.onTreeDrop}
@@ -737,7 +741,7 @@ export class CollectionTreeView extends CollectionSubView(Document) {
                 <ul className="no-indent" style={{ width: "max-content" }} >
                     {
                         TreeView.GetChildElements(this.childDocs, this.props.Document, this.props.Document, this.props.DataDoc, this.props.fieldKey, this.props.ContainingCollectionDoc, undefined, addDoc, this.remove,
-                            moveDoc, dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform,
+                            moveDoc, dropAction, this.props.addDocTab, this.props.pinToPres, this.props.backgroundColor, this.props.ScreenToLocalTransform,
                             this.outerXf, this.props.active, this.props.PanelWidth, this.props.ChromeHeight, this.props.renderDepth, () => BoolCast(this.props.Document.treeViewHideHeaderFields),
                             BoolCast(this.props.Document.treeViewPreventOpen), [], this.props.LibraryPath, ScriptCast(this.props.Document.onCheckedClick))
                     }
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 39ce4d6e8..be971eda6 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -38,6 +38,7 @@ import { CollectionViewBaseChrome } from './CollectionViewChromes';
 import { CollectionTimeView } from './CollectionTimeView';
 import { CollectionMultirowView } from './collectionMulticolumn/CollectionMultirowView';
 import { List } from '../../../new_fields/List';
+import { SubCollectionViewProps } from './CollectionSubView';
 export const COLLECTION_BORDER_WIDTH = 2;
 const path = require('path');
 library.add(faTh, faTree, faSquare, faProjectDiagram, faSignature, faThList, faFingerprint, faColumns, faEllipsisV, faImage, faEye as any, faCopy);
@@ -177,7 +178,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
     }
 
     private SubViewHelper = (type: CollectionViewType, renderProps: CollectionRenderProps) => {
-        const props = { ...this.props, ...renderProps, ChromeHeight: this.chromeHeight, CollectionView: this, annotationsKey: "" };
+        const props: SubCollectionViewProps = { ...this.props, ...renderProps, ChromeHeight: this.chromeHeight, CollectionView: this, annotationsKey: "" };
         switch (type) {
             case CollectionViewType.Schema: return (<CollectionSchemaView key="collview" {...props} />);
             case CollectionViewType.Docking: return (<CollectionDockingView key="collview" {...props} />);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index e1854fc2d..969d6b3c8 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -10,7 +10,7 @@ import { Id } from "../../../../new_fields/FieldSymbols";
 import { InkTool } from "../../../../new_fields/InkField";
 import { createSchema, listSpec, makeInterface } from "../../../../new_fields/Schema";
 import { ScriptField } from "../../../../new_fields/ScriptField";
-import { Cast, NumCast, ScriptCast, StrCast } from "../../../../new_fields/Types";
+import { Cast, NumCast, ScriptCast, BoolCast, StrCast } from "../../../../new_fields/Types";
 import { TraceMobx } from "../../../../new_fields/util";
 import { GestureUtils } from "../../../../pen-gestures/GestureUtils";
 import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils";
@@ -206,6 +206,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     }
 
     @undoBatch
+    @action
     updateClusters(useClusters: boolean) {
         this.props.Document.useClusters = useClusters;
         this._clusterSets.length = 0;
@@ -243,7 +244,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 docs.map(doc => this._clusterSets[doc.cluster = NumCast(docFirst.cluster)].push(doc));
             }
             childLayouts.map(child => !this._clusterSets.some((set, i) => Doc.IndexOf(child, set) !== -1 && child.cluster === i) && this.updateCluster(child));
-            childLayouts.map(child => Doc.GetProto(child).clusterStr = child.cluster?.toString());
         }
     }
 
@@ -279,16 +279,16 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     }
 
     getClusterColor = (doc: Doc) => {
-        let clusterColor = "";
+        let clusterColor = this.props.backgroundColor?.(doc);
         const cluster = NumCast(doc.cluster);
         if (this.Document.useClusters) {
             if (this._clusterSets.length <= cluster) {
                 setTimeout(() => this.updateCluster(doc), 0);
             } else {
                 // choose a cluster color from a palette
-                const colors = ["#da42429e", "#31ea318c", "#8c4000", "#4a7ae2c4", "#d809ff", "#ff7601", "#1dffff", "yellow", "#1b8231f2", "#000000ad"];
+                const colors = ["#da42429e", "#31ea318c", "rgba(197, 87, 20, 0.55)", "#4a7ae2c4", "rgba(216, 9, 255, 0.5)", "#ff7601", "#1dffff", "yellow", "rgba(27, 130, 49, 0.55)", "rgba(0, 0, 0, 0.268)"];
                 clusterColor = colors[cluster % colors.length];
-                const set = this._clusterSets[cluster] && this._clusterSets[cluster].filter(s => s.backgroundColor && (s.backgroundColor !== s.defaultBackgroundColor));
+                const set = this._clusterSets[cluster]?.filter(s => s.backgroundColor);
                 // override the cluster color with an explicitly set color on a non-background document.  then override that with an explicitly set color on a background document
                 set && set.filter(s => !s.isBackground).map(s => clusterColor = StrCast(s.backgroundColor));
                 set && set.filter(s => s.isBackground).map(s => clusterColor = StrCast(s.backgroundColor));
@@ -709,6 +709,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
     @computed get libraryPath() { return this.props.LibraryPath ? [...this.props.LibraryPath, this.props.Document] : []; }
     @computed get onChildClickHandler() { return ScriptCast(this.Document.onChildClick); }
+    backgroundHalo = () => BoolCast(this.Document.useClusters);
 
     getChildDocumentViewProps(childLayout: Doc, childData?: Doc): DocumentViewProps {
         return {
@@ -728,6 +729,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             ContainingCollectionDoc: this.props.Document,
             focus: this.focusDocument,
             backgroundColor: this.getClusterColor,
+            backgroundHalo: this.backgroundHalo,
             parentActive: this.props.active,
             bringToFront: this.bringToFront,
             zoomToScale: this.zoomToScale,
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 4b0855635..85cda4ecb 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -308,8 +308,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
             y: bounds.top,
             _panX: 0,
             _panY: 0,
-            backgroundColor: this.props.isAnnotationOverlay ? "#00000015" : "white",
-            defaultBackgroundColor: this.props.isAnnotationOverlay ? "#00000015" : "white",
+            backgroundColor: this.props.isAnnotationOverlay ? "#00000015" : undefined,
             _width: bounds.width,
             _height: bounds.height,
             _LODdisable: true,
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index 7d8de0db4..db5673573 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -207,6 +207,7 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
             {...this.props}
             Document={layout}
             DataDocument={layout.resolvedDataDoc as Doc}
+            backgroundColor={this.props.backgroundColor}
             CollectionDoc={this.props.Document}
             PanelWidth={width}
             PanelHeight={height}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index ec05443e5..630a178cf 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -208,6 +208,7 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument)
             {...this.props}
             Document={layout}
             DataDocument={layout.resolvedDataDoc as Doc}
+            backgroundColor={this.props.backgroundColor}
             CollectionDoc={this.props.Document}
             PanelWidth={width}
             PanelHeight={height}
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 3c27ac9a1..53d17b580 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -61,15 +61,12 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     }
 
     contentScaling = () => this.nativeWidth > 0 && !this.props.Document.ignoreAspect && !this.props.fitToBox ? this.width / this.nativeWidth : 1;
-    clusterColorFunc = (doc: Doc) => this.clusterColor;
     panelWidth = () => (this.dataProvider?.width || this.props.PanelWidth());
     panelHeight = () => (this.dataProvider?.height || this.props.PanelHeight());
     getTransform = (): Transform => this.props.ScreenToLocalTransform()
         .translate(-this.X, -this.Y)
         .scale(1 / this.contentScaling())
 
-    @computed
-    get clusterColor() { return this.props.backgroundColor(this.props.Document); }
     focusDoc = (doc: Doc) => this.props.focus(doc, false);
     render() {
         TraceMobx();
@@ -78,7 +75,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                 boxShadow:
                     this.layoutDoc.opacity === 0 ? undefined :  // if it's not visible, then no shadow
                         this.layoutDoc.z ? `#9c9396  ${StrCast(this.layoutDoc.boxShadow, "10px 10px 0.9vw")}` :  // if it's a floating doc, give it a big shadow
-                            this.clusterColor ? (`${this.clusterColor} ${StrCast(this.layoutDoc.boxShadow, `0vw 0vw ${(this.layoutDoc.isBackground ? 100 : 50) / this.props.ContentScaling()}px`)}`) :  // if it's just in a cluster, make the shadown roughly match the cluster border extent
+                            this.props.backgroundHalo?.() ? (`${this.props.backgroundColor?.(this.props.Document)} ${StrCast(this.layoutDoc.boxShadow, `0vw 0vw ${(this.layoutDoc.isBackground ? 100 : 50) / this.props.ContentScaling()}px`)}`) :  // if it's just in a cluster, make the shadown roughly match the cluster border extent
                                 this.layoutDoc.isBackground ? undefined :  // if it's a background & has a cluster color, make the shadow spread really big
                                     StrCast(this.layoutDoc.boxShadow, ""),
                 borderRadius: StrCast(Doc.Layout(this.layoutDoc).borderRounding),
@@ -96,7 +93,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                 dragDivName={"collectionFreeFormDocumentView-container"}
                 ContentScaling={this.contentScaling}
                 ScreenToLocalTransform={this.getTransform}
-                backgroundColor={this.clusterColorFunc}
+                backgroundColor={this.props.backgroundColor}
                 PanelWidth={this.panelWidth}
                 PanelHeight={this.panelHeight}
             /> : <ContentFittingDocumentView {...this.props}
diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx
index 5c449026e..387da88f5 100644
--- a/src/client/views/nodes/ContentFittingDocumentView.tsx
+++ b/src/client/views/nodes/ContentFittingDocumentView.tsx
@@ -6,7 +6,7 @@ import { Doc, Opt } from "../../../new_fields/Doc";
 import { ScriptField } from "../../../new_fields/ScriptField";
 import { NumCast, StrCast } from "../../../new_fields/Types";
 import { TraceMobx } from "../../../new_fields/util";
-import { emptyFunction, returnEmptyString, returnOne } from "../../../Utils";
+import { emptyFunction, returnOne } from "../../../Utils";
 import { Transform } from "../../util/Transform";
 import { CollectionView } from "../collections/CollectionView";
 import '../DocumentDecorations.scss';
@@ -27,6 +27,7 @@ interface ContentFittingDocumentViewProps {
     CollectionView?: CollectionView;
     CollectionDoc?: Doc;
     onClick?: ScriptField;
+    backgroundColor?: (doc: Doc) => string | undefined;
     getTransform: () => Transform;
     addDocument?: (document: Doc) => boolean;
     moveDocument?: (document: Doc, target: Doc | undefined, addDoc: ((doc: Doc) => boolean)) => boolean;
@@ -86,6 +87,7 @@ export class ContentFittingDocumentView extends React.Component<ContentFittingDo
                         LibraryPath={this.props.LibraryPath}
                         fitToBox={this.props.fitToBox}
                         onClick={this.props.onClick}
+                        backgroundColor={this.props.backgroundColor}
                         addDocument={this.props.addDocument}
                         removeDocument={this.props.removeDocument}
                         moveDocument={this.props.moveDocument}
@@ -101,7 +103,6 @@ export class ContentFittingDocumentView extends React.Component<ContentFittingDo
                         PanelWidth={this.PanelWidth}
                         PanelHeight={this.PanelHeight}
                         focus={this.props.focus || emptyFunction}
-                        backgroundColor={returnEmptyString}
                         bringToFront={emptyFunction}
                         dontRegisterView={this.props.dontRegisterView}
                         zoomToScale={emptyFunction}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index ef065f51d..52928e8cb 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -77,7 +77,8 @@ export interface DocumentViewProps {
     addDocTab: (doc: Doc, dataDoc: Doc | undefined, where: string, libraryPath?: Doc[]) => boolean;
     pinToPres: (document: Doc) => void;
     zoomToScale: (scale: number) => void;
-    backgroundColor: (doc: Doc) => string | undefined;
+    backgroundHalo?: () => boolean;
+    backgroundColor?: (doc: Doc) => string | undefined;
     getScale: () => number;
     animateBetweenIcon?: (maximize: boolean, target: number[]) => void;
     ChromeHeight?: () => number;
@@ -295,12 +296,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             this._downX = touch.clientX;
             this._downY = touch.clientY;
             if (!e.nativeEvent.cancelBubble) {
-                this._hitTemplateDrag = false;
-                for (let element = (e.target as any); element && !this._hitTemplateDrag; element = element.parentElement) {
-                    if (element.className && element.className.toString() === "collectionViewBaseChrome-collapse") {
-                        this._hitTemplateDrag = true;
-                    }
-                }
                 if ((this.active || this.Document.onDragStart || this.Document.onClick) && !e.ctrlKey && !this.Document.lockedPosition && !this.Document.inOverlay) e.stopPropagation();
                 this.removeMoveListeners();
                 this.addMoveListeners();
@@ -778,14 +773,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         });
     }
 
-    // does Document set a layout prop 
-    setsLayoutProp = (prop: string) => this.props.Document[prop] !== this.props.Document["default" + prop[0].toUpperCase() + prop.slice(1)] && this.props.Document["default" + prop[0].toUpperCase() + prop.slice(1)];
-    // get the a layout prop by first choosing the prop from Document, then falling back to the layout doc otherwise.
-    getLayoutPropStr = (prop: string) => {
-        return StrCast(this.setsLayoutProp(prop) ? this.props.Document[prop] : Cast(this.layoutDoc?.expandedTemplate, Doc, null)?.[prop] || this.layoutDoc[prop]);
-    }
-    getLayoutPropNum = (prop: string) => NumCast(this.setsLayoutProp(prop) ? this.props.Document[prop] : this.layoutDoc[prop]);
-
     isSelected = (outsideReaction?: boolean) => SelectionManager.IsSelected(this, outsideReaction);
     select = (ctrlPressed: boolean) => { SelectionManager.SelectDoc(this, ctrlPressed); };
 
@@ -848,9 +835,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
     @computed get innards() {
         TraceMobx();
-        const showTitle = StrCast(this.getLayoutPropStr("_showTitle"));
-        const showTitleHover = StrCast(this.getLayoutPropStr("_showTitleHover"));
-        const showCaption = this.getLayoutPropStr("_showCaption");
+        const showTitle = StrCast(this.layoutDoc._showTitle);
+        const showTitleHover = StrCast(this.layoutDoc._showTitleHover);
+        const showCaption = StrCast(this.layoutDoc._showCaption);
         const showTextTitle = showTitle && (StrCast(this.layoutDoc.layout).indexOf("PresBox") !== -1 || StrCast(this.layoutDoc.layout).indexOf("FormattedTextBox") !== -1) ? showTitle : undefined;
         const searchHighlight = (!this.Document.searchFields ? (null) :
             <div className="documentView-searchHighlight">
@@ -921,14 +908,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
     render() {
         if (!(this.props.Document instanceof Doc)) return (null);
-        const colorSet = this.setsLayoutProp("backgroundColor");
-        const clusterCol = this.props.ContainingCollectionDoc && this.props.ContainingCollectionDoc.clusterOverridesDefaultBackground;
-        const backgroundColor = (clusterCol && !colorSet) ?
-            this.props.backgroundColor(this.Document) || StrCast(this.layoutDoc.backgroundColor) :
-            StrCast(this.layoutDoc.backgroundColor) || this.props.backgroundColor(this.Document);
-
+        const backgroundColor = StrCast(this.layoutDoc.backgroundColor) || this.props.backgroundColor?.(this.Document);
         const fullDegree = Doc.isBrushedHighlightedDegree(this.props.Document);
-        const borderRounding = this.getLayoutPropStr("borderRounding");
+        const borderRounding = this.layoutDoc.borderRounding;
         const localScale = fullDegree;
 
         const highlightColors = ["transparent", "maroon", "maroon", "yellow", "magenta", "cyan", "orange"];
@@ -943,7 +925,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 transform: this._animate ? `scale(${this._animate})` : undefined,
                 transition: !this._animate ? StrCast(this.Document.transition) : this._animate < 1 ? "transform 0.5s ease-in" : "transform 0.5s ease-out",
                 pointerEvents: this.ignorePointerEvents ? "none" : "all",
-                color: StrCast(this.Document.color),
+                color: StrCast(this.Document.color, "inherit"),
                 outline: highlighting && !borderRounding ? `${highlightColors[fullDegree]} ${highlightStyles[fullDegree]} ${localScale}px` : "solid 0px",
                 border: highlighting && borderRounding ? `${highlightStyles[fullDegree]} ${highlightColors[fullDegree]} ${localScale}px` : undefined,
                 boxShadow: this.props.Document.isTemplateForField ? "black 0.2vw 0.2vw 0.8vw" : undefined,
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 8250f41f3..00f00dd52 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -3,14 +3,13 @@ import { computed } from "mobx";
 import { observer } from "mobx-react";
 import { DateField } from "../../../new_fields/DateField";
 import { Doc, FieldResult, Opt } from "../../../new_fields/Doc";
-import { IconField } from "../../../new_fields/IconField";
 import { List } from "../../../new_fields/List";
-import { AudioField, ImageField, VideoField } from "../../../new_fields/URLField";
+import { ScriptField } from "../../../new_fields/ScriptField";
+import { AudioField, VideoField } from "../../../new_fields/URLField";
 import { Transform } from "../../util/Transform";
 import { CollectionView } from "../collections/CollectionView";
 import { AudioBox } from "./AudioBox";
 import { VideoBox } from "./VideoBox";
-import { ScriptField } from "../../../new_fields/ScriptField";
 
 //
 // these properties get assigned through the render() method of the DocumentView when it creates this node.
@@ -34,6 +33,7 @@ export interface FieldViewProps {
     pinToPres: (document: Doc) => void;
     removeDocument?: (document: Doc) => boolean;
     moveDocument?: (document: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => boolean;
+    backgroundColor?: (document: Doc) => string | undefined;
     ScreenToLocalTransform: () => Transform;
     active: (outsideReaction?: boolean) => boolean;
     whenActiveChanged: (isActive: boolean) => void;
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 2fb61f9db..3c64b3974 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -186,7 +186,6 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
             tsel.marks().filter(m => m.type === this._editorView!.state.schema.marks.user_mark).map(m => AudioBox.SetScrubTime(Math.max(0, m.attrs.modified * 5000 - 1000)));
             this._applyingChange = true;
             if (!this.props.Document._textTemplate || Doc.GetProto(this.props.Document) === this.dataDoc) {
-                this.dataDoc[this.props.fieldKey + "-lastModified"] && (this.dataDoc[this.props.fieldKey + "-backgroundColor"] = "lightGray");
                 this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()));
                 this.dataDoc[this.props.fieldKey] = new RichTextField(JSON.stringify(state.toJSON()), state.doc.textBetween(0, state.doc.content.size, "\n\n"));
             }
@@ -1075,7 +1074,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
             <div className={`formattedTextBox-cont`} ref={this._ref}
                 style={{
                     height: this.layoutDoc._autoHeight ? "max-content" : undefined,
-                    background: this.props.hideOnLeave ? "rgba(0,0,0 ,0.4)" : undefined,
+                    background: this.props.hideOnLeave ? "rgba(0,0,0 ,0.4)" : StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"]),
                     opacity: this.props.hideOnLeave ? (this._entered ? 1 : 0.1) : 1,
                     color: this.props.hideOnLeave ? "white" : "inherit",
                     pointerEvents: interactive ? "none" : "all",
-- 
cgit v1.2.3-70-g09d2


From b42e1e1e2da941955d7751b6003f18fecd5f2f8d Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Sun, 16 Feb 2020 12:22:10 -0500
Subject: collection sub view and google authentication manager cleanup,
 deleteAssets route added

---
 src/client/apis/GoogleAuthenticationManager.tsx    |  46 ++--
 .../views/collections/CollectionCarouselView.tsx   |  28 +--
 .../views/collections/CollectionLinearView.tsx     |   2 +-
 .../collections/CollectionMasonryViewFieldRow.tsx  |   2 +-
 .../views/collections/CollectionSchemaView.tsx     |   7 +-
 .../views/collections/CollectionStackingView.tsx   |  12 +-
 .../CollectionStackingViewFieldColumn.tsx          |   2 +-
 src/client/views/collections/CollectionSubView.tsx | 264 +++++++++++----------
 .../views/collections/CollectionTreeView.tsx       |   6 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  10 +-
 .../CollectionMulticolumnView.tsx                  |   6 +-
 .../CollectionMultirowView.tsx                     |   6 +-
 src/server/ApiManagers/DeleteManager.ts            |  31 +--
 13 files changed, 213 insertions(+), 209 deletions(-)

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

diff --git a/src/client/apis/GoogleAuthenticationManager.tsx b/src/client/apis/GoogleAuthenticationManager.tsx
index ce1277667..417dc3c3b 100644
--- a/src/client/apis/GoogleAuthenticationManager.tsx
+++ b/src/client/apis/GoogleAuthenticationManager.tsx
@@ -12,8 +12,8 @@ const prompt = "Paste authorization code here...";
 @observer
 export default class GoogleAuthenticationManager extends React.Component<{}> {
     public static Instance: GoogleAuthenticationManager;
-    @observable private openState = false;
     private authenticationLink: Opt<string> = undefined;
+    @observable private openState = false;
     @observable private authenticationCode: Opt<string> = undefined;
     @observable private clickedState = false;
     @observable private success: Opt<boolean> = undefined;
@@ -39,24 +39,18 @@ export default class GoogleAuthenticationManager extends React.Component<{}> {
                 const disposer = reaction(
                     () => this.authenticationCode,
                     async authenticationCode => {
-                        if (!authenticationCode) {
-                            return;
+                        if (authenticationCode) {
+                            disposer();
+                            const { access_token, avatar, name } = await Networking.PostToServer("/writeGoogleAccessToken", { authenticationCode });
+                            runInAction(() => {
+                                this.avatar = avatar;
+                                this.username = name;
+                                this.hasBeenClicked = false;
+                                this.success = false;
+                            });
+                            this.beginFadeout();
+                            resolve(access_token);
                         }
-                        const { access_token, avatar, name } = await Networking.PostToServer(
-                            "/writeGoogleAccessToken",
-                            { authenticationCode }
-                        );
-                        runInAction(() => {
-                            this.avatar = avatar;
-                            this.username = name;
-                        });
-                        this.beginFadeout();
-                        disposer();
-                        resolve(access_token);
-                        action(() => {
-                            this.hasBeenClicked = false;
-                            this.success = false;
-                        });
                     }
                 );
             });
@@ -86,26 +80,20 @@ export default class GoogleAuthenticationManager extends React.Component<{}> {
         GoogleAuthenticationManager.Instance = this;
     }
 
-    private handleClick = () => {
-        window.open(this.authenticationLink);
-        setTimeout(() => this.hasBeenClicked = true, 500);
-    }
-
-    private handlePaste = action((e: React.ChangeEvent<HTMLInputElement>) => {
-        this.authenticationCode = e.currentTarget.value;
-    });
-
     private get renderPrompt() {
         return (
             <div className={'authorize-container'}>
                 {this.displayLauncher ? <button
                     className={"dispatch"}
-                    onClick={this.handleClick}
+                    onClick={() => {
+                        window.open(this.authenticationLink);
+                        setTimeout(() => this.hasBeenClicked = true, 500);
+                    }}
                     style={{ marginBottom: this.clickedState ? 15 : 0 }}
                 >Authorize a Google account...</button> : (null)}
                 {this.clickedState ? <input
                     className={'paste-target'}
-                    onChange={this.handlePaste}
+                    onChange={action(e => this.authenticationCode = e.currentTarget.value)}
                     placeholder={prompt}
                 /> : (null)}
                 {this.avatar ? <img
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index 8167416c6..a0cb1fe19 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -27,7 +27,7 @@ export class CollectionCarouselView extends CollectionSubView(CarouselDocument)
     protected createDashEventsTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
         this._dropDisposer?.();
         if (ele) {
-            this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
+            this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this));
         }
     }
 
@@ -41,21 +41,21 @@ export class CollectionCarouselView extends CollectionSubView(CarouselDocument)
     }
 
     panelHeight = () => this.props.PanelHeight() - 50;
-    @computed get content() { 
+    @computed get content() {
         const index = NumCast(this.layoutDoc._itemIndex);
         return !(this.childLayoutPairs?.[index]?.layout instanceof Doc) ? (null) :
-        <>
-            <div className="collectionCarouselView-image" key="image">
-                <ContentFittingDocumentView {...this.props}
-                    Document={this.childLayoutPairs[index].layout}
-                    DataDocument={this.childLayoutPairs[index].data}
-                    PanelHeight={this.panelHeight}
-                    getTransform={this.props.ScreenToLocalTransform} />
-            </div>
-            <div className="collectionCarouselView-caption" key="caption" style={{ background: this.props.backgroundColor?.(this.props.Document) }}>
-                <FormattedTextBox key={index} {...this.props} Document={this.childLayoutPairs[index].layout} DataDoc={undefined} fieldKey={"caption"}></FormattedTextBox>
-            </div>
-        </>
+            <>
+                <div className="collectionCarouselView-image" key="image">
+                    <ContentFittingDocumentView {...this.props}
+                        Document={this.childLayoutPairs[index].layout}
+                        DataDocument={this.childLayoutPairs[index].data}
+                        PanelHeight={this.panelHeight}
+                        getTransform={this.props.ScreenToLocalTransform} />
+                </div>
+                <div className="collectionCarouselView-caption" key="caption" style={{ background: this.props.backgroundColor?.(this.props.Document) }}>
+                    <FormattedTextBox key={index} {...this.props} Document={this.childLayoutPairs[index].layout} DataDoc={undefined} fieldKey={"caption"}></FormattedTextBox>
+                </div>
+            </>;
     }
     @computed get buttons() {
         return <>
diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx
index 7eb316cf0..9bbc9f1b6 100644
--- a/src/client/views/collections/CollectionLinearView.tsx
+++ b/src/client/views/collections/CollectionLinearView.tsx
@@ -67,7 +67,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
     protected createDashEventsTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
         this._dropDisposer && this._dropDisposer();
         if (ele) {
-            this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
+            this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this));
         }
     }
 
diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
index e25a2f5eb..f3d512a97 100644
--- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
+++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
@@ -80,7 +80,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
             const key = StrCast(this.props.parent.props.Document.sectionFilter);
             const castedValue = this.getValue(this._heading);
             de.complete.docDragData.droppedDocuments.forEach(d => d[key] = castedValue);
-            this.props.parent.drop(e, de);
+            this.props.parent.onInternalDrop(e, de);
             e.stopPropagation();
         }
     });
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index fa8be5177..c422c38f1 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -14,7 +14,6 @@ import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
 import { ComputedField } from "../../../new_fields/ScriptField";
 import { Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types";
 import { Docs, DocumentOptions } from "../../documents/Documents";
-import { DocumentType } from "../../documents/DocumentTypes";
 import { Gateway } from "../../northstar/manager/Gateway";
 import { CompileScript, Transformer, ts } from "../../util/Scripting";
 import { Transform } from "../../util/Transform";
@@ -175,7 +174,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
             moveDocument={this.props.moveDocument}
             ScreenToLocalTransform={this.props.ScreenToLocalTransform}
             active={this.props.active}
-            onDrop={this.onDrop}
+            onDrop={this.onExternalDrop}
             addDocTab={this.props.addDocTab}
             pinToPres={this.props.pinToPres}
             isSelected={this.props.isSelected}
@@ -199,7 +198,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
 
     render() {
         return <div className="collectionSchemaView-container">
-            <div className="collectionSchemaView-tableContainer" onPointerDown={this.onPointerDown} onWheel={e => this.props.active(true) && e.stopPropagation()} onDrop={e => this.onDrop(e, {})} ref={this.createTarget}>
+            <div className="collectionSchemaView-tableContainer" onPointerDown={this.onPointerDown} onWheel={e => this.props.active(true) && e.stopPropagation()} onDrop={e => this.onExternalDrop(e, {})} ref={this.createTarget}>
                 {this.schemaTable}
             </div>
             {this.dividerDragger}
@@ -692,7 +691,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
     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
             // ContextMenu.Instance.addItem({ description: "Make DB", event: this.makeDB, icon: "table" });
-            ContextMenu.Instance.addItem({ description: "Toggle text wrapping", event: this.toggleTextwrap, icon: "table" })
+            ContextMenu.Instance.addItem({ description: "Toggle text wrapping", event: this.toggleTextwrap, icon: "table" });
         }
     }
 
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 055035b3e..a9cefae6a 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -189,7 +189,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     }
     getDocHeight(d?: Doc) {
         if (!d) return 0;
-        let layoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.());
+        const layoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.());
         const nw = NumCast(layoutDoc._nativeWidth);
         const nh = NumCast(layoutDoc._nativeHeight);
         let wid = this.columnWidth / (this.isStackingView ? this.numGroupColumns : 1);
@@ -234,7 +234,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
 
     @undoBatch
     @action
-    drop = (e: Event, de: DragManager.DropEvent) => {
+    onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
         const where = [de.x, de.y];
         let targInd = -1;
         let plusOne = 0;
@@ -248,7 +248,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
                     plusOne = where[axis] > (pos[axis] + pos1[axis]) / 2 ? 1 : 0;
                 }
             });
-            if (super.drop(e, de)) {
+            if (super.onInternalDrop(e, de)) {
                 const newDoc = de.complete.docDragData.droppedDocuments[0];
                 const docs = this.childDocList;
                 if (docs) {
@@ -264,7 +264,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     }
     @undoBatch
     @action
-    onDrop = async (e: React.DragEvent): Promise<void> => {
+    onExternalDrop = async (e: React.DragEvent): Promise<void> => {
         const where = [e.clientX, e.clientY];
         let targInd = -1;
         this._docXfs.map((cd, i) => {
@@ -274,7 +274,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
                 targInd = i;
             }
         });
-        super.onDrop(e, {}, () => {
+        super.onExternalDrop(e, {}, () => {
             if (targInd !== -1) {
                 const newDoc = this.childDocs[this.childDocs.length - 1];
                 const docs = this.childDocList;
@@ -405,7 +405,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
                         transformOrigin: "top left",
                     }}
                     onScroll={action((e: React.UIEvent<HTMLDivElement>) => this._scroll = e.currentTarget.scrollTop)}
-                    onDrop={this.onDrop.bind(this)}
+                    onDrop={this.onExternalDrop.bind(this)}
                     onContextMenu={this.onContextMenu}
                     onWheel={e => this.props.active() && e.stopPropagation()} >
                     {this.renderedSections}
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 2ff477c57..87c35679f 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -71,7 +71,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
             else {
                 de.complete.docDragData.droppedDocuments.forEach(d => d[key] = undefined);
             }
-            this.props.parent.drop(e, de);
+            this.props.parent.onInternalDrop(e, de);
             e.stopPropagation();
         }
     });
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index e80e1c802..042385dcd 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -57,7 +57,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
             this.gestureDisposer?.();
             this.multiTouchDisposer?.();
             if (ele) {
-                this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
+                this.dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this));
                 this.gestureDisposer = GestureUtils.MakeGestureTarget(ele, this.onGesture.bind(this));
                 this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(ele, this.onTouchStart.bind(this));
             }
@@ -156,7 +156,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
 
         @undoBatch
         @action
-        protected drop(e: Event, de: DragManager.DropEvent): boolean {
+        protected onInternalDrop(e: Event, de: DragManager.DropEvent): boolean {
             const docDragData = de.complete.docDragData;
             (this.props.Document.dropConverter instanceof ScriptField) &&
                 this.props.Document.dropConverter.script.run({ dragData: docDragData }); /// bcz: check this 
@@ -195,158 +195,172 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
 
         @undoBatch
         @action
-        protected async onDrop(e: React.DragEvent, options: DocumentOptions, completed?: () => void) {
+        protected async onExternalDrop(e: React.DragEvent, options: DocumentOptions, completed?: () => void) {
             if (e.ctrlKey) {
                 e.stopPropagation(); // bcz: this is a hack to stop propagation when dropping an image on a text document with shift+ctrl
                 return;
             }
-            const html = e.dataTransfer.getData("text/html");
-            const text = e.dataTransfer.getData("text/plain");
+
+            const { dataTransfer } = e;
+            const html = dataTransfer.getData("text/html");
+            const text = dataTransfer.getData("text/plain");
 
             if (text && text.startsWith("<div")) {
                 return;
             }
+
             e.stopPropagation();
             e.preventDefault();
-
-            if (html && FormattedTextBox.IsFragment(html)) {
-                const href = FormattedTextBox.GetHref(html);
-                if (href) {
-                    const docid = FormattedTextBox.GetDocFromUrl(href);
-                    if (docid) { // prosemirror text containing link to dash document
-                        DocServer.GetRefField(docid).then(f => {
-                            if (f instanceof Doc) {
-                                if (options.x || options.y) { f.x = options.x; f.y = options.y; } // should be in CollectionFreeFormView
-                                (f instanceof Doc) && this.props.addDocument(f);
-                            }
-                        });
-                    } else {
-                        this.props.addDocument && this.props.addDocument(Docs.Create.WebDocument(href, { ...options, title: href }));
-                    }
-                } else if (text) {
-                    this.props.addDocument && this.props.addDocument(Docs.Create.TextDocument(text, { ...options, _width: 100, _height: 25 }));
-                }
+            const { addDocument } = this.props;
+            if (!addDocument) {
+                alert("this.props.addDocument does not exist. Aborting drop operation.");
                 return;
             }
-            if (html && !html.startsWith("<a")) {
-                const tags = html.split("<");
-                if (tags[0] === "") tags.splice(0, 1);
-                const img = tags[0].startsWith("img") ? tags[0] : tags.length > 1 && tags[1].startsWith("img") ? tags[1] : "";
-                if (img) {
-                    const split = img.split("src=\"")[1].split("\"")[0];
-                    let source = split;
-                    if (split.startsWith("data:image") && split.includes("base64")) {
-                        const [{ accessPaths }] = await Networking.PostToServer("/uploadRemoteImage", { sources: [split] });
-                        source = Utils.prepend(accessPaths.agnostic.client);
+
+            if (html) {
+                if (FormattedTextBox.IsFragment(html)) {
+                    const href = FormattedTextBox.GetHref(html);
+                    if (href) {
+                        const docid = FormattedTextBox.GetDocFromUrl(href);
+                        if (docid) { // prosemirror text containing link to dash document
+                            DocServer.GetRefField(docid).then(f => {
+                                if (f instanceof Doc) {
+                                    if (options.x || options.y) { f.x = options.x; f.y = options.y; } // should be in CollectionFreeFormView
+                                    (f instanceof Doc) && addDocument(f);
+                                }
+                            });
+                        } else {
+                            addDocument(Docs.Create.WebDocument(href, { ...options, title: href }));
+                        }
+                    } else if (text) {
+                        addDocument(Docs.Create.TextDocument(text, { ...options, _width: 100, _height: 25 }));
                     }
-                    const doc = Docs.Create.ImageDocument(source, { ...options, _width: 300 });
-                    ImageUtils.ExtractExif(doc);
-                    this.props.addDocument(doc);
                     return;
-                } else {
-                    const path = window.location.origin + "/doc/";
-                    if (text.startsWith(path)) {
-                        const docid = text.replace(Utils.prepend("/doc/"), "").split("?")[0];
-                        DocServer.GetRefField(docid).then(f => {
-                            if (f instanceof Doc) {
-                                if (options.x || options.y) { f.x = options.x; f.y = options.y; } // should be in CollectionFreeFormView
-                                (f instanceof Doc) && this.props.addDocument(f);
-                            }
-                        });
+                }
+                if (!html.startsWith("<a")) {
+                    const tags = html.split("<");
+                    if (tags[0] === "") tags.splice(0, 1);
+                    const img = tags[0].startsWith("img") ? tags[0] : tags.length > 1 && tags[1].startsWith("img") ? tags[1] : "";
+                    if (img) {
+                        const split = img.split("src=\"")[1].split("\"")[0];
+                        let source = split;
+                        if (split.startsWith("data:image") && split.includes("base64")) {
+                            const [{ accessPaths }] = await Networking.PostToServer("/uploadRemoteImage", { sources: [split] });
+                            source = Utils.prepend(accessPaths.agnostic.client);
+                        }
+                        const doc = Docs.Create.ImageDocument(source, { ...options, _width: 300 });
+                        ImageUtils.ExtractExif(doc);
+                        addDocument(doc);
+                        return;
                     } else {
-                        const htmlDoc = Docs.Create.HtmlDocument(html, { ...options, title: "-web page-", _width: 300, _height: 300 });
-                        Doc.GetProto(htmlDoc)["data-text"] = text;
-                        this.props.addDocument(htmlDoc);
+                        const path = window.location.origin + "/doc/";
+                        if (text.startsWith(path)) {
+                            const docid = text.replace(Utils.prepend("/doc/"), "").split("?")[0];
+                            DocServer.GetRefField(docid).then(f => {
+                                if (f instanceof Doc) {
+                                    if (options.x || options.y) { f.x = options.x; f.y = options.y; } // should be in CollectionFreeFormView
+                                    (f instanceof Doc) && this.props.addDocument(f);
+                                }
+                            });
+                        } else {
+                            const htmlDoc = Docs.Create.HtmlDocument(html, { ...options, title: "-web page-", _width: 300, _height: 300 });
+                            Doc.GetProto(htmlDoc)["data-text"] = text;
+                            this.props.addDocument(htmlDoc);
+                        }
+                        return;
                     }
-                    return;
                 }
             }
-            if (text && text.indexOf("www.youtube.com/watch") !== -1) {
-                const url = text.replace("youtube.com/watch?v=", "youtube.com/embed/");
-                this.props.addDocument(Docs.Create.VideoDocument(url, { ...options, title: url, _width: 400, _height: 315, _nativeWidth: 600, _nativeHeight: 472.5 }));
-                return;
-            }
-            let matches: RegExpExecArray | null;
-            if ((matches = /(https:\/\/)?docs\.google\.com\/document\/d\/([^\\]+)\/edit/g.exec(text)) !== null) {
-                const newBox = Docs.Create.TextDocument("", { ...options, _width: 400, _height: 200, title: "Awaiting title from Google Docs..." });
-                const proto = newBox.proto!;
-                const documentId = matches[2];
-                proto[GoogleRef] = documentId;
-                proto.data = "Please select this document and then click on its pull button to load its contents from from Google Docs...";
-                proto.backgroundColor = "#eeeeff";
-                this.props.addDocument(newBox);
-                // const parent = Docs.Create.StackingDocument([newBox], { title: `Google Doc Import (${documentId})` });
-                // CollectionDockingView.Instance.AddRightSplit(parent, undefined);
-                // proto.height = parent[HeightSym]();
-                return;
-            }
-            if ((matches = /(https:\/\/)?photos\.google\.com\/(u\/3\/)?album\/([^\\]+)/g.exec(text)) !== null) {
-                const albums = await GooglePhotos.Transactions.ListAlbums();
-                const albumId = matches[3];
-                const mediaItems = await GooglePhotos.Query.AlbumSearch(albumId);
-                console.log(mediaItems);
-                return;
+
+            if (text) {
+                if (text.includes("www.youtube.com/watch")) {
+                    const url = text.replace("youtube.com/watch?v=", "youtube.com/embed/");
+                    addDocument(Docs.Create.VideoDocument(url, {
+                        ...options,
+                        title: url,
+                        _width: 400,
+                        _height: 315,
+                        _nativeWidth: 600,
+                        _nativeHeight: 472.5
+                    }));
+                    return;
+                }
+                let matches: RegExpExecArray | null;
+                if ((matches = /(https:\/\/)?docs\.google\.com\/document\/d\/([^\\]+)\/edit/g.exec(text)) !== null) {
+                    const newBox = Docs.Create.TextDocument("", { ...options, _width: 400, _height: 200, title: "Awaiting title from Google Docs..." });
+                    const proto = newBox.proto!;
+                    const documentId = matches[2];
+                    proto[GoogleRef] = documentId;
+                    proto.data = "Please select this document and then click on its pull button to load its contents from from Google Docs...";
+                    proto.backgroundColor = "#eeeeff";
+                    addDocument(newBox);
+                    return;
+                }
+                if ((matches = /(https:\/\/)?photos\.google\.com\/(u\/3\/)?album\/([^\\]+)/g.exec(text)) !== null) {
+                    const albumId = matches[3];
+                    const mediaItems = await GooglePhotos.Query.AlbumSearch(albumId);
+                    console.log(mediaItems);
+                    return;
+                }
             }
+
             const { items } = e.dataTransfer;
             const { length } = items;
             const files: File[] = [];
-            if (length) {
-                const batch = UndoManager.StartBatch("collection view drop");
-                const promises: Promise<void>[] = [];
-                // tslint:disable-next-line:prefer-for-of
-                for (let i = 0; i < length; i++) {
-                    const item = e.dataTransfer.items[i];
-                    if (item.kind === "string" && item.type.indexOf("uri") !== -1) {
-                        let str: string;
-                        const prom = new Promise<string>(resolve => item.getAsString(resolve))
-                            .then(action((s: string) => rp.head(Utils.CorsProxy(str = s))))
-                            .then(result => {
-                                const type = result["content-type"];
-                                if (type) {
-                                    Docs.Get.DocumentFromType(type, str, options)
-                                        .then(doc => doc && this.props.addDocument(doc));
-                                }
-                            });
-                        promises.push(prom);
-                    }
-                    if (item.kind === "file") {
-                        const file = item.getAsFile();
-                        file && file.type && files.push(file);
+            const generatedDocuments: Doc[] = [];
+            if (!length) {
+                alert("No uploadable content found.");
+                return;
+            }
+
+            const batch = UndoManager.StartBatch("collection view drop");
+            for (let i = 0; i < length; i++) {
+                const item = e.dataTransfer.items[i];
+                if (item.kind === "string" && item.type.includes("uri")) {
+                    const stringContents = await new Promise<string>(resolve => item.getAsString(resolve));
+                    const type = (await rp.head(Utils.CorsProxy(stringContents)))["content-type"];
+                    if (type) {
+                        const doc = await Docs.Get.DocumentFromType(type, stringContents, options);
+                        doc && generatedDocuments.push(doc);
                     }
                 }
-                promises.push(Networking.UploadFilesToServer(files).then(responses => responses.forEach(({ source: { name, type }, result }) => {
-                    if (result instanceof Error) {
-                        alert(`Upload failed: ${result.message}`);
-                        return;
-                    }
-                    const full = { ...options, _width: 300, title: name };
-                    const pathname = Utils.prepend(result.accessPaths.agnostic.client);
-                    Docs.Get.DocumentFromType(type, pathname, full).then(doc => {
-                        if (doc) {
-                            const proto = Doc.GetProto(doc);
-                            proto.fileUpload = basename(pathname).replace("upload_", "").replace(/\.[a-z0-9]*$/, "");
-                            if (Upload.isImageInformation(result)) {
-                                proto["data-nativeWidth"] = result.nativeWidth;
-                                proto["data-nativeHeight"] = result.nativeHeight;
-                                proto.contentSize = result.contentSize;
-                            }
-                            this.props?.addDocument(doc);
-                        }
-                    });
-                })));
-                if (promises.length) {
-                    Promise.all(promises).finally(() => { completed && completed(); batch.end(); });
-                } else {
-                    if (text && !text.includes("https://")) {
-                        this.props.addDocument(Docs.Create.TextDocument(text, { ...options, _width: 400, _height: 315 }));
-                    }
-                    batch.end();
+                if (item.kind === "file") {
+                    const file = item.getAsFile();
+                    file && file.type && files.push(file);
+                }
+            }
+            for (const { source: { name, type }, result } of await Networking.UploadFilesToServer(files)) {
+                if (result instanceof Error) {
+                    alert(`Upload failed: ${result.message}`);
+                    return;
                 }
+                const full = { ...options, _width: 300, title: name };
+                const pathname = Utils.prepend(result.accessPaths.agnostic.client);
+                const doc = await Docs.Get.DocumentFromType(type, pathname, full);
+                if (!doc) {
+                    continue;
+                }
+                const proto = Doc.GetProto(doc);
+                proto.fileUpload = basename(pathname).replace("upload_", "").replace(/\.[a-z0-9]*$/, "");
+                if (Upload.isImageInformation(result)) {
+                    proto["data-nativeWidth"] = result.nativeWidth;
+                    proto["data-nativeHeight"] = result.nativeHeight;
+                    proto.contentSize = result.contentSize;
+                }
+                generatedDocuments.push(doc);
+            }
+            if (generatedDocuments.length) {
+                generatedDocuments.forEach(addDocument);
+                completed && completed();
             } else {
-                alert("No uploadable content found.");
+                if (text && !text.includes("https://")) {
+                    addDocument(Docs.Create.TextDocument(text, { ...options, _width: 400, _height: 315 }));
+                }
             }
+            batch.end();
         }
     }
+
     return CollectionSubView;
 }
 
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 8720ce002..13ab7c1a4 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -210,7 +210,7 @@ class TreeView extends React.Component<TreeViewProps> {
             } else {
                 ContextMenu.Instance.addItem({ description: "Delete Workspace", event: () => this.props.deleteDoc(this.props.document), icon: "trash-alt" });
                 ContextMenu.Instance.addItem({ description: "Create New Workspace", event: () => MainView.Instance.createNewWorkspace(), icon: "plus" });
-            }    
+            }
             ContextMenu.Instance.addItem({ description: "Toggle Theme Colors", event: () => this.props.document.darkScheme = !this.props.document.darkScheme, icon: "minus" });
             ContextMenu.Instance.addItem({ description: "Open Fields", event: () => { const kvp = Docs.Create.KVPDocument(this.props.document, { _width: 300, _height: 300 }); this.props.addDocTab(kvp, this.props.dataDoc ? this.props.dataDoc : kvp, "onRight"); }, icon: "layer-group" });
             ContextMenu.Instance.addItem({ description: "Publish", event: () => DocUtils.Publish(this.props.document, StrCast(this.props.document.title), () => { }, () => { }), icon: "file" });
@@ -594,7 +594,7 @@ export class CollectionTreeView extends CollectionSubView(Document) {
     protected createTreeDropTarget = (ele: HTMLDivElement) => {
         this.treedropDisposer && this.treedropDisposer();
         if (this._mainEle = ele) {
-            this.treedropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
+            this.treedropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this));
         }
     }
 
@@ -702,7 +702,7 @@ export class CollectionTreeView extends CollectionSubView(Document) {
         !existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
     }
     outerXf = () => Utils.GetScreenTransform(this._mainEle!);
-    onTreeDrop = (e: React.DragEvent) => this.onDrop(e, {});
+    onTreeDrop = (e: React.DragEvent) => this.onExternalDrop(e, {});
 
     @computed get renderClearButton() {
         return <div id="toolbar" key="toolbar">
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 969d6b3c8..bdc5e03e3 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -117,20 +117,20 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     }
 
     @action
-    onDrop = (e: React.DragEvent): Promise<void> => {
+    onExternalDrop = (e: React.DragEvent): Promise<void> => {
         const pt = this.getTransform().transformPoint(e.pageX, e.pageY);
-        return super.onDrop(e, { x: pt[0], y: pt[1] });
+        return super.onExternalDrop(e, { x: pt[0], y: pt[1] });
     }
 
     @undoBatch
     @action
-    drop = (e: Event, de: DragManager.DropEvent) => {
+    onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
         if (this.props.Document.isBackground) return false;
         const xf = this.getTransform();
         const xfo = this.getTransformOverlay();
         const [xp, yp] = xf.transformPoint(de.x, de.y);
         const [xpo, ypo] = xfo.transformPoint(de.x, de.y);
-        if (super.drop(e, de)) {
+        if (super.onInternalDrop(e, de)) {
             if (de.complete.docDragData) {
                 if (de.complete.docDragData.droppedDocuments.length) {
                     const firstDoc = de.complete.docDragData.droppedDocuments[0];
@@ -1079,7 +1079,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         return <div className={"collectionfreeformview-container"}
             ref={this.createDashEventsTarget}
             onWheel={this.onPointerWheel}//pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined,
-            onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onContextMenu={this.onContextMenu}
+            onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onExternalDrop.bind(this)} onContextMenu={this.onContextMenu}
             style={{
                 pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined,
                 transform: this.contentScaling ? `scale(${this.contentScaling})` : "",
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index db5673573..785f90212 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -189,8 +189,8 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
 
     @undoBatch
     @action
-    drop = (e: Event, de: DragManager.DropEvent) => {
-        if (super.drop(e, de)) {
+    onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
+        if (super.onInternalDrop(e, de)) {
             de.complete.docDragData?.droppedDocuments.forEach(action((d: Doc) => {
                 d.dimUnit = "*";
                 d.dimMagnitude = 1;
@@ -214,7 +214,7 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
             getTransform={dxf}
             onClick={this.onChildClickHandler}
             renderDepth={this.props.renderDepth + 1}
-        />
+        />;
     }
     /**
      * @returns the resolved list of rendered child documents, displayed
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index 630a178cf..5e59f8237 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -190,8 +190,8 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument)
 
     @undoBatch
     @action
-    drop = (e: Event, de: DragManager.DropEvent) => {
-        if (super.drop(e, de)) {
+    onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
+        if (super.onInternalDrop(e, de)) {
             de.complete.docDragData?.droppedDocuments.forEach(action((d: Doc) => {
                 d.dimUnit = "*";
                 d.dimMagnitude = 1;
@@ -215,7 +215,7 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument)
             getTransform={dxf}
             onClick={this.onChildClickHandler}
             renderDepth={this.props.renderDepth + 1}
-        />
+        />;
     }
     /**
      * @returns the resolved list of rendered child documents, displayed
diff --git a/src/server/ApiManagers/DeleteManager.ts b/src/server/ApiManagers/DeleteManager.ts
index be452c0ff..9e70af2eb 100644
--- a/src/server/ApiManagers/DeleteManager.ts
+++ b/src/server/ApiManagers/DeleteManager.ts
@@ -2,6 +2,11 @@ import ApiManager, { Registration } from "./ApiManager";
 import { Method, _permission_denied, PublicHandler } from "../RouteManager";
 import { WebSocket } from "../Websocket/Websocket";
 import { Database } from "../database";
+import rimraf = require("rimraf");
+import { pathToDirectory, Directory } from "./UploadManager";
+import { filesDirectory } from "..";
+import { DashUploadUtils } from "../DashUploadUtils";
+import { mkdirSync } from "fs";
 
 export default class DeleteManager extends ApiManager {
 
@@ -31,21 +36,19 @@ export default class DeleteManager extends ApiManager {
             }
         });
 
-        const hi: PublicHandler = async ({ res, isRelease }) => {
-            if (isRelease) {
-                return _permission_denied(res, deletionPermissionError);
+        register({
+            method: Method.GET,
+            subscription: "/deleteAssets",
+            secureHandler: async ({ res, isRelease }) => {
+                if (isRelease) {
+                    return _permission_denied(res, deletionPermissionError);
+                }
+                rimraf.sync(filesDirectory);
+                mkdirSync(filesDirectory);
+                await DashUploadUtils.buildFileDirectories();
+                res.redirect("/delete");
             }
-            await Database.Instance.deleteAll('users');
-            res.redirect("/home");
-        };
-
-        // register({
-        //     method: Method.GET,
-        //     subscription: "/deleteUsers",
-        //     onValidation: hi,
-        //     onUnauthenticated: hi
-        // });
-
+        });
 
         register({
             method: Method.GET,
-- 
cgit v1.2.3-70-g09d2


From c395bb6082884dba910e1e1a505650b2566ba4da Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sun, 16 Feb 2020 19:00:48 -0500
Subject: mostly code simple cleanup.

---
 src/client/views/DocumentDecorations.tsx           | 16 ++--
 src/client/views/collections/CollectionSubView.tsx | 27 ++-----
 .../CollectionFreeFormLayoutEngines.tsx            |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 85 +++++++++-------------
 src/client/views/nodes/DocumentView.tsx            | 20 +----
 5 files changed, 50 insertions(+), 100 deletions(-)

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

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index f516bb8fc..5fc14e716 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -51,15 +51,12 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
     @observable private _titleControlString: string = "#title";
     @observable private _edtingTitle = false;
     @observable private _hidden = false;
-    @observable private _opacity = 1;
-    @observable public Interacting = false;
+    @observable private _addedCloseCalls: CloseCall[] = [];
 
+    @observable public Interacting = false;
     @observable public pushIcon: IconProp = "arrow-alt-circle-up";
     @observable public pullIcon: IconProp = "arrow-alt-circle-down";
     @observable public pullColor: string = "white";
-    @observable public openHover = false;
-    @observable private addedCloseCalls: CloseCall[] = [];
-
 
     constructor(props: Readonly<{}>) {
         super(props);
@@ -93,9 +90,9 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
     }
 
     addCloseCall = (handler: CloseCall) => {
-        const currentOffset = this.addedCloseCalls.length - 1;
-        this.addedCloseCalls.push((toBeDeleted: DocumentView[]) => {
-            this.addedCloseCalls.splice(currentOffset, 1);
+        const currentOffset = this._addedCloseCalls.length - 1;
+        this._addedCloseCalls.push((toBeDeleted: DocumentView[]) => {
+            this._addedCloseCalls.splice(currentOffset, 1);
             handler(toBeDeleted);
         });
     }
@@ -173,7 +170,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
             const recent = Cast(CurrentUserUtils.UserDocument.recentlyClosed, Doc) as Doc;
             const selected = SelectionManager.SelectedDocuments().slice();
             SelectionManager.DeselectAll();
-            this.addedCloseCalls.forEach(handler => handler(selected));
+            this._addedCloseCalls.forEach(handler => handler(selected));
 
             selected.map(dv => {
                 recent && Doc.AddDocToList(recent, "data", dv.props.Document, undefined, true, true);
@@ -412,7 +409,6 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 height: (bounds.b - bounds.y + this._resizeBorderWidth + this._titleHeight) + "px",
                 left: bounds.x - this._resizeBorderWidth / 2,
                 top: bounds.y - this._resizeBorderWidth / 2 - this._titleHeight,
-                opacity: this._opacity
             }}>
                 {minimizeIcon}
                 {titleArea}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 042385dcd..bebd99a3a 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -67,23 +67,24 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
         }
 
         componentDidMount() {
-            this._childLayoutDisposer = reaction(() => [this.childDocs, (Cast(this.props.Document.childLayout, Doc) as Doc)?.[Id]],
-                (args) => {
-                    const childLayout = Cast(this.props.Document.childLayout, Doc);
+            this._childLayoutDisposer = reaction(() => ({ childDocs: this.childDocs, childLayout: Cast(this.props.Document.childLayout, Doc) }),
+                ({ childDocs, childLayout }) => {
                     if (childLayout instanceof Doc) {
-                        this.childDocs.map(doc => {
+                        childDocs.map(doc => {
                             doc.layout_fromParent = childLayout;
                             doc.layoutKey = "layout_fromParent";
                         });
                     }
                     else if (!(childLayout instanceof Promise)) {
-                        this.childDocs.filter(d => !d.isTemplateForField).map(doc => doc.layoutKey === "layout_fromParent" && (doc.layoutKey = "layout"));
+                        childDocs.filter(d => !d.isTemplateForField).map(doc => doc.layoutKey === "layout_fromParent" && (doc.layoutKey = "layout"));
                     }
                 }, { fireImmediately: true });
 
         }
         componentWillUnmount() {
-            this._childLayoutDisposer && this._childLayoutDisposer();
+            this.gestureDisposer?.();
+            this.multiTouchDisposer?.();
+            this._childLayoutDisposer?.();
         }
 
         @computed get dataDoc() {
@@ -96,11 +97,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
         // to its children which may be templates.
         // If 'annotationField' is specified, then all children exist on that field of the extension document, otherwise, they exist directly on the data document under 'fieldKey'
         @computed get dataField() {
-            const { annotationsKey, fieldKey } = this.props;
-            if (annotationsKey) {
-                return this.dataDoc[fieldKey + "-" + annotationsKey];
-            }
-            return this.dataDoc[fieldKey];
+            return this.dataDoc[this.props.fieldKey + (this.props.annotationsKey ? "-" + this.props.annotationsKey : "")];
         }
 
         get childLayoutPairs(): { layout: Doc; data: Doc; }[] {
@@ -161,14 +158,6 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
             (this.props.Document.dropConverter instanceof ScriptField) &&
                 this.props.Document.dropConverter.script.run({ dragData: docDragData }); /// bcz: check this 
             if (docDragData) {
-                if (de.altKey && docDragData.draggedDocuments.length) {
-                    this.childDocs.map(doc => {
-                        doc.layout_fromParent = docDragData.draggedDocuments[0];
-                        doc.layoutKey = "layout_fromParent";
-                    });
-                    e.stopPropagation();
-                    return true;
-                }
                 let added = false;
                 if (this.props.Document._freezeOnDrop) {
                     de.complete.docDragData?.droppedDocuments.forEach(drop => Doc.freezeNativeDimensions(drop, drop[WidthSym](), drop[HeightSym]()));
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index d363770bf..671a3e0c4 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -289,7 +289,7 @@ export function computeTimelineLayout(
         groupNames.push({ type: "text", text: Math.ceil(maxTime).toString(), x: Math.ceil(maxTime - minTime) * scaling, y: 0, height: fontHeight, fontSize, payload: undefined });
     }
 
-    const divider = { type: "div", color: "black", x: 0, y: 0, width: panelDim[0], height: -1, payload: undefined };
+    const divider = { type: "div", color: Cast(Doc.UserDoc().activeWorkspace, Doc, null)?.darkScheme ? "dimGray" : "black", x: 0, y: 0, width: panelDim[0], height: -1, payload: undefined };
     return normalizeResults(panelDim, fontHeight, childPairs, docMap, poolData, viewDefsToJSX, groupNames, (maxTime - minTime) * scaling, [divider], childDocs.filter(c => !filterDocs.includes(c)));
 
     function layoutDocsAtTime(keyDocs: Doc[], key: number) {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index bdc5e03e3..427a8d9fe 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -72,6 +72,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     private _hitCluster = false;
     private _layoutComputeReaction: IReactionDisposer | undefined;
     private _layoutPoolData = observable.map<string, any>();
+    private _cachedPool: Map<string, any> = new Map();
 
     public get displayName() { return "CollectionFreeFormView(" + this.props.Document.title?.toString() + ")"; } // this makes mobx trace() statements more descriptive
     @observable.shallow _layoutElements: ViewDefResult[] = []; // shallow because some layout items (eg pivot labels) are just generated 'divs' and can't be frozen as observables
@@ -436,15 +437,15 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         const [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
         if (!this.isAnnotationOverlay && docs.length && this.childDataProvider(docs[0])) {
             PDFMenu.Instance.fadeOut(true);
-            const minx = this.childDataProvider(docs[0]).x;//docs.length ? NumCast(docs[0].x) : 0;
-            const miny = this.childDataProvider(docs[0]).y;//docs.length ? NumCast(docs[0].y) : 0;
-            const maxx = this.childDataProvider(docs[0]).width + minx;//docs.length ? NumCast(docs[0].width) + minx : minx;
-            const maxy = this.childDataProvider(docs[0]).height + miny;//docs.length ? NumCast(docs[0].height) + miny : miny;
-            const ranges = docs.filter(doc => doc).filter(doc => this.childDataProvider(doc)).reduce((range, doc) => {
-                const x = this.childDataProvider(doc).x;//NumCast(doc.x);
-                const y = this.childDataProvider(doc).y;//NumCast(doc.y);
-                const xe = this.childDataProvider(doc).width + x;//x + NumCast(layoutDoc.width);
-                const ye = this.childDataProvider(doc).height + y; //y + NumCast(layoutDoc.height);
+            const minx = this.childDataProvider(docs[0]).x;
+            const miny = this.childDataProvider(docs[0]).y;
+            const maxx = this.childDataProvider(docs[0]).width + minx;
+            const maxy = this.childDataProvider(docs[0]).height + miny;
+            const ranges = docs.filter(doc => doc && this.childDataProvider(doc)).reduce((range, doc) => {
+                const x = this.childDataProvider(doc).x;
+                const y = this.childDataProvider(doc).y;
+                const xe = this.childDataProvider(doc).width + x;
+                const ye = this.childDataProvider(doc).height + y;
                 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]]);
@@ -743,9 +744,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             return { ...result, transition: "transform 1s" };
         }
         const layoutDoc = Doc.Layout(params.doc);
+        const { x, y, z, color, zIndex } = params.doc;
         return {
-            x: Cast(params.doc.x, "number"), y: Cast(params.doc.y, "number"), z: Cast(params.doc.z, "number"), color: Cast(params.doc.color, "string"),
-            zIndex: Cast(params.doc.zIndex, "number"), width: Cast(layoutDoc._width, "number"), height: Cast(layoutDoc._height, "number")
+            x: NumCast(x), y: NumCast(y), z: Cast(z, "number"), color: StrCast(color), zIndex: Cast(zIndex, "number"),
+            width: Cast(layoutDoc._width, "number"), height: Cast(layoutDoc._height, "number")
         };
     }
 
@@ -757,55 +759,46 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         (this.props.Document.onViewDefDivClick as ScriptField)?.script.run({ this: this.props.Document, payload });
     }
     private viewDefToJSX(viewDef: ViewDefBounds): Opt<ViewDefResult> {
-        const x = Cast(viewDef.x, "number");
-        const y = Cast(viewDef.y, "number");
-        const z = Cast(viewDef.z, "number");
-        const highlight = Cast(viewDef.highlight, "boolean");
-        const zIndex = Cast(viewDef.zIndex, "number");
-        const color = Cast(viewDef.color, "string");
-        const width = Cast(viewDef.width, "number", null);
-        const height = Cast(viewDef.height, "number", null);
+        const { x, y, z } = viewDef;
+        const color = StrCast(viewDef.color);
+        const width = Cast(viewDef.width, "number");
+        const height = Cast(viewDef.height, "number");
+        const transform = `translate(${x}px, ${y}px)`;
         if (viewDef.type === "text") {
             const text = Cast(viewDef.text, "string"); // don't use NumCast, StrCast, etc since we want to test for undefined below
             const fontSize = Cast(viewDef.fontSize, "number");
             return [text, x, y].some(val => val === undefined) ? undefined :
                 {
-                    ele: <div className="collectionFreeform-customText" key={(text || "") + x + y + z + color}
-                        style={{ width, height, color, fontSize, transform: `translate(${x}px, ${y}px)` }}>
+                    ele: <div className="collectionFreeform-customText" key={(text || "") + x + y + z + color} style={{ width, height, color, fontSize, transform }}>
                         {text}
                     </div>,
                     bounds: viewDef
                 };
         } else if (viewDef.type === "div") {
-            const backgroundColor = Cast(viewDef.color, "string");
             return [x, y].some(val => val === undefined) ? undefined :
                 {
                     ele: <div className="collectionFreeform-customDiv" title={viewDef.payload?.join(" ")} key={"div" + x + y + z} onClick={e => this.onViewDefDivClick(e, viewDef)}
-                        style={{ width, height, backgroundColor, transform: `translate(${x}px, ${y}px)` }} />,
+                        style={{ width, height, backgroundColor: color, transform }} />,
                     bounds: viewDef
                 };
         }
     }
 
     childDataProvider = computedFn(function childDataProvider(this: any, doc: Doc) {
-        if (!doc) {
-            console.log(doc);
-        }
         return this._layoutPoolData.get(doc[Id]);
     }.bind(this));
 
-    doTimelineLayout(poolData: Map<string, any>) {
+    doTimelineLayout(poolData: Map<string, PoolData>) {
         return computeTimelineLayout(poolData, this.props.Document, this.childDocs, this.filterDocs,
             this.childLayoutPairs, [this.props.PanelWidth(), this.props.PanelHeight()], this.viewDefsToJSX);
     }
 
-    doPivotLayout(poolData: Map<string, any>) {
+    doPivotLayout(poolData: Map<string, PoolData>) {
         return computePivotLayout(poolData, this.props.Document, this.childDocs, this.filterDocs,
             this.childLayoutPairs, [this.props.PanelWidth(), this.props.PanelHeight()], this.viewDefsToJSX);
     }
 
-    _cachedPool: Map<string, any> = new Map();
-    doFreeformLayout(poolData: Map<string, any>) {
+    doFreeformLayout(poolData: Map<string, PoolData>) {
         const layoutDocs = this.childLayoutPairs.map(pair => pair.layout);
         const initResult = this.Document.arrangeInit && this.Document.arrangeInit.script.run({ docs: layoutDocs, collection: this.Document }, console.log);
         const state = initResult && initResult.success ? initResult.result.scriptState : undefined;
@@ -830,25 +823,19 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     @computed get filterDocs() {
         const docFilters = Cast(this.props.Document._docFilters, listSpec("string"), []);
         const docRangeFilters = Cast(this.props.Document._docRangeFilters, listSpec("string"), []);
-        const clusters: { [key: string]: { [value: string]: string } } = {};
+        const filterFacets: { [key: string]: { [value: string]: string } } = {};  // maps each filter key to an object with value=>modifier fields
         for (let i = 0; i < docFilters.length; i += 3) {
             const [key, value, modifiers] = docFilters.slice(i, i + 3);
-            const cluster = clusters[key];
-            if (!cluster) {
-                const child: { [value: string]: string } = {};
-                child[value] = modifiers;
-                clusters[key] = child;
-            } else {
-                cluster[value] = modifiers;
+            if (!filterFacets[key]) {
+                filterFacets[key] = {};
             }
+            filterFacets[key][value] = modifiers;
         }
         const filteredDocs = docFilters.length ? this.childDocs.filter(d => {
-            for (const key of Object.keys(clusters)) {
-                const cluster = clusters[key];
-                const satisfiesFacet = Object.keys(cluster).some(inner => {
-                    const modifier = cluster[inner];
-                    return (modifier === "x") !== Doc.matchFieldValue(d, key, inner);
-                });
+            for (const facetKey of Object.keys(filterFacets)) {
+                const facet = filterFacets[facetKey];
+                const satisfiesFacet = Object.keys(facet).some(value =>
+                    (facet[value] === "x") !== Doc.matchFieldValue(d, facetKey, value));
                 if (!satisfiesFacet) {
                     return false;
                 }
@@ -897,8 +884,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
     componentDidMount() {
         super.componentDidMount();
-        this._layoutComputeReaction = reaction(
-            () => (this.doLayoutComputation),
+        this._layoutComputeReaction = reaction(() => this.doLayoutComputation,
             (computation) => this._layoutElements = computation?.elements.slice() || [],
             { fireImmediately: true, name: "doLayout" });
     }
@@ -1028,7 +1014,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         ContextMenu.Instance.addItem({ description: "Freeform Options ...", subitems: layoutItems, icon: "eye" });
     }
 
-
     private childViews = () => {
         const children = typeof this.props.children === "function" ? (this.props.children as any)() as JSX.Element[] : [];
         return [
@@ -1037,13 +1022,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         ];
     }
 
-    // @observable private _palette?: JSX.Element;
-
     children = () => {
         const eles: JSX.Element[] = [];
         eles.push(...this.childViews());
-        // this._palette && (eles.push(this._palette));
-        // this.currentStroke && (eles.push(this.currentStroke));
         eles.push(<CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" />);
         return eles;
     }
@@ -1101,7 +1082,7 @@ interface CollectionFreeFormOverlayViewProps {
 @observer
 class CollectionFreeFormOverlayView extends React.Component<CollectionFreeFormOverlayViewProps>{
     render() {
-        return this.props.elements().filter(ele => ele.bounds && ele.bounds.z).map(ele => ele.ele);
+        return this.props.elements().filter(ele => ele.bounds?.z).map(ele => ele.ele);
     }
 }
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 4efaef408..b4ec09aee 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,13 +1,12 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
 import * as fa from '@fortawesome/free-solid-svg-icons';
-import { action, computed, runInAction, observable } from "mobx";
+import { action, computed, observable, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import * as rp from "request-promise";
-import { Doc, DocListCast, DocListCastAsync, Opt } from "../../../new_fields/Doc";
+import { Doc, DocListCast, Opt } from "../../../new_fields/Doc";
 import { Document, PositionDocument } from '../../../new_fields/documentSchemas';
 import { Id } from '../../../new_fields/FieldSymbols';
 import { InkTool } from '../../../new_fields/InkField';
-import { List } from '../../../new_fields/List';
 import { RichTextField } from '../../../new_fields/RichTextField';
 import { listSpec } from "../../../new_fields/Schema";
 import { ScriptField } from '../../../new_fields/ScriptField';
@@ -59,8 +58,6 @@ export interface DocumentViewProps {
     LibraryPath: Doc[];
     fitToBox?: boolean;
     onClick?: ScriptField;
-    onPointerDown?: ScriptField;
-    onPointerUp?: ScriptField;
     dragDivName?: string;
     addDocument?: (doc: Doc) => boolean;
     removeDocument?: (doc: Doc) => boolean;
@@ -107,8 +104,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     @computed get nativeWidth() { return this.layoutDoc._nativeWidth || 0; }
     @computed get nativeHeight() { return this.layoutDoc._nativeHeight || 0; }
     @computed get onClickHandler() { return this.props.onClick || this.layoutDoc.onClick || this.Document.onClick; }
-    @computed get onPointerDownHandler() { return this.props.onPointerDown ? this.props.onPointerDown : this.Document.onPointerDown; }
-    @computed get onPointerUpHandler() { return this.props.onPointerUp ? this.props.onPointerUp : this.Document.onPointerUp; }
 
     private _firstX: number = 0;
     private _firstY: number = 0;
@@ -409,12 +404,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     }
 
     onPointerDown = (e: React.PointerEvent): void => {
-        if (this.onPointerDownHandler && this.onPointerDownHandler.script) {
-            this.onPointerDownHandler.script.run({ this: this.Document.isTemplateForField && this.props.DataDoc ? this.props.DataDoc : this.props.Document }, console.log);
-            document.removeEventListener("pointerup", this.onPointerUp);
-            document.addEventListener("pointerup", this.onPointerUp);
-            return;
-        }
         // console.log(e.button)
         // console.log(e.nativeEvent)
         // continue if the event hasn't been canceled AND we are using a moues or this is has an onClick or onDragStart function (meaning it is a button document)
@@ -464,11 +453,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     }
 
     onPointerUp = (e: PointerEvent): void => {
-        if (this.onPointerUpHandler && this.onPointerUpHandler.script && !InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
-            this.onPointerUpHandler.script.run({ this: this.Document.isTemplateForField && this.props.DataDoc ? this.props.DataDoc : this.props.Document }, console.log);
-            document.removeEventListener("pointerup", this.onPointerUp);
-            return;
-        }
         document.removeEventListener("pointermove", this.onPointerMove);
         document.removeEventListener("pointerup", this.onPointerUp);
         this._doubleTap = (Date.now() - this._lastTap < 300 && e.button === 0 && Math.abs(e.clientX - this._downX) < 2 && Math.abs(e.clientY - this._downY) < 2);
-- 
cgit v1.2.3-70-g09d2


From ba9351bc009b6f78ca9815b481bc5643f8af96e0 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sun, 16 Feb 2020 22:59:36 -0500
Subject: fixed warnings

---
 src/Utils.ts                                       |  4 +-
 src/client/util/RichTextRules.ts                   |  2 +-
 src/client/views/DocumentButtonBar.tsx             |  1 -
 src/client/views/DocumentDecorations.tsx           |  2 +-
 .../views/collections/CollectionDockingView.tsx    |  2 +-
 .../views/collections/CollectionSchemaHeaders.tsx  |  4 +-
 .../views/collections/CollectionViewChromes.tsx    |  2 +-
 .../CollectionFreeFormLayoutEngines.tsx            | 38 ++++++------
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  2 +-
 .../caption_toggle/DetailedCaptionToggle.tsx       | 72 ----------------------
 .../document_templates/image_card/ImageCard.tsx    | 15 -----
 .../views/nodes/ContentFittingDocumentView.tsx     |  2 +-
 src/client/views/nodes/DocumentView.tsx            |  2 +-
 src/client/views/nodes/FormattedTextBox.tsx        |  4 +-
 src/client/views/nodes/PDFBox.tsx                  |  2 +-
 src/client/views/pdf/PDFViewer.tsx                 |  7 +--
 src/new_fields/util.ts                             |  2 +-
 17 files changed, 38 insertions(+), 125 deletions(-)
 delete mode 100644 src/client/views/document_templates/caption_toggle/DetailedCaptionToggle.tsx
 delete mode 100644 src/client/views/document_templates/image_card/ImageCard.tsx

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

diff --git a/src/Utils.ts b/src/Utils.ts
index 6a0b3fad8..9162771aa 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -495,7 +495,7 @@ export function setupMoveUpEvents(
         (target as any)._lastX = e.clientX;
         (target as any)._lastY = e.clientY;
         e.stopPropagation();
-    }
+    };
     const _upEvent = (e: PointerEvent): void => {
         upEvent(e);
         if (Math.abs(e.clientX - (target as any)._downX) < 4 || Math.abs(e.clientY - (target as any)._downY) < 4) {
@@ -503,7 +503,7 @@ export function setupMoveUpEvents(
         }
         document.removeEventListener("pointermove", _moveEvent);
         document.removeEventListener("pointerup", _upEvent);
-    }
+    };
     e.stopPropagation();
     document.removeEventListener("pointermove", _moveEvent);
     document.removeEventListener("pointerup", _upEvent);
diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts
index de0f46202..a4f1ff22c 100644
--- a/src/client/util/RichTextRules.ts
+++ b/src/client/util/RichTextRules.ts
@@ -298,5 +298,5 @@ export class RichTextRules {
                     return null;
                 }),
         ]
-    }
+    };
 }
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index ec1f879c2..5d289c5e5 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -318,7 +318,6 @@ export class DocumentButtonBar extends React.Component<{ views: (DocumentView |
         const isText = this.view0.props.Document.data instanceof RichTextField; // bcz: Todo - can't assume layout is using the 'data' field.  need to add fieldKey to DocumentView
         const considerPull = isText && this.considerGoogleDocsPull;
         const considerPush = isText && this.considerGoogleDocsPush;
-        Doc.UserDoc().pr
         return <div className="documentButtonBar">
             <div className="documentButtonBar-button">
                 {this.linkButton}
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index cc7388a61..44e8a3a48 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -109,7 +109,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 UndoManager.RunInBatch(() => selectionTitleFieldKey && SelectionManager.SelectedDocuments().forEach(d => {
                     const value = typeof d.props.Document[selectionTitleFieldKey] === "number" ? +this._accumulatedTitle : this._accumulatedTitle;
                     didAnything = didAnything || d.props.Document[selectionTitleFieldKey] !== value;
-                    Doc.SetInPlace(d.props.Document, selectionTitleFieldKey, value, true)
+                    Doc.SetInPlace(d.props.Document, selectionTitleFieldKey, value, true);
                 }), "title blur");
                 if (!didAnything) UndoManager.Undo();
             }
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index bf0333d55..902016365 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -434,7 +434,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                 tab.titleElement[0].onchange = (e: any) => {
                     tab.titleElement[0].size = e.currentTarget.value.length + 1;
                     Doc.SetInPlace(doc, "title", e.currentTarget.value, true);
-                }
+                };
                 tab.titleElement[0].size = StrCast(doc.title).length + 1;
                 tab.titleElement[0].value = doc.title;
                 const gearSpan = document.createElement("span");
diff --git a/src/client/views/collections/CollectionSchemaHeaders.tsx b/src/client/views/collections/CollectionSchemaHeaders.tsx
index 92dc8780e..c585506b3 100644
--- a/src/client/views/collections/CollectionSchemaHeaders.tsx
+++ b/src/client/views/collections/CollectionSchemaHeaders.tsx
@@ -5,11 +5,13 @@ import "./CollectionSchemaView.scss";
 import { faPlus, faFont, faHashtag, faAlignJustify, faCheckSquare, faToggleOn, faSortAmountDown, faSortAmountUp, faTimes } from '@fortawesome/free-solid-svg-icons';
 import { library, IconProp } from "@fortawesome/fontawesome-svg-core";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { Flyout, anchorPoints } from "../DocumentDecorations";
 import { ColumnType } from "./CollectionSchemaView";
 import { faFile } from "@fortawesome/free-regular-svg-icons";
 import { SchemaHeaderField, PastelSchemaPalette } from "../../../new_fields/SchemaHeaderField";
 import { undoBatch } from "../../util/UndoManager";
+const higflyout = require("@hig/flyout");
+export const { anchorPoints } = higflyout;
+export const Flyout = higflyout.default;
 
 library.add(faPlus, faFont, faHashtag, faAlignJustify, faCheckSquare, faToggleOn, faFile as any, faSortAmountDown, faSortAmountUp, faTimes);
 
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index a21e78188..0378c818c 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -179,7 +179,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
     @action closeViewSpecs = () => {
         this._viewSpecsOpen = false;
         document.removeEventListener("pointerdown", this.closeViewSpecs);
-    };
+    }
 
     @action
     openDatePicker = (e: React.PointerEvent) => {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 671a3e0c4..8132d2f7c 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -27,15 +27,15 @@ export interface ViewDefBounds {
 }
 
 export interface PoolData {
-    x?: number,
-    y?: number,
-    z?: number,
-    zIndex?: number,
-    width?: number,
-    height?: number,
-    color?: string,
-    transition?: string,
-    highlight?: boolean,
+    x?: number;
+    y?: number;
+    z?: number;
+    zIndex?: number;
+    width?: number;
+    height?: number;
+    color?: string;
+    transition?: string;
+    highlight?: boolean;
 }
 
 export interface ViewDefResult {
@@ -63,16 +63,16 @@ function toLabel(target: FieldResult<Field>) {
  */
 function getTextWidth(text: string, font: string): number {
     // re-use canvas object for better performance
-    var canvas = (getTextWidth as any).canvas || ((getTextWidth as any).canvas = document.createElement("canvas"));
-    var context = canvas.getContext("2d");
+    const canvas = (getTextWidth as any).canvas || ((getTextWidth as any).canvas = document.createElement("canvas"));
+    const context = canvas.getContext("2d");
     context.font = font;
-    var metrics = context.measureText(text);
+    const metrics = context.measureText(text);
     return metrics.width;
 }
 
-interface pivotColumn {
-    docs: Doc[],
-    filters: string[]
+interface PivotColumn {
+    docs: Doc[];
+    filters: string[];
 }
 
 
@@ -86,7 +86,7 @@ export function computePivotLayout(
     viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[]
 ) {
     const fieldKey = "data";
-    const pivotColumnGroups = new Map<FieldResult<Field>, pivotColumn>();
+    const pivotColumnGroups = new Map<FieldResult<Field>, PivotColumn>();
 
     const pivotFieldKey = toLabel(pivotDoc._pivotField);
     for (const doc of filterDocs) {
@@ -123,7 +123,7 @@ export function computePivotLayout(
     const desc = `${fontSize}px ${getComputedStyle(document.body).fontFamily}`;
     const textlen = Array.from(pivotColumnGroups.keys()).map(c => getTextWidth(toLabel(c), desc)).reduce((p, c) => Math.max(p, c), 0 as number);
     const max_text = Math.min(Math.ceil(textlen / 120) * 28, panelDim[1] / 2);
-    let maxInColumn = Array.from(pivotColumnGroups.values()).reduce((p, s) => Math.max(p, s.docs.length), 1);
+    const maxInColumn = Array.from(pivotColumnGroups.values()).reduce((p, s) => Math.max(p, s.docs.length), 1);
 
     const colWidth = panelDim[0] / pivotColumnGroups.size;
     const colHeight = panelDim[1] - max_text;
@@ -223,7 +223,7 @@ export function computeTimelineLayout(
     const findStack = (time: number, stack: number[]) => {
         const index = stack.findIndex(val => val === undefined || val < x);
         return index === -1 ? stack.length : index;
-    }
+    };
 
     let minTime = minTimeReq === undefined ? Number.MAX_VALUE : minTimeReq;
     let maxTime = maxTimeReq === undefined ? -Number.MAX_VALUE : maxTimeReq;
@@ -266,7 +266,7 @@ export function computeTimelineLayout(
     }
 
     const pivotAxisWidth = NumCast(pivotDoc.pivotTimeWidth, panelDim[1] / 2.5);
-    let stacking: number[] = [];
+    const stacking: number[] = [];
     let zind = 0;
     sortedKeys.forEach(key => {
         if (curTime !== undefined && curTime > prevKey && curTime <= key) {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 427a8d9fe..60410544b 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -856,7 +856,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         });
         return rangeFilteredDocs;
     }
-    childLayoutDocFunc = () => this.props.childLayoutTemplate?.() || Cast(this.props.Document.childLayoutTemplate, Doc, null) as Doc;
+    childLayoutDocFunc = () => this.props.childLayoutTemplate?.() || Cast(this.props.Document.childLayoutTemplate, Doc, null);
     get doLayoutComputation() {
         const { newPool, computedElementData } = this.doInternalLayoutComputation;
         runInAction(() =>
diff --git a/src/client/views/document_templates/caption_toggle/DetailedCaptionToggle.tsx b/src/client/views/document_templates/caption_toggle/DetailedCaptionToggle.tsx
deleted file mode 100644
index 3aaf4120c..000000000
--- a/src/client/views/document_templates/caption_toggle/DetailedCaptionToggle.tsx
+++ /dev/null
@@ -1,72 +0,0 @@
-import * as React from 'react';
-import { FontStyleProperty, ColorProperty } from 'csstype';
-import { observer } from 'mobx-react';
-import { observable, action, runInAction } from 'mobx';
-import { FormattedTextBox } from '../../nodes/FormattedTextBox';
-import { FieldViewProps } from '../../nodes/FieldView';
-
-interface DetailedCaptionDataProps {
-    captionFieldKey?: string;
-    detailsFieldKey?: string;
-}
-
-interface DetailedCaptionStylingProps {
-    sharedFontColor?: ColorProperty;
-    captionFontStyle?: FontStyleProperty;
-    detailsFontStyle?: FontStyleProperty;
-    toggleSize?: number;
-}
-
-@observer
-export default class DetailedCaptionToggle extends React.Component<DetailedCaptionDataProps & DetailedCaptionStylingProps & FieldViewProps> {
-    @observable loaded: boolean = false;
-    @observable detailsExpanded: boolean = false;
-
-    @action toggleDetails = (e: React.MouseEvent<HTMLDivElement>) => {
-        e.preventDefault();
-        e.stopPropagation();
-        this.detailsExpanded = !this.detailsExpanded;
-    }
-
-    componentDidMount() {
-        runInAction(() => this.loaded = true);
-    }
-
-    render() {
-        const size = this.props.toggleSize || 20;
-        return (
-            <div style={{
-                transition: "0.5s opacity ease",
-                opacity: this.loaded ? 1 : 0,
-                bottom: 0,
-                fontSize: 14,
-                width: "100%",
-                position: "absolute"
-            }}>
-                {/* caption */}
-                <div style={{ opacity: this.detailsExpanded ? 0 : 1, transition: "opacity 0.3s ease" }}>
-                    <FormattedTextBox {...this.props} fieldKey={this.props.captionFieldKey || "caption"} />
-                </div>
-                {/* details */}
-                <div style={{ opacity: this.detailsExpanded ? 1 : 0, transition: "opacity 0.3s ease" }}>
-                    <FormattedTextBox {...this.props} fieldKey={this.props.detailsFieldKey || "captiondetails"} />
-                </div>
-                {/* toggle */}
-                <div
-                    style={{
-                        width: size,
-                        height: size,
-                        borderRadius: "50%",
-                        backgroundColor: "red",
-                        zIndex: 3,
-                        cursor: "pointer"
-                    }}
-                    onClick={this.toggleDetails}
-                >
-                    <span style={{ color: "white" }}></span>
-                </div>
-            </div>
-        );
-    }
-
-}
diff --git a/src/client/views/document_templates/image_card/ImageCard.tsx b/src/client/views/document_templates/image_card/ImageCard.tsx
deleted file mode 100644
index 868afc423..000000000
--- a/src/client/views/document_templates/image_card/ImageCard.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import * as React from 'react';
-import { FieldViewProps } from '../../nodes/FieldView';
-import { ImageBox } from '../../nodes/ImageBox';
-
-export default class ImageCard extends React.Component<FieldViewProps> {
-
-    render() {
-        return (
-            <div style={{ padding: 30, borderRadius: 15 }}>
-                <ImageBox {...this.props} />
-            </div>
-        );
-    }
-
-}
\ No newline at end of file
diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx
index 387da88f5..73fe4fb5d 100644
--- a/src/client/views/nodes/ContentFittingDocumentView.tsx
+++ b/src/client/views/nodes/ContentFittingDocumentView.tsx
@@ -55,7 +55,7 @@ export class ContentFittingDocumentView extends React.Component<ContentFittingDo
     private contentScaling = () => this.scaling;
 
     private PanelWidth = () => this.panelWidth;
-    private PanelHeight = () => this.panelHeight;;
+    private PanelHeight = () => this.panelHeight;
 
     @computed get panelWidth() { return this.nativeWidth && (!this.props.Document || !this.props.Document._fitWidth) ? this.nativeWidth * this.contentScaling() : this.props.PanelWidth(); }
     @computed get panelHeight() { return this.nativeHeight && (!this.props.Document || !this.props.Document._fitWidth) ? this.nativeHeight * this.contentScaling() : this.props.PanelHeight(); }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 330410a92..704da3c74 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -271,7 +271,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             }
             preventDefault && e.preventDefault();
         }
-    };
+    }
 
     buttonClick = async (altKey: boolean, ctrlKey: boolean) => {
         const linkDocs = DocListCast(this.props.Document.links);
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 30be64040..c2f1c9ac7 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -369,7 +369,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
 
     specificContextMenu = (e: React.MouseEvent): void => {
         const funcs: ContextMenuProps[] = [];
-        funcs.push({ description: "Toggle Sidebar", event: () => { e.stopPropagation(); this.props.Document._showSidebar = !this.props.Document._showSidebar }, icon: "expand-arrows-alt" });
+        funcs.push({ description: "Toggle Sidebar", event: () => { e.stopPropagation(); this.props.Document._showSidebar = !this.props.Document._showSidebar; }, icon: "expand-arrows-alt" });
         funcs.push({ description: "Record Bullet", event: () => { e.stopPropagation(); this.recordBullet(); }, icon: "expand-arrows-alt" });
         ["My Text", "Text from Others", "Todo Items", "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"].forEach(option =>
             funcs.push({
@@ -1067,7 +1067,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
     }
 
     @computed get sidebarWidthPercent() { return StrCast(this.props.Document.sidebarWidthPercent, "0%"); }
-    sidebarWidth = () => { return Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100 * this.props.PanelWidth(); }
+    sidebarWidth = () => Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100 * this.props.PanelWidth();
     sidebarScreenToLocal = () => this.props.ScreenToLocalTransform().translate(-(this.props.PanelWidth() - this.sidebarWidth()), 0);
     @computed get sidebarColor() { return StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], "transparent")); }
     render() {
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index a2aec699f..a96a26b61 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -259,7 +259,7 @@ export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument>
             if (!this._pdfjsRequested) {
                 this._pdfjsRequested = true;
                 const promise = Pdfjs.getDocument(pdfUrl.url.href).promise;
-                promise.then(pdf => { runInAction(() => { this._pdf = pdf; console.log("promise"); }) });
+                promise.then(action(pdf => { this._pdf = pdf; console.log("promise"); }));
 
             }
         }
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 4f50be5b0..a9b8c6bbe 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -127,9 +127,8 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
         // change the address to be the file address of the PNG version of each page
         // file address of the pdf
         const { url: { href } } = Cast(this.props.Document[this.props.fieldKey], PdfField)!;
-        this._coverPath = href.startsWith(window.location.origin) ?
-            JSON.parse(await rp.get(Utils.prepend(`/thumbnail${this.props.url.substring("files/pdfs/".length, this.props.url.length - ".pdf".length)}-${(this.Document.curPage || 1)}.png`))) :
-            { width: 100, height: 100, path: "" };
+        const addr = Utils.prepend(`/thumbnail${this.props.url.substring("files/pdfs/".length, this.props.url.length - ".pdf".length)}-${(this.Document.curPage || 1)}.png`);
+        this._coverPath = href.startsWith(window.location.origin) ? JSON.parse(await rp.get(addr)) : { width: 100, height: 100, path: "" };
         runInAction(() => this._showWaiting = this._showCover = true);
         this.props.startupLive && this.setupPdfJsViewer();
         this._searchReactionDisposer = reaction(() => this.Document.searchMatch, search => {
@@ -623,7 +622,7 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
         TraceMobx();
         return <div className="pdfViewer-annotationLayer" style={{ height: NumCast(this.Document.nativeHeight), transform: `scale(${this._zoomed})` }} ref={this._annotationLayer}>
             {this.nonDocAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map((anno, index) =>
-                <Annotation {...this.props} focus={this.props.focus} dataDoc={this.dataDoc!} fieldKey={this.props.fieldKey} anno={anno} key={`${anno[Id]}-annotation`} />)}
+                <Annotation {...this.props} focus={this.props.focus} dataDoc={this.dataDoc} fieldKey={this.props.fieldKey} anno={anno} key={`${anno[Id]}-annotation`} />)}
         </div>;
     }
     overlayTransform = () => this.scrollXf().scale(1 / this._zoomed);
diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts
index ebc0a1272..6d7f6b56e 100644
--- a/src/new_fields/util.ts
+++ b/src/new_fields/util.ts
@@ -100,7 +100,7 @@ export function makeEditable() {
     _setter = _setterImpl;
 }
 
-let layoutProps = ["panX", "panY", "width", "height", "nativeWidth", "nativeHeight", "fitWidth", "fitToBox",
+const layoutProps = ["panX", "panY", "width", "height", "nativeWidth", "nativeHeight", "fitWidth", "fitToBox",
     "LODdisable", "chromeStatus", "viewType", "gridGap", "xMargin", "yMargin", "autoHeight"];
 export function setter(target: any, in_prop: string | symbol | number, value: any, receiver: any): boolean {
     let prop = in_prop;
-- 
cgit v1.2.3-70-g09d2


From e1e50cd52ccd41d599bcbbfff7eca452f02b96db Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Mon, 17 Feb 2020 19:16:40 -0500
Subject: added ':' syntax to free form view to create specific note types.

---
 .../collectionFreeForm/CollectionFreeFormView.tsx       |  9 ---------
 .../collections/collectionFreeForm/MarqueeView.tsx      | 17 ++++++++++++++++-
 src/client/views/nodes/FormattedTextBox.tsx             |  2 +-
 3 files changed, 17 insertions(+), 11 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 60410544b..59c3033e9 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1002,15 +1002,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             }
         });
 
-        layoutItems.push({
-            description: "Add Note ...",
-            subitems: DocListCast((CurrentUserUtils.UserDocument.noteTypes as Doc).data).map((note, i) => ({
-                description: (i + 1) + ": " + StrCast(note.title),
-                event: (args: { x: number, y: number }) => this.addLiveTextBox(Docs.Create.TextDocument("", { _width: 200, _height: 100, x: this.getTransform().transformPoint(args.x, args.y)[0], y: this.getTransform().transformPoint(args.x, args.y)[1], _autoHeight: true, layout: note, title: StrCast(note.title) })),
-                icon: "eye"
-            })) as ContextMenuProps[],
-            icon: "eye"
-        });
         ContextMenu.Instance.addItem({ description: "Freeform Options ...", subitems: layoutItems, icon: "eye" });
     }
 
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 85cda4ecb..a29823734 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -19,6 +19,8 @@ import "./MarqueeView.scss";
 import React = require("react");
 import MarqueeOptionsMenu from "./MarqueeOptionsMenu";
 import { SubCollectionViewProps } from "../CollectionSubView";
+import { ContextMenu } from "../../ContextMenu";
+import { ContextMenuProps } from "../../ContextMenuItem";
 
 interface MarqueeViewProps {
     getContainerTransform: () => Transform;
@@ -66,7 +68,20 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         //make textbox and add it to this collection
         // tslint:disable-next-line:prefer-const
         let [x, y] = this.props.getTransform().transformPoint(this._downX, this._downY);
-        if (e.key === "q" && e.ctrlKey) {
+        if (e.key === ":") {
+            const layoutItems: ContextMenuProps[] = [];
+            layoutItems.push({
+                description: "Add Note ...",
+                subitems: DocListCast((CurrentUserUtils.UserDocument.noteTypes as Doc).data).map((note, i) => ({
+                    description: ":" + (i + 1) + " " + StrCast(note.title),
+                    event: (args: { x: number, y: number }) => this.props.addLiveTextDocument(Docs.Create.TextDocument("", { _width: 200, _height: 100, x, y, _autoHeight: true, layout: note, title: StrCast(note.title) })),
+                    icon: "eye"
+                })) as ContextMenuProps[],
+                icon: "eye"
+            });
+            ContextMenu.Instance.addItem(layoutItems[0]);
+            ContextMenu.Instance.displayMenu(this._downX, this._downY);
+        } else if (e.key === "q" && e.ctrlKey) {
             e.preventDefault();
             (async () => {
                 const text: string = await navigator.clipboard.readText();
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index c2f1c9ac7..56926382e 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -770,7 +770,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
             }
         }
 
-        const selectOnLoad = this.props.Document[Id] === FormattedTextBox.SelectOnLoad;
+        const selectOnLoad = (Cast(this.props.Document.expandedTemplate, Doc, null) || this.props.Document)[Id] === FormattedTextBox.SelectOnLoad;
         if (selectOnLoad) {
             FormattedTextBox.SelectOnLoad = "";
             this.props.select(false);
-- 
cgit v1.2.3-70-g09d2


From 6d7f56cead95b1ab59a62ccc21ee52491386a655 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Tue, 18 Feb 2020 14:00:06 -0500
Subject: cleaned up context menu ordering

---
 src/client/views/DocumentDecorations.tsx           |  4 +-
 src/client/views/collections/CollectionView.tsx    | 13 +++--
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  1 +
 src/client/views/nodes/DocumentView.tsx            | 61 +++++++++++-----------
 src/client/views/nodes/FormattedTextBox.tsx        |  3 +-
 src/client/views/nodes/ImageBox.tsx                |  2 +-
 6 files changed, 46 insertions(+), 38 deletions(-)

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

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 8e4598339..07cc1d984 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -171,7 +171,9 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
     }
     @action onSettingsClick = (e: PointerEvent): void => {
         if (e.button === 0 && !e.altKey && !e.ctrlKey) {
-            this.simulateMouseClick(SelectionManager.SelectedDocuments()[0].ContentDiv!.children[0].children[0], e.clientX, e.clientY + 30, e.screenX, e.screenY + 30);
+            let child = SelectionManager.SelectedDocuments()[0].ContentDiv!.children[0];
+            while (child.children.length && child.className !== "jsx-parser") child = child.children[0];
+            this.simulateMouseClick(child.children[0], e.clientX, e.clientY + 30, e.screenX, e.screenY + 30);
         }
     }
 
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 52a18fb99..79fd7d792 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -236,15 +236,20 @@ export class CollectionView extends Touchable<FieldViewProps> {
             }
             !existing && ContextMenu.Instance.addItem({ description: "Layout...", subitems: layoutItems, icon: "hand-point-right" });
 
-            const more = ContextMenu.Instance.findByDescription("More...");
-            const moreItems = more && "subitems" in more ? more.subitems : [];
-            moreItems.push({ description: "Export Image Hierarchy", icon: "columns", event: () => ImageUtils.ExportHierarchyToFileSystem(this.props.Document) });
-            !more && ContextMenu.Instance.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" });
+            const open = ContextMenu.Instance.findByDescription("Open...");
+            const openItems = open && "subitems" in open ? open.subitems : [];
+            !open && ContextMenu.Instance.addItem({ description: "Open...", subitems: openItems, icon: "hand-point-right" });
 
             const existingOnClick = ContextMenu.Instance.findByDescription("OnClick...");
             const onClicks = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
             onClicks.push({ description: "Edit onChildClick script", icon: "edit", event: (obj: any) => ScriptBox.EditButtonScript("On Child Clicked...", this.props.Document, "onChildClick", obj.x, obj.y) });
             !existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
+
+            const more = ContextMenu.Instance.findByDescription("More...");
+            const moreItems = more && "subitems" in more ? more.subitems : [];
+            moreItems.push({ description: "Export Image Hierarchy", icon: "columns", event: () => ImageUtils.ExportHierarchyToFileSystem(this.props.Document) });
+            !more && ContextMenu.Instance.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" });
+
         }
     }
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 59c3033e9..fa8a203b4 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -965,6 +965,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     // }
 
     onContextMenu = (e: React.MouseEvent) => {
+        if (this.props.children && this.props.annotationsKey) return;
         const layoutItems: ContextMenuProps[] = [];
 
         layoutItems.push({ description: "reset view", event: () => { this.props.Document._panX = this.props.Document._panY = 0; this.props.Document.scale = 1; }, icon: "compress-arrows-alt" });
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 5062f193e..229e7fa45 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -628,22 +628,37 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         }
         e.preventDefault();
 
+        const cm = ContextMenu.Instance;
         const templateDoc = Cast(this.props.Document[StrCast(this.props.Document.layoutKey)], Doc, null);
 
-        const cm = ContextMenu.Instance;
-        const subitems: ContextMenuProps[] = [];
-        subitems.push({ description: "Open Full Screen", event: () => CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(this, this.props.LibraryPath), icon: "desktop" });
-        subitems.push({ description: "Open Tab        ", event: () => this.props.addDocTab(this.props.Document, this.props.DataDoc, "inTab", this.props.LibraryPath), icon: "folder" });
-        subitems.push({ description: "Open Right      ", event: () => this.props.addDocTab(this.props.Document, this.props.DataDoc, "onRight", this.props.LibraryPath), icon: "caret-square-right" });
-        subitems.push({ description: "Open Alias Tab  ", event: () => this.props.addDocTab(Doc.MakeAlias(this.props.Document), this.props.DataDoc, "inTab"), icon: "folder" });
-        subitems.push({ description: "Open Alias Right", event: () => this.props.addDocTab(Doc.MakeAlias(this.props.Document), this.props.DataDoc, "onRight"), icon: "caret-square-right" });
-        subitems.push({ description: "Open Fields     ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), undefined, "onRight"), icon: "layer-group" });
-        templateDoc && subitems.push({ description: "Open Template   ", event: () => this.props.addDocTab(templateDoc, undefined, "onRight"), icon: "eye" });
-        subitems.push({ description: "Open Repl", icon: "laptop-code", event: () => OverlayView.Instance.addWindow(<ScriptingRepl />, { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" }) });
-        cm.addItem({ description: "Open...", subitems: subitems, icon: "external-link-alt" });
-
-
-        const existingOnClick = ContextMenu.Instance.findByDescription("OnClick...");
+        const existing = cm.findByDescription("Layout...");
+        const layoutItems: ContextMenuProps[] = existing && "subitems" in existing ? existing.subitems : [];
+        layoutItems.push({ description: this.Document.isBackground ? "As Foreground" : "As Background", event: this.makeBackground, icon: this.Document.lockedPosition ? "unlock" : "lock" });
+        layoutItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc), icon: "concierge-bell" });
+
+        layoutItems.push({ description: `${this.Document._chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.Document._chromeStatus = (this.Document._chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" });
+        layoutItems.push({ description: `${this.Document._autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" });
+        layoutItems.push({ description: !this.Document._nativeWidth || !this.Document._nativeHeight ? "Freeze" : "Unfreeze", event: this.toggleNativeDimensions, icon: "snowflake" });
+        layoutItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" });
+        layoutItems.push({ description: this.Document.lockedTransform ? "Unlock Transform" : "Lock Transform", event: this.toggleLockTransform, icon: BoolCast(this.Document.lockedTransform) ? "unlock" : "lock" });
+        layoutItems.push({ description: "Center View", event: () => this.props.focus(this.props.Document, false), icon: "crosshairs" });
+        layoutItems.push({ description: "Zoom to Document", event: () => this.props.focus(this.props.Document, true), icon: "search" });
+        !existing && cm.addItem({ description: "Layout...", subitems: layoutItems, icon: "compass" });
+
+        const open = ContextMenu.Instance.findByDescription("Open...");
+        const openItems: ContextMenuProps[] = open && "subitems" in open ? open.subitems : [];
+        openItems.push({ description: "Open Full Screen", event: () => CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(this, this.props.LibraryPath), icon: "desktop" });
+        openItems.push({ description: "Open Tab        ", event: () => this.props.addDocTab(this.props.Document, this.props.DataDoc, "inTab", this.props.LibraryPath), icon: "folder" });
+        openItems.push({ description: "Open Right      ", event: () => this.props.addDocTab(this.props.Document, this.props.DataDoc, "onRight", this.props.LibraryPath), icon: "caret-square-right" });
+        openItems.push({ description: "Open Alias Tab  ", event: () => this.props.addDocTab(Doc.MakeAlias(this.props.Document), this.props.DataDoc, "inTab"), icon: "folder" });
+        openItems.push({ description: "Open Alias Right", event: () => this.props.addDocTab(Doc.MakeAlias(this.props.Document), this.props.DataDoc, "onRight"), icon: "caret-square-right" });
+        openItems.push({ description: "Open Fields     ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), undefined, "onRight"), icon: "layer-group" });
+        templateDoc && openItems.push({ description: "Open Template   ", event: () => this.props.addDocTab(templateDoc, undefined, "onRight"), icon: "eye" });
+        openItems.push({ description: "Open Repl", icon: "laptop-code", event: () => OverlayView.Instance.addWindow(<ScriptingRepl />, { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" }) });
+        !open && cm.addItem({ description: "Open...", subitems: openItems, icon: "external-link-alt" });
+
+
+        const existingOnClick = cm.findByDescription("OnClick...");
         const onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
         onClicks.push({ description: "Enter Portal", event: this.makeIntoPortal, icon: "window-restore" });
         onClicks.push({ description: "Toggle Detail", event: () => this.Document.onClick = ScriptField.MakeScript(`toggleDetail(this, "${this.props.Document.layoutKey}")`), icon: "window-restore" });
@@ -657,24 +672,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             funcs.push({ description: "Drag an Alias", icon: "edit", event: () => this.Document.dragFactory && (this.Document.onDragStart = ScriptField.MakeFunction('getAlias(this.dragFactory)')) });
             funcs.push({ description: "Drag a Copy", icon: "edit", event: () => this.Document.dragFactory && (this.Document.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)')) });
             funcs.push({ description: "Drag Document", icon: "edit", event: () => this.Document.onDragStart = undefined });
-            ContextMenu.Instance.addItem({ description: "OnDrag...", subitems: funcs, icon: "asterisk" });
+            cm.addItem({ description: "OnDrag...", subitems: funcs, icon: "asterisk" });
         }
 
-        const existing = ContextMenu.Instance.findByDescription("Layout...");
-        const layoutItems: ContextMenuProps[] = existing && "subitems" in existing ? existing.subitems : [];
-        layoutItems.push({ description: this.Document.isBackground ? "As Foreground" : "As Background", event: this.makeBackground, icon: this.Document.lockedPosition ? "unlock" : "lock" });
-        layoutItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc), icon: "concierge-bell" });
-
-        layoutItems.push({ description: `${this.Document._chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.Document._chromeStatus = (this.Document._chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" });
-        layoutItems.push({ description: `${this.Document._autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" });
-        layoutItems.push({ description: !this.Document._nativeWidth || !this.Document._nativeHeight ? "Freeze" : "Unfreeze", event: this.toggleNativeDimensions, icon: "snowflake" });
-        layoutItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" });
-        layoutItems.push({ description: this.Document.lockedTransform ? "Unlock Transform" : "Lock Transform", event: this.toggleLockTransform, icon: BoolCast(this.Document.lockedTransform) ? "unlock" : "lock" });
-        layoutItems.push({ description: "Center View", event: () => this.props.focus(this.props.Document, false), icon: "crosshairs" });
-        layoutItems.push({ description: "Zoom to Document", event: () => this.props.focus(this.props.Document, true), icon: "search" });
-        !existing && cm.addItem({ description: "Layout...", subitems: layoutItems, icon: "compass" });
-
-        const more = ContextMenu.Instance.findByDescription("More...");
+        const more = cm.findByDescription("More...");
         const moreItems: ContextMenuProps[] = more && "subitems" in more ? more.subitems : [];
 
         if (!ClientUtils.RELEASE) {
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 26711259c..fd288f720 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -990,7 +990,6 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
     }
 
     richTextMenuPlugin() {
-        const self = FormattedTextBox;
         return new Plugin({
             view(newView) {
                 RichTextMenu.Instance.changeView(newView);
@@ -1091,7 +1090,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
         return (
             <div className={`formattedTextBox-cont`} ref={this._ref}
                 style={{
-                    height: this.layoutDoc._autoHeight ? "max-content" : undefined,
+                    height: this.layoutDoc._autoHeight && this.props.renderDepth ? "max-content" : undefined,
                     background: this.props.hideOnLeave ? "rgba(0,0,0 ,0.4)" : StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"]),
                     opacity: this.props.hideOnLeave ? (this._entered ? 1 : 0.1) : 1,
                     color: this.props.hideOnLeave ? "white" : "inherit",
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 5b5768d54..ba09a02a3 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -377,7 +377,7 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
 
         this.resize(srcpath);
 
-        return <div className="imageBox-cont" key={this.props.Document[Id]} ref={this.createDropTarget} onContextMenu={this.specificContextMenu}>
+        return <div className="imageBox-cont" key={this.props.Document[Id]} ref={this.createDropTarget}>
             <div className="imageBox-fader" >
                 <img key={this._smallRetryCount + (this._mediumRetryCount << 4) + (this._largeRetryCount << 8)} // force cache to update on retrys
                     src={srcpath}
-- 
cgit v1.2.3-70-g09d2


From c53d6c5df8b98bcf1c88316312497ea4198ae30a Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 22 Feb 2020 00:56:22 -0500
Subject: fixed freeform view to update its documents when a non-rendering (eg
 onClick script) property changes

---
 .../collectionFreeForm/CollectionFreeFormLayoutEngines.tsx    |  8 +++-----
 .../collections/collectionFreeForm/CollectionFreeFormView.tsx | 11 ++++++-----
 2 files changed, 9 insertions(+), 10 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 8132d2f7c..7e37f646d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -314,7 +314,7 @@ export function computeTimelineLayout(
 
 function normalizeResults(panelDim: number[], fontHeight: number, childPairs: { data?: Doc, layout: Doc }[], docMap: Map<Doc, ViewDefBounds>,
     poolData: Map<string, PoolData>, viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], groupNames: ViewDefBounds[], minWidth: number, extras: ViewDefBounds[],
-    extraDocs: Doc[]) {
+    extraDocs: Doc[]):ViewDefResult[]  {
 
     const grpEles = groupNames.map(gn => ({ x: gn.x, y: gn.y, width: gn.width, height: gn.height }) as ViewDefBounds);
     const docEles = childPairs.filter(d => docMap.get(d.layout)).map(pair => docMap.get(pair.layout) as ViewDefBounds);
@@ -341,8 +341,7 @@ function normalizeResults(panelDim: number[], fontHeight: number, childPairs: {
     });
     extraDocs.map(ed => poolData.set(ed[Id], { x: 0, y: 0, zIndex: -99 }));
 
-    return {
-        elements: viewDefsToJSX(extras.concat(groupNames).map(gname => ({
+    return viewDefsToJSX(extras.concat(groupNames).map(gname => ({
             type: gname.type,
             text: gname.text,
             x: gname.x * scale,
@@ -352,8 +351,7 @@ function normalizeResults(panelDim: number[], fontHeight: number, childPairs: {
             height: gname.height === -1 ? 1 : Math.max(fontHeight, (gname.height || 0) * scale),
             fontSize: gname.fontSize,
             payload: gname.payload
-        })))
-    };
+        })));
 }
 
 export function AddCustomFreeFormLayout(doc: Doc, dataKey: string): () => void {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index fa8a203b4..5902cc22e 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -808,7 +808,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             const pos = this.getCalculatedPositions({ doc: pair.layout, index: i, collection: this.Document, docs: layoutDocs, state });
             poolData.set(pair.layout[Id], pos);
         });
-        return { elements: elements };
+        return elements;
     }
 
     @computed get doInternalLayoutComputation() {
@@ -869,8 +869,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             }));
         this._cachedPool.clear();
         Array.from(newPool.keys()).forEach(k => this._cachedPool.set(k, newPool.get(k)));
-        this.childLayoutPairs.filter((pair, i) => this.isCurrent(pair.layout)).forEach(pair =>
-            computedElementData.elements.push({
+        const elements:ViewDefResult[] = computedElementData.slice();
+        this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).forEach(pair =>
+            elements.push({
                 ele: <CollectionFreeFormDocumentView key={pair.layout[Id]}  {...this.getChildDocumentViewProps(pair.layout, pair.data)}
                     dataProvider={this.childDataProvider}
                     LayoutDoc={this.childLayoutDocFunc}
@@ -879,13 +880,13 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 bounds: this.childDataProvider(pair.layout)
             }));
 
-        return computedElementData;
+        return elements;
     }
 
     componentDidMount() {
         super.componentDidMount();
         this._layoutComputeReaction = reaction(() => this.doLayoutComputation,
-            (computation) => this._layoutElements = computation?.elements.slice() || [],
+            (elements) => this._layoutElements = elements || [],
             { fireImmediately: true, name: "doLayout" });
     }
     componentWillUnmount() {
-- 
cgit v1.2.3-70-g09d2


From 1c73798b14bc91e59ab95225b341008271d0159a Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 22 Feb 2020 01:04:55 -0500
Subject: removed LinkFollow box.

---
 src/client/documents/DocumentTypes.ts              |   1 -
 src/client/documents/Documents.ts                  |   8 -
 src/client/views/OverlayView.tsx                   |   2 +-
 .../views/collections/CollectionLinearView.tsx     |   2 +-
 .../views/collections/CollectionStackingView.tsx   |   2 +-
 .../views/collections/CollectionTreeView.tsx       |   1 -
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   6 +-
 src/client/views/linking/LinkFollowBox.scss        |  93 ----
 src/client/views/linking/LinkFollowBox.tsx         | 571 ---------------------
 src/client/views/linking/LinkMenuItem.tsx          |   1 -
 src/client/views/nodes/DocumentContentsView.tsx    |   3 +-
 .../authentication/models/current_user_utils.ts    |   2 -
 12 files changed, 7 insertions(+), 685 deletions(-)
 delete mode 100644 src/client/views/linking/LinkFollowBox.scss
 delete mode 100644 src/client/views/linking/LinkFollowBox.tsx

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

diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts
index 0e6b59b33..06b15d78c 100644
--- a/src/client/documents/DocumentTypes.ts
+++ b/src/client/documents/DocumentTypes.ts
@@ -19,7 +19,6 @@ export enum DocumentType {
     WEBCAM = "webcam",
     FONTICON = "fonticonbox",
     PRES = "presentation",
-    LINKFOLLOW = "linkfollow",
     PRESELEMENT = "preselement",
     QUERY = "search",
     COLOR = "color",
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index aed14e4f6..ff64489bb 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -40,7 +40,6 @@ import { PresBox } from "../views/nodes/PresBox";
 import { ComputedField, ScriptField } from "../../new_fields/ScriptField";
 import { ProxyField } from "../../new_fields/Proxy";
 import { DocumentType } from "./DocumentTypes";
-import { LinkFollowBox } from "../views/linking/LinkFollowBox";
 import { PresElementBox } from "../views/presentationview/PresElementBox";
 import { DashWebRTCVideo } from "../views/webcam/DashWebRTCVideo";
 import { QueryBox } from "../views/nodes/QueryBox";
@@ -250,9 +249,6 @@ export namespace Docs {
                 layout: { view: FontIconBox, dataField: data },
                 options: { _width: 40, _height: 40, borderRounding: "100%" },
             }],
-            [DocumentType.LINKFOLLOW, {
-                layout: { view: LinkFollowBox, dataField: data }
-            }],
             [DocumentType.WEBCAM, {
                 layout: { view: DashWebRTCVideo, dataField: data }
             }],
@@ -658,10 +654,6 @@ export namespace Docs {
             return InstanceFromProto(Prototypes.get(DocumentType.FONTICON), undefined, { ...(options || {}) });
         }
 
-        export function LinkFollowBoxDocument(options?: DocumentOptions) {
-            return InstanceFromProto(Prototypes.get(DocumentType.LINKFOLLOW), undefined, { ...(options || {}) });
-        }
-
         export function PresElementBoxDocument(options?: DocumentOptions) {
             return InstanceFromProto(Prototypes.get(DocumentType.PRESELEMENT), undefined, { ...(options || {}) });
         }
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index 7a99bf0ae..220efd4a8 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -169,7 +169,7 @@ export class OverlayView extends React.Component {
                 document.addEventListener("pointermove", onPointerMove);
                 document.addEventListener("pointerup", onPointerUp);
             };
-            return <div className="overlayView-doc" key={d[Id]} onPointerDown={onPointerDown} style={{ transform: `translate(${d.x}px, ${d.y}px)`, display: d.isMinimized ? "none" : "" }}>
+            return <div className="overlayView-doc" key={d[Id]} onPointerDown={onPointerDown} style={{ transform: `translate(${d.x}px, ${d.y}px)` }}>
                 <DocumentView
                     Document={d}
                     LibraryPath={emptyPath}
diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx
index 9bbc9f1b6..4a14a30cd 100644
--- a/src/client/views/collections/CollectionLinearView.tsx
+++ b/src/client/views/collections/CollectionLinearView.tsx
@@ -71,7 +71,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
         }
     }
 
-    public isCurrent(doc: Doc) { return !doc.isMinimized && (Math.abs(NumCast(doc.displayTimecode, -1) - NumCast(this.Document.currentTimecode, -1)) < 1.5 || NumCast(doc.displayTimecode, -1) === -1); }
+    public isCurrent(doc: Doc) { return (Math.abs(NumCast(doc.displayTimecode, -1) - NumCast(this.Document.currentTimecode, -1)) < 1.5 || NumCast(doc.displayTimecode, -1) === -1); }
 
     dimension = () => NumCast(this.props.Document._height); // 2 * the padding
     getTransform = (ele: React.RefObject<HTMLDivElement>) => () => {
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 0d5d3e449..a1cc21319 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -38,7 +38,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     @observable _scroll = 0; // used to force the document decoration to update when scrolling
     @computed get sectionHeaders() { return Cast(this.props.Document.sectionHeaders, listSpec(SchemaHeaderField)); }
     @computed get sectionFilter() { return StrCast(this.props.Document.sectionFilter); }
-    @computed get filteredChildren() { return this.childLayoutPairs.filter(pair => pair.layout instanceof Doc && !pair.layout.isMinimized).map(pair => pair.layout); }
+    @computed get filteredChildren() { return this.childLayoutPairs.filter(pair => pair.layout instanceof Doc).map(pair => pair.layout); }
     @computed get xMargin() { return NumCast(this.props.Document._xMargin, 2 * Math.min(this.gridGap, .05 * this.props.PanelWidth())); }
     @computed get yMargin() { return Math.max(this.props.Document._showTitle && !this.props.Document._showTitleHover ? 30 : 0, NumCast(this.props.Document._yMargin, 0)); } // 2 * this.gridGap)); }
     @computed get gridGap() { return NumCast(this.props.Document._gridGap, 10); }
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index ed1d674c6..7eeeb6ff1 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -421,7 +421,6 @@ class TreeView extends React.Component<TreeViewProps> {
         return <>
             <div className="docContainer" title="click to edit title" id={`docContainer-${this.props.parentKey}`} ref={reference} onPointerDown={onItemDown}
                 style={{
-                    color: this.props.document.isMinimized ? "red" : "inherit",
                     background: Doc.IsHighlighted(this.props.document) ? "orange" : Doc.IsBrushed(this.props.document) ? "#06121212" : "0",
                     fontWeight: this.props.document.searchMatch ? "bold" : undefined,
                     outline: BoolCast(this.props.document.workspaceBrush) ? "dashed 1px #06123232" : undefined,
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 5902cc22e..4458c7dcf 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -111,7 +111,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         SelectionManager.DeselectAll();
         docs.map(doc => DocumentManager.Instance.getDocumentView(doc)).map(dv => dv && SelectionManager.SelectDoc(dv, true));
     }
-    public isCurrent(doc: Doc) { return !doc.isMinimized && (Math.abs(NumCast(doc.displayTimecode, -1) - NumCast(this.Document.currentTimecode, -1)) < 1.5 || NumCast(doc.displayTimecode, -1) === -1); }
+    public isCurrent(doc: Doc) { return (Math.abs(NumCast(doc.displayTimecode, -1) - NumCast(this.Document.currentTimecode, -1)) < 1.5 || NumCast(doc.displayTimecode, -1) === -1); }
 
     public getActiveDocuments = () => {
         return this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => pair.layout);
@@ -433,7 +433,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
         let x = this.Document._panX || 0;
         let y = this.Document._panY || 0;
-        const docs = this.childLayoutPairs.filter(pair => pair.layout instanceof Doc && !pair.layout.isMinimized).map(pair => pair.layout);
+        const docs = this.childLayoutPairs.filter(pair => pair.layout instanceof Doc).map(pair => pair.layout);
         const [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
         if (!this.isAnnotationOverlay && docs.length && this.childDataProvider(docs[0])) {
             PDFMenu.Instance.fadeOut(true);
@@ -869,7 +869,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             }));
         this._cachedPool.clear();
         Array.from(newPool.keys()).forEach(k => this._cachedPool.set(k, newPool.get(k)));
-        const elements:ViewDefResult[] = computedElementData.slice();
+        const elements: ViewDefResult[] = computedElementData.slice();
         this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).forEach(pair =>
             elements.push({
                 ele: <CollectionFreeFormDocumentView key={pair.layout[Id]}  {...this.getChildDocumentViewProps(pair.layout, pair.data)}
diff --git a/src/client/views/linking/LinkFollowBox.scss b/src/client/views/linking/LinkFollowBox.scss
deleted file mode 100644
index 9eeed1cc8..000000000
--- a/src/client/views/linking/LinkFollowBox.scss
+++ /dev/null
@@ -1,93 +0,0 @@
-@import "../globalCssVariables";
-
-.linkFollowBox-main {
-    position: absolute;
-    background: whitesmoke;
-    color: grey;
-    border-radius: 15px;
-    box-shadow: $intermediate-color 0.2vw 0.2vw 0.4vw;
-    border: solid #BBBBBBBB 5px;
-    pointer-events: all;
-
-    .linkFollowBox-header {
-        height: 50px;
-        text-align: center;
-        text-transform: uppercase;
-        letter-spacing: 2px;
-        font-size: 16px;
-        width: 100%;
-    }
-
-    .direction-indicator {
-        font-size: 12px;
-    }
-
-    .closeDocument {
-        position: relative;
-        max-width: 30px;
-        top: -20px;
-        left: 460px;
-        color: $darker-alt-accent
-    }
-
-    .closeDocument:hover {
-        color: $main-accent;
-    }
-
-    .topHeader {
-        width: 100%;
-        height: 25px;
-    }
-
-    .linkFollowBox-footer {
-        height: 50px;
-        text-align: center;
-        display: flex;
-        justify-content: center;
-        align-items: center;
-
-        button {
-            background-color: $darker-alt-accent;
-            width: 30%;
-        }
-    }
-
-    .linkFollowBox-content {
-        display: grid;
-        grid-template-columns: 1fr 1fr 1fr;
-        grid-column-gap: 5px;
-        margin-left: 5px;
-        margin-right: 5px;
-
-        .linkFollowBox-item {
-            background-color: $light-color;
-            width: 100%;
-            height: 100%;
-
-            .linkFollowBox-itemContent {
-                padding: 5px;
-                font-size: 12px;
-                overflow: scroll;
-
-                input[type=radio] {
-                    border: 0px;
-                    margin-right: 5px;
-                }
-            }
-
-            .title {
-                display: flex;
-                justify-content: center;
-                align-items: center;
-                text-transform: uppercase;
-                color: $light-color;
-                background-color: $lighter-alt-accent;
-                width: 100%;
-                height: 30px;
-                border-bottom: solid $darker-alt-accent 5px;
-                font-size: 12px;
-                text-align: center;
-            }
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/client/views/linking/LinkFollowBox.tsx b/src/client/views/linking/LinkFollowBox.tsx
deleted file mode 100644
index b47464517..000000000
--- a/src/client/views/linking/LinkFollowBox.tsx
+++ /dev/null
@@ -1,571 +0,0 @@
-import { observable, computed, action, runInAction, reaction, IReactionDisposer } from "mobx";
-import React = require("react");
-import { observer } from "mobx-react";
-import { FieldViewProps, FieldView } from "../nodes/FieldView";
-import { Doc, DocListCastAsync, Opt } from "../../../new_fields/Doc";
-import { undoBatch } from "../../util/UndoManager";
-import { NumCast, FieldValue, Cast, StrCast } from "../../../new_fields/Types";
-import { CollectionViewType } from "../collections/CollectionView";
-import { CollectionDockingView } from "../collections/CollectionDockingView";
-import { SelectionManager } from "../../util/SelectionManager";
-import { DocumentManager } from "../../util/DocumentManager";
-import { DocumentView } from "../nodes/DocumentView";
-import "./LinkFollowBox.scss";
-import { SearchUtil } from "../../util/SearchUtil";
-import { Id } from "../../../new_fields/FieldSymbols";
-import { listSpec } from "../../../new_fields/Schema";
-import { DocServer } from "../../DocServer";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { faTimes } from '@fortawesome/free-solid-svg-icons';
-import { docs_v1 } from "googleapis";
-import { Utils } from "../../../Utils";
-import { Link } from "@react-pdf/renderer";
-
-enum FollowModes {
-    OPENTAB = "Open in Tab",
-    OPENRIGHT = "Open in Right Split",
-    OPENFULL = "Open Full Screen",
-    PAN = "Pan to Document",
-    INPLACE = "Open In Place"
-}
-
-enum FollowOptions {
-    ZOOM = "Zoom",
-    NOZOOM = "No Zoom",
-}
-
-@observer
-export class LinkFollowBox extends React.Component<FieldViewProps> {
-
-    public static LayoutString(fieldKey: string) { return FieldView.LayoutString(LinkFollowBox, fieldKey); }
-    public static Instance: LinkFollowBox | undefined;
-    @observable static linkDoc: Doc | undefined = undefined;
-    @observable static destinationDoc: Doc | undefined = undefined;
-    @observable static sourceDoc: Doc | undefined = undefined;
-    @observable selectedMode: string = "";
-    @observable selectedContext: Doc | undefined = undefined;
-    @observable selectedContextAliases: Doc[] | undefined = undefined;
-    @observable selectedOption: string = "";
-    @observable selectedContextString: string = "";
-    @observable sourceView: DocumentView | undefined = undefined;
-    @observable canPan: boolean = false;
-    @observable shouldUseOnlyParentContext = false;
-    _contextDisposer?: IReactionDisposer;
-
-    @observable private _docs: { col: Doc, target: Doc }[] = [];
-    @observable private _otherDocs: { col: Doc, target: Doc }[] = [];
-
-    constructor(props: FieldViewProps) {
-        super(props);
-        LinkFollowBox.Instance = this;
-        this.resetVars();
-        this.props.Document.isBackground = true;
-    }
-
-    componentDidMount = () => {
-        this.resetVars();
-
-        this._contextDisposer = reaction(
-            () => this.selectedContextString,
-            async () => {
-                const ref = await DocServer.GetRefField(this.selectedContextString);
-                runInAction(() => {
-                    if (ref instanceof Doc) {
-                        this.selectedContext = ref;
-                    }
-                });
-                if (this.selectedContext instanceof Doc) {
-                    const aliases = await SearchUtil.GetViewsOfDocument(this.selectedContext);
-                    runInAction(() => { this.selectedContextAliases = aliases; });
-                }
-            }
-        );
-    }
-
-    componentWillUnmount = () => {
-        this._contextDisposer && this._contextDisposer();
-    }
-
-    async resetPan() {
-        if (LinkFollowBox.destinationDoc && this.sourceView && this.sourceView.props.ContainingCollectionDoc) {
-            runInAction(() => this.canPan = false);
-            if (this.sourceView.props.ContainingCollectionDoc._viewType === CollectionViewType.Freeform) {
-                const docs = Cast(this.sourceView.props.ContainingCollectionDoc.data, listSpec(Doc), []);
-                const aliases = await SearchUtil.GetViewsOfDocument(Doc.GetProto(LinkFollowBox.destinationDoc));
-
-                aliases.forEach(alias => {
-                    if (docs.filter(doc => doc === alias).length > 0) {
-                        runInAction(() => { this.canPan = true; });
-                    }
-                });
-            }
-        }
-    }
-
-    @action
-    resetVars = () => {
-        this.selectedContext = undefined;
-        this.selectedContextString = "";
-        this.selectedMode = "";
-        this.selectedOption = "";
-        LinkFollowBox.linkDoc = undefined;
-        LinkFollowBox.sourceDoc = undefined;
-        LinkFollowBox.destinationDoc = undefined;
-        this.sourceView = undefined;
-        this.canPan = false;
-        this.shouldUseOnlyParentContext = false;
-    }
-
-    async fetchDocuments() {
-        if (LinkFollowBox.destinationDoc) {
-            const dest: Doc = LinkFollowBox.destinationDoc;
-            const aliases = await SearchUtil.GetViewsOfDocument(Doc.GetProto(dest));
-            const { docs } = await SearchUtil.Search("", true, { fq: `data_l:"${dest[Id]}"` });
-            const map: Map<Doc, Doc> = new Map;
-            const allDocs = await Promise.all(aliases.map(doc => SearchUtil.Search("", true, { fq: `data_l:"${doc[Id]}"` }).then(result => result.docs)));
-            allDocs.forEach((docs, index) => docs.forEach(doc => map.set(doc, aliases[index])));
-            docs.forEach(doc => map.delete(doc));
-            runInAction(async () => {
-                this._docs = docs.filter(doc => !Doc.AreProtosEqual(doc, CollectionDockingView.Instance.props.Document)).map(doc => ({ col: doc, target: dest }));
-                this._otherDocs = Array.from(map.entries()).filter(entry => !Doc.AreProtosEqual(entry[0], CollectionDockingView.Instance.props.Document)).map(([col, target]) => ({ col, target }));
-                const tcontext = LinkFollowBox.linkDoc && (await Cast(LinkFollowBox.linkDoc.anchor2Context, Doc)) as Doc;
-                runInAction(() => tcontext && this._docs.splice(0, 0, { col: tcontext, target: dest }));
-            });
-        }
-    }
-
-    @action
-    setLinkDocs = (linkDoc: Doc, source: Doc, dest: Doc) => {
-        this.resetVars();
-
-        LinkFollowBox.linkDoc = linkDoc;
-        LinkFollowBox.sourceDoc = source;
-        LinkFollowBox.destinationDoc = dest;
-        this.fetchDocuments();
-
-        SelectionManager.SelectedDocuments().forEach(dv => {
-            if (dv.props.Document === LinkFollowBox.sourceDoc) {
-                this.sourceView = dv;
-            }
-        });
-
-        this.resetPan();
-    }
-
-    highlightDoc = () => LinkFollowBox.destinationDoc && Doc.linkFollowHighlight(LinkFollowBox.destinationDoc);
-
-    @undoBatch
-    openFullScreen = () => {
-        if (LinkFollowBox.destinationDoc) {
-            const view = DocumentManager.Instance.getDocumentView(LinkFollowBox.destinationDoc);
-            view && CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(view);
-        }
-    }
-
-    @undoBatch
-    openColFullScreen = (options: { context: Doc }) => {
-        if (LinkFollowBox.destinationDoc) {
-            if (NumCast(options.context._viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) {
-                const newPanX = NumCast(LinkFollowBox.destinationDoc.x) + NumCast(LinkFollowBox.destinationDoc._width) / 2;
-                const newPanY = NumCast(LinkFollowBox.destinationDoc.y) + NumCast(LinkFollowBox.destinationDoc._height) / 2;
-                options.context._panX = newPanX;
-                options.context._panY = newPanY;
-            }
-            const view = DocumentManager.Instance.getDocumentView(options.context);
-            view && CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(view);
-            this.highlightDoc();
-        }
-    }
-
-    // should container be a doc or documentview or what? This one needs work and is more long term
-    @undoBatch
-    openInContainer = (options: { container: Doc }) => {
-
-    }
-
-    static _addDocTab: (undefined | ((doc: Doc, where: string) => boolean));
-
-    static setAddDocTab = (addFunc: (doc: Doc, where: string) => boolean) => {
-        LinkFollowBox._addDocTab = addFunc;
-    }
-
-    @undoBatch
-    openLinkColRight = (options: { context: Doc, shouldZoom: boolean }) => {
-        if (LinkFollowBox.destinationDoc) {
-            options.context = Doc.IsPrototype(options.context) ? Doc.MakeDelegate(options.context) : options.context;
-            if (NumCast(options.context._viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) {
-                const newPanX = NumCast(LinkFollowBox.destinationDoc.x) + NumCast(LinkFollowBox.destinationDoc._width) / 2;
-                const newPanY = NumCast(LinkFollowBox.destinationDoc.y) + NumCast(LinkFollowBox.destinationDoc._height) / 2;
-                options.context._panX = newPanX;
-                options.context._panY = newPanY;
-            }
-            (LinkFollowBox._addDocTab || this.props.addDocTab)(options.context, "onRight");
-
-            if (options.shouldZoom) this.jumpToLink({ shouldZoom: options.shouldZoom });
-
-            this.highlightDoc();
-            SelectionManager.DeselectAll();
-        }
-    }
-
-    @undoBatch
-    openLinkRight = () => {
-        if (LinkFollowBox.destinationDoc) {
-            const alias = Doc.MakeAlias(LinkFollowBox.destinationDoc);
-            (LinkFollowBox._addDocTab || this.props.addDocTab)(alias, "onRight");
-            this.highlightDoc();
-            SelectionManager.DeselectAll();
-        }
-
-    }
-
-    @undoBatch
-    jumpToLink = async (options: { shouldZoom: boolean }) => {
-        if (LinkFollowBox.sourceDoc && LinkFollowBox.linkDoc) {
-            const focus = (document: Doc) => { (LinkFollowBox._addDocTab || this.props.addDocTab)(document, "inTab"); SelectionManager.DeselectAll(); };
-            //let focus = (doc: Doc, maxLocation: string) => this.props.focus(docthis.props.focus(LinkFollowBox.destinationDoc, true, 1, () => this.props.addDocTab(doc, maxLocation));
-
-            DocumentManager.Instance.FollowLink(LinkFollowBox.linkDoc, LinkFollowBox.sourceDoc, focus, options && options.shouldZoom, false, undefined);
-        }
-    }
-
-    @undoBatch
-    openLinkTab = () => {
-        if (LinkFollowBox.destinationDoc) {
-            const fullScreenAlias = Doc.MakeAlias(LinkFollowBox.destinationDoc);
-            // this.prosp.addDocTab is empty -- use the link source's addDocTab 
-            (LinkFollowBox._addDocTab || this.props.addDocTab)(fullScreenAlias, "inTab");
-
-            this.highlightDoc();
-            SelectionManager.DeselectAll();
-        }
-    }
-
-    @undoBatch
-    openLinkColTab = (options: { context: Doc, shouldZoom: boolean }) => {
-        if (LinkFollowBox.destinationDoc) {
-            options.context = Doc.IsPrototype(options.context) ? Doc.MakeDelegate(options.context) : options.context;
-            if (NumCast(options.context._viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) {
-                const newPanX = NumCast(LinkFollowBox.destinationDoc.x) + NumCast(LinkFollowBox.destinationDoc._width) / 2;
-                const newPanY = NumCast(LinkFollowBox.destinationDoc.y) + NumCast(LinkFollowBox.destinationDoc._height) / 2;
-                options.context._panX = newPanX;
-                options.context._panY = newPanY;
-            }
-            (LinkFollowBox._addDocTab || this.props.addDocTab)(options.context, "inTab");
-            if (options.shouldZoom) this.jumpToLink({ shouldZoom: options.shouldZoom });
-
-            this.highlightDoc();
-            SelectionManager.DeselectAll();
-        }
-    }
-
-    @undoBatch
-    openLinkInPlace = (options: { shouldZoom: boolean }) => {
-
-        if (LinkFollowBox.destinationDoc && LinkFollowBox.sourceDoc) {
-            if (this.sourceView && this.sourceView.props.addDocument) {
-                const destViews = DocumentManager.Instance.getDocumentViews(LinkFollowBox.destinationDoc);
-                if (!destViews.find(dv => dv.props.ContainingCollectionView === this.sourceView!.props.ContainingCollectionView)) {
-                    const alias = Doc.MakeAlias(LinkFollowBox.destinationDoc);
-                    const y = NumCast(LinkFollowBox.sourceDoc.y);
-                    const x = NumCast(LinkFollowBox.sourceDoc.x);
-
-                    const width = NumCast(LinkFollowBox.sourceDoc._width);
-                    const height = NumCast(LinkFollowBox.sourceDoc._height);
-
-                    alias.x = x + width + 30;
-                    alias.y = y;
-                    alias._width = width;
-                    alias._height = height;
-
-                    this.sourceView.props.addDocument(alias);
-                }
-            }
-
-            this.jumpToLink({ shouldZoom: options.shouldZoom });
-
-            this.highlightDoc();
-            SelectionManager.DeselectAll();
-        }
-    }
-
-    //set this to be the default link behavior, can be any of the above
-    public defaultLinkBehavior: (options?: any) => void = this.jumpToLink;
-
-    @action
-    currentLinkBehavior = () => {
-        // this.resetPan();
-        if (LinkFollowBox.destinationDoc) {
-            if (this.selectedContextString === "") {
-                this.selectedContextString = "self";
-                this.selectedContext = LinkFollowBox.destinationDoc;
-            }
-            if (this.selectedOption === "") this.selectedOption = FollowOptions.NOZOOM;
-            const shouldZoom: boolean = this.selectedOption === FollowOptions.NOZOOM ? false : true;
-            const notOpenInContext: boolean = this.selectedContextString === "self" || this.selectedContextString === LinkFollowBox.destinationDoc[Id];
-
-            if (this.selectedMode === FollowModes.INPLACE) {
-                if (shouldZoom !== undefined) this.openLinkInPlace({ shouldZoom: shouldZoom });
-            }
-            else if (this.selectedMode === FollowModes.OPENFULL) {
-                if (notOpenInContext) this.openFullScreen();
-                else this.selectedContext && this.openColFullScreen({ context: this.selectedContext });
-            }
-            else if (this.selectedMode === FollowModes.OPENRIGHT) {
-                if (notOpenInContext) this.openLinkRight();
-                else this.selectedContext && this.openLinkColRight({ context: this.selectedContext, shouldZoom: shouldZoom });
-            }
-            else if (this.selectedMode === FollowModes.OPENTAB) {
-                if (notOpenInContext) this.openLinkTab();
-                else this.selectedContext && this.openLinkColTab({ context: this.selectedContext, shouldZoom: shouldZoom });
-            }
-            else if (this.selectedMode === FollowModes.PAN) {
-                this.jumpToLink({ shouldZoom: shouldZoom });
-            }
-            else return;
-        }
-    }
-
-    @action
-    handleModeChange = (e: React.ChangeEvent) => {
-        const target = e.target as HTMLInputElement;
-        this.selectedMode = target.value;
-        this.selectedContext = undefined;
-        this.selectedContextString = "";
-
-        this.shouldUseOnlyParentContext = (this.selectedMode === FollowModes.INPLACE || this.selectedMode === FollowModes.PAN);
-
-        if (this.shouldUseOnlyParentContext) {
-            if (this.sourceView && this.sourceView.props.ContainingCollectionDoc) {
-                this.selectedContext = this.sourceView.props.ContainingCollectionDoc;
-                this.selectedContextString = (StrCast(this.sourceView.props.ContainingCollectionDoc.title));
-            }
-        }
-    }
-
-    @action
-    handleOptionChange = (e: React.ChangeEvent) => {
-        const target = e.target as HTMLInputElement;
-        this.selectedOption = target.value;
-    }
-
-    @action
-    handleContextChange = (e: React.ChangeEvent) => {
-        const target = e.target as HTMLInputElement;
-        this.selectedContextString = target.value;
-        // selectedContext is updated in reaction
-        this.selectedOption = "";
-    }
-
-    @computed
-    get canOpenInPlace() {
-        if (this.sourceView && this.sourceView.props.ContainingCollectionDoc) {
-            const colDoc = this.sourceView.props.ContainingCollectionDoc;
-            if (colDoc._viewType === CollectionViewType.Freeform) return true;
-        }
-        return false;
-    }
-
-    @computed
-    get availableModes() {
-        return (
-            <div>
-                <label><input
-                    type="radio"
-                    name="mode"
-                    value={FollowModes.OPENRIGHT}
-                    checked={this.selectedMode === FollowModes.OPENRIGHT}
-                    onChange={this.handleModeChange}
-                    disabled={false} />
-                    {FollowModes.OPENRIGHT}
-                </label><br />
-                <label><input
-                    type="radio"
-                    name="mode"
-                    value={FollowModes.OPENTAB}
-                    checked={this.selectedMode === FollowModes.OPENTAB}
-                    onChange={this.handleModeChange}
-                    disabled={false} />
-                    {FollowModes.OPENTAB}
-                </label><br />
-                <label><input
-                    type="radio"
-                    name="mode"
-                    value={FollowModes.OPENFULL}
-                    checked={this.selectedMode === FollowModes.OPENFULL}
-                    onChange={this.handleModeChange}
-                    disabled={false} />
-                    {FollowModes.OPENFULL}
-                </label><br />
-                <label><input
-                    type="radio"
-                    name="mode"
-                    value={FollowModes.PAN}
-                    checked={this.selectedMode === FollowModes.PAN}
-                    onChange={this.handleModeChange}
-                    disabled={!this.canPan} />
-                    {FollowModes.PAN}
-                </label><br />
-                <label><input
-                    type="radio"
-                    name="mode"
-                    value={FollowModes.INPLACE}
-                    checked={this.selectedMode === FollowModes.INPLACE}
-                    onChange={this.handleModeChange}
-                    disabled={!this.canOpenInPlace} />
-                    {FollowModes.INPLACE}
-                </label><br />
-            </div>
-        );
-    }
-
-    @computed
-    get parentName() {
-        if (this.sourceView && this.sourceView.props.ContainingCollectionDoc) {
-            return this.sourceView.props.ContainingCollectionDoc.title;
-        }
-    }
-
-    @computed
-    get parentID(): string {
-        if (this.sourceView && this.sourceView.props.ContainingCollectionDoc) {
-            return StrCast(this.sourceView.props.ContainingCollectionDoc[Id]);
-        }
-        return "col";
-    }
-
-    @computed
-    get availableContexts() {
-        return (
-            this.shouldUseOnlyParentContext ?
-                <label><input
-                    type="radio" disabled={true}
-                    name="context"
-                    value={this.parentID}
-                    checked={true} />
-                    {this.parentName} (Parent Collection)
-                </label>
-                :
-                <div>
-                    <label><input
-                        type="radio" disabled={LinkFollowBox.linkDoc ? false : true}
-                        name="context"
-                        value={LinkFollowBox.destinationDoc ? StrCast(LinkFollowBox.destinationDoc[Id]) : "self"}
-                        checked={LinkFollowBox.destinationDoc ? this.selectedContextString === StrCast(LinkFollowBox.destinationDoc[Id]) || this.selectedContextString === "self" : true}
-                        onChange={this.handleContextChange} />
-                        Open Self
-                </label><br />
-                    {[...this._docs, ...this._otherDocs].map(doc => {
-                        if (doc && doc.target && doc.col.title !== "Recently Closed") {
-                            return <div key={doc.col[Id] + doc.target[Id]}><label key={doc.col[Id] + doc.target[Id]}>
-                                <input
-                                    type="radio" disabled={LinkFollowBox.linkDoc ? false : true}
-                                    name="context"
-                                    value={StrCast(doc.col[Id])}
-                                    checked={this.selectedContextString === StrCast(doc.col[Id])}
-                                    onChange={this.handleContextChange} />
-                                {doc.col.title}
-                            </label><br /></div>;
-                        }
-                    })}
-                </div>
-        );
-    }
-
-    @computed
-    get shouldShowZoom(): boolean {
-        if (this.selectedMode === FollowModes.OPENFULL) return false;
-        if (this.shouldUseOnlyParentContext) return true;
-        if (LinkFollowBox.destinationDoc ? this.selectedContextString === LinkFollowBox.destinationDoc[Id] : "self") return false;
-
-        let contextMatch: boolean = false;
-        if (this.selectedContextAliases) {
-            this.selectedContextAliases.forEach(alias => {
-                if (alias._viewType === CollectionViewType.Freeform) contextMatch = true;
-            });
-        }
-        if (contextMatch) return true;
-
-        return false;
-    }
-
-    @computed
-    get availableOptions() {
-        if (LinkFollowBox.destinationDoc) {
-            return (
-                this.shouldShowZoom ?
-                    <div>
-                        <label><input
-                            type="radio"
-                            name="option"
-                            value={FollowOptions.ZOOM}
-                            checked={this.selectedOption === FollowOptions.ZOOM}
-                            onChange={this.handleOptionChange}
-                            disabled={false} />
-                            {FollowOptions.ZOOM}
-                        </label><br />
-                        <label><input
-                            type="radio"
-                            name="option"
-                            value={FollowOptions.NOZOOM}
-                            checked={this.selectedOption === FollowOptions.NOZOOM}
-                            onChange={this.handleOptionChange}
-                            disabled={false} />
-                            {FollowOptions.NOZOOM}
-                        </label><br />
-                    </div>
-                    :
-                    <div>No Available Options</div>
-            );
-        }
-        return null;
-    }
-
-    render() {
-        return (
-            <div className="linkFollowBox-main" style={{ height: NumCast(this.props.Document._height), width: NumCast(this.props.Document._width) }}>
-                <div className="linkFollowBox-header">
-                    <div className="topHeader">
-                        {LinkFollowBox.linkDoc ? "Link Title: " + StrCast(LinkFollowBox.linkDoc.title) : "No Link Selected"}
-                        <div onClick={() => this.props.Document.isMinimized = true} className="closeDocument"><FontAwesomeIcon icon={faTimes} size="lg" /></div>
-                    </div>
-                    <div className=" direction-indicator">{LinkFollowBox.linkDoc ?
-                        LinkFollowBox.sourceDoc && LinkFollowBox.destinationDoc ? "Source: " + StrCast(LinkFollowBox.sourceDoc.title) + ", Destination: " + StrCast(LinkFollowBox.destinationDoc.title)
-                            : "" : ""}</div>
-                </div>
-                <div className="linkFollowBox-content" style={{ height: NumCast(this.props.Document._height) - 110 }}>
-                    <div className="linkFollowBox-item">
-                        <div className="linkFollowBox-item title">Mode</div>
-                        <div className="linkFollowBox-itemContent">
-                            {LinkFollowBox.linkDoc ? this.availableModes : "Please select a link to view modes"}
-                        </div>
-                    </div>
-                    <div className="linkFollowBox-item">
-                        <div className="linkFollowBox-item title">Context</div>
-                        <div className="linkFollowBox-itemContent">
-                            {this.selectedMode !== "" ? this.availableContexts : "Please select a mode to view contexts"}
-                        </div>
-                    </div>
-                    <div className="linkFollowBox-item">
-                        <div className="linkFollowBox-item title">Options</div>
-                        <div className="linkFollowBox-itemContent">
-                            {this.selectedContextString !== "" ? this.availableOptions : "Please select a context to view options"}
-                        </div>
-                    </div>
-                </div>
-                <div className="linkFollowBox-footer">
-                    <button
-                        onClick={this.resetVars}>
-                        Clear Link
-                    </button>
-                    <div style={{ width: 20 }}></div>
-                    <button
-                        onClick={this.currentLinkBehavior}
-                        disabled={(LinkFollowBox.linkDoc) ? false : true}>
-                        Follow Link
-                    </button>
-                </div>
-            </div>
-        );
-    }
-}
\ No newline at end of file
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index 376afa64b..5fd6e4630 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -8,7 +8,6 @@ import { Cast, StrCast } from '../../../new_fields/Types';
 import { DragManager } from '../../util/DragManager';
 import { LinkManager } from '../../util/LinkManager';
 import { ContextMenu } from '../ContextMenu';
-import { LinkFollowBox } from './LinkFollowBox';
 import './LinkMenuItem.scss';
 import React = require("react");
 import { DocumentManager } from '../../util/DocumentManager';
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 73a53f5cc..bfda13eb3 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -9,7 +9,6 @@ import { CollectionDockingView } from "../collections/CollectionDockingView";
 import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
 import { CollectionSchemaView } from "../collections/CollectionSchemaView";
 import { CollectionView } from "../collections/CollectionView";
-import { LinkFollowBox } from "../linking/LinkFollowBox";
 import { YoutubeBox } from "./../../apis/youtube/YoutubeBox";
 import { AudioBox } from "./AudioBox";
 import { ButtonBox } from "./ButtonBox";
@@ -108,7 +107,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
                 components={{
                     FormattedTextBox, ImageBox, DirectoryImportBox, FontIconBox, ButtonBox, SliderBox, FieldView,
                     CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebBox, KeyValueBox,
-                    PDFBox, VideoBox, AudioBox, HistogramBox, PresBox, YoutubeBox, LinkFollowBox, PresElementBox, QueryBox,
+                    PDFBox, VideoBox, AudioBox, HistogramBox, PresBox, YoutubeBox, PresElementBox, QueryBox,
                     ColorBox, DashWebRTCVideo, DocuLinkBox, InkingStroke, DocumentBox, LinkBox
                 }}
                 bindings={this.CreateBindings()}
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 32439757c..8fe2257fc 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -265,8 +265,6 @@ export class CurrentUserUtils {
     // sets up the default set of documents to be shown in the Overlay layer
     static setupOverlays(doc: Doc) {
         doc.overlays = Docs.Create.FreeformDocument([], { title: "Overlays", backgroundColor: "#aca3a6" });
-        doc.linkFollowBox = Docs.Create.LinkFollowBoxDocument({ x: 250, y: 20, _width: 500, _height: 370, title: "Link Follower" });
-        Doc.AddDocToList(doc.overlays as Doc, "data", doc.linkFollowBox as Doc);
     }
 
     // the initial presentation Doc to use
-- 
cgit v1.2.3-70-g09d2


From 5c139f0001cd10e5699a716c1e64d2c2c0b2d800 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Fri, 28 Feb 2020 12:17:03 -0500
Subject: allowing different text templates to be the default.  fixing
 transparency.  removing audio by default from notes.

---
 src/client/documents/Documents.ts                  |  5 +++-
 src/client/util/Import & Export/ImageUtils.ts      |  2 +-
 src/client/util/RichTextSchema.tsx                 |  8 ++---
 src/client/views/InkingControl.tsx                 |  3 +-
 src/client/views/TemplateMenu.tsx                  |  5 ++++
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  4 +--
 .../collections/collectionFreeForm/MarqueeView.tsx |  3 +-
 src/client/views/nodes/DocumentView.tsx            |  6 ++--
 src/client/views/nodes/FormattedTextBox.scss       |  2 +-
 src/client/views/nodes/FormattedTextBox.tsx        | 34 +++++++++++++---------
 src/client/views/nodes/ImageBox.tsx                | 19 ++++++------
 src/new_fields/documentSchemas.ts                  | 13 +++++----
 12 files changed, 63 insertions(+), 41 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index a0b8a6382..aab44f668 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -77,6 +77,8 @@ export interface DocumentOptions {
     _gridGap?: number; // gap between items in masonry view
     _xMargin?: number; // gap between left edge of document and start of masonry/stacking layouts
     _yMargin?: number; // gap between top edge of dcoument and start of masonry/stacking layouts
+    _xPadding?: number;
+    _yPadding?: number;
     _itemIndex?: number; // which item index the carousel viewer is showing
     _showSidebar?: boolean;  //whether an annotationsidebar should be displayed for text docuemnts
     x?: number;
@@ -174,7 +176,7 @@ export namespace Docs {
         const TemplateMap: TemplateMap = new Map([
             [DocumentType.TEXT, {
                 layout: { view: FormattedTextBox, dataField: data },
-                options: { _height: 150 }
+                options: { _height: 150, _xMargin: 10, _yMargin: 10 }
             }],
             [DocumentType.HIST, {
                 layout: { view: HistogramBox, dataField: data },
@@ -407,6 +409,7 @@ export namespace Docs {
 
         const delegateKeys = ["x", "y", "layoutKey", "_width", "_height", "_panX", "_panY", "_viewType", "_nativeWidth", "_nativeHeight", "dropAction", "childDropAction", "_annotationOn",
             "_chromeStatus", "_forceActive", "_autoHeight", "_fitWidth", "_LODdisable", "_itemIndex", "_showSidebar", "_showTitle", "_showCaption", "_showTitleHover", "_backgroundColor",
+            "_xMargin", "_yMargin", "_xPadding", "_yPadding",
             "_color", "isButton", "isBackground", "removeDropProperties", "treeViewOpen"];
 
         /**
diff --git a/src/client/util/Import & Export/ImageUtils.ts b/src/client/util/Import & Export/ImageUtils.ts
index ff909cc6b..ab8c73d15 100644
--- a/src/client/util/Import & Export/ImageUtils.ts	
+++ b/src/client/util/Import & Export/ImageUtils.ts	
@@ -24,7 +24,7 @@ export namespace ImageUtils {
         const proto = Doc.GetProto(document);
         proto["data-nativeWidth"] = nativeWidth;
         proto["data-nativeHeight"] = nativeHeight;
-        proto.contentSize = contentSize;
+        proto.contentSize = contentSize ? contentSize : undefined;
         return data !== undefined;
     };
 
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index a56aac9b1..4a80a1af8 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -910,11 +910,11 @@ export class DashFieldView {
                 }
                 e.preventDefault();
             }
-            if (e.key === "Enter" && e.ctrlKey) {
-                Doc.addEnumerationToTextField(self._textBoxDoc, node.attrs.fieldKey, [Docs.Create.TextDocument(self._fieldSpan.innerText, { title: self._fieldSpan.innerText })]);
-                e.preventDefault();
-            } else if (e.key === "Enter") {
+            if (e.key === "Enter") {
                 e.preventDefault();
+                if (e.ctrlKey) {
+                    Doc.addEnumerationToTextField(self._textBoxDoc, node.attrs.fieldKey, [Docs.Create.TextDocument(self._fieldSpan.innerText, { title: self._fieldSpan.innerText })]);
+                }
                 let newText = self._fieldSpan.innerText.startsWith(":=") ? ":=-computed-" : self._fieldSpan.innerText;
                 // look for a document whose id === the fieldKey being displayed.  If there's a match, then that document
                 // holds the different enumerated values for the field in the titles of its collected documents.
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index 374c2df27..5cd3c265d 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -29,8 +29,7 @@ export class InkingControl {
         if (number < 0) {
             number = 0xFFFFFFFF + number + 1;
         }
-
-        return number.toString(16).toUpperCase();
+        return (number < 16 ? "0" : "") + number.toString(16).toUpperCase();
     }
 
     @undoBatch
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 595c3817e..5029b4074 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -61,6 +61,10 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
         DocumentView.FloatDoc(topDocView, ex, ey);
     }
 
+    toggleAudio = (e: React.ChangeEvent<HTMLInputElement>): void => {
+        this.props.docViews.map(dv => dv.props.Document._showAudio = e.target.checked);
+    }
+
     @undoBatch
     @action
     toggleTemplate = (event: React.ChangeEvent<HTMLInputElement>, template: Template): void => {
@@ -102,6 +106,7 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
         const templateMenu: Array<JSX.Element> = [];
         this.props.templates.forEach((checked, template) =>
             templateMenu.push(<TemplateToggle key={template.Name} template={template} checked={checked} toggle={this.toggleTemplate} />));
+        templateMenu.push(<OtherToggle key={"audio"} name={"Audio"} checked={this.props.docViews[0].Document._showAudio ? true : false} toggle={this.toggleAudio} />);
         templateMenu.push(<OtherToggle key={"float"} name={"Float"} checked={this.props.docViews[0].Document.z ? true : false} toggle={this.toggleFloat} />);
         templateMenu.push(<OtherToggle key={"chrome"} name={"Chrome"} checked={layout._chromeStatus !== "disabled"} toggle={this.toggleChrome} />);
         this._addedKeys && Array.from(this._addedKeys).map(layout =>
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 4458c7dcf..ca8d5e18b 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -51,8 +51,8 @@ export const panZoomSchema = createSchema({
     arrangeInit: ScriptField,
     useClusters: "boolean",
     fitToBox: "boolean",
-    xPadding: "number",         // pixels of padding on left/right of collectionfreeformview contents when fitToBox is set
-    yPadding: "number",         // pixels of padding on left/right of collectionfreeformview contents when fitToBox is set
+    _xPadding: "number",         // pixels of padding on left/right of collectionfreeformview contents when fitToBox is set
+    _yPadding: "number",         // pixels of padding on left/right of collectionfreeformview contents when fitToBox is set
     panTransformType: "string",
     scrollHeight: "number",
     fitX: "number",
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index b1cca027d..b8e9acf85 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -18,6 +18,7 @@ import MarqueeOptionsMenu from "./MarqueeOptionsMenu";
 import "./MarqueeView.scss";
 import React = require("react");
 import { CollectionView } from "../CollectionView";
+import { FormattedTextBox } from "../../nodes/FormattedTextBox";
 
 interface MarqueeViewProps {
     getContainerTransform: () => Transform;
@@ -104,7 +105,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
             });
         } else if (!e.ctrlKey) {
             this.props.addLiveTextDocument(
-                Docs.Create.TextDocument("", { _width: 200, _height: 100, x: x, y: y, _autoHeight: true, title: "-typed text-" }));
+                Docs.Create.TextDocument("", { _width: NumCast((FormattedTextBox.DefaultLayout as Doc)?._width) || 200, _height: 100, layout: FormattedTextBox.DefaultLayout, x: x, y: y, _autoHeight: true, title: "-typed text-" }));
         } else if (e.keyCode > 48 && e.keyCode <= 57) {
             const notes = DocListCast((CurrentUserUtils.UserDocument.noteTypes as Doc).data);
             const text = Docs.Create.TextDocument("", { _width: 200, _height: 100, x: x, y: y, _autoHeight: true, title: "-typed text-" });
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 06ca9b5f4..7683ad269 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -909,17 +909,19 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     render() {
         if (!(this.props.Document instanceof Doc)) return (null);
         const backgroundColor = StrCast(this.layoutDoc._backgroundColor) || StrCast(this.layoutDoc.backgroundColor) || StrCast(this.Document.backgroundColor) || this.props.backgroundColor?.(this.Document);
+        const finalColor = this.layoutDoc.type === DocumentType.FONTICON || this.layoutDoc._viewType === CollectionViewType.Linear ? undefined : backgroundColor;
         const fullDegree = Doc.isBrushedHighlightedDegree(this.props.Document);
         const borderRounding = this.layoutDoc.borderRounding;
         const localScale = fullDegree;
 
+        console.log("Background = " + this.props.Document.title + " " + finalColor);
         const highlightColors = Cast(Doc.UserDoc().activeWorkspace, Doc, null)?.darkScheme ?
             ["transparent", "#65350c", "#65350c", "yellow", "magenta", "cyan", "orange"] :
             ["transparent", "maroon", "maroon", "yellow", "magenta", "cyan", "orange"];
         const highlightStyles = ["solid", "dashed", "solid", "solid", "solid", "solid", "solid"];
         let highlighting = fullDegree && this.layoutDoc.type !== DocumentType.FONTICON && this.layoutDoc._viewType !== CollectionViewType.Linear;
         highlighting = highlighting && this.props.focus !== emptyFunction;  // bcz: hack to turn off highlighting onsidebar panel documents.  need to flag a document as not highlightable in a more direct way
-        return <div className={`documentView-node${this.topMost ? "-topmost" : ""}`} ref={this._mainCont} onKeyDown={this.onKeyDown}
+        return <div id={this.props.Document[Id]} className={`documentView-node${this.topMost ? "-topmost" : ""}`} ref={this._mainCont} onKeyDown={this.onKeyDown}
             onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick}
             onPointerEnter={e => Doc.BrushDoc(this.props.Document)} onPointerLeave={e => Doc.UnBrushDoc(this.props.Document)}
             style={{
@@ -931,7 +933,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 outline: highlighting && !borderRounding ? `${highlightColors[fullDegree]} ${highlightStyles[fullDegree]} ${localScale}px` : "solid 0px",
                 border: highlighting && borderRounding ? `${highlightStyles[fullDegree]} ${highlightColors[fullDegree]} ${localScale}px` : undefined,
                 boxShadow: this.props.Document.isTemplateForField ? "black 0.2vw 0.2vw 0.8vw" : undefined,
-                background: this.layoutDoc.type === DocumentType.FONTICON || this.layoutDoc._viewType === CollectionViewType.Linear ? undefined : backgroundColor,
+                background: finalColor,
                 width: "100%",
                 height: "100%",
                 opacity: this.Document.opacity
diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss
index c203ca0c3..db2bb751f 100644
--- a/src/client/views/nodes/FormattedTextBox.scss
+++ b/src/client/views/nodes/FormattedTextBox.scss
@@ -95,8 +95,8 @@
 
 .formattedTextBox-inner-rounded,
 .formattedTextBox-inner {
-    padding: 10px 10px;
     height: 100%;
+    white-space: pre-wrap; 
 }
 
 // .menuicon {
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index a320cff75..3567dbbd5 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -375,12 +375,15 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
 
     toggleSidebar = () => this._sidebarMovement < 5 && (this.props.Document.sidebarWidthPercent = StrCast(this.props.Document.sidebarWidthPercent, "0%") === "0%" ? "25%" : "0%");
 
+    public static DefaultLayout: Doc | string | undefined;
     specificContextMenu = (e: React.MouseEvent): void => {
         const funcs: ContextMenuProps[] = [];
+        this.props.Document.isTemplateDoc && funcs.push({ description: "Make Default Layout", event: async () => FormattedTextBox.DefaultLayout = this.props.Document.proto as Doc, icon: "eye" });
+        funcs.push({ description: "Reset Default Layout", event: () => FormattedTextBox.DefaultLayout = undefined, icon: "eye" });
         !this.props.Document.expandedTemplate && funcs.push({ description: "Make Template", event: () => { this.props.Document.isTemplateDoc = true; Doc.AddDocToList(Cast(Doc.UserDoc().noteTypes, Doc, null), "data", this.props.Document); }, icon: "eye" });
-        funcs.push({ description: "Toggle Sidebar", event: () => { e.stopPropagation(); this.props.Document._showSidebar = !this.props.Document._showSidebar; }, icon: "expand-arrows-alt" });
-        funcs.push({ description: "Record Bullet", event: () => { e.stopPropagation(); this.recordBullet(); }, icon: "expand-arrows-alt" });
-        funcs.push({ description: "Toggle Menubar", event: () => { e.stopPropagation(); this.toggleMenubar(); }, icon: "expand-arrows-alt" });
+        funcs.push({ description: "Toggle Sidebar", event: () => this.props.Document._showSidebar = !this.props.Document._showSidebar, icon: "expand-arrows-alt" });
+        funcs.push({ description: "Record Bullet", event: () => this.recordBullet(), icon: "expand-arrows-alt" });
+        funcs.push({ description: "Toggle Menubar", event: () => this.toggleMenubar(), icon: "expand-arrows-alt" });
         ["My Text", "Text from Others", "Todo Items", "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"].forEach(option =>
             funcs.push({
                 description: (FormattedTextBox._highlights.indexOf(option) === -1 ? "Highlight " : "Unhighlight ") + option, event: () => {
@@ -1125,7 +1128,11 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
                 onPointerLeave={action(() => this._entered = false)}
             >
                 <div className={`formattedTextBox-outer`} style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, }} onScroll={this.onscrolled} ref={this._scrollRef}>
-                    <div className={`formattedTextBox-inner${rounded}`} style={{ whiteSpace: "pre-wrap", pointerEvents: ((this.Document.isButton || this.props.onClick) && !this.props.isSelected()) ? "none" : undefined }} ref={this.createDropTarget} />
+                    <div className={`formattedTextBox-inner${rounded}`} ref={this.createDropTarget}
+                        style={{
+                            padding: `${NumCast(this.Document._xMargin, 0)}px  ${NumCast(this.Document._yMargin, 0)}px`,
+                            pointerEvents: ((this.Document.isButton || this.props.onClick) && !this.props.isSelected()) ? "none" : undefined
+                        }} />
                 </div>
                 {!this.props.Document._showSidebar ? (null) : this.sidebarWidthPercent === "0%" ?
                     <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown} onClick={e => this.toggleSidebar()} /> :
@@ -1152,15 +1159,16 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
                         </CollectionFreeFormView>
                         <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown} onClick={e => this.toggleSidebar()} />
                     </div>}
-                <div className="formattedTextBox-dictation"
-                    onClick={e => {
-                        this._recording ? this.stopDictation(true) : this.recordDictation();
-                        setTimeout(() => this._editorView!.focus(), 500);
-                        e.stopPropagation();
-                    }} >
-                    <FontAwesomeIcon className="formattedTExtBox-audioFont"
-                        style={{ color: this._recording ? "red" : "blue", opacity: this._recording ? 1 : 0.5, display: this.props.isSelected() ? "" : "none" }} icon={"microphone"} size="sm" />
-                </div>
+                {!this.props.Document._showAudio ? (null) :
+                    <div className="formattedTextBox-dictation"
+                        onClick={e => {
+                            this._recording ? this.stopDictation(true) : this.recordDictation();
+                            setTimeout(() => this._editorView!.focus(), 500);
+                            e.stopPropagation();
+                        }} >
+                        <FontAwesomeIcon className="formattedTExtBox-audioFont"
+                            style={{ color: this._recording ? "red" : "blue", opacity: this._recording ? 1 : 0.5, display: this.props.isSelected() ? "" : "none" }} icon={"microphone"} size="sm" />
+                    </div>}
             </div>
         );
     }
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 99000a0a9..c46191270 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -407,15 +407,16 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
                         ref={this._imgRef}
                         onError={this.onError} /></div>}
             </div>
-            <div className="imageBox-audioBackground"
-                onPointerDown={this.audioDown}
-                onPointerEnter={this.onPointerEnter}
-                style={{ height: `calc(${.1 * nativeHeight / nativeWidth * 100}%)` }}
-            >
-                <FontAwesomeIcon className="imageBox-audioFont"
-                    style={{ color: [DocListCast(this.dataDoc[this.props.fieldKey + "-audioAnnotations"]).length ? "blue" : "gray", "green", "red"][this._audioState] }}
-                    icon={!DocListCast(this.dataDoc[this.props.fieldKey + "-audioAnnotations"]).length ? "microphone" : faFileAudio} size="sm" />
-            </div>
+            {!this.props.Document._showAudio ? (null) :
+                <div className="imageBox-audioBackground"
+                    onPointerDown={this.audioDown}
+                    onPointerEnter={this.onPointerEnter}
+                    style={{ height: `calc(${.1 * nativeHeight / nativeWidth * 100}%)` }}
+                >
+                    <FontAwesomeIcon className="imageBox-audioFont"
+                        style={{ color: [DocListCast(this.dataDoc[this.props.fieldKey + "-audioAnnotations"]).length ? "blue" : "gray", "green", "red"][this._audioState] }}
+                        icon={!DocListCast(this.dataDoc[this.props.fieldKey + "-audioAnnotations"]).length ? "microphone" : faFileAudio} size="sm" />
+                </div>}
             {this.considerDownloadIcon}
             {this.considerGooglePhotosLink()}
             <FaceRectangles document={this.dataDoc} color={"#0000FF"} backgroundColor={"#0000FF"} />
diff --git a/src/new_fields/documentSchemas.ts b/src/new_fields/documentSchemas.ts
index f3726cb5a..7006163e0 100644
--- a/src/new_fields/documentSchemas.ts
+++ b/src/new_fields/documentSchemas.ts
@@ -15,9 +15,14 @@ export const documentSchema = createSchema({
     _nativeHeight: "number",    // "
     _width: "number",           // width of document in its container's coordinate system
     _height: "number",          // "
-    _showCaption: "string",      // whether editable caption text is overlayed at the bottom of the document 
-    _showTitle: "string",        // the fieldkey whose contents should be displayed at the top of the document
-    _showTitleHover: "string",   // the showTitle should be shown only on hover
+    _xPadding: "number",        // pixels of padding on left/right of collectionfreeformview contents when fitToBox is set
+    _yPadding: "number",        // pixels of padding on top/bottom of collectionfreeformview contents when fitToBox is set
+    _xMargin: "number",         // margin added on left/right of most documents to add separation from their container
+    _yMargin: "number",         // margin added on top/bottom of most documents to add separation from their container
+    _showCaption: "string",     // whether editable caption text is overlayed at the bottom of the document 
+    _showTitle: "string",       // the fieldkey whose contents should be displayed at the top of the document
+    _showTitleHover: "string",  // the showTitle should be shown only on hover
+    _showAudio: "boolean",      // whether to show the audio record icon on documents
     _freeformLayoutEngine: "string",// the string ID for the layout engine to use to layout freeform view documents
     _LODdisable: "boolean",     // whether to disbale LOD switching for CollectionFreeFormViews
     _pivotField: "string",      // specifies which field should be used as the timeline/pivot axis
@@ -53,8 +58,6 @@ export const documentSchema = createSchema({
     strokeWidth: "number",
     fontSize: "string",
     fitToBox: "boolean",        // whether freeform view contents should be zoomed/panned to fill the area of the document view
-    xPadding: "number",         // pixels of padding on left/right of collectionfreeformview contents when fitToBox is set
-    yPadding: "number",         // pixels of padding on left/right of collectionfreeformview contents when fitToBox is set
     letterSpacing: "string",
     textTransform: "string"
 });
-- 
cgit v1.2.3-70-g09d2


From cad4ca15ec12808915b7aa901859e349144d8a50 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Mon, 2 Mar 2020 14:42:23 -0500
Subject: fixed pdfs to sort of support region clippings.

---
 package-lock.json                                  | 79 +++++++++++++---------
 src/client/documents/Documents.ts                  |  3 +-
 src/client/util/DocumentManager.ts                 |  2 +-
 src/client/util/DragManager.ts                     |  2 +-
 src/client/util/RichTextSchema.tsx                 | 17 ++---
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  4 +-
 src/client/views/nodes/DocumentView.tsx            |  7 +-
 src/client/views/nodes/PDFBox.tsx                  | 18 +++--
 src/client/views/pdf/PDFViewer.tsx                 | 50 ++++++++------
 9 files changed, 103 insertions(+), 79 deletions(-)

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

diff --git a/package-lock.json b/package-lock.json
index 375b41a01..827fb05b8 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2226,7 +2226,7 @@
                 },
                 "util": {
                     "version": "0.10.3",
-                    "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz",
+                    "resolved": "http://registry.npmjs.org/util/-/util-0.10.3.tgz",
                     "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=",
                     "dev": true,
                     "requires": {
@@ -2846,7 +2846,7 @@
         },
         "browserify-aes": {
             "version": "1.2.0",
-            "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
+            "resolved": "http://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
             "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==",
             "requires": {
                 "buffer-xor": "^1.0.3",
@@ -2880,7 +2880,7 @@
         },
         "browserify-rsa": {
             "version": "4.0.1",
-            "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
+            "resolved": "http://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
             "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=",
             "requires": {
                 "bn.js": "^4.1.0",
@@ -3051,7 +3051,7 @@
         },
         "camelcase-keys": {
             "version": "2.1.0",
-            "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
+            "resolved": "http://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
             "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=",
             "requires": {
                 "camelcase": "^2.0.0",
@@ -3844,7 +3844,7 @@
         },
         "create-hash": {
             "version": "1.2.0",
-            "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
+            "resolved": "http://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
             "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
             "requires": {
                 "cipher-base": "^1.0.1",
@@ -3856,7 +3856,7 @@
         },
         "create-hmac": {
             "version": "1.1.7",
-            "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
+            "resolved": "http://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
             "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==",
             "requires": {
                 "cipher-base": "^1.0.3",
@@ -4398,7 +4398,7 @@
         },
         "diffie-hellman": {
             "version": "5.0.3",
-            "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
+            "resolved": "http://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
             "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==",
             "requires": {
                 "bn.js": "^4.1.0",
@@ -5697,7 +5697,8 @@
                 },
                 "ansi-regex": {
                     "version": "2.1.1",
-                    "bundled": true
+                    "bundled": true,
+                    "optional": true
                 },
                 "aproba": {
                     "version": "1.2.0",
@@ -5734,7 +5735,8 @@
                 },
                 "code-point-at": {
                     "version": "1.1.0",
-                    "bundled": true
+                    "bundled": true,
+                    "optional": true
                 },
                 "concat-map": {
                     "version": "0.0.1",
@@ -5743,7 +5745,8 @@
                 },
                 "console-control-strings": {
                     "version": "1.1.0",
-                    "bundled": true
+                    "bundled": true,
+                    "optional": true
                 },
                 "core-util-is": {
                     "version": "1.0.2",
@@ -5846,7 +5849,8 @@
                 },
                 "inherits": {
                     "version": "2.0.4",
-                    "bundled": true
+                    "bundled": true,
+                    "optional": true
                 },
                 "ini": {
                     "version": "1.3.5",
@@ -5856,6 +5860,7 @@
                 "is-fullwidth-code-point": {
                     "version": "1.0.0",
                     "bundled": true,
+                    "optional": true,
                     "requires": {
                         "number-is-nan": "^1.0.0"
                     }
@@ -5881,6 +5886,7 @@
                 "minipass": {
                     "version": "2.9.0",
                     "bundled": true,
+                    "optional": true,
                     "requires": {
                         "safe-buffer": "^5.1.2",
                         "yallist": "^3.0.0"
@@ -5897,6 +5903,7 @@
                 "mkdirp": {
                     "version": "0.5.1",
                     "bundled": true,
+                    "optional": true,
                     "requires": {
                         "minimist": "0.0.8"
                     }
@@ -5988,6 +5995,7 @@
                 "once": {
                     "version": "1.4.0",
                     "bundled": true,
+                    "optional": true,
                     "requires": {
                         "wrappy": "1"
                     }
@@ -6063,7 +6071,8 @@
                 },
                 "safe-buffer": {
                     "version": "5.1.2",
-                    "bundled": true
+                    "bundled": true,
+                    "optional": true
                 },
                 "safer-buffer": {
                     "version": "2.1.2",
@@ -6093,6 +6102,7 @@
                 "string-width": {
                     "version": "1.0.2",
                     "bundled": true,
+                    "optional": true,
                     "requires": {
                         "code-point-at": "^1.0.0",
                         "is-fullwidth-code-point": "^1.0.0",
@@ -6110,6 +6120,7 @@
                 "strip-ansi": {
                     "version": "3.0.1",
                     "bundled": true,
+                    "optional": true,
                     "requires": {
                         "ansi-regex": "^2.0.0"
                     }
@@ -6148,11 +6159,13 @@
                 },
                 "wrappy": {
                     "version": "1.0.2",
-                    "bundled": true
+                    "bundled": true,
+                    "optional": true
                 },
                 "yallist": {
                     "version": "3.1.1",
-                    "bundled": true
+                    "bundled": true,
+                    "optional": true
                 }
             }
         },
@@ -7370,7 +7383,7 @@
         },
         "is-accessor-descriptor": {
             "version": "0.1.6",
-            "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
+            "resolved": "http://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
             "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=",
             "requires": {
                 "kind-of": "^3.0.2"
@@ -7425,7 +7438,7 @@
         },
         "is-data-descriptor": {
             "version": "0.1.4",
-            "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
+            "resolved": "http://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
             "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=",
             "requires": {
                 "kind-of": "^3.0.2"
@@ -8156,7 +8169,7 @@
         },
         "load-json-file": {
             "version": "1.1.0",
-            "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
+            "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
             "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
             "requires": {
                 "graceful-fs": "^4.1.2",
@@ -8493,7 +8506,7 @@
         },
         "media-typer": {
             "version": "0.3.0",
-            "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+            "resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
             "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
         },
         "mem": {
@@ -8525,7 +8538,7 @@
         },
         "meow": {
             "version": "3.7.0",
-            "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
+            "resolved": "http://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
             "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=",
             "requires": {
                 "camelcase-keys": "^2.0.0",
@@ -8700,7 +8713,7 @@
         },
         "mkdirp": {
             "version": "0.5.1",
-            "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+            "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
             "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
             "requires": {
                 "minimist": "0.0.8"
@@ -9038,7 +9051,7 @@
         },
         "next-tick": {
             "version": "1.0.0",
-            "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
+            "resolved": "http://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
             "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw="
         },
         "nice-try": {
@@ -9121,7 +9134,7 @@
                 },
                 "semver": {
                     "version": "5.3.0",
-                    "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
+                    "resolved": "http://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
                     "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8="
                 },
                 "tar": {
@@ -12772,7 +12785,7 @@
         },
         "os-homedir": {
             "version": "1.0.2",
-            "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
+            "resolved": "http://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
             "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M="
         },
         "os-locale": {
@@ -12785,7 +12798,7 @@
         },
         "os-tmpdir": {
             "version": "1.0.2",
-            "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
+            "resolved": "http://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
             "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ="
         },
         "osenv": {
@@ -13025,7 +13038,7 @@
         },
         "path-is-absolute": {
             "version": "1.0.1",
-            "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+            "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
             "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
         },
         "path-is-inside": {
@@ -14451,7 +14464,7 @@
         },
         "readable-stream": {
             "version": "2.3.6",
-            "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+            "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
             "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
             "requires": {
                 "core-util-is": "~1.0.0",
@@ -14893,7 +14906,7 @@
         },
         "safe-regex": {
             "version": "1.1.0",
-            "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
+            "resolved": "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
             "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=",
             "requires": {
                 "ret": "~0.1.10"
@@ -15173,7 +15186,7 @@
         },
         "sha.js": {
             "version": "2.4.11",
-            "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
+            "resolved": "http://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
             "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
             "requires": {
                 "inherits": "^2.0.1",
@@ -16035,7 +16048,7 @@
         },
         "string_decoder": {
             "version": "1.1.1",
-            "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+            "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
             "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
             "requires": {
                 "safe-buffer": "~5.1.0"
@@ -16065,7 +16078,7 @@
         },
         "strip-ansi": {
             "version": "3.0.1",
-            "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+            "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
             "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
             "requires": {
                 "ansi-regex": "^2.0.0"
@@ -16081,7 +16094,7 @@
         },
         "strip-eof": {
             "version": "1.0.0",
-            "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
+            "resolved": "http://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
             "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8="
         },
         "strip-indent": {
@@ -16898,7 +16911,7 @@
         },
         "tty-browserify": {
             "version": "0.0.0",
-            "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
+            "resolved": "http://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
             "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=",
             "dev": true
         },
@@ -18370,7 +18383,7 @@
         },
         "wrap-ansi": {
             "version": "2.1.0",
-            "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
+            "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
             "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
             "requires": {
                 "string-width": "^1.0.1",
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index d0385918c..abef72f21 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -75,6 +75,7 @@ export interface DocumentOptions {
     _showTitleHover?: string; // 
     _showTitle?: string; // which field to display in the title area.  leave empty to have no title
     _showCaption?: string; // which field to display in the caption area.  leave empty to have no caption
+    _scrollTop?: number; // scroll location for pdfs
     _chromeStatus?: string;
     _viewType?: number;
     _gridGap?: number; // gap between items in masonry view
@@ -420,7 +421,7 @@ export namespace Docs {
 
         const delegateKeys = ["x", "y", "layoutKey", "_width", "_height", "_panX", "_panY", "_viewType", "_nativeWidth", "_nativeHeight", "dropAction", "childDropAction", "_annotationOn",
             "_chromeStatus", "_forceActive", "_autoHeight", "_fitWidth", "_LODdisable", "_itemIndex", "_showSidebar", "_showTitle", "_showCaption", "_showTitleHover", "_backgroundColor",
-            "_xMargin", "_yMargin", "_xPadding", "_yPadding", "_singleLine",
+            "_xMargin", "_yMargin", "_xPadding", "_yPadding", "_singleLine", "_scrollTop",
             "_color", "isButton", "isBackground", "removeDropProperties", "treeViewOpen"];
 
         /**
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index c639f07f5..4f721cb77 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -195,7 +195,7 @@ export class DocumentManager {
         const linkFollowDocContexts = first.length ? [await first[0].anchor2Context as Doc, await first[0].anchor1Context as Doc] : second.length ? [await second[0].anchor1Context as Doc, await second[0].anchor2Context as Doc] : [undefined, undefined];
         const linkFollowTimecodes = first.length ? [NumCast(first[0].anchor2Timecode), NumCast(first[0].anchor1Timecode)] : second.length ? [NumCast(second[0].anchor1Timecode), NumCast(second[0].anchor2Timecode)] : [undefined, undefined];
         if (linkFollowDocs && linkDoc) {
-            const maxLocation = StrCast(linkFollowDocs[0].maximizeLocation, "inTab");
+            const maxLocation = StrCast(linkDoc.maximizeLocation, "inTab");
             const targetContext = !Doc.AreProtosEqual(linkFollowDocContexts[reverse ? 1 : 0], currentContext) ? linkFollowDocContexts[reverse ? 1 : 0] : undefined;
             const target = linkFollowDocs[reverse ? 1 : 0];
             target.currentTimecode !== undefined && (target.currentTimecode = linkFollowTimecodes[reverse ? 1 : 0]);
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 1cfebf414..42ae704dd 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -387,8 +387,8 @@ export namespace DragManager {
             hideDragShowOriginalElements();
             dispatchDrag(eles, e, dragData, options, finishDrag);
             SelectionManager.SetIsDragging(false);
-            options?.dragComplete?.(new DragCompleteEvent(false, dragData));
             endDrag();
+            options?.dragComplete?.(new DragCompleteEvent(false, dragData));
         };
         document.addEventListener("pointermove", moveHandler, true);
         document.addEventListener("pointerup", upHandler);
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 4a80a1af8..b2ee7320a 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -796,15 +796,8 @@ export class DashDocView {
     }
     doRender(dashDoc: Doc, removeDoc: any, node: any, view: any, getPos: any) {
         this._dashDoc = dashDoc;
-        if (node.attrs.width !== dashDoc._width + "px" || node.attrs.height !== dashDoc._height + "px") {
-            try { // bcz: an exception will be thrown if two aliases are open at the same time when a doc view comment is made
-                view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, { ...node.attrs, width: dashDoc._width + "px", height: dashDoc._height + "px" }));
-            } catch (e) {
-                console.log(e);
-            }
-        }
         const self = this;
-        const finalLayout = Doc.expandTemplateLayout(dashDoc, !Doc.AreProtosEqual(this._textBox.dataDoc, this._textBox.Document) ? this._textBox.dataDoc : undefined);
+        const finalLayout = this._textBox.props.Document instanceof Doc && (Doc.expandTemplateLayout(dashDoc, !Doc.AreProtosEqual(this._textBox.dataDoc, this._textBox.props.Document) ? this._textBox.dataDoc : undefined));
         if (!finalLayout) setTimeout(() => self.doRender(dashDoc, removeDoc, node, view, getPos), 0);
         else {
             const layoutKey = StrCast(finalLayout.layoutKey);
@@ -846,9 +839,17 @@ export class DashDocView {
                 ContainingCollectionDoc={undefined}
                 ContentScaling={this.contentScaling}
             />, this._dashSpan);
+            if (node.attrs.width !== dashDoc._width + "px" || node.attrs.height !== dashDoc._height + "px") {
+                try { // bcz: an exception will be thrown if two aliases are open at the same time when a doc view comment is made
+                    view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, { ...node.attrs, width: dashDoc._width + "px", height: dashDoc._height + "px" }));
+                } catch (e) {
+                    console.log(e);
+                }
+            }
         }
     }
     destroy() {
+        ReactDOM.unmountComponentAtNode(this._dashSpan);
         this._reactionDisposer?.();
     }
 }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index a73e601fd..055be7f86 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -817,6 +817,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 const offset = annotOn && (contextHgt / 2 * 96 / 72);
                 this.props.Document.scrollY = NumCast(doc.y) - offset;
             }
+
+            afterFocus && setTimeout(() => afterFocus?.(), 1000);
         } else {
             const layoutdoc = Doc.Layout(doc);
             const newPanX = NumCast(doc.x) + NumCast(layoutdoc._width) / 2;
@@ -834,7 +836,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             Doc.linkFollowHighlight(doc);
 
             afterFocus && setTimeout(() => {
-                if (afterFocus && afterFocus()) {
+                if (afterFocus?.()) {
                     this.Document._panX = savedState.px;
                     this.Document._panY = savedState.py;
                     this.Document.scale = savedState.s;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 850225652..64d85589f 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -217,7 +217,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         this.multiTouchDisposer && this.multiTouchDisposer();
         this.holdDisposer && this.holdDisposer();
         Doc.UnBrushDoc(this.props.Document);
-        !this.props.dontRegisterView && DocumentManager.Instance.DocumentViews.splice(DocumentManager.Instance.DocumentViews.indexOf(this), 1);
+        if (!this.props.dontRegisterView) {
+            const index = DocumentManager.Instance.DocumentViews.indexOf(this);
+            index !== -1 && DocumentManager.Instance.DocumentViews.splice(index, 1);
+        }
     }
 
     startDragging(x: number, y: number, dropAction: dropActionType) {
@@ -829,7 +832,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
             if (!this.topMost) {
                 // DocumentViews should stop propagation of this event
-                me?.stopPropagation();
+                e.stopPropagation();
             }
             ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
             if (!SelectionManager.IsSelected(this, true)) {
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 593f40f10..7b545eee5 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -56,7 +56,7 @@ export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument>
 
         const backup = "oldPath";
         const { Document } = this.props;
-        const { url: { href } } = Cast(Document[this.props.fieldKey], PdfField)!;
+        const { url: { href } } = Cast(this.dataDoc[this.props.fieldKey], PdfField)!;
         const pathCorrectionTest = /upload\_[a-z0-9]{32}.(.*)/g;
         const matches = pathCorrectionTest.exec(href);
         console.log("\nHere's the { url } being fed into the outer regex:");
@@ -78,9 +78,7 @@ export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument>
         }
     }
 
-    componentWillUnmount() {
-        this._selectReactionDisposer && this._selectReactionDisposer();
-    }
+    componentWillUnmount() { this._selectReactionDisposer?.(); }
     componentDidMount() {
         this._selectReactionDisposer = reaction(() => this.props.isSelected(),
             () => {
@@ -96,11 +94,11 @@ export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument>
         !this.Document._fitWidth && (this.Document._height = this.Document[WidthSym]() * (nh / nw));
     }
 
-    public search(string: string, fwd: boolean) { this._pdfViewer && this._pdfViewer.search(string, fwd); }
-    public prevAnnotation() { this._pdfViewer && this._pdfViewer.prevAnnotation(); }
-    public nextAnnotation() { this._pdfViewer && this._pdfViewer.nextAnnotation(); }
-    public backPage() { this._pdfViewer!.gotoPage((this.Document.curPage || 1) - 1); }
-    public forwardPage() { this._pdfViewer!.gotoPage((this.Document.curPage || 1) + 1); }
+    public search = (string: string, fwd: boolean) => { this._pdfViewer?.search(string, fwd); }
+    public prevAnnotation = () => { this._pdfViewer?.prevAnnotation(); }
+    public nextAnnotation = () => { this._pdfViewer?.nextAnnotation(); }
+    public backPage = () => { this._pdfViewer!.gotoPage((this.Document.curPage || 1) - 1); }
+    public forwardPage = () => { this._pdfViewer!.gotoPage((this.Document.curPage || 1) + 1); }
     public gotoPage = (p: number) => { this._pdfViewer!.gotoPage(p); };
 
     @undoBatch
@@ -233,7 +231,7 @@ export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument>
     isChildActive = (outsideReaction?: boolean) => this._isChildActive;
     @computed get renderPdfView() {
         const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField);
-        return <div className={"pdfBox"} onContextMenu={this.specificContextMenu}>
+        return <div className={"pdfBox"} onContextMenu={this.specificContextMenu} style={{ height: this.props.Document._scrollTop ? NumCast(this.Document._height) * this.props.PanelWidth() / NumCast(this.Document._width) : undefined }}>
             <PDFViewer {...this.props} pdf={this._pdf!} url={pdfUrl!.url.pathname} active={this.props.active} loaded={this.loaded}
                 setPdfViewer={this.setPdfViewer} ContainingCollectionView={this.props.ContainingCollectionView}
                 renderDepth={this.props.renderDepth} PanelHeight={this.props.PanelHeight} PanelWidth={this.props.PanelWidth}
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 198aeb856..b110aa172 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -4,7 +4,7 @@ import * as Pdfjs from "pdfjs-dist";
 import "pdfjs-dist/web/pdf_viewer.css";
 import { Dictionary } from "typescript-collections";
 import { Doc, DocListCast, FieldResult, WidthSym, Opt, HeightSym } from "../../../new_fields/Doc";
-import { Id } from "../../../new_fields/FieldSymbols";
+import { Id, Copy } from "../../../new_fields/FieldSymbols";
 import { List } from "../../../new_fields/List";
 import { makeInterface, createSchema } from "../../../new_fields/Schema";
 import { ScriptField } from "../../../new_fields/ScriptField";
@@ -31,6 +31,7 @@ import { InkingControl } from "../InkingControl";
 import { InkTool } from "../../../new_fields/InkField";
 import { TraceMobx } from "../../../new_fields/util";
 import { PdfField } from "../../../new_fields/URLField";
+import { PDFBox } from "../nodes/PDFBox";
 const PDFJSViewer = require("pdfjs-dist/web/pdf_viewer");
 const pdfjsLib = require("pdfjs-dist");
 
@@ -101,6 +102,7 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
     private _reactionDisposer?: IReactionDisposer;
     private _selectionReactionDisposer?: IReactionDisposer;
     private _annotationReactionDisposer?: IReactionDisposer;
+    private _scrollTopReactionDisposer?: IReactionDisposer;
     private _filterReactionDisposer?: IReactionDisposer;
     private _searchReactionDisposer?: IReactionDisposer;
     private _viewer: React.RefObject<HTMLDivElement> = React.createRef();
@@ -126,7 +128,7 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
         !this.props.Document.lockedTransform && (this.props.Document.lockedTransform = true);
         // change the address to be the file address of the PNG version of each page
         // file address of the pdf
-        const { url: { href } } = Cast(this.props.Document[this.props.fieldKey], PdfField)!;
+        const { url: { href } } = Cast(this.dataDoc[this.props.fieldKey], PdfField)!;
         const addr = Utils.prepend(`/thumbnail${this.props.url.substring("files/pdfs/".length, this.props.url.length - ".pdf".length)}-${(this.Document.curPage || 1)}.png`);
         this._coverPath = href.startsWith(window.location.origin) ? JSON.parse(await rp.get(addr)) : { width: 100, height: 100, path: "" };
         runInAction(() => this._showWaiting = this._showCover = true);
@@ -162,10 +164,11 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
 
     componentWillUnmount = () => {
         this._reactionDisposer && this._reactionDisposer();
-        this._annotationReactionDisposer && this._annotationReactionDisposer();
-        this._filterReactionDisposer && this._filterReactionDisposer();
-        this._selectionReactionDisposer && this._selectionReactionDisposer();
-        this._searchReactionDisposer && this._searchReactionDisposer();
+        this._scrollTopReactionDisposer?.();
+        this._annotationReactionDisposer?.();
+        this._filterReactionDisposer?.();
+        this._selectionReactionDisposer?.();
+        this._searchReactionDisposer?.();
         document.removeEventListener("copy", this.copy);
     }
 
@@ -206,6 +209,13 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
         this.props.setPdfViewer(this);
         await this.initialLoad();
 
+        this._scrollTopReactionDisposer = reaction(() => Cast(this.props.Document._scrollTop, "number", null),
+            (stop) => {
+                if (stop !== undefined) {
+                    const offset = this.visibleHeight() / 2 * 96 / 72;
+                    this._mainCont.current && smoothScroll(500, this._mainCont.current, stop);
+                }
+            }, { fireImmediately: true });
         this._annotationReactionDisposer = reaction(
             () => DocListCast(this.dataDoc[this.props.fieldKey + "-annotations"]),
             annotations => annotations?.length && (this._annotations = annotations),
@@ -267,7 +277,7 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
         let minY = Number.MAX_VALUE;
         if ((this._savedAnnotations.values()[0][0] as any).marqueeing) {
             const anno = this._savedAnnotations.values()[0][0];
-            const annoDoc = Docs.Create.FreeformDocument([], { backgroundColor: color, title: "Annotation on " + this.Document.title });
+            const annoDoc = Docs.Create.FreeformDocument([], { backgroundColor: color, _LODdisable: true, title: "Annotation on " + this.Document.title });
             if (anno.style.left) annoDoc.x = parseInt(anno.style.left);
             if (anno.style.top) annoDoc.y = parseInt(anno.style.top);
             if (anno.style.height) annoDoc._height = parseInt(anno.style.height);
@@ -558,28 +568,23 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
         e.preventDefault();
         e.stopPropagation();
         const targetDoc = Docs.Create.TextDocument("", { _width: 200, _height: 200, title: "Note linked to " + this.props.Document.title });
+        Doc.GetProto(targetDoc).snipped = this.dataDoc[this.props.fieldKey][Copy]();
+        const snipLayout = Docs.Create.PdfDocument("http://www.msn.com", { title: "snippetView", isTemplateDoc: true, isTemplateForField: "snipped", _fitWidth: true, _width: this.marqueeWidth(), _height: this.marqueeHeight(), _scrollTop: this.marqueeY() });
+        Doc.GetProto(snipLayout).layout = PDFBox.LayoutString("snipped");
+        Doc.GetProto(targetDoc).layout_snipped = snipLayout;
         const annotationDoc = this.highlight("rgba(146, 245, 95, 0.467)"); // yellowish highlight color when dragging out a text selection
         if (annotationDoc) {
             DragManager.StartPdfAnnoDrag([ele], new DragManager.PdfAnnoDragData(this.props.Document, annotationDoc, targetDoc), e.pageX, e.pageY, {
-                dragComplete: e => !e.aborted && e.annoDragData && !e.annoDragData.linkedToDoc &&
-                    DocUtils.MakeLink({ doc: annotationDoc }, { doc: e.annoDragData.dropDocument, ctx: e.annoDragData.targetContext }, `Annotation from ${this.Document.title}`, "link from PDF")
+                dragComplete: e => {
+                    if (!e.aborted && e.annoDragData && !e.annoDragData.linkedToDoc) {
+                        const link = DocUtils.MakeLink({ doc: annotationDoc }, { doc: e.annoDragData.dropDocument, ctx: e.annoDragData.targetContext }, `Annotation from ${this.Document.title}`, "link from PDF");
+                        if (link) link.maximizeLocation = "onRight";
+                    }
+                }
             });
         }
     }
 
-    createSnippet = (marquee: { left: number, top: number, width: number, height: number }): void => {
-        const view = Doc.MakeAlias(this.props.Document);
-        const data = Doc.MakeDelegate(Doc.GetProto(this.props.Document));
-        data.title = StrCast(data.title) + "_snippet";
-        view.proto = data;
-        view._nativeHeight = marquee.height;
-        view._height = (this.Document[WidthSym]() / (this.Document._nativeWidth || 1)) * marquee.height;
-        view._nativeWidth = this.Document._nativeWidth;
-        view.startY = marquee.top;
-        view._width = this.Document[WidthSym]();
-        DragManager.StartDocumentDrag([], new DragManager.DocumentDragData([view]), 0, 0);
-    }
-
     scrollXf = () => {
         return this._mainCont.current ? this.props.ScreenToLocalTransform().translate(0, this._scrollTop) : this.props.ScreenToLocalTransform();
     }
@@ -643,6 +648,7 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
                 select={emptyFunction}
                 active={this.annotationsActive}
                 ContentScaling={this.contentZoom}
+                bringToFront={emptyFunction}
                 whenActiveChanged={this.whenActiveChanged}
                 removeDocument={this.removeDocument}
                 moveDocument={this.moveDocument}
-- 
cgit v1.2.3-70-g09d2


From d4fd33ea337b1136344b73b92e9d5494ff94f090 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Tue, 3 Mar 2020 10:49:22 -0500
Subject: fixes to allow navigation from hashtag tag to pivot/schema/stacking
 views

---
 src/client/documents/Documents.ts                  |  2 +-
 src/client/util/RichTextRules.ts                   | 10 +++++
 src/client/util/RichTextSchema.tsx                 | 52 +++++++++++++++-------
 .../collections/CollectionMasonryViewFieldRow.tsx  | 12 ++---
 .../views/collections/CollectionStackingView.tsx   | 30 ++++++-------
 .../CollectionStackingViewFieldColumn.tsx          | 15 +++----
 .../views/collections/CollectionViewChromes.scss   | 16 +++----
 .../views/collections/CollectionViewChromes.tsx    | 20 ++++-----
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 24 +++++-----
 .../authentication/models/current_user_utils.ts    |  2 +-
 10 files changed, 105 insertions(+), 78 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 4df90ceb8..49e7520f2 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -122,7 +122,7 @@ export interface DocumentOptions {
     displayTimecode?: number; // the time that a document should be displayed (e.g., time an annotation should be displayed on a video)
     borderRounding?: string;
     boxShadow?: string;
-    sectionFilter?: string; // field key used to determine headings for sections in stacking and masonry views
+    _pivotField?: string; // field key used to determine headings for sections in stacking, masonry, pivot views
     schemaColumns?: List<SchemaHeaderField>;
     dockingConfig?: string;
     annotationOn?: Doc;
diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts
index af3b1a81e..70a1a5154 100644
--- a/src/client/util/RichTextRules.ts
+++ b/src/client/util/RichTextRules.ts
@@ -100,6 +100,16 @@ export class RichTextRules {
                     const fieldView = state.schema.nodes.dashField.create({ fieldKey, docid });
                     return state.tr.deleteRange(start, end).insert(start, fieldView);
                 }),
+            // create an inline view of a tag stored under the '#' field
+            new InputRule(
+                new RegExp(/#([a-zA-Z_\-0-9]+)\s$/),
+                (state, match, start, end) => {
+                    const tag = match[1];
+                    if (!tag) return state.tr;
+                    this.Document[DataSym]["#"] = tag;
+                    const fieldView = state.schema.nodes.dashField.create({ fieldKey: "#" });
+                    return state.tr.deleteRange(start, end).insert(start, fieldView);
+                }),
             // create an inline view of a document {{ <layoutKey> : <Doc> }}  // {{:Doc}} => show default view of document   {{<layout>}} => show layout for this doc   {{<layout> : Doc}} => show layout for another doc
             new InputRule(
                 new RegExp(/\{\{([a-zA-Z_ \-0-9]*)(:[a-zA-Z_ \-0-9]+)?\}\}$/),
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index b2ee7320a..c67b42766 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -25,7 +25,11 @@ import { CollectionSchemaBooleanCell } from "../views/collections/CollectionSche
 import { ContextMenu } from "../views/ContextMenu";
 import { ContextMenuProps } from "../views/ContextMenuItem";
 import { Docs } from "../documents/Documents";
-import { CollectionView } from "../views/collections/CollectionView";
+import { CollectionView, CollectionViewType } from "../views/collections/CollectionView";
+import { toBlob } from "html-to-image";
+import { listSpec } from "../../new_fields/Schema";
+import { List } from "../../new_fields/List";
+import { SchemaHeaderField } from "../../new_fields/SchemaHeaderField";
 
 const blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"],
     preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0];
@@ -880,7 +884,7 @@ export class DashFieldView {
         this._fieldSpan.contentEditable = "true";
         this._fieldSpan.style.position = "relative";
         this._fieldSpan.style.display = "inline-block";
-        this._fieldSpan.style.minWidth = "50px";
+        this._fieldSpan.style.minWidth = "5px";
         this._fieldSpan.style.backgroundColor = "rgba(155, 155, 155, 0.24)";
         this._fieldSpan.onkeypress = function (e: any) { e.stopPropagation(); };
         this._fieldSpan.onkeyup = function (e: any) { e.stopPropagation(); };
@@ -893,6 +897,21 @@ export class DashFieldView {
                 }, icon: "expand-arrows-alt"
             });
         };
+        this._fieldSpan.onblur = function (e: any) {
+            let newText = self._fieldSpan.innerText.startsWith(":=") ? ":=-computed-" : self._fieldSpan.innerText;
+            // look for a document whose id === the fieldKey being displayed.  If there's a match, then that document
+            // holds the different enumerated values for the field in the titles of its collected documents.
+            // if there's a partial match from the start of the input text, complete the text --- TODO: make this an auto suggest box and select from a drop down.
+
+            // alternatively, if the text starts with a ':=' then treat it as an expression by making a computed field from its value storing it in the key
+            DocServer.GetRefField(node.attrs.fieldKey).then(options => {
+                (options instanceof Doc) && DocListCast(options.data).forEach(opt => StrCast(opt.title).startsWith(newText) && (newText = StrCast(opt.title)));
+                self._fieldSpan.innerHTML = self._dashDoc![self._fieldKey] = newText;
+                if (newText.startsWith(":=") && self._dashDoc && e.data === null && !e.inputType.includes("delete")) {
+                    Doc.Layout(tbox.props.Document)[self._fieldKey] = ComputedField.MakeFunction(self._fieldSpan.innerText.substring(2));
+                }
+            });
+        }
 
         const setDashDoc = (doc: Doc) => {
             self._dashDoc = doc;
@@ -916,19 +935,7 @@ export class DashFieldView {
                 if (e.ctrlKey) {
                     Doc.addEnumerationToTextField(self._textBoxDoc, node.attrs.fieldKey, [Docs.Create.TextDocument(self._fieldSpan.innerText, { title: self._fieldSpan.innerText })]);
                 }
-                let newText = self._fieldSpan.innerText.startsWith(":=") ? ":=-computed-" : self._fieldSpan.innerText;
-                // look for a document whose id === the fieldKey being displayed.  If there's a match, then that document
-                // holds the different enumerated values for the field in the titles of its collected documents.
-                // if there's a partial match from the start of the input text, complete the text --- TODO: make this an auto suggest box and select from a drop down.
-
-                // alternatively, if the text starts with a ':=' then treat it as an expression by making a computed field from its value storing it in the key
-                DocServer.GetRefField(node.attrs.fieldKey).then(options => {
-                    (options instanceof Doc) && DocListCast(options.data).forEach(opt => StrCast(opt.title).startsWith(newText) && (newText = StrCast(opt.title)));
-                    self._fieldSpan.innerHTML = self._dashDoc![self._fieldKey] = newText;
-                    if (newText.startsWith(":=") && self._dashDoc && e.data === null && !e.inputType.includes("delete")) {
-                        Doc.Layout(tbox.props.Document)[self._fieldKey] = ComputedField.MakeFunction(self._fieldSpan.innerText.substring(2));
-                    }
-                });
+                self._fieldSpan.onblur?.(undefined as any);
             }
         };
 
@@ -937,6 +944,21 @@ export class DashFieldView {
         this._labelSpan.style.display = "inline";
         this._labelSpan.style.fontWeight = "bold";
         this._labelSpan.style.fontSize = "larger";
+        this._labelSpan.onpointerdown = function (e: any) {
+            e.stopPropagation();
+            if (tbox.props.ContainingCollectionDoc) {
+                const alias = Doc.MakeAlias(tbox.props.ContainingCollectionDoc);
+                alias.viewType = CollectionViewType.Time;
+                let list = Cast(alias.schemaColumns, listSpec(SchemaHeaderField));
+                if (!list) {
+                    alias.schemaColumns = list = new List<SchemaHeaderField>();
+                }
+                list.map(c => c.heading).indexOf("#") === -1 && list.push(new SchemaHeaderField("#", "#f1efeb"));
+                list.map(c => c.heading).indexOf("text") === -1 && list.push(new SchemaHeaderField("text", "#f1efeb"));
+                alias._pivotField = "#";
+                tbox.props.addDocTab(alias, "onRight");
+            }
+        }
         this._labelSpan.innerHTML = `${node.attrs.fieldKey}: `;
         if (node.attrs.docid) {
             DocServer.GetRefField(node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && runInAction(() => setDashDoc(dashDoc)));
diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
index 3c2cbb5b0..6ebd3194d 100644
--- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
+++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
@@ -80,7 +80,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
         if (de.complete.docDragData) {
             (this.props.parent.Document.dropConverter instanceof ScriptField) &&
                 this.props.parent.Document.dropConverter.script.run({ dragData: de.complete.docDragData });
-            const key = StrCast(this.props.parent.props.Document.sectionFilter);
+            const key = StrCast(this.props.parent.props.Document._pivotField);
             const castedValue = this.getValue(this._heading);
             de.complete.docDragData.droppedDocuments.forEach(d => d[key] = castedValue);
             this.props.parent.onInternalDrop(e, de);
@@ -99,7 +99,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
     @action
     headingChanged = (value: string, shiftDown?: boolean) => {
         this._createAliasSelected = false;
-        const key = StrCast(this.props.parent.props.Document.sectionFilter);
+        const key = StrCast(this.props.parent.props.Document._pivotField);
         const castedValue = this.getValue(value);
         if (castedValue) {
             if (this.props.parent.sectionHeaders) {
@@ -138,7 +138,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
     @action
     addDocument = (value: string, shiftDown?: boolean) => {
         this._createAliasSelected = false;
-        const key = StrCast(this.props.parent.props.Document.sectionFilter);
+        const key = StrCast(this.props.parent.props.Document._pivotField);
         const newDoc = Docs.Create.TextDocument("", { _height: 18, _width: 200, title: value });
         newDoc[key] = this.getValue(this.props.heading);
         return this.props.parent.props.addDocument(newDoc);
@@ -146,7 +146,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
 
     deleteRow = undoBatch(action(() => {
         this._createAliasSelected = false;
-        const key = StrCast(this.props.parent.props.Document.sectionFilter);
+        const key = StrCast(this.props.parent.props.Document._pivotField);
         this.props.docList.forEach(d => d[key] = undefined);
         if (this.props.parent.sectionHeaders && this.props.headingObject) {
             const index = this.props.parent.sectionHeaders.indexOf(this.props.headingObject);
@@ -168,7 +168,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
         const [dx, dy] = this.props.screenToLocalTransform().transformDirection(e.clientX - this._startDragPosition.x, e.clientY - this._startDragPosition.y);
         if (Math.abs(dx) + Math.abs(dy) > this._sensitivity) {
             const alias = Doc.MakeAlias(this.props.parent.props.Document);
-            const key = StrCast(this.props.parent.props.Document.sectionFilter);
+            const key = StrCast(this.props.parent.props.Document._pivotField);
             let value = this.getValue(this._heading);
             value = typeof value === "string" ? `"${value}"` : value;
             const script = `return doc.${key} === ${value}`;
@@ -296,7 +296,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
 
     @computed get headingView() {
         const heading = this._heading;
-        const key = StrCast(this.props.parent.props.Document.sectionFilter);
+        const key = StrCast(this.props.parent.props.Document._pivotField);
         const evContents = heading ? heading : this.props.type && this.props.type === "number" ? "0" : `NO ${key.toUpperCase()} VALUE`;
         const headerEditableViewProps = {
             GetValue: () => evContents,
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index d1f45af90..f84b0af20 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -31,21 +31,21 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     _masonryGridRef: HTMLDivElement | null = null;
     _draggerRef = React.createRef<HTMLDivElement>();
     _heightDisposer?: IReactionDisposer;
-    _sectionFilterDisposer?: IReactionDisposer;
+    _pivotFieldDisposer?: IReactionDisposer;
     _docXfs: any[] = [];
     _columnStart: number = 0;
     @observable _heightMap = new Map<string, number>();
     @observable _cursor: CursorProperty = "grab";
     @observable _scroll = 0; // used to force the document decoration to update when scrolling
     @computed get sectionHeaders() { return Cast(this.props.Document.sectionHeaders, listSpec(SchemaHeaderField)); }
-    @computed get sectionFilter() { return StrCast(this.props.Document.sectionFilter); }
+    @computed get pivotField() { return StrCast(this.props.Document._pivotField); }
     @computed get filteredChildren() { return this.childLayoutPairs.filter(pair => pair.layout instanceof Doc).map(pair => pair.layout); }
     @computed get xMargin() { return NumCast(this.props.Document._xMargin, 2 * Math.min(this.gridGap, .05 * this.props.PanelWidth())); }
     @computed get yMargin() { return Math.max(this.props.Document._showTitle && !this.props.Document._showTitleHover ? 30 : 0, NumCast(this.props.Document._yMargin, 0)); } // 2 * this.gridGap)); }
     @computed get gridGap() { return NumCast(this.props.Document._gridGap, 10); }
     @computed get isStackingView() { return BoolCast(this.props.Document.singleColumn, true); }
     @computed get numGroupColumns() { return this.isStackingView ? Math.max(1, this.Sections.size + (this.showAddAGroup ? 1 : 0)) : 1; }
-    @computed get showAddAGroup() { return (this.sectionFilter && (this.props.Document._chromeStatus !== 'view-mode' && this.props.Document._chromeStatus !== 'disabled')); }
+    @computed get showAddAGroup() { return (this.pivotField && (this.props.Document._chromeStatus !== 'view-mode' && this.props.Document._chromeStatus !== 'disabled')); }
     @computed get columnWidth() {
         return Math.min(this.props.PanelWidth() / (this.props as any).ContentScaling() - 2 * this.xMargin,
             this.isStackingView ? Number.MAX_VALUE : NumCast(this.props.Document.columnWidth, 250));
@@ -73,7 +73,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     }
 
     get Sections() {
-        if (!this.sectionFilter || this.sectionHeaders instanceof Promise) return new Map<SchemaHeaderField, Doc[]>();
+        if (!this.pivotField || this.sectionHeaders instanceof Promise) return new Map<SchemaHeaderField, Doc[]>();
 
         if (this.sectionHeaders === undefined) {
             setTimeout(() => this.props.Document.sectionHeaders = new List<SchemaHeaderField>(), 0);
@@ -83,18 +83,18 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         const fields = new Map<SchemaHeaderField, Doc[]>(sectionHeaders.map(sh => [sh, []] as [SchemaHeaderField, []]));
         let changed = false;
         this.filteredChildren.map(d => {
-            const sectionValue = (d[this.sectionFilter] ? d[this.sectionFilter] : `NO ${this.sectionFilter.toUpperCase()} VALUE`) as object;
+            const sectionValue = (d[this.pivotField] ? d[this.pivotField] : `NO ${this.pivotField.toUpperCase()} VALUE`) as object;
             // the next five lines ensures that floating point rounding errors don't create more than one section -syip
             const parsed = parseInt(sectionValue.toString());
             const castedSectionValue = !isNaN(parsed) ? parsed : sectionValue;
 
             // look for if header exists already
-            const existingHeader = sectionHeaders.find(sh => sh.heading === (castedSectionValue ? castedSectionValue.toString() : `NO ${this.sectionFilter.toUpperCase()} VALUE`));
+            const existingHeader = sectionHeaders.find(sh => sh.heading === (castedSectionValue ? castedSectionValue.toString() : `NO ${this.pivotField.toUpperCase()} VALUE`));
             if (existingHeader) {
                 fields.get(existingHeader)!.push(d);
             }
             else {
-                const newSchemaHeader = new SchemaHeaderField(castedSectionValue ? castedSectionValue.toString() : `NO ${this.sectionFilter.toUpperCase()} VALUE`);
+                const newSchemaHeader = new SchemaHeaderField(castedSectionValue ? castedSectionValue.toString() : `NO ${this.pivotField.toUpperCase()} VALUE`);
                 fields.set(newSchemaHeader, [d]);
                 sectionHeaders.push(newSchemaHeader);
                 changed = true;
@@ -134,15 +134,15 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         );
 
         // reset section headers when a new filter is inputted
-        this._sectionFilterDisposer = reaction(
-            () => this.sectionFilter,
+        this._pivotFieldDisposer = reaction(
+            () => this.pivotField,
             () => this.props.Document.sectionHeaders = new List()
         );
     }
     componentWillUnmount() {
         super.componentWillUnmount();
-        this._heightDisposer && this._heightDisposer();
-        this._sectionFilterDisposer && this._sectionFilterDisposer();
+        this._heightDisposer?.();
+        this._pivotFieldDisposer?.();
     }
 
     @action
@@ -278,7 +278,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     }
     headings = () => Array.from(this.Sections.keys());
     sectionStacking = (heading: SchemaHeaderField | undefined, docList: Doc[]) => {
-        const key = this.sectionFilter;
+        const key = this.pivotField;
         let type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined = undefined;
         const types = docList.length ? docList.map(d => typeof d[key]) : this.filteredChildren.map(d => typeof d[key]);
         if (types.map((i, idx) => types.indexOf(i) === idx).length === 1) {
@@ -313,7 +313,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     }
 
     sectionMasonry = (heading: SchemaHeaderField | undefined, docList: Doc[]) => {
-        const key = this.sectionFilter;
+        const key = this.pivotField;
         let type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined = undefined;
         const types = docList.length ? docList.map(d => typeof d[key]) : this.filteredChildren.map(d => typeof d[key]);
         if (types.map((i, idx) => types.indexOf(i) === idx).length === 1) {
@@ -341,7 +341,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         if (value && this.sectionHeaders) {
             const schemaHdrField = new SchemaHeaderField(value);
             this.sectionHeaders.push(schemaHdrField);
-            Doc.addEnumerationToTextField(undefined, this.sectionFilter, [Docs.Create.TextDocument(value, { title: value, _backgroundColor: schemaHdrField.color })]);
+            Doc.addEnumerationToTextField(undefined, this.pivotField, [Docs.Create.TextDocument(value, { title: value, _backgroundColor: schemaHdrField.color })]);
             return true;
         }
         return false;
@@ -370,7 +370,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     @computed get renderedSections() {
         TraceMobx();
         let sections = [[undefined, this.filteredChildren] as [SchemaHeaderField | undefined, Doc[]]];
-        if (this.sectionFilter) {
+        if (this.pivotField) {
             const entries = Array.from(this.Sections.entries());
             sections = entries.sort(this.sortFunc);
         }
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 516e583d4..646b433bf 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -23,7 +23,6 @@ import { CollectionStackingView } from "./CollectionStackingView";
 import { setupMoveUpEvents, emptyFunction } from "../../../Utils";
 import "./CollectionStackingView.scss";
 import { listSpec } from "../../../new_fields/Schema";
-import { Schema } from "prosemirror-model";
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -62,7 +61,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
     @undoBatch
     columnDrop = action((e: Event, de: DragManager.DropEvent) => {
         if (de.complete.docDragData) {
-            const key = StrCast(this.props.parent.props.Document.sectionFilter);
+            const key = StrCast(this.props.parent.props.Document._pivotField);
             const castedValue = this.getValue(this._heading);
             de.complete.docDragData.droppedDocuments.forEach(d => Doc.SetInPlace(d, key, castedValue, false));
             this.props.parent.onInternalDrop(e, de);
@@ -85,7 +84,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
 
     @action
     headingChanged = (value: string, shiftDown?: boolean) => {
-        const key = StrCast(this.props.parent.props.Document.sectionFilter);
+        const key = StrCast(this.props.parent.props.Document._pivotField);
         const castedValue = this.getValue(value);
         if (castedValue) {
             if (this.props.parent.sectionHeaders) {
@@ -126,7 +125,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
     @action
     addDocument = (value: string, shiftDown?: boolean) => {
         if (!value) return false;
-        const key = StrCast(this.props.parent.props.Document.sectionFilter);
+        const key = StrCast(this.props.parent.props.Document._pivotField);
         const newDoc = Docs.Create.TextDocument(value, { _height: 18, _width: 200, title: value, _autoHeight: true });
         newDoc[key] = this.getValue(this.props.heading);
         const maxHeading = this.props.docList.reduce((maxHeading, doc) => NumCast(doc.heading) > maxHeading ? NumCast(doc.heading) : maxHeading, 0);
@@ -137,7 +136,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
 
     @action
     deleteColumn = () => {
-        const key = StrCast(this.props.parent.props.Document.sectionFilter);
+        const key = StrCast(this.props.parent.props.Document._pivotField);
         this.props.docList.forEach(d => d[key] = undefined);
         if (this.props.parent.sectionHeaders && this.props.headingObject) {
             const index = this.props.parent.sectionHeaders.indexOf(this.props.headingObject);
@@ -161,8 +160,8 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
     startDrag = (e: PointerEvent, down: number[], delta: number[]) => {
         const alias = Doc.MakeAlias(this.props.parent.props.Document);
         alias._width = this.props.parent.props.PanelWidth() / (Cast(this.props.parent.props.Document.sectionHeaders, listSpec(SchemaHeaderField))?.length || 1);
-        alias.sectionFilter = undefined;
-        const key = StrCast(this.props.parent.props.Document.sectionFilter);
+        alias._pivotField = undefined;
+        const key = StrCast(this.props.parent.props.Document._pivotField);
         let value = this.getValue(this._heading);
         value = typeof value === "string" ? `"${value}"` : value;
         alias.viewSpecScript = ScriptField.MakeFunction(`doc.${key} === ${value}`, { doc: Doc.name });
@@ -277,7 +276,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
     render() {
         TraceMobx();
         const cols = this.props.cols();
-        const key = StrCast(this.props.parent.props.Document.sectionFilter);
+        const key = StrCast(this.props.parent.props.Document._pivotField);
         let templatecols = "";
         const headings = this.props.headings();
         const heading = this._heading;
diff --git a/src/client/views/collections/CollectionViewChromes.scss b/src/client/views/collections/CollectionViewChromes.scss
index 8602b2369..19a14976c 100644
--- a/src/client/views/collections/CollectionViewChromes.scss
+++ b/src/client/views/collections/CollectionViewChromes.scss
@@ -169,21 +169,21 @@
         }
 
 
-        .collectionStackingViewChrome-sectionFilter-cont,
-        .collectionTreeViewChrome-sectionFilter-cont {
+        .collectionStackingViewChrome-pivotField-cont,
+        .collectionTreeViewChrome-pivotField-cont {
             justify-self: right;
             display: flex;
             font-size: 75%;
             letter-spacing: 2px;
 
-            .collectionStackingViewChrome-sectionFilter-label,
-            .collectionTreeViewChrome-sectionFilter-label {
+            .collectionStackingViewChrome-pivotField-label,
+            .collectionTreeViewChrome-pivotField-label {
                 vertical-align: center;
                 padding: 10px;
             }
 
-            .collectionStackingViewChrome-sectionFilter,
-            .collectionTreeViewChrome-sectionFilter {
+            .collectionStackingViewChrome-pivotField,
+            .collectionTreeViewChrome-pivotField {
                 color: white;
                 width: 100px;
                 text-align: center;
@@ -210,8 +210,8 @@
             }
         }
 
-        .collectionStackingViewChrome-sectionFilter:hover,
-        .collectionTreeViewChrome-sectionFilter:hover {
+        .collectionStackingViewChrome-pivotField:hover,
+        .collectionTreeViewChrome-pivotField:hover {
             cursor: text;
         }
     }
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index 4f504ab1c..57a73000d 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -166,7 +166,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
             this._viewSpecsOpen = true;
 
             //@ts-ignore
-            if (!e.target ?.classList[0] ?.startsWith("qs")) {
+            if (!e.target?.classList[0]?.startsWith("qs")) {
                 this.closeDatePicker();
             }
 
@@ -291,7 +291,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
     @action
     protected drop(e: Event, de: DragManager.DropEvent): boolean {
         if (de.complete.docDragData && de.complete.docDragData.draggedDocuments.length) {
-            this._buttonizableCommands.filter(c => c.title === this._currentKey).map(c => c.immediate(de.complete.docDragData ?.draggedDocuments || []));
+            this._buttonizableCommands.filter(c => c.title === this._currentKey).map(c => c.immediate(de.complete.docDragData?.draggedDocuments || []));
             e.stopPropagation();
         }
         return true;
@@ -472,7 +472,7 @@ export class CollectionStackingViewChrome extends React.Component<CollectionView
     @observable private suggestions: string[] = [];
 
     @computed private get descending() { return BoolCast(this.props.CollectionView.props.Document.stackingHeadersSortDescending); }
-    @computed get sectionFilter() { return StrCast(this.props.CollectionView.props.Document.sectionFilter); }
+    @computed get pivotField() { return StrCast(this.props.CollectionView.props.Document._pivotField); }
 
     getKeySuggestions = async (value: string): Promise<string[]> => {
         value = value.toLowerCase();
@@ -510,26 +510,26 @@ export class CollectionStackingViewChrome extends React.Component<CollectionView
     }
 
     setValue = (value: string) => {
-        this.props.CollectionView.props.Document.sectionFilter = value;
+        this.props.CollectionView.props.Document._pivotField = value;
         return true;
     }
 
     @action toggleSort = () => { this.props.CollectionView.props.Document.stackingHeadersSortDescending = !this.props.CollectionView.props.Document.stackingHeadersSortDescending; };
-    @action resetValue = () => { this._currentKey = this.sectionFilter; };
+    @action resetValue = () => { this._currentKey = this.pivotField; };
 
     render() {
         return (
             <div className="collectionStackingViewChrome-cont">
-                <div className="collectionStackingViewChrome-sectionFilter-cont">
-                    <div className="collectionStackingViewChrome-sectionFilter-label">
+                <div className="collectionStackingViewChrome-pivotField-cont">
+                    <div className="collectionStackingViewChrome-pivotField-label">
                         GROUP ITEMS BY:
                     </div>
                     <div className="collectionStackingViewChrome-sortIcon" onClick={this.toggleSort} style={{ transform: `rotate(${this.descending ? "180" : "0"}deg)` }}>
                         <FontAwesomeIcon icon="caret-up" size="2x" color="white" />
                     </div>
-                    <div className="collectionStackingViewChrome-sectionFilter">
+                    <div className="collectionStackingViewChrome-pivotField">
                         <EditableView
-                            GetValue={() => this.sectionFilter}
+                            GetValue={() => this.pivotField}
                             autosuggestProps={
                                 {
                                     resetValue: this.resetValue,
@@ -551,7 +551,7 @@ export class CollectionStackingViewChrome extends React.Component<CollectionView
                                 }}
                             oneLine
                             SetValue={this.setValue}
-                            contents={this.sectionFilter ? this.sectionFilter : "N/A"}
+                            contents={this.pivotField ? this.pivotField : "N/A"}
                         />
                     </div>
                 </div>
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 055be7f86..ea86bff99 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,20 +1,22 @@
 import { library } from "@fortawesome/fontawesome-svg-core";
 import { faEye } from "@fortawesome/free-regular-svg-icons";
-import { faBraille, faChalkboard, faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faFileUpload, faPaintBrush, faTable, faUpload, faTextHeight } from "@fortawesome/free-solid-svg-icons";
-import { action, computed, observable, ObservableMap, reaction, runInAction, IReactionDisposer } from "mobx";
+import { faBraille, faChalkboard, faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faFileUpload, faPaintBrush, faTable, faUpload } from "@fortawesome/free-solid-svg-icons";
+import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import { computedFn } from "mobx-utils";
-import { Doc, DocListCast, HeightSym, Opt, WidthSym, DocCastAsync } from "../../../../new_fields/Doc";
+import { Doc, HeightSym, Opt, WidthSym } from "../../../../new_fields/Doc";
 import { documentSchema, positionSchema } from "../../../../new_fields/documentSchemas";
 import { Id } from "../../../../new_fields/FieldSymbols";
-import { InkTool, InkField, InkData } from "../../../../new_fields/InkField";
+import { InkData, InkField, InkTool } from "../../../../new_fields/InkField";
+import { List } from "../../../../new_fields/List";
+import { RichTextField } from "../../../../new_fields/RichTextField";
 import { createSchema, listSpec, makeInterface } from "../../../../new_fields/Schema";
 import { ScriptField } from "../../../../new_fields/ScriptField";
-import { Cast, NumCast, ScriptCast, BoolCast, StrCast, FieldValue } from "../../../../new_fields/Types";
+import { BoolCast, Cast, FieldValue, NumCast, ScriptCast, StrCast } from "../../../../new_fields/Types";
 import { TraceMobx } from "../../../../new_fields/util";
 import { GestureUtils } from "../../../../pen-gestures/GestureUtils";
-import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils";
 import { aggregateBounds, intersectRect, returnOne, Utils } from "../../../../Utils";
+import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
 import { DocServer } from "../../../DocServer";
 import { Docs } from "../../../documents/Documents";
 import { DocumentManager } from "../../../util/DocumentManager";
@@ -29,10 +31,11 @@ import { ContextMenu } from "../../ContextMenu";
 import { ContextMenuProps } from "../../ContextMenuItem";
 import { InkingControl } from "../../InkingControl";
 import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView";
-import { DocumentContentsView } from "../../nodes/DocumentContentsView";
+import { DocumentViewProps } from "../../nodes/DocumentView";
 import { FormattedTextBox } from "../../nodes/FormattedTextBox";
 import { pageSchema } from "../../nodes/ImageBox";
 import PDFMenu from "../../pdf/PDFMenu";
+import { CollectionDockingView } from "../CollectionDockingView";
 import { CollectionSubView } from "../CollectionSubView";
 import { computePivotLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult } from "./CollectionFreeFormLayoutEngines";
 import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors";
@@ -40,13 +43,6 @@ import "./CollectionFreeFormView.scss";
 import MarqueeOptionsMenu from "./MarqueeOptionsMenu";
 import { MarqueeView } from "./MarqueeView";
 import React = require("react");
-import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
-import { RichTextField } from "../../../../new_fields/RichTextField";
-import { List } from "../../../../new_fields/List";
-import { DocumentViewProps } from "../../nodes/DocumentView";
-import { CollectionDockingView } from "../CollectionDockingView";
-import { MainView } from "../../MainView";
-import { TouchScrollableMenuItem } from "../../TouchScrollableMenu";
 
 library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload);
 
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 6216ab7e6..0f8d8fec8 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -254,7 +254,7 @@ export class CurrentUserUtils {
 
         // Finally, setup the list of buttons to display in the sidebar
         doc.sidebarButtons = Docs.Create.StackingDocument([doc.SearchBtn as Doc, doc.LibraryBtn as Doc, doc.ToolsBtn as Doc], {
-            _width: 500, _height: 80, boxShadow: "0 0", sectionFilter: "title", hideHeadings: true, ignoreClick: true,
+            _width: 500, _height: 80, boxShadow: "0 0", _pivotField: "title", hideHeadings: true, ignoreClick: true,
             _chromeStatus: "disabled", title: "library stack", backgroundColor: "dimGray",
         });
     }
-- 
cgit v1.2.3-70-g09d2


From 2721713f409045594fa9fbf1d23bc5e4d9a08641 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Tue, 3 Mar 2020 12:08:52 -0500
Subject: fixed opening of metadata from text box.  fixed minimizing
 presentatino view.  cleaned up closeRightSplit.

---
 src/client/util/DocumentManager.ts                 |  2 +-
 src/client/util/RichTextSchema.tsx                 |  6 ++--
 .../views/collections/CollectionDockingView.tsx    | 42 ++++++++--------------
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 14 +-------
 src/client/views/nodes/PresBox.tsx                 |  3 +-
 5 files changed, 22 insertions(+), 45 deletions(-)

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

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 6bd2fd016..0ec1d23d9 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -55,7 +55,7 @@ export class DocumentManager {
     }
 
     public getDocumentViewById(id: string, preferredCollection?: CollectionView): DocumentView | undefined {
-
+        if (!id) return undefined;
         let toReturn: DocumentView | undefined;
         const passes = preferredCollection ? [preferredCollection, undefined] : [undefined];
 
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index c67b42766..d66890cda 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -940,10 +940,12 @@ export class DashFieldView {
         };
 
         this._labelSpan = document.createElement("span");
+        this._labelSpan.style.backgroundColor = "rgba(155, 155, 155, 0.44)";
         this._labelSpan.style.position = "relative";
         this._labelSpan.style.display = "inline";
         this._labelSpan.style.fontWeight = "bold";
         this._labelSpan.style.fontSize = "larger";
+        this._labelSpan.title = "click to see related tags";
         this._labelSpan.onpointerdown = function (e: any) {
             e.stopPropagation();
             if (tbox.props.ContainingCollectionDoc) {
@@ -953,9 +955,9 @@ export class DashFieldView {
                 if (!list) {
                     alias.schemaColumns = list = new List<SchemaHeaderField>();
                 }
-                list.map(c => c.heading).indexOf("#") === -1 && list.push(new SchemaHeaderField("#", "#f1efeb"));
+                list.map(c => c.heading).indexOf(self._fieldKey) === -1 && list.push(new SchemaHeaderField(self._fieldKey, "#f1efeb"));
                 list.map(c => c.heading).indexOf("text") === -1 && list.push(new SchemaHeaderField("text", "#f1efeb"));
-                alias._pivotField = "#";
+                alias._pivotField = self._fieldKey;
                 tbox.props.addDocTab(alias, "onRight");
             }
         }
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index b85cc9b56..db8f7d5e4 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -127,36 +127,22 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
     @undoBatch
     @action
     public static CloseRightSplit(document: Opt<Doc>): boolean {
-        if (!CollectionDockingView.Instance) return false;
         const instance = CollectionDockingView.Instance;
-        let retVal = false;
-        if (instance._goldenLayout.root.contentItems[0].isRow) {
-            retVal = Array.from(instance._goldenLayout.root.contentItems[0].contentItems).some((child: any) => {
-                if (child.contentItems.length === 1 && child.contentItems[0].config.component === "DocumentFrameRenderer" &&
-                    DocumentManager.Instance.getDocumentViewById(child.contentItems[0].config.props.documentId) &&
-                    ((!document && DocumentManager.Instance.getDocumentViewById(child.contentItems[0].config.props.documentId)!.Document.isDisplayPanel) ||
-                        (document && Doc.AreProtosEqual(DocumentManager.Instance.getDocumentViewById(child.contentItems[0].config.props.documentId)!.Document, document)))) {
-                    child.contentItems[0].remove();
+        const tryClose = (childItem: any) => {
+            if (childItem.config?.component === "DocumentFrameRenderer") {
+                const docView = DocumentManager.Instance.getDocumentViewById(childItem.config.props.documentId);
+                if (docView && ((!document && docView.Document.isDisplayPanel) || (document && Doc.AreProtosEqual(docView.props.Document, document)))) {
+                    childItem.remove();
                     instance.layoutChanged(document);
                     return true;
-                } else {
-                    Array.from(child.contentItems).filter((tab: any) => tab.config.component === "DocumentFrameRenderer").some((tab: any, j: number) => {
-                        if (DocumentManager.Instance.getDocumentViewById(tab.config.props.documentId) &&
-                            ((!document && DocumentManager.Instance.getDocumentViewById(tab.config.props.documentId)!.Document.isDisplayPanel) ||
-                                (document && Doc.AreProtosEqual(DocumentManager.Instance.getDocumentViewById(tab.config.props.documentId)!.Document, document)))) {
-                            child.contentItems[j].remove();
-                            child.config.activeItemIndex = Math.max(child.contentItems.length - 1, 0);
-                            return true;
-                        }
-                        return false;
-                    });
                 }
-                return false;
-            });
-        }
-        if (retVal) {
-            instance.stateChanged();
+            }
+            return false;
         }
+        let retVal = !instance?._goldenLayout.root.contentItems[0].isRow ? false :
+            Array.from(instance._goldenLayout.root.contentItems[0].contentItems).some((child: any) => Array.from(child.contentItems).some(tryClose));
+
+        retVal && instance.stateChanged();
         return retVal;
     }
 
@@ -240,16 +226,16 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
 
 
     //
-    //  Creates a split on the any side of the docking view, based on the passed input pullSide and then adds the Document to the requested side
+    //  Creates a split on any side of the docking view based on the passed input pullSide and then adds the Document to the requested side
     //
     @undoBatch
     @action
-    public static AddSplit(document: Doc, pullSide: string, dataDoc: Doc | undefined, libraryPath?: Doc[]) {
+    public static AddSplit(document: Doc, pullSide: string, libraryPath?: Doc[]) {
         if (!CollectionDockingView.Instance) return false;
         const instance = CollectionDockingView.Instance;
         const newItemStackConfig = {
             type: 'stack',
-            content: [CollectionDockingView.makeDocumentConfig(document, dataDoc, undefined, libraryPath)]
+            content: [CollectionDockingView.makeDocumentConfig(document, undefined, libraryPath)]
         };
 
         const newContentItem = instance._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, instance._goldenLayout);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index ea86bff99..a331f736f 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -701,25 +701,13 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     }
 
     cleanUpInteractions = () => {
-
         switch (this._pullDirection) {
-
             case "left":
-                CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([], { title: "New Collection" }), "left", undefined);
-                break;
             case "right":
-                CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([], { title: "New Collection" }), "right", undefined);
-                break;
             case "top":
-                CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([], { title: "New Collection" }), "top", undefined);
-                break;
             case "bottom":
-                CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([], { title: "New Collection" }), "bottom", undefined);
-                break;
-            default:
-                break;
+                CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([], { title: "New Collection" }), this._pullDirection);
         }
-        console.log("");
 
         this._pullDirection = "";
         this._pullCoords = [0, 0];
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index 4180ee255..27c2d6957 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -340,7 +340,7 @@ export class PresBox extends React.Component<FieldViewProps> {
         const funcs: ContextMenuProps[] = [];
         funcs.push({ description: "Show as Slideshow", event: action(() => this.props.Document._viewType = CollectionViewType.Carousel), icon: "asterisk" });
         funcs.push({ description: "Show as Timeline", event: action(() => this.props.Document._viewType = CollectionViewType.Time), icon: "asterisk" });
-        funcs.push({ description: "Show as List", event: action(() => this.props.Document._viewType = CollectionViewType.Invalid), icon: "asterisk" });
+        funcs.push({ description: "Show as List", event: action(() => { this.props.Document._viewType = CollectionViewType.Stacking; this.props.Document._pivotField = undefined; }), icon: "asterisk" });
         ContextMenu.Instance.addItem({ description: "Presentation Funcs...", subitems: funcs, icon: "asterisk" });
     }
 
@@ -377,6 +377,7 @@ export class PresBox extends React.Component<FieldViewProps> {
     viewChanged = action((e: React.ChangeEvent) => {
         //@ts-ignore
         this.props.Document._viewType = Number(e.target.selectedOptions[0].value);
+        this.props.Document._viewType === CollectionViewType.Stacking && (this.props.Document._pivotField = undefined);
         this.updateMinimize(e, Number(this.props.Document._viewType));
     });
 
-- 
cgit v1.2.3-70-g09d2


From 6a2b210f32cb70646a5d9097e667c0d199057901 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Fri, 6 Mar 2020 13:06:34 -0500
Subject: fixes to documentBox to work with templates.  changed tree view to
 select documents.  fixed panning in freeform view to account for scale
 correctly.  increased max renderdepth to 12

---
 .../views/collections/CollectionTreeView.tsx       |  5 ++++
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  3 +-
 src/client/views/nodes/DocumentBox.tsx             | 33 ++++++++++++----------
 src/client/views/nodes/DocumentContentsView.tsx    |  2 +-
 4 files changed, 25 insertions(+), 18 deletions(-)

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

diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 28f620157..deff3d177 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -184,6 +184,11 @@ class TreeView extends React.Component<TreeViewProps> {
             EditableView.loadId = doc[Id];
             return this.props.addDocument(doc);
         })}
+        onClick={() => {
+            SelectionManager.DeselectAll();
+            Doc.UserDoc().SelectedDocs = new List([this.props.document]);
+            return false;
+        }}
         OnTab={undoBatch((shift?: boolean) => {
             EditableView.loadId = this.dataDoc[Id];
             shift ? this.props.outdentDocument?.() : this.props.indentDocument?.();
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index a331f736f..28f8bc048 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -542,8 +542,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             }, [[minx, maxx], [miny, maxy]]);
 
             const cscale = this.props.ContainingCollectionDoc ? NumCast(this.props.ContainingCollectionDoc.scale) : 1;
-            const panelDim = this.props.ScreenToLocalTransform().transformDirection(this.props.PanelWidth() / this.zoomScaling() * cscale,
-                this.props.PanelHeight() / this.zoomScaling() * cscale);
+            const panelDim = [this.props.PanelWidth() * cscale / this.zoomScaling(), this.props.PanelHeight() * cscale / this.zoomScaling()];
             if (ranges[0][0] - dx > (this.panX() + panelDim[0] / 2)) x = ranges[0][1] + panelDim[0] / 2;
             if (ranges[0][1] - dx < (this.panX() - panelDim[0] / 2)) x = ranges[0][0] - panelDim[0] / 2;
             if (ranges[1][0] - dy > (this.panY() + panelDim[1] / 2)) y = ranges[1][1] + panelDim[1] / 2;
diff --git a/src/client/views/nodes/DocumentBox.tsx b/src/client/views/nodes/DocumentBox.tsx
index db7be334f..f04962db9 100644
--- a/src/client/views/nodes/DocumentBox.tsx
+++ b/src/client/views/nodes/DocumentBox.tsx
@@ -1,17 +1,16 @@
-import { IReactionDisposer, reaction } from "mobx";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { IReactionDisposer, reaction, computed } from "mobx";
 import { observer } from "mobx-react";
 import { Doc, Field } from "../../../new_fields/Doc";
 import { documentSchema } from "../../../new_fields/documentSchemas";
-import { List } from "../../../new_fields/List";
 import { makeInterface } from "../../../new_fields/Schema";
 import { ComputedField } from "../../../new_fields/ScriptField";
-import { Cast, StrCast, BoolCast } from "../../../new_fields/Types";
-import { emptyFunction, emptyPath } from "../../../Utils";
+import { Cast, StrCast } from "../../../new_fields/Types";
+import { emptyPath } from "../../../Utils";
 import { ContextMenu } from "../ContextMenu";
 import { ContextMenuProps } from "../ContextMenuItem";
-import { DocComponent, DocAnnotatableComponent } from "../DocComponent";
+import { DocAnnotatableComponent } from "../DocComponent";
 import { ContentFittingDocumentView } from "./ContentFittingDocumentView";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import "./DocumentBox.scss";
 import { FieldView, FieldViewProps } from "./FieldView";
 import React = require("react");
@@ -26,8 +25,8 @@ export class DocumentBox extends DocAnnotatableComponent<FieldViewProps, DocBoxS
     _selections: Doc[] = [];
     _curSelection = -1;
     componentDidMount() {
-        this._prevSelectionDisposer = reaction(() => Cast(this.props.Document[this.props.fieldKey], Doc) as Doc, (data) => {
-            if (data && !this.isSelectionLocked()) {
+        this._prevSelectionDisposer = reaction(() => this.contentDoc[this.props.fieldKey], (data) => {
+            if (data instanceof Doc && !this.isSelectionLocked()) {
                 this._selections.indexOf(data) !== -1 && this._selections.splice(this._selections.indexOf(data), 1);
                 this._selections.push(data);
                 this._curSelection = this._selections.length - 1;
@@ -40,19 +39,23 @@ export class DocumentBox extends DocAnnotatableComponent<FieldViewProps, DocBoxS
     specificContextMenu = (e: React.MouseEvent): void => {
         const funcs: ContextMenuProps[] = [];
         funcs.push({ description: (this.isSelectionLocked() ? "Show" : "Lock") + " Selection", event: () => this.toggleLockSelection, icon: "expand-arrows-alt" });
+        funcs.push({ description: (this.props.Document.excludeCollections ? "Include" : "Exclude") + " Collections", event: () => this.props.Document.excludeCollections = !this.props.Document.excludeCollections, icon: "expand-arrows-alt" });
         funcs.push({ description: `${this.props.Document.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.props.Document.forceActive = !this.props.Document.forceActive, icon: "project-diagram" });
 
         ContextMenu.Instance.addItem({ description: "DocumentBox Funcs...", subitems: funcs, icon: "asterisk" });
     }
+    @computed get contentDoc() {
+        return (this.props.Document.isTemplateDoc || this.props.Document.isTemplateForField ? this.props.Document : Doc.GetProto(this.props.Document));
+    }
     lockSelection = () => {
-        Doc.GetProto(this.props.Document)[this.props.fieldKey] = this.props.Document[this.props.fieldKey];
+        this.contentDoc[this.props.fieldKey] = this.props.Document[this.props.fieldKey];
     }
     showSelection = () => {
-        Doc.GetProto(this.props.Document)[this.props.fieldKey] = ComputedField.MakeFunction("selectedDocs(this,true,[_last_])?.[0]");
+        this.contentDoc[this.props.fieldKey] = ComputedField.MakeFunction(`selectedDocs(this,this.excludeCollections,[_last_])?.[0]`);
     }
     isSelectionLocked = () => {
-        const kvpstring = Field.toKeyValueString(this.props.Document, this.props.fieldKey);
-        return !(kvpstring.startsWith("=") || kvpstring.startsWith(":="));
+        const kvpstring = Field.toKeyValueString(this.contentDoc, this.props.fieldKey);
+        return !kvpstring || kvpstring.includes("DOC");
     }
     toggleLockSelection = () => {
         !this.isSelectionLocked() ? this.lockSelection() : this.showSelection();
@@ -61,13 +64,13 @@ export class DocumentBox extends DocAnnotatableComponent<FieldViewProps, DocBoxS
     prevSelection = () => {
         this.lockSelection();
         if (this._curSelection > 0) {
-            Doc.GetProto(this.props.Document)[this.props.fieldKey] = this._selections[--this._curSelection];
+            this.contentDoc[this.props.fieldKey] = this._selections[--this._curSelection];
             return true;
         }
     }
     nextSelection = () => {
         if (this._curSelection < this._selections.length - 1 && this._selections.length) {
-            Doc.GetProto(this.props.Document)[this.props.fieldKey] = this._selections[++this._curSelection];
+            this.contentDoc[this.props.fieldKey] = this._selections[++this._curSelection];
             return true;
         }
     }
@@ -94,7 +97,7 @@ export class DocumentBox extends DocAnnotatableComponent<FieldViewProps, DocBoxS
     pheight = () => this.props.PanelHeight() - 30;
     getTransform = () => this.props.ScreenToLocalTransform().translate(-15, -15);
     render() {
-        const containedDoc = this.dataDoc[this.props.fieldKey] as Doc;
+        const containedDoc = this.contentDoc[this.props.fieldKey];
         return <div className="documentBox-container" ref={this._contRef}
             onContextMenu={this.specificContextMenu}
             onPointerDown={this.onPointerDown} onClick={this.onClick}
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index dcb6d4a31..f121ba3e0 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -91,7 +91,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
 
     render() {
         TraceMobx();
-        return (this.props.renderDepth > 7 || !this.layout || !this.layoutDoc) ? (null) :
+        return (this.props.renderDepth > 12 || !this.layout || !this.layoutDoc) ? (null) :
             <ObserverJsxParser
                 blacklistedAttrs={[]}
                 components={{
-- 
cgit v1.2.3-70-g09d2


From 636e6880a52d3a1a3e24437f47194a902731401d Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 9 Mar 2020 16:39:42 -0400
Subject: cleanup of warnings

---
 src/client/cognitive_services/CognitiveServices.ts   |  8 ++++----
 src/client/util/DocumentManager.ts                   |  2 +-
 src/client/util/DragManager.ts                       |  2 +-
 src/client/util/ProsemirrorExampleTransfer.ts        |  2 +-
 src/client/util/RichTextSchema.tsx                   | 14 +++++++-------
 src/client/util/SearchUtil.ts                        |  4 ++--
 src/client/views/KeyphraseQueryView.tsx              |  4 ++--
 .../views/collections/CollectionDockingView.tsx      |  4 ++--
 src/client/views/collections/CollectionSubView.tsx   |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx    |  2 +-
 .../CollectionMulticolumnView.tsx                    |  2 +-
 src/client/views/linking/LinkEditor.tsx              |  4 ++--
 src/client/views/nodes/AudioBox.tsx                  |  6 ++----
 src/client/views/nodes/DocuLinkBox.tsx               |  4 ++--
 src/client/views/nodes/DocumentView.tsx              | 14 +++++++-------
 src/client/views/nodes/FormattedTextBox.tsx          | 20 ++++++++------------
 src/client/views/nodes/PDFBox.tsx                    | 10 +++++-----
 17 files changed, 49 insertions(+), 55 deletions(-)

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

diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts
index ce829eb1e..542ccf04d 100644
--- a/src/client/cognitive_services/CognitiveServices.ts
+++ b/src/client/cognitive_services/CognitiveServices.ts
@@ -371,9 +371,9 @@ export namespace CognitiveServices {
                 let args = { method: 'POST', uri: Utils.prepend("/recommender"), body: { keyphrases: keyterms }, json: true };
                 await requestPromise.post(args).then(async (wordvecs) => {
                     if (wordvecs) {
-                        let indices = Object.keys(wordvecs);
+                        const indices = Object.keys(wordvecs);
                         console.log("successful vectorization!");
-                        var vectorValues = new List<number>();
+                        const vectorValues = new List<number>();
                         indices.forEach((ind: any) => {
                             //console.log(wordvec.word);
                             vectorValues.push(wordvecs[ind]);
@@ -389,9 +389,9 @@ export namespace CognitiveServices {
             }
 
             export const analyzer = async (dataDoc: Doc, target: Doc, keys: string[], data: string, converter: TextConverter, isMainDoc: boolean = false, isInternal: boolean = true) => {
-                let results = await ExecuteQuery(Service.Text, Manager, data);
+                const results = await ExecuteQuery(Service.Text, Manager, data);
                 console.log("Cognitive Services keyphrases: ", results);
-                let { keyterms, external_recommendations, kp_string } = await converter(results, data);
+                const { keyterms, external_recommendations, kp_string } = await converter(results, data);
                 target[keys[0]] = keyterms;
                 if (isInternal) {
                     //await vectorize([data], dataDoc, isMainDoc);
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 162a8fffe..c41304b9f 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -209,7 +209,7 @@ export class DocumentManager {
             const maxLocation = StrCast(linkDoc.maximizeLocation, "inTab");
             const targetContext = !Doc.AreProtosEqual(linkFollowDocContexts[reverse ? 1 : 0], currentContext) ? linkFollowDocContexts[reverse ? 1 : 0] : undefined;
             const target = linkFollowDocs[reverse ? 1 : 0];
-            let annotatedDoc = await Cast(target.annotationOn, Doc);
+            const annotatedDoc = await Cast(target.annotationOn, Doc);
             if (annotatedDoc) {
                 annotatedDoc.currentTimecode !== undefined && (target.currentTimecode = linkFollowTimecodes[reverse ? 1 : 0]);
             } else {
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index dab5c842c..c11675894 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -198,7 +198,7 @@ export namespace DragManager {
             dropDoc && !dropDoc.creationDate && (dropDoc.creationDate = new DateField);
             dropDoc instanceof Doc && AudioBox.ActiveRecordings.map(d => DocUtils.MakeLink({ doc: dropDoc }, { doc: d }, "audio link", "audio timeline"));
             return dropDoc;
-        }
+        };
         const finishDrag = (e: DragCompleteEvent) => {
             e.docDragData && (e.docDragData.droppedDocuments =
                 dragData.draggedDocuments.map(d => !dragData.isSelectionMove && !dragData.userDropAction && ScriptCast(d.onDragStart) ? addAudioTag(ScriptCast(d.onDragStart).script.run({ this: d }).result) :
diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts
index ec5d1e72a..5cbf401d4 100644
--- a/src/client/util/ProsemirrorExampleTransfer.ts
+++ b/src/client/util/ProsemirrorExampleTransfer.ts
@@ -180,7 +180,7 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, props: any
             return true;
         }
         return false;
-    }
+    };
     bind("Alt-Enter", (state: EditorState<S>, dispatch: (tx: Transaction<Schema<any, any>>) => void) => {
         return addTextOnRight(true);
     });
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 0adf060ec..31935df3e 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -889,10 +889,10 @@ export class DashFieldView {
             e.stopPropagation();
             const collview = await Doc.addFieldEnumerations(self._textBoxDoc, node.attrs.fieldKey, [{ title: self._fieldSpan.innerText }]);
             collview instanceof Doc && tbox.props.addDocTab(collview, "onRight");
-        }
+        };
         const updateText = (forceMatch: boolean) => {
             self._enumerables.style.display = "none";
-            let newText = self._fieldSpan.innerText.startsWith(":=") || self._fieldSpan.innerText.startsWith("=:=") ? ":=-computed-" : self._fieldSpan.innerText;
+            const newText = self._fieldSpan.innerText.startsWith(":=") || self._fieldSpan.innerText.startsWith("=:=") ? ":=-computed-" : self._fieldSpan.innerText;
 
             // look for a document whose id === the fieldKey being displayed.  If there's a match, then that document
             // holds the different enumerated values for the field in the titles of its collected documents.
@@ -909,12 +909,12 @@ export class DashFieldView {
 
                 // if the text starts with a ':=' then treat it as an expression by making a computed field from its value storing it in the key
                 if (self._fieldSpan.innerText.startsWith(":=") && self._dashDoc) {
-                    self._dashDoc![self._fieldKey] = ComputedField.MakeFunction(self._fieldSpan.innerText.substring(2));
+                    self._dashDoc[self._fieldKey] = ComputedField.MakeFunction(self._fieldSpan.innerText.substring(2));
                 } else if (self._fieldSpan.innerText.startsWith("=:=") && self._dashDoc) {
                     Doc.Layout(tbox.props.Document)[self._fieldKey] = ComputedField.MakeFunction(self._fieldSpan.innerText.substring(3));
                 }
             });
-        }
+        };
 
         this._fieldSpan = document.createElement("div");
         this._fieldSpan.id = Utils.GenerateGuid();
@@ -926,14 +926,14 @@ export class DashFieldView {
         this._fieldSpan.onkeypress = function (e: any) { e.stopPropagation(); };
         this._fieldSpan.onkeyup = function (e: any) { e.stopPropagation(); };
         this._fieldSpan.onmousedown = function (e: any) { e.stopPropagation(); self._enumerables.style.display = "inline-block"; };
-        this._fieldSpan.onblur = function (e: any) { updateText(false); }
+        this._fieldSpan.onblur = function (e: any) { updateText(false); };
 
         const setDashDoc = (doc: Doc) => {
             self._dashDoc = doc;
             if (self._dashDoc && self._options?.length && !self._dashDoc[node.attrs.fieldKey]) {
                 self._dashDoc[node.attrs.fieldKey] = StrCast(self._options[0].title);
             }
-        }
+        };
         this._fieldSpan.onkeydown = function (e: any) {
             e.stopPropagation();
             if ((e.key === "a" && e.ctrlKey) || (e.key === "a" && e.metaKey)) {
@@ -976,7 +976,7 @@ export class DashFieldView {
                 alias._pivotField = self._fieldKey;
                 tbox.props.addDocTab(alias, "onRight");
             }
-        }
+        };
         this._labelSpan.innerHTML = `${node.attrs.fieldKey}: `;
         if (node.attrs.docid) {
             DocServer.GetRefField(node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && runInAction(() => setDashDoc(dashDoc)));
diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts
index 64874b994..2d9c807dd 100644
--- a/src/client/util/SearchUtil.ts
+++ b/src/client/util/SearchUtil.ts
@@ -121,12 +121,12 @@ export namespace SearchUtil {
 
     export async function GetAllDocs() {
         const query = "*";
-        let response = await rp.get(Utils.prepend('/search'), {
+        const response = await rp.get(Utils.prepend('/search'), {
             qs:
                 { start: 0, rows: 10000, q: query },
 
         });
-        let result: IdSearchResult = JSON.parse(response);
+        const result: IdSearchResult = JSON.parse(response);
         const { ids, numFound, highlighting } = result;
         //console.log(ids.length);
         const docMap = await DocServer.GetRefFields(ids);
diff --git a/src/client/views/KeyphraseQueryView.tsx b/src/client/views/KeyphraseQueryView.tsx
index a9dafc4a4..1dc156968 100644
--- a/src/client/views/KeyphraseQueryView.tsx
+++ b/src/client/views/KeyphraseQueryView.tsx
@@ -15,8 +15,8 @@ export class KeyphraseQueryView extends React.Component<KP_Props>{
     }
 
     render() {
-        let kps = this.props.keyphrases.toString();
-        let keyterms = this.props.keyphrases.split(',');
+        const kps = this.props.keyphrases.toString();
+        const keyterms = this.props.keyphrases.split(',');
         return (
             <div>
                 <h5>Select queries to send:</h5>
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index db8f7d5e4..00e22d6fb 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -138,8 +138,8 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                 }
             }
             return false;
-        }
-        let retVal = !instance?._goldenLayout.root.contentItems[0].isRow ? false :
+        };
+        const retVal = !instance?._goldenLayout.root.contentItems[0].isRow ? false :
             Array.from(instance._goldenLayout.root.contentItems[0].contentItems).some((child: any) => Array.from(child.contentItems).some(tryClose));
 
         retVal && instance.stateChanged();
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 527623ad4..b995fc7d5 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -107,7 +107,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
         get childLayoutPairs(): { layout: Doc; data: Doc; }[] {
             const { Document, DataDoc } = this.props;
             const validPairs = this.childDocs.map(doc => Doc.GetLayoutDataDocPair(Document, !this.props.annotationsKey ? DataDoc : undefined, doc)).filter(pair => pair.layout);
-            return validPairs.map(({ data, layout }) => ({ data: data!, layout: layout! })); // this mapping is a bit of a hack to coerce types
+            return validPairs.map(({ data, layout }) => ({ data, layout: layout! })); // this mapping is a bit of a hack to coerce types
         }
         get childDocList() {
             return Cast(this.dataField, listSpec(Doc));
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 28f8bc048..7adafea0e 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -801,7 +801,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 this.props.Document.scrollY = NumCast(doc.y) - offset;
             }
 
-            afterFocus && setTimeout(() => afterFocus?.(), 1000);
+            afterFocus && setTimeout(afterFocus, 1000);
         } else {
             const layoutdoc = Doc.Layout(doc);
             const newPanX = NumCast(doc.x) + NumCast(layoutdoc._width) / 2;
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index bd20781dc..aa8e1fb43 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -223,7 +223,7 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
      */
     @computed
     private get contents(): JSX.Element[] | null {
-        let { childLayoutPairs } = this;
+        const { childLayoutPairs } = this;
         const { Document, PanelHeight } = this.props;
         const collector: JSX.Element[] = [];
         for (let i = 0; i < childLayoutPairs.length; i++) {
diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx
index ac4f8a3cf..b7f3dd995 100644
--- a/src/client/views/linking/LinkEditor.tsx
+++ b/src/client/views/linking/LinkEditor.tsx
@@ -253,8 +253,8 @@ export class LinkGroupEditor extends React.Component<LinkGroupEditorProps> {
     render() {
         const groupType = StrCast(this.props.groupDoc.linkRelationship);
         // if ((groupType && LinkManager.Instance.getMetadataKeysInGroup(groupType).length > 0) || groupType === "") {
-        let buttons = <button className="linkEditor-button" disabled={groupType === ""} onClick={() => this.deleteGroup(groupType)} title="Delete Relationship from all links"><FontAwesomeIcon icon="trash" size="sm" /></button>;
-        let addButton = <button className="linkEditor-addbutton" onClick={() => this.addMetadata(groupType)} disabled={groupType === ""} title="Add metadata to relationship"><FontAwesomeIcon icon="plus" size="sm" /></button>;
+        const buttons = <button className="linkEditor-button" disabled={groupType === ""} onClick={() => this.deleteGroup(groupType)} title="Delete Relationship from all links"><FontAwesomeIcon icon="trash" size="sm" /></button>;
+        const addButton = <button className="linkEditor-addbutton" onClick={() => this.addMetadata(groupType)} disabled={groupType === ""} title="Add metadata to relationship"><FontAwesomeIcon icon="plus" size="sm" /></button>;
 
         return (
             <div className="linkEditor-group">
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 3c8b1c3b8..47883db4e 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -50,7 +50,6 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume
     _recordStart = 0;
     _stream: MediaStream | undefined;
 
-    public static START = 0;
     @observable private static _scrubTime = 0;
     @computed get audioState(): undefined | "recording" | "paused" | "playing" { return this.dataDoc.audioState as (undefined | "recording" | "paused" | "playing"); }
     set audioState(value) { this.dataDoc.audioState = value; }
@@ -146,8 +145,7 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume
         }).then(function (stream) {
             self._stream = stream;
             self._recorder = new MediaRecorder(stream);
-            self.dataDoc[self.props.fieldKey + "-recordingStart"] = new DateField(new Date());
-            AudioBox.START = new DateField(new Date()).date.getTime();
+            self.dataDoc[self.props.fieldKey + "-recordingStart"] = new DateField(new Date()); \
             AudioBox.ActiveRecordings.push(self.props.Document);
             self._recorder.ondataavailable = async function (e: any) {
                 const formData = new FormData();
@@ -208,7 +206,7 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume
             _width: NumCast(this.props.Document._width), _height: 3 * NumCast(this.props.Document._height)
         });
         Doc.GetProto(newDoc).recordingSource = this.dataDoc;
-        Doc.GetProto(newDoc).recordingStart = 0;
+        Doc.GetProto(newDoc).recordingStart = ComputedField.MakeFunction(`this.recordingSource[${this.props.fieldKey}+"-recordingStart"]`);
         Doc.GetProto(newDoc).audioState = ComputedField.MakeFunction("this.recordingSource.audioState");
         this.props.addDocument?.(newDoc);
         e.stopPropagation();
diff --git a/src/client/views/nodes/DocuLinkBox.tsx b/src/client/views/nodes/DocuLinkBox.tsx
index a0f5b3152..bc663d084 100644
--- a/src/client/views/nodes/DocuLinkBox.tsx
+++ b/src/client/views/nodes/DocuLinkBox.tsx
@@ -113,7 +113,7 @@ export class DocuLinkBox extends DocComponent<FieldViewProps, DocLinkSchema>(Doc
     openLinkEditor = action((e: React.MouseEvent) => {
         SelectionManager.DeselectAll();
         this._editing = this._forceOpen = true;
-    })
+    });
 
     specificContextMenu = (e: React.MouseEvent): void => {
         const funcs: ContextMenuProps[] = [];
@@ -135,7 +135,7 @@ export class DocuLinkBox extends DocComponent<FieldViewProps, DocLinkSchema>(Doc
         const targetTitle = StrCast((this.props.Document[anchor]! as Doc).title) + (timecode !== undefined ? ":" + timecode : "");
         const flyout = (
             <div className="docuLinkBox-flyout" title=" " onPointerOver={() => Doc.UnBrushDoc(this.props.Document)}>
-                <LinkEditor sourceDoc={Cast(this.props.Document[this.props.fieldKey], Doc, null)!} hideback={true} linkDoc={this.props.Document} showLinks={action(() => { })} />
+                <LinkEditor sourceDoc={Cast(this.props.Document[this.props.fieldKey], Doc, null)} hideback={true} linkDoc={this.props.Document} showLinks={action(() => { })} />
                 {!this._forceOpen ? (null) : <div className="docuLinkBox-linkCloser" onPointerDown={action(() => this._isOpen = this._editing = this._forceOpen = false)}>
                     <FontAwesomeIcon color="dimGray" icon={"times"} size={"sm"} />
                 </div>}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 63c6b9f88..a1cba4c2e 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -571,9 +571,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             else if (de.complete.docDragData.draggedDocuments[0].type === "text") {
                 const text = Cast(de.complete.docDragData.draggedDocuments[0].data, RichTextField)?.Text;
                 if (text && text[0] === "{" && text[text.length - 1] === "}" && text.includes(":")) {
-                    let loc = text.indexOf(":");
-                    let key = text.slice(1, loc);
-                    let value = text.slice(loc + 1, text.length - 1);
+                    const loc = text.indexOf(":");
+                    const key = text.slice(1, loc);
+                    const value = text.slice(loc + 1, text.length - 1);
                     console.log(key);
                     console.log(value);
                     console.log(this.props.Document);
@@ -756,7 +756,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             // a.download = `DocExport-${this.props.Document[Id]}.zip`;
             // a.click();
         });
-        let recommender_subitems: ContextMenuProps[] = [];
+        const recommender_subitems: ContextMenuProps[] = [];
 
         recommender_subitems.push({
             description: "Internal recommendations",
@@ -764,7 +764,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             icon: "brain"
         });
 
-        let ext_recommender_subitems: ContextMenuProps[] = [];
+        const ext_recommender_subitems: ContextMenuProps[] = [];
 
         ext_recommender_subitems.push({
             description: "arXiv",
@@ -872,7 +872,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             }
         }));
         const doclist = ClientRecommender.Instance.computeSimilarities("cosine");
-        let recDocs: { preview: Doc, score: number }[] = [];
+        const recDocs: { preview: Doc, score: number }[] = [];
         // tslint:disable-next-line: prefer-for-of
         for (let i = 0; i < doclist.length; i++) {
             recDocs.push({ preview: doclist[i].actualDoc, score: doclist[i].score });
@@ -1113,7 +1113,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             </> :
                 this.innards}
         </div>;
-        { this._showKPQuery ? <KeyphraseQueryView keyphrases={this._queries}></KeyphraseQueryView> : undefined }
+        { this._showKPQuery ? <KeyphraseQueryView keyphrases={this._queries}></KeyphraseQueryView> : undefined; }
     }
 }
 
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 7f5f8538a..1fa603cbd 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -20,7 +20,7 @@ import { InkTool } from '../../../new_fields/InkField';
 import { RichTextField } from "../../../new_fields/RichTextField";
 import { RichTextUtils } from '../../../new_fields/RichTextUtils';
 import { createSchema, makeInterface } from "../../../new_fields/Schema";
-import { Cast, NumCast, StrCast, BoolCast } from "../../../new_fields/Types";
+import { Cast, NumCast, StrCast, BoolCast, DateCast } from "../../../new_fields/Types";
 import { TraceMobx } from '../../../new_fields/util';
 import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnOne, Utils, returnTrue } from '../../../Utils';
 import { GoogleApiClientUtils, Pulls, Pushes } from '../../apis/google_docs/GoogleApiClientUtils';
@@ -420,11 +420,11 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
             //this._editorView!.focus();
         });
     }
-    stopDictation = (abort: boolean) => { DictationManager.Controls.stop(!abort); }
+    stopDictation = (abort: boolean) => { DictationManager.Controls.stop(!abort); };
 
     @action
     toggleMenubar = () => {
-        this.props.Document._chromeStatus = this.props.Document._chromeStatus == "disabled" ? "enabled" : "disabled";
+        this.props.Document._chromeStatus = this.props.Document._chromeStatus === "disabled" ? "enabled" : "disabled";
     }
 
     recordBullet = async () => {
@@ -444,12 +444,8 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
 
     setCurrentBulletContent = (value: string) => {
         if (this._editorView) {
-            let state = this._editorView.state;
-            let now = Date.now();
-            if (NumCast(this.props.Document.recordingStart, -1) === 0) {
-                this.props.Document.recordingStart = now = AudioBox.START;
-            }
-            console.log("NOW = " + (now - AudioBox.START) / 1000);
+            const state = this._editorView.state;
+            const now = Date.now();
             let mark = schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(now / 1000) });
             if (!this._break && state.selection.to !== state.selection.from) {
                 for (let i = state.selection.from; i <= state.selection.to; i++) {
@@ -461,9 +457,9 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
                     }
                 }
             }
+            const recordingStart = DateCast(this.props.Document.recordingStart).date.getTime();
             this._break = false;
-            console.log("start = " + (mark.attrs.modified * 1000 - AudioBox.START) / 1000);
-            value = "" + (mark.attrs.modified * 1000 - AudioBox.START) / 1000 + value;
+            value = "" + (mark.attrs.modified * 1000 - recordingStart) / 1000 + value;
             const from = state.selection.from;
             const inserted = state.tr.insertText(value).addMark(from, from + value.length + 1, mark);
             this._editorView.dispatch(inserted.setSelection(TextSelection.create(inserted.doc, from, from + value.length + 1)));
@@ -867,7 +863,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
         if (this._recording && !e.ctrlKey && e.button === 0) {
             this.stopDictation(true);
             this._break = true;
-            let state = this._editorView!.state;
+            const state = this._editorView!.state;
             const to = state.selection.to;
             const updated = TextSelection.create(state.doc, to, to);
             this._editorView!.dispatch(this._editorView!.state.tr.setSelection(updated).insertText("\n", to));
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index f47620c24..4076128b2 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -94,11 +94,11 @@ export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument>
         !this.Document._fitWidth && (this.Document._height = this.Document[WidthSym]() * (nh / nw));
     }
 
-    public search = (string: string, fwd: boolean) => { this._pdfViewer?.search(string, fwd); }
-    public prevAnnotation = () => { this._pdfViewer?.prevAnnotation(); }
-    public nextAnnotation = () => { this._pdfViewer?.nextAnnotation(); }
-    public backPage = () => { this._pdfViewer!.gotoPage((this.Document.curPage || 1) - 1); }
-    public forwardPage = () => { this._pdfViewer!.gotoPage((this.Document.curPage || 1) + 1); }
+    public search = (string: string, fwd: boolean) => { this._pdfViewer?.search(string, fwd); };
+    public prevAnnotation = () => { this._pdfViewer?.prevAnnotation(); };
+    public nextAnnotation = () => { this._pdfViewer?.nextAnnotation(); };
+    public backPage = () => { this._pdfViewer!.gotoPage((this.Document.curPage || 1) - 1); };
+    public forwardPage = () => { this._pdfViewer!.gotoPage((this.Document.curPage || 1) + 1); };
     public gotoPage = (p: number) => { this._pdfViewer!.gotoPage(p); };
 
     @undoBatch
-- 
cgit v1.2.3-70-g09d2


From dafd2ce66e28e91408c76712fdc5710a835aa388 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 11 Mar 2020 22:52:36 -0400
Subject: removed scrubber tool which isn't being used anymore (audiobox has
 its own scrubber)

---
 src/client/views/InkingControl.tsx                  |  1 -
 .../collectionFreeForm/CollectionFreeFormView.tsx   | 21 +--------------------
 src/new_fields/InkField.ts                          |  1 -
 .../authentication/models/current_user_utils.ts     |  2 --
 4 files changed, 1 insertion(+), 24 deletions(-)

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

diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index 5cd3c265d..645c7fa54 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -85,7 +85,6 @@ export class InkingControl {
 Scripting.addGlobal(function activatePen(pen: any, width: any, color: any) { InkingControl.Instance.switchTool(pen ? InkTool.Pen : InkTool.None); InkingControl.Instance.switchWidth(width); InkingControl.Instance.updateSelectedColor(color); });
 Scripting.addGlobal(function activateBrush(pen: any, width: any, color: any) { InkingControl.Instance.switchTool(pen ? InkTool.Highlighter : InkTool.None); InkingControl.Instance.switchWidth(width); InkingControl.Instance.updateSelectedColor(color); });
 Scripting.addGlobal(function activateEraser(pen: any) { return InkingControl.Instance.switchTool(pen ? InkTool.Eraser : InkTool.None); });
-Scripting.addGlobal(function activateScrubber(pen: any) { return InkingControl.Instance.switchTool(pen ? InkTool.Scrubber : InkTool.None); });
 Scripting.addGlobal(function activateStamp(pen: any) { return InkingControl.Instance.switchTool(pen ? InkTool.Stamp : InkTool.None); });
 Scripting.addGlobal(function deactivateInk() { return InkingControl.Instance.switchTool(InkTool.None); });
 Scripting.addGlobal(function setInkWidth(width: any) { return InkingControl.Instance.switchWidth(width); });
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 7adafea0e..fd679b7b2 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -330,31 +330,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 this._lastX = e.pageX;
                 this._lastY = e.pageY;
             }
-            // eraser or scrubber plus anything else mode
+            // eraser plus anything else mode
             else {
                 e.stopPropagation();
                 e.preventDefault();
             }
         }
-        // if (e.button === 0 && !e.shiftKey && !e.altKey && !e.ctrlKey && this.props.active(true)) {
-        //     document.removeEventListener("pointermove", this.onPointerMove);
-        //     document.removeEventListener("pointerup", this.onPointerUp);
-        //     document.addEventListener("pointermove", this.onPointerMove);
-        //     document.addEventListener("pointerup", this.onPointerUp);
-        //     if (InkingControl.Instance.selectedTool === InkTool.None) {
-        //         this._lastX = e.pageX;
-        //         this._lastY = e.pageY;
-        //     }
-        //     else {
-        //         e.stopPropagation();
-        //         e.preventDefault();
-
-        //         if (InkingControl.Instance.selectedTool !== InkTool.Eraser && InkingControl.Instance.selectedTool !== InkTool.Scrubber) {
-        //             let point = this.getTransform().transformPoint(e.pageX, e.pageY);
-        //             this._points.push({ x: point[0], y: point[1] });
-        //         }
-        //     }
-        // }
     }
 
     @action
diff --git a/src/new_fields/InkField.ts b/src/new_fields/InkField.ts
index 4a44b4f55..bb93de5ac 100644
--- a/src/new_fields/InkField.ts
+++ b/src/new_fields/InkField.ts
@@ -8,7 +8,6 @@ export enum InkTool {
     Pen,
     Highlighter,
     Eraser,
-    Scrubber,
     Stamp
 }
 
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index a2be93d73..d1f68ba49 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -77,7 +77,6 @@ export class CurrentUserUtils {
             { title: "use highlighter", icon: "highlighter", click: 'activateBrush(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this,20,this.backgroundColor)', backgroundColor: "yellow", ischecked: `sameDocs(this.activePen.pen, this)`, activePen: doc },
             { title: "use stamp", icon: "stamp", click: 'activateStamp(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this)', backgroundColor: "orange", ischecked: `sameDocs(this.activePen.pen, this)`, activePen: doc },
             { title: "use eraser", icon: "eraser", click: 'activateEraser(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this);', ischecked: `sameDocs(this.activePen.pen, this)`, backgroundColor: "pink", activePen: doc },
-            { title: "use scrubber", icon: "eraser", click: 'activateScrubber(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this);', ischecked: `sameDocs(this.activePen.pen, this)`, backgroundColor: "green", activePen: doc },
             { title: "use drag", icon: "mouse-pointer", click: 'deactivateInk();this.activePen.pen = this;', ischecked: `sameDocs(this.activePen.pen, this)`, backgroundColor: "white", activePen: doc },
         ];
         return docProtoData.filter(d => !buttons || !buttons.includes(d.title)).map(data => Docs.Create.FontIconDocument({
@@ -115,7 +114,6 @@ export class CurrentUserUtils {
             { title: "use pen", icon: "pen-nib", click: 'activatePen(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this,2, this.backgroundColor)', backgroundColor: "blue", ischecked: `sameDocs(this.activePen.pen,  this)`, activePen: doc },
             { title: "use highlighter", icon: "highlighter", click: 'activateBrush(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this,20,this.backgroundColor)', backgroundColor: "yellow", ischecked: `sameDocs(this.activePen.pen, this)`, activePen: doc },
             { title: "use eraser", icon: "eraser", click: 'activateEraser(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this);', ischecked: `sameDocs(this.activePen.pen, this)`, backgroundColor: "pink", activePen: doc },
-            { title: "use scrubber", icon: "eraser", click: 'activateScrubber(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this);', ischecked: `sameDocs(this.activePen.pen, this)`, backgroundColor: "green", activePen: doc },
             { title: "use drag", icon: "mouse-pointer", click: 'deactivateInk();this.activePen.pen = this;', ischecked: `sameDocs(this.activePen.pen, this)`, backgroundColor: "white", activePen: doc },
             // { title: "draw", icon: "pen-nib", click: 'switchMobileView(setupMobileInkingDoc, renderMobileInking, onSwitchMobileInking);', ischecked: `sameDocs(this.activePen.pen, this)`, backgroundColor: "red", activePen: doc },
             { title: "upload", icon: "upload", click: 'switchMobileView(setupMobileUploadDoc, renderMobileUpload, onSwitchMobileUpload);', backgroundColor: "orange" },
-- 
cgit v1.2.3-70-g09d2


From 61e12f2e08ed0c0e47b82456b75c441b7278cdd6 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 19 Mar 2020 15:53:07 -0400
Subject: fixed buttons on screenshotbox.  added 'place' option to dragDrop to
 drop the original document without moving it.   switched to passing
 childDropAction as a prop

---
 src/client/util/DragManager.ts                     |  2 +-
 .../views/collections/CollectionStackingView.tsx   |  3 +-
 .../views/collections/CollectionTreeView.tsx       |  5 +--
 src/client/views/collections/CollectionView.tsx    | 25 ++++++-------
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  3 +-
 .../views/nodes/ContentFittingDocumentView.tsx     |  3 ++
 src/client/views/nodes/DocumentView.tsx            |  6 ++--
 src/client/views/nodes/FieldView.tsx               |  1 +
 src/client/views/nodes/ScreenshotBox.scss          | 41 +++++++++++++---------
 src/client/views/nodes/ScreenshotBox.tsx           |  7 ++--
 .../authentication/models/current_user_utils.ts    |  2 +-
 11 files changed, 58 insertions(+), 40 deletions(-)

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

diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index f4461d2f7..63a11c85a 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -18,7 +18,7 @@ import { AudioBox } from "../views/nodes/AudioBox";
 import { DateField } from "../../new_fields/DateField";
 import { DocumentView } from "../views/nodes/DocumentView";
 
-export type dropActionType = "alias" | "copy" | undefined;
+export type dropActionType = "place" | "alias" | "copy" | undefined;
 export function SetupDrag(
     _reference: React.RefObject<HTMLElement>,
     docFunc: () => Doc | Promise<Doc> | undefined,
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index a936582c3..719778eb1 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -12,7 +12,7 @@ import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
 import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../../new_fields/Types";
 import { TraceMobx } from "../../../new_fields/util";
 import { Utils, setupMoveUpEvents, emptyFunction } from "../../../Utils";
-import { DragManager } from "../../util/DragManager";
+import { DragManager, dropActionType } from "../../util/DragManager";
 import { Transform } from "../../util/Transform";
 import { undoBatch } from "../../util/UndoManager";
 import { ContextMenu } from "../ContextMenu";
@@ -176,6 +176,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
             LibraryPath={this.props.LibraryPath}
             renderDepth={this.props.renderDepth + 1}
             fitToBox={this.props.fitToBox}
+            dropAction={StrCast(this.props.Document.childDropAction) as dropActionType}
             onClick={layoutDoc.isTemplateDoc ? this.onClickHandler : this.onChildClickHandler}
             PanelWidth={width}
             PanelHeight={height}
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 4757e5a53..f8f8a0943 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -130,7 +130,7 @@ class TreeView extends React.Component<TreeViewProps> {
     }
 
     @undoBatch delete = () => this.props.deleteDoc(this.props.document);
-    @undoBatch openRight = () => this.props.addDocTab(this.props.containingCollection.childDropAction === "alias" ? Doc.MakeAlias(this.props.document) : this.props.document, "onRight", this.props.libraryPath);
+    @undoBatch openRight = () => this.props.addDocTab(this.props.dropAction === "alias" ? Doc.MakeAlias(this.props.document) : this.props.document, "onRight", this.props.libraryPath);
     @undoBatch indent = () => this.props.addDocument(this.props.document) && this.delete();
     @undoBatch move = (doc: Doc, target: Doc | undefined, addDoc: (doc: Doc) => boolean) => {
         return this.props.document !== target && this.props.deleteDoc(doc) && addDoc(doc);
@@ -455,6 +455,7 @@ class TreeView extends React.Component<TreeViewProps> {
                         addDocTab={this.props.addDocTab}
                         pinToPres={emptyFunction}
                         onClick={editTitle}
+                        dropAction={this.props.dropAction}
                         moveDocument={this.props.moveDocument}
                         removeDocument={undefined}
                         ScreenToLocalTransform={this.getTransform}
@@ -761,7 +762,7 @@ export class CollectionTreeView extends CollectionSubView(Document) {
     }
 
     render() {
-        const dropAction = StrCast(this.props.Document.dropAction) as dropActionType;
+        const dropAction = StrCast(this.props.Document.childDropAction) as dropActionType;
         const addDoc = (doc: Doc, relativeTo?: Doc, before?: boolean) => this.addDoc(doc, relativeTo, before);
         const moveDoc = (d: Doc, target: Doc | undefined, addDoc: (doc: Doc) => boolean) => this.props.moveDocument(d, target, addDoc);
         const childDocs = this.props.overrideDocuments ? this.props.overrideDocuments : this.childDocs;
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index c74b9ad6d..edac59efe 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -1,18 +1,17 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
 import { faEye } from '@fortawesome/free-regular-svg-icons';
 import { faColumns, faCopy, faEllipsisV, faFingerprint, faImage, faProjectDiagram, faSignature, faSquare, faTh, faThList, faTree } from '@fortawesome/free-solid-svg-icons';
-import { action, IReactionDisposer, observable, reaction, runInAction, computed } from 'mobx';
+import { action, observable } from 'mobx';
 import { observer } from "mobx-react";
 import * as React from 'react';
 import Lightbox from 'react-image-lightbox-with-rotate';
 import 'react-image-lightbox-with-rotate/style.css'; // This only needs to be imported once in your app
 import { DateField } from '../../../new_fields/DateField';
-import { Doc, DocListCast, DataSym } from '../../../new_fields/Doc';
-import { Id } from '../../../new_fields/FieldSymbols';
-import { BoolCast, Cast, StrCast, NumCast } from '../../../new_fields/Types';
+import { DataSym, Doc, DocListCast } from '../../../new_fields/Doc';
+import { List } from '../../../new_fields/List';
+import { BoolCast, Cast, NumCast, StrCast } from '../../../new_fields/Types';
 import { ImageField } from '../../../new_fields/URLField';
 import { TraceMobx } from '../../../new_fields/util';
-import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
 import { Utils } from '../../../Utils';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { DocumentManager } from '../../util/DocumentManager';
@@ -22,22 +21,23 @@ import { ContextMenu } from "../ContextMenu";
 import { FieldView, FieldViewProps } from '../nodes/FieldView';
 import { ScriptBox } from '../ScriptBox';
 import { Touchable } from '../Touchable';
+import { CollectionCarouselView } from './CollectionCarouselView';
 import { CollectionDockingView } from "./CollectionDockingView";
 import { AddCustomFreeFormLayout } from './collectionFreeForm/CollectionFreeFormLayoutEngines';
 import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
-import { CollectionCarouselView } from './CollectionCarouselView';
 import { CollectionLinearView } from './CollectionLinearView';
 import { CollectionMulticolumnView } from './collectionMulticolumn/CollectionMulticolumnView';
+import { CollectionMultirowView } from './collectionMulticolumn/CollectionMultirowView';
 import { CollectionSchemaView } from "./CollectionSchemaView";
 import { CollectionStackingView } from './CollectionStackingView';
 import { CollectionStaffView } from './CollectionStaffView';
+import { SubCollectionViewProps } from './CollectionSubView';
+import { CollectionTimeView } from './CollectionTimeView';
 import { CollectionTreeView } from "./CollectionTreeView";
 import './CollectionView.scss';
 import { CollectionViewBaseChrome } from './CollectionViewChromes';
-import { CollectionTimeView } from './CollectionTimeView';
-import { CollectionMultirowView } from './collectionMulticolumn/CollectionMultirowView';
-import { List } from '../../../new_fields/List';
-import { SubCollectionViewProps } from './CollectionSubView';
+import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
+import { Id } from '../../../new_fields/FieldSymbols';
 export const COLLECTION_BORDER_WIDTH = 2;
 const path = require('path');
 library.add(faTh, faTree, faSquare, faProjectDiagram, faSignature, faThList, faFingerprint, faColumns, faEllipsisV, faImage, faEye as any, faCopy);
@@ -75,7 +75,7 @@ export namespace CollectionViewType {
     ]);
 
     export const valueOf = (value: string) => stringMapping.get(value.toLowerCase());
-    export const stringFor = (value: number) => Array.from(stringMapping.entries()).find(entry => entry[1] === value)[0];
+    export const stringFor = (value: number) => Array.from(stringMapping.entries()).find(entry => entry[1] === value)?.[0];
 }
 
 export interface CollectionRenderProps {
@@ -116,7 +116,8 @@ export class CollectionView extends Touchable<FieldViewProps> {
     @action.bound
     addDocument(doc: Doc): boolean {
         const targetDataDoc = this.props.Document[DataSym];
-        targetDataDoc[this.props.fieldKey] = new List<Doc>([...DocListCast(targetDataDoc[this.props.fieldKey]), doc]);  // DocAddToList may write to targetdataDoc's parent ... we don't want this. should really change GetProto to GetDataDoc and test for resolvedDataDoc there
+        const docList = DocListCast(targetDataDoc[this.props.fieldKey]);
+        !docList.includes(doc) && (targetDataDoc[this.props.fieldKey] = new List<Doc>([...docList, doc]));  // DocAddToList may write to targetdataDoc's parent ... we don't want this. should really change GetProto to GetDataDoc and test for resolvedDataDoc there
         // Doc.AddDocToList(targetDataDoc, this.props.fieldKey, doc);
         targetDataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()));
         Doc.GetProto(doc).lastOpened = new DateField;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index fd679b7b2..da44d0d59 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -20,7 +20,7 @@ import { CognitiveServices } from "../../../cognitive_services/CognitiveServices
 import { DocServer } from "../../../DocServer";
 import { Docs } from "../../../documents/Documents";
 import { DocumentManager } from "../../../util/DocumentManager";
-import { DragManager } from "../../../util/DragManager";
+import { DragManager, dropActionType } from "../../../util/DragManager";
 import { HistoryUtil } from "../../../util/History";
 import { InteractionUtils } from "../../../util/InteractionUtils";
 import { SelectionManager } from "../../../util/SelectionManager";
@@ -832,6 +832,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             Document: childLayout,
             LibraryPath: this.libraryPath,
             layoutKey: undefined,
+            dropAction: StrCast(this.props.Document.childDropAction) as dropActionType,
             //onClick: undefined, // this.props.onClick,  // bcz: check this out -- I don't think we want to inherit click handlers, or we at least need a way to ignore them
             onClick: this.onChildClickHandler,
             ScreenToLocalTransform: childLayout.z ? this.getTransformOverlay : this.getTransform,
diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx
index 36233a7e6..8632f9c9a 100644
--- a/src/client/views/nodes/ContentFittingDocumentView.tsx
+++ b/src/client/views/nodes/ContentFittingDocumentView.tsx
@@ -12,6 +12,7 @@ import { CollectionView } from "../collections/CollectionView";
 import '../DocumentDecorations.scss';
 import { DocumentView } from "../nodes/DocumentView";
 import "./ContentFittingDocumentView.scss";
+import { dropActionType } from "../../util/DragManager";
 
 interface ContentFittingDocumentViewProps {
     Document?: Doc;
@@ -21,6 +22,7 @@ interface ContentFittingDocumentViewProps {
     childDocs?: Doc[];
     renderDepth: number;
     fitToBox?: boolean;
+    dropAction?: dropActionType;
     PanelWidth: () => number;
     PanelHeight: () => number;
     focus?: (doc: Doc) => void;
@@ -86,6 +88,7 @@ export class ContentFittingDocumentView extends React.Component<ContentFittingDo
                         LayoutDoc={this.props.LayoutDoc}
                         LibraryPath={this.props.LibraryPath}
                         fitToBox={this.props.fitToBox}
+                        dropAction={this.props.dropAction}
                         onClick={this.props.onClick}
                         backgroundColor={this.props.backgroundColor}
                         addDocument={this.props.addDocument}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index c558a1da7..7480f0db8 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -14,7 +14,6 @@ import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types";
 import { AudioField, ImageField, PdfField, VideoField } from '../../../new_fields/URLField';
 import { TraceMobx } from '../../../new_fields/util';
 import { GestureUtils } from '../../../pen-gestures/GestureUtils';
-import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
 import { emptyFunction, returnOne, returnTransparent, returnTrue, Utils, OmitKeys } from "../../../Utils";
 import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
 import { DocServer } from "../../DocServer";
@@ -65,6 +64,7 @@ export interface DocumentViewProps {
     onClick?: ScriptField;
     onPointerDown?: ScriptField;
     onPointerUp?: ScriptField;
+    dropAction?: dropActionType;
     dragDivName?: string;
     addDocument?: (doc: Doc) => boolean;
     removeDocument?: (doc: Doc) => boolean;
@@ -275,7 +275,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     }
 
     onClick = (e: React.MouseEvent | React.PointerEvent) => {
-        if (!e.nativeEvent.cancelBubble && !this.Document.ignoreClick && CurrentUserUtils.MainDocId !== this.props.Document[Id] &&
+        if (!e.nativeEvent.cancelBubble && !this.Document.ignoreClick && //CurrentUserUtils.MainDocId !== this.props.Document[Id] &&
             (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) {
             e.stopPropagation();
             let preventDefault = true;
@@ -474,7 +474,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 if (!e.altKey && (!this.topMost || this.Document.onDragStart || this.onClickHandler) && (e.buttons === 1 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE))) {
                     document.removeEventListener("pointermove", this.onPointerMove);
                     document.removeEventListener("pointerup", this.onPointerUp);
-                    this.startDragging(this._downX, this._downY, this.props.ContainingCollectionDoc?.childDropAction ? this.props.ContainingCollectionDoc?.childDropAction : this.Document.dropAction ? this.Document.dropAction as any : e.ctrlKey || e.altKey ? "alias" : undefined);
+                    this.startDragging(this._downX, this._downY, this.props.dropAction ? this.props.dropAction : this.Document.dropAction ? this.Document.dropAction as any : e.ctrlKey || e.altKey ? "alias" : undefined);
                 }
             }
             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
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index d030d1f4d..6619330a1 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -25,6 +25,7 @@ export interface FieldViewProps {
     DataDoc?: Doc;
     LibraryPath: Doc[];
     onClick?: ScriptField;
+    dropAction: dropAction;
     isSelected: (outsideReaction?: boolean) => boolean;
     select: (isCtrlPressed: boolean) => void;
     renderDepth: number;
diff --git a/src/client/views/nodes/ScreenshotBox.scss b/src/client/views/nodes/ScreenshotBox.scss
index 4aaccb472..6cc184948 100644
--- a/src/client/views/nodes/ScreenshotBox.scss
+++ b/src/client/views/nodes/ScreenshotBox.scss
@@ -25,22 +25,31 @@
     pointer-events: all;
 }
 
-.screenshotBox-snapshot{
-    color : white;
-    top :25px;
-    right : 25px;
+.screenshotBox-uiButtons {
+    background:dimgray;
+    border: orange solid 1px;
     position: absolute;
-    background-color:rgba(50, 50, 50, 0.2);
-    transform-origin: left top;
-    pointer-events:all;
-}
+    right: 25;
+    top: 0;
+    width:50;
+    height: 25;
+    .screenshotBox-snapshot{
+        color : white;
+        top :0px;
+        right : 5px;
+        position: absolute;
+        background-color:rgba(50, 50, 50, 0.2);
+        transform-origin: left top;
+        pointer-events:all;
+    }
 
-.screenshotBox-recorder{
-    color : white;
-    top :25px;
-    right : 50px;
-    position: absolute;
-    background-color:rgba(50, 50, 50, 0.2);
-    transform-origin: left top;
-    pointer-events:all;
+    .screenshotBox-recorder{
+        color : white;
+        top :0px;
+        left: 5px;
+        position: absolute;
+        background-color:rgba(50, 50, 50, 0.2);
+        transform-origin: left top;
+        pointer-events:all;
+    }
 }
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index cfa6fa8a3..548066f1c 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -145,13 +145,14 @@ export class ScreenshotBox extends DocAnnotatableComponent<FieldViewProps, Scree
     })
 
     private get uIButtons() {
-        return ([
+        return (<div className="screenshotBox-uiButtons">
             <div className="screenshotBox-recorder" key="snap" onPointerDown={this.toggleRecording} >
                 <FontAwesomeIcon icon="file" size="lg" />
             </div>,
             <div className="screenshotBox-snapshot" key="snap" onPointerDown={this.onSnapshot} >
                 <FontAwesomeIcon icon="camera" size="lg" />
-            </div>]);
+            </div>
+        </div>);
     }
 
     onSnapshot = (e: React.PointerEvent) => {
@@ -187,7 +188,7 @@ export class ScreenshotBox extends DocAnnotatableComponent<FieldViewProps, Scree
                     {this.contentFunc}
                 </CollectionFreeFormView>
             </div>
-            {this.uIButtons}
+            {this.active() ? this.uIButtons : (null)}
         </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 ea7b91b23..31588bf82 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -222,7 +222,7 @@ export class CurrentUserUtils {
             _width: 50, _height: 25, title: "Library", fontSize: 10, dontSelect: true,
             letterSpacing: "0px", textTransform: "unset", borderRounding: "5px 5px 0px 0px", boxShadow: "3px 3px 0px rgb(34, 34, 34)",
             sourcePanel: Docs.Create.TreeDocument([doc.workspaces as Doc, doc.documents as Doc, Docs.Prototypes.MainLinkDocument(), doc, doc.recentlyClosed as Doc], {
-                title: "Library", _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, dropAction: "alias", lockedPosition: true, boxShadow: "0 0", dontRegisterChildren: true
+                title: "Library", _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, childDropAction: "place", lockedPosition: true, boxShadow: "0 0", dontRegisterChildren: true
             }),
             targetContainer: sidebarContainer,
             onClick: ScriptField.MakeScript("this.targetContainer.proto = this.sourcePanel;")
-- 
cgit v1.2.3-70-g09d2


From b3f689c2ce60f92574430dad0448650ccf6b8d7e Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 19 Mar 2020 19:18:55 -0400
Subject: moved facet collection stuff into CollectionView from
 CollectionTreeView

---
 src/client/views/collections/CollectionSubView.tsx |  40 +++-
 .../views/collections/CollectionTimeView.scss      |  51 -----
 .../views/collections/CollectionTimeView.tsx       | 244 +++++----------------
 src/client/views/collections/CollectionView.scss   |  54 +++++
 src/client/views/collections/CollectionView.tsx    | 175 ++++++++++++++-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  40 +---
 6 files changed, 324 insertions(+), 280 deletions(-)

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

diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 97177855f..a5480d567 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -112,12 +112,48 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
         get childDocList() {
             return Cast(this.dataField, listSpec(Doc));
         }
-        get childDocs() {
+        @computed get childDocs() {
+            const docFilters = Cast(this.props.Document._docFilters, listSpec("string"), []);
+            const docRangeFilters = Cast(this.props.Document._docRangeFilters, listSpec("string"), []);
+            const filterFacets: { [key: string]: { [value: string]: string } } = {};  // maps each filter key to an object with value=>modifier fields
+            for (let i = 0; i < docFilters.length; i += 3) {
+                const [key, value, modifiers] = docFilters.slice(i, i + 3);
+                if (!filterFacets[key]) {
+                    filterFacets[key] = {};
+                }
+                filterFacets[key][value] = modifiers;
+            }
+
             const dfield = this.dataField;
             const rawdocs = (dfield instanceof Doc) ? [dfield] : Cast(dfield, listSpec(Doc), this.props.Document.expandedTemplate && !this.props.annotationsKey ? [Cast(this.props.Document.expandedTemplate, Doc, null)] : []);
             const docs = rawdocs.filter(d => !(d instanceof Promise)).map(d => d as Doc);
             const viewSpecScript = Cast(this.props.Document.viewSpecScript, ScriptField);
-            return viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs;
+            const childDocs = viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs;
+
+            const filteredDocs = docFilters.length ? childDocs.filter(d => {
+                for (const facetKey of Object.keys(filterFacets)) {
+                    const facet = filterFacets[facetKey];
+                    const satisfiesFacet = Object.keys(facet).some(value =>
+                        (facet[value] === "x") !== Doc.matchFieldValue(d, facetKey, value));
+                    if (!satisfiesFacet) {
+                        return false;
+                    }
+                }
+                return true;
+            }) : childDocs;
+            const rangeFilteredDocs = filteredDocs.filter(d => {
+                for (let i = 0; i < docRangeFilters.length; i += 3) {
+                    const key = docRangeFilters[i];
+                    const min = Number(docRangeFilters[i + 1]);
+                    const max = Number(docRangeFilters[i + 2]);
+                    const val = Cast(d[key], "number", null);
+                    if (val !== undefined && (val < min || val > max)) {
+                        return false;
+                    }
+                }
+                return true;
+            });
+            return rangeFilteredDocs;
         }
 
         @action
diff --git a/src/client/views/collections/CollectionTimeView.scss b/src/client/views/collections/CollectionTimeView.scss
index 310668ad2..be745000e 100644
--- a/src/client/views/collections/CollectionTimeView.scss
+++ b/src/client/views/collections/CollectionTimeView.scss
@@ -72,62 +72,11 @@
         }
     }
 
-    .collectionTimeView-treeView {
-        display: flex;
-        flex-direction: column;
-        width: 200px;
-        height: 100%;
-
-        .collectionTimeView-addfacet {
-            display: inline-block;
-            width: 200px;
-            height: 30px;
-            background: darkGray;
-            text-align: center;
-
-            .collectionTimeView-button {
-                align-items: center;
-                display: flex;
-                width: 100%;
-                height: 100%;
-
-                .collectionTimeView-span {
-                    margin: auto;
-                }
-            }
-
-            >div,
-            >div>div {
-                width: 100%;
-                height: 100%;
-                text-align: center;
-            }
-        }
-
-        .collectionTimeView-tree {
-            display: inline-block;
-            width: 100%;
-            height: calc(100% - 30px);
-        }
-    }
-
     .collectionTimeView-innards {
         display: inline-block;
         width: calc(100% - 200px);
         height: 100%;
     }
-
-    .collectionTimeView-dragger {
-        background-color: lightgray;
-        height: 40px;
-        width: 20px;
-        position: absolute;
-        border-radius: 10px;
-        top: 55%;
-        border: 1px black solid;
-        z-index: 2;
-        left: -10px;
-    }
 }
 
 .collectionTimeView-pivot {
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 6d3b9b7f1..69922d6d9 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -1,16 +1,12 @@
-import { faEdit } from "@fortawesome/free-solid-svg-icons";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed, observable, runInAction, trace } from "mobx";
+import { action, computed, observable, runInAction } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, DocListCast, Field, WidthSym, HeightSym } from "../../../new_fields/Doc";
+import { Doc } from "../../../new_fields/Doc";
 import { List } from "../../../new_fields/List";
 import { ObjectField } from "../../../new_fields/ObjectField";
 import { RichTextField } from "../../../new_fields/RichTextField";
-import { listSpec } from "../../../new_fields/Schema";
 import { ComputedField, ScriptField } from "../../../new_fields/ScriptField";
-import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
-import { Docs } from "../../documents/Documents";
-import { DocumentType } from "../../documents/DocumentTypes";
+import { NumCast, StrCast } from "../../../new_fields/Types";
+import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../../Utils";
 import { Scripting } from "../../util/Scripting";
 import { ContextMenu } from "../ContextMenu";
 import { ContextMenuProps } from "../ContextMenuItem";
@@ -19,145 +15,33 @@ import { ViewDefBounds } from "./collectionFreeForm/CollectionFreeFormLayoutEngi
 import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView";
 import { CollectionSubView } from "./CollectionSubView";
 import "./CollectionTimeView.scss";
-import { CollectionTreeView } from "./CollectionTreeView";
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
 import React = require("react");
-import { setupMoveUpEvents, returnFalse, emptyFunction } from "../../../Utils";
 
 @observer
 export class CollectionTimeView extends CollectionSubView(doc => doc) {
     _changing = false;
     @observable _layoutEngine = "pivot";
-    @observable _facetWidth = 0;
     @observable _collapsed: boolean = false;
     componentWillUnmount() {
         this.props.Document.onChildClick = undefined;
     }
     componentDidMount() {
         this.props.Document._freezeOnDrop = true;
-        if (!this.props.Document._facetCollection) {
-            const scriptText = "setDocFilter(containingTreeView.target, heading, this.title, checked)";
-            const facetCollection = Docs.Create.TreeDocument([], { title: "facetFilters", _yMargin: 0, treeViewHideTitle: true, treeViewHideHeaderFields: true });
-            facetCollection.target = this.props.Document;
-            facetCollection.onCheckedClick = ScriptField.MakeScript(scriptText, { this: Doc.name, heading: "string", checked: "string", containingTreeView: Doc.name });
-            this.props.Document.excludeFields = new List<string>(["_facetCollection", "_docFilters"]);
-
-            this.props.Document._facetCollection = facetCollection;
-            this.props.Document._fitToBox = true;
-        }
         const childDetailed = this.props.Document.childDetailed; // bcz: needs to be here to make sure the childDetailed layout template has been loaded when the first item is clicked;
         const childText = "const alias = getAlias(this); Doc.ApplyTemplateTo(containingCollection.childDetailed, alias, 'layout_detailView'); alias.dropAction='alias'; alias.removeDropProperties=new List<string>(['dropAction']);  useRightSplit(alias, shiftKey); ";
         this.props.Document.onChildClick = ScriptField.MakeScript(childText, { this: Doc.name, heading: "string", containingCollection: Doc.name, shiftKey: "boolean" });
-
+        this.props.Document._fitToBox = true;
         if (!this.props.Document.onViewDefClick) {
             this.props.Document.onViewDefDivClick = ScriptField.MakeScript("pivotColumnClick(this,payload)", { payload: "any" });
         }
     }
 
-    bodyPanelWidth = () => this.props.PanelWidth() - this._facetWidth;
-    getTransform = () => this.props.ScreenToLocalTransform().translate(-this._facetWidth, 0);
     layoutEngine = () => this._layoutEngine;
-    facetWidth = () => this._facetWidth;
     toggleVisibility = action(() => this._collapsed = !this._collapsed);
 
-    @computed get _allFacets() {
-        const facets = new Set<string>();
-        this.childDocs.forEach(child => Object.keys(Doc.GetProto(child)).forEach(key => facets.add(key)));
-        Doc.AreProtosEqual(this.dataDoc, this.props.Document) && this.childDocs.forEach(child => Object.keys(child).forEach(key => facets.add(key)));
-        return Array.from(facets);
-    }
-
-    /**
-     * Responds to clicking the check box in the flyout menu
-     */
-    facetClick = (facetHeader: string) => {
-        const facetCollection = this.props.Document._facetCollection;
-        if (facetCollection instanceof Doc) {
-            const found = DocListCast(facetCollection.data).findIndex(doc => doc.title === facetHeader);
-            if (found !== -1) {
-                (facetCollection.data as List<Doc>).splice(found, 1);
-                const docFilter = Cast(this.props.Document._docFilters, listSpec("string"));
-                if (docFilter) {
-                    let index: number;
-                    while ((index = docFilter.findIndex(item => item === facetHeader)) !== -1) {
-                        docFilter.splice(index, 3);
-                    }
-                }
-                const docRangeFilters = Cast(this.props.Document._docRangeFilters, listSpec("string"));
-                if (docRangeFilters) {
-                    let index: number;
-                    while ((index = docRangeFilters.findIndex(item => item === facetHeader)) !== -1) {
-                        docRangeFilters.splice(index, 3);
-                    }
-                }
-            } else {
-                const allCollectionDocs = DocListCast(this.dataDoc[this.props.fieldKey]);
-                const facetValues = Array.from(allCollectionDocs.reduce((set, child) =>
-                    set.add(Field.toString(child[facetHeader] as Field)), new Set<string>()));
-
-                let nonNumbers = 0;
-                let minVal = Number.MAX_VALUE, maxVal = -Number.MAX_VALUE;
-                facetValues.map(val => {
-                    const num = Number(val);
-                    if (Number.isNaN(num)) {
-                        nonNumbers++;
-                    } else {
-                        minVal = Math.min(num, minVal);
-                        maxVal = Math.max(num, maxVal);
-                    }
-                });
-                if (nonNumbers / allCollectionDocs.length < .1) {
-                    const ranged = Doc.readDocRangeFilter(this.props.Document, facetHeader);
-                    const newFacet = Docs.Create.SliderDocument({ title: facetHeader });
-                    Doc.GetProto(newFacet).type = DocumentType.COL; // forces item to show an open/close button instead ofa checkbox
-                    newFacet.treeViewExpandedView = "layout";
-                    newFacet.treeViewOpen = true;
-                    newFacet._sliderMin = ranged === undefined ? minVal : ranged[0];
-                    newFacet._sliderMax = ranged === undefined ? maxVal : ranged[1];
-                    newFacet._sliderMinThumb = minVal;
-                    newFacet._sliderMaxThumb = maxVal;
-                    newFacet.target = this.props.Document;
-                    const scriptText = `setDocFilterRange(this.target, "${facetHeader}", range)`;
-                    newFacet.onThumbChanged = ScriptField.MakeScript(scriptText, { this: Doc.name, range: "number" });
-
-                    Doc.AddDocToList(facetCollection, "data", newFacet);
-                } else {
-                    const newFacet = Docs.Create.TreeDocument([], { title: facetHeader, treeViewOpen: true, isFacetFilter: true });
-                    const capturedVariables = { layoutDoc: this.props.Document, dataDoc: this.dataDoc };
-                    const params = { layoutDoc: Doc.name, dataDoc: Doc.name, };
-                    newFacet.data = ComputedField.MakeFunction(`readFacetData(layoutDoc, dataDoc, "${this.props.fieldKey}", "${facetHeader}")`, params, capturedVariables);
-                    Doc.AddDocToList(facetCollection, "data", newFacet);
-                }
-            }
-        }
-    }
-
-    menuCallback = (x: number, y: number) => {
-        ContextMenu.Instance.clearItems();
-        const docItems: ContextMenuProps[] = [];
-        const keySet: Set<string> = new Set();
-
-        this.childLayoutPairs.map(pair => this._allFacets.filter(fieldKey =>
-            pair.layout[fieldKey] instanceof RichTextField ||
-            typeof (pair.layout[fieldKey]) === "number" ||
-            typeof (pair.layout[fieldKey]) === "string").map(fieldKey => keySet.add(fieldKey)));
-        Array.from(keySet).map(fieldKey =>
-            docItems.push({ description: ":" + fieldKey, event: () => this.props.Document._pivotField = fieldKey, icon: "compress-arrows-alt" }));
-        docItems.push({ description: ":(null)", event: () => this.props.Document._pivotField = undefined, icon: "compress-arrows-alt" });
-        ContextMenu.Instance.addItem({ description: "Pivot Fields ...", subitems: docItems, icon: "eye" });
-        const pt = this.props.ScreenToLocalTransform().inverse().transformPoint(x, y);
-        ContextMenu.Instance.displayMenu(x, y, ":");
-    }
-
-    onPointerDown = (e: React.PointerEvent) => {
-        setupMoveUpEvents(this, e, action((e: PointerEvent, down: number[], delta: number[]) => {
-            this._facetWidth = Math.max(this.props.ScreenToLocalTransform().transformPoint(e.clientX,0)[0], 0);
-            return false;
-        }), returnFalse, () => this._facetWidth = this._facetWidth < 15 ? 200 : 0);
-    }
-
     onMinDown = (e: React.PointerEvent) => {
         setupMoveUpEvents(this, e, action((e: PointerEvent, down: number[], delta: number[]) => {
             const minReq = NumCast(this.props.Document[this.props.fieldKey + "-timelineMinReq"], NumCast(this.props.Document[this.props.fieldKey + "-timelineMin"], 0));
@@ -188,34 +72,8 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
     }
 
     @computed get contents() {
-        return <div className="collectionTimeView-innards" key="timeline" style={{ width: this.bodyPanelWidth() }}>
-            <CollectionFreeFormView  {...this.props} layoutEngine={this.layoutEngine} ScreenToLocalTransform={this.getTransform} PanelWidth={this.bodyPanelWidth} />
-        </div>;
-    }
-    @computed get filterView() {
-        trace();
-        const facetCollection = Cast(this.props.Document?._facetCollection, Doc, null);
-        const flyout = (
-            <div className="collectionTimeView-flyout" style={{ width: `${this._facetWidth}`, height: this.props.PanelHeight() - 30 }} onWheel={e => e.stopPropagation()}>
-                {this._allFacets.map(facet => <label className="collectionTimeView-flyout-item" key={`${facet}`} onClick={e => this.facetClick(facet)}>
-                    <input type="checkbox" onChange={e => { }} checked={DocListCast((this.props.Document._facetCollection as Doc)?.data).some(d => d.title === facet)} />
-                    <span className="checkmark" />
-                    {facet}
-                </label>)}
-            </div>
-        );
-        return <div className="collectionTimeView-treeView" style={{ width: `${this._facetWidth}px`, overflow: this._facetWidth < 15 ? "hidden" : undefined }}>
-            <div className="collectionTimeView-addFacet" style={{ width: `${this._facetWidth}px` }} onPointerDown={e => e.stopPropagation()}>
-                <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={flyout}>
-                    <div className="collectionTimeView-button">
-                        <span className="collectionTimeView-span">Facet Filters</span>
-                        <FontAwesomeIcon icon={faEdit} size={"lg"} />
-                    </div>
-                </Flyout>
-            </div>
-            <div className="collectionTimeView-tree" key="tree">
-                <CollectionTreeView {...this.props} PanelWidth={this.facetWidth} DataDoc={undefined} Document={facetCollection} />
-            </div>
+        return <div className="collectionTimeView-innards" key="timeline" style={{ width: "100%" }}>
+            <CollectionFreeFormView  {...this.props} layoutEngine={this.layoutEngine} />
         </div>;
     }
 
@@ -234,8 +92,30 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
 
         ContextMenu.Instance.addItem({ description: "Pivot/Time Options ...", subitems: layoutItems, icon: "eye" });
     }
+    @computed get _allFacets() {
+        const facets = new Set<string>();
+        this.childDocs.forEach(child => Object.keys(Doc.GetProto(child)).forEach(key => facets.add(key)));
+        Doc.AreProtosEqual(this.dataDoc, this.props.Document) && this.childDocs.forEach(child => Object.keys(child).forEach(key => facets.add(key)));
+        return Array.from(facets);
+    }
+    menuCallback = (x: number, y: number) => {
+        ContextMenu.Instance.clearItems();
+        const docItems: ContextMenuProps[] = [];
+        const keySet: Set<string> = new Set();
 
-    render() {
+        this.childLayoutPairs.map(pair => this._allFacets.filter(fieldKey =>
+            pair.layout[fieldKey] instanceof RichTextField ||
+            typeof (pair.layout[fieldKey]) === "number" ||
+            typeof (pair.layout[fieldKey]) === "string").map(fieldKey => keySet.add(fieldKey)));
+        Array.from(keySet).map(fieldKey =>
+            docItems.push({ description: ":" + fieldKey, event: () => this.props.Document._pivotField = fieldKey, icon: "compress-arrows-alt" }));
+        docItems.push({ description: ":(null)", event: () => this.props.Document._pivotField = undefined, icon: "compress-arrows-alt" });
+        ContextMenu.Instance.addItem({ description: "Pivot Fields ...", subitems: docItems, icon: "eye" });
+        const pt = this.props.ScreenToLocalTransform().inverse().transformPoint(x, y);
+        ContextMenu.Instance.displayMenu(x, y, ":");
+    }
+
+    @computed get pivotKeyUI() {
         const newEditableViewProps = {
             GetValue: () => "",
             SetValue: (value: any) => {
@@ -250,7 +130,26 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
             toggle: this.toggleVisibility,
             color: "#f1efeb" // this.props.headingObject ? this.props.headingObject.color : "#f1efeb";
         };
+        return <div className={"pivotKeyEntry"}>
+            <button className="collectionTimeView-backBtn"
+                onClick={action(() => {
+                    let prevFilterIndex = NumCast(this.props.Document._prevFilterIndex);
+                    if (prevFilterIndex > 0) {
+                        prevFilterIndex--;
+                        this.props.Document._docFilters = ObjectField.MakeCopy(this.props.Document["_prevDocFilter" + prevFilterIndex] as ObjectField);
+                        this.props.Document._docRangeFilters = ObjectField.MakeCopy(this.props.Document["_prevDocRangeFilters" + prevFilterIndex] as ObjectField);
+                        this.props.Document._prevFilterIndex = prevFilterIndex;
+                    } else {
+                        this.props.Document._docFilters = new List([]);
+                    }
+                })}>
+                back
+            </button>
+            <EditableView {...newEditableViewProps} display={"inline"} menuCallback={this.menuCallback} />
+        </div>
+    }
 
+    render() {
         let nonNumbers = 0;
         this.childDocs.map(doc => {
             const num = NumCast(doc[StrCast(this.props.Document._pivotField)], Number(StrCast(doc[StrCast(this.props.Document._pivotField)])));
@@ -270,41 +169,16 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
             }
         }
 
-
-        const facetCollection = Cast(this.props.Document?._facetCollection, Doc, null);
-        return !facetCollection ? (null) :
-            <div className={"collectionTimeView" + (doTimeline ? "" : "-pivot")} onContextMenu={this.specificMenu}
-                style={{ height: `calc(100%  - ${this.props.Document._chromeStatus === "enabled" ? 51 : 0}px)` }}>
-                <div className={"pivotKeyEntry"}>
-                    <button className="collectionTimeView-backBtn"
-                        onClick={action(() => {
-                            let prevFilterIndex = NumCast(this.props.Document._prevFilterIndex);
-                            if (prevFilterIndex > 0) {
-                                prevFilterIndex--;
-                                this.props.Document._docFilters = ObjectField.MakeCopy(this.props.Document["_prevDocFilter" + prevFilterIndex] as ObjectField);
-                                this.props.Document._docRangeFilters = ObjectField.MakeCopy(this.props.Document["_prevDocRangeFilters" + prevFilterIndex] as ObjectField);
-                                this.props.Document._prevFilterIndex = prevFilterIndex;
-                            } else {
-                                this.props.Document._docFilters = new List([]);
-                            }
-                        })}>
-                        back
-                    </button>
-                    <EditableView {...newEditableViewProps} display={"inline"} menuCallback={this.menuCallback} />
-                </div>
-                {!this.props.isSelected() || this.props.PanelHeight() < 100 ? (null) :
-                    <div className="collectionTimeView-dragger" key="dragger" onPointerDown={this.onPointerDown} style={{ transform: `translate(${this._facetWidth}px, 0px)` }} >
-                        <span title="library View Dragger" style={{ width: "5px", position: "absolute", top: "0" }} />
-                    </div>
-                }
-                {this.filterView}
-                {this.contents}
-                {!this.props.isSelected() || !doTimeline ? (null) : <>
-                    <div className="collectionTimeView-thumb-min collectionTimeView-thumb" key="min" onPointerDown={this.onMinDown} />
-                    <div className="collectionTimeView-thumb-max collectionTimeView-thumb" key="mid" onPointerDown={this.onMaxDown} />
-                    <div className="collectionTimeView-thumb-mid collectionTimeView-thumb" key="max" onPointerDown={this.onMidDown} />
-                </>}
-            </div>;
+        return <div className={"collectionTimeView" + (doTimeline ? "" : "-pivot")} onContextMenu={this.specificMenu}
+            style={{ height: `calc(100%  - ${this.props.Document._chromeStatus === "enabled" ? 51 : 0}px)` }}>
+            {this.pivotKeyUI}
+            {this.contents}
+            {!this.props.isSelected() || !doTimeline ? (null) : <>
+                <div className="collectionTimeView-thumb-min collectionTimeView-thumb" key="min" onPointerDown={this.onMinDown} />
+                <div className="collectionTimeView-thumb-max collectionTimeView-thumb" key="mid" onPointerDown={this.onMaxDown} />
+                <div className="collectionTimeView-thumb-mid collectionTimeView-thumb" key="max" onPointerDown={this.onMidDown} />
+            </>}
+        </div>;
     }
 }
 
diff --git a/src/client/views/collections/CollectionView.scss b/src/client/views/collections/CollectionView.scss
index 1c46081a1..0246abccc 100644
--- a/src/client/views/collections/CollectionView.scss
+++ b/src/client/views/collections/CollectionView.scss
@@ -10,6 +10,60 @@
     width: 100%;
     height: 100%;
     overflow: hidden; // bcz: used to be 'auto' which would create scrollbars when there's a floating doc that's not visible. not sure if that's better, but the scrollbars are annoying...
+
+
+    .collectionTimeView-dragger {
+        background-color: lightgray;
+        height: 40px;
+        width: 20px;
+        position: absolute;
+        border-radius: 10px;
+        top: 55%;
+        border: 1px black solid;
+        z-index: 2;
+        left: -10px;
+    }
+    .collectionTimeView-treeView {
+        display: flex;
+        flex-direction: column;
+        width: 200px;
+        height: 100%;
+        position: absolute;
+        left: 0;
+        top: 0;
+
+        .collectionTimeView-addfacet {
+            display: inline-block;
+            width: 200px;
+            height: 30px;
+            background: darkGray;
+            text-align: left;
+
+            .collectionTimeView-button {
+                align-items: center;
+                display: flex;
+                width: 100%;
+                height: 100%;
+
+                .collectionTimeView-span {
+                    margin: auto;
+                }
+            }
+
+            >div,
+            >div>div {
+                width: 100%;
+                height: 100%;
+                text-align: center;
+            }
+        }
+
+        .collectionTimeView-tree {
+            display: inline-block;
+            width: 100%;
+            height: calc(100% - 30px);
+        }
+    }
 }
 
 #google-tags {
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 7683e1550..a1f173746 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -1,18 +1,19 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
-import { faEye } from '@fortawesome/free-regular-svg-icons';
+import { faEye, faEdit } from '@fortawesome/free-regular-svg-icons';
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { faColumns, faCopy, faEllipsisV, faFingerprint, faImage, faProjectDiagram, faSignature, faSquare, faTh, faThList, faTree } from '@fortawesome/free-solid-svg-icons';
-import { action, observable } from 'mobx';
+import { action, observable, computed } from 'mobx';
 import { observer } from "mobx-react";
 import * as React from 'react';
 import Lightbox from 'react-image-lightbox-with-rotate';
 import 'react-image-lightbox-with-rotate/style.css'; // This only needs to be imported once in your app
 import { DateField } from '../../../new_fields/DateField';
-import { DataSym, Doc, DocListCast } from '../../../new_fields/Doc';
+import { DataSym, Doc, DocListCast, Field } from '../../../new_fields/Doc';
 import { List } from '../../../new_fields/List';
 import { BoolCast, Cast, NumCast, StrCast } from '../../../new_fields/Types';
 import { ImageField } from '../../../new_fields/URLField';
 import { TraceMobx } from '../../../new_fields/util';
-import { Utils } from '../../../Utils';
+import { Utils, setupMoveUpEvents, returnFalse } from '../../../Utils';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { DocumentManager } from '../../util/DocumentManager';
 import { ImageUtils } from '../../util/Import & Export/ImageUtils';
@@ -38,6 +39,12 @@ import './CollectionView.scss';
 import { CollectionViewBaseChrome } from './CollectionViewChromes';
 import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
 import { Id } from '../../../new_fields/FieldSymbols';
+import { listSpec } from '../../../new_fields/Schema';
+import { Docs } from '../../documents/Documents';
+import { ScriptField, ComputedField } from '../../../new_fields/ScriptField';
+const higflyout = require("@hig/flyout");
+export const { anchorPoints } = higflyout;
+export const Flyout = higflyout.default;
 export const COLLECTION_BORDER_WIDTH = 2;
 const path = require('path');
 library.add(faTh, faTree, faSquare, faProjectDiagram, faSignature, faThList, faFingerprint, faColumns, faEllipsisV, faImage, faEye as any, faCopy);
@@ -84,6 +91,7 @@ export interface CollectionRenderProps {
     moveDocument: (document: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => boolean;
     active: () => boolean;
     whenActiveChanged: (isActive: boolean) => void;
+    PanelWidth: () => number;
 }
 
 @observer
@@ -268,6 +276,158 @@ export class CollectionView extends Touchable<FieldViewProps> {
             onMovePrevRequest={action(() => this._curLightboxImg = (this._curLightboxImg + images.length - 1) % images.length)}
             onMoveNextRequest={action(() => this._curLightboxImg = (this._curLightboxImg + 1) % images.length)} />);
     }
+    @observable _facetWidth = 0;
+
+    bodyPanelWidth = () => this.props.PanelWidth() - this._facetWidth;
+    getTransform = () => this.props.ScreenToLocalTransform().translate(-this._facetWidth, 0);
+    facetWidth = () => this._facetWidth;
+
+    @computed get dataDoc() {
+        return (this.props.DataDoc && this.props.Document.isTemplateForField ? Doc.GetProto(this.props.DataDoc) :
+            this.props.Document.resolvedDataDoc ? this.props.Document : Doc.GetProto(this.props.Document)); // if the layout document has a resolvedDataDoc, then we don't want to get its parent which would be the unexpanded template
+    }
+    // The data field for rendering this collection will be on the this.props.Document unless we're rendering a template in which case we try to use props.DataDoc.
+    // When a document has a DataDoc but it's not a template, then it contains its own rendering data, but needs to pass the DataDoc through
+    // to its children which may be templates.
+    // If 'annotationField' is specified, then all children exist on that field of the extension document, otherwise, they exist directly on the data document under 'fieldKey'
+    @computed get dataField() {
+        return this.dataDoc[this.props.fieldKey];
+    }
+
+    get childLayoutPairs(): { layout: Doc; data: Doc; }[] {
+        const { Document, DataDoc } = this.props;
+        const validPairs = this.childDocs.map(doc => Doc.GetLayoutDataDocPair(Document, DataDoc, doc)).filter(pair => pair.layout);
+        return validPairs.map(({ data, layout }) => ({ data, layout: layout! })); // this mapping is a bit of a hack to coerce types
+    }
+    get childDocList() {
+        return Cast(this.dataField, listSpec(Doc));
+    }
+    get childDocs() {
+        const dfield = this.dataField;
+        const rawdocs = (dfield instanceof Doc) ? [dfield] : Cast(dfield, listSpec(Doc), this.props.Document.expandedTemplate ? [Cast(this.props.Document.expandedTemplate, Doc, null)] : []);
+        const docs = rawdocs.filter(d => !(d instanceof Promise)).map(d => d as Doc);
+        const viewSpecScript = Cast(this.props.Document.viewSpecScript, ScriptField);
+        return viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs;
+    }
+    @computed get _allFacets() {
+        const facets = new Set<string>();
+        this.childDocs.forEach(child => Object.keys(Doc.GetProto(child)).forEach(key => facets.add(key)));
+        Doc.AreProtosEqual(this.dataDoc, this.props.Document) && this.childDocs.forEach(child => Object.keys(child).forEach(key => facets.add(key)));
+        return Array.from(facets);
+    }
+
+    /**
+     * Responds to clicking the check box in the flyout menu
+     */
+    facetClick = (facetHeader: string) => {
+        const facetCollection = this.props.Document._facetCollection;
+        if (facetCollection instanceof Doc) {
+            const found = DocListCast(facetCollection.data).findIndex(doc => doc.title === facetHeader);
+            if (found !== -1) {
+                (facetCollection.data as List<Doc>).splice(found, 1);
+                const docFilter = Cast(this.props.Document._docFilters, listSpec("string"));
+                if (docFilter) {
+                    let index: number;
+                    while ((index = docFilter.findIndex(item => item === facetHeader)) !== -1) {
+                        docFilter.splice(index, 3);
+                    }
+                }
+                const docRangeFilters = Cast(this.props.Document._docRangeFilters, listSpec("string"));
+                if (docRangeFilters) {
+                    let index: number;
+                    while ((index = docRangeFilters.findIndex(item => item === facetHeader)) !== -1) {
+                        docRangeFilters.splice(index, 3);
+                    }
+                }
+            } else {
+                const allCollectionDocs = DocListCast(this.dataDoc[this.props.fieldKey]);
+                const facetValues = Array.from(allCollectionDocs.reduce((set, child) =>
+                    set.add(Field.toString(child[facetHeader] as Field)), new Set<string>()));
+
+                let nonNumbers = 0;
+                let minVal = Number.MAX_VALUE, maxVal = -Number.MAX_VALUE;
+                facetValues.map(val => {
+                    const num = Number(val);
+                    if (Number.isNaN(num)) {
+                        nonNumbers++;
+                    } else {
+                        minVal = Math.min(num, minVal);
+                        maxVal = Math.max(num, maxVal);
+                    }
+                });
+                if (nonNumbers / allCollectionDocs.length < .1) {
+                    const ranged = Doc.readDocRangeFilter(this.props.Document, facetHeader);
+                    const newFacet = Docs.Create.SliderDocument({ title: facetHeader });
+                    Doc.GetProto(newFacet).type = DocumentType.COL; // forces item to show an open/close button instead ofa checkbox
+                    newFacet.treeViewExpandedView = "layout";
+                    newFacet.treeViewOpen = true;
+                    newFacet._sliderMin = ranged === undefined ? minVal : ranged[0];
+                    newFacet._sliderMax = ranged === undefined ? maxVal : ranged[1];
+                    newFacet._sliderMinThumb = minVal;
+                    newFacet._sliderMaxThumb = maxVal;
+                    newFacet.target = this.props.Document;
+                    const scriptText = `setDocFilterRange(this.target, "${facetHeader}", range)`;
+                    newFacet.onThumbChanged = ScriptField.MakeScript(scriptText, { this: Doc.name, range: "number" });
+
+                    Doc.AddDocToList(facetCollection, "data", newFacet);
+                } else {
+                    const newFacet = Docs.Create.TreeDocument([], { title: facetHeader, treeViewOpen: true, isFacetFilter: true });
+                    const capturedVariables = { layoutDoc: this.props.Document, dataDoc: this.dataDoc };
+                    const params = { layoutDoc: Doc.name, dataDoc: Doc.name, };
+                    newFacet.data = ComputedField.MakeFunction(`readFacetData(layoutDoc, dataDoc, "${this.props.fieldKey}", "${facetHeader}")`, params, capturedVariables);
+                    Doc.AddDocToList(facetCollection, "data", newFacet);
+                }
+            }
+        }
+    }
+
+
+    onPointerDown = (e: React.PointerEvent) => {
+        setupMoveUpEvents(this, e, action((e: PointerEvent, down: number[], delta: number[]) => {
+            this._facetWidth = Math.max(this.props.ScreenToLocalTransform().transformPoint(e.clientX, 0)[0], 0);
+            return false;
+        }), returnFalse, action(() => this._facetWidth = this._facetWidth < 15 ? 200 : 0));
+    }
+    @computed get filterView() {
+        const facetCollection = Cast(this.props.Document?._facetCollection, Doc);
+        if (this._facetWidth && facetCollection === undefined) setTimeout(() => {
+            const scriptText = "setDocFilter(containingTreeView.target, heading, this.title, checked)";
+            const facetCollection = Docs.Create.TreeDocument([], { title: "facetFilters", _yMargin: 0, treeViewHideTitle: true, treeViewHideHeaderFields: true });
+            facetCollection.target = this.props.Document;
+            facetCollection.onCheckedClick = ScriptField.MakeScript(scriptText, { this: Doc.name, heading: "string", checked: "string", containingTreeView: Doc.name });
+            this.props.Document.excludeFields = new List<string>(["_facetCollection", "_docFilters"]);
+
+            this.props.Document._facetCollection = facetCollection;
+        }, 0);
+        const flyout = (
+            <div className="collectionTimeView-flyout" style={{ width: `${this._facetWidth}`, height: this.props.PanelHeight() - 30 }} onWheel={e => e.stopPropagation()}>
+                {this._allFacets.map(facet => <label className="collectionTimeView-flyout-item" key={`${facet}`} onClick={e => this.facetClick(facet)}>
+                    <input type="checkbox" onChange={e => { }} checked={DocListCast((this.props.Document._facetCollection as Doc)?.data).some(d => d.title === facet)} />
+                    <span className="checkmark" />
+                    {facet}
+                </label>)}
+            </div>
+        );
+        return !facetCollection ? (null) :
+            <div className="collectionTimeView-treeView" style={{ width: `${this._facetWidth}px`, overflow: this._facetWidth < 15 ? "hidden" : undefined }}>
+                <div className="collectionTimeView-addFacet" style={{ width: `${this._facetWidth}px` }} onPointerDown={e => e.stopPropagation()}>
+                    <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={flyout}>
+                        <div className="collectionTimeView-button">
+                            <span className="collectionTimeView-span">Facet Filters</span>
+                            <FontAwesomeIcon icon={faEdit} size={"lg"} />
+                        </div>
+                    </Flyout>
+                </div>
+                <div className="collectionTimeView-tree" key="tree">
+                    <CollectionTreeView {...this.props} CollectionView={this} annotationsKey={""} PanelWidth={this.facetWidth}
+                        DataDoc={undefined} Document={facetCollection}
+                        moveDocument={(doc: Doc) => false}
+                        removeDocument={(doc: Doc) => false}
+                        addDocument={(doc: Doc) => false} />
+                </div>
+            </div>;
+    }
+
     render() {
         TraceMobx();
         const props: CollectionRenderProps = {
@@ -276,6 +436,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
             moveDocument: this.moveDocument,
             active: this.active,
             whenActiveChanged: this.whenActiveChanged,
+            PanelWidth: this.bodyPanelWidth
         };
         return (<div className={"collectionView"}
             style={{
@@ -292,6 +453,12 @@ export class CollectionView extends Touchable<FieldViewProps> {
                         Utils.CorsProxy(Cast(d.data, ImageField)!.url.href) : Cast(d.data, ImageField)!.url.href
                     :
                     ""))}
+            {!this.props.isSelected() || this.props.PanelHeight() < 100 ? (null) :
+                <div className="collectionTimeView-dragger" key="dragger" onPointerDown={this.onPointerDown} style={{ transform: `translate(${this._facetWidth}px, 0px)` }} >
+                    <span title="library View Dragger" style={{ width: "5px", position: "absolute", top: "0" }} />
+                </div>
+            }
+            {this.filterView}
         </div>);
     }
 }
\ 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 da44d0d59..a0dd4f2de 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -903,12 +903,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     }.bind(this));
 
     doTimelineLayout(poolData: Map<string, PoolData>) {
-        return computeTimelineLayout(poolData, this.props.Document, this.childDocs, this.filterDocs,
+        return computeTimelineLayout(poolData, this.props.Document, this.childDocs, this.childDocs,
             this.childLayoutPairs, [this.props.PanelWidth(), this.props.PanelHeight()], this.viewDefsToJSX);
     }
 
     doPivotLayout(poolData: Map<string, PoolData>) {
-        return computePivotLayout(poolData, this.props.Document, this.childDocs, this.filterDocs,
+        return computePivotLayout(poolData, this.props.Document, this.childDocs, this.childDocs,
             this.childLayoutPairs, [this.props.PanelWidth(), this.props.PanelHeight()], this.viewDefsToJSX);
     }
 
@@ -934,42 +934,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         return { newPool, computedElementData: this.doFreeformLayout(newPool) };
     }
 
-    @computed get filterDocs() {
-        const docFilters = Cast(this.props.Document._docFilters, listSpec("string"), []);
-        const docRangeFilters = Cast(this.props.Document._docRangeFilters, listSpec("string"), []);
-        const filterFacets: { [key: string]: { [value: string]: string } } = {};  // maps each filter key to an object with value=>modifier fields
-        for (let i = 0; i < docFilters.length; i += 3) {
-            const [key, value, modifiers] = docFilters.slice(i, i + 3);
-            if (!filterFacets[key]) {
-                filterFacets[key] = {};
-            }
-            filterFacets[key][value] = modifiers;
-        }
-        const filteredDocs = docFilters.length ? this.childDocs.filter(d => {
-            for (const facetKey of Object.keys(filterFacets)) {
-                const facet = filterFacets[facetKey];
-                const satisfiesFacet = Object.keys(facet).some(value =>
-                    (facet[value] === "x") !== Doc.matchFieldValue(d, facetKey, value));
-                if (!satisfiesFacet) {
-                    return false;
-                }
-            }
-            return true;
-        }) : this.childDocs;
-        const rangeFilteredDocs = filteredDocs.filter(d => {
-            for (let i = 0; i < docRangeFilters.length; i += 3) {
-                const key = docRangeFilters[i];
-                const min = Number(docRangeFilters[i + 1]);
-                const max = Number(docRangeFilters[i + 2]);
-                const val = Cast(d[key], "number", null);
-                if (val !== undefined && (val < min || val > max)) {
-                    return false;
-                }
-            }
-            return true;
-        });
-        return rangeFilteredDocs;
-    }
     childLayoutDocFunc = () => this.props.childLayoutTemplate?.() || Cast(this.props.Document.childLayoutTemplate, Doc, null);
     get doLayoutComputation() {
         const { newPool, computedElementData } = this.doInternalLayoutComputation;
-- 
cgit v1.2.3-70-g09d2


From 7259b8562e8097ff5b6708cf15bb2f33e9efb148 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 3 Apr 2020 12:23:40 -0400
Subject: fixed ButtonBox drop to allow reordering buttons in a collection. 
 fixed pdfmenu to allow pnning. fixed panning in nested freeformviews.

---
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 54 +++++++++++-----------
 src/client/views/nodes/ButtonBox.tsx               | 14 +++---
 src/client/views/pdf/PDFMenu.tsx                   |  5 +-
 3 files changed, 36 insertions(+), 37 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index a0dd4f2de..2871fe192 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -500,36 +500,36 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
     @action
     pan = (e: PointerEvent | React.Touch | { clientX: number, clientY: number }): void => {
-        // I think it makes sense for the marquee menu to go away when panned. -syip2
-        MarqueeOptionsMenu.Instance && MarqueeOptionsMenu.Instance.fadeOut(true);
+        // bcz: theres should be a better way of doing these than referencing these static instances directly
+        MarqueeOptionsMenu.Instance?.fadeOut(true);// I think it makes sense for the marquee menu to go away when panned. -syip2
+        PDFMenu.Instance.fadeOut(true);
 
-        let x = this.Document._panX || 0;
-        let y = this.Document._panY || 0;
-        const docs = this.childLayoutPairs.filter(pair => pair.layout instanceof Doc).map(pair => pair.layout);
         const [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
-        if (!this.isAnnotationOverlay && docs.length && this.childDataProvider(docs[0])) {
-            PDFMenu.Instance.fadeOut(true);
-            const minx = this.childDataProvider(docs[0]).x;
-            const miny = this.childDataProvider(docs[0]).y;
-            const maxx = this.childDataProvider(docs[0]).width + minx;
-            const maxy = this.childDataProvider(docs[0]).height + miny;
-            const ranges = docs.filter(doc => doc && this.childDataProvider(doc)).reduce((range, doc) => {
-                const x = this.childDataProvider(doc).x;
-                const y = this.childDataProvider(doc).y;
-                const xe = this.childDataProvider(doc).width + x;
-                const ye = this.childDataProvider(doc).height + y;
-                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]]);
-
-            const cscale = this.props.ContainingCollectionDoc ? NumCast(this.props.ContainingCollectionDoc.scale) : 1;
-            const panelDim = [this.props.PanelWidth() * cscale / this.zoomScaling(), this.props.PanelHeight() * cscale / this.zoomScaling()];
-            if (ranges[0][0] - dx > (this.panX() + panelDim[0] / 2)) x = ranges[0][1] + panelDim[0] / 2;
-            if (ranges[0][1] - dx < (this.panX() - panelDim[0] / 2)) x = ranges[0][0] - panelDim[0] / 2;
-            if (ranges[1][0] - dy > (this.panY() + panelDim[1] / 2)) y = ranges[1][1] + panelDim[1] / 2;
-            if (ranges[1][1] - dy < (this.panY() - panelDim[1] / 2)) y = ranges[1][0] - panelDim[1] / 2;
+        let x = (this.Document._panX || 0) - dx;
+        let y = (this.Document._panY || 0) - dy;
+        if (!this.isAnnotationOverlay) {
+            // this section wraps the pan position, horizontally and/or vertically whenever the content is panned out of the viewing bounds
+            const docs = this.childLayoutPairs.filter(pair => pair.layout instanceof Doc).map(pair => pair.layout);
+            const measuredDocs = docs.filter(doc => doc && this.childDataProvider(doc)).map(doc => this.childDataProvider(doc));
+            if (measuredDocs.length) {
+                const ranges = measuredDocs.reduce(({ xrange, yrange }, { x, y, width, height }) =>  // computes range of content
+                    ({
+                        xrange: { min: Math.min(xrange.min, x), max: Math.max(xrange.max, x + width) },
+                        yrange: { min: Math.min(yrange.min, y), max: Math.max(yrange.max, y + height) }
+                    })
+                    , {
+                        xrange: { min: Number.MAX_VALUE, max: -Number.MAX_VALUE },
+                        yrange: { min: Number.MAX_VALUE, max: -Number.MAX_VALUE }
+                    });
+
+                const panelDim = [this.props.PanelWidth() / this.zoomScaling(), this.props.PanelHeight() / this.zoomScaling()];
+                if (ranges.xrange.min > (this.panX() + panelDim[0] / 2)) x = ranges.xrange.max + panelDim[0] / 2;  // snaps pan position of range of content goes out of bounds
+                if (ranges.xrange.max < (this.panX() - panelDim[0] / 2)) x = ranges.xrange.min - panelDim[0] / 2;
+                if (ranges.yrange.min > (this.panY() + panelDim[1] / 2)) y = ranges.yrange.max + panelDim[1] / 2;
+                if (ranges.yrange.max < (this.panY() - panelDim[1] / 2)) y = ranges.yrange.min - panelDim[1] / 2;
+            }
         }
-        this.setPan(x - dx, y - dy);
+        this.setPan(x, y);
         this._lastX = e.clientX;
         this._lastY = e.clientY;
     }
diff --git a/src/client/views/nodes/ButtonBox.tsx b/src/client/views/nodes/ButtonBox.tsx
index f1bf7cfcf..1b70ff824 100644
--- a/src/client/views/nodes/ButtonBox.tsx
+++ b/src/client/views/nodes/ButtonBox.tsx
@@ -42,9 +42,7 @@ export class ButtonBox extends DocComponent<FieldViewProps, ButtonDocument>(Butt
 
 
     protected createDropTarget = (ele: HTMLDivElement) => {
-        if (this.dropDisposer) {
-            this.dropDisposer();
-        }
+        this.dropDisposer?.();
         if (ele) {
             this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
         }
@@ -55,7 +53,7 @@ export class ButtonBox extends DocComponent<FieldViewProps, ButtonDocument>(Butt
         funcs.push({
             description: "Clear Script Params", event: () => {
                 const params = FieldValue(this.Document.buttonParams);
-                params && params.map(p => this.props.Document[p] = undefined);
+                params?.map(p => this.props.Document[p] = undefined);
             }, icon: "trash"
         });
 
@@ -66,7 +64,9 @@ export class ButtonBox extends DocComponent<FieldViewProps, ButtonDocument>(Butt
     @action
     drop = (e: Event, de: DragManager.DropEvent) => {
         const docDragData = de.complete.docDragData;
-        if (docDragData && e.target) {
+        const params = this.Document.buttonParams;
+        const missingParams = params?.filter(p => this.props.Document[p] === undefined);
+        if (docDragData && missingParams?.includes((e.target as any).textContent)) {
             this.props.Document[(e.target as any).textContent] = new List<Doc>(docDragData.droppedDocuments.map((d, i) =>
                 d.onDragStart ? docDragData.draggedDocuments[i] : d));
             e.stopPropagation();
@@ -75,8 +75,8 @@ export class ButtonBox extends DocComponent<FieldViewProps, ButtonDocument>(Butt
     // (!missingParams || !missingParams.length ? "" : "(" + missingParams.map(m => m + ":").join(" ") + ")")
     render() {
         const params = this.Document.buttonParams;
-        const missingParams = params && params.filter(p => this.props.Document[p] === undefined);
-        params && params.map(p => DocListCast(this.props.Document[p])); // bcz: really hacky form of prefetching ... 
+        const missingParams = params?.filter(p => this.props.Document[p] === undefined);
+        params?.map(p => DocListCast(this.props.Document[p])); // bcz: really hacky form of prefetching ... 
         return (
             <div className="buttonBox-outerDiv" ref={this.createDropTarget} onContextMenu={this.specificContextMenu}
                 style={{ boxShadow: this.Document.opacity === 0 ? undefined : StrCast(this.Document.boxShadow, "") }}>
diff --git a/src/client/views/pdf/PDFMenu.tsx b/src/client/views/pdf/PDFMenu.tsx
index 05c70b74a..5913c5a82 100644
--- a/src/client/views/pdf/PDFMenu.tsx
+++ b/src/client/views/pdf/PDFMenu.tsx
@@ -61,11 +61,10 @@ export default class PDFMenu extends AntimodeMenu {
         e.preventDefault();
     }
 
-    @action
-    togglePin = (e: React.MouseEvent) => {
+    togglePin = action((e: React.MouseEvent) => {
         this.Pinned = !this.Pinned;
         !this.Pinned && (this.Highlighting = false);
-    }
+    })
 
     @action
     highlightClicked = (e: React.MouseEvent) => {
-- 
cgit v1.2.3-70-g09d2


From adabfbea2b034beb310530aef9cc8b206e260b67 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 3 Apr 2020 20:23:15 -0400
Subject: added rootSelected() to make forceActive work better

---
 src/client/documents/Documents.ts                  |  2 +-
 src/client/util/RichTextSchema.tsx                 |  1 +
 src/client/views/DocComponent.tsx                  |  6 +++--
 src/client/views/GestureOverlay.tsx                |  1 +
 src/client/views/MainView.tsx                      |  4 ++++
 src/client/views/OverlayView.tsx                   |  1 +
 src/client/views/Palette.tsx                       | 26 +++++++---------------
 src/client/views/RecommendationsBox.tsx            |  1 +
 src/client/views/SearchDocBox.tsx                  |  3 ++-
 .../views/collections/CollectionDockingView.tsx    |  1 +
 .../views/collections/CollectionLinearView.tsx     |  3 ++-
 .../views/collections/CollectionSchemaView.tsx     |  1 +
 .../views/collections/CollectionStackingView.tsx   |  1 +
 src/client/views/collections/CollectionSubView.tsx |  5 +++++
 .../views/collections/CollectionTreeView.tsx       |  2 ++
 src/client/views/collections/CollectionView.tsx    |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  1 +
 .../collections/collectionFreeForm/MarqueeView.tsx |  1 -
 src/client/views/nodes/AudioBox.tsx                | 12 +++++++---
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  7 +++---
 .../views/nodes/ContentFittingDocumentView.tsx     |  1 +
 src/client/views/nodes/DocumentBox.tsx             |  1 +
 src/client/views/nodes/DocumentContentsView.tsx    |  4 ++--
 src/client/views/nodes/DocumentView.tsx            | 24 +++++++++++++++-----
 src/client/views/nodes/FieldView.tsx               |  1 +
 src/client/views/nodes/FormattedTextBoxComment.tsx |  1 +
 src/client/views/pdf/PDFViewer.tsx                 |  3 +--
 .../views/presentationview/PresElementBox.tsx      |  3 ++-
 src/client/views/search/SearchItem.tsx             |  3 ++-
 src/mobile/MobileInterface.tsx                     |  2 ++
 .../authentication/models/current_user_utils.ts    |  2 +-
 31 files changed, 83 insertions(+), 43 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index a69bfae01..0a2269bac 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -435,7 +435,7 @@ export namespace Docs {
         Scripting.addGlobal(Buxton);
 
         const delegateKeys = ["x", "y", "layoutKey", "_width", "_height", "_panX", "_panY", "_viewType", "_nativeWidth", "_nativeHeight", "dropAction", "childDropAction", "_annotationOn",
-            "_chromeStatus", "_forceActive", "_autoHeight", "_fitWidth", "_LODdisable", "_itemIndex", "_showSidebar", "_showTitle", "_showCaption", "_showTitleHover", "_backgroundColor",
+            "_chromeStatus", "_autoHeight", "_fitWidth", "_LODdisable", "_itemIndex", "_showSidebar", "_showTitle", "_showCaption", "_showTitleHover", "_backgroundColor",
             "_xMargin", "_yMargin", "_xPadding", "_yPadding", "_singleLine", "_scrollTop",
             "_color", "isButton", "isBackground", "removeDropProperties", "treeViewOpen"];
 
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 094cd58f3..4a930177d 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -818,6 +818,7 @@ export class DashDocView {
                     LibraryPath={this._textBox.props.LibraryPath}
                     fitToBox={BoolCast(dashDoc._fitToBox)}
                     addDocument={returnFalse}
+                    rootSelected={this._textBox.props.isSelected}
                     removeDocument={removeDoc}
                     ScreenToLocalTransform={this.getDocTransform}
                     addDocTab={this._textBox.props.addDocTab}
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index f4e830a48..454c245cc 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -29,6 +29,7 @@ interface DocExtendableProps {
     fieldKey: string;
     isSelected: (outsideReaction?: boolean) => boolean;
     renderDepth: number;
+    rootSelected: () => boolean;
 }
 export function DocExtendableComponent<P extends DocExtendableProps, T>(schemaCtor: (doc: Doc) => T) {
     class Component extends Touchable<P> {
@@ -36,7 +37,7 @@ export function DocExtendableComponent<P extends DocExtendableProps, T>(schemaCt
         @computed get Document(): T { return schemaCtor(this.props.Document); }
         @computed get layoutDoc() { return Doc.Layout(this.props.Document); }
         @computed get dataDoc() { return (this.props.DataDoc && (this.props.Document.isTemplateForField || this.props.Document.isTemplateDoc) ? this.props.DataDoc : Cast(this.props.Document.resolvedDataDoc, Doc, null) || Doc.GetProto(this.props.Document)) as Doc; }
-        active = (outsideReaction?: boolean) => !this.props.Document.isBackground && (this.props.Document.forceActive || this.props.isSelected(outsideReaction) || this.props.renderDepth === 0);//  && !InkingControl.Instance.selectedTool;  // bcz: inking state shouldn't affect static tools 
+        active = (outsideReaction?: boolean) => !this.props.Document.isBackground && ((this.props.Document.forceActive && this.props.rootSelected()) || this.props.isSelected(outsideReaction) || this.props.renderDepth === 0);//  && !InkingControl.Instance.selectedTool;  // bcz: inking state shouldn't affect static tools 
     }
     return Component;
 }
@@ -50,6 +51,7 @@ export interface DocAnnotatableProps {
     active: () => boolean;
     whenActiveChanged: (isActive: boolean) => void;
     isSelected: (outsideReaction?: boolean) => boolean;
+    rootSelected: () => boolean;
     renderDepth: number;
 }
 export function DocAnnotatableComponent<P extends DocAnnotatableProps, T>(schemaCtor: (doc: Doc) => T) {
@@ -86,7 +88,7 @@ export function DocAnnotatableComponent<P extends DocAnnotatableProps, T>(schema
 
         whenActiveChanged = action((isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive));
         active = (outsideReaction?: boolean) => ((InkingControl.Instance.selectedTool === InkTool.None && !this.props.Document.isBackground) &&
-            (this.props.Document.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false)
+            ((this.props.Document.forceActive && this.props.rootSelected()) || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false)
         annotationsActive = (outsideReaction?: boolean) => (InkingControl.Instance.selectedTool !== InkTool.None ||
             (this.props.Document.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false)
     }
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index ea60907f6..26bee9a6f 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -705,6 +705,7 @@ export default class GestureOverlay extends Touchable {
                 LibraryPath={emptyPath}
                 addDocument={undefined}
                 addDocTab={returnFalse}
+                rootSelected={returnTrue}
                 pinToPres={emptyFunction}
                 onClick={undefined}
                 removeDocument={undefined}
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 8d9be5980..fae520e40 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -309,6 +309,7 @@ export class MainView extends React.Component {
             addDocument={undefined}
             addDocTab={this.addDocTabFunc}
             pinToPres={emptyFunction}
+            rootSelected={returnTrue}
             onClick={undefined}
             backgroundColor={this.defaultBackgroundColors}
             removeDocument={undefined}
@@ -407,6 +408,7 @@ export class MainView extends React.Component {
                     DataDoc={undefined}
                     LibraryPath={emptyPath}
                     addDocument={undefined}
+                    rootSelected={returnTrue}
                     addDocTab={this.addDocTabFunc}
                     pinToPres={emptyFunction}
                     removeDocument={undefined}
@@ -435,6 +437,7 @@ export class MainView extends React.Component {
                     addDocument={undefined}
                     addDocTab={this.addDocTabFunc}
                     pinToPres={emptyFunction}
+                    rootSelected={returnTrue}
                     removeDocument={returnFalse}
                     onClick={undefined}
                     ScreenToLocalTransform={this.mainContainerXf}
@@ -523,6 +526,7 @@ export class MainView extends React.Component {
                     fieldKey={"data"}
                     dropAction={"alias"}
                     annotationsKey={""}
+                    rootSelected={returnTrue}
                     bringToFront={emptyFunction}
                     select={emptyFunction}
                     active={returnFalse}
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index 220efd4a8..7587071db 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -174,6 +174,7 @@ export class OverlayView extends React.Component {
                     Document={d}
                     LibraryPath={emptyPath}
                     ChromeHeight={returnZero}
+                    rootSelected={returnTrue}
                     // isSelected={returnFalse}
                     // select={emptyFunction}
                     // layoutKey={"layout"}
diff --git a/src/client/views/Palette.tsx b/src/client/views/Palette.tsx
index e04f814d1..674b27918 100644
--- a/src/client/views/Palette.tsx
+++ b/src/client/views/Palette.tsx
@@ -1,23 +1,12 @@
+import { IReactionDisposer, observable, reaction } from "mobx";
+import { observer } from "mobx-react";
 import * as React from "react";
-import "./Palette.scss";
-import { PointData } from "../../new_fields/InkField";
 import { Doc } from "../../new_fields/Doc";
-import { Docs } from "../documents/Documents";
-import { ScriptField, ComputedField } from "../../new_fields/ScriptField";
-import { List } from "../../new_fields/List";
-import { DocumentView } from "./nodes/DocumentView";
-import { emptyPath, returnFalse, emptyFunction, returnOne, returnEmptyString, returnTrue } from "../../Utils";
-import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils";
+import { NumCast } from "../../new_fields/Types";
+import { emptyFunction, emptyPath, returnEmptyString, returnFalse, returnOne, returnTrue } from "../../Utils";
 import { Transform } from "../util/Transform";
-import { computed, action, IReactionDisposer, reaction, observable } from "mobx";
-import { FieldValue, Cast, NumCast } from "../../new_fields/Types";
-import { observer } from "mobx-react";
-import { DocumentContentsView } from "./nodes/DocumentContentsView";
-import { CollectionStackingView } from "./collections/CollectionStackingView";
-import { CollectionView } from "./collections/CollectionView";
-import { CollectionSubView, SubCollectionViewProps } from "./collections/CollectionSubView";
-import { makeInterface } from "../../new_fields/Schema";
-import { documentSchema } from "../../new_fields/documentSchemas";
+import { DocumentView } from "./nodes/DocumentView";
+import "./Palette.scss";
 
 export interface PaletteProps {
     x: number;
@@ -40,7 +29,7 @@ export default class Palette extends React.Component<PaletteProps> {
     }
 
     componentWillUnmount = () => {
-        this._selectedDisposer && this._selectedDisposer();
+        this._selectedDisposer?.();
     }
 
     render() {
@@ -54,6 +43,7 @@ export default class Palette extends React.Component<PaletteProps> {
                             LibraryPath={emptyPath}
                             addDocument={undefined}
                             addDocTab={returnFalse}
+                            rootSelected={returnTrue}
                             pinToPres={emptyFunction}
                             removeDocument={undefined}
                             onClick={undefined}
diff --git a/src/client/views/RecommendationsBox.tsx b/src/client/views/RecommendationsBox.tsx
index 5ebba0abb..bf8de36b4 100644
--- a/src/client/views/RecommendationsBox.tsx
+++ b/src/client/views/RecommendationsBox.tsx
@@ -68,6 +68,7 @@ export class RecommendationsBox extends React.Component<FieldViewProps> {
                 addDocument={returnFalse}
                 LibraryPath={emptyPath}
                 removeDocument={returnFalse}
+                rootSelected={returnFalse}
                 ScreenToLocalTransform={Transform.Identity}
                 addDocTab={returnFalse}
                 pinToPres={returnFalse}
diff --git a/src/client/views/SearchDocBox.tsx b/src/client/views/SearchDocBox.tsx
index c57f9e737..e66de39d4 100644
--- a/src/client/views/SearchDocBox.tsx
+++ b/src/client/views/SearchDocBox.tsx
@@ -410,8 +410,9 @@ export class SearchDocBox extends React.Component<FieldViewProps> {
             <div style={{ pointerEvents: "all" }}>
                 <ContentFittingDocumentView {...this.props}
                     Document={this.content}
+                    rootSelected={returnFalse}
                     getTransform={this.props.ScreenToLocalTransform}>
-                    </ContentFittingDocumentView>
+                </ContentFittingDocumentView>
                 <div
                     style={{
                         position: "absolute",
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 4e1e76f39..4209bd574 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -788,6 +788,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
             Document={document}
             DataDoc={resolvedDataDoc}
             bringToFront={emptyFunction}
+            rootSelected={returnTrue}
             addDocument={undefined}
             removeDocument={undefined}
             ContentScaling={this.contentScaling}
diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx
index 344605412..a6ada75b2 100644
--- a/src/client/views/collections/CollectionLinearView.tsx
+++ b/src/client/views/collections/CollectionLinearView.tsx
@@ -4,7 +4,7 @@ import * as React from 'react';
 import { Doc, HeightSym, WidthSym } from '../../../new_fields/Doc';
 import { makeInterface } from '../../../new_fields/Schema';
 import { BoolCast, NumCast, StrCast, Cast } from '../../../new_fields/Types';
-import { emptyFunction, returnEmptyString, returnOne, returnTrue, Utils } from '../../../Utils';
+import { emptyFunction, returnEmptyString, returnOne, returnTrue, Utils, returnFalse } from '../../../Utils';
 import { DragManager } from '../../util/DragManager';
 import { Transform } from '../../util/Transform';
 import "./CollectionLinearView.scss";
@@ -110,6 +110,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
                                 moveDocument={this.props.moveDocument}
                                 addDocTab={this.props.addDocTab}
                                 pinToPres={emptyFunction}
+                                rootSelected={this.props.isSelected}
                                 removeDocument={this.props.removeDocument}
                                 onClick={undefined}
                                 ScreenToLocalTransform={this.getTransform(dref)}
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 981438513..a1b541f74 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -124,6 +124,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
                 LibraryPath={this.props.LibraryPath}
                 childDocs={this.childDocs}
                 renderDepth={this.props.renderDepth}
+                rootSelected={this.rootSelected}
                 PanelWidth={this.previewWidth}
                 PanelHeight={this.previewHeight}
                 getTransform={this.getPreviewTransform}
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 076dd3629..d21e17bbc 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -176,6 +176,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
             LibraryPath={this.props.LibraryPath}
             renderDepth={this.props.renderDepth + 1}
             fitToBox={this.props.fitToBox}
+            rootSelected={this.rootSelected}
             dropAction={StrCast(this.props.Document.childDropAction) as dropActionType}
             onClick={layoutDoc.isTemplateDoc ? this.onClickHandler : this.onChildClickHandler}
             PanelWidth={width}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 70927cf22..d1d6ae3c1 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -34,6 +34,7 @@ export interface CollectionViewProps extends FieldViewProps {
     PanelHeight: () => number;
     VisibleHeight?: () => number;
     setPreviewCursor?: (func: (x: number, y: number, drag: boolean) => void) => void;
+    rootSelected: () => boolean;
     fieldKey: string;
 }
 
@@ -95,6 +96,10 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
                 this.props.Document.resolvedDataDoc ? this.props.Document : Doc.GetProto(this.props.Document)); // if the layout document has a resolvedDataDoc, then we don't want to get its parent which would be the unexpanded template
         }
 
+        rootSelected = () => {
+            return this.props.isSelected() || (this.props.Document.rootDocument || this.props.Document.forceActive ? this.props.rootSelected() : false);
+        }
+
         // The data field for rendering this collection will be on the this.props.Document unless we're rendering a template in which case we try to use props.DataDoc.
         // When a document has a DataDoc but it's not a template, then it contains its own rendering data, but needs to pass the DataDoc through
         // to its children which may be templates.
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 6ee48f11b..fc612c66d 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -373,6 +373,7 @@ class TreeView extends React.Component<TreeViewProps> {
                     DataDocument={this.templateDataDoc}
                     LibraryPath={emptyPath}
                     renderDepth={this.props.renderDepth + 1}
+                    rootSelected={returnTrue}
                     backgroundColor={this.props.backgroundColor}
                     fitToBox={this.boundsOfCollectionDocument !== undefined}
                     PanelWidth={this.docWidth}
@@ -454,6 +455,7 @@ class TreeView extends React.Component<TreeViewProps> {
                         LibraryPath={this.props.libraryPath || []}
                         addDocument={undefined}
                         addDocTab={this.props.addDocTab}
+                        rootSelected={returnTrue}
                         pinToPres={emptyFunction}
                         onClick={this.props.onChildClick || editTitle}
                         dropAction={this.props.dropAction}
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index df1770ffe..3913ccd88 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -117,7 +117,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
         return viewField;
     }
 
-    active = (outsideReaction?: boolean) => this.props.isSelected(outsideReaction) || BoolCast(this.props.Document.forceActive) || this._isChildActive || this.props.renderDepth === 0;
+    active = (outsideReaction?: boolean) => this.props.isSelected(outsideReaction) || (this.props.rootSelected() && BoolCast(this.props.Document.forceActive)) || this._isChildActive || this.props.renderDepth === 0;
 
     whenActiveChanged = (isActive: boolean) => { this.props.whenActiveChanged(this._isChildActive = isActive); };
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 2871fe192..a164e1794 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -832,6 +832,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             Document: childLayout,
             LibraryPath: this.libraryPath,
             layoutKey: undefined,
+            rootSelected: this.rootSelected,
             dropAction: StrCast(this.props.Document.childDropAction) as dropActionType,
             //onClick: undefined, // this.props.onClick,  // bcz: check this out -- I don't think we want to inherit click handlers, or we at least need a way to ignore them
             onClick: this.onChildClickHandler,
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 276a49570..503df10c2 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -5,7 +5,6 @@ import { InkField, InkData } from "../../../../new_fields/InkField";
 import { List } from "../../../../new_fields/List";
 import { SchemaHeaderField } from "../../../../new_fields/SchemaHeaderField";
 import { Cast, NumCast, FieldValue, StrCast } from "../../../../new_fields/Types";
-import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils";
 import { Utils } from "../../../../Utils";
 import { Docs, DocUtils } from "../../../documents/Documents";
 import { SelectionManager } from "../../../util/SelectionManager";
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 03b2a2297..bd54d64ff 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -7,7 +7,7 @@ import { AudioField, nullAudio } from "../../../new_fields/URLField";
 import { DocExtendableComponent } from "../DocComponent";
 import { makeInterface, createSchema } from "../../../new_fields/Schema";
 import { documentSchema } from "../../../new_fields/documentSchemas";
-import { Utils, returnTrue, emptyFunction, returnOne, returnTransparent } from "../../../Utils";
+import { Utils, returnTrue, emptyFunction, returnOne, returnTransparent, returnFalse } from "../../../Utils";
 import { runInAction, observable, reaction, IReactionDisposer, computed, action } from "mobx";
 import { DateField } from "../../../new_fields/DateField";
 import { SelectionManager } from "../../util/SelectionManager";
@@ -258,9 +258,15 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume
                                 return !linkTime ? (null) :
                                     <div className={this.props.PanelHeight() < 32 ? "audiobox-marker-minicontainer" : "audiobox-marker-container"} key={l[Id]} style={{ left: `${linkTime / NumCast(this.dataDoc.duration, 1) * 100}%` }}>
                                         <div className={this.props.PanelHeight() < 32 ? "audioBox-linker-mini" : "audioBox-linker"} key={"linker" + i}>
-                                            <DocumentView {...this.props} Document={l} layoutKey={Doc.LinkEndpoint(l, la2)}
+                                            <DocumentView {...this.props}
+                                                Document={l}
+                                                rootSelected={returnFalse}
+                                                layoutKey={Doc.LinkEndpoint(l, la2)}
                                                 ContainingCollectionDoc={this.props.Document}
-                                                parentActive={returnTrue} bringToFront={emptyFunction} zoomToScale={emptyFunction} getScale={returnOne}
+                                                parentActive={returnTrue}
+                                                bringToFront={emptyFunction}
+                                                zoomToScale={emptyFunction}
+                                                getScale={returnOne}
                                                 backgroundColor={returnTransparent} />
                                         </div>
                                         <div key={i} className="audiobox-marker" onPointerEnter={() => Doc.linkFollowHighlight(la1)}
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index eaab4086c..356192797 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -89,14 +89,15 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                 pointerEvents: this.props.Document.isBackground ? "none" : undefined
             }} >
 
-            {!this.props.fitToBox ? <DocumentView {...this.props}
+            {!this.props.fitToBox ? 
+            <DocumentView {...this.props}
                 dragDivName={"collectionFreeFormDocumentView-container"}
                 ContentScaling={this.contentScaling}
                 ScreenToLocalTransform={this.getTransform}
                 backgroundColor={this.props.backgroundColor}
                 PanelWidth={this.panelWidth}
-                PanelHeight={this.panelHeight}
-            /> : <ContentFittingDocumentView {...this.props}
+                PanelHeight={this.panelHeight} /> 
+            : <ContentFittingDocumentView {...this.props}
                 CollectionDoc={this.props.ContainingCollectionDoc}
                 DataDocument={this.props.DataDoc}
                 getTransform={this.getTransform}
diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx
index 9494a4bc4..fdf2a9551 100644
--- a/src/client/views/nodes/ContentFittingDocumentView.tsx
+++ b/src/client/views/nodes/ContentFittingDocumentView.tsx
@@ -40,6 +40,7 @@ interface ContentFittingDocumentViewProps {
     addDocTab: (document: Doc, where: string) => boolean;
     pinToPres: (document: Doc) => void;
     dontRegisterView?: boolean;
+    rootSelected: () => boolean;
 }
 
 @observer
diff --git a/src/client/views/nodes/DocumentBox.tsx b/src/client/views/nodes/DocumentBox.tsx
index debe104d7..47118e758 100644
--- a/src/client/views/nodes/DocumentBox.tsx
+++ b/src/client/views/nodes/DocumentBox.tsx
@@ -122,6 +122,7 @@ export class DocumentBox extends DocAnnotatableComponent<FieldViewProps, DocBoxS
             CollectionView={this as any} // bcz: hack!  need to pass a prop that can be used to select the container (ie, 'this') when the up selector in document decorations is clicked.  currently, the up selector allows only a containing collection to be selected
             fitToBox={this.props.fitToBox}
             layoutKey={"layout_" + childTemplateName}
+            rootSelected={this.props.isSelected}
             addDocument={this.props.addDocument}
             moveDocument={this.props.moveDocument}
             removeDocument={this.props.removeDocument}
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index fc61487e5..687137adc 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -1,6 +1,6 @@
 import { computed } from "mobx";
 import { observer } from "mobx-react";
-import { Doc } from "../../../new_fields/Doc";
+import { Doc, Opt } from "../../../new_fields/Doc";
 import { Cast, StrCast } from "../../../new_fields/Types";
 import { OmitKeys, Without } from "../../../Utils";
 import { HistogramBox } from "../../northstar/dash-nodes/HistogramBox";
@@ -62,7 +62,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
     forceLayout?: string,
     forceFieldKey?: string,
     hideOnLeave?: boolean,
-    makeLink?: () => Opt<Doc>;
+    makeLink?: () => Opt<Doc>,  // function to call when a link is made
 }> {
     @computed get layout(): string {
         TraceMobx();
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 1e02ab44e..42f5fb946 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -60,6 +60,7 @@ export interface DocumentViewProps {
     LayoutDoc?: () => Opt<Doc>;
     LibraryPath: Doc[];
     fitToBox?: boolean;
+    rootSelected: () => boolean; // whether the root of a template has been selected
     onClick?: ScriptField;
     onPointerDown?: ScriptField;
     onPointerUp?: ScriptField;
@@ -277,7 +278,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         this.dontDecorateSelection = this.props.Document.dontDecorateSelection && (!e.ctrlKey || e.button < 2);
         if (!e.nativeEvent.cancelBubble && !this.Document.ignoreClick &&
             (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) {
-            e.stopPropagation();
+            let stopPropagate = true;
             let preventDefault = true;
             this.props.bringToFront(this.props.Document);
             if (this._doubleTap && this.props.renderDepth && !this.onClickHandler?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click
@@ -301,9 +302,14 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 SelectionManager.SelectDoc(this, e.ctrlKey); // don't think this should happen if a button action is actually triggered.
                 UndoManager.RunInBatch(() => this.buttonClick(e.altKey, e.ctrlKey), "on link button follow");
             } else {
-                SelectionManager.SelectDoc(this, e.ctrlKey);
+                if (this.props.Document.isTemplateForField && !(e.ctrlKey || e.button > 0)) {
+                    stopPropagate = false;
+                } else {
+                    SelectionManager.SelectDoc(this, e.ctrlKey);
+                }
                 preventDefault = false;
             }
+            stopPropagate && e.stopPropagation();
             preventDefault && e.preventDefault();
         }
     }
@@ -928,6 +934,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         const fallback = Cast(this.props.Document.layoutKey, "string");
         return typeof fallback === "string" ? fallback : "layout";
     }
+    rootSelected = () => {
+        return this.isSelected(false) || (this.props.Document.forceActive && this.props.rootSelected?.() ? true : false);
+    }
     childScaling = () => (this.layoutDoc._fitWidth ? this.props.PanelWidth() / this.nativeWidth : this.props.ContentScaling());
     @computed get contents() {
         TraceMobx();
@@ -937,6 +946,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             DataDoc={this.props.DataDoc}
             LayoutDoc={this.props.LayoutDoc}
             makeLink={this.makeLink}
+            rootSelected={this.rootSelected}
             fitToBox={this.props.fitToBox}
             LibraryPath={this.props.LibraryPath}
             addDocument={this.props.addDocument}
@@ -985,9 +995,13 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 {StrCast(this.props.Document.title)}
                 {this.Document.links && DocListCast(this.Document.links).filter(d => !d.hidden).filter(this.isNonTemporalLink).map((d, i) =>
                     <div className="documentView-docuLinkWrapper" style={{ position: "absolute", top: 0, left: 0 }} key={`${d[Id]}`}>
-                        <DocumentView {...this.props} Document={d} ContainingCollectionDoc={this.props.Document}
-                            PanelWidth={returnOne} PanelHeight={returnOne} layoutKey={this.linkEndpoint(d)} ContentScaling={returnOne}
-                            backgroundColor={returnTransparent} removeDocument={undoBatch(doc => doc.hidden = true)} />
+                        <DocumentView {...this.props}
+                            Document={d}
+                            ContainingCollectionDoc={this.props.Document}
+                            PanelWidth={returnOne} PanelHeight={returnOne}
+                            layoutKey={this.linkEndpoint(d)} ContentScaling={returnOne}
+                            backgroundColor={returnTransparent}
+                            removeDocument={undoBatch(doc => doc.hidden = true)} />
                     </div>)}
             </div>;
         }
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 0305f43d5..13a1becf7 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -29,6 +29,7 @@ export interface FieldViewProps {
     dropAction: dropActionType;
     isSelected: (outsideReaction?: boolean) => boolean;
     select: (isCtrlPressed: boolean) => void;
+    rootSelected: () => boolean;
     renderDepth: number;
     addDocument?: (document: Doc) => boolean;
     addDocTab: (document: Doc, where: string) => boolean;
diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx
index 61df188f8..d1a563494 100644
--- a/src/client/views/nodes/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/FormattedTextBoxComment.tsx
@@ -181,6 +181,7 @@ export class FormattedTextBoxComment {
                                     LibraryPath={emptyPath}
                                     fitToBox={true}
                                     moveDocument={returnFalse}
+                                    rootSelected={returnFalse}
                                     getTransform={Transform.Identity}
                                     active={returnFalse}
                                     addDocument={returnFalse}
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 71495d95f..c032f019d 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -31,8 +31,6 @@ import { InkingControl } from "../InkingControl";
 import { InkTool } from "../../../new_fields/InkField";
 import { TraceMobx } from "../../../new_fields/util";
 import { PdfField } from "../../../new_fields/URLField";
-import { PDFBox } from "../nodes/PDFBox";
-import { FormattedTextBox } from "../nodes/FormattedTextBox";
 import { DocumentView } from "../nodes/DocumentView";
 const PDFJSViewer = require("pdfjs-dist/web/pdf_viewer");
 const pdfjsLib = require("pdfjs-dist");
@@ -61,6 +59,7 @@ interface IViewerProps {
     PanelHeight: () => number;
     ContentScaling: () => number;
     select: (isCtrlPressed: boolean) => void;
+    rootSelected: () => boolean;
     startupLive: boolean;
     renderDepth: number;
     focus: (doc: Doc) => void;
diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx
index 758795ed5..7ad3474e8 100644
--- a/src/client/views/presentationview/PresElementBox.tsx
+++ b/src/client/views/presentationview/PresElementBox.tsx
@@ -9,7 +9,7 @@ import { documentSchema } from '../../../new_fields/documentSchemas';
 import { Id } from "../../../new_fields/FieldSymbols";
 import { createSchema, makeInterface } from '../../../new_fields/Schema';
 import { Cast, NumCast } from "../../../new_fields/Types";
-import { emptyFunction, emptyPath, returnFalse } from "../../../Utils";
+import { emptyFunction, emptyPath, returnFalse, returnTrue } from "../../../Utils";
 import { Transform } from "../../util/Transform";
 import { CollectionViewType } from '../collections/CollectionView';
 import { DocExtendableComponent } from '../DocComponent';
@@ -175,6 +175,7 @@ export class PresElementBox extends DocExtendableComponent<FieldViewProps, PresD
                     DataDocument={this.targetDoc[DataSym] !== this.targetDoc && this.targetDoc[DataSym]}
                     LibraryPath={emptyPath}
                     fitToBox={true}
+                    rootSelected={returnTrue}
                     addDocument={returnFalse}
                     removeDocument={returnFalse}
                     addDocTab={returnFalse}
diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx
index 63cef5101..0d77026ad 100644
--- a/src/client/views/search/SearchItem.tsx
+++ b/src/client/views/search/SearchItem.tsx
@@ -7,7 +7,7 @@ import { observer } from "mobx-react";
 import { Doc } from "../../../new_fields/Doc";
 import { Id } from "../../../new_fields/FieldSymbols";
 import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
-import { emptyFunction, emptyPath, returnFalse, Utils } from "../../../Utils";
+import { emptyFunction, emptyPath, returnFalse, Utils, returnTrue } from "../../../Utils";
 import { DocumentType } from "../../documents/DocumentTypes";
 import { DocumentManager } from "../../util/DocumentManager";
 import { DragManager, SetupDrag } from "../../util/DragManager";
@@ -158,6 +158,7 @@ export class SearchItem extends React.Component<SearchItemProps> {
                 <ContentFittingDocumentView
                     Document={this.props.doc}
                     LibraryPath={emptyPath}
+                    rootSelected={returnFalse}
                     fitToBox={StrCast(this.props.doc.type).indexOf(DocumentType.COL) !== -1}
                     addDocument={returnFalse}
                     removeDocument={returnFalse}
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index 1d2d57b96..98a6d384e 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -124,6 +124,7 @@ export default class MobileInterface extends React.Component {
                 addDocument={returnFalse}
                 addDocTab={returnFalse}
                 pinToPres={emptyFunction}
+                rootSelected={returnFalse}
                 removeDocument={undefined}
                 onClick={undefined}
                 ScreenToLocalTransform={Transform.Identity}
@@ -285,6 +286,7 @@ export default class MobileInterface extends React.Component {
                         addDocument={returnFalse}
                         addDocTab={returnFalse}
                         pinToPres={emptyFunction}
+                        rootSelected={returnFalse}
                         removeDocument={undefined}
                         onClick={undefined}
                         ScreenToLocalTransform={Transform.Identity}
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 9a90ce17f..7fe623f18 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -213,7 +213,7 @@ export class CurrentUserUtils {
             _width: 35, _height: 25, title: "Tools", fontSize: 10, targetContainer: sidebarContainer, dontDecorateSelection: true,
             letterSpacing: "0px", textTransform: "unset", borderRounding: "5px 5px 0px 0px", boxShadow: "3px 3px 0px rgb(34, 34, 34)",
             sourcePanel: Docs.Create.StackingDocument([dragCreators, color], {
-                _width: 500, lockedPosition: true, _chromeStatus: "disabled", title: "tools stack"
+                _width: 500, lockedPosition: true, _chromeStatus: "disabled", title: "tools stack", forceActive: true
             }),
             onClick: ScriptField.MakeScript("this.targetContainer.proto = this.sourcePanel"),
         });
-- 
cgit v1.2.3-70-g09d2


From 5338dfd24d0f6a65bb3131332746c1d2c82ef7f2 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 4 Apr 2020 15:23:42 -0400
Subject: fix to presentationView opacity. removed getScale and zoomToScale
 props which weren't being used.

---
 src/client/util/DocumentManager.ts                 | 17 --------
 src/client/util/RichTextSchema.tsx                 |  2 -
 src/client/views/GestureOverlay.tsx                |  2 -
 src/client/views/MainView.tsx                      | 12 +-----
 src/client/views/OverlayView.tsx                   |  4 +-
 src/client/views/Palette.tsx                       |  5 +--
 src/client/views/RecommendationsBox.tsx            |  2 -
 .../views/collections/CollectionDockingView.tsx    |  4 +-
 .../views/collections/CollectionLinearView.tsx     |  5 +--
 .../views/collections/CollectionTreeView.tsx       |  2 -
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  8 ----
 src/client/views/nodes/AudioBox.tsx                |  2 -
 .../views/nodes/ContentFittingDocumentView.tsx     |  2 -
 src/client/views/nodes/DocumentView.tsx            |  4 --
 src/client/views/nodes/PresBox.tsx                 | 50 +++++-----------------
 src/mobile/MobileInterface.tsx                     | 10 +----
 16 files changed, 19 insertions(+), 112 deletions(-)

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

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index e0ffaf7e0..63d864db5 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -219,22 +219,5 @@ export class DocumentManager {
             DocumentManager.Instance.jumpToDocument(link, zoom, (doc: Doc) => focus(doc, "onRight"), undefined, undefined);
         }
     }
-
-    @action
-    zoomIntoScale = (docDelegate: Doc, scale: number) => {
-        const docView = DocumentManager.Instance.getDocumentView(Doc.GetProto(docDelegate));
-        docView?.props.zoomToScale(scale);
-    }
-
-    getScaleOfDocView = (docDelegate: Doc) => {
-        const doc = Doc.GetProto(docDelegate);
-
-        const docView = DocumentManager.Instance.getDocumentView(doc);
-        if (docView) {
-            return docView.props.getScale();
-        } else {
-            return 1;
-        }
-    }
 }
 Scripting.addGlobal(function focus(doc: any) { DocumentManager.Instance.getDocumentViews(Doc.GetProto(doc)).map(view => view.props.focus(doc, true)); });
\ No newline at end of file
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 4a930177d..d4568b17f 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -831,8 +831,6 @@ export class DashDocView {
                     parentActive={returnFalse}
                     whenActiveChanged={returnFalse}
                     bringToFront={emptyFunction}
-                    zoomToScale={emptyFunction}
-                    getScale={returnOne}
                     dontRegisterView={false}
                     ContainingCollectionView={this._textBox.props.ContainingCollectionView}
                     ContainingCollectionDoc={this._textBox.props.ContainingCollectionDoc}
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 26bee9a6f..68f1d13fb 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -721,8 +721,6 @@ export default class GestureOverlay extends Touchable {
                 bringToFront={emptyFunction}
                 ContainingCollectionView={undefined}
                 ContainingCollectionDoc={undefined}
-                zoomToScale={emptyFunction}
-                getScale={returnOne}
             />;
     }
 
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 558bc1a4a..420cc95ec 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -316,8 +316,6 @@ export class MainView extends React.Component {
             bringToFront={emptyFunction}
             ContainingCollectionView={undefined}
             ContainingCollectionDoc={undefined}
-            zoomToScale={emptyFunction}
-            getScale={returnOne}
         />;
     }
     @computed get dockingContent() {
@@ -416,10 +414,7 @@ export class MainView extends React.Component {
                     whenActiveChanged={emptyFunction}
                     bringToFront={emptyFunction}
                     ContainingCollectionView={undefined}
-                    ContainingCollectionDoc={undefined}
-                    zoomToScale={emptyFunction}
-                    getScale={returnOne}>
-                </DocumentView>
+                    ContainingCollectionDoc={undefined} />
             </div>
             <div className="mainView-contentArea" style={{ position: "relative", height: `calc(100% - ${this._buttonBarHeight}px)`, width: "100%", overflow: "visible" }}>
                 <DocumentView
@@ -443,10 +438,7 @@ export class MainView extends React.Component {
                     whenActiveChanged={emptyFunction}
                     bringToFront={emptyFunction}
                     ContainingCollectionView={undefined}
-                    ContainingCollectionDoc={undefined}
-                    zoomToScale={emptyFunction}
-                    getScale={returnOne}>
-                </DocumentView>
+                    ContainingCollectionDoc={undefined} />
                 <button className="mainView-settings" key="settings" onClick={() => SettingsManager.Instance.open()}>
                     Settings
                 </button>
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index 7587071db..7cfae0bbb 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -193,9 +193,7 @@ export class OverlayView extends React.Component {
                     addDocTab={returnFalse}
                     pinToPres={emptyFunction}
                     ContainingCollectionView={undefined}
-                    ContainingCollectionDoc={undefined}
-                    zoomToScale={emptyFunction}
-                    getScale={returnOne} />
+                    ContainingCollectionDoc={undefined}/>
             </div>;
         });
     }
diff --git a/src/client/views/Palette.tsx b/src/client/views/Palette.tsx
index 674b27918..d088d5bba 100644
--- a/src/client/views/Palette.tsx
+++ b/src/client/views/Palette.tsx
@@ -58,10 +58,7 @@ export default class Palette extends React.Component<PaletteProps> {
                             whenActiveChanged={emptyFunction}
                             bringToFront={emptyFunction}
                             ContainingCollectionView={undefined}
-                            ContainingCollectionDoc={undefined}
-                            zoomToScale={emptyFunction}
-                            getScale={returnOne}>
-                        </DocumentView>
+                            ContainingCollectionDoc={undefined} />
                         <div className="palette-cover" style={{ transform: `translate(${Math.max(0, this._selectedIndex) * 50.75 + 23}px, 0px)` }}></div>
                     </div>
                 </div>
diff --git a/src/client/views/RecommendationsBox.tsx b/src/client/views/RecommendationsBox.tsx
index bf8de36b4..ddeda28ac 100644
--- a/src/client/views/RecommendationsBox.tsx
+++ b/src/client/views/RecommendationsBox.tsx
@@ -80,8 +80,6 @@ export class RecommendationsBox extends React.Component<FieldViewProps> {
                 parentActive={returnFalse}
                 whenActiveChanged={returnFalse}
                 bringToFront={emptyFunction}
-                zoomToScale={emptyFunction}
-                getScale={returnOne}
                 ContainingCollectionView={undefined}
                 ContainingCollectionDoc={undefined}
                 ContentScaling={scale}
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 4209bd574..11eecf771 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -803,9 +803,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
             addDocTab={this.addDocTab}
             pinToPres={DockedFrameRenderer.PinDoc}
             ContainingCollectionView={undefined}
-            ContainingCollectionDoc={undefined}
-            zoomToScale={emptyFunction}
-            getScale={returnOne} />;
+            ContainingCollectionDoc={undefined} />;
     }
 
     render() {
diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx
index 728b3066d..3bd50ad52 100644
--- a/src/client/views/collections/CollectionLinearView.tsx
+++ b/src/client/views/collections/CollectionLinearView.tsx
@@ -123,10 +123,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
                                 whenActiveChanged={emptyFunction}
                                 bringToFront={emptyFunction}
                                 ContainingCollectionView={undefined}
-                                ContainingCollectionDoc={undefined}
-                                zoomToScale={emptyFunction}
-                                getScale={returnOne}>
-                            </DocumentView>
+                                ContainingCollectionDoc={undefined}/>
                         </div>;
                     })}
                 </div>
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index fc612c66d..77dd9a162 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -473,8 +473,6 @@ class TreeView extends React.Component<TreeViewProps> {
                         dontRegisterView={BoolCast(this.props.treeViewId.dontRegisterChildren)}
                         ContainingCollectionView={undefined}
                         ContainingCollectionDoc={undefined}
-                        zoomToScale={emptyFunction}
-                        getScale={returnOne}
                     />}
             </div >
             {this.props.treeViewHideHeaderFields() ? (null) : headerElements}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index a164e1794..376d217bb 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -815,12 +815,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         this.Document.scale = scale * Math.min(this.props.PanelWidth() / NumCast(doc._width), this.props.PanelHeight() / NumCast(doc._height));
     }
 
-    zoomToScale = (scale: number) => {
-        this.Document.scale = scale;
-    }
-
-    getScale = () => this.Document.scale || 1;
-
     @computed get libraryPath() { return this.props.LibraryPath ? [...this.props.LibraryPath, this.props.Document] : []; }
     @computed get onChildClickHandler() { return ScriptCast(this.Document.onChildClick); }
     backgroundHalo = () => BoolCast(this.Document.useClusters);
@@ -848,8 +842,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             backgroundHalo: this.backgroundHalo,
             parentActive: this.props.active,
             bringToFront: this.bringToFront,
-            zoomToScale: this.zoomToScale,
-            getScale: this.getScale
         };
     }
 
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index bd54d64ff..627a71d67 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -265,8 +265,6 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume
                                                 ContainingCollectionDoc={this.props.Document}
                                                 parentActive={returnTrue}
                                                 bringToFront={emptyFunction}
-                                                zoomToScale={emptyFunction}
-                                                getScale={returnOne}
                                                 backgroundColor={returnTransparent} />
                                         </div>
                                         <div key={i} className="audiobox-marker" onPointerEnter={() => Doc.linkFollowHighlight(la1)}
diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx
index fdf2a9551..9ab6826a3 100644
--- a/src/client/views/nodes/ContentFittingDocumentView.tsx
+++ b/src/client/views/nodes/ContentFittingDocumentView.tsx
@@ -111,8 +111,6 @@ export class ContentFittingDocumentView extends React.Component<ContentFittingDo
                         focus={this.props.focus || emptyFunction}
                         bringToFront={emptyFunction}
                         dontRegisterView={this.props.dontRegisterView}
-                        zoomToScale={emptyFunction}
-                        getScale={returnOne}
                     />
                 </div>)}
         </div>);
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index d12530142..11bf5a6a7 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -80,10 +80,8 @@ export interface DocumentViewProps {
     bringToFront: (doc: Doc, sendToBack?: boolean) => void;
     addDocTab: (doc: Doc, where: string, libraryPath?: Doc[]) => boolean;
     pinToPres: (document: Doc) => void;
-    zoomToScale: (scale: number) => void;
     backgroundHalo?: () => boolean;
     backgroundColor?: (doc: Doc) => string | undefined;
-    getScale: () => number;
     ChromeHeight?: () => number;
     dontRegisterView?: boolean;
     layoutKey?: string;
@@ -959,9 +957,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             bringToFront={this.props.bringToFront}
             addDocTab={this.props.addDocTab}
             pinToPres={this.props.pinToPres}
-            zoomToScale={this.props.zoomToScale}
             backgroundColor={this.props.backgroundColor}
-            getScale={this.props.getScale}
             ContentScaling={this.childScaling}
             ChromeHeight={this.chromeHeight}
             isSelected={this.isSelected}
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index a39c337ca..73d09b4e1 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -49,14 +49,15 @@ export class PresBox extends React.Component<FieldViewProps> {
         this.updateCurrentPresentation();
         if (this.childDocs[this.currentIndex + 1] !== undefined) {
             let nextSelected = this.currentIndex + 1;
+            this.gotoDocument(nextSelected, this.currentIndex);
 
-            for (; nextSelected < this.childDocs.length - 1; nextSelected++) {
-                if (!this.childDocs[nextSelected + 1].groupButton) {
+            for (nextSelected = nextSelected + 1; nextSelected < this.childDocs.length; nextSelected++) {
+                if (!this.childDocs[nextSelected].groupButton) {
                     break;
+                } else {
+                    this.gotoDocument(nextSelected, this.currentIndex);
                 }
             }
-
-            this.gotoDocument(nextSelected, this.currentIndex);
         }
     }
     back = () => {
@@ -72,13 +73,6 @@ export class PresBox extends React.Component<FieldViewProps> {
             }
             prevSelected = Math.max(0, prevSelected - 1);
 
-            if (this.currentIndex > 0 && didZoom) {
-                const prevScale = NumCast(this.childDocs[prevSelected].viewScale);
-                const curScale = DocumentManager.Instance.getScaleOfDocView(docAtCurrent);
-                if (prevScale && prevScale !== curScale) {
-                    DocumentManager.Instance.zoomIntoScale(docAtCurrent, prevScale);
-                }
-            }
             this.gotoDocument(prevSelected, this.currentIndex);
         }
     }
@@ -171,31 +165,15 @@ export class PresBox extends React.Component<FieldViewProps> {
             if (curDoc.navButton && target) {
                 DocumentManager.Instance.jumpToDocument(target, false, undefined, srcContext);
             } else if (curDoc.zoomButton && target) {
-                const curScale = DocumentManager.Instance.getScaleOfDocView(fromDoc);
                 //awaiting jump so that new scale can be found, since jumping is async
                 await DocumentManager.Instance.jumpToDocument(target, true, undefined, srcContext);
-                curDoc.viewScale = DocumentManager.Instance.getScaleOfDocView(target);
-
-                //saving the scale user was on before zooming in
-                if (curScale !== 1) {
-                    fromDoc.viewScale = curScale;
-                }
-
             }
             return;
         }
-        const curScale = DocumentManager.Instance.getScaleOfDocView(fromDoc);
 
         //awaiting jump so that new scale can be found, since jumping is async
         const presTargetDoc = await docToJump.presentationTargetDoc as Doc;
         await DocumentManager.Instance.jumpToDocument(presTargetDoc, willZoom, undefined, srcContext);
-        const newScale = DocumentManager.Instance.getScaleOfDocView(await curDoc.presentationTargetDoc as Doc);
-        curDoc.viewScale = newScale;
-        //saving the scale that user was on
-        if (curScale !== 1) {
-            fromDoc.viewScale = curScale;
-        }
-
     }
 
 
@@ -246,10 +224,9 @@ export class PresBox extends React.Component<FieldViewProps> {
     //stops the presentaton.
     resetPresentation = () => {
         this.updateCurrentPresentation();
-        this.childDocs.forEach(doc => doc.opacity = doc.viewScale = 1);
+        this.childDocs.forEach(doc => (doc.presentationTargetDoc as Doc).opacity = 1);
         this.props.Document._itemIndex = 0;
         this.props.Document.presStatus = false;
-        this.childDocs.length && DocumentManager.Instance.zoomIntoScale(this.childDocs[0], 1);
     }
 
     //The function that starts the presentation, also checking if actions should be applied
@@ -258,13 +235,13 @@ export class PresBox extends React.Component<FieldViewProps> {
         this.updateCurrentPresentation();
         this.childDocs.map(doc => {
             if (doc.hideTillShownButton && this.childDocs.indexOf(doc) > startIndex) {
-                doc.opacity = 0;
+                (doc.presentationTargetDoc as Doc).opacity = 0;
             }
             if (doc.hideAfterButton && this.childDocs.indexOf(doc) < startIndex) {
-                doc.opacity = 0;
+                (doc.presentationTargetDoc as Doc).opacity = 0;
             }
             if (doc.fadeButton && this.childDocs.indexOf(doc) < startIndex) {
-                doc.opacity = 0.5;
+                (doc.presentationTargetDoc as Doc).opacity = 0.5;
             }
         });
     }
@@ -284,16 +261,11 @@ export class PresBox extends React.Component<FieldViewProps> {
         }
     }));
 
-    /**
-     * Initially every document starts with a viewScale 1, which means
-     * that they will be displayed in a canvas with scale 1.
-     */
-    initializeScaleViews = (docList: Doc[], viewtype: number) => {
+    initializeViewAliases = (docList: Doc[], viewtype: number) => {
         const hgt = (viewtype === CollectionViewType.Tree) ? 50 : 46;
         docList.forEach(doc => {
             doc.presBox = this.props.Document; // give contained documents a reference to the presentation
             doc.collapsedHeight = hgt;  //  set the collpased height for documents based on the type of view (Tree or Stack) they will be displaye din
-            !NumCast(doc.viewScale) && (doc.viewScale = 1);
         });
     }
 
@@ -319,7 +291,7 @@ export class PresBox extends React.Component<FieldViewProps> {
     childLayoutTemplate = () => this.props.Document._viewType === CollectionViewType.Stacking ? Cast(Doc.UserDoc().presentationTemplate, Doc, null) : undefined;
     render() {
         const mode = NumCast(this.props.Document._viewType, CollectionViewType.Invalid);
-        this.initializeScaleViews(this.childDocs, mode);
+        this.initializeViewAliases(this.childDocs, mode);
         return <div className="presBox-cont" style={{ minWidth: this.props.Document.inOverlay ? 240 : undefined, pointerEvents: this.active() || this.props.Document.inOverlay ? "all" : "none" }} >
             <div className="presBox-buttons" style={{ display: this.props.Document._chromeStatus === "disabled" ? "none" : undefined }}>
                 <select className="collectionViewBaseChrome-viewPicker"
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index 98a6d384e..ed6b74ca8 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -138,10 +138,7 @@ export default class MobileInterface extends React.Component {
                 whenActiveChanged={emptyFunction}
                 bringToFront={emptyFunction}
                 ContainingCollectionView={undefined}
-                ContainingCollectionDoc={undefined}
-                zoomToScale={emptyFunction}
-                getScale={returnOne}>
-            </DocumentView>;
+                ContainingCollectionDoc={undefined} />
         }
         return "hello";
     }
@@ -300,10 +297,7 @@ export default class MobileInterface extends React.Component {
                         whenActiveChanged={emptyFunction}
                         bringToFront={emptyFunction}
                         ContainingCollectionView={undefined}
-                        ContainingCollectionDoc={undefined}
-                        zoomToScale={emptyFunction}
-                        getScale={returnOne}>
-                    </DocumentView>
+                        ContainingCollectionDoc={undefined} />
                 </div>
             );
         }
-- 
cgit v1.2.3-70-g09d2


From c291e8b2c8fb9034916b4eb4b286f99a8cb35a04 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 4 Apr 2020 21:30:38 -0400
Subject: fixed link zoom following for inPlace and otherwise.

---
 src/client/util/DocumentManager.ts                 | 90 ++++++++++++--------
 src/client/views/collections/CollectionView.tsx    |  7 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 10 ++-
 src/client/views/nodes/DocuLinkBox.tsx             | 57 +++++--------
 src/client/views/nodes/DocumentView.scss           |  7 +-
 src/client/views/nodes/DocumentView.tsx            | 98 +++++++++++++---------
 src/client/views/nodes/FormattedTextBoxComment.tsx |  2 +-
 src/client/views/pdf/Annotation.tsx                |  2 +-
 src/client/views/pdf/PDFViewer.tsx                 |  2 +-
 src/mobile/MobileInterface.tsx                     |  4 +-
 src/new_fields/documentSchemas.ts                  |  3 +-
 src/new_fields/util.ts                             |  2 +-
 12 files changed, 158 insertions(+), 126 deletions(-)

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

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 63d864db5..ae392a35b 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -5,11 +5,12 @@ import { List } from '../../new_fields/List';
 import { Cast, NumCast, StrCast } from '../../new_fields/Types';
 import { CollectionDockingView } from '../views/collections/CollectionDockingView';
 import { CollectionView } from '../views/collections/CollectionView';
-import { DocumentView } from '../views/nodes/DocumentView';
+import { DocumentView, DocFocusFunc } from '../views/nodes/DocumentView';
 import { LinkManager } from './LinkManager';
 import { Scripting } from './Scripting';
 import { SelectionManager } from './SelectionManager';
 import { DocumentType } from '../documents/DocumentTypes';
+import { UndoManager } from './UndoManager';
 
 
 export class DocumentManager {
@@ -84,24 +85,20 @@ export class DocumentManager {
         return this.getDocumentViewById(toFind[Id], preferredCollection);
     }
 
-    public getFirstDocumentView(toFind: Doc, originatingDoc: Opt<Doc> = undefined): DocumentView | undefined {
-        const views = this.getDocumentViews(toFind);
-        return views?.find(view => view.props.Document !== originatingDoc);
+    public getFirstDocumentView = (toFind: Doc, originatingDoc: Opt<Doc> = undefined): DocumentView | undefined => {
+        return this.getDocumentViews(toFind)?.find(view => view.props.Document !== originatingDoc);
     }
     public getDocumentViews(toFind: Doc): DocumentView[] {
         const toReturn: DocumentView[] = [];
+        const docViews = DocumentManager.Instance.DocumentViews;
 
-        // heurstic to return the "best" documents first:
+        // heuristic to return the "best" documents first:
         //   choose an exact match over an alias match
         //   choose documents that have a PanelWidth() over those that don't (the treeview documents have no panelWidth)
-        DocumentManager.Instance.DocumentViews.map(view =>
-            view.props.Document.presBox === undefined && view.props.PanelWidth() > 1 && view.props.Document === toFind && toReturn.push(view));
-        DocumentManager.Instance.DocumentViews.map(view =>
-            view.props.Document.presBox === undefined && view.props.PanelWidth() <= 1 && view.props.Document === toFind && toReturn.push(view));
-        DocumentManager.Instance.DocumentViews.map(view =>
-            view.props.Document.presBox === undefined && view.props.PanelWidth() > 1 && view.props.Document !== toFind && Doc.AreProtosEqual(view.props.Document, toFind) && toReturn.push(view));
-        DocumentManager.Instance.DocumentViews.map(view =>
-            view.props.Document.presBox === undefined && view.props.PanelWidth() <= 1 && view.props.Document !== toFind && Doc.AreProtosEqual(view.props.Document, toFind) && toReturn.push(view));
+        docViews.map(view => !view.props.Document.presBox && view.props.PanelWidth() > 1 && view.props.Document === toFind && toReturn.push(view));
+        docViews.map(view => !view.props.Document.presBox && view.props.PanelWidth() <= 1 && view.props.Document === toFind && toReturn.push(view));
+        docViews.map(view => !view.props.Document.presBox && view.props.PanelWidth() > 1 && view.props.Document !== toFind && Doc.AreProtosEqual(view.props.Document, toFind) && toReturn.push(view));
+        docViews.map(view => !view.props.Document.presBox && view.props.PanelWidth() <= 1 && view.props.Document !== toFind && Doc.AreProtosEqual(view.props.Document, toFind) && toReturn.push(view));
 
         return toReturn;
     }
@@ -133,31 +130,47 @@ export class DocumentManager {
         return pairs;
     }
 
-    public jumpToDocument = async (targetDoc: Doc, willZoom: boolean, dockFunc?: (doc: Doc) => void, docContext?: Doc, linkId?: string, closeContextIfNotFound: boolean = false, originatingDoc: Opt<Doc> = undefined): Promise<void> => {
+    static addRightSplit = (doc: Doc, finished?: () => void) => {
+        CollectionDockingView.AddRightSplit(doc);
+        finished?.();
+    }
+    public jumpToDocument = async (
+        targetDoc: Doc,
+        willZoom: boolean,
+        dockFunc = DocumentManager.addRightSplit,
+        docContext?: Doc,
+        linkId?: string,
+        closeContextIfNotFound: boolean = false,
+        originatingDoc: Opt<Doc> = undefined,
+        finished?: () => void): Promise<void> => {
+        const getFirstDocView = DocumentManager.Instance.getFirstDocumentView;
+        const finishFalse: DocFocusFunc = () => { finished?.(); return false; };
         const highlight = () => {
-            const finalDocView = DocumentManager.Instance.getFirstDocumentView(targetDoc);
-            finalDocView && (finalDocView.Document.scrollToLinkID = linkId);
-            finalDocView && Doc.linkFollowHighlight(finalDocView.props.Document);
+            const finalDocView = getFirstDocView(targetDoc);
+            if (finalDocView) {
+                finalDocView.Document.scrollToLinkID = linkId;
+                Doc.linkFollowHighlight(finalDocView.props.Document);
+            }
         };
-        const docView = DocumentManager.Instance.getFirstDocumentView(targetDoc, originatingDoc);
+        const docView = getFirstDocView(targetDoc, originatingDoc);
         let annotatedDoc = await Cast(targetDoc.annotationOn, Doc);
         if (annotatedDoc) {
-            const first = DocumentManager.Instance.getFirstDocumentView(annotatedDoc);
+            const first = getFirstDocView(annotatedDoc);
             if (first) annotatedDoc = first.props.Document;
         }
         if (docView) {  // we have a docView already and aren't forced to create a new one ... just focus on the document.  TODO move into view if necessary otherwise just highlight?
-            docView.props.focus(docView.props.Document, willZoom);
+            docView.props.focus(docView.props.Document, willZoom, undefined, finishFalse);
             highlight();
         } else {
             const contextDocs = docContext ? await DocListCastAsync(docContext.data) : undefined;
-            const contextDoc = contextDocs && contextDocs.find(doc => Doc.AreProtosEqual(doc, targetDoc)) ? docContext : undefined;
-            const targetDocContext = (annotatedDoc ? annotatedDoc : contextDoc);
+            const contextDoc = contextDocs?.find(doc => Doc.AreProtosEqual(doc, targetDoc)) ? docContext : undefined;
+            const targetDocContext = annotatedDoc || contextDoc;
 
             if (!targetDocContext) { // we don't have a view and there's no context specified ... create a new view of the target using the dockFunc or default
-                (dockFunc || CollectionDockingView.AddRightSplit)(Doc.BrushDoc(Doc.MakeAlias(targetDoc)));
+                dockFunc(Doc.BrushDoc(targetDoc), finished); // bcz: should we use this?: Doc.MakeAlias(targetDoc)));
                 highlight();
             } else {
-                const targetDocContextView = DocumentManager.Instance.getFirstDocumentView(targetDocContext);
+                const targetDocContextView = getFirstDocView(targetDocContext);
                 targetDocContext.scrollY = 0;  // this will force PDFs to activate and load their annotations / allow scrolling
                 if (targetDocContextView) { // we have a context view and aren't forced to create a new one ... focus on the context
                     targetDocContext.panTransformType = "Ease";
@@ -166,32 +179,33 @@ export class DocumentManager {
                     // now find the target document within the context
                     if (targetDoc.displayTimecode) {  // the target should show up once the video scrubs to the display timecode;
                         targetDocContext.currentTimecode = targetDoc.displayTimecode;
+                        finished?.();
                     } else {
                         setTimeout(() => {
-                            const retryDocView = DocumentManager.Instance.getDocumentView(targetDoc);
+                            const retryDocView = getFirstDocView(targetDoc);
                             if (retryDocView) {
-                                retryDocView.props.focus(targetDoc, willZoom); // focus on the target if it now exists in the context
+                                retryDocView.props.focus(targetDoc, willZoom, undefined, finishFalse); // focus on the target if it now exists in the context
                             } else {
-                                if (closeContextIfNotFound && targetDocContextView.props.removeDocument) targetDocContextView.props.removeDocument(targetDocContextView.props.Document);
-                                targetDoc.layout && (dockFunc || CollectionDockingView.AddRightSplit)(Doc.BrushDoc(Doc.MakeAlias(targetDoc))); // otherwise create a new view of the target
+                                if (closeContextIfNotFound) targetDocContextView.props.removeDocument?.(targetDocContextView.props.Document);
+                                targetDoc.layout && dockFunc(Doc.BrushDoc(Doc.MakeAlias(targetDoc)), finished); // otherwise create a new view of the target
                             }
                             highlight();
                         }, 0);
                     }
                 } else {  // there's no context view so we need to create one first and try again
-                    (dockFunc || CollectionDockingView.AddRightSplit)(targetDocContext);
+                    dockFunc(targetDocContext);
                     setTimeout(() => {
-                        const finalDocView = DocumentManager.Instance.getFirstDocumentView(targetDoc);
-                        const finalDocContextView = DocumentManager.Instance.getFirstDocumentView(targetDocContext);
+                        const finalDocView = getFirstDocView(targetDoc);
+                        const finalDocContextView = getFirstDocView(targetDocContext);
                         setTimeout(() =>  // if not, wait a bit to see if the context can be loaded (e.g., a PDF). wait interval heurisitic tries to guess how we're animating based on what's just become visible
-                            this.jumpToDocument(targetDoc, willZoom, dockFunc, undefined, linkId, true), finalDocView ? 0 : finalDocContextView ? 250 : 2000); // so call jump to doc again and if the doc isn't found, it will be created.
+                            this.jumpToDocument(targetDoc, willZoom, dockFunc, undefined, linkId, true, undefined, finished), finalDocView ? 0 : finalDocContextView ? 250 : 2000); // so call jump to doc again and if the doc isn't found, it will be created.
                     }, 0);
                 }
             }
         }
     }
 
-    public async FollowLink(link: Doc | undefined, doc: Doc, focus: (doc: Doc, maxLocation: string) => void, zoom: boolean = false, reverse: boolean = false, currentContext?: Doc) {
+    public async FollowLink(link: Doc | undefined, doc: Doc, focus: (doc: Doc, followLinkLocation: string, afterFocus?: DocFocusFunc) => void, zoom = false, reverse = false, currentContext?: Doc, finished?: () => void) {
         const linkDocs = link ? [link] : DocListCast(doc.links);
         SelectionManager.DeselectAll();
         const firstDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor1 as Doc, doc));
@@ -204,8 +218,10 @@ export class DocumentManager {
         const linkFollowDocs = first.length ? [await first[0].anchor2 as Doc, await first[0].anchor1 as Doc] : second.length ? [await second[0].anchor1 as Doc, await second[0].anchor2 as Doc] : undefined;
         const linkFollowDocContexts = first.length ? [await first[0].context as Doc, await first[0].context as Doc] : second.length ? [await second[0].context as Doc, await second[0].context as Doc] : [undefined, undefined];
         const linkFollowTimecodes = first.length ? [NumCast(first[0].anchor2_timecode), NumCast(first[0].anchor1_timecode)] : second.length ? [NumCast(second[0].anchor1_timecode), NumCast(second[0].anchor2_timecode)] : [undefined, undefined];
+        const Jump = DocumentManager.Instance.jumpToDocument;
+        const Focus = (where: string) => (doc: Doc, finished?: () => void) => focus(doc, where, () => { finished?.(); return false; });
         if (linkFollowDocs && linkDoc) {
-            const maxLocation = StrCast(linkDoc.maximizeLocation, "inTab");
+            const followLinkLocation = StrCast(linkDoc.followLinkLocation, "inTab");
             const targetContext = !Doc.AreProtosEqual(linkFollowDocContexts[reverse ? 1 : 0], currentContext) ? linkFollowDocContexts[reverse ? 1 : 0] : undefined;
             const target = linkFollowDocs[reverse ? 1 : 0];
             const annotatedDoc = await Cast(target.annotationOn, Doc);
@@ -214,9 +230,11 @@ export class DocumentManager {
             } else {
                 target.currentTimecode !== undefined && (target.currentTimecode = linkFollowTimecodes[reverse ? 1 : 0]);
             }
-            DocumentManager.Instance.jumpToDocument(linkFollowDocs[reverse ? 1 : 0], zoom, (doc: Doc) => focus(doc, maxLocation), targetContext, linkDoc[Id], undefined, doc);
+            Jump(linkFollowDocs[reverse ? 1 : 0], zoom, Focus(followLinkLocation), targetContext, linkDoc[Id], undefined, doc, finished);
         } else if (link) {
-            DocumentManager.Instance.jumpToDocument(link, zoom, (doc: Doc) => focus(doc, "onRight"), undefined, undefined);
+            Jump(link, zoom, Focus("onRight"), undefined, undefined, undefined, undefined, finished);
+        } else {
+            finished?.();
         }
     }
 }
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 729101bc3..c96d6bed3 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -99,8 +99,8 @@ export class CollectionView extends Touchable<FieldViewProps> {
     public static LayoutString(fieldStr: string) { return FieldView.LayoutString(CollectionView, fieldStr); }
 
     private _isChildActive = false;   //TODO should this be observable?
-    get _isLightboxOpen() { return BoolCast(this.props.Document.isLightboxOpen); };
-    set _isLightboxOpen(value) { this.props.Document.isLightboxOpen = value; };
+    get _isLightboxOpen() { return BoolCast(this.props.Document.isLightboxOpen); }
+    set _isLightboxOpen(value) { this.props.Document.isLightboxOpen = value; }
     @observable private _curLightboxImg = 0;
     @observable private static _safeMode = false;
     public static SetSafeMode(safeMode: boolean) { this._safeMode = safeMode; }
@@ -395,6 +395,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
         const scriptText = "setDocFilter(containingTreeView, heading, this.title, checked)";
         return ScriptField.MakeScript(scriptText, { this: Doc.name, heading: "string", checked: "string", containingTreeView: Doc.name });
     }
+    @computed get treeIgnoreFields() { return ["_facetCollection", "_docFilters"]; }
     @computed get filterView() {
         const facetCollection = this.props.Document;
         const flyout = (
@@ -422,7 +423,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
                         treeViewHideTitle={true}
                         treeViewHideHeaderFields={true}
                         onCheckedClick={this.scriptField!}
-                        ignoreFields={["_facetCollection", "_docFilters"]}
+                        ignoreFields={this.treeIgnoreFields}
                         annotationsKey={""}
                         dontRegisterView={true}
                         PanelWidth={this.facetWidth}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 376d217bb..d4e44b9a9 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -842,9 +842,17 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             backgroundHalo: this.backgroundHalo,
             parentActive: this.props.active,
             bringToFront: this.bringToFront,
+            addDocTab: this.addDocTab,
         };
     }
 
+    addDocTab = (doc: Doc, where: string) => {
+        if (where === "inPlace") {
+            this.dataDoc[this.props.fieldKey] = new List<Doc>([doc]);
+            return true;
+        }
+        return this.props.addDocTab(doc, where);
+    }
     getCalculatedPositions(params: { doc: Doc, index: number, collection: Doc, docs: Doc[], state: any }): PoolData {
         const result = this.Document.arrangeScript?.script.run(params, console.log);
         if (result?.success) {
@@ -1066,7 +1074,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                             if (doc instanceof Doc) {
                                 const [xx, yy] = this.props.ScreenToLocalTransform().transformPoint(x, y);
                                 doc.x = xx, doc.y = yy;
-                                this.props.addDocument && this.props.addDocument(doc);
+                                this.props.addDocument?.(doc);
                             }
                         }
                     }
diff --git a/src/client/views/nodes/DocuLinkBox.tsx b/src/client/views/nodes/DocuLinkBox.tsx
index 81cf90f92..31ce58079 100644
--- a/src/client/views/nodes/DocuLinkBox.tsx
+++ b/src/client/views/nodes/DocuLinkBox.tsx
@@ -4,7 +4,7 @@ import { Doc, DocListCast } from "../../../new_fields/Doc";
 import { documentSchema } from "../../../new_fields/documentSchemas";
 import { makeInterface } from "../../../new_fields/Schema";
 import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
-import { Utils } from '../../../Utils';
+import { Utils, setupMoveUpEvents } from '../../../Utils';
 import { DocumentManager } from "../../util/DocumentManager";
 import { DragManager } from "../../util/DragManager";
 import { DocComponent } from "../DocComponent";
@@ -16,6 +16,7 @@ import { ContextMenu } from "../ContextMenu";
 import { LinkEditor } from "../linking/LinkEditor";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { SelectionManager } from "../../util/SelectionManager";
+import { TraceMobx } from "../../../new_fields/util";
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -29,8 +30,6 @@ export class DocuLinkBox extends DocComponent<FieldViewProps, DocLinkSchema>(Doc
     _doubleTap = false;
     _lastTap: number = 0;
     _ref = React.createRef<HTMLDivElement>();
-    _downX = 0;
-    _downY = 0;
     _isOpen = false;
     _timeout: NodeJS.Timeout | undefined;
     @observable _x = 0;
@@ -40,56 +39,42 @@ export class DocuLinkBox extends DocComponent<FieldViewProps, DocLinkSchema>(Doc
     @observable _forceOpen = false;
 
     onPointerDown = (e: React.PointerEvent) => {
-        this._downX = e.clientX;
-        this._downY = e.clientY;
-        document.removeEventListener("pointermove", this.onPointerMove);
-        document.removeEventListener("pointerup", this.onPointerUp);
-        document.addEventListener("pointermove", this.onPointerMove);
-        document.addEventListener("pointerup", this.onPointerUp);
-        (e.button === 0 && !e.ctrlKey) && e.stopPropagation();
+        setupMoveUpEvents(this, e, this.onPointerMove, () => { }, this.onClick);
     }
-    onPointerMove = action((e: PointerEvent) => {
+    onPointerMove = action((e: PointerEvent, down: number[], delta: number[]) => {
         const cdiv = this._ref && this._ref.current && this._ref.current.parentElement;
-        if (!this._isOpen && cdiv && (Math.abs(e.clientX - this._downX) > 5 || Math.abs(e.clientY - this._downY) > 5)) {
+        if (!this._isOpen && cdiv) {
             const bounds = cdiv.getBoundingClientRect();
             const pt = Utils.getNearestPointInPerimeter(bounds.left, bounds.top, bounds.width, bounds.height, e.clientX, e.clientY);
             const separation = Math.sqrt((pt[0] - e.clientX) * (pt[0] - e.clientX) + (pt[1] - e.clientY) * (pt[1] - e.clientY));
-            const dragdist = Math.sqrt((pt[0] - this._downX) * (pt[0] - this._downX) + (pt[1] - this._downY) * (pt[1] - this._downY));
+            const dragdist = Math.sqrt((pt[0] - down[0]) * (pt[0] - down[0]) + (pt[1] - down[1]) * (pt[1] - down[1]));
             if (separation > 100) {
-                //DragManager.StartLinkTargetsDrag(this._ref.current!, pt[0], pt[1], Cast(this.props.Document[this.props.fieldKey], Doc) as Doc, [this.props.Document]); // Containging collection is the document, not a collection... hack.
                 const dragData = new DragManager.DocumentDragData([this.props.Document]);
                 dragData.dropAction = "alias";
-                dragData.removeDropProperties = ["anchor1_x", "anchor1_y", "anchor2_x", "anchor2_y"];
-                DragManager.StartDocumentDrag([this._ref.current!], dragData, this._downX, this._downY);
-                document.removeEventListener("pointermove", this.onPointerMove);
-                document.removeEventListener("pointerup", this.onPointerUp);
+                dragData.removeDropProperties = ["anchor1_x", "anchor1_y", "anchor2_x", "anchor2_y", "isButton"];
+                DragManager.StartDocumentDrag([this._ref.current!], dragData, down[0], down[1]);
+                return true;
             } else if (dragdist > separation) {
                 this.props.Document[this.props.fieldKey + "_x"] = (pt[0] - bounds.left) / bounds.width * 100;
                 this.props.Document[this.props.fieldKey + "_y"] = (pt[1] - bounds.top) / bounds.height * 100;
             }
         }
+        return false;
     });
-    onPointerUp = (e: PointerEvent) => {
-        document.removeEventListener("pointermove", this.onPointerMove);
-        document.removeEventListener("pointerup", this.onPointerUp);
-        if (Math.abs(e.clientX - this._downX) < 3 && Math.abs(e.clientY - this._downY) < 3 && (e.button === 2 || e.ctrlKey || !this.props.Document.isButton)) {
+    @action
+    onClick = (e: PointerEvent) => {
+        this._doubleTap = (Date.now() - this._lastTap < 300 && e.button === 0);
+        this._lastTap = Date.now();
+        if ((e.button === 2 || e.ctrlKey || !this.props.Document.isButton)) {
             this.props.select(false);
         }
-        this._doubleTap = (Date.now() - this._lastTap < 300 && e.button === 0 && Math.abs(e.clientX - this._downX) < 2 && Math.abs(e.clientY - this._downY) < 2);
-        this._lastTap = Date.now();
-    }
-
-    @action
-    onClick = (e: React.MouseEvent) => {
         if (!this._doubleTap) {
+            const anchorContainerDoc = this.props.ContainingCollectionDoc; // bcz: hack!  need a better prop for passing the anchor's container 
             this._editing = true;
-            this.props.ContainingCollectionDoc && this.props.bringToFront(this.props.ContainingCollectionDoc, false);
-            const {clientX, clientY} = e;
-            if (!this.props.Document.onClick && !this._isOpen) {
+            anchorContainerDoc && this.props.bringToFront(anchorContainerDoc, false);
+            if (anchorContainerDoc && !this.props.Document.onClick && !this._isOpen) {
                 this._timeout = setTimeout(action(() => {
-                    if (Math.abs(clientX - this._downX) < 3 && Math.abs(clientY - this._downY) < 3 && (e.button !== 2 && !e.ctrlKey && this.props.Document.isButton)) {
-                        DocumentManager.Instance.FollowLink(this.props.Document, this.props.ContainingCollectionDoc as Doc, document => this.props.addDocTab(document, StrCast(this.props.Document.linkOpenLocation, "inTab")), false);
-                    }
+                    DocumentManager.Instance.FollowLink(this.props.Document, anchorContainerDoc, document => this.props.addDocTab(document, StrCast(this.props.Document.linkOpenLocation, "inTab")), false);
                     this._editing = false;
                 }), 300 - (Date.now() - this._lastTap));
             }
@@ -97,7 +82,6 @@ export class DocuLinkBox extends DocComponent<FieldViewProps, DocLinkSchema>(Doc
             this._timeout && clearTimeout(this._timeout);
             this._timeout = undefined;
         }
-        e.stopPropagation();
     }
 
     openLinkDocOnRight = (e: React.MouseEvent) => {
@@ -126,6 +110,7 @@ export class DocuLinkBox extends DocComponent<FieldViewProps, DocLinkSchema>(Doc
     }
 
     render() {
+        TraceMobx();
         const x = this.props.PanelWidth() > 1 ? NumCast(this.props.Document[this.props.fieldKey + "_x"], 100) : 0;
         const y = this.props.PanelWidth() > 1 ? NumCast(this.props.Document[this.props.fieldKey + "_y"], 100) : 0;
         const c = StrCast(this.props.Document.backgroundColor, "lightblue");
@@ -143,7 +128,7 @@ export class DocuLinkBox extends DocComponent<FieldViewProps, DocLinkSchema>(Doc
             </div>
         );
         const small = this.props.PanelWidth() <= 1;
-        return <div className={`docuLinkBox-cont${small ? "-small" : ""}`} onPointerDown={this.onPointerDown} onClick={this.onClick} title={targetTitle} onContextMenu={this.specificContextMenu}
+        return <div className={`docuLinkBox-cont${small ? "-small" : ""}`} onPointerDown={this.onPointerDown} title={targetTitle} onContextMenu={this.specificContextMenu}
             ref={this._ref} style={{
                 background: c,
                 left: !small ? `calc(${x}% - 7.5px)` : undefined,
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index d1d96f0a1..ce7bcd206 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -34,13 +34,18 @@
         overflow-y: scroll;
         height: calc(100% - 20px);
     }
-
+    .documentView-docuLinkAnchor {
+        display:flex;
+        overflow: hidden;
+    }
     .documentView-docuLinkWrapper {
         pointer-events: none;
         position: absolute;
         transform-origin: top left;
         width: 100%;
         height: 100%;
+        top:0;
+        left:0;
         z-index: 1;
     }
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 11bf5a6a7..f74255933 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -52,6 +52,7 @@ library.add(fa.faEdit, fa.faTrash, fa.faShare, fa.faDownload, fa.faExpandArrowsA
     fa.faSquare, fa.faConciergeBell, fa.faWindowRestore, fa.faFolder, fa.faMapPin, fa.faLink, fa.faFingerprint, fa.faCrosshairs, fa.faDesktop, fa.faUnlock, fa.faLock, fa.faLaptopCode, fa.faMale,
     fa.faCopy, fa.faHandPointRight, fa.faCompass, fa.faSnowflake, fa.faMicrophone);
 
+export type DocFocusFunc = () => boolean;
 export interface DocumentViewProps {
     ContainingCollectionView: Opt<CollectionView>;
     ContainingCollectionDoc: Opt<Doc>;
@@ -74,7 +75,7 @@ export interface DocumentViewProps {
     ContentScaling: () => number;
     PanelWidth: () => number;
     PanelHeight: () => number;
-    focus: (doc: Doc, willZoom: boolean, scale?: number, afterFocus?: () => boolean) => void;
+    focus: (doc: Doc, willZoom: boolean, scale?: number, afterFocus?: DocFocusFunc) => void;
     parentActive: (outsideReaction: boolean) => boolean;
     whenActiveChanged: (isActive: boolean) => void;
     bringToFront: (doc: Doc, sendToBack?: boolean) => void;
@@ -287,15 +288,18 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 Doc.UnBrushDoc(this.props.Document);
             } else if (this.onClickHandler?.script) {
                 SelectionManager.DeselectAll();
-                UndoManager.RunInBatch(() => this.onClickHandler!.script.run({
+                const func = () => this.onClickHandler!.script.run({
                     this: this.props.Document,
                     self: Cast(this.props.Document.rootDocument, Doc, null) || this.props.Document,
                     containingCollection: this.props.ContainingCollectionDoc, shiftKey: e.shiftKey
-                }, console.log) && !this.props.Document.isButton && this.select(false), "on click");
+                }, console.log) && !this.props.Document.isButton && this.select(false);
+                if (this.props.Document !== Doc.UserDoc().undoBtn && this.props.Document !== Doc.UserDoc().redoBtn) {
+                    UndoManager.RunInBatch(func, "on click");
+                } else func();
             } else if (this.Document.type === DocumentType.BUTTON) {
                 UndoManager.RunInBatch(() => ScriptBox.EditButtonScript("On Button Clicked ...", this.props.Document, "onClick", e.clientX, e.clientY), "on button click");
             } else if (this.Document.isButton) {
-                UndoManager.RunInBatch(() => this.buttonClick(e.altKey, e.ctrlKey), "on link button follow");
+                DocListCast(this.props.Document.links).length && this.followLinkClick(e.altKey, e.ctrlKey);
             } else {
                 if (this.props.Document.isTemplateForField && !(e.ctrlKey || e.button > 0)) {
                     stopPropagate = false; // don't stop propagation for field templates -- want the selection to propagate up to the root document of the template
@@ -309,14 +313,20 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         }
     }
 
-    buttonClick = async (altKey: boolean, ctrlKey: boolean) => {
-        const linkDocs = DocListCast(this.props.Document.links);
-        if (linkDocs.length) {
-            DocumentManager.Instance.FollowLink(undefined, this.props.Document,
-                // open up target if it's not already in view ... by zooming into the button document first and setting flag to reset zoom afterwards
-                (doc: Doc, maxLocation: string) => this.props.focus(this.props.Document, true, 1, () => this.props.addDocTab(doc, maxLocation)),
-                ctrlKey, altKey, this.props.ContainingCollectionDoc);
-        }
+    // follows a link - if the target is on screen, it highlights/pans to it.
+    // if the target isn't onscreen, then it will open up the target in a tab, on the right, or in place
+    // depending on the followLinkLocation property of the source (or the link itself as a fallback);
+    followLinkClick = async (altKey: boolean, ctrlKey: boolean) => {
+        const batch = UndoManager.StartBatch("follow link click");
+        const docFollowLoc = StrCast(this.Document.followLinkLocation);
+        const { focus, addDocTab } = this.props;
+        await DocumentManager.Instance.FollowLink(undefined, this.props.Document,
+            // open up target if it's not already in view ... by zooming into the button document first and setting flag to reset zoom afterwards (except if 'inPlace', then leave alone)
+            (doc, followLoc, afterFocus) => focus(this.props.Document, true, 1, () => {
+                addDocTab(doc, docFollowLoc || followLoc) && focus(doc, true, undefined, afterFocus);
+                return (docFollowLoc || followLoc) === "inPlace" ? false : true;
+            }),
+            ctrlKey, altKey, this.props.ContainingCollectionDoc, batch.end);
     }
 
     handle1PointerDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>) => {
@@ -564,6 +574,17 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             this.Document.onClick = undefined;
         } else {
             this.Document.isButton = true;
+            this.Document.followLinkLocation = undefined;
+        }
+    }
+
+    @undoBatch
+    toggleFollowInPlace = (): void => {
+        if (this.Document.isButton) {
+            this.Document.isButton = false;
+        } else {
+            this.Document.isButton = true;
+            this.Document.followLinkLocation = "inPlace";
         }
     }
 
@@ -693,6 +714,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         onClicks.push({ description: "Enter Portal", event: this.makeIntoPortal, icon: "window-restore" });
         onClicks.push({ description: "Toggle Detail", event: () => this.Document.onClick = ScriptField.MakeScript(`toggleDetail(this, "${this.props.Document.layoutKey}")`), icon: "window-restore" });
         onClicks.push({ description: this.Document.ignoreClick ? "Select" : "Do Nothing", event: () => this.Document.ignoreClick = !this.Document.ignoreClick, icon: this.Document.ignoreClick ? "unlock" : "lock" });
+        onClicks.push({ description: this.Document.isButton ? "Remove Follow Behavior" : "Follow Link in Place", event: this.toggleFollowInPlace, icon: "concierge-bell" });
         onClicks.push({ description: this.Document.isButton || this.Document.onClick ? "Remove Click Behavior" : "Follow Link", event: this.toggleButtonBehavior, icon: "concierge-bell" });
         onClicks.push({ description: "Edit onClick Script", icon: "edit", event: (obj: any) => ScriptBox.EditButtonScript("On Button Clicked ...", this.props.Document, "onClick", obj.x, obj.y) });
         !existingOnClick && cm.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
@@ -981,31 +1003,36 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         return this._link;
     }
 
+    hideLinkAnchor = (doc: Doc) => undoBatch(doc => doc.hidden = true)();
+    anchorPanelWidth = () => this.props.PanelWidth() || 1;
+    anchorPanelHeight = () => this.props.PanelHeight() || 1;
+    @computed get anchors() {
+        TraceMobx();
+        return DocListCast(this.Document.links).filter(d => !d.hidden && this.isNonTemporalLink).map((d, i) =>
+            <div className="documentView-docuLinkWrapper" key={d[Id]}>
+                <DocumentView {...this.props}
+                    Document={d}
+                    ContainingCollectionDoc={this.props.Document} // bcz: hack this.props.Document is not a collection  Need a better prop for passing the containing document to the DocuLinkBox
+                    PanelWidth={this.anchorPanelWidth}
+                    PanelHeight={this.anchorPanelHeight}
+                    layoutKey={this.linkEndpoint(d)}
+                    ContentScaling={returnOne}
+                    backgroundColor={returnTransparent}
+                    removeDocument={this.hideLinkAnchor} />
+            </div>);
+    }
     @computed get innards() {
         TraceMobx();
-        if (!this.props.PanelWidth()) {
-            return <div style={{ display: "flex", overflow: "hidden" }}>
+        if (!this.props.PanelWidth()) {  // this happens when the document is a tree view label
+            return <div className="documentView-docuLinkAnchor" >
                 {StrCast(this.props.Document.title)}
-                {this.Document.links && DocListCast(this.Document.links).filter(d => !d.hidden).filter(this.isNonTemporalLink).map((d, i) =>
-                    <div className="documentView-docuLinkWrapper" style={{ position: "absolute", top: 0, left: 0 }} key={`${d[Id]}`}>
-                        <DocumentView {...this.props}
-                            Document={d}
-                            ContainingCollectionDoc={this.props.Document}
-                            PanelWidth={returnOne} PanelHeight={returnOne}
-                            layoutKey={this.linkEndpoint(d)} ContentScaling={returnOne}
-                            backgroundColor={returnTransparent}
-                            removeDocument={undoBatch(doc => doc.hidden = true)} />
-                    </div>)}
+                {this.anchors}
             </div>;
         }
         const showTitle = StrCast(this.layoutDoc._showTitle);
         const showTitleHover = StrCast(this.layoutDoc._showTitleHover);
         const showCaption = StrCast(this.layoutDoc._showCaption);
         const showTextTitle = showTitle && (StrCast(this.layoutDoc.layout).indexOf("PresBox") !== -1 || StrCast(this.layoutDoc.layout).indexOf("FormattedTextBox") !== -1) ? showTitle : undefined;
-        const searchHighlight = (!this.Document.searchFields ? (null) :
-            <div className="documentView-searchHighlight">
-                {this.Document.searchFields}
-            </div>);
         const captionView = (!showCaption ? (null) :
             <div className="documentView-captionWrapper">
                 <DocumentContentsView {...OmitKeys(this.props, ['children']).omit}
@@ -1032,26 +1059,15 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 />
             </div>);
         return <>
-            {this.Document.links && DocListCast(this.Document.links).filter(d => !d.hidden).filter(this.isNonTemporalLink).map((d, i) =>
-                <div className="documentView-docuLinkWrapper" key={`${d[Id]}`}>
-                    <DocumentView {...this.props} ContentScaling={returnOne} ContainingCollectionDoc={this.props.Document} Document={d} layoutKey={this.linkEndpoint(d)} backgroundColor={returnTransparent} removeDocument={undoBatch(doc => doc.hidden = true)} />
-                </div>)}
+            {this.anchors}
             {!showTitle && !showCaption ?
-                this.Document.searchFields ?
-                    (<div className="documentView-searchWrapper">
-                        {this.contents}
-                        {searchHighlight}
-                    </div>)
-                    :
-                    this.contents
-                :
+                this.contents :
                 <div className="documentView-styleWrapper" >
                     <div className="documentView-styleContentWrapper" style={{ height: showTextTitle ? `calc(100% - ${this.chromeHeight()}px)` : "100%", top: showTextTitle ? this.chromeHeight() : undefined }}>
                         {this.contents}
                     </div>
                     {titleView}
                     {captionView}
-                    {searchHighlight}
                 </div>
             }
         </>;
diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx
index d1a563494..babca2a14 100644
--- a/src/client/views/nodes/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/FormattedTextBoxComment.tsx
@@ -84,7 +84,7 @@ export class FormattedTextBoxComment {
                 const textBox = FormattedTextBoxComment.textBox;
                 if (FormattedTextBoxComment.linkDoc && !keep && textBox) {
                     DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document,
-                        (doc: Doc, maxLocation: string) => textBox.props.addDocTab(doc, e.ctrlKey ? "inTab" : "onRight"));
+                        (doc: Doc, followLinkLocation: string) => textBox.props.addDocTab(doc, e.ctrlKey ? "inTab" : "onRight"));
                 } else if (textBox && (FormattedTextBoxComment.tooltipText as any).href) {
                     textBox.props.addDocTab(Docs.Create.WebDocument((FormattedTextBoxComment.tooltipText as any).href, { title: (FormattedTextBoxComment.tooltipText as any).href, _width: 200, _height: 400 }), "onRight");
                 }
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index d23c81065..5cb766c03 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -98,7 +98,7 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
             const annoGroup = await Cast(this.props.document.group, Doc);
             if (annoGroup) {
                 DocumentManager.Instance.FollowLink(undefined, annoGroup,
-                    (doc: Doc, maxLocation: string) => this.props.addDocTab(doc, e.ctrlKey ? "inTab" : "onRight"),
+                    (doc: Doc, followLinkLocation: string) => this.props.addDocTab(doc, e.ctrlKey ? "inTab" : "onRight"),
                     false, false, undefined);
                 e.stopPropagation();
             }
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index c032f019d..25ceb6218 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -585,7 +585,7 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
                 dragComplete: e => {
                     if (!e.aborted && e.annoDragData && !e.annoDragData.linkedToDoc) {
                         const link = DocUtils.MakeLink({ doc: annotationDoc }, { doc: e.annoDragData.dropDocument }, "Annotation");
-                        if (link) link.maximizeLocation = "onRight";
+                        if (link) link.followLinkLocation = "onRight";
                     }
                 }
             });
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index ed6b74ca8..dfd217e97 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -138,7 +138,7 @@ export default class MobileInterface extends React.Component {
                 whenActiveChanged={emptyFunction}
                 bringToFront={emptyFunction}
                 ContainingCollectionView={undefined}
-                ContainingCollectionDoc={undefined} />
+                ContainingCollectionDoc={undefined} />;
         }
         return "hello";
     }
@@ -198,7 +198,7 @@ export default class MobileInterface extends React.Component {
                         LibraryPath={emptyPath}
                         fieldKey={""}
                         dropAction={"alias"}
-                        bringToFront={emptyFunction }
+                        bringToFront={emptyFunction}
                         addDocTab={returnFalse}
                         pinToPres={emptyFunction}
                         PanelHeight={() => window.innerHeight}
diff --git a/src/new_fields/documentSchemas.ts b/src/new_fields/documentSchemas.ts
index 3ac46362a..1a5a471a0 100644
--- a/src/new_fields/documentSchemas.ts
+++ b/src/new_fields/documentSchemas.ts
@@ -46,12 +46,11 @@ export const documentSchema = createSchema({
     treeViewExpandedView: "string", // name of field whose contents are being displayed as the document's subtree
     treeViewPreventOpen: "boolean", // ignores the treeViewOpen flag (for allowing a view to not be slaved to other views of the document)
     currentTimecode: "number",  // current play back time of a temporal document (video / audio)
-    maximizeLocation: "string", // flag for where to place content when following a click interaction (e.g., onRight, inPlace, inTab) 
+    followLinkLocation: "string",// flag for where to place content when following a click interaction (e.g., onRight, inPlace, inTab, ) 
     lockedPosition: "boolean",  // whether the document can be moved (dragged)
     lockedTransform: "boolean", // whether the document can be panned/zoomed
     inOverlay: "boolean",       // whether the document is rendered in an OverlayView which handles selection/dragging differently
     borderRounding: "string",   // border radius rounding of document
-    searchFields: "string",     // the search fields to display when this document matches a search in its metadata
     heading: "number",          // the logical layout 'heading' of this document (used by rule provider to stylize h1 header elements, from h2, etc)
     isButton: "boolean",        // whether document functions as a button (overiding native interactions of its content)    
     ignoreClick: "boolean",     // whether documents ignores input clicks (but does not ignore manipulation and other events) 
diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts
index 8c719ccd8..480a55da0 100644
--- a/src/new_fields/util.ts
+++ b/src/new_fields/util.ts
@@ -12,7 +12,7 @@ function _readOnlySetter(): never {
     throw new Error("Documents can't be modified in read-only mode");
 }
 
-const tracing = false;
+const tracing = true;
 export function TraceMobx() {
     tracing && trace();
 }
-- 
cgit v1.2.3-70-g09d2


From 48478855cc28aa423b2d1e482bd181c8c5066da6 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sun, 5 Apr 2020 15:13:25 -0400
Subject: adding nativeWidth as props stuff

---
 src/client/util/RichTextSchema.tsx                 |  4 +-
 src/client/views/GestureOverlay.tsx                | 13 ++++---
 src/client/views/MainView.tsx                      | 10 ++++-
 src/client/views/OverlayView.tsx                   |  2 +
 src/client/views/Palette.tsx                       |  4 +-
 src/client/views/RecommendationsBox.tsx            |  4 +-
 src/client/views/TemplateMenu.tsx                  |  6 ++-
 .../views/collections/CollectionDockingView.tsx    |  4 +-
 .../views/collections/CollectionLinearView.tsx     |  6 ++-
 .../views/collections/CollectionSchemaCells.tsx    |  2 +
 .../views/collections/CollectionStackingView.tsx   | 32 +++++++++------
 src/client/views/collections/CollectionSubView.tsx |  8 ++--
 .../views/collections/CollectionTimeView.tsx       |  5 +--
 .../views/collections/CollectionTreeView.tsx       |  2 +
 src/client/views/collections/CollectionView.tsx    |  4 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  5 ++-
 src/client/views/nodes/AudioBox.tsx                |  4 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx | 45 +++++++++++++---------
 .../views/nodes/ContentFittingDocumentView.tsx     | 25 +++++++-----
 src/client/views/nodes/DocumentView.tsx            | 14 +++++--
 src/client/views/nodes/FieldView.tsx               |  2 +
 src/client/views/nodes/FormattedTextBox.tsx        |  4 +-
 src/client/views/nodes/ImageBox.tsx                |  4 +-
 src/client/views/nodes/KeyValuePair.tsx            |  6 ++-
 src/client/views/nodes/LinkBox.tsx                 |  2 +
 src/client/views/nodes/ScreenshotBox.tsx           |  4 +-
 src/client/views/nodes/VideoBox.tsx                |  4 +-
 src/client/views/nodes/WebBox.tsx                  |  4 +-
 src/client/views/pdf/PDFViewer.tsx                 |  4 +-
 src/mobile/MobileInterface.tsx                     | 14 +++++--
 src/new_fields/documentSchemas.ts                  |  2 +-
 src/new_fields/util.ts                             |  2 +-
 32 files changed, 170 insertions(+), 81 deletions(-)

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

diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index d4568b17f..de2707d36 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -16,7 +16,7 @@ import { listSpec } from "../../new_fields/Schema";
 import { SchemaHeaderField } from "../../new_fields/SchemaHeaderField";
 import { ComputedField } from "../../new_fields/ScriptField";
 import { BoolCast, Cast, NumCast, StrCast } from "../../new_fields/Types";
-import { emptyFunction, returnEmptyString, returnFalse, returnOne, Utils } from "../../Utils";
+import { emptyFunction, returnEmptyString, returnFalse, returnOne, Utils, returnZero } from "../../Utils";
 import { DocServer } from "../DocServer";
 import { Docs } from "../documents/Documents";
 import { CollectionViewType } from "../views/collections/CollectionView";
@@ -824,6 +824,8 @@ export class DashDocView {
                     addDocTab={this._textBox.props.addDocTab}
                     pinToPres={returnFalse}
                     renderDepth={self._textBox.props.renderDepth + 1}
+                    NativeHeight={returnZero}
+                    NativeWidth={returnZero}
                     PanelWidth={finalLayout[WidthSym]}
                     PanelHeight={finalLayout[HeightSym]}
                     focus={this.outerFocus}
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 68f1d13fb..110b61efe 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -15,7 +15,7 @@ import { Scripting } from "../util/Scripting";
 import { FieldValue, Cast, NumCast, BoolCast } from "../../new_fields/Types";
 import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils";
 import HorizontalPalette from "./Palette";
-import { Utils, emptyPath, emptyFunction, returnFalse, returnOne, returnEmptyString, returnTrue, numberRange } from "../../Utils";
+import { Utils, emptyPath, emptyFunction, returnFalse, returnOne, returnEmptyString, returnTrue, numberRange, returnZero } from "../../Utils";
 import { DocumentView } from "./nodes/DocumentView";
 import { Transform } from "../util/Transform";
 import { DocumentContentsView } from "./nodes/DocumentContentsView";
@@ -695,7 +695,8 @@ export default class GestureOverlay extends Touchable {
             </svg>]
         ];
     }
-
+    screenToLocalTransform = () => new Transform(-(this._thumbX ?? 0), -(this._thumbY ?? 0) + this.height, 1);
+    return300 = () => 300;
     @action
     public openFloatingDoc = (doc: Doc) => {
         this._clipboardDoc =
@@ -709,10 +710,12 @@ export default class GestureOverlay extends Touchable {
                 pinToPres={emptyFunction}
                 onClick={undefined}
                 removeDocument={undefined}
-                ScreenToLocalTransform={() => new Transform(-(this._thumbX ?? 0), -(this._thumbY ?? 0) + this.height, 1)}
+                ScreenToLocalTransform={this.screenToLocalTransform}
                 ContentScaling={returnOne}
-                PanelWidth={() => 300}
-                PanelHeight={() => 300}
+                PanelWidth={this.return300}
+                PanelHeight={this.return300}
+                NativeHeight={returnZero}
+                NativeWidth={returnZero}
                 renderDepth={0}
                 backgroundColor={returnEmptyString}
                 focus={emptyFunction}
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 420cc95ec..bef92f0fd 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -13,7 +13,7 @@ import { listSpec } from '../../new_fields/Schema';
 import { BoolCast, Cast, FieldValue, StrCast } from '../../new_fields/Types';
 import { TraceMobx } from '../../new_fields/util';
 import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils';
-import { emptyFunction, emptyPath, returnFalse, returnOne, returnTrue, Utils } from '../../Utils';
+import { emptyFunction, emptyPath, returnFalse, returnOne, returnZero, returnTrue, Utils } from '../../Utils';
 import GoogleAuthenticationManager from '../apis/GoogleAuthenticationManager';
 import { DocServer } from '../DocServer';
 import { Docs, DocumentOptions } from '../documents/Documents';
@@ -307,6 +307,8 @@ export class MainView extends React.Component {
             removeDocument={undefined}
             ScreenToLocalTransform={Transform.Identity}
             ContentScaling={returnOne}
+            NativeHeight={returnZero}
+            NativeWidth={returnZero}
             PanelWidth={this.getPWidth}
             PanelHeight={this.getPHeight}
             renderDepth={0}
@@ -405,6 +407,8 @@ export class MainView extends React.Component {
                     onClick={undefined}
                     ScreenToLocalTransform={Transform.Identity}
                     ContentScaling={returnOne}
+                    NativeHeight={returnZero}
+                    NativeWidth={returnZero}
                     PanelWidth={this.flyoutWidthFunc}
                     PanelHeight={this.getPHeight}
                     renderDepth={0}
@@ -424,6 +428,8 @@ export class MainView extends React.Component {
                     addDocument={undefined}
                     addDocTab={this.addDocTabFunc}
                     pinToPres={emptyFunction}
+                    NativeHeight={returnZero}
+                    NativeWidth={returnZero}
                     rootSelected={returnTrue}
                     removeDocument={returnFalse}
                     onClick={undefined}
@@ -524,6 +530,8 @@ export class MainView extends React.Component {
                     onClick={undefined}
                     ScreenToLocalTransform={this.buttonBarXf}
                     ContentScaling={returnOne}
+                    NativeHeight={returnZero}
+                    NativeWidth={returnZero}
                     PanelWidth={this.flyoutWidthFunc}
                     PanelHeight={this.getContentsHeight}
                     renderDepth={0}
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index 7cfae0bbb..4000cade5 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -182,6 +182,8 @@ export class OverlayView extends React.Component {
                     addDocument={undefined}
                     removeDocument={undefined}
                     ContentScaling={returnOne}
+                    NativeHeight={returnZero}
+                    NativeWidth={returnZero}
                     PanelWidth={returnOne}
                     PanelHeight={returnOne}
                     ScreenToLocalTransform={Transform.Identity}
diff --git a/src/client/views/Palette.tsx b/src/client/views/Palette.tsx
index d088d5bba..63744cb50 100644
--- a/src/client/views/Palette.tsx
+++ b/src/client/views/Palette.tsx
@@ -3,7 +3,7 @@ import { observer } from "mobx-react";
 import * as React from "react";
 import { Doc } from "../../new_fields/Doc";
 import { NumCast } from "../../new_fields/Types";
-import { emptyFunction, emptyPath, returnEmptyString, returnFalse, returnOne, returnTrue } from "../../Utils";
+import { emptyFunction, emptyPath, returnEmptyString, returnZero, returnFalse, returnOne, returnTrue } from "../../Utils";
 import { Transform } from "../util/Transform";
 import { DocumentView } from "./nodes/DocumentView";
 import "./Palette.scss";
@@ -49,6 +49,8 @@ export default class Palette extends React.Component<PaletteProps> {
                             onClick={undefined}
                             ScreenToLocalTransform={Transform.Identity}
                             ContentScaling={returnOne}
+                            NativeHeight={returnZero}
+                            NativeWidth={returnZero}
                             PanelWidth={() => window.screen.width}
                             PanelHeight={() => window.screen.height}
                             renderDepth={0}
diff --git a/src/client/views/RecommendationsBox.tsx b/src/client/views/RecommendationsBox.tsx
index ddeda28ac..e66fd3eb4 100644
--- a/src/client/views/RecommendationsBox.tsx
+++ b/src/client/views/RecommendationsBox.tsx
@@ -6,7 +6,7 @@ import "./RecommendationsBox.scss";
 import { Doc, DocListCast, WidthSym, HeightSym } from "../../new_fields/Doc";
 import { DocumentIcon } from "./nodes/DocumentIcon";
 import { StrCast, NumCast } from "../../new_fields/Types";
-import { returnFalse, emptyFunction, returnEmptyString, returnOne, emptyPath } from "../../Utils";
+import { returnFalse, emptyFunction, returnEmptyString, returnOne, emptyPath, returnZero } from "../../Utils";
 import { Transform } from "../util/Transform";
 import { ObjectField } from "../../new_fields/ObjectField";
 import { DocumentView } from "./nodes/DocumentView";
@@ -73,6 +73,8 @@ export class RecommendationsBox extends React.Component<FieldViewProps> {
                 addDocTab={returnFalse}
                 pinToPres={returnFalse}
                 renderDepth={1}
+                NativeHeight={returnZero}
+                NativeWidth={returnZero}
                 PanelWidth={returnXDimension}
                 PanelHeight={returnYDimension}
                 focus={emptyFunction}
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index f2fb26679..c72f3681a 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -10,7 +10,7 @@ import { Doc, DocListCast } from "../../new_fields/Doc";
 import { Docs, } from "../documents/Documents";
 import { StrCast, Cast } from "../../new_fields/Types";
 import { CollectionTreeView } from "./collections/CollectionTreeView";
-import { returnTrue, emptyFunction, returnFalse, returnOne, emptyPath } from "../../Utils";
+import { returnTrue, emptyFunction, returnFalse, returnOne, emptyPath, returnZero } from "../../Utils";
 import { Transform } from "../util/Transform";
 import { ScriptField, ComputedField } from "../../new_fields/ScriptField";
 import { Scripting } from "../util/Scripting";
@@ -149,6 +149,8 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
                     select={emptyFunction}
                     renderDepth={1}
                     addDocTab={returnFalse}
+                    NativeHeight={returnZero}
+                    NativeWidth={returnZero}
                     PanelWidth={this.return100}
                     PanelHeight={this.return100}
                     treeViewHideHeaderFields={true}
@@ -161,7 +163,7 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
             );
         }
         return <ul className="template-list" style={{ display: "block" }}>
-            <input placeholder="+ layout" ref={this._customRef} onKeyPress={this.onCustomKeypress}/>
+            <input placeholder="+ layout" ref={this._customRef} onKeyPress={this.onCustomKeypress} />
             {templateMenu}
         </ul>;
     }
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 11eecf771..28aaf0c57 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -15,7 +15,7 @@ import { FieldId } from "../../../new_fields/RefField";
 import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
 import { TraceMobx } from '../../../new_fields/util';
 import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
-import { emptyFunction, returnOne, returnTrue, Utils } from "../../../Utils";
+import { emptyFunction, returnOne, returnTrue, Utils, returnZero } from "../../../Utils";
 import { DocServer } from "../../DocServer";
 import { Docs } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
@@ -794,6 +794,8 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
             ContentScaling={this.contentScaling}
             PanelWidth={this.panelWidth}
             PanelHeight={this.panelHeight}
+            NativeHeight={returnZero}
+            NativeWidth={returnZero}
             ScreenToLocalTransform={this.ScreenToLocalTransform}
             renderDepth={0}
             parentActive={returnTrue}
diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx
index 3bd50ad52..cb0206260 100644
--- a/src/client/views/collections/CollectionLinearView.tsx
+++ b/src/client/views/collections/CollectionLinearView.tsx
@@ -4,7 +4,7 @@ import * as React from 'react';
 import { Doc, HeightSym, WidthSym } from '../../../new_fields/Doc';
 import { makeInterface } from '../../../new_fields/Schema';
 import { BoolCast, NumCast, StrCast, Cast, ScriptCast } from '../../../new_fields/Types';
-import { emptyFunction, returnEmptyString, returnOne, returnTrue, Utils, returnFalse } from '../../../Utils';
+import { emptyFunction, returnEmptyString, returnOne, returnTrue, Utils, returnFalse, returnZero } from '../../../Utils';
 import { DragManager } from '../../util/DragManager';
 import { Transform } from '../../util/Transform';
 import "./CollectionLinearView.scss";
@@ -114,6 +114,8 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
                                 onClick={undefined}
                                 ScreenToLocalTransform={this.getTransform(dref)}
                                 ContentScaling={returnOne}
+                                NativeHeight={returnZero}
+                                NativeWidth={returnZero}
                                 PanelWidth={nested ? pair.layout[WidthSym] : () => this.dimension()}// ugh - need to get rid of this inline function to avoid recomputing
                                 PanelHeight={nested ? pair.layout[HeightSym] : () => this.dimension()}
                                 renderDepth={this.props.renderDepth + 1}
@@ -123,7 +125,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
                                 whenActiveChanged={emptyFunction}
                                 bringToFront={emptyFunction}
                                 ContainingCollectionView={undefined}
-                                ContainingCollectionDoc={undefined}/>
+                                ContainingCollectionDoc={undefined} />
                         </div>;
                     })}
                 </div>
diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx
index 7b16ff546..ae71c54f7 100644
--- a/src/client/views/collections/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/CollectionSchemaCells.tsx
@@ -172,6 +172,8 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
             whenActiveChanged: emptyFunction,
             PanelHeight: returnZero,
             PanelWidth: returnZero,
+            NativeHeight: returnZero,
+            NativeWidth: returnZero,
             addDocTab: this.props.addDocTab,
             pinToPres: this.props.pinToPres,
             ContentScaling: returnOne
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index d21e17bbc..8ceeb66f1 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -11,7 +11,7 @@ import { listSpec } from "../../../new_fields/Schema";
 import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
 import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../../new_fields/Types";
 import { TraceMobx } from "../../../new_fields/util";
-import { Utils, setupMoveUpEvents, emptyFunction } from "../../../Utils";
+import { Utils, setupMoveUpEvents, emptyFunction, returnZero, returnOne } from "../../../Utils";
 import { DragManager, dropActionType } from "../../util/DragManager";
 import { Transform } from "../../util/Transform";
 import { undoBatch } from "../../util/UndoManager";
@@ -24,7 +24,7 @@ import "./CollectionStackingView.scss";
 import { CollectionStackingViewFieldColumn } from "./CollectionStackingViewFieldColumn";
 import { CollectionSubView } from "./CollectionSubView";
 import { CollectionViewType } from "./CollectionView";
-import { Docs } from "../../documents/Documents";
+import { DocumentView } from "../nodes/DocumentView";
 
 @observer
 export class CollectionStackingView extends CollectionSubView(doc => doc) {
@@ -53,6 +53,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     @computed get NodeWidth() { return this.props.PanelWidth() - this.gridGap; }
 
     children(docs: Doc[], columns?: number) {
+        TraceMobx();
         this._docXfs.length = 0;
         return docs.map((d, i) => {
             const height = () => this.getDocHeight(d);
@@ -115,7 +116,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     componentDidMount() {
         super.componentDidMount();
         this._heightDisposer = reaction(() => {
-            if (this.props.Document._autoHeight) {
+            if (this.props.Document._autoHeight && !this.props.NativeHeight()) {
                 const sectionsList = Array.from(this.Sections.size ? this.Sections.values() : [this.filteredChildren]);
                 if (this.isStackingView) {
                     const res = this.props.ContentScaling() * sectionsList.reduce((maxHght, s) => {
@@ -168,12 +169,13 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     getDisplayDoc(doc: Doc, dataDoc: Doc | undefined, dxf: () => Transform, width: () => number) {
         const layoutDoc = Doc.Layout(doc, this.props.childLayoutTemplate?.());
         const height = () => this.getDocHeight(doc);
-        return <ContentFittingDocumentView
+        return <DocumentView
             Document={doc}
-            DataDocument={dataDoc || (doc[DataSym] !== doc && doc[DataSym])}
+            DataDoc={dataDoc || (doc[DataSym] !== doc && doc[DataSym])}
             backgroundColor={this.props.backgroundColor}
             LayoutDoc={this.props.childLayoutTemplate}
             LibraryPath={this.props.LibraryPath}
+            FreezeDimensions={this.props.freezeDimensions}
             renderDepth={this.props.renderDepth + 1}
             fitToBox={this.props.fitToBox}
             rootSelected={this.rootSelected}
@@ -181,18 +183,22 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
             onClick={layoutDoc.isTemplateDoc ? this.onClickHandler : this.onChildClickHandler}
             PanelWidth={width}
             PanelHeight={height}
-            getTransform={dxf}
+            ScreenToLocalTransform={dxf}
             focus={this.props.focus}
-            CollectionDoc={this.props.CollectionView && this.props.CollectionView.props.Document}
-            CollectionView={this.props.CollectionView}
+            ContainingCollectionDoc={this.props.CollectionView && this.props.CollectionView.props.Document}
+            ContainingCollectionView={this.props.CollectionView}
             addDocument={this.props.addDocument}
             moveDocument={this.props.moveDocument}
             removeDocument={this.props.removeDocument}
-            active={this.props.active}
+            parentActive={this.props.active}
+            bringToFront={emptyFunction}
+            NativeHeight={returnZero}
+            NativeWidth={returnZero}
+            ContentScaling={returnOne}
             whenActiveChanged={this.props.whenActiveChanged}
             addDocTab={this.props.addDocTab}
             pinToPres={this.props.pinToPres}>
-        </ContentFittingDocumentView>;
+        </DocumentView>;
     }
 
     getDocWidth(d?: Doc) {
@@ -387,7 +393,11 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         return sections.map(section => this.isStackingView ? this.sectionStacking(section[0], section[1]) : this.sectionMasonry(section[0], section[1]));
     }
 
-    @computed get scaling() { return !this.props.Document._nativeWidth ? 1 : this.props.PanelHeight() / NumCast(this.props.Document._nativeHeight); }
+
+    @computed get nativeWidth() { return NumCast(this.layoutDoc._nativeWidth) || this.props.NativeWidth() || 0; }
+    @computed get nativeHeight() { return NumCast(this.layoutDoc._nativeHeight) || this.props.NativeHeight() || 0; }
+
+    @computed get scaling() { return !this.nativeWidth ? 1 : this.props.PanelHeight() / this.nativeHeight; }
 
     render() {
         TraceMobx();
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index d1d6ae3c1..32449de5e 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -36,12 +36,15 @@ export interface CollectionViewProps extends FieldViewProps {
     setPreviewCursor?: (func: (x: number, y: number, drag: boolean) => void) => void;
     rootSelected: () => boolean;
     fieldKey: string;
+    NativeWidth: () => number;
+    NativeHeight: () => number;
 }
 
 export interface SubCollectionViewProps extends CollectionViewProps {
     CollectionView: Opt<CollectionView>;
     children?: never | (() => JSX.Element[]) | React.ReactNode;
-    overrideDocuments?: Doc[]; // used to override the documents shown by the sub collection to an explict list (see LinkBox)
+    freezeDimensions?: boolean; // used by TimeView to coerce documents to treat their width height as their native width/height
+    overrideDocuments?: Doc[]; // used to override the documents shown by the sub collection to an explicit list (see LinkBox)
     ignoreFields?: string[]; // used in TreeView to ignore specified fields (see LinkBox)
     isAnnotationOverlay?: boolean;
     annotationsKey: string;
@@ -213,9 +216,6 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
                 this.props.Document.dropConverter.script.run({ dragData: docDragData }); /// bcz: check this 
             if (docDragData) {
                 let added = false;
-                if (this.props.Document._freezeOnDrop) {
-                    de.complete.docDragData?.droppedDocuments.forEach(drop => Doc.freezeNativeDimensions(drop, drop[WidthSym](), drop[HeightSym]()));
-                }
                 if (docDragData.dropAction || docDragData.userDropAction) {
                     added = docDragData.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d) || added, false);
                 } else if (docDragData.moveDocument) {
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 4f77e8b0e..d9a10cdc8 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -5,7 +5,7 @@ import { List } from "../../../new_fields/List";
 import { ObjectField } from "../../../new_fields/ObjectField";
 import { RichTextField } from "../../../new_fields/RichTextField";
 import { ComputedField, ScriptField } from "../../../new_fields/ScriptField";
-import { NumCast, StrCast } from "../../../new_fields/Types";
+import { NumCast, StrCast, BoolCast } from "../../../new_fields/Types";
 import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../../Utils";
 import { Scripting } from "../../util/Scripting";
 import { ContextMenu } from "../ContextMenu";
@@ -29,7 +29,6 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
         this.props.Document.onChildClick = undefined;
     }
     componentDidMount() {
-        this.props.Document._freezeOnDrop = true;
         const childDetailed = this.props.Document.childDetailed; // bcz: needs to be here to make sure the childDetailed layout template has been loaded when the first item is clicked;
         const childText = "const alias = getAlias(this); Doc.ApplyTemplateTo(containingCollection.childDetailed, alias, 'layout_detailView'); alias.layoutKey='layout_detailedView'; alias.dropAction='alias'; alias.removeDropProperties=new List<string>(['dropAction']); useRightSplit(alias, shiftKey); ";
         this.props.Document.onChildClick = ScriptField.MakeScript(childText, { this: Doc.name, heading: "string", containingCollection: Doc.name, shiftKey: "boolean" });
@@ -73,7 +72,7 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
 
     @computed get contents() {
         return <div className="collectionTimeView-innards" key="timeline" style={{ width: "100%" }}>
-            <CollectionFreeFormView  {...this.props} layoutEngine={this.layoutEngine} />
+            <CollectionFreeFormView {...this.props} freezeDimensions={BoolCast(this.layoutDoc._freezeChildDimensions, true)} layoutEngine={this.layoutEngine} />
         </div>;
     }
 
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 77dd9a162..10da50e35 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -465,6 +465,8 @@ class TreeView extends React.Component<TreeViewProps> {
                         ContentScaling={returnOne}
                         PanelWidth={returnZero}
                         PanelHeight={returnZero}
+                        NativeHeight={returnZero}
+                        NativeWidth={returnZero}
                         renderDepth={1}
                         focus={emptyFunction}
                         parentActive={returnTrue}
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index a727da267..5d08a2bd8 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -13,7 +13,7 @@ import { List } from '../../../new_fields/List';
 import { BoolCast, Cast, NumCast, StrCast, ScriptCast } from '../../../new_fields/Types';
 import { ImageField } from '../../../new_fields/URLField';
 import { TraceMobx } from '../../../new_fields/util';
-import { Utils, setupMoveUpEvents, returnFalse } from '../../../Utils';
+import { Utils, setupMoveUpEvents, returnFalse, returnZero } from '../../../Utils';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { DocumentManager } from '../../util/DocumentManager';
 import { ImageUtils } from '../../util/Import & Export/ImageUtils';
@@ -424,6 +424,8 @@ export class CollectionView extends Touchable<FieldViewProps> {
                     <CollectionTreeView {...this.props}
                         CollectionView={this}
                         treeViewHideTitle={true}
+                        NativeHeight={returnZero}
+                        NativeWidth={returnZero}
                         treeViewHideHeaderFields={true}
                         onCheckedClick={this.scriptField!}
                         ignoreFields={this.treeIgnoreFields}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index d4e44b9a9..f22f1a304 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -89,8 +89,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     @computed get fitToContent() { return (this.props.fitToBox || this.Document._fitToBox) && !this.isAnnotationOverlay; }
     @computed get parentScaling() { return this.props.ContentScaling && this.fitToContent && !this.isAnnotationOverlay ? this.props.ContentScaling() : 1; }
     @computed get contentBounds() { return aggregateBounds(this._layoutElements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!), NumCast(this.layoutDoc.xPadding, 10), NumCast(this.layoutDoc.yPadding, 10)); }
-    @computed get nativeWidth() { return this.Document._fitToContent ? 0 : NumCast(this.Document._nativeWidth); }
-    @computed get nativeHeight() { return this.fitToContent ? 0 : NumCast(this.Document._nativeHeight); }
+    @computed get nativeWidth() { return this.Document._fitToContent ? 0 : NumCast(this.Document._nativeWidth, this.props.NativeWidth()); }
+    @computed get nativeHeight() { return this.fitToContent ? 0 : NumCast(this.Document._nativeHeight, this.props.NativeHeight()); }
     private get isAnnotationOverlay() { return this.props.isAnnotationOverlay; }
     private get borderWidth() { return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH; }
     private easing = () => this.props.Document.panTransformType === "Ease";
@@ -825,6 +825,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             DataDoc: childData,
             Document: childLayout,
             LibraryPath: this.libraryPath,
+            FreezeDimensions: this.props.freezeDimensions,
             layoutKey: undefined,
             rootSelected: this.rootSelected,
             dropAction: StrCast(this.props.Document.childDropAction) as dropActionType,
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index be0e1aec4..ff9630273 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -7,7 +7,7 @@ import { AudioField, nullAudio } from "../../../new_fields/URLField";
 import { DocExtendableComponent } from "../DocComponent";
 import { makeInterface, createSchema } from "../../../new_fields/Schema";
 import { documentSchema } from "../../../new_fields/documentSchemas";
-import { Utils, returnTrue, emptyFunction, returnOne, returnTransparent, returnFalse } from "../../../Utils";
+import { Utils, returnTrue, emptyFunction, returnOne, returnTransparent, returnFalse, returnZero } from "../../../Utils";
 import { runInAction, observable, reaction, IReactionDisposer, computed, action } from "mobx";
 import { DateField } from "../../../new_fields/DateField";
 import { SelectionManager } from "../../util/SelectionManager";
@@ -261,6 +261,8 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume
                                         <div className={this.props.PanelHeight() < 32 ? "audioBox-linker-mini" : "audioBox-linker"} key={"linker" + i}>
                                             <DocumentView {...this.props}
                                                 Document={l}
+                                                NativeHeight={returnZero}
+                                                NativeWidth={returnZero}
                                                 rootSelected={returnFalse}
                                                 layoutKey={Doc.LinkEndpoint(l, la2)}
                                                 ContainingCollectionDoc={this.props.Document}
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 356192797..8a4842427 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -41,10 +41,11 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         const hgt = this.renderScriptDim ? this.renderScriptDim.height : this.props.height !== undefined ? this.props.height : this.props.dataProvider && this.dataProvider ? this.dataProvider.height : this.layoutDoc[HeightSym]();
         return (hgt === undefined && this.nativeWidth && this.nativeHeight) ? this.width * this.nativeHeight / this.nativeWidth : hgt;
     }
+    @computed get freezeDimensions() { return this.props.FreezeDimensions || this.layoutDoc._freezeChildDimensions; }
     @computed get dataProvider() { return this.props.dataProvider && this.props.dataProvider(this.props.Document) ? this.props.dataProvider(this.props.Document) : undefined; }
-    @computed get nativeWidth() { return NumCast(this.layoutDoc._nativeWidth); }
-    @computed get nativeHeight() { return NumCast(this.layoutDoc._nativeHeight); }
-
+    @computed get nativeWidth() { return NumCast(this.layoutDoc._nativeWidth, this.props.NativeWidth() || (this.freezeDimensions ? this.layoutDoc[WidthSym]() : 0)); }
+    @computed get nativeHeight() { return NumCast(this.layoutDoc._nativeHeight, this.props.NativeHeight() || (this.freezeDimensions ? this.layoutDoc[HeightSym]() : 0)); }
+   
     @computed get renderScriptDim() {
         if (this.Document.renderScript) {
             const someView = Cast(this.props.Document.someView, Doc);
@@ -68,6 +69,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         .scale(1 / this.contentScaling())
 
     focusDoc = (doc: Doc) => this.props.focus(doc, false);
+    NativeWidth = () => this.nativeWidth;
+    NativeHeight = () => this.nativeHeight;
     render() {
         TraceMobx();
         return <div className="collectionFreeFormDocumentView-container"
@@ -89,22 +92,26 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                 pointerEvents: this.props.Document.isBackground ? "none" : undefined
             }} >
 
-            {!this.props.fitToBox ? 
-            <DocumentView {...this.props}
-                dragDivName={"collectionFreeFormDocumentView-container"}
-                ContentScaling={this.contentScaling}
-                ScreenToLocalTransform={this.getTransform}
-                backgroundColor={this.props.backgroundColor}
-                PanelWidth={this.panelWidth}
-                PanelHeight={this.panelHeight} /> 
-            : <ContentFittingDocumentView {...this.props}
-                CollectionDoc={this.props.ContainingCollectionDoc}
-                DataDocument={this.props.DataDoc}
-                getTransform={this.getTransform}
-                active={returnFalse}
-                focus={this.focusDoc}
-                PanelWidth={this.panelWidth}
-                PanelHeight={this.panelHeight}
+            {!this.props.fitToBox ?
+                <DocumentView {...this.props}
+                    dragDivName={"collectionFreeFormDocumentView-container"}
+                    ContentScaling={this.contentScaling}
+                    ScreenToLocalTransform={this.getTransform}
+                    backgroundColor={this.props.backgroundColor}
+                    NativeHeight={this.NativeHeight}
+                    NativeWidth={this.NativeWidth}
+                    PanelWidth={this.panelWidth}
+                    PanelHeight={this.panelHeight} />
+                : <ContentFittingDocumentView {...this.props}
+                    CollectionDoc={this.props.ContainingCollectionDoc}
+                    DataDocument={this.props.DataDoc}
+                    getTransform={this.getTransform}
+                    NativeHeight={this.NativeHeight}
+                    NativeWidth={this.NativeWidth}
+                    active={returnFalse}
+                    focus={this.focusDoc}
+                    PanelWidth={this.panelWidth}
+                    PanelHeight={this.panelHeight}
                 />}
         </div>;
     }
diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx
index 9ab6826a3..09675bf73 100644
--- a/src/client/views/nodes/ContentFittingDocumentView.tsx
+++ b/src/client/views/nodes/ContentFittingDocumentView.tsx
@@ -18,6 +18,9 @@ interface ContentFittingDocumentViewProps {
     Document?: Doc;
     DataDocument?: Doc;
     LayoutDoc?: () => Opt<Doc>;
+    NativeWidth?: () => number;
+    NativeHeight?: () => number;
+    FreezeDimensions?: boolean;
     LibraryPath: Doc[];
     childDocs?: Doc[];
     renderDepth: number;
@@ -47,12 +50,12 @@ interface ContentFittingDocumentViewProps {
 export class ContentFittingDocumentView extends React.Component<ContentFittingDocumentViewProps>{
     public get displayName() { return "DocumentView(" + this.props.Document?.title + ")"; } // this makes mobx trace() statements more descriptive
     private get layoutDoc() { return this.props.Document && (this.props.LayoutDoc?.() || Doc.Layout(this.props.Document)); }
-    private get nativeWidth() { return NumCast(this.layoutDoc?._nativeWidth, this.props.PanelWidth()); }
-    private get nativeHeight() { return NumCast(this.layoutDoc?._nativeHeight, this.props.PanelHeight()); }
+    private nativeWidth = () => NumCast(this.layoutDoc?._nativeWidth, this.props.NativeWidth?.() || this.props.PanelWidth());
+    private nativeHeight = () => NumCast(this.layoutDoc?._nativeHeight, this.props.NativeHeight?.() || this.props.PanelHeight());
     @computed get scaling() {
-        const wscale = this.props.PanelWidth() / (this.nativeWidth || this.props.PanelWidth() || 1);
-        if (wscale * this.nativeHeight > this.props.PanelHeight()) {
-            return (this.props.PanelHeight() / (this.nativeHeight || this.props.PanelHeight() || 1)) || 1;
+        const wscale = this.props.PanelWidth() / (this.nativeWidth() || this.props.PanelWidth() || 1);
+        if (wscale * this.nativeHeight() > this.props.PanelHeight()) {
+            return (this.props.PanelHeight() / (this.nativeHeight() || this.props.PanelHeight() || 1)) || 1;
         }
         return wscale || 1;
     }
@@ -61,12 +64,12 @@ export class ContentFittingDocumentView extends React.Component<ContentFittingDo
     private PanelWidth = () => this.panelWidth;
     private PanelHeight = () => this.panelHeight;
 
-    @computed get panelWidth() { return this.nativeWidth && (!this.props.Document || !this.props.Document._fitWidth) ? this.nativeWidth * this.contentScaling() : this.props.PanelWidth(); }
-    @computed get panelHeight() { return this.nativeHeight && (!this.props.Document || !this.props.Document._fitWidth) ? this.nativeHeight * this.contentScaling() : this.props.PanelHeight(); }
+    @computed get panelWidth() { return this.nativeWidth && (!this.props.Document || !this.props.Document._fitWidth) ? this.nativeWidth() * this.contentScaling() : this.props.PanelWidth(); }
+    @computed get panelHeight() { return this.nativeHeight && (!this.props.Document || !this.props.Document._fitWidth) ? this.nativeHeight() * this.contentScaling() : this.props.PanelHeight(); }
 
     private getTransform = () => this.props.getTransform().translate(-this.centeringOffset, -this.centeringYOffset).scale(1 / this.contentScaling());
-    private get centeringOffset() { return this.nativeWidth && (!this.props.Document || !this.props.Document._fitWidth) ? (this.props.PanelWidth() - this.nativeWidth * this.contentScaling()) / 2 : 0; }
-    private get centeringYOffset() { return Math.abs(this.centeringOffset) < 0.001 ? (this.props.PanelHeight() - this.nativeHeight * this.contentScaling()) / 2 : 0; }
+    private get centeringOffset() { return this.nativeWidth && (!this.props.Document || !this.props.Document._fitWidth) ? (this.props.PanelWidth() - this.nativeWidth() * this.contentScaling()) / 2 : 0; }
+    private get centeringYOffset() { return Math.abs(this.centeringOffset) < 0.001 ? (this.props.PanelHeight() - this.nativeHeight() * this.contentScaling()) / 2 : 0; }
 
     @computed get borderRounding() { return StrCast(this.props.Document?.borderRounding); }
 
@@ -81,7 +84,7 @@ export class ContentFittingDocumentView extends React.Component<ContentFittingDo
                     style={{
                         transform: `translate(${this.centeringOffset}px, 0px)`,
                         borderRadius: this.borderRounding,
-                        height: Math.abs(this.centeringYOffset) > 0.001 ? `${100 * this.nativeHeight / this.nativeWidth * this.props.PanelWidth() / this.props.PanelHeight()}%` : this.props.PanelHeight(),
+                        height: Math.abs(this.centeringYOffset) > 0.001 ? `${100 * this.nativeHeight() / this.nativeWidth() * this.props.PanelWidth() / this.props.PanelHeight()}%` : this.props.PanelHeight(),
                         width: Math.abs(this.centeringOffset) > 0.001 ? `${100 * (this.props.PanelWidth() - this.centeringOffset * 2) / this.props.PanelWidth()}%` : this.props.PanelWidth()
                     }}>
                     <DocumentView {...this.props}
@@ -89,6 +92,8 @@ export class ContentFittingDocumentView extends React.Component<ContentFittingDo
                         DataDoc={this.props.DataDocument}
                         LayoutDoc={this.props.LayoutDoc}
                         LibraryPath={this.props.LibraryPath}
+                        NativeWidth={this.nativeWidth}
+                        NativeHeight={this.nativeHeight}
                         fitToBox={this.props.fitToBox}
                         layoutKey={this.props.layoutKey}
                         dropAction={this.props.dropAction}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index aa3c1b3a2..9c9f2e48e 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -3,7 +3,7 @@ import * as fa from '@fortawesome/free-solid-svg-icons';
 import { action, computed, runInAction, trace, observable } from "mobx";
 import { observer } from "mobx-react";
 import * as rp from "request-promise";
-import { Doc, DocListCast, Opt } from "../../../new_fields/Doc";
+import { Doc, DocListCast, Opt, WidthSym, HeightSym } from "../../../new_fields/Doc";
 import { Document, PositionDocument } from '../../../new_fields/documentSchemas';
 import { Id } from '../../../new_fields/FieldSymbols';
 import { InkTool } from '../../../new_fields/InkField';
@@ -56,6 +56,9 @@ export type DocFocusFunc = () => boolean;
 export interface DocumentViewProps {
     ContainingCollectionView: Opt<CollectionView>;
     ContainingCollectionDoc: Opt<Doc>;
+    FreezeDimensions?: boolean;
+    NativeWidth: () => number;
+    NativeHeight: () => number;
     Document: Doc;
     DataDoc?: Doc;
     LayoutDoc?: () => Opt<Doc>;
@@ -109,11 +112,14 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     public get ContentDiv() { return this._mainCont.current; }
     @computed get active() { return SelectionManager.IsSelected(this, true) || this.props.parentActive(true); }
     @computed get topMost() { return this.props.renderDepth === 0; }
-    @computed get nativeWidth() { return this.layoutDoc._nativeWidth || 0; }
-    @computed get nativeHeight() { return this.layoutDoc._nativeHeight || 0; }
+    @computed get freezeDimensions() { return this.props.FreezeDimensions || this.layoutDoc._freezeChildDimensions; }
+    @computed get nativeWidth() { return NumCast(this.layoutDoc._nativeWidth, this.props.NativeWidth() || (this.freezeDimensions ? this.layoutDoc[WidthSym]() : 0)); }
+    @computed get nativeHeight() { return NumCast(this.layoutDoc._nativeHeight, this.props.NativeHeight() || (this.freezeDimensions ? this.layoutDoc[HeightSym]() : 0)); }
     @computed get onClickHandler() { return this.props.onClick || this.layoutDoc.onClick || this.Document.onClick; }
     @computed get onPointerDownHandler() { return this.props.onPointerDown ? this.props.onPointerDown : this.Document.onPointerDown; }
     @computed get onPointerUpHandler() { return this.props.onPointerUp ? this.props.onPointerUp : this.Document.onPointerUp; }
+    NativeWidth = () => this.nativeWidth;
+    NativeHeight = () => this.nativeHeight;
 
     private _firstX: number = -1;
     private _firstY: number = -1;
@@ -965,6 +971,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         TraceMobx();
         return (<DocumentContentsView ContainingCollectionView={this.props.ContainingCollectionView}
             ContainingCollectionDoc={this.props.ContainingCollectionDoc}
+            NativeHeight={this.NativeHeight}
+            NativeWidth={this.NativeWidth}
             Document={this.props.Document}
             DataDoc={this.props.DataDoc}
             LayoutDoc={this.props.LayoutDoc}
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 13a1becf7..dfcb03985 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -45,6 +45,8 @@ export interface FieldViewProps {
     focus: (doc: Doc) => void;
     PanelWidth: () => number;
     PanelHeight: () => number;
+    NativeHeight: () => number;
+    NativeWidth: () => number;
     setVideoBox?: (player: VideoBox) => void;
     ContentScaling: () => number;
     ChromeHeight?: () => number;
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 94195fbd6..37770a2e1 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -22,7 +22,7 @@ import { RichTextUtils } from '../../../new_fields/RichTextUtils';
 import { createSchema, makeInterface } from "../../../new_fields/Schema";
 import { Cast, NumCast, StrCast, BoolCast, DateCast } from "../../../new_fields/Types";
 import { TraceMobx } from '../../../new_fields/util';
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnOne, Utils, returnTrue } from '../../../Utils';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnOne, Utils, returnTrue, returnZero } from '../../../Utils';
 import { GoogleApiClientUtils, Pulls, Pushes } from '../../apis/google_docs/GoogleApiClientUtils';
 import { DocServer } from "../../DocServer";
 import { Docs, DocUtils } from '../../documents/Documents';
@@ -1216,6 +1216,8 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
                         <CollectionFreeFormView {...this.props}
                             PanelHeight={this.props.PanelHeight}
                             PanelWidth={this.sidebarWidth}
+                            NativeHeight={returnZero}
+                            NativeWidth={returnZero}
                             annotationsKey={this.annotationKey}
                             isAnnotationOverlay={false}
                             focus={this.props.focus}
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 00057055f..a4d3f3d79 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -14,7 +14,7 @@ import { ComputedField } from '../../../new_fields/ScriptField';
 import { Cast, NumCast, StrCast } from '../../../new_fields/Types';
 import { AudioField, ImageField } from '../../../new_fields/URLField';
 import { TraceMobx } from '../../../new_fields/util';
-import { emptyFunction, returnOne, Utils } from '../../../Utils';
+import { emptyFunction, returnOne, Utils, returnZero } from '../../../Utils';
 import { CognitiveServices, Confidence, Service, Tag } from '../../cognitive_services/CognitiveServices';
 import { Docs } from '../../documents/Documents';
 import { Networking } from '../../Network';
@@ -445,6 +445,8 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
             <CollectionFreeFormView {...this.props}
                 PanelHeight={this.props.PanelHeight}
                 PanelWidth={this.props.PanelWidth}
+                NativeHeight={returnZero}
+                NativeWidth={returnZero}
                 annotationsKey={this.annotationKey}
                 isAnnotationOverlay={true}
                 focus={this.props.focus}
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index ef039c857..6dc4ae578 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -62,13 +62,15 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
             rootSelected: returnFalse,
             isSelected: returnFalse,
             select: emptyFunction,
-            dropAction:"alias",
-            bringToFront:emptyFunction,
+            dropAction: "alias",
+            bringToFront: emptyFunction,
             renderDepth: 1,
             active: returnFalse,
             whenActiveChanged: emptyFunction,
             ScreenToLocalTransform: Transform.Identity,
             focus: emptyFunction,
+            NativeHeight: returnZero,
+            NativeWidth: returnZero,
             PanelWidth: this.props.PanelWidth,
             PanelHeight: this.props.PanelHeight,
             addDocTab: returnFalse,
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index 0e327e130..542c86049 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -23,6 +23,8 @@ export class LinkBox extends DocExtendableComponent<FieldViewProps, LinkDocument
             <CollectionTreeView {...this.props}
                 ChromeHeight={returnZero}
                 overrideDocuments={[this.dataDoc]}
+                NativeHeight={returnZero}
+                NativeWidth={returnZero}
                 ignoreFields={Cast(this.props.Document.linkBoxExcludedKeys, listSpec("string"), null)}
                 annotationsKey={""}
                 CollectionView={undefined}
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index 7c58a5148..58ff4971a 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -10,7 +10,7 @@ import { makeInterface } from "../../../new_fields/Schema";
 import { ScriptField } from "../../../new_fields/ScriptField";
 import { Cast, StrCast } from "../../../new_fields/Types";
 import { VideoField } from "../../../new_fields/URLField";
-import { emptyFunction, returnFalse, returnOne, Utils } from "../../../Utils";
+import { emptyFunction, returnFalse, returnOne, Utils, returnZero } from "../../../Utils";
 import { Docs, DocUtils } from "../../documents/Documents";
 import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
 import { ContextMenu } from "../ContextMenu";
@@ -170,6 +170,8 @@ export class ScreenshotBox extends DocAnnotatableComponent<FieldViewProps, Scree
                 <CollectionFreeFormView {...this.props}
                     PanelHeight={this.props.PanelHeight}
                     PanelWidth={this.props.PanelWidth}
+                    NativeHeight={returnZero}
+                    NativeWidth={returnZero}
                     annotationsKey={this.annotationKey}
                     focus={this.props.focus}
                     isSelected={this.props.isSelected}
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 24b66d8f7..d384ad12f 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -11,7 +11,7 @@ import { createSchema, makeInterface } from "../../../new_fields/Schema";
 import { ScriptField } from "../../../new_fields/ScriptField";
 import { Cast, StrCast, NumCast } from "../../../new_fields/Types";
 import { VideoField } from "../../../new_fields/URLField";
-import { Utils, emptyFunction, returnOne } from "../../../Utils";
+import { Utils, emptyFunction, returnOne, returnZero } from "../../../Utils";
 import { Docs, DocUtils } from "../../documents/Documents";
 import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
 import { ContextMenu } from "../ContextMenu";
@@ -352,6 +352,8 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
                 <CollectionFreeFormView {...this.props}
                     PanelHeight={this.props.PanelHeight}
                     PanelWidth={this.props.PanelWidth}
+                    NativeHeight={returnZero}
+                    NativeWidth={returnZero}
                     annotationsKey={this.annotationKey}
                     focus={this.props.focus}
                     isSelected={this.props.isSelected}
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 838fbefb1..ea5d601ec 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -9,7 +9,7 @@ import { InkTool } from "../../../new_fields/InkField";
 import { makeInterface } from "../../../new_fields/Schema";
 import { Cast, NumCast } from "../../../new_fields/Types";
 import { WebField } from "../../../new_fields/URLField";
-import { Utils, returnOne, emptyFunction } from "../../../Utils";
+import { Utils, returnOne, emptyFunction, returnZero } from "../../../Utils";
 import { Docs } from "../../documents/Documents";
 import { DragManager } from "../../util/DragManager";
 import { ImageUtils } from "../../util/Import & Export/ImageUtils";
@@ -335,6 +335,8 @@ export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument>
                 PanelHeight={this.props.PanelHeight}
                 PanelWidth={this.props.PanelWidth}
                 annotationsKey={this.annotationKey}
+                NativeHeight={returnZero}
+                NativeWidth={returnZero}
                 focus={this.props.focus}
                 isSelected={this.props.isSelected}
                 isAnnotationOverlay={true}
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 25ceb6218..36e687f71 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -9,7 +9,7 @@ import { List } from "../../../new_fields/List";
 import { makeInterface, createSchema } from "../../../new_fields/Schema";
 import { ScriptField, ComputedField } from "../../../new_fields/ScriptField";
 import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
-import { smoothScroll, Utils, emptyFunction, returnOne, intersectRect, addStyleSheet, addStyleSheetRule, clearStyleSheetRules } from "../../../Utils";
+import { smoothScroll, Utils, emptyFunction, returnOne, intersectRect, addStyleSheet, addStyleSheetRule, clearStyleSheetRules, returnZero } from "../../../Utils";
 import { Docs, DocUtils } from "../../documents/Documents";
 import { DragManager } from "../../util/DragManager";
 import { CompiledScript, CompileScript } from "../../util/Scripting";
@@ -648,6 +648,8 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
                 setPreviewCursor={this.setPreviewCursor}
                 PanelHeight={this.panelWidth}
                 PanelWidth={this.panelHeight}
+                NativeHeight={returnZero}
+                NativeWidth={returnZero}
                 dropAction={"alias"}
                 VisibleHeight={this.visibleHeight}
                 focus={this.props.focus}
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index 5f766c121..c87ac719c 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -7,7 +7,7 @@ import { Doc, DocListCast } from '../new_fields/Doc';
 import { Docs } from '../client/documents/Documents';
 import { CollectionView } from '../client/views/collections/CollectionView';
 import { DocumentView } from '../client/views/nodes/DocumentView';
-import { emptyPath, emptyFunction, returnFalse, returnOne, returnEmptyString, returnTrue } from '../Utils';
+import { emptyPath, emptyFunction, returnFalse, returnOne, returnEmptyString, returnTrue, returnZero } from '../Utils';
 import { Transform } from '../client/util/Transform';
 import { library } from '@fortawesome/fontawesome-svg-core';
 import { faPenNib, faHighlighter, faEraser, faMousePointer, faBreadSlice, faTrash, faCheck, faLongArrowAltLeft } from '@fortawesome/free-solid-svg-icons';
@@ -129,6 +129,8 @@ export default class MobileInterface extends React.Component {
                 onClick={undefined}
                 ScreenToLocalTransform={Transform.Identity}
                 ContentScaling={returnOne}
+                NativeHeight={returnZero}
+                NativeWidth={returnZero}
                 PanelWidth={() => window.screen.width}
                 PanelHeight={() => window.screen.height}
                 renderDepth={0}
@@ -173,6 +175,8 @@ export default class MobileInterface extends React.Component {
         e.stopPropagation();
     }
 
+    panelHeight = () => window.innerHeight;
+    panelWidth = () => window.innerWidth;
     renderInkingContent = () => {
         console.log("rendering inking content");
         // TODO: support panning and zooming
@@ -201,8 +205,10 @@ export default class MobileInterface extends React.Component {
                         bringToFront={emptyFunction}
                         addDocTab={returnFalse}
                         pinToPres={emptyFunction}
-                        PanelHeight={() => window.innerHeight}
-                        PanelWidth={() => window.innerWidth}
+                        PanelWidth={this.panelWidth}
+                        PanelHeight={this.panelHeight}
+                        NativeHeight={returnZero}
+                        NativeWidth={returnZero}
                         focus={emptyFunction}
                         isSelected={returnFalse}
                         select={emptyFunction}
@@ -289,6 +295,8 @@ export default class MobileInterface extends React.Component {
                         onClick={undefined}
                         ScreenToLocalTransform={Transform.Identity}
                         ContentScaling={returnOne}
+                        NativeHeight={returnZero}
+                        NativeWidth={returnZero}
                         PanelWidth={() => window.screen.width}
                         PanelHeight={() => window.screen.height}
                         renderDepth={0}
diff --git a/src/new_fields/documentSchemas.ts b/src/new_fields/documentSchemas.ts
index 1a5a471a0..ff02596f7 100644
--- a/src/new_fields/documentSchemas.ts
+++ b/src/new_fields/documentSchemas.ts
@@ -28,7 +28,7 @@ export const documentSchema = createSchema({
     _pivotField: "string",      // specifies which field should be used as the timeline/pivot axis
     _replacedChrome: "string",  // what the default chrome is replaced with. Currently only supports the value of 'replaced' for PresBox's.
     _chromeStatus: "string",    // determines the state of the collection chrome. values allowed are 'replaced', 'enabled', 'disabled', 'collapsed'
-    _freezeOnDrop: "boolean",    // whether a document without native dimensions should have its width/height frozen as native dimensions on drop.  Used by Timeline view to make sure documents are scaled to fit the display thumbnail
+    _freezeChildDimensions: "boolean", // freezes child document dimensions (e.g., used by time/pivot view to make sure all children will be scaled to fit their display rectangle)
     color: "string",            // foreground color of document
     backgroundColor: "string",  // background color of document
     opacity: "number",          // opacity of document
diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts
index 8c719ccd8..480a55da0 100644
--- a/src/new_fields/util.ts
+++ b/src/new_fields/util.ts
@@ -12,7 +12,7 @@ function _readOnlySetter(): never {
     throw new Error("Documents can't be modified in read-only mode");
 }
 
-const tracing = false;
+const tracing = true;
 export function TraceMobx() {
     tracing && trace();
 }
-- 
cgit v1.2.3-70-g09d2


From 881a522e1562f948202fcdb6a5a87485bf778597 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sun, 5 Apr 2020 18:30:46 -0400
Subject: more fixes to NativeWidth/Height & FreezeDimensions props stuff

---
 .../views/collections/CollectionSchemaView.tsx      | 11 +++++++----
 .../views/collections/CollectionStackingView.tsx    |  1 -
 .../collectionFreeForm/CollectionFreeFormView.tsx   |  8 ++++++--
 .../views/nodes/CollectionFreeFormDocumentView.tsx  |  6 +++---
 .../views/nodes/ContentFittingDocumentView.tsx      |  1 -
 src/client/views/nodes/DocumentContentsView.tsx     | 10 ----------
 src/client/views/nodes/DocumentView.tsx             | 21 +++++++++++----------
 src/client/views/nodes/ImageBox.tsx                 |  6 +++---
 8 files changed, 30 insertions(+), 34 deletions(-)

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

diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index a1b541f74..df9f65a21 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -28,7 +28,8 @@ import "./CollectionSchemaView.scss";
 import { CollectionSubView } from "./CollectionSubView";
 import { CollectionView } from "./CollectionView";
 import { ContentFittingDocumentView } from "../nodes/ContentFittingDocumentView";
-import { setupMoveUpEvents, emptyFunction } from "../../../Utils";
+import { setupMoveUpEvents, emptyFunction, returnZero, returnOne } from "../../../Utils";
+import { DocumentView } from "../nodes/DocumentView";
 
 library.add(faCog, faPlus, faSortUp, faSortDown);
 library.add(faTable);
@@ -117,12 +118,14 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
 
     @computed
     get previewPanel() {
-        return <div ref={this.createTarget}>
+        return !this.previewDocument ? (null) : <div ref={this.createTarget} style={{ width: `${this.previewWidth()}px` }}>
             <ContentFittingDocumentView
                 Document={this.previewDocument}
                 DataDocument={undefined}
+                NativeHeight={returnZero}
+                NativeWidth={returnZero}
+                focus={emptyFunction}
                 LibraryPath={this.props.LibraryPath}
-                childDocs={this.childDocs}
                 renderDepth={this.props.renderDepth}
                 rootSelected={this.rootSelected}
                 PanelWidth={this.previewWidth}
@@ -180,7 +183,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
 
     render() {
         return <div className="collectionSchemaView-container">
-            <div className="collectionSchemaView-tableContainer" onPointerDown={this.onPointerDown} onWheel={e => this.props.active(true) && e.stopPropagation()} onDrop={e => this.onExternalDrop(e, {})} ref={this.createTarget}>
+            <div className="collectionSchemaView-tableContainer" style={{ width: `calc(100% - ${this.previewWidth()}px)` }} onPointerDown={this.onPointerDown} onWheel={e => this.props.active(true) && e.stopPropagation()} onDrop={e => this.onExternalDrop(e, {})} ref={this.createTarget}>
                 {this.schemaTable}
             </div>
             {this.dividerDragger}
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index b11baeb7a..8ceeb66f1 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -47,7 +47,6 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     @computed get numGroupColumns() { return this.isStackingView ? Math.max(1, this.Sections.size + (this.showAddAGroup ? 1 : 0)) : 1; }
     @computed get showAddAGroup() { return (this.pivotField && (this.props.Document._chromeStatus !== 'view-mode' && this.props.Document._chromeStatus !== 'disabled')); }
     @computed get columnWidth() {
-        TraceMobx();
         return Math.min(this.props.PanelWidth() / (this.props as any).ContentScaling() - 2 * this.xMargin,
             this.isStackingView ? Number.MAX_VALUE : NumCast(this.props.Document.columnWidth, 250));
     }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index f22f1a304..ceec9dfcc 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -952,11 +952,15 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         const elements: ViewDefResult[] = computedElementData.slice();
         this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).forEach(pair =>
             elements.push({
-                ele: <CollectionFreeFormDocumentView key={pair.layout[Id]}  {...this.getChildDocumentViewProps(pair.layout, pair.data)}
+                ele: <CollectionFreeFormDocumentView
+                    key={pair.layout[Id]}
+                    {...this.getChildDocumentViewProps(pair.layout, pair.data)}
                     dataProvider={this.childDataProvider}
                     LayoutDoc={this.childLayoutDocFunc}
                     jitterRotation={NumCast(this.props.Document.jitterRotation)}
-                    fitToBox={this.props.fitToBox || this.props.layoutEngine !== undefined} />,
+                    fitToBox={this.props.fitToBox || this.props.layoutEngine !== undefined}
+                    FreezeDimensions={this.props.layoutEngine !== undefined}
+                />,
                 bounds: this.childDataProvider(pair.layout)
             }));
 
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 8a4842427..e4c8bbd7b 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -41,11 +41,11 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         const hgt = this.renderScriptDim ? this.renderScriptDim.height : this.props.height !== undefined ? this.props.height : this.props.dataProvider && this.dataProvider ? this.dataProvider.height : this.layoutDoc[HeightSym]();
         return (hgt === undefined && this.nativeWidth && this.nativeHeight) ? this.width * this.nativeHeight / this.nativeWidth : hgt;
     }
-    @computed get freezeDimensions() { return this.props.FreezeDimensions || this.layoutDoc._freezeChildDimensions; }
+    @computed get freezeDimensions() { return this.props.FreezeDimensions; }
     @computed get dataProvider() { return this.props.dataProvider && this.props.dataProvider(this.props.Document) ? this.props.dataProvider(this.props.Document) : undefined; }
     @computed get nativeWidth() { return NumCast(this.layoutDoc._nativeWidth, this.props.NativeWidth() || (this.freezeDimensions ? this.layoutDoc[WidthSym]() : 0)); }
     @computed get nativeHeight() { return NumCast(this.layoutDoc._nativeHeight, this.props.NativeHeight() || (this.freezeDimensions ? this.layoutDoc[HeightSym]() : 0)); }
-   
+
     @computed get renderScriptDim() {
         if (this.Document.renderScript) {
             const someView = Cast(this.props.Document.someView, Doc);
@@ -61,7 +61,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         return undefined;
     }
 
-    contentScaling = () => this.nativeWidth > 0 && !this.props.fitToBox ? this.width / this.nativeWidth : 1;
+    contentScaling = () => this.nativeWidth > 0 && !this.props.fitToBox && !this.freezeDimensions ? this.width / this.nativeWidth : 1;
     panelWidth = () => (this.dataProvider?.width || this.props.PanelWidth());
     panelHeight = () => (this.dataProvider?.height || this.props.PanelHeight());
     getTransform = (): Transform => this.props.ScreenToLocalTransform()
diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx
index 09675bf73..52e5ed1dd 100644
--- a/src/client/views/nodes/ContentFittingDocumentView.tsx
+++ b/src/client/views/nodes/ContentFittingDocumentView.tsx
@@ -22,7 +22,6 @@ interface ContentFittingDocumentViewProps {
     NativeHeight?: () => number;
     FreezeDimensions?: boolean;
     LibraryPath: Doc[];
-    childDocs?: Doc[];
     renderDepth: number;
     fitToBox?: boolean;
     layoutKey?: string;
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 4b6729fe7..dc71ba280 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -63,16 +63,6 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
     hideOnLeave?: boolean,
     makeLink?: () => Opt<Doc>,  // function to call when a link is made
 }> {
-    constructor(props: any) {
-        super(props);
-        console.log("Consructr" + this.props.Document.title);
-    }
-    componentWillUpdate() {
-        console.log("WillUpdate" + this.props.Document.title);
-    }
-    componentWillReceiveProps() {
-        console.log("Receive" + this.props.Document.title);
-    }
     @computed get layout(): string {
         TraceMobx();
         if (!this.layoutDoc) return "<p>awaiting layout</p>";
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index eceda4a18..d800579a1 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -112,11 +112,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     public get ContentDiv() { return this._mainCont.current; }
     @computed get active() { return SelectionManager.IsSelected(this, true) || this.props.parentActive(true); }
     @computed get topMost() { return this.props.renderDepth === 0; }
-    @computed get freezeDimensions() { return this.props.FreezeDimensions || this.layoutDoc._freezeChildDimensions; }
-    @computed get nativeWidth() { return this.layoutDoc._nativeWidth || 0; }
-    @computed get nativeHeight() { return this.layoutDoc._nativeHeight || 0; }
-    // @computed get nativeWidth() { return NumCast(this.layoutDoc._nativeWidth, this.props.NativeWidth() || (this.freezeDimensions ? this.layoutDoc[WidthSym]() : 0)); }
-    // @computed get nativeHeight() { return NumCast(this.layoutDoc._nativeHeight, this.props.NativeHeight() || (this.freezeDimensions ? this.layoutDoc[HeightSym]() : 0)); }
+    @computed get freezeDimensions() { return this.props.FreezeDimensions; }
+    @computed get nativeWidth() { return NumCast(this.layoutDoc._nativeWidth, this.props.NativeWidth() || (this.freezeDimensions ? this.layoutDoc[WidthSym]() : 0)); }
+    @computed get nativeHeight() { return NumCast(this.layoutDoc._nativeHeight, this.props.NativeHeight() || (this.freezeDimensions ? this.layoutDoc[HeightSym]() : 0)); }
     @computed get onClickHandler() { return this.props.onClick || this.layoutDoc.onClick || this.Document.onClick; }
     @computed get onPointerDownHandler() { return this.props.onPointerDown ? this.props.onPointerDown : this.Document.onPointerDown; }
     @computed get onPointerUpHandler() { return this.props.onPointerUp ? this.props.onPointerUp : this.Document.onPointerUp; }
@@ -969,12 +967,15 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         return this.isSelected(false) || (this.props.Document.forceActive && this.props.rootSelected?.() ? true : false);
     }
     childScaling = () => (this.layoutDoc._fitWidth ? this.props.PanelWidth() / this.nativeWidth : this.props.ContentScaling());
+    panelWidth = () => this.props.PanelWidth();
+    panelHeight = () => this.props.PanelHeight();
+    screenToLocalTransform = () => this.props.ScreenToLocalTransform();
     @computed get contents() {
         TraceMobx();
         return (<DocumentContentsView ContainingCollectionView={this.props.ContainingCollectionView}
             ContainingCollectionDoc={this.props.ContainingCollectionDoc}
-            NativeHeight={returnZero}
-            NativeWidth={returnZero}
+            NativeWidth={this.NativeWidth}
+            NativeHeight={this.NativeHeight}
             Document={this.props.Document}
             DataDoc={this.props.DataDoc}
             LayoutDoc={this.props.LayoutDoc}
@@ -985,10 +986,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             addDocument={this.props.addDocument}
             removeDocument={this.props.removeDocument}
             moveDocument={this.props.moveDocument}
-            ScreenToLocalTransform={this.props.ScreenToLocalTransform}
+            ScreenToLocalTransform={this.screenToLocalTransform}
             renderDepth={this.props.renderDepth}
-            PanelWidth={this.props.PanelWidth}
-            PanelHeight={this.props.PanelHeight}
+            PanelWidth={this.panelWidth}
+            PanelHeight={this.panelHeight}
             focus={this.props.focus}
             parentActive={this.props.parentActive}
             whenActiveChanged={this.props.whenActiveChanged}
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index a4d3f3d79..052251d94 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -436,9 +436,9 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
         const dragging = !SelectionManager.GetIsDragging() ? "" : "-dragging";
         return (<div className={`imageBox${dragging}`} onContextMenu={this.specificContextMenu}
             style={{
-                transform: `scale(${this.props.ContentScaling()})`,
-                width: `${100 / this.props.ContentScaling()}%`,
-                height: `${100 / this.props.ContentScaling()}%`,
+                transform: this.props.PanelWidth() ? undefined : `scale(${this.props.ContentScaling()})`,
+                width: this.props.PanelWidth() ? `${this.props.PanelWidth()}px` : `${100 / this.props.ContentScaling()}%`,
+                height: this.props.PanelWidth() ? undefined : `${100 / this.props.ContentScaling()}%`,
                 pointerEvents: this.props.Document.isBackground ? "none" : undefined,
                 borderRadius: `${Number(StrCast(this.layoutDoc.borderRounding).replace("px", "")) / this.props.ContentScaling()}px`
             }} >
-- 
cgit v1.2.3-70-g09d2


From bc23e69a88234c213e444b1aa3c5eb895c35aca9 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 6 Apr 2020 00:23:19 -0400
Subject: many more fixes to nativewidth/height layout changes.

---
 .../views/collections/CollectionSchemaView.tsx     | 51 ++++++++++++----------
 .../views/collections/CollectionStackingView.tsx   | 14 +++---
 src/client/views/collections/CollectionSubView.tsx |  2 +-
 .../views/collections/CollectionTimeView.tsx       |  2 +-
 .../views/collections/CollectionTreeView.tsx       |  7 +--
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 19 +++++---
 .../CollectionMulticolumnView.tsx                  |  5 +++
 .../CollectionMultirowView.tsx                     |  6 ++-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  3 +-
 .../views/nodes/ContentFittingDocumentView.tsx     | 17 ++++----
 src/client/views/nodes/DocumentView.tsx            |  4 +-
 src/client/views/nodes/FormattedTextBox.scss       |  9 ++--
 src/client/views/nodes/ImageBox.tsx                |  4 +-
 src/new_fields/util.ts                             |  2 +-
 14 files changed, 82 insertions(+), 63 deletions(-)

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

diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index df9f65a21..e835811c9 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -12,7 +12,7 @@ import { List } from "../../../new_fields/List";
 import { listSpec } from "../../../new_fields/Schema";
 import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
 import { ComputedField } from "../../../new_fields/ScriptField";
-import { Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types";
+import { Cast, FieldValue, NumCast, StrCast, BoolCast } from "../../../new_fields/Types";
 import { Docs, DocumentOptions } from "../../documents/Documents";
 import { Gateway } from "../../northstar/manager/Gateway";
 import { CompileScript, Transformer, ts } from "../../util/Scripting";
@@ -118,29 +118,32 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
 
     @computed
     get previewPanel() {
-        return !this.previewDocument ? (null) : <div ref={this.createTarget} style={{ width: `${this.previewWidth()}px` }}>
-            <ContentFittingDocumentView
-                Document={this.previewDocument}
-                DataDocument={undefined}
-                NativeHeight={returnZero}
-                NativeWidth={returnZero}
-                focus={emptyFunction}
-                LibraryPath={this.props.LibraryPath}
-                renderDepth={this.props.renderDepth}
-                rootSelected={this.rootSelected}
-                PanelWidth={this.previewWidth}
-                PanelHeight={this.previewHeight}
-                getTransform={this.getPreviewTransform}
-                CollectionDoc={this.props.CollectionView && this.props.CollectionView.props.Document}
-                CollectionView={this.props.CollectionView}
-                moveDocument={this.props.moveDocument}
-                addDocument={this.props.addDocument}
-                removeDocument={this.props.removeDocument}
-                active={this.props.active}
-                whenActiveChanged={this.props.whenActiveChanged}
-                addDocTab={this.props.addDocTab}
-                pinToPres={this.props.pinToPres}
-            />
+        return <div ref={this.createTarget} style={{ width: `${this.previewWidth()}px` }}>
+            {!this.previewDocument ? (null) :
+                <ContentFittingDocumentView
+                    Document={this.previewDocument}
+                    DataDocument={undefined}
+                    NativeHeight={returnZero}
+                    NativeWidth={returnZero}
+                    fitToBox={true}
+                    FreezeDimensions={true}
+                    focus={emptyFunction}
+                    LibraryPath={this.props.LibraryPath}
+                    renderDepth={this.props.renderDepth}
+                    rootSelected={this.rootSelected}
+                    PanelWidth={this.previewWidth}
+                    PanelHeight={this.previewHeight}
+                    getTransform={this.getPreviewTransform}
+                    CollectionDoc={this.props.CollectionView?.props.Document}
+                    CollectionView={this.props.CollectionView}
+                    moveDocument={this.props.moveDocument}
+                    addDocument={this.props.addDocument}
+                    removeDocument={this.props.removeDocument}
+                    active={this.props.active}
+                    whenActiveChanged={this.props.whenActiveChanged}
+                    addDocTab={this.props.addDocTab}
+                    pinToPres={this.props.pinToPres}
+                />}
         </div>;
     }
 
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 8ceeb66f1..d17b1ee48 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -116,7 +116,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     componentDidMount() {
         super.componentDidMount();
         this._heightDisposer = reaction(() => {
-            if (this.props.Document._autoHeight && !this.props.NativeHeight()) {
+            if (this.props.Document._autoHeight && !this.layoutDoc._nativeHeight) {
                 const sectionsList = Array.from(this.Sections.size ? this.Sections.values() : [this.filteredChildren]);
                 if (this.isStackingView) {
                     const res = this.props.ContentScaling() * sectionsList.reduce((maxHght, s) => {
@@ -175,17 +175,19 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
             backgroundColor={this.props.backgroundColor}
             LayoutDoc={this.props.childLayoutTemplate}
             LibraryPath={this.props.LibraryPath}
-            FreezeDimensions={this.props.freezeDimensions}
+            FreezeDimensions={this.props.freezeChildDimensions}
             renderDepth={this.props.renderDepth + 1}
-            fitToBox={this.props.fitToBox}
+            PanelWidth={width}
+            PanelHeight={height}
+            NativeHeight={returnZero}
+            NativeWidth={returnZero}
+            fitToBox={BoolCast(this.props.Document._freezeChildDimensions)}
             rootSelected={this.rootSelected}
             dropAction={StrCast(this.props.Document.childDropAction) as dropActionType}
             onClick={layoutDoc.isTemplateDoc ? this.onClickHandler : this.onChildClickHandler}
-            PanelWidth={width}
-            PanelHeight={height}
             ScreenToLocalTransform={dxf}
             focus={this.props.focus}
-            ContainingCollectionDoc={this.props.CollectionView && this.props.CollectionView.props.Document}
+            ContainingCollectionDoc={this.props.CollectionView?.props.Document}
             ContainingCollectionView={this.props.CollectionView}
             addDocument={this.props.addDocument}
             moveDocument={this.props.moveDocument}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 32449de5e..a46bc67a2 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -43,7 +43,7 @@ export interface CollectionViewProps extends FieldViewProps {
 export interface SubCollectionViewProps extends CollectionViewProps {
     CollectionView: Opt<CollectionView>;
     children?: never | (() => JSX.Element[]) | React.ReactNode;
-    freezeDimensions?: boolean; // used by TimeView to coerce documents to treat their width height as their native width/height
+    freezeChildDimensions?: boolean; // used by TimeView to coerce documents to treat their width height as their native width/height
     overrideDocuments?: Doc[]; // used to override the documents shown by the sub collection to an explicit list (see LinkBox)
     ignoreFields?: string[]; // used in TreeView to ignore specified fields (see LinkBox)
     isAnnotationOverlay?: boolean;
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index d9a10cdc8..4b005ba42 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -72,7 +72,7 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
 
     @computed get contents() {
         return <div className="collectionTimeView-innards" key="timeline" style={{ width: "100%" }}>
-            <CollectionFreeFormView {...this.props} freezeDimensions={BoolCast(this.layoutDoc._freezeChildDimensions, true)} layoutEngine={this.layoutEngine} />
+            <CollectionFreeFormView {...this.props} freezeChildDimensions={BoolCast(this.layoutDoc._freezeChildDimensions, true)} layoutEngine={this.layoutEngine} />
         </div>;
     }
 
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 10da50e35..eff4758cf 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -103,7 +103,7 @@ class TreeView extends React.Component<TreeViewProps> {
     set treeViewOpen(c: boolean) { if (this.props.treeViewPreventOpen) this._overrideTreeViewOpen = c; else this.props.document.treeViewOpen = this._overrideTreeViewOpen = c; }
     @computed get treeViewOpen() { return (!this.props.treeViewPreventOpen && BoolCast(this.props.document.treeViewOpen)) || this._overrideTreeViewOpen; }
     @computed get treeViewExpandedView() { return StrCast(this.props.document.treeViewExpandedView, this.defaultExpandedView); }
-    @computed get MAX_EMBED_HEIGHT() { return NumCast(this.props.document.maxEmbedHeight, 300); }
+    @computed get MAX_EMBED_HEIGHT() { return NumCast(this.props.containingCollection.maxEmbedHeight, 200); }
     @computed get dataDoc() { return this.templateDataDoc ? this.templateDataDoc : this.props.document; }
     @computed get fieldKey() {
         const splits = StrCast(Doc.LayoutField(this.props.document)).split("fieldKey={\'");
@@ -280,7 +280,7 @@ class TreeView extends React.Component<TreeViewProps> {
     }
     docWidth = () => {
         const layoutDoc = Doc.Layout(this.props.document);
-        const aspect = NumCast(layoutDoc._nativeHeight) / NumCast(layoutDoc._nativeWidth);
+        const aspect = NumCast(layoutDoc._nativeHeight, layoutDoc._fitWidth ? 0 : layoutDoc[HeightSym]()) / NumCast(layoutDoc._nativeWidth, layoutDoc._fitWidth ? 1 : layoutDoc[WidthSym]());
         if (aspect) return Math.min(layoutDoc[WidthSym](), Math.min(this.MAX_EMBED_HEIGHT / aspect, this.props.panelWidth() - 20));
         return NumCast(layoutDoc._nativeWidth) ? Math.min(layoutDoc[WidthSym](), this.props.panelWidth() - 20) : this.props.panelWidth() - 20;
     }
@@ -288,7 +288,7 @@ class TreeView extends React.Component<TreeViewProps> {
         const layoutDoc = Doc.Layout(this.props.document);
         const bounds = this.boundsOfCollectionDocument;
         return Math.min(this.MAX_EMBED_HEIGHT, (() => {
-            const aspect = NumCast(layoutDoc._nativeHeight) / NumCast(layoutDoc._nativeWidth, 1);
+            const aspect = NumCast(layoutDoc._nativeHeight, layoutDoc._fitWidth ? 0 : layoutDoc[HeightSym]()) / NumCast(layoutDoc._nativeWidth, layoutDoc._fitWidth ? 1 : layoutDoc[WidthSym]());
             if (aspect) return this.docWidth() * aspect;
             if (bounds) return this.docWidth() * (bounds.b - bounds.y) / (bounds.r - bounds.x);
             return layoutDoc._fitWidth ? (!this.props.document.nativeHeight ? NumCast(this.props.containingCollection._height) :
@@ -376,6 +376,7 @@ class TreeView extends React.Component<TreeViewProps> {
                     rootSelected={returnTrue}
                     backgroundColor={this.props.backgroundColor}
                     fitToBox={this.boundsOfCollectionDocument !== undefined}
+                    FreezeDimensions={true}
                     PanelWidth={this.docWidth}
                     PanelHeight={this.docHeight}
                     getTransform={this.docTransform}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index ceec9dfcc..22dd2d831 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -15,7 +15,7 @@ import { ScriptField } from "../../../../new_fields/ScriptField";
 import { BoolCast, Cast, FieldValue, NumCast, ScriptCast, StrCast } from "../../../../new_fields/Types";
 import { TraceMobx } from "../../../../new_fields/util";
 import { GestureUtils } from "../../../../pen-gestures/GestureUtils";
-import { aggregateBounds, intersectRect, returnOne, Utils } from "../../../../Utils";
+import { aggregateBounds, intersectRect, returnOne, Utils, returnZero } from "../../../../Utils";
 import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
 import { DocServer } from "../../../DocServer";
 import { Docs } from "../../../documents/Documents";
@@ -89,7 +89,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     @computed get fitToContent() { return (this.props.fitToBox || this.Document._fitToBox) && !this.isAnnotationOverlay; }
     @computed get parentScaling() { return this.props.ContentScaling && this.fitToContent && !this.isAnnotationOverlay ? this.props.ContentScaling() : 1; }
     @computed get contentBounds() { return aggregateBounds(this._layoutElements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!), NumCast(this.layoutDoc.xPadding, 10), NumCast(this.layoutDoc.yPadding, 10)); }
-    @computed get nativeWidth() { return this.Document._fitToContent ? 0 : NumCast(this.Document._nativeWidth, this.props.NativeWidth()); }
+    @computed get nativeWidth() { return this.fitToContent ? 0 : NumCast(this.Document._nativeWidth, this.props.NativeWidth()); }
     @computed get nativeHeight() { return this.fitToContent ? 0 : NumCast(this.Document._nativeHeight, this.props.NativeHeight()); }
     private get isAnnotationOverlay() { return this.props.isAnnotationOverlay; }
     private get borderWidth() { return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH; }
@@ -822,10 +822,13 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     getChildDocumentViewProps(childLayout: Doc, childData?: Doc): DocumentViewProps {
         return {
             ...this.props,
+            NativeHeight: returnZero,
+            NativeWidth: returnZero,
+            fitToBox: false,
             DataDoc: childData,
             Document: childLayout,
             LibraryPath: this.libraryPath,
-            FreezeDimensions: this.props.freezeDimensions,
+            FreezeDimensions: this.props.freezeChildDimensions,
             layoutKey: undefined,
             rootSelected: this.rootSelected,
             dropAction: StrCast(this.props.Document.childDropAction) as dropActionType,
@@ -958,8 +961,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                     dataProvider={this.childDataProvider}
                     LayoutDoc={this.childLayoutDocFunc}
                     jitterRotation={NumCast(this.props.Document.jitterRotation)}
-                    fitToBox={this.props.fitToBox || this.props.layoutEngine !== undefined}
-                    FreezeDimensions={this.props.layoutEngine !== undefined}
+                    fitToBox={this.props.fitToBox || BoolCast(this.props.freezeChildDimensions)}
+                    FreezeDimensions={BoolCast(this.props.freezeChildDimensions)}
                 />,
                 bounds: this.childDataProvider(pair.layout)
             }));
@@ -1122,8 +1125,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
     @computed get contentScaling() {
         if (this.props.annotationsKey) return 0;
-        const hscale = this.nativeHeight ? this.props.PanelHeight() / this.nativeHeight : 1;
-        const wscale = this.nativeWidth ? this.props.PanelWidth() / this.nativeWidth : 1;
+        const nw = NumCast(this.Document._nativeWidth, this.props.NativeWidth());
+        const nh = NumCast(this.Document._nativeHeight, this.props.NativeHeight());
+        const hscale = nh ? this.props.PanelHeight() / nh : 1;
+        const wscale = nw ? this.props.PanelWidth() / nw : 1;
         return wscale < hscale ? wscale : hscale;
     }
     render() {
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index aa8e1fb43..7e511ae34 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -14,6 +14,7 @@ import "./collectionMulticolumnView.scss";
 import ResizeBar from './MulticolumnResizer';
 import WidthLabel from './MulticolumnWidthLabel';
 import { List } from '../../../../new_fields/List';
+import { returnZero } from '../../../../Utils';
 
 type MulticolumnDocument = makeInterface<[typeof documentSchema]>;
 const MulticolumnDocument = makeInterface(documentSchema);
@@ -208,6 +209,10 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
             {...this.props}
             Document={layout}
             DataDocument={layout.resolvedDataDoc as Doc}
+            NativeHeight={returnZero}
+            NativeWidth={returnZero}
+            fitToBox={BoolCast(this.props.Document._freezeChildDimensions)}
+            FreezeDimensions={BoolCast(this.props.Document._freezeChildDimensions)}
             backgroundColor={this.props.backgroundColor}
             CollectionDoc={this.props.Document}
             PanelWidth={width}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index 5e59f8237..daf1fda6c 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -6,7 +6,7 @@ import * as React from "react";
 import { Doc } from '../../../../new_fields/Doc';
 import { NumCast, StrCast, BoolCast, ScriptCast } from '../../../../new_fields/Types';
 import { ContentFittingDocumentView } from '../../nodes/ContentFittingDocumentView';
-import { Utils } from '../../../../Utils';
+import { Utils, returnZero } from '../../../../Utils';
 import "./collectionMultirowView.scss";
 import { computed, trace, observable, action } from 'mobx';
 import { Transform } from '../../../util/Transform';
@@ -208,6 +208,10 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument)
             {...this.props}
             Document={layout}
             DataDocument={layout.resolvedDataDoc as Doc}
+            NativeHeight={returnZero}
+            NativeWidth={returnZero}
+            fitToBox={BoolCast(this.props.Document._freezeChildDimensions)}
+            FreezeDimensions={BoolCast(this.props.Document._freezeChildDimensions)}
             backgroundColor={this.props.backgroundColor}
             CollectionDoc={this.props.Document}
             PanelWidth={width}
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index e4c8bbd7b..7e9e654cd 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -10,7 +10,6 @@ import { DocumentView, DocumentViewProps } from "./DocumentView";
 import React = require("react");
 import { PositionDocument } from "../../../new_fields/documentSchemas";
 import { TraceMobx } from "../../../new_fields/util";
-import { returnFalse } from "../../../Utils";
 import { ContentFittingDocumentView } from "./ContentFittingDocumentView";
 
 export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
@@ -108,7 +107,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                     getTransform={this.getTransform}
                     NativeHeight={this.NativeHeight}
                     NativeWidth={this.NativeWidth}
-                    active={returnFalse}
+                    active={this.props.parentActive}
                     focus={this.focusDoc}
                     PanelWidth={this.panelWidth}
                     PanelHeight={this.panelHeight}
diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx
index 52e5ed1dd..f66ff8020 100644
--- a/src/client/views/nodes/ContentFittingDocumentView.tsx
+++ b/src/client/views/nodes/ContentFittingDocumentView.tsx
@@ -2,7 +2,7 @@ import React = require("react");
 import { computed } from "mobx";
 import { observer } from "mobx-react";
 import "react-table/react-table.css";
-import { Doc, Opt } from "../../../new_fields/Doc";
+import { Doc, Opt, WidthSym, HeightSym } from "../../../new_fields/Doc";
 import { ScriptField } from "../../../new_fields/ScriptField";
 import { NumCast, StrCast } from "../../../new_fields/Types";
 import { TraceMobx } from "../../../new_fields/util";
@@ -15,7 +15,7 @@ import "./ContentFittingDocumentView.scss";
 import { dropActionType } from "../../util/DragManager";
 
 interface ContentFittingDocumentViewProps {
-    Document?: Doc;
+    Document: Doc;
     DataDocument?: Doc;
     LayoutDoc?: () => Opt<Doc>;
     NativeWidth?: () => number;
@@ -48,13 +48,14 @@ interface ContentFittingDocumentViewProps {
 @observer
 export class ContentFittingDocumentView extends React.Component<ContentFittingDocumentViewProps>{
     public get displayName() { return "DocumentView(" + this.props.Document?.title + ")"; } // this makes mobx trace() statements more descriptive
-    private get layoutDoc() { return this.props.Document && (this.props.LayoutDoc?.() || Doc.Layout(this.props.Document)); }
-    private nativeWidth = () => NumCast(this.layoutDoc?._nativeWidth, this.props.NativeWidth?.() || this.props.PanelWidth());
-    private nativeHeight = () => NumCast(this.layoutDoc?._nativeHeight, this.props.NativeHeight?.() || this.props.PanelHeight());
+    private get layoutDoc() { return this.props.LayoutDoc?.() || Doc.Layout(this.props.Document); }
+    @computed get freezeDimensions() { return this.props.FreezeDimensions; }
+    nativeWidth = () => { return NumCast(this.layoutDoc?._nativeWidth, this.props.NativeWidth?.() || (this.freezeDimensions && this.layoutDoc ? this.layoutDoc[WidthSym]() : this.props.PanelWidth())); }
+    nativeHeight = () => { return NumCast(this.layoutDoc?._nativeHeight, this.props.NativeHeight?.() || (this.freezeDimensions && this.layoutDoc ? this.layoutDoc[HeightSym]() : this.props.PanelHeight())); }
     @computed get scaling() {
-        const wscale = this.props.PanelWidth() / (this.nativeWidth() || this.props.PanelWidth() || 1);
+        const wscale = this.props.PanelWidth() / this.nativeWidth();
         if (wscale * this.nativeHeight() > this.props.PanelHeight()) {
-            return (this.props.PanelHeight() / (this.nativeHeight() || this.props.PanelHeight() || 1)) || 1;
+            return (this.props.PanelHeight() / this.nativeHeight()) || 1;
         }
         return wscale || 1;
     }
@@ -67,7 +68,7 @@ export class ContentFittingDocumentView extends React.Component<ContentFittingDo
     @computed get panelHeight() { return this.nativeHeight && (!this.props.Document || !this.props.Document._fitWidth) ? this.nativeHeight() * this.contentScaling() : this.props.PanelHeight(); }
 
     private getTransform = () => this.props.getTransform().translate(-this.centeringOffset, -this.centeringYOffset).scale(1 / this.contentScaling());
-    private get centeringOffset() { return this.nativeWidth && (!this.props.Document || !this.props.Document._fitWidth) ? (this.props.PanelWidth() - this.nativeWidth() * this.contentScaling()) / 2 : 0; }
+    private get centeringOffset() { return this.nativeWidth() && !this.props.Document._fitWidth ? (this.props.PanelWidth() - this.nativeWidth() * this.contentScaling()) / 2 : 0; }
     private get centeringYOffset() { return Math.abs(this.centeringOffset) < 0.001 ? (this.props.PanelHeight() - this.nativeHeight() * this.contentScaling()) / 2 : 0; }
 
     @computed get borderRounding() { return StrCast(this.props.Document?.borderRounding); }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index d800579a1..32c5a02e5 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -110,7 +110,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
     public get displayName() { return "DocumentView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive
     public get ContentDiv() { return this._mainCont.current; }
-    @computed get active() { return SelectionManager.IsSelected(this, true) || this.props.parentActive(true); }
+    get active() { return SelectionManager.IsSelected(this, true) || this.props.parentActive(true); }
     @computed get topMost() { return this.props.renderDepth === 0; }
     @computed get freezeDimensions() { return this.props.FreezeDimensions; }
     @computed get nativeWidth() { return NumCast(this.layoutDoc._nativeWidth, this.props.NativeWidth() || (this.freezeDimensions ? this.layoutDoc[WidthSym]() : 0)); }
@@ -124,8 +124,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     private _firstX: number = -1;
     private _firstY: number = -1;
 
-
-
     handle1PointerHoldStart = (e: Event, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): any => {
         this.removeMoveListeners();
         this.removeEndListeners();
diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss
index 526939438..7d40b3149 100644
--- a/src/client/views/nodes/FormattedTextBox.scss
+++ b/src/client/views/nodes/FormattedTextBox.scss
@@ -39,11 +39,6 @@
         position: absolute;
     }
 }
-
-.collectionfreeformview-container {
-    position: relative;
-}
-
 .formattedTextBox-outer {
     position: relative;
     overflow: auto;
@@ -75,6 +70,10 @@
     position: absolute;
     right: 0;
 
+    .collectionfreeformview-container {
+        position: relative;
+    }
+    
     >.formattedTextBox-sidebar-handle {
         right: unset;
         left: -5;
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 052251d94..e0b3a6809 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -433,12 +433,14 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
     contentFunc = () => [this.content];
     render() {
         TraceMobx();
+        const rotation = NumCast(this.dataDoc[this.fieldKey + "-rotation"]);
+        const aspect = (rotation % 180) ? this.Document[HeightSym]() / this.Document[WidthSym]() : 1;
         const dragging = !SelectionManager.GetIsDragging() ? "" : "-dragging";
         return (<div className={`imageBox${dragging}`} onContextMenu={this.specificContextMenu}
             style={{
                 transform: this.props.PanelWidth() ? undefined : `scale(${this.props.ContentScaling()})`,
                 width: this.props.PanelWidth() ? `${this.props.PanelWidth()}px` : `${100 / this.props.ContentScaling()}%`,
-                height: this.props.PanelWidth() ? undefined : `${100 / this.props.ContentScaling()}%`,
+                height: this.props.PanelWidth() ? `${this.props.PanelWidth() * aspect}px` : `${100 / this.props.ContentScaling()}%`,
                 pointerEvents: this.props.Document.isBackground ? "none" : undefined,
                 borderRadius: `${Number(StrCast(this.layoutDoc.borderRounding).replace("px", "")) / this.props.ContentScaling()}px`
             }} >
diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts
index 480a55da0..8c719ccd8 100644
--- a/src/new_fields/util.ts
+++ b/src/new_fields/util.ts
@@ -12,7 +12,7 @@ function _readOnlySetter(): never {
     throw new Error("Documents can't be modified in read-only mode");
 }
 
-const tracing = true;
+const tracing = false;
 export function TraceMobx() {
     tracing && trace();
 }
-- 
cgit v1.2.3-70-g09d2


From 11f5c3139074db379f64f7a3be98493cf18cbfd3 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 6 Apr 2020 04:07:45 -0400
Subject: everything's working with panelwidth/height etc stuff?? maybe?

---
 .../views/collections/CollectionStackingView.tsx   | 109 +++++++++++++--------
 .../CollectionStackingViewFieldColumn.tsx          |   3 +-
 .../views/collections/CollectionTreeView.tsx       |   5 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   1 +
 .../views/nodes/ContentFittingDocumentView.tsx     |  14 +--
 src/new_fields/util.ts                             |   2 +-
 6 files changed, 82 insertions(+), 52 deletions(-)

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

diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index d17b1ee48..d9183cdb6 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -24,7 +24,7 @@ import "./CollectionStackingView.scss";
 import { CollectionStackingViewFieldColumn } from "./CollectionStackingViewFieldColumn";
 import { CollectionSubView } from "./CollectionSubView";
 import { CollectionViewType } from "./CollectionView";
-import { DocumentView } from "../nodes/DocumentView";
+const _global = (window /* browser */ || global /* node */) as any;
 
 @observer
 export class CollectionStackingView extends CollectionSubView(doc => doc) {
@@ -47,6 +47,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     @computed get numGroupColumns() { return this.isStackingView ? Math.max(1, this.Sections.size + (this.showAddAGroup ? 1 : 0)) : 1; }
     @computed get showAddAGroup() { return (this.pivotField && (this.props.Document._chromeStatus !== 'view-mode' && this.props.Document._chromeStatus !== 'disabled')); }
     @computed get columnWidth() {
+        TraceMobx();
         return Math.min(this.props.PanelWidth() / (this.props as any).ContentScaling() - 2 * this.xMargin,
             this.isStackingView ? Number.MAX_VALUE : NumCast(this.props.Document.columnWidth, 250));
     }
@@ -113,34 +114,49 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         return fields;
     }
 
+    getSimpleDocHeight(d?: Doc) {
+        if (!d) return 0;
+        const layoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.());
+        const nw = NumCast(layoutDoc._nativeWidth);
+        const nh = NumCast(layoutDoc._nativeHeight);
+        let wid = this.columnWidth / (this.isStackingView ? this.numGroupColumns : 1);
+        if (!layoutDoc._fitWidth && nw && nh) {
+            const aspect = nw && nh ? nh / nw : 1;
+            if (!(this.props.Document.fillColumn)) wid = Math.min(layoutDoc[WidthSym](), wid);
+            return wid * aspect;
+        }
+        return layoutDoc._fitWidth ? wid * NumCast(layoutDoc.scrollHeight, nh) / (nw || 1) : layoutDoc[HeightSym]();
+    }
     componentDidMount() {
         super.componentDidMount();
-        this._heightDisposer = reaction(() => {
-            if (this.props.Document._autoHeight && !this.layoutDoc._nativeHeight) {
-                const sectionsList = Array.from(this.Sections.size ? this.Sections.values() : [this.filteredChildren]);
-                if (this.isStackingView) {
-                    const res = this.props.ContentScaling() * sectionsList.reduce((maxHght, s) => {
-                        const r1 = Math.max(maxHght,
-                            (this.Sections.size ? 50 : 0) + s.reduce((height, d, i) => {
-                                const val = height + this.getDocHeight(d) + (i === s.length - 1 ? this.yMargin : this.gridGap);
-                                return val;
-                            }, this.yMargin));
-                        return r1;
-                    }, 0);
-                    return res;
-                } else {
-                    const sum = Array.from(this._heightMap.values()).reduce((acc: number, curr: number) => acc += curr, 0);
-                    return this.props.ContentScaling() * (sum + (this.Sections.size ? (this.props.Document.miniHeaders ? 20 : 85) : -15));
-                }
-            }
-            return -1;
-        },
-            (hgt: number) => {
-                const doc = hgt === -1 ? undefined : this.props.DataDoc && this.props.DataDoc.layout === this.layoutDoc ? this.props.DataDoc : this.layoutDoc;
-                doc && hgt > 0 && (Doc.Layout(doc)._height = hgt);
-            },
-            { fireImmediately: true }
-        );
+        // this._heightDisposer = reaction(() => {
+        //     TraceMobx();
+        //     if (this.props.Document._autoHeight && !this.layoutDoc._nativeHeight) {
+        //         const sectionsList = Array.from(this.Sections.size ? this.Sections.values() : [this.filteredChildren]);
+        //         if (this.isStackingView) {
+        //             const res = Math.max(...sectionsList.map((s, i) => {
+        //                 const secSize = (this.Sections.size ? 50 : 0) + s.reduce((height, d, i) => {
+        //                     const val = this.getSimpleDocHeight(d) + (i === s.length - 1 ? this.yMargin : this.gridGap);
+        //                     console.log(d.title + " " + val + " => " + (val + height));
+        //                     return height + val;
+        //                 }, this.yMargin);
+        //                 console.log("Sec" + i + " = " + secSize);
+        //                 return secSize;
+        //             }));
+        //             return res * this.props.ContentScaling();
+        //         } else {
+        //             const sum = Array.from(this._heightMap.values()).reduce((acc: number, curr: number) => acc += curr, 0);
+        //             return this.props.ContentScaling() * (sum + (this.Sections.size ? (this.props.Document.miniHeaders ? 20 : 85) : -15));
+        //         }
+        //     }
+        //     return -1;
+        // },
+        //     (hgt: number) => {
+        //         const doc = hgt === -1 ? undefined : this.props.DataDoc && this.props.DataDoc.layout === this.layoutDoc ? this.props.DataDoc : this.layoutDoc;
+        //         doc && hgt > 0 && (Doc.Layout(doc)._height = hgt);
+        //     },
+        //     { fireImmediately: true }
+        // );
 
         // reset section headers when a new filter is inputted
         this._pivotFieldDisposer = reaction(
@@ -169,9 +185,9 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     getDisplayDoc(doc: Doc, dataDoc: Doc | undefined, dxf: () => Transform, width: () => number) {
         const layoutDoc = Doc.Layout(doc, this.props.childLayoutTemplate?.());
         const height = () => this.getDocHeight(doc);
-        return <DocumentView
+        return <ContentFittingDocumentView
             Document={doc}
-            DataDoc={dataDoc || (doc[DataSym] !== doc && doc[DataSym])}
+            DataDocument={dataDoc || (doc[DataSym] !== doc && doc[DataSym])}
             backgroundColor={this.props.backgroundColor}
             LayoutDoc={this.props.childLayoutTemplate}
             LibraryPath={this.props.LibraryPath}
@@ -185,22 +201,18 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
             rootSelected={this.rootSelected}
             dropAction={StrCast(this.props.Document.childDropAction) as dropActionType}
             onClick={layoutDoc.isTemplateDoc ? this.onClickHandler : this.onChildClickHandler}
-            ScreenToLocalTransform={dxf}
+            getTransform={dxf}
             focus={this.props.focus}
-            ContainingCollectionDoc={this.props.CollectionView?.props.Document}
-            ContainingCollectionView={this.props.CollectionView}
+            CollectionDoc={this.props.CollectionView?.props.Document}
+            CollectionView={this.props.CollectionView}
             addDocument={this.props.addDocument}
             moveDocument={this.props.moveDocument}
             removeDocument={this.props.removeDocument}
-            parentActive={this.props.active}
-            bringToFront={emptyFunction}
-            NativeHeight={returnZero}
-            NativeWidth={returnZero}
-            ContentScaling={returnOne}
+            active={this.props.active}
             whenActiveChanged={this.props.whenActiveChanged}
             addDocTab={this.props.addDocTab}
             pinToPres={this.props.pinToPres}>
-        </DocumentView>;
+        </ContentFittingDocumentView>;
     }
 
     getDocWidth(d?: Doc) {
@@ -295,6 +307,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         });
     }
     headings = () => Array.from(this.Sections);
+    refList = new Map<any, number>();
     sectionStacking = (heading: SchemaHeaderField | undefined, docList: Doc[]) => {
         const key = this.pivotField;
         let type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined = undefined;
@@ -305,6 +318,21 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         const cols = () => this.isStackingView ? 1 : Math.max(1, Math.min(this.filteredChildren.length,
             Math.floor((this.props.PanelWidth() - 2 * this.xMargin) / (this.columnWidth + this.gridGap))));
         return <CollectionStackingViewFieldColumn
+            observeHeight={(ref) => {
+                if (ref) {
+                    this.refList.set(ref, 0);
+                    this.observer = new _global.ResizeObserver(action((entries: any) => {
+                        if (this.props.Document.autoHeight && ref) {
+                            const doc = this.props.DataDoc && this.props.DataDoc.layout === this.layoutDoc ? this.props.DataDoc : this.layoutDoc;
+                            for (const entry of entries) {
+                                this.refList.set(ref, entry.contentRect.height);
+                            }
+                            Doc.Layout(doc)._height = Math.max(...Array.from(this.refList.entries()).map(entry => Number(getComputedStyle(entry[0]).height.replace("px", ""))));
+                        }
+                    }));
+                    this.observer.observe(ref);
+                }
+            }}
             key={heading ? heading.heading : ""}
             cols={cols}
             headings={this.headings}
@@ -323,11 +351,9 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         const y = this._scroll; // required for document decorations to update when the text box container is scrolled
         const { scale, translateX, translateY } = Utils.GetScreenTransform(dref);
         const outerXf = Utils.GetScreenTransform(this._masonryGridRef!);
-        const scaling = 1 / Math.min(1, this.props.PanelHeight() / this.layoutDoc[HeightSym]());
         const offset = this.props.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY);
-        const offsetx = (doc[WidthSym]() - doc[WidthSym]() / scaling) / 2;
         const offsety = (this.props.ChromeHeight && this.props.ChromeHeight() < 0 ? this.props.ChromeHeight() : 0);
-        return this.props.ScreenToLocalTransform().translate(offset[0] - offsetx, offset[1] + offsety).scale(scaling);
+        return this.props.ScreenToLocalTransform().translate(offset[0], offset[1] + offsety);
     }
 
     sectionMasonry = (heading: SchemaHeaderField | undefined, docList: Doc[]) => {
@@ -401,6 +427,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
 
     @computed get scaling() { return !this.nativeWidth ? 1 : this.props.PanelHeight() / this.nativeHeight; }
 
+    observer: any;
     render() {
         TraceMobx();
         const editableViewProps = {
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 0a48c95e4..dcaffe7af 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -39,6 +39,7 @@ interface CSVFieldColumnProps {
     type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined;
     createDropTarget: (ele: HTMLDivElement) => void;
     screenToLocalTransform: () => Transform;
+    observeHeight: (myref: any) => void;
 }
 
 @observer
@@ -358,7 +359,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
                 {this.props.parent.Document.hideHeadings ? (null) : headingView}
                 {
                     this.collapsed ? (null) :
-                        <div>
+                        <div ref={ref => ref && this.props.observeHeight(ref)}>
                             <div key={`${heading}-stack`} className={`collectionStackingView-masonry${singleColumn ? "Single" : "Grid"}`}
                                 style={{
                                     padding: singleColumn ? `${columnYMargin}px ${0}px ${style.yMargin}px ${0}px` : `${columnYMargin}px ${0}px`,
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index eff4758cf..7edda5a4f 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -267,8 +267,9 @@ class TreeView extends React.Component<TreeViewProps> {
     docTransform = () => {
         const { scale, translateX, translateY } = Utils.GetScreenTransform(this._dref.current!);
         const outerXf = this.props.outerXf();
-        const offset = this.props.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY);
-        const finalXf = this.props.ScreenToLocalTransform().translate(offset[0], offset[1] + (this.props.ChromeHeight && this.props.ChromeHeight() < 0 ? this.props.ChromeHeight() : 0));
+        const offset = this.props.ScreenToLocalTransform().transformDirection((outerXf.translateX - translateX), outerXf.translateY - translateY);
+        const finalXf = this.props.ScreenToLocalTransform().translate(offset[0], offset[1]);
+
         return finalXf;
     }
     getTransform = () => {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 22dd2d831..9581ec255 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -99,6 +99,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     private zoomScaling = () => (1 / this.parentScaling) * (this.fitToContent ?
         Math.min(this.props.PanelHeight() / (this.contentBounds.b - this.contentBounds.y), this.props.PanelWidth() / (this.contentBounds.r - this.contentBounds.x)) :
         this.Document.scale || 1)
+
     private centeringShiftX = () => !this.nativeWidth && !this.isAnnotationOverlay ? this.props.PanelWidth() / 2 / this.parentScaling : 0;  // shift so pan position is at center of window for non-overlay collections
     private centeringShiftY = () => !this.nativeHeight && !this.isAnnotationOverlay ? this.props.PanelHeight() / 2 / this.parentScaling : 0;// shift so pan position is at center of window for non-overlay collections
     private getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth + 1, -this.borderWidth + 1).translate(-this.centeringShiftX(), -this.centeringShiftY()).transform(this.getLocalTransform());
diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx
index f66ff8020..642264b85 100644
--- a/src/client/views/nodes/ContentFittingDocumentView.tsx
+++ b/src/client/views/nodes/ContentFittingDocumentView.tsx
@@ -50,8 +50,8 @@ export class ContentFittingDocumentView extends React.Component<ContentFittingDo
     public get displayName() { return "DocumentView(" + this.props.Document?.title + ")"; } // this makes mobx trace() statements more descriptive
     private get layoutDoc() { return this.props.LayoutDoc?.() || Doc.Layout(this.props.Document); }
     @computed get freezeDimensions() { return this.props.FreezeDimensions; }
-    nativeWidth = () => { return NumCast(this.layoutDoc?._nativeWidth, this.props.NativeWidth?.() || (this.freezeDimensions && this.layoutDoc ? this.layoutDoc[WidthSym]() : this.props.PanelWidth())); }
-    nativeHeight = () => { return NumCast(this.layoutDoc?._nativeHeight, this.props.NativeHeight?.() || (this.freezeDimensions && this.layoutDoc ? this.layoutDoc[HeightSym]() : this.props.PanelHeight())); }
+    nativeWidth = () => NumCast(this.layoutDoc?._nativeWidth, this.props.NativeWidth?.() || (this.freezeDimensions && this.layoutDoc ? this.layoutDoc[WidthSym]() : this.props.PanelWidth()));
+    nativeHeight = () => NumCast(this.layoutDoc?._nativeHeight, this.props.NativeHeight?.() || (this.freezeDimensions && this.layoutDoc ? this.layoutDoc[HeightSym]() : this.props.PanelHeight()));
     @computed get scaling() {
         const wscale = this.props.PanelWidth() / this.nativeWidth();
         if (wscale * this.nativeHeight() > this.props.PanelHeight()) {
@@ -64,8 +64,8 @@ export class ContentFittingDocumentView extends React.Component<ContentFittingDo
     private PanelWidth = () => this.panelWidth;
     private PanelHeight = () => this.panelHeight;
 
-    @computed get panelWidth() { return this.nativeWidth && (!this.props.Document || !this.props.Document._fitWidth) ? this.nativeWidth() * this.contentScaling() : this.props.PanelWidth(); }
-    @computed get panelHeight() { return this.nativeHeight && (!this.props.Document || !this.props.Document._fitWidth) ? this.nativeHeight() * this.contentScaling() : this.props.PanelHeight(); }
+    @computed get panelWidth() { return this.nativeWidth && !this.props.Document._fitWidth ? this.nativeWidth() * this.contentScaling() : this.props.PanelWidth(); }
+    @computed get panelHeight() { return this.nativeHeight && !this.props.Document._fitWidth ? this.nativeHeight() * this.contentScaling() : this.props.PanelHeight(); }
 
     private getTransform = () => this.props.getTransform().translate(-this.centeringOffset, -this.centeringYOffset).scale(1 / this.contentScaling());
     private get centeringOffset() { return this.nativeWidth() && !this.props.Document._fitWidth ? (this.props.PanelWidth() - this.nativeWidth() * this.contentScaling()) / 2 : 0; }
@@ -94,6 +94,9 @@ export class ContentFittingDocumentView extends React.Component<ContentFittingDo
                         LibraryPath={this.props.LibraryPath}
                         NativeWidth={this.nativeWidth}
                         NativeHeight={this.nativeHeight}
+                        PanelWidth={this.PanelWidth}
+                        PanelHeight={this.PanelHeight}
+                        ContentScaling={this.contentScaling}
                         fitToBox={this.props.fitToBox}
                         layoutKey={this.props.layoutKey}
                         dropAction={this.props.dropAction}
@@ -110,9 +113,6 @@ export class ContentFittingDocumentView extends React.Component<ContentFittingDo
                         parentActive={this.props.active}
                         ScreenToLocalTransform={this.getTransform}
                         renderDepth={this.props.renderDepth}
-                        ContentScaling={this.contentScaling}
-                        PanelWidth={this.PanelWidth}
-                        PanelHeight={this.PanelHeight}
                         focus={this.props.focus || emptyFunction}
                         bringToFront={emptyFunction}
                         dontRegisterView={this.props.dontRegisterView}
diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts
index 8c719ccd8..480a55da0 100644
--- a/src/new_fields/util.ts
+++ b/src/new_fields/util.ts
@@ -12,7 +12,7 @@ function _readOnlySetter(): never {
     throw new Error("Documents can't be modified in read-only mode");
 }
 
-const tracing = false;
+const tracing = true;
 export function TraceMobx() {
     tracing && trace();
 }
-- 
cgit v1.2.3-70-g09d2


From cc5251534850bf3dd7bb39be54e8dbd7cb73dc9f Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 6 Apr 2020 14:47:43 -0400
Subject: fixed padding.  added isInPlaceContainer flag.  changed button
 content for setting collection content to use the dropped document directly
 (not an alias of it)

---
 src/client/views/collections/CollectionView.tsx                      | 2 ++
 src/client/views/collections/CollectionViewChromes.tsx               | 5 +++--
 .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx  | 4 ++--
 src/client/views/nodes/CollectionFreeFormDocumentView.tsx            | 4 ++--
 src/new_fields/documentSchemas.ts                                    | 1 +
 5 files changed, 10 insertions(+), 6 deletions(-)

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

diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 5d08a2bd8..183d01b74 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -248,6 +248,8 @@ export class CollectionView extends Touchable<FieldViewProps> {
             if (this.props.Document.childDetailed instanceof Doc) {
                 layoutItems.push({ description: "View Child Detailed Layout", event: () => this.props.addDocTab(this.props.Document.childDetailed as Doc, "onRight"), icon: "project-diagram" });
             }
+            layoutItems.push({ description: "Toggle is inPlace Container", event: () => this.props.Document.isInPlaceContainer = !this.props.Document.isInPlaceContainer, icon: "project-diagram" });
+
             !existing && ContextMenu.Instance.addItem({ description: "Layout...", subitems: layoutItems, icon: "hand-point-right" });
 
             const open = ContextMenu.Instance.findByDescription("Open...");
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index 2d565d9db..4a0eb3aa4 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -18,6 +18,7 @@ import { CollectionView } from "./CollectionView";
 import "./CollectionViewChromes.scss";
 import * as Autosuggest from 'react-autosuggest';
 import KeyRestrictionRow from "./KeyRestrictionRow";
+import { ObjectField } from "../../../new_fields/ObjectField";
 const datepicker = require('js-datepicker');
 
 interface CollectionViewChromeProps {
@@ -54,8 +55,8 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
     };
     _contentCommand = {
         params: ["target", "source"], title: "=> content",
-        script: "getProto(this.target).data = aliasDocs(this.source);",
-        immediate: (source: Doc[]) => Doc.GetProto(this.target).data = Doc.aliasDocs(source),
+        script: "getProto(this.target).data = copyField(this.source);",
+        immediate: (source: Doc[]) => Doc.GetProto(this.target).data = new List<Doc>(source), // Doc.aliasDocs(source),
         initialize: emptyFunction,
     };
     _viewCommand = {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 9581ec255..f12dd76d8 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -88,7 +88,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
     @computed get fitToContent() { return (this.props.fitToBox || this.Document._fitToBox) && !this.isAnnotationOverlay; }
     @computed get parentScaling() { return this.props.ContentScaling && this.fitToContent && !this.isAnnotationOverlay ? this.props.ContentScaling() : 1; }
-    @computed get contentBounds() { return aggregateBounds(this._layoutElements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!), NumCast(this.layoutDoc.xPadding, 10), NumCast(this.layoutDoc.yPadding, 10)); }
+    @computed get contentBounds() { return aggregateBounds(this._layoutElements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!), NumCast(this.layoutDoc._xPadding, 10), NumCast(this.layoutDoc._yPadding, 10)); }
     @computed get nativeWidth() { return this.fitToContent ? 0 : NumCast(this.Document._nativeWidth, this.props.NativeWidth()); }
     @computed get nativeHeight() { return this.fitToContent ? 0 : NumCast(this.Document._nativeHeight, this.props.NativeHeight()); }
     private get isAnnotationOverlay() { return this.props.isAnnotationOverlay; }
@@ -852,7 +852,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     }
 
     addDocTab = (doc: Doc, where: string) => {
-        if (where === "inPlace") {
+        if (where === "inPlace" && this.layoutDoc.isInPlaceContainer) {
             this.dataDoc[this.props.fieldKey] = new List<Doc>([doc]);
             return true;
         }
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 7e9e654cd..f9f5f449c 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -61,8 +61,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     }
 
     contentScaling = () => this.nativeWidth > 0 && !this.props.fitToBox && !this.freezeDimensions ? this.width / this.nativeWidth : 1;
-    panelWidth = () => (this.dataProvider?.width || this.props.PanelWidth());
-    panelHeight = () => (this.dataProvider?.height || this.props.PanelHeight());
+    panelWidth = () => (this.dataProvider?.width || this.props.PanelWidth?.());
+    panelHeight = () => (this.dataProvider?.height || this.props.PanelHeight?.());
     getTransform = (): Transform => this.props.ScreenToLocalTransform()
         .translate(-this.X, -this.Y)
         .scale(1 / this.contentScaling())
diff --git a/src/new_fields/documentSchemas.ts b/src/new_fields/documentSchemas.ts
index ff02596f7..03519cb94 100644
--- a/src/new_fields/documentSchemas.ts
+++ b/src/new_fields/documentSchemas.ts
@@ -29,6 +29,7 @@ export const documentSchema = createSchema({
     _replacedChrome: "string",  // what the default chrome is replaced with. Currently only supports the value of 'replaced' for PresBox's.
     _chromeStatus: "string",    // determines the state of the collection chrome. values allowed are 'replaced', 'enabled', 'disabled', 'collapsed'
     _freezeChildDimensions: "boolean", // freezes child document dimensions (e.g., used by time/pivot view to make sure all children will be scaled to fit their display rectangle)
+    isInPlaceContainer: "boolean",// whether the marked object will display addDocTab() calls that target "inPlace" destinations
     color: "string",            // foreground color of document
     backgroundColor: "string",  // background color of document
     opacity: "number",          // opacity of document
-- 
cgit v1.2.3-70-g09d2


From d34cb78c3d42f946be62a06bed73fe1997bb25ab Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 8 Apr 2020 01:48:02 -0400
Subject: fixed bug with getDocTemplate().  fixed inPlace link following when
 there's no inPlaceContainer.

---
 src/client/views/collections/CollectionView.tsx                     | 2 +-
 .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx | 6 ++++--
 src/new_fields/Doc.ts                                               | 2 +-
 3 files changed, 6 insertions(+), 4 deletions(-)

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

diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 5819c829f..6e0e44d35 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -230,7 +230,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
             if (this.props.Document.childDetailed instanceof Doc) {
                 layoutItems.push({ description: "View Child Detailed Layout", event: () => this.props.addDocTab(this.props.Document.childDetailed as Doc, "onRight"), icon: "project-diagram" });
             }
-            layoutItems.push({ description: "Toggle is inPlace Container", event: () => this.props.Document.isInPlaceContainer = !this.props.Document.isInPlaceContainer, icon: "project-diagram" });
+            layoutItems.push({ description: `${this.props.Document.isInPlaceContainer ? "Unset":"Set"} inPlace Container`, event: () => this.props.Document.isInPlaceContainer = !this.props.Document.isInPlaceContainer, icon: "project-diagram" });
 
             !existing && ContextMenu.Instance.addItem({ description: "Layout...", subitems: layoutItems, icon: "hand-point-right" });
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index f12dd76d8..df1373b14 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -4,7 +4,7 @@ import { faBraille, faChalkboard, faCompass, faCompressArrowsAlt, faExpandArrows
 import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import { computedFn } from "mobx-utils";
-import { Doc, HeightSym, Opt, WidthSym } from "../../../../new_fields/Doc";
+import { Doc, HeightSym, Opt, WidthSym, DocListCast } from "../../../../new_fields/Doc";
 import { documentSchema, positionSchema } from "../../../../new_fields/documentSchemas";
 import { Id } from "../../../../new_fields/FieldSymbols";
 import { InkData, InkField, InkTool } from "../../../../new_fields/InkField";
@@ -794,7 +794,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
             const savedState = { px: this.Document._panX, py: this.Document._panY, s: this.Document.scale, pt: this.Document.panTransformType };
 
-            if (!doc.z) this.setPan(newPanX, newPanY, "Ease"); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow
+            if (DocListCast(this.dataDoc[this.props.fieldKey]).includes(doc)) {
+                if (!doc.z) this.setPan(newPanX, newPanY, "Ease"); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow
+            }
             Doc.BrushDoc(this.props.Document);
             this.props.focus(this.props.Document);
             willZoom && this.setScaleToZoom(layoutdoc, scale);
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 4fc4dc1cf..a9c97fc19 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -899,7 +899,7 @@ export namespace Doc {
 
 Scripting.addGlobal(function renameAlias(doc: any, n: any) { return StrCast(Doc.GetProto(doc).title).replace(/\([0-9]*\)/, "") + `(${n})`; });
 Scripting.addGlobal(function getProto(doc: any) { return Doc.GetProto(doc); });
-Scripting.addGlobal(function getDocTemplate(doc?: any) { Doc.getDocTemplate(doc); });
+Scripting.addGlobal(function getDocTemplate(doc?: any) { return Doc.getDocTemplate(doc); });
 Scripting.addGlobal(function getAlias(doc: any) { return Doc.MakeAlias(doc); });
 Scripting.addGlobal(function getCopy(doc: any, copyProto: any) { return doc.isTemplateDoc ? Doc.ApplyTemplate(doc) : Doc.MakeCopy(doc, copyProto); });
 Scripting.addGlobal(function copyField(field: any) { return ObjectField.MakeCopy(field); });
-- 
cgit v1.2.3-70-g09d2


From 097a3af816b540082bfe370816ced74126d5c96e Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 8 Apr 2020 12:17:46 -0400
Subject: fixed annotation overlays broken from previous changes for
 images/pdfs/etc

---
 src/client/util/LinkManager.ts                                    | 4 ++--
 .../collections/collectionFreeForm/CollectionFreeFormView.tsx     | 7 +++++--
 src/client/views/nodes/ImageBox.tsx                               | 8 ++++++--
 3 files changed, 13 insertions(+), 6 deletions(-)

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

diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index 4457f41e2..e236c7f47 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -136,12 +136,12 @@ export class LinkManager {
         }
     }
     public addGroupToAnchor(linkDoc: Doc, anchor: Doc, groupDoc: Doc, replace: boolean = false) {
-        linkDoc.linkRelationship = groupDoc.linkRelationship;
+        Doc.GetProto(linkDoc).linkRelationship = groupDoc.linkRelationship;
     }
 
     // removes group doc of given group type only from given anchor on given link
     public removeGroupFromAnchor(linkDoc: Doc, anchor: Doc, groupType: string) {
-        linkDoc.linkRelationship = "-ungrouped-";
+        Doc.GetProto(linkDoc).linkRelationship = "-ungrouped-";
     }
 
     // returns map of group type to anchor's links in that group type
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index df1373b14..146ec9f7d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -66,9 +66,12 @@ export const panZoomSchema = createSchema({
 
 type PanZoomDocument = makeInterface<[typeof panZoomSchema, typeof documentSchema, typeof positionSchema, typeof pageSchema]>;
 const PanZoomDocument = makeInterface(panZoomSchema, documentSchema, positionSchema, pageSchema);
+export type collectionFreeformViewProps = {
+    forceScaling?:boolean; // whether to force scaling of content (needed by ImageBox)
+};
 
 @observer
-export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
+export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument, undefined as any as collectionFreeformViewProps) {
     private _lastX: number = 0;
     private _lastY: number = 0;
     private _inkToTextStartX: number | undefined;
@@ -1127,7 +1130,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     }
 
     @computed get contentScaling() {
-        if (this.props.annotationsKey) return 0;
+        if (this.props.annotationsKey && !this.props.forceScaling) return 0;
         const nw = NumCast(this.Document._nativeWidth, this.props.NativeWidth());
         const nh = NumCast(this.Document._nativeHeight, this.props.NativeHeight());
         const hscale = nh ? this.props.PanelHeight() / nh : 1;
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 8818b8098..325d759ad 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -313,6 +313,7 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
     considerGooglePhotosLink = () => {
         const remoteUrl = this.Document.googlePhotosUrl;
         return !remoteUrl ? (null) : (<img
+            style={{ transform: `scale(${this.props.ContentScaling()})`, transformOrigin: "bottom right" }}
             id={"google-photos"}
             src={"/assets/google_photos.png"}
             onClick={() => window.open(remoteUrl)}
@@ -337,6 +338,7 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
         return (
             <img
                 id={"upload-icon"}
+                style={{ transform: `scale(${1 / this.props.ContentScaling()})`, transformOrigin: "bottom right" }}
                 src={`/assets/${this.uploadIcon}`}
                 onClick={async () => {
                     const { dataDoc } = this;
@@ -437,16 +439,18 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
         TraceMobx();
         const { nativeWidth, nativeHeight } = this.nativeSize;
         const aspect = nativeWidth / nativeHeight;
+        const pwidth = this.props.PanelWidth() > this.props.PanelHeight() / aspect ? this.props.PanelHeight() / aspect : this.props.PanelWidth();
         const dragging = !SelectionManager.GetIsDragging() ? "" : "-dragging";
         return (<div className={`imageBox${dragging}`} onContextMenu={this.specificContextMenu}
             style={{
                 transform: this.props.PanelWidth() ? undefined : `scale(${this.props.ContentScaling()})`,
-                width: this.props.PanelWidth() ? `${this.props.PanelWidth()}px` : `${100 / this.props.ContentScaling()}%`,
-                height: this.props.PanelWidth() ? `${this.props.PanelWidth() / aspect}px` : `${100 / this.props.ContentScaling()}%`,
+                width: this.props.PanelWidth() ? `${pwidth}px` : `${100 / this.props.ContentScaling()}%`,
+                height: this.props.PanelWidth() ? `${pwidth / aspect}px` : `${100 / this.props.ContentScaling()}%`,
                 pointerEvents: this.props.Document.isBackground ? "none" : undefined,
                 borderRadius: `${Number(StrCast(this.layoutDoc.borderRounding).replace("px", "")) / this.props.ContentScaling()}px`
             }} >
             <CollectionFreeFormView {...this.props}
+                forceScaling={true}
                 PanelHeight={this.props.PanelHeight}
                 PanelWidth={this.props.PanelWidth}
                 NativeHeight={returnZero}
-- 
cgit v1.2.3-70-g09d2


From 7b1a4b93be9d01ab5613de09b8f308291f709b01 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 9 Apr 2020 23:27:41 -0400
Subject: fix presbox to work with linked documents. more cleanup to use
 dataDoc/layoutDoc/rootDoc.   changed ## to >> for inline comment to open up
 #### heading markdown

---
 src/client/util/DocumentManager.ts                 |   2 +-
 src/client/util/RichTextRules.ts                   |  25 ++---
 src/client/util/RichTextSchema.tsx                 |  10 +-
 src/client/views/InkingStroke.tsx                  |  17 ++--
 .../views/collections/ParentDocumentSelector.tsx   |   8 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  41 --------
 src/client/views/nodes/AudioBox.tsx                |   2 +-
 src/client/views/nodes/ColorBox.tsx                |   2 +-
 src/client/views/nodes/DocumentView.tsx            |  11 ++-
 src/client/views/nodes/FontIconBox.tsx             |   2 +-
 src/client/views/nodes/FormattedTextBox.tsx        |  10 +-
 src/client/views/nodes/FormattedTextBoxComment.tsx |   9 +-
 src/client/views/nodes/ImageBox.tsx                |   7 +-
 src/client/views/nodes/LinkAnchorBox.tsx           |  36 +++----
 src/client/views/nodes/PresBox.tsx                 | 110 +++++++++++----------
 15 files changed, 138 insertions(+), 154 deletions(-)

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

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index fc2f251a5..2d6078cf3 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -148,7 +148,7 @@ export class DocumentManager {
         const highlight = () => {
             const finalDocView = getFirstDocView(targetDoc);
             if (finalDocView) {
-                finalDocView.Document.scrollToLinkID = linkId;
+                finalDocView.layoutDoc.scrollToLinkID = linkId;
                 Doc.linkFollowHighlight(finalDocView.props.Document);
             }
         };
diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts
index b0a124cb8..8f2df054b 100644
--- a/src/client/util/RichTextRules.ts
+++ b/src/client/util/RichTextRules.ts
@@ -62,6 +62,17 @@ export class RichTextRules {
             // ``` code block
             textblockTypeInputRule(/^```$/, schema.nodes.code_block),
 
+            // create an inline view of a tag stored under the '#' field
+            new InputRule(
+                new RegExp(/#([a-zA-Z_\-]+[a-zA-Z_\-0-9]*)\s$/),
+                (state, match, start, end) => {
+                    const tag = match[1];
+                    if (!tag) return state.tr;
+                    this.Document[DataSym]["#"] = tag;
+                    const fieldView = state.schema.nodes.dashField.create({ fieldKey: "#" });
+                    return state.tr.deleteRange(start, end).insert(start, fieldView);
+                }),
+
             // # heading
             textblockTypeInputRule(
                 new RegExp(/^(#{1,6})\s$/),
@@ -81,7 +92,7 @@ export class RichTextRules {
 
             // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document [[ <fieldKey> : <Doc>]]   // [[:Doc]] => hyperlink   [[fieldKey]] => show field   [[fieldKey:Doc]] => show field of doc
             new InputRule(
-                new RegExp(/\[\[([a-zA-Z_#@\? \-0-9]*)(=[a-zA-Z_#@\? \-0-9]*)?(:[a-zA-Z_#@\? \-0-9]+)?\]\]$/),
+                new RegExp(/\[\[([a-zA-Z_@\? \-0-9]*)(=[a-zA-Z_@\? \-0-9]*)?(:[a-zA-Z_@\? \-0-9]+)?\]\]$/),
                 (state, match, start, end) => {
                     const fieldKey = match[1];
                     const docid = match[3]?.substring(1);
@@ -104,16 +115,6 @@ export class RichTextRules {
                     const fieldView = state.schema.nodes.dashField.create({ fieldKey, docid });
                     return state.tr.deleteRange(start, end).insert(start, fieldView);
                 }),
-            // create an inline view of a tag stored under the '#' field
-            new InputRule(
-                new RegExp(/#([a-zA-Z_\-0-9]+)\s$/),
-                (state, match, start, end) => {
-                    const tag = match[1];
-                    if (!tag) return state.tr;
-                    this.Document[DataSym]["#"] = tag;
-                    const fieldView = state.schema.nodes.dashField.create({ fieldKey: "#" });
-                    return state.tr.deleteRange(start, end).insert(start, fieldView);
-                }),
             // create an inline view of a document {{ <layoutKey> : <Doc> }}  // {{:Doc}} => show default view of document   {{<layout>}} => show layout for this doc   {{<layout> : Doc}} => show layout for another doc
             new InputRule(
                 new RegExp(/\{\{([a-zA-Z_ \-0-9]*)(\([a-zA-Z0-9…._\-]*\))?(:[a-zA-Z_ \-0-9]+)?\}\}$/),
@@ -134,7 +135,7 @@ export class RichTextRules {
                     return node ? state.tr.replaceRangeWith(start, end, dashDoc).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
                 }),
             new InputRule(
-                new RegExp(/##$/),
+                new RegExp(/>>$/),
                 (state, match, start, end) => {
                     const textDoc = this.Document[DataSym];
                     const numInlines = NumCast(textDoc.inlineTextCount);
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 3e6cbce56..0599b3ebe 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -27,8 +27,12 @@ import ParagraphNodeSpec from "./ParagraphNodeSpec";
 import { Transform } from "./Transform";
 import React = require("react");
 
-const blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"],
-    preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0];
+const 
+    blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], 
+    hrDOM: DOMOutputSpecArray = ["hr"],
+    preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], 
+    brDOM: DOMOutputSpecArray = ["br"], 
+    ulDOM: DOMOutputSpecArray = ["ul", 0];
 
 // :: Object
 // [Specs](#model.NodeSpec) for the nodes defined in this schema.
@@ -738,7 +742,7 @@ export class DashDocView {
         this._outer = document.createElement("span");
         this._outer.style.position = "relative";
         this._outer.style.textIndent = "0";
-        this._outer.style.border = "1px solid " + StrCast(tbox.Document.color, (Cast(Doc.UserDoc().activeWorkspace, Doc, null).darkScheme ? "dimGray" : "lightGray"));
+        this._outer.style.border = "1px solid " + StrCast(tbox.layoutDoc.color, (Cast(Doc.UserDoc().activeWorkspace, Doc, null).darkScheme ? "dimGray" : "lightGray"));
         this._outer.style.width = node.attrs.width;
         this._outer.style.height = node.attrs.height;
         this._outer.style.display = node.attrs.hidden ? "none" : "inline-block";
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index db5e2912d..ff63a7c33 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -3,7 +3,7 @@ import { observer } from "mobx-react";
 import { documentSchema } from "../../new_fields/documentSchemas";
 import { InkData, InkField, InkTool } from "../../new_fields/InkField";
 import { makeInterface } from "../../new_fields/Schema";
-import { Cast, StrCast } from "../../new_fields/Types";
+import { Cast, StrCast, NumCast } from "../../new_fields/Types";
 import { DocExtendableComponent } from "./DocComponent";
 import { InkingControl } from "./InkingControl";
 import "./InkingStroke.scss";
@@ -25,28 +25,27 @@ const InkDocument = makeInterface(documentSchema);
 export class InkingStroke extends DocExtendableComponent<FieldViewProps, InkDocument>(InkDocument) {
     public static LayoutString(fieldStr: string) { return FieldView.LayoutString(InkingStroke, fieldStr); }
 
-    @computed get PanelWidth() { return this.props.PanelWidth(); }
-    @computed get PanelHeight() { return this.props.PanelHeight(); }
-
     private analyzeStrokes = () => {
-        const data: InkData = Cast(this.dataDoc[this.fieldKey], InkField) ?.inkData ?? [];
+        const data: InkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? [];
         CognitiveServices.Inking.Appliers.ConcatenateHandwriting(this.dataDoc, ["inkAnalysis", "handwriting"], [data]);
     }
 
     render() {
         TraceMobx();
-        const data: InkData = Cast(this.dataDoc[this.fieldKey], InkField) ?.inkData ?? [];
+        const data: InkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? [];
         const xs = data.map(p => p.X);
         const ys = data.map(p => p.Y);
         const left = Math.min(...xs);
         const top = Math.min(...ys);
         const right = Math.max(...xs);
         const bottom = Math.max(...ys);
-        const points = InteractionUtils.CreatePolyline(data, left, top, StrCast(this.layoutDoc.color, InkingControl.Instance.selectedColor), this.Document.strokeWidth ?? parseInt(InkingControl.Instance.selectedWidth));
+        const points = InteractionUtils.CreatePolyline(data, left, top,
+            StrCast(this.layoutDoc.color, InkingControl.Instance.selectedColor),
+            NumCast(this.layoutDoc.strokeWidth, parseInt(InkingControl.Instance.selectedWidth)));
         const width = right - left;
         const height = bottom - top;
-        const scaleX = this.PanelWidth / width;
-        const scaleY = this.PanelHeight / height;
+        const scaleX = this.props.PanelWidth() / width;
+        const scaleY = this.props.PanelHeight() / height;
         return (
             <svg
                 width={width}
diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx
index afc1c6754..5c9d2b0dd 100644
--- a/src/client/views/collections/ParentDocumentSelector.tsx
+++ b/src/client/views/collections/ParentDocumentSelector.tsx
@@ -114,7 +114,13 @@ export class DockingViewButtonSelector extends React.Component<{ views: Document
     }
 
     render() {
-        return <span title="Tap for menu, drag tab as document" onPointerDown={e => { if (getComputedStyle(this._ref.current!).width !== "100%") {e.stopPropagation();e.preventDefault();} this.props.views[0].select(false); }} className="buttonSelector">
+        return <span title="Tap for menu, drag tab as document"
+            onPointerDown={e => {
+                if (getComputedStyle(this._ref.current!).width !== "100%") {
+                    e.stopPropagation(); e.preventDefault();
+                }
+                this.props.views[0]?.select(false);
+            }} className="buttonSelector">
             <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={this.flyout} stylesheet={this.customStylesheet}>
                 <FontAwesomeIcon icon={"cog"} size={"sm"} />
             </Flyout>
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 146ec9f7d..b5bcc0cc2 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1017,47 +1017,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument, u
 
     private thumbIdentifier?: number;
 
-    // @action
-    // handleHandDown = (e: React.TouchEvent) => {
-    //     const fingers = InteractionUtils.GetMyTargetTouches(e, this.prevPoints, true);
-    //     const thumb = fingers.reduce((a, v) => a.clientY > v.clientY ? a : v, fingers[0]);
-    //     this.thumbIdentifier = thumb?.identifier;
-    //     const others = fingers.filter(f => f !== thumb);
-    //     const minX = Math.min(...others.map(f => f.clientX));
-    //     const minY = Math.min(...others.map(f => f.clientY));
-    //     const t = this.getTransform().transformPoint(minX, minY);
-    //     const th = this.getTransform().transformPoint(thumb.clientX, thumb.clientY);
-
-    //     const thumbDoc = FieldValue(Cast(CurrentUserUtils.setupThumbDoc(CurrentUserUtils.UserDocument), Doc));
-    //     if (thumbDoc) {
-    //         this._palette = <Palette x={t[0]} y={t[1]} thumb={th} thumbDoc={thumbDoc} />;
-    //     }
-
-    //     document.removeEventListener("touchmove", this.onTouch);
-    //     document.removeEventListener("touchmove", this.handleHandMove);
-    //     document.addEventListener("touchmove", this.handleHandMove);
-    //     document.removeEventListener("touchend", this.handleHandUp);
-    //     document.addEventListener("touchend", this.handleHandUp);
-    // }
-
-    // @action
-    // handleHandMove = (e: TouchEvent) => {
-    //     for (let i = 0; i < e.changedTouches.length; i++) {
-    //         const pt = e.changedTouches.item(i);
-    //         if (pt?.identifier === this.thumbIdentifier) {
-    //         }
-    //     }
-    // }
-
-    // @action
-    // handleHandUp = (e: TouchEvent) => {
-    //     this.onTouchEnd(e);
-    //     if (this.prevPoints.size < 3) {
-    //         this._palette = undefined;
-    //         document.removeEventListener("touchend", this.handleHandUp);
-    //     }
-    // }
-
     onContextMenu = (e: React.MouseEvent) => {
         if (this.props.children && this.props.annotationsKey) return;
         const layoutItems: ContextMenuProps[] = [];
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 9ac41a528..c08a2b808 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -235,7 +235,7 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume
                 <div className="audiobox-controls">
                     <div className="audiobox-player" onClick={this.onPlay}>
                         <div className="audiobox-playhead"> <FontAwesomeIcon style={{ width: "100%" }} icon={this.audioState === "paused" ? "play" : "pause"} size={this.props.PanelHeight() < 36 ? "1x" : "2x"} /></div>
-                        <div className="audiobox-playhead" onClick={this.onStop}><FontAwesomeIcon style={{ width: "100%", background: this.Document.playOnSelect ? "yellow" : "dimGray" }} icon="hand-point-left" size={this.props.PanelHeight() < 36 ? "1x" : "2x"} /></div>
+                        <div className="audiobox-playhead" onClick={this.onStop}><FontAwesomeIcon style={{ width: "100%", background: this.layoutDoc.playOnSelect ? "yellow" : "dimGray" }} icon="hand-point-left" size={this.props.PanelHeight() < 36 ? "1x" : "2x"} /></div>
                         <div className="audiobox-timeline" onClick={e => e.stopPropagation()}
                             onPointerDown={e => {
                                 if (e.button === 0 && !e.ctrlKey) {
diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx
index d34d63d01..b6d687f27 100644
--- a/src/client/views/nodes/ColorBox.tsx
+++ b/src/client/views/nodes/ColorBox.tsx
@@ -19,7 +19,7 @@ export class ColorBox extends DocExtendableComponent<FieldViewProps, ColorDocume
     public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ColorBox, fieldKey); }
 
     render() {
-        const selDoc = SelectionManager.SelectedDocuments()?.[0]?.Document;
+        const selDoc = SelectionManager.SelectedDocuments()?.[0]?.rootDoc;
         return <div className={`colorBox-container${this.active() ? "-interactive" : ""}`}
             onPointerDown={e => e.button === 0 && !e.ctrlKey && e.stopPropagation()}
             style={{ transformOrigin: "top left", transform: `scale(${this.props.ContentScaling()})`, width: `${100 / this.props.ContentScaling()}%`, height: `${100 / this.props.ContentScaling()}%` }} >
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 75d635a21..1bccce054 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1024,25 +1024,28 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
 
     @observable _link: Opt<Doc>;  // see DocumentButtonBar for explanation of how this works
-    makeLink = () => { return this._link; } // pass the link placeholde to child views so they can react to make a specialized anchor.  This is essentially a function call to the descendants since the value of the _link variable will immediately get set back to undefined.
+    makeLink = () => this._link; // pass the link placeholde to child views so they can react to make a specialized anchor.  This is essentially a function call to the descendants since the value of the _link variable will immediately get set back to undefined.
 
     @undoBatch
-    hideLinkAnchor = (doc: Doc) => doc.hidden = true;
+    hideLinkAnchor = (doc: Doc) => doc.hidden = true
     anchorPanelWidth = () => this.props.PanelWidth() || 1;
     anchorPanelHeight = () => this.props.PanelHeight() || 1;
     @computed get anchors() {
         TraceMobx();
-        return DocListCast(this.Document.links).filter(d => !d.hidden && this.isNonTemporalLink).map((d, i) =>
+        return this.layoutDoc.presBox ? (null) : DocListCast(this.Document.links).filter(d => !d.hidden && this.isNonTemporalLink).map((d, i) =>
             <div className="documentView-linkAnchorBoxWrapper" key={d[Id]}>
                 <DocumentView {...this.props}
                     Document={d}
+                    ContainingCollectionView={this.props.ContainingCollectionView}
                     ContainingCollectionDoc={this.props.Document} // bcz: hack this.props.Document is not a collection  Need a better prop for passing the containing document to the LinkAnchorBox
                     PanelWidth={this.anchorPanelWidth}
                     PanelHeight={this.anchorPanelHeight}
                     layoutKey={this.linkEndpoint(d)}
                     ContentScaling={returnOne}
                     backgroundColor={returnTransparent}
-                    removeDocument={this.hideLinkAnchor} />
+                    removeDocument={this.hideLinkAnchor}
+                    LayoutDoc={undefined}
+                />
             </div>);
     }
     @computed get innards() {
diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx
index d4da21239..bd7ea4c92 100644
--- a/src/client/views/nodes/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox.tsx
@@ -56,7 +56,7 @@ export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>(
                 background: StrCast(referenceLayout.backgroundColor),
                 boxShadow: this.props.Document.ischecked ? `4px 4px 12px black` : undefined
             }}>
-            <FontAwesomeIcon className="fontIconBox-icon" icon={this.Document.icon as any} color={this._foregroundColor} size="sm" />
+            <FontAwesomeIcon className="fontIconBox-icon" icon={this.dataDoc.icon} color={this._foregroundColor} size="sm" />
         </button>;
     }
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 4cccfc966..fb919a716 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -215,7 +215,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
 
     updateTitle = () => {
         if ((this.props.Document.isTemplateForField === "text" || !this.props.Document.isTemplateForField) && // only update the title if the data document's data field is changing
-            StrCast(this.dataDoc.title).startsWith("-") && this._editorView && !this.Document.customTitle) {
+            StrCast(this.dataDoc.title).startsWith("-") && this._editorView && !this.rootDoc.customTitle) {
             const str = this._editorView.state.doc.textContent;
             const titlestr = str.substr(0, Math.min(40, str.length));
             this.dataDoc.title = "-" + titlestr + (str.length > 40 ? "..." : "");
@@ -723,7 +723,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
                 }
             }, 0);
             dataDoc.title = exportState.title;
-            this.Document.customTitle = true;
+            this.rootDoc.customTitle = true;
             dataDoc.unchanged = true;
         } else {
             delete dataDoc[GoogleRef];
@@ -1155,7 +1155,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
                 this.layoutDoc.limitHeight = undefined;
                 this.layoutDoc._autoHeight = false;
             }
-            const nh = this.Document.isTemplateForField ? 0 : NumCast(this.dataDoc._nativeHeight, 0);
+            const nh = this.layoutDoc.isTemplateForField ? 0 : NumCast(this.dataDoc._nativeHeight, 0);
             const dh = NumCast(this.layoutDoc._height, 0);
             const newHeight = Math.max(10, (nh ? dh / nh * scrollHeight : scrollHeight) + (this.props.ChromeHeight ? this.props.ChromeHeight() : 0));
             if (Math.abs(newHeight - dh) > 1) { // bcz: Argh!  without this, we get into a React crash if the same document is opened in a freeform view and in the treeview.  no idea why, but after dragging the freeform document, selecting it, and selecting text, it will compute to 1 pixel higher than the treeview which causes a cycle
@@ -1205,8 +1205,8 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
                 <div className={`formattedTextBox-outer`} style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, }} onScroll={this.onscrolled} ref={this._scrollRef}>
                     <div className={`formattedTextBox-inner${rounded}`} ref={this.createDropTarget}
                         style={{
-                            padding: `${NumCast(this.Document._xMargin, 0)}px  ${NumCast(this.Document._yMargin, 0)}px`,
-                            pointerEvents: ((this.Document.isLinkButton || this.props.onClick) && !this.props.isSelected()) ? "none" : undefined
+                            padding: `${NumCast(this.layoutDoc._xMargin, 0)}px  ${NumCast(this.layoutDoc._yMargin, 0)}px`,
+                            pointerEvents: ((this.layoutDoc.isLinkButton || this.props.onClick) && !this.props.isSelected()) ? "none" : undefined
                         }} />
                 </div>
                 {!this.props.Document._showSidebar ? (null) : this.sidebarWidthPercent === "0%" ?
diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx
index 1e48c76e7..35304033f 100644
--- a/src/client/views/nodes/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/FormattedTextBoxComment.tsx
@@ -15,6 +15,7 @@ import './FormattedTextBoxComment.scss';
 import React = require("react");
 import { Docs } from "../../documents/Documents";
 import wiki from "wikijs";
+import { DocumentType } from "../../documents/DocumentTypes";
 
 export let formattedTextBoxCommentPlugin = new Plugin({
     view(editorView) { return new FormattedTextBoxComment(editorView); }
@@ -83,8 +84,12 @@ export class FormattedTextBoxComment {
                 const keep = e.target && (e.target as any).type === "checkbox" ? true : false;
                 const textBox = FormattedTextBoxComment.textBox;
                 if (FormattedTextBoxComment.linkDoc && !keep && textBox) {
-                    DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document,
-                        (doc: Doc, followLinkLocation: string) => textBox.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation));
+                    if (FormattedTextBoxComment.linkDoc.type !== DocumentType.LINK) {
+                        textBox.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "inTab":"onRight");
+                    } else {
+                        DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document,
+                            (doc: Doc, followLinkLocation: string) => textBox.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation));
+                    }
                 } else if (textBox && (FormattedTextBoxComment.tooltipText as any).href) {
                     textBox.props.addDocTab(Docs.Create.WebDocument((FormattedTextBoxComment.tooltipText as any).href, { title: (FormattedTextBoxComment.tooltipText as any).href, _width: 200, _height: 400 }), "onRight");
                 }
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 55983ab77..bb7d78ace 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -276,8 +276,8 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
                 this.layoutDoc._height = this.layoutDoc[WidthSym]() * cachedAspect;
             }
         } else if (this.layoutDoc._nativeWidth !== cachedNativeSize.width || this.layoutDoc._nativeHeight !== cachedNativeSize.height) {
-            !(this.Document[StrCast(this.props.Document.layoutKey)] instanceof Doc) && setTimeout(() => {
-                if (!(this.Document[StrCast(this.props.Document.layoutKey)] instanceof Doc)) {
+            !(this.layoutDoc[StrCast(this.layoutDoc.layoutKey)] instanceof Doc) && setTimeout(() => {
+                if (!(this.layoutDoc[StrCast(this.layoutDoc.layoutKey)] instanceof Doc)) {
                     this.layoutDoc._nativeWidth = cachedNativeSize.width;
                     this.layoutDoc._nativeHeight = cachedNativeSize.height;
                 }
@@ -432,9 +432,6 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
     contentFunc = () => [this.content];
     render() {
         TraceMobx();
-        const { nativeWidth, nativeHeight } = this.nativeSize;
-        const aspect = nativeWidth / nativeHeight;
-        const pwidth = this.props.PanelWidth() > this.props.PanelHeight() / aspect ? this.props.PanelHeight() / aspect : this.props.PanelWidth();
         const dragging = !SelectionManager.GetIsDragging() ? "" : "-dragging";
         return (<div className={`imageBox${dragging}`} onContextMenu={this.specificContextMenu}
             style={{
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index 3e7cf6808..f3e099dd6 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -7,7 +7,7 @@ import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
 import { Utils, setupMoveUpEvents } from '../../../Utils';
 import { DocumentManager } from "../../util/DocumentManager";
 import { DragManager } from "../../util/DragManager";
-import { DocComponent } from "../DocComponent";
+import { DocExtendableComponent } from "../DocComponent";
 import "./LinkAnchorBox.scss";
 import { FieldView, FieldViewProps } from "./FieldView";
 import React = require("react");
@@ -25,7 +25,7 @@ type LinkAnchorSchema = makeInterface<[typeof documentSchema]>;
 const LinkAnchorDocument = makeInterface(documentSchema);
 
 @observer
-export class LinkAnchorBox extends DocComponent<FieldViewProps, LinkAnchorSchema>(LinkAnchorDocument) {
+export class LinkAnchorBox extends DocExtendableComponent<FieldViewProps, LinkAnchorSchema>(LinkAnchorDocument) {
     public static LayoutString(fieldKey: string) { return FieldView.LayoutString(LinkAnchorBox, fieldKey); }
     _doubleTap = false;
     _lastTap: number = 0;
@@ -49,14 +49,14 @@ export class LinkAnchorBox extends DocComponent<FieldViewProps, LinkAnchorSchema
             const separation = Math.sqrt((pt[0] - e.clientX) * (pt[0] - e.clientX) + (pt[1] - e.clientY) * (pt[1] - e.clientY));
             const dragdist = Math.sqrt((pt[0] - down[0]) * (pt[0] - down[0]) + (pt[1] - down[1]) * (pt[1] - down[1]));
             if (separation > 100) {
-                const dragData = new DragManager.DocumentDragData([this.props.Document]);
+                const dragData = new DragManager.DocumentDragData([this.rootDoc]);
                 dragData.dropAction = "alias";
                 dragData.removeDropProperties = ["anchor1_x", "anchor1_y", "anchor2_x", "anchor2_y", "isLinkButton"];
                 DragManager.StartDocumentDrag([this._ref.current!], dragData, down[0], down[1]);
                 return true;
             } else if (dragdist > separation) {
-                this.props.Document[this.props.fieldKey + "_x"] = (pt[0] - bounds.left) / bounds.width * 100;
-                this.props.Document[this.props.fieldKey + "_y"] = (pt[1] - bounds.top) / bounds.height * 100;
+                this.layoutDoc[this.fieldKey + "_x"] = (pt[0] - bounds.left) / bounds.width * 100;
+                this.layoutDoc[this.fieldKey + "_y"] = (pt[1] - bounds.top) / bounds.height * 100;
             }
         }
         return false;
@@ -65,16 +65,16 @@ export class LinkAnchorBox extends DocComponent<FieldViewProps, LinkAnchorSchema
     onClick = (e: PointerEvent) => {
         this._doubleTap = (Date.now() - this._lastTap < 300 && e.button === 0);
         this._lastTap = Date.now();
-        if ((e.button === 2 || e.ctrlKey || !this.props.Document.isLinkButton)) {
+        if ((e.button === 2 || e.ctrlKey || !this.layoutDoc.isLinkButton)) {
             this.props.select(false);
         }
         if (!this._doubleTap && !e.ctrlKey && e.button < 2) {
             const anchorContainerDoc = this.props.ContainingCollectionDoc; // bcz: hack!  need a better prop for passing the anchor's container 
             this._editing = true;
             anchorContainerDoc && this.props.bringToFront(anchorContainerDoc, false);
-            if (anchorContainerDoc && !this.props.Document.onClick && !this._isOpen) {
+            if (anchorContainerDoc && !this.layoutDoc.onClick && !this._isOpen) {
                 this._timeout = setTimeout(action(() => {
-                    DocumentManager.Instance.FollowLink(this.props.Document, anchorContainerDoc, document => this.props.addDocTab(document, StrCast(this.props.Document.linkOpenLocation, "inTab")), false);
+                    DocumentManager.Instance.FollowLink(this.rootDoc, anchorContainerDoc, document => this.props.addDocTab(document, StrCast(this.layoutDoc.linkOpenLocation, "inTab")), false);
                     this._editing = false;
                 }), 300 - (Date.now() - this._lastTap));
             }
@@ -85,10 +85,10 @@ export class LinkAnchorBox extends DocComponent<FieldViewProps, LinkAnchorSchema
     }
 
     openLinkDocOnRight = (e: React.MouseEvent) => {
-        this.props.addDocTab(this.props.Document, "onRight");
+        this.props.addDocTab(this.rootDoc, "onRight");
     }
     openLinkTargetOnRight = (e: React.MouseEvent) => {
-        const alias = Doc.MakeAlias(Cast(this.props.Document[this.props.fieldKey], Doc, null));
+        const alias = Doc.MakeAlias(Cast(this.layoutDoc[this.fieldKey], Doc, null));
         alias.isLinkButton = undefined;
         alias.isBackground = undefined;
         alias.layoutKey = "layout";
@@ -111,17 +111,17 @@ export class LinkAnchorBox extends DocComponent<FieldViewProps, LinkAnchorSchema
 
     render() {
         TraceMobx();
-        const x = this.props.PanelWidth() > 1 ? NumCast(this.props.Document[this.props.fieldKey + "_x"], 100) : 0;
-        const y = this.props.PanelWidth() > 1 ? NumCast(this.props.Document[this.props.fieldKey + "_y"], 100) : 0;
-        const c = StrCast(this.props.Document.backgroundColor, "lightblue");
-        const anchor = this.props.fieldKey === "anchor1" ? "anchor2" : "anchor1";
+        const x = this.props.PanelWidth() > 1 ? NumCast(this.layoutDoc[this.fieldKey + "_x"], 100) : 0;
+        const y = this.props.PanelWidth() > 1 ? NumCast(this.layoutDoc[this.fieldKey + "_y"], 100) : 0;
+        const c = StrCast(this.layoutDoc.backgroundColor, "lightblue");
+        const anchor = this.fieldKey === "anchor1" ? "anchor2" : "anchor1";
         const anchorScale = (x === 0 || x === 100 || y === 0 || y === 100) ? 1 : .15;
 
-        const timecode = this.props.Document[anchor + "Timecode"];
-        const targetTitle = StrCast((this.props.Document[anchor]! as Doc).title) + (timecode !== undefined ? ":" + timecode : "");
+        const timecode = this.dataDoc[anchor + "_timecode"];
+        const targetTitle = StrCast((this.dataDoc[anchor] as Doc)?.title) + (timecode !== undefined ? ":" + timecode : "");
         const flyout = (
-            <div className="linkAnchorBoxBox-flyout" title=" " onPointerOver={() => Doc.UnBrushDoc(this.props.Document)}>
-                <LinkEditor sourceDoc={Cast(this.props.Document[this.props.fieldKey], Doc, null)} hideback={true} linkDoc={this.props.Document} showLinks={action(() => { })} />
+            <div className="linkAnchorBoxBox-flyout" title=" " onPointerOver={() => Doc.UnBrushDoc(this.rootDoc)}>
+                <LinkEditor sourceDoc={Cast(this.dataDoc[this.fieldKey], Doc, null)} hideback={true} linkDoc={this.rootDoc} showLinks={action(() => { })} />
                 {!this._forceOpen ? (null) : <div className="linkAnchorBox-linkCloser" onPointerDown={action(() => this._isOpen = this._editing = this._forceOpen = false)}>
                     <FontAwesomeIcon color="dimGray" icon={"times"} size={"sm"} />
                 </div>}
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index e7434feaa..bea3170ac 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -4,10 +4,11 @@ import { faArrowLeft, faArrowRight, faEdit, faMinus, faPlay, faPlus, faStop, faH
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, DocListCast } from "../../../new_fields/Doc";
+import { Doc, DocListCast, DocCastAsync } from "../../../new_fields/Doc";
 import { InkTool } from "../../../new_fields/InkField";
 import { BoolCast, Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types";
 import { returnFalse } from "../../../Utils";
+import { documentSchema } from "../../../new_fields/documentSchemas";
 import { DocumentManager } from "../../util/DocumentManager";
 import { undoBatch } from "../../util/UndoManager";
 import { CollectionDockingView } from "../collections/CollectionDockingView";
@@ -15,6 +16,8 @@ import { CollectionView, CollectionViewType } from "../collections/CollectionVie
 import { InkingControl } from "../InkingControl";
 import { FieldView, FieldViewProps } from './FieldView';
 import "./PresBox.scss";
+import { DocExtendableComponent } from "../DocComponent";
+import { makeInterface } from "../../../new_fields/Schema";
 
 library.add(faArrowLeft);
 library.add(faArrowRight);
@@ -26,24 +29,27 @@ library.add(faTimes);
 library.add(faMinus);
 library.add(faEdit);
 
+type PresBoxSchema = makeInterface<[typeof documentSchema]>;
+const PresBoxDocument = makeInterface(documentSchema);
+
 @observer
-export class PresBox extends React.Component<FieldViewProps> {
+export class PresBox extends DocExtendableComponent<FieldViewProps, PresBoxSchema>(PresBoxDocument) {
     public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PresBox, fieldKey); }
     _childReaction: IReactionDisposer | undefined;
     @observable _isChildActive = false;
     componentDidMount() {
-        this.props.Document._forceRenderEngine = "timeline";
-        this.props.Document._replacedChrome = "replaced";
+        this.layoutDoc._forceRenderEngine = "timeline";
+        this.layoutDoc._replacedChrome = "replaced";
         this._childReaction = reaction(() => this.childDocs.slice(), (children) => children.forEach((child, i) => child.presentationIndex = i), { fireImmediately: true });
     }
     componentWillUnmount() {
         this._childReaction?.();
     }
 
-    @computed get childDocs() { return DocListCast(this.props.Document[this.props.fieldKey]); }
-    @computed get currentIndex() { return NumCast(this.props.Document._itemIndex); }
+    @computed get childDocs() { return DocListCast(this.dataDoc[this.fieldKey]); }
+    @computed get currentIndex() { return NumCast(this.layoutDoc._itemIndex); }
 
-    updateCurrentPresentation = action(() => Doc.UserDoc().curPresentation = this.props.Document);
+    updateCurrentPresentation = action(() => Doc.UserDoc().curPresentation = this.rootDoc);
 
     next = () => {
         this.updateCurrentPresentation();
@@ -78,8 +84,8 @@ export class PresBox extends React.Component<FieldViewProps> {
     }
 
     whenActiveChanged = action((isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive));
-    active = (outsideReaction?: boolean) => ((InkingControl.Instance.selectedTool === InkTool.None && !this.props.Document.isBackground) &&
-        (this.props.Document.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false)
+    active = (outsideReaction?: boolean) => ((InkingControl.Instance.selectedTool === InkTool.None && !this.layoutDoc.isBackground) &&
+        (this.layoutDoc.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false)
 
     /**
      * This is the method that checks for the actions that need to be performed
@@ -131,11 +137,10 @@ export class PresBox extends React.Component<FieldViewProps> {
      */
     navigateToElement = async (curDoc: Doc, fromDocIndex: number) => {
         this.updateCurrentPresentation();
-        const fromDoc = this.childDocs[fromDocIndex].presentationTargetDoc as Doc;
         let docToJump = curDoc;
         let willZoom = false;
 
-        const presDocs = DocListCast(this.props.Document[this.props.fieldKey]);
+        const presDocs = DocListCast(this.dataDoc[this.props.fieldKey]);
         let nextSelected = presDocs.indexOf(curDoc);
         const currentDocGroups: Doc[] = [];
         for (; nextSelected < presDocs.length - 1; nextSelected++) {
@@ -157,29 +162,28 @@ export class PresBox extends React.Component<FieldViewProps> {
         });
 
         //docToJump stayed same meaning, it was not in the group or was the last element in the group
-        const aliasOf = await Cast(docToJump.aliasOf, Doc);
-        const srcContext = aliasOf && await Cast(aliasOf.context, Doc);
+        const aliasOf = await DocCastAsync(docToJump.aliasOf);
+        const srcContext = aliasOf && await DocCastAsync(aliasOf.context);
         if (docToJump === curDoc) {
             //checking if curDoc has navigation open
-            const target = await Cast(curDoc.presentationTargetDoc, Doc);
+            const target = await DocCastAsync(curDoc.presentationTargetDoc);
             if (curDoc.navButton && target) {
                 DocumentManager.Instance.jumpToDocument(target, false, undefined, srcContext);
             } else if (curDoc.zoomButton && target) {
                 //awaiting jump so that new scale can be found, since jumping is async
                 await DocumentManager.Instance.jumpToDocument(target, true, undefined, srcContext);
             }
-            return;
+        } else {
+            //awaiting jump so that new scale can be found, since jumping is async
+            const presTargetDoc = await DocCastAsync(docToJump.presentationTargetDoc);
+            presTargetDoc && await DocumentManager.Instance.jumpToDocument(presTargetDoc, willZoom, undefined, srcContext);
         }
-
-        //awaiting jump so that new scale can be found, since jumping is async
-        const presTargetDoc = await docToJump.presentationTargetDoc as Doc;
-        await DocumentManager.Instance.jumpToDocument(presTargetDoc, willZoom, undefined, srcContext);
     }
 
 
     @undoBatch
     public removeDocument = (doc: Doc) => {
-        return Doc.RemoveDocFromList(this.props.Document, this.props.fieldKey, doc);
+        return Doc.RemoveDocFromList(this.dataDoc, this.fieldKey, doc);
     }
 
     //The function that is called when a document is clicked or reached through next or back.
@@ -188,10 +192,10 @@ export class PresBox extends React.Component<FieldViewProps> {
         this.updateCurrentPresentation();
         Doc.UnBrushAllDocs();
         if (index >= 0 && index < this.childDocs.length) {
-            this.props.Document._itemIndex = index;
+            this.layoutDoc._itemIndex = index;
 
-            if (!this.props.Document.presStatus) {
-                this.props.Document.presStatus = true;
+            if (!this.layoutDoc.presStatus) {
+                this.layoutDoc.presStatus = true;
                 this.startPresentation(index);
             }
 
@@ -204,10 +208,10 @@ export class PresBox extends React.Component<FieldViewProps> {
     //The function that starts or resets presentaton functionally, depending on status flag.
     startOrResetPres = () => {
         this.updateCurrentPresentation();
-        if (this.props.Document.presStatus) {
+        if (this.layoutDoc.presStatus) {
             this.resetPresentation();
         } else {
-            this.props.Document.presStatus = true;
+            this.layoutDoc.presStatus = true;
             this.startPresentation(0);
             this.gotoDocument(0, this.currentIndex);
         }
@@ -216,7 +220,7 @@ export class PresBox extends React.Component<FieldViewProps> {
     addDocument = (doc: Doc) => {
         const newPinDoc = Doc.MakeAlias(doc);
         newPinDoc.presentationTargetDoc = doc;
-        return Doc.AddDocToList(this.props.Document, this.props.fieldKey, newPinDoc);
+        return Doc.AddDocToList(this.dataDoc, this.fieldKey, newPinDoc);
     }
 
 
@@ -225,8 +229,8 @@ export class PresBox extends React.Component<FieldViewProps> {
     resetPresentation = () => {
         this.updateCurrentPresentation();
         this.childDocs.forEach(doc => (doc.presentationTargetDoc as Doc).opacity = 1);
-        this.props.Document._itemIndex = 0;
-        this.props.Document.presStatus = false;
+        this.layoutDoc._itemIndex = 0;
+        this.layoutDoc.presStatus = false;
     }
 
     //The function that starts the presentation, also checking if actions should be applied
@@ -247,16 +251,16 @@ export class PresBox extends React.Component<FieldViewProps> {
     }
 
     updateMinimize = undoBatch(action((e: React.ChangeEvent, mode: CollectionViewType) => {
-        if (BoolCast(this.props.Document.inOverlay) !== (mode === CollectionViewType.Invalid)) {
-            if (this.props.Document.inOverlay) {
-                Doc.RemoveDocFromList((Doc.UserDoc().overlays as Doc), undefined, this.props.Document);
-                CollectionDockingView.AddRightSplit(this.props.Document);
-                this.props.Document.inOverlay = false;
+        if (BoolCast(this.layoutDoc.inOverlay) !== (mode === CollectionViewType.Invalid)) {
+            if (this.layoutDoc.inOverlay) {
+                Doc.RemoveDocFromList((Doc.UserDoc().overlays as Doc), undefined, this.rootDoc);
+                CollectionDockingView.AddRightSplit(this.rootDoc);
+                this.layoutDoc.inOverlay = false;
             } else {
-                this.props.Document.x = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0)[0];// 500;//e.clientX + 25;
-                this.props.Document.y = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0)[1];////e.clientY - 25;
-                this.props.addDocTab?.(this.props.Document, "close");
-                Doc.AddDocToList((Doc.UserDoc().overlays as Doc), undefined, this.props.Document);
+                this.layoutDoc.x = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0)[0];// 500;//e.clientX + 25;
+                this.layoutDoc.y = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0)[1];////e.clientY - 25;
+                this.props.addDocTab?.(this.rootDoc, "close");
+                Doc.AddDocToList((Doc.UserDoc().overlays as Doc), undefined, this.rootDoc);
             }
         }
     }));
@@ -264,13 +268,13 @@ export class PresBox extends React.Component<FieldViewProps> {
     initializeViewAliases = (docList: Doc[], viewtype: CollectionViewType) => {
         const hgt = (viewtype === CollectionViewType.Tree) ? 50 : 46;
         docList.forEach(doc => {
-            doc.presBox = this.props.Document; // give contained documents a reference to the presentation
+            doc.presBox = this.rootDoc; // give contained documents a reference to the presentation
             doc.collapsedHeight = hgt;  //  set the collpased height for documents based on the type of view (Tree or Stack) they will be displaye din
         });
     }
 
     selectElement = (doc: Doc) => {
-        this.gotoDocument(this.childDocs.indexOf(doc), NumCast(this.props.Document._itemIndex));
+        this.gotoDocument(this.childDocs.indexOf(doc), NumCast(this.layoutDoc._itemIndex));
     }
 
     getTransform = () => {
@@ -283,17 +287,17 @@ export class PresBox extends React.Component<FieldViewProps> {
     @undoBatch
     viewChanged = action((e: React.ChangeEvent) => {
         //@ts-ignore
-        this.props.Document._viewType = e.target.selectedOptions[0].value;
-        this.props.Document._viewType === CollectionViewType.Stacking && (this.props.Document._pivotField = undefined); // pivot field may be set by the user in timeline view (or some other way) -- need to reset it here
-        this.updateMinimize(e, Number(this.props.Document._viewType));
+        this.layoutDoc._viewType = e.target.selectedOptions[0].value;
+        this.layoutDoc._viewType === CollectionViewType.Stacking && (this.layoutDoc._pivotField = undefined); // pivot field may be set by the user in timeline view (or some other way) -- need to reset it here
+        this.updateMinimize(e, StrCast(this.layoutDoc._viewType));
     });
 
-    childLayoutTemplate = () => this.props.Document._viewType === CollectionViewType.Stacking ? Cast(Doc.UserDoc().presentationTemplate, Doc, null) : undefined;
+    childLayoutTemplate = () => this.layoutDoc._viewType === CollectionViewType.Stacking ? Cast(Doc.UserDoc().presentationTemplate, Doc, null) : undefined;
     render() {
-        const mode = StrCast(this.props.Document._viewType) as CollectionViewType;
+        const mode = StrCast(this.layoutDoc._viewType) as CollectionViewType;
         this.initializeViewAliases(this.childDocs, mode);
-        return <div className="presBox-cont" style={{ minWidth: this.props.Document.inOverlay ? 240 : undefined, pointerEvents: this.active() || this.props.Document.inOverlay ? "all" : "none" }} >
-            <div className="presBox-buttons" style={{ display: this.props.Document._chromeStatus === "disabled" ? "none" : undefined }}>
+        return <div className="presBox-cont" style={{ minWidth: this.layoutDoc.inOverlay ? 240 : undefined, pointerEvents: this.active() || this.layoutDoc.inOverlay ? "all" : "none" }} >
+            <div className="presBox-buttons" style={{ display: this.layoutDoc._chromeStatus === "disabled" ? "none" : undefined }}>
                 <select className="collectionViewBaseChrome-viewPicker"
                     onPointerDown={e => e.stopPropagation()}
                     onChange={this.viewChanged}
@@ -304,15 +308,21 @@ export class PresBox extends React.Component<FieldViewProps> {
                     <option className="collectionViewBaseChrome-viewOption" onPointerDown={e => e.stopPropagation()} value={CollectionViewType.Carousel}>Slides</option>
                 </select>
                 <button className="presBox-button" title="Back" onClick={this.back}><FontAwesomeIcon icon={"arrow-left"} /></button>
-                <button className="presBox-button" title={"Reset Presentation" + this.props.Document.presStatus ? "" : " From Start"} onClick={this.startOrResetPres}>
-                    <FontAwesomeIcon icon={this.props.Document.presStatus ? "stop" : "play"} />
+                <button className="presBox-button" title={"Reset Presentation" + this.layoutDoc.presStatus ? "" : " From Start"} onClick={this.startOrResetPres}>
+                    <FontAwesomeIcon icon={this.layoutDoc.presStatus ? "stop" : "play"} />
                 </button>
                 <button className="presBox-button" title="Next" onClick={this.next}><FontAwesomeIcon icon={"arrow-right"} /></button>
             </div>
             <div className="presBox-listCont" >
                 {mode !== CollectionViewType.Invalid ?
-                    <CollectionView {...this.props} PanelHeight={this.panelHeight} moveDocument={returnFalse} childLayoutTemplate={this.childLayoutTemplate}
-                        addDocument={this.addDocument} removeDocument={returnFalse} focus={this.selectElement} ScreenToLocalTransform={this.getTransform} />
+                    <CollectionView {...this.props}
+                        PanelHeight={this.panelHeight}
+                        moveDocument={returnFalse}
+                        childLayoutTemplate={this.childLayoutTemplate}
+                        addDocument={this.addDocument}
+                        removeDocument={returnFalse}
+                        focus={this.selectElement}
+                        ScreenToLocalTransform={this.getTransform} />
                     : (null)
                 }
             </div>
-- 
cgit v1.2.3-70-g09d2


From 7afce5c0ba803b66f3b8c6722f25604290abc1c1 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 11 Apr 2020 15:59:14 -0400
Subject: fixed deiconifying things. fixed interactions with backgrounds to
 allow their content to be active.  added overflow flag for collection
 freeforms.

---
 src/client/views/DocComponent.tsx                              |  2 +-
 src/client/views/collections/CollectionDockingView.tsx         |  2 +-
 .../collections/collectionFreeForm/CollectionFreeFormView.tsx  |  6 +++---
 .../views/collections/collectionFreeForm/MarqueeView.tsx       | 10 ++++++++--
 src/new_fields/documentSchemas.ts                              |  1 +
 src/server/authentication/models/current_user_utils.ts         |  4 ++--
 6 files changed, 16 insertions(+), 9 deletions(-)

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

diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index c97e46f91..b1bd4191c 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -115,7 +115,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
         whenActiveChanged = action((isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive));
         active = (outsideReaction?: boolean) => ((InkingControl.Instance.selectedTool === InkTool.None && !this.props.Document.isBackground) &&
             ((this.props.Document.forceActive && this.props.rootSelected(outsideReaction)) || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false)
-        annotationsActive = (outsideReaction?: boolean) => (InkingControl.Instance.selectedTool !== InkTool.None ||
+        annotationsActive = (outsideReaction?: boolean) => (InkingControl.Instance.selectedTool !== InkTool.None || (this.props.Document.isBackground && this.props.active()) ||
             (this.props.Document.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false)
     }
     return Component;
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 28aaf0c57..d77ef812f 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -773,7 +773,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
             return CollectionDockingView.AddRightSplit(doc, libraryPath);
         } else if (location === "close") {
             return CollectionDockingView.CloseRightSplit(doc);
-        } else {
+        } else {// if (location === "inPlace") {
             return CollectionDockingView.Instance.AddTab(this._stack, doc, libraryPath);
         }
     }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index b5bcc0cc2..e4e1a543a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -67,7 +67,7 @@ export const panZoomSchema = createSchema({
 type PanZoomDocument = makeInterface<[typeof panZoomSchema, typeof documentSchema, typeof positionSchema, typeof pageSchema]>;
 const PanZoomDocument = makeInterface(panZoomSchema, documentSchema, positionSchema, pageSchema);
 export type collectionFreeformViewProps = {
-    forceScaling?:boolean; // whether to force scaling of content (needed by ImageBox)
+    forceScaling?: boolean; // whether to force scaling of content (needed by ImageBox)
 };
 
 @observer
@@ -138,7 +138,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument, u
     @undoBatch
     @action
     onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
-        if (this.props.Document.isBackground) return false;
+        // if (this.props.Document.isBackground) return false;
         const xf = this.getTransform();
         const xfo = this.getTransformOverlay();
         const [xp, yp] = xf.transformPoint(de.x, de.y);
@@ -164,7 +164,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument, u
                             const nh = NumCast(layoutDoc._nativeHeight);
                             layoutDoc._height = nw && nh ? nh / nw * NumCast(layoutDoc._width) : 300;
                         }
-                        this.bringToFront(d);
+                        d.isBackground === undefined && this.bringToFront(d);
                     }));
 
                     (de.complete.docDragData.droppedDocuments.length === 1 || de.shiftKey) && this.updateClusterDocs(de.complete.docDragData.droppedDocuments);
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index ec2b2749c..96a7c4fc0 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -586,13 +586,19 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
          * This contains the "C for collection, ..." text on marquees.
          * Commented out by syip2 when the marquee menu was added.
          */
-        return <div className="marquee" style={{ transform: `translate(${p[0]}px, ${p[1]}px)`, width: `${Math.abs(v[0])}`, height: `${Math.abs(v[1])}`, zIndex: 2000 }} >
+        return <div className="marquee" style={{
+            transform: `translate(${p[0]}px, ${p[1]}px)`,
+            width: `${Math.abs(v[0])}`,
+            height: `${Math.abs(v[1])}`, zIndex: 2000
+        }} >
             {/* <span className="marquee-legend" /> */}
         </div>;
     }
 
     render() {
-        return <div className="marqueeView" onScroll={(e) => e.currentTarget.scrollTop = e.currentTarget.scrollLeft = 0} onClick={this.onClick} onPointerDown={this.onPointerDown}>
+        return <div className="marqueeView"
+            style={{ overflow: StrCast(this.props.Document.overflow), }}
+            onScroll={(e) => e.currentTarget.scrollTop = e.currentTarget.scrollLeft = 0} onClick={this.onClick} onPointerDown={this.onPointerDown}>
             {this._visible ? this.marqueeDiv : null}
             {this.props.children}
         </div>;
diff --git a/src/new_fields/documentSchemas.ts b/src/new_fields/documentSchemas.ts
index a640862f3..b11941f40 100644
--- a/src/new_fields/documentSchemas.ts
+++ b/src/new_fields/documentSchemas.ts
@@ -33,6 +33,7 @@ export const documentSchema = createSchema({
     color: "string",            // foreground color of document
     backgroundColor: "string",  // background color of document
     opacity: "number",          // opacity of document
+    overflow: "string",         // sets overflow behvavior for CollectionFreeForm views
     creationDate: DateField,    // when the document was created
     links: listSpec(Doc),       // computed (readonly) list of links associated with this document
     onClick: ScriptField,       // script to run when document is clicked (can be overriden by an onClick prop)
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index b4dc17178..5e0e9ad67 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -57,9 +57,9 @@ export class CurrentUserUtils {
         doc.iconView = new PrefetchProxy(Docs.Create.TextDocument("", { title: "icon", _width: 150, _height: 30, isTemplateDoc: true, onClick: ScriptField.MakeScript("deiconifyView(this)") }));
         Doc.GetProto(doc.iconView as any as Doc).icon = new RichTextField('{"doc":{"type":"doc","content":[{"type":"paragraph","attrs":{"align":null,"color":null,"id":null,"indent":null,"inset":null,"lineSpacing":null,"paddingBottom":null,"paddingTop":null},"content":[{"type":"dashField","attrs":{"fieldKey":"title","docid":""}}]}]},"selection":{"type":"text","anchor":2,"head":2},"storedMarks":[]}', "");
         doc.isTemplateDoc = makeTemplate(doc.iconView as any as Doc);
-        doc.iconImageView = new PrefetchProxy(Docs.Create.ImageDocument("http://www.cs.brown.edu/~bcz/face.gif", { title: "data", _width: 50, isTemplateDoc: true, onClick: ScriptField.MakeScript("deiconifyView(this)") }));
+        doc.iconImageView = new PrefetchProxy(Docs.Create.ImageDocument("http://www.cs.brown.edu/~bcz/face.gif", { title: "data", _width: 50, isTemplateDoc: true, onClick: ScriptField.MakeScript("deiconifyView(self)") }));
         doc.isTemplateDoc = makeTemplate(doc.iconImageView as any as Doc, true, "image_icon");
-        doc.iconColView = new PrefetchProxy(Docs.Create.TreeDocument([], { title: "data", _width: 180, _height: 80, isTemplateDoc: true, onClick: ScriptField.MakeScript("deiconifyView(this)") }));
+        doc.iconColView = new PrefetchProxy(Docs.Create.TreeDocument([], { title: "data", _width: 180, _height: 80, isTemplateDoc: true, onClick: ScriptField.MakeScript("deiconifyView(self)") }));
         doc.isTemplateDoc = makeTemplate(doc.iconColView as any as Doc, true, "collection_icon");
         doc.iconViews = Docs.Create.TreeDocument([doc.iconView as any as Doc, doc.iconImageView as any as Doc, doc.iconColView as any as Doc], { title: "icon types", _height: 75 });
     }
-- 
cgit v1.2.3-70-g09d2


From 46581d75b4171a92f0ba61e3ce55a99499864820 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 11 Apr 2020 20:55:46 -0400
Subject: added tap-to-zoom

---
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 49 +++++++++++++++-------
 src/client/views/nodes/DocumentView.tsx            | 25 +++++++----
 2 files changed, 51 insertions(+), 23 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index e4e1a543a..ed6286675 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -74,6 +74,8 @@ export type collectionFreeformViewProps = {
 export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument, undefined as any as collectionFreeformViewProps) {
     private _lastX: number = 0;
     private _lastY: number = 0;
+    private _downX: number = 0;
+    private _downY: number = 0;
     private _inkToTextStartX: number | undefined;
     private _inkToTextStartY: number | undefined;
     private _wordPalette: Map<string, string> = new Map<string, string>();
@@ -322,17 +324,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument, u
             document.removeEventListener("pointerup", this.onPointerUp);
             document.addEventListener("pointermove", this.onPointerMove);
             document.addEventListener("pointerup", this.onPointerUp);
-            // if physically using a pen or we're in pen or highlighter mode
-            // if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || (InkingControl.Instance.selectedTool === InkTool.Highlighter || InkingControl.Instance.selectedTool === InkTool.Pen)) {
-            //     e.stopPropagation();
-            //     e.preventDefault();
-            //     const point = this.getTransform().transformPoint(e.pageX, e.pageY);
-            //     this._points.push({ X: point[0], Y: point[1] });
-            // }
             // if not using a pen and in no ink mode
             if (InkingControl.Instance.selectedTool === InkTool.None) {
-                this._lastX = e.pageX;
-                this._lastY = e.pageY;
+                this._downX = this._lastX = e.pageX;
+                this._downY = this._lastY = e.pageY;
             }
             // eraser plus anything else mode
             else {
@@ -492,10 +487,19 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument, u
         }
     }
 
+    _lastTap = 0;
+
     @action
     onPointerUp = (e: PointerEvent): void => {
         if (InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) return;
 
+        if (this.layoutDoc.targetScale && (Math.abs(e.pageX - this._downX) < 3 && Math.abs(e.pageY - this._downY) < 3)) {
+            if (Date.now() - this._lastTap < 300) {
+                const docpt = this.getTransform().transformPoint(e.clientX, e.clientY);
+                this.scaleAtPt(docpt, NumCast(this.layoutDoc.targetScale, NumCast(this.layoutDoc.scale)));
+            }
+            this._lastTap = Date.now();
+        }
         document.removeEventListener("pointermove", this.onPointerMove);
         document.removeEventListener("pointerup", this.onPointerUp);
         this.removeMoveListeners();
@@ -730,6 +734,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument, u
             e.stopPropagation();
             this.zoom(e.clientX, e.clientY, e.deltaY);
         }
+        this.props.Document.targetScale = NumCast(this.props.Document.scale);
     }
 
     @action
@@ -759,6 +764,17 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument, u
         }
     }
 
+    scaleAtPt(docpt: number[], scale: number) {
+        const screenXY = this.getTransform().inverse().transformPoint(docpt[0], docpt[1]);
+        this.Document.panTransformType = "Ease";
+        this.layoutDoc.scale = scale;
+        const newScreenXY = this.getTransform().inverse().transformPoint(docpt[0], docpt[1]);
+        const scrDelta = { x: screenXY[0] - newScreenXY[0], y: screenXY[1] - newScreenXY[1] };
+        const newpan = this.getTransform().transformDirection(scrDelta.x, scrDelta.y);
+        this.layoutDoc._panX = NumCast(this.layoutDoc._panX) - newpan[0];
+        this.layoutDoc._panY = NumCast(this.layoutDoc._panY) - newpan[1];
+    }
+
     focusDocument = (doc: Doc, willZoom: boolean, scale?: number, afterFocus?: () => boolean) => {
         const state = HistoryUtil.getState();
 
@@ -797,12 +813,17 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument, u
 
             const savedState = { px: this.Document._panX, py: this.Document._panY, s: this.Document.scale, pt: this.Document.panTransformType };
 
-            if (DocListCast(this.dataDoc[this.props.fieldKey]).includes(doc)) {
-                if (!doc.z) this.setPan(newPanX, newPanY, "Ease"); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow
+            if (!willZoom) {
+                Doc.BrushDoc(this.props.Document);
+                !doc.z && this.scaleAtPt([NumCast(doc.x), NumCast(doc.y)], 1);
+            } else {
+                if (DocListCast(this.dataDoc[this.props.fieldKey]).includes(doc)) {
+                    if (!doc.z) this.setPan(newPanX, newPanY, "Ease"); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow
+                }
+                Doc.BrushDoc(this.props.Document);
+                this.props.focus(this.props.Document);
+                willZoom && this.setScaleToZoom(layoutdoc, scale);
             }
-            Doc.BrushDoc(this.props.Document);
-            this.props.focus(this.props.Document);
-            willZoom && this.setScaleToZoom(layoutdoc, scale);
             Doc.linkFollowHighlight(doc);
 
             afterFocus && setTimeout(() => {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 7a47ad10a..1d1f5d3fd 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -286,7 +286,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) {
             let stopPropagate = true;
             let preventDefault = true;
-            this.props.bringToFront(this.props.Document);
+            this.props.Document.isBackground === undefined && this.props.bringToFront(this.props.Document);
             if (this._doubleTap && this.props.renderDepth && !this.onClickHandler?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click
                 const fullScreenAlias = Doc.MakeAlias(this.props.Document);
                 if (StrCast(fullScreenAlias.layoutKey) !== "layout_fullScreen" && fullScreenAlias.layout_fullScreen) {
@@ -313,6 +313,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 if ((this.props.Document.onDragStart || (this.props.Document.rootDocument && this.props.Document.isTemplateForField)) && !(e.ctrlKey || e.button > 0)) {  // onDragStart implies a button doc that we don't want to select when clicking.   RootDocument & isTEmplaetForField implies we're clicking on part of a template instance and we want to select the whole template, not the part
                     stopPropagate = false; // don't stop propagation for field templates -- want the selection to propagate up to the root document of the template
                 } else {
+                    this.props.focus(this.props.Document, false);
                     SelectionManager.SelectDoc(this, e.ctrlKey);
                 }
                 preventDefault = false;
@@ -547,16 +548,21 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         });
         batch.end();
     }
-    static createCustomView = (doc: Doc, creator: (documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc, templateSignature: string = "custom", docLayoutTemplate?: Doc) => {
+    static findTemplate(templateName: string, type: string, signature: string) {
+        let docLayoutTemplate: Opt<Doc>;
         const iconViews = DocListCast(Cast(Doc.UserDoc().iconViews, Doc, null)?.data);
         const templBtns = DocListCast(Cast(Doc.UserDoc().templateButtons, Doc, null)?.data);
         const noteTypes = DocListCast(Cast(Doc.UserDoc().noteTypes, Doc, null)?.data);
         const allTemplates = iconViews.concat(templBtns).concat(noteTypes).map(btnDoc => (btnDoc.dragFactory as Doc) || btnDoc).filter(doc => doc.isTemplateDoc);
-        const templateName = templateSignature.replace(/\(.*\)/, "");
         // bcz: this is hacky -- want to have different templates be applied depending on the "type" of a document.  but type is not reliable and there could be other types of template searches so this should be generalized
         // first try to find a template that matches the specific document type (<typeName>_<templateName>).  otherwise, fallback to a general match on <templateName>
-        !docLayoutTemplate && allTemplates.forEach(tempDoc => StrCast(tempDoc.title) === doc.type + "_" + templateName && (docLayoutTemplate = tempDoc));
+        !docLayoutTemplate && allTemplates.forEach(tempDoc => StrCast(tempDoc.title) === type + "_" + templateName && (docLayoutTemplate = tempDoc));
         !docLayoutTemplate && allTemplates.forEach(tempDoc => StrCast(tempDoc.title) === templateName && (docLayoutTemplate = tempDoc));
+        return docLayoutTemplate;
+    }
+    static createCustomView = (doc: Doc, creator: (documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc, templateSignature: string = "custom", docLayoutTemplate?: Doc) => {
+        const templateName = templateSignature.replace(/\(.*\)/, "");
+        docLayoutTemplate = docLayoutTemplate || DocumentView.findTemplate(templateName, doc.type, templateSignature);
 
         const customName = "layout_" + templateSignature;
         const _width = NumCast(doc._width);
@@ -661,8 +667,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
     @undoBatch
     @action
-    makeBackground = (): void => {
-        this.Document.isBackground = !this.Document.isBackground;
+    toggleBackground = (temporary: boolean): void => {
+        this.Document.overflow = temporary;
+        this.Document.isBackground = !temporary ? !this.Document.isBackground : (this.Document.isBackground ? undefined : true);
         this.Document.isBackground && this.props.bringToFront(this.Document, true);
     }
 
@@ -702,7 +709,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
         const existing = cm.findByDescription("Layout...");
         const layoutItems: ContextMenuProps[] = existing && "subitems" in existing ? existing.subitems : [];
-        layoutItems.push({ description: this.Document.isBackground ? "As Foreground" : "As Background", event: this.makeBackground, icon: this.Document.lockedPosition ? "unlock" : "lock" });
+        layoutItems.push({ description: this.Document.isBackground ? "As Foreground" : "As Background", event: (e) => this.toggleBackground(true), icon: this.Document.lockedPosition ? "unlock" : "lock" });
         layoutItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc), icon: "concierge-bell" });
 
         layoutItems.push({ description: `${this.Document._chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.Document._chromeStatus = (this.Document._chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" });
@@ -1100,7 +1107,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         </>;
     }
     @computed get ignorePointerEvents() {
-        return (this.Document.isBackground && !this.isSelected()) || this.props.layoutKey?.includes("layout_key") || (this.Document.type === DocumentType.INK && InkingControl.Instance.selectedTool !== InkTool.None);
+        return (this.Document.isBackground && !this.isSelected() && !SelectionManager.GetIsDragging()) || this.props.layoutKey?.includes("layout_key") || (this.Document.type === DocumentType.INK && InkingControl.Instance.selectedTool !== InkTool.None);
     }
 
     @observable _animate = 0;
@@ -1146,12 +1153,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 background: finalColor,
                 opacity: this.Document.opacity
             }}>
-            {this.Document.isBackground ? <div className="documentView-lock"> <FontAwesomeIcon icon="unlock" size="lg" /> </div> : (null)}
             {this.onClickHandler && this.props.ContainingCollectionView?.props.Document._viewType === CollectionViewType.Time ? <>
                 {this.innards}
                 <div className="documentView-contentBlocker" />
             </> :
                 this.innards}
+            {this.Document.isBackground !== undefined || this.isSelected(false) ? <div className="documentView-lock" onClick={() => this.toggleBackground(false)}> <FontAwesomeIcon icon={this.Document.isBackground ? "unlock" : "lock"} size="lg" /> </div> : (null)}
         </div>;
         { this._showKPQuery ? <KeyphraseQueryView keyphrases={this._queries}></KeyphraseQueryView> : undefined; }
     }
-- 
cgit v1.2.3-70-g09d2


From 952318fb56cce16c428a5b41afe27a0ee4d20589 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 11 Apr 2020 21:50:07 -0400
Subject: fixed double click on freeformviews. changed signature for
 DocumentHierarchyFromJson() to have an 'all' parameter

---
 src/client/apis/youtube/YoutubeBox.tsx             |  4 +--
 src/client/cognitive_services/CognitiveServices.ts |  2 +-
 src/client/documents/Documents.ts                  | 33 +++++++++++++---------
 .../util/Import & Export/DirectoryImportBox.tsx    |  2 +-
 src/client/util/Import & Export/ImageUtils.ts      |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 15 ++++++----
 src/client/views/nodes/ImageBox.tsx                |  2 +-
 7 files changed, 35 insertions(+), 25 deletions(-)

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

diff --git a/src/client/apis/youtube/YoutubeBox.tsx b/src/client/apis/youtube/YoutubeBox.tsx
index 4b4145fcc..4e990537f 100644
--- a/src/client/apis/youtube/YoutubeBox.tsx
+++ b/src/client/apis/youtube/YoutubeBox.tsx
@@ -156,14 +156,14 @@ export class YoutubeBox extends React.Component<FieldViewProps> {
     @action
     processVideoDetails = (videoDetails: any[]) => {
         this.videoDetails = videoDetails;
-        this.props.Document.cachedDetails = Docs.Get.DocumentHierarchyFromJson(videoDetails, "detailBackUp");
+        this.props.Document.cachedDetails = Docs.Get.DocumentHierarchyFromJson(videoDetails, "detailBackUp", undefined, false);
     }
 
     /**
      * The function that stores the search results in the props document.
      */
     backUpSearchResults = (videos: any[]) => {
-        this.props.Document.cachedSearchResults = Docs.Get.DocumentHierarchyFromJson(videos, "videosBackUp");
+        this.props.Document.cachedSearchResults = Docs.Get.DocumentHierarchyFromJson(videos, "videosBackUp", undefined, false);
     }
 
     /**
diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts
index 3f3726621..e464aff55 100644
--- a/src/client/cognitive_services/CognitiveServices.ts
+++ b/src/client/cognitive_services/CognitiveServices.ts
@@ -192,7 +192,7 @@ export namespace CognitiveServices {
                 let results = await ExecuteQuery(Service.Handwriting, Manager, inkData);
                 if (results) {
                     results.recognitionUnits && (results = results.recognitionUnits);
-                    target[keys[0]] = Docs.Get.DocumentHierarchyFromJson(results, "Ink Analysis");
+                    target[keys[0]] = Docs.Get.DocumentHierarchyFromJson(results, "Ink Analysis", undefined, false);
                     const recognizedText = results.map((item: any) => item.recognizedText);
                     const recognizedObjects = results.map((item: any) => item.recognizedObject);
                     const individualWords = recognizedText.filter((text: string) => text && text.split(" ").length === 1);
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index b5c6dc06a..30f72a6b2 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -116,7 +116,7 @@ export interface DocumentOptions {
     borderRounding?: string;
     boxShadow?: string;
     dontRegisterChildren?: boolean;
-    "onClick-rawScript"?:string; // onClick script in raw text form
+    "onClick-rawScript"?: string; // onClick script in raw text form
     _pivotField?: string; // field key used to determine headings for sections in stacking, masonry, pivot views
     schemaColumns?: List<SchemaHeaderField>;
     dockingConfig?: string;
@@ -405,7 +405,7 @@ export namespace Docs {
                     const doc = StackingDocument(deviceImages, { title: device.title, _LODdisable: true });
                     const deviceProto = Doc.GetProto(doc);
                     deviceProto.hero = new ImageField(constructed[0].url);
-                    Docs.Get.DocumentHierarchyFromJson(device, undefined, deviceProto);
+                    Docs.Get.DocumentHierarchyFromJson(device, undefined, deviceProto, false);
                     Doc.AddDocToList(parentProto, "data", doc);
                 } else if (errors) {
                     console.log(errors);
@@ -732,19 +732,21 @@ export namespace Docs {
          * @param input for convenience and flexibility, either a valid JSON string to be parsed,
          * or the result of any JSON.parse() call.
          * @param title an optional title to give to the highest parent document in the hierarchy
+         * @param appendToTarget -???
+         * @param all whether fields should be converted even if they contain no data
          */
-        export function DocumentHierarchyFromJson(input: any, title?: string, appendToTarget?: Doc): Opt<Doc> {
+        export function DocumentHierarchyFromJson(input: any, title: string, appendToTarget: Opt<Doc>, all?:boolean): Opt<Doc> {
             if (input === undefined || input === null || ![...primitives, "object"].includes(typeof input)) {
                 return undefined;
             }
             input = JSON.parse(typeof input === "string" ? input : JSON.stringify(input));
-            let converted: Doc;
+            let converted: Opt<Doc>;
             if (typeof input === "object" && !(input instanceof Array)) {
-                converted = convertObject(input, title, appendToTarget);
+                converted = convertObject(input, title, appendToTarget, all);
             } else {
                 (converted = new Doc).json = toField(input);
             }
-            title && (converted.title = title);
+            title && converted && (converted.title = title);
             return converted;
         }
 
@@ -755,12 +757,15 @@ export namespace Docs {
          * @returns the object mapped from JSON to field values, where each mapping 
          * might involve arbitrary recursion (since toField might itself call convertObject)
          */
-        const convertObject = (object: any, title?: string, target?: Doc): Doc => {
-            const resolved = target ?? new Doc;
-            let result: Opt<Field>;
-            Object.keys(object).map(key => (result = toField(object[key], key)) && (resolved[key] = result));
-            title && !resolved.title && (resolved.title = title);
-            return resolved;
+        const convertObject = (object: any, title?: string, target?: Doc, all?:boolean): Opt<Doc> => {
+            const objkeys = Object.keys(object);
+            if (objkeys.length || all) {
+                const resolved = target ?? new Doc;
+                let result: Opt<Field>;
+                Object.keys(object).map(key => (result = toField(object[key], key)) && (resolved[key] = result));
+                title && !resolved.title && (resolved.title = title);
+                return resolved;
+            }
         };
 
         /**
@@ -778,7 +783,7 @@ export namespace Docs {
         };
 
 
-        const toField = (data: any, title?: string): Opt<Field> => {
+        const toField = (data: any, title?: string, all?:boolean): Opt<Field> => {
             if (data === null || data === undefined) {
                 return undefined;
             }
@@ -786,7 +791,7 @@ export namespace Docs {
                 return data;
             }
             if (typeof data === "object") {
-                return data instanceof Array ? convertList(data) : convertObject(data, title);
+                return data instanceof Array ? convertList(data) : convertObject(data, title, undefined, all);
             }
             throw new Error(`How did ${data} of type ${typeof data} end up in JSON?`);
         };
diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx
index 3d8bcbab7..01a0eddf8 100644
--- a/src/client/util/Import & Export/DirectoryImportBox.tsx	
+++ b/src/client/util/Import & Export/DirectoryImportBox.tsx	
@@ -126,7 +126,7 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
             const document = await Docs.Get.DocumentFromType(type, path, { _width: 300, title: name });
             const { data, error } = exifData;
             if (document) {
-                Doc.GetProto(document).exif = error || Docs.Get.DocumentHierarchyFromJson(data);
+                Doc.GetProto(document).exif = error || Docs.Get.DocumentHierarchyFromJson(data, "", undefined, false);
                 docs.push(document);
             }
         }));
diff --git a/src/client/util/Import & Export/ImageUtils.ts b/src/client/util/Import & Export/ImageUtils.ts
index ab8c73d15..135454a33 100644
--- a/src/client/util/Import & Export/ImageUtils.ts	
+++ b/src/client/util/Import & Export/ImageUtils.ts	
@@ -20,7 +20,7 @@ export namespace ImageUtils {
             nativeHeight,
             exifData: { error, data }
         } = await Networking.PostToServer("/inspectImage", { source });
-        document.exif = error || Docs.Get.DocumentHierarchyFromJson(data);
+        document.exif = error || Docs.Get.DocumentHierarchyFromJson(data, "", undefined, false);
         const proto = Doc.GetProto(document);
         proto["data-nativeWidth"] = nativeWidth;
         proto["data-nativeHeight"] = nativeHeight;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index ed6286675..f27b37e2c 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -493,17 +493,22 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument, u
     onPointerUp = (e: PointerEvent): void => {
         if (InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) return;
 
+        document.removeEventListener("pointermove", this.onPointerMove);
+        document.removeEventListener("pointerup", this.onPointerUp);
+        this.removeMoveListeners();
+        this.removeEndListeners();
+    }
+
+    onClick = (e: React.MouseEvent) => {
         if (this.layoutDoc.targetScale && (Math.abs(e.pageX - this._downX) < 3 && Math.abs(e.pageY - this._downY) < 3)) {
             if (Date.now() - this._lastTap < 300) {
                 const docpt = this.getTransform().transformPoint(e.clientX, e.clientY);
                 this.scaleAtPt(docpt, NumCast(this.layoutDoc.targetScale, NumCast(this.layoutDoc.scale)));
+                e.stopPropagation();
+                e.preventDefault();
             }
             this._lastTap = Date.now();
         }
-        document.removeEventListener("pointermove", this.onPointerMove);
-        document.removeEventListener("pointerup", this.onPointerUp);
-        this.removeMoveListeners();
-        this.removeEndListeners();
     }
 
     @action
@@ -1129,7 +1134,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument, u
         // otherwise, they are stored in fieldKey.  All annotations to this document are stored in the extension document
         return <div className={"collectionfreeformview-container"}
             ref={this.createDashEventsTarget}
-            onWheel={this.onPointerWheel}//pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined,
+            onWheel={this.onPointerWheel} onClick={this.onClick}  //pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined,
             onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onExternalDrop.bind(this)} onContextMenu={this.onContextMenu}
             style={{
                 pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined,
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 5b0edf34f..c45f14975 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -187,7 +187,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
     extractFaces = () => {
         const converter = (results: any) => {
             const faceDocs = new List<Doc>();
-            results.reduce((face: CognitiveServices.Image.Face, faceDocs: List<Doc>) => faceDocs.push(Docs.Get.DocumentHierarchyFromJson(face, `Face: ${face.faceId}`)!), new List<Doc>());
+            results.reduce((face: CognitiveServices.Image.Face, faceDocs: List<Doc>) => faceDocs.push(Docs.Get.DocumentHierarchyFromJson(face, `Face: ${face.faceId}`, undefined, false)!), new List<Doc>());
             return faceDocs;
         };
         this.url && CognitiveServices.Image.Appliers.ProcessImage(this.dataDoc, [this.fieldKey + "-faces"], this.url, Service.Face, converter);
-- 
cgit v1.2.3-70-g09d2


From bd70f0e07e7d578c4ecb544f0e5b1ad16132fc05 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sun, 12 Apr 2020 00:21:38 -0400
Subject: added arrowkey nudging of freeform view. fixed background overflow.

---
 src/client/documents/Documents.ts                  |  8 +--
 src/client/util/DragManager.ts                     |  1 -
 src/client/views/PreviewCursor.tsx                 | 15 ++++-
 .../collectionFreeForm/CollectionFreeFormView.scss |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 64 ++++++++++++----------
 .../collections/collectionFreeForm/MarqueeView.tsx |  5 +-
 src/client/views/nodes/DocumentView.tsx            |  8 +--
 7 files changed, 60 insertions(+), 43 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 30f72a6b2..e3e3d895b 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -405,7 +405,7 @@ export namespace Docs {
                     const doc = StackingDocument(deviceImages, { title: device.title, _LODdisable: true });
                     const deviceProto = Doc.GetProto(doc);
                     deviceProto.hero = new ImageField(constructed[0].url);
-                    Docs.Get.DocumentHierarchyFromJson(device, undefined, deviceProto, false);
+                    Docs.Get.DocumentHierarchyFromJson(device, "", deviceProto, false);
                     Doc.AddDocToList(parentProto, "data", doc);
                 } else if (errors) {
                     console.log(errors);
@@ -735,7 +735,7 @@ export namespace Docs {
          * @param appendToTarget -???
          * @param all whether fields should be converted even if they contain no data
          */
-        export function DocumentHierarchyFromJson(input: any, title: string, appendToTarget: Opt<Doc>, all?:boolean): Opt<Doc> {
+        export function DocumentHierarchyFromJson(input: any, title: string, appendToTarget: Opt<Doc>, all?: boolean): Opt<Doc> {
             if (input === undefined || input === null || ![...primitives, "object"].includes(typeof input)) {
                 return undefined;
             }
@@ -757,7 +757,7 @@ export namespace Docs {
          * @returns the object mapped from JSON to field values, where each mapping 
          * might involve arbitrary recursion (since toField might itself call convertObject)
          */
-        const convertObject = (object: any, title?: string, target?: Doc, all?:boolean): Opt<Doc> => {
+        const convertObject = (object: any, title?: string, target?: Doc, all?: boolean): Opt<Doc> => {
             const objkeys = Object.keys(object);
             if (objkeys.length || all) {
                 const resolved = target ?? new Doc;
@@ -783,7 +783,7 @@ export namespace Docs {
         };
 
 
-        const toField = (data: any, title?: string, all?:boolean): Opt<Field> => {
+        const toField = (data: any, title?: string, all?: boolean): Opt<Field> => {
             if (data === null || data === undefined) {
                 return undefined;
             }
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index b28bac125..3e9a5b63a 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -419,7 +419,6 @@ export namespace DragManager {
         if (target) {
             const complete = new DragCompleteEvent(false, dragData);
             finishDrag?.(complete);
-            console.log(complete.aborted);
             target.dispatchEvent(
                 new CustomEvent<DropEvent>("dashOnDrop", {
                     bubbles: true,
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index c011adb20..fad2f2f0a 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -13,6 +13,7 @@ export class PreviewCursor extends React.Component<{}> {
     static _getTransform: () => Transform;
     static _addLiveTextDoc: (doc: Doc) => void;
     static _addDocument: (doc: Doc) => boolean;
+    static _nudge: (x: number, y: number) => void;
     @observable static _clickPoint = [0, 0];
     @observable public static Visible = false;
     constructor(props: any) {
@@ -85,9 +86,17 @@ export class PreviewCursor extends React.Component<{}> {
             !e.key.startsWith("Arrow") &&
             !e.defaultPrevented) {
             if ((!e.ctrlKey || (e.keyCode >= 48 && e.keyCode <= 57)) && !e.metaKey) {//  /^[a-zA-Z0-9$*^%#@+-=_|}{[]"':;?/><.,}]$/.test(e.key)) {
-                PreviewCursor.Visible && PreviewCursor._onKeyPress && PreviewCursor._onKeyPress(e);
+                PreviewCursor.Visible && PreviewCursor._onKeyPress?.(e);
                 PreviewCursor.Visible = false;
             }
+        } else if (e.key === "ArrowRight") {
+            PreviewCursor._nudge?.(1, 0);
+        } else if (e.key === "ArrowLeft") {
+            PreviewCursor._nudge?.(-1, 0);
+        } else if (e.key === "ArrowUp") {
+            PreviewCursor._nudge?.(0, 1);
+        } else if (e.key === "ArrowDown") {
+            PreviewCursor._nudge?.(0, -1);
         }
     }
 
@@ -101,12 +110,14 @@ export class PreviewCursor extends React.Component<{}> {
         onKeyPress: (e: KeyboardEvent) => void,
         addLiveText: (doc: Doc) => void,
         getTransform: () => Transform,
-        addDocument: (doc: Doc) => boolean) {
+        addDocument: (doc: Doc) => boolean,
+        nudge: (nudgeX: number, nudgeY: number) => void) {
         this._clickPoint = [x, y];
         this._onKeyPress = onKeyPress;
         this._addLiveTextDoc = addLiveText;
         this._getTransform = getTransform;
         this._addDocument = addDocument;
+        this._nudge = nudge;
         this.Visible = true;
     }
     render() {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index 730392ab5..e1516b468 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -12,7 +12,7 @@
 }
 
 .collectionfreeformview-ease {
-    transition: transform 1s;
+    transition: transform 500ms;
 }
 
 .collectionfreeformview-none {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index f27b37e2c..7dca19073 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -518,31 +518,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument, u
         PDFMenu.Instance.fadeOut(true);
 
         const [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
-        let x = (this.Document._panX || 0) - dx;
-        let y = (this.Document._panY || 0) - dy;
-        if (!this.isAnnotationOverlay) {
-            // this section wraps the pan position, horizontally and/or vertically whenever the content is panned out of the viewing bounds
-            const docs = this.childLayoutPairs.filter(pair => pair.layout instanceof Doc).map(pair => pair.layout);
-            const measuredDocs = docs.filter(doc => doc && this.childDataProvider(doc)).map(doc => this.childDataProvider(doc));
-            if (measuredDocs.length) {
-                const ranges = measuredDocs.reduce(({ xrange, yrange }, { x, y, width, height }) =>  // computes range of content
-                    ({
-                        xrange: { min: Math.min(xrange.min, x), max: Math.max(xrange.max, x + width) },
-                        yrange: { min: Math.min(yrange.min, y), max: Math.max(yrange.max, y + height) }
-                    })
-                    , {
-                        xrange: { min: Number.MAX_VALUE, max: -Number.MAX_VALUE },
-                        yrange: { min: Number.MAX_VALUE, max: -Number.MAX_VALUE }
-                    });
-
-                const panelDim = [this.props.PanelWidth() / this.zoomScaling(), this.props.PanelHeight() / this.zoomScaling()];
-                if (ranges.xrange.min > (this.panX() + panelDim[0] / 2)) x = ranges.xrange.max + panelDim[0] / 2;  // snaps pan position of range of content goes out of bounds
-                if (ranges.xrange.max < (this.panX() - panelDim[0] / 2)) x = ranges.xrange.min - panelDim[0] / 2;
-                if (ranges.yrange.min > (this.panY() + panelDim[1] / 2)) y = ranges.yrange.max + panelDim[1] / 2;
-                if (ranges.yrange.max < (this.panY() - panelDim[1] / 2)) y = ranges.yrange.min - panelDim[1] / 2;
-            }
-        }
-        this.setPan(x, y);
+        this.setPan((this.Document._panX || 0) - dx, (this.Document._panY || 0) - dy, undefined, true);
         this._lastX = e.clientX;
         this._lastY = e.clientY;
     }
@@ -743,7 +719,29 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument, u
     }
 
     @action
-    setPan(panX: number, panY: number, panType: string = "None") {
+    setPan(panX: number, panY: number, panType: string = "None", clamp: boolean = false) {
+        if (!this.isAnnotationOverlay && clamp) {
+            // this section wraps the pan position, horizontally and/or vertically whenever the content is panned out of the viewing bounds
+            const docs = this.childLayoutPairs.filter(pair => pair.layout instanceof Doc).map(pair => pair.layout);
+            const measuredDocs = docs.filter(doc => doc && this.childDataProvider(doc)).map(doc => this.childDataProvider(doc));
+            if (measuredDocs.length) {
+                const ranges = measuredDocs.reduce(({ xrange, yrange }, { x, y, width, height }) =>  // computes range of content
+                    ({
+                        xrange: { min: Math.min(xrange.min, x), max: Math.max(xrange.max, x + width) },
+                        yrange: { min: Math.min(yrange.min, y), max: Math.max(yrange.max, y + height) }
+                    })
+                    , {
+                        xrange: { min: Number.MAX_VALUE, max: -Number.MAX_VALUE },
+                        yrange: { min: Number.MAX_VALUE, max: -Number.MAX_VALUE }
+                    });
+
+                const panelDim = [this.props.PanelWidth() / this.zoomScaling(), this.props.PanelHeight() / this.zoomScaling()];
+                if (ranges.xrange.min >= (panX + panelDim[0] / 2)) panX = ranges.xrange.max + panelDim[0] / 2;  // snaps pan position of range of content goes out of bounds
+                else if (ranges.xrange.max <= (panX - panelDim[0] / 2)) panX = ranges.xrange.min - panelDim[0] / 2;
+                if (ranges.yrange.min >= (panY + panelDim[1] / 2)) panY = ranges.yrange.max + panelDim[1] / 2;
+                else if (ranges.yrange.max <= (panY - panelDim[1] / 2)) panY = ranges.yrange.min - panelDim[1] / 2;
+            }
+        }
         if (!this.Document.lockedTransform || this.Document.inOverlay) {
             this.Document.panTransformType = panType;
             const scale = this.getLocalTransform().inverse().Scale;
@@ -823,7 +821,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument, u
                 !doc.z && this.scaleAtPt([NumCast(doc.x), NumCast(doc.y)], 1);
             } else {
                 if (DocListCast(this.dataDoc[this.props.fieldKey]).includes(doc)) {
-                    if (!doc.z) this.setPan(newPanX, newPanY, "Ease"); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow
+                    if (!doc.z) this.setPan(newPanX, newPanY, "Ease", true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow
                 }
                 Doc.BrushDoc(this.props.Document);
                 this.props.focus(this.props.Document);
@@ -838,7 +836,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument, u
                     this.Document.scale = savedState.s;
                     this.Document.panTransformType = savedState.pt;
                 }
-            }, 1000);
+            }, 500);
         }
 
     }
@@ -1104,8 +1102,16 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument, u
             <span className="collectionfreeformview-placeholderSpan">{this.props.Document.title?.toString()}</span>
         </div>;
     }
+
+    _nudgeTime = 0;
+    nudge = action((x: number, y: number) => {
+        this.setPan(NumCast(this.layoutDoc._panX) + this.props.PanelWidth() / 2 * x / this.zoomScaling(),
+            NumCast(this.layoutDoc._panY) + this.props.PanelHeight() / 2 * (-y) / this.zoomScaling(), "Ease", true);
+        this._nudgeTime = Date.now();
+        setTimeout(() => (Date.now() - this._nudgeTime >= 500) && (this.Document.panTransformType = undefined), 500);
+    });
     @computed get marqueeView() {
-        return <MarqueeView {...this.props} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments} addDocument={this.addDocument}
+        return <MarqueeView {...this.props} nudge={this.nudge} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments} addDocument={this.addDocument}
             addLiveTextDocument={this.addLiveTextBox} getContainerTransform={this.getContainerTransform} getTransform={this.getTransform} isAnnotationOverlay={this.isAnnotationOverlay}>
             <CollectionFreeFormViewPannableContents centeringShiftX={this.centeringShiftX} centeringShiftY={this.centeringShiftY}
                 easing={this.easing} zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}>
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 96a7c4fc0..5a6c8573b 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -31,6 +31,7 @@ interface MarqueeViewProps {
     addLiveTextDocument: (doc: Doc) => void;
     isSelected: () => boolean;
     isAnnotationOverlay?: boolean;
+    nudge:(x:number, y:number) => void;
     setPreviewCursor?: (func: (x: number, y: number, drag: boolean) => void) => void;
 }
 
@@ -46,7 +47,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
     _commandExecuted = false;
 
     componentDidMount() {
-        this.props.setPreviewCursor && this.props.setPreviewCursor(this.setPreviewCursor);
+        this.props.setPreviewCursor?.(this.setPreviewCursor);
     }
 
     @action
@@ -243,7 +244,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         } else {
             this._downX = x;
             this._downY = y;
-            PreviewCursor.Show(x, y, this.onKeyPress, this.props.addLiveTextDocument, this.props.getTransform, this.props.addDocument);
+            PreviewCursor.Show(x, y, this.onKeyPress, this.props.addLiveTextDocument, this.props.getTransform, this.props.addDocument, this.props.nudge);
         }
     });
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 1d1f5d3fd..05ff5a371 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -562,7 +562,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     }
     static createCustomView = (doc: Doc, creator: (documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc, templateSignature: string = "custom", docLayoutTemplate?: Doc) => {
         const templateName = templateSignature.replace(/\(.*\)/, "");
-        docLayoutTemplate = docLayoutTemplate || DocumentView.findTemplate(templateName, doc.type, templateSignature);
+        docLayoutTemplate = docLayoutTemplate || DocumentView.findTemplate(templateName, StrCast(doc.type), templateSignature);
 
         const customName = "layout_" + templateSignature;
         const _width = NumCast(doc._width);
@@ -668,7 +668,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     @undoBatch
     @action
     toggleBackground = (temporary: boolean): void => {
-        this.Document.overflow = temporary;
+        this.Document.overflow = temporary ? "visible" : "hidden";
         this.Document.isBackground = !temporary ? !this.Document.isBackground : (this.Document.isBackground ? undefined : true);
         this.Document.isBackground && this.props.bringToFront(this.Document, true);
     }
@@ -709,7 +709,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
         const existing = cm.findByDescription("Layout...");
         const layoutItems: ContextMenuProps[] = existing && "subitems" in existing ? existing.subitems : [];
-        layoutItems.push({ description: this.Document.isBackground ? "As Foreground" : "As Background", event: (e) => this.toggleBackground(true), icon: this.Document.lockedPosition ? "unlock" : "lock" });
+        layoutItems.push({ description: this.Document.isBackground ? "As Foreground" : "As Background", event: (e) => this.toggleBackground(false), icon: this.Document.lockedPosition ? "unlock" : "lock" });
         layoutItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc), icon: "concierge-bell" });
 
         layoutItems.push({ description: `${this.Document._chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.Document._chromeStatus = (this.Document._chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" });
@@ -1158,7 +1158,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 <div className="documentView-contentBlocker" />
             </> :
                 this.innards}
-            {this.Document.isBackground !== undefined || this.isSelected(false) ? <div className="documentView-lock" onClick={() => this.toggleBackground(false)}> <FontAwesomeIcon icon={this.Document.isBackground ? "unlock" : "lock"} size="lg" /> </div> : (null)}
+            {this.Document.isBackground !== undefined || this.isSelected(false) ? <div className="documentView-lock" onClick={() => this.toggleBackground(true)}> <FontAwesomeIcon icon={this.Document.isBackground ? "unlock" : "lock"} size="lg" /> </div> : (null)}
         </div>;
         { this._showKPQuery ? <KeyphraseQueryView keyphrases={this._queries}></KeyphraseQueryView> : undefined; }
     }
-- 
cgit v1.2.3-70-g09d2


From b235c94614141752c98d24a395682a02297d9e6a Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 13 Apr 2020 11:15:59 -0400
Subject: cleaned up rootSelected() to work with templates.  adjusted nudge to
 favor moving documents over panning collection

---
 src/client/views/DocComponent.tsx                       |  4 ++--
 src/client/views/PreviewCursor.tsx                      | 14 +++++---------
 src/client/views/collections/CollectionSubView.tsx      |  2 +-
 src/client/views/collections/CollectionView.tsx         |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx       | 17 +++++++++++------
 .../collections/collectionFreeForm/MarqueeView.tsx      |  2 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx      |  4 ++--
 src/client/views/nodes/DocumentView.tsx                 | 16 +++++++++-------
 8 files changed, 32 insertions(+), 29 deletions(-)

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

diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index b1bd4191c..f19f9308a 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -53,7 +53,7 @@ export function ViewBoxBaseComponent<P extends ViewBoxBaseProps, T>(schemaCtor:
         // key where data is stored
         @computed get fieldKey() { return this.props.fieldKey; }
 
-        active = (outsideReaction?: boolean) => !this.props.Document.isBackground && ((this.props.Document.forceActive && this.props.rootSelected(outsideReaction)) || this.props.isSelected(outsideReaction) || this.props.renderDepth === 0);//  && !InkingControl.Instance.selectedTool;  // bcz: inking state shouldn't affect static tools 
+        active = (outsideReaction?: boolean) => !this.props.Document.isBackground && (this.props.rootSelected(outsideReaction) || this.props.isSelected(outsideReaction) || this.props.renderDepth === 0);//  && !InkingControl.Instance.selectedTool;  // bcz: inking state shouldn't affect static tools 
         protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
     }
     return Component;
@@ -114,7 +114,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
 
         whenActiveChanged = action((isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive));
         active = (outsideReaction?: boolean) => ((InkingControl.Instance.selectedTool === InkTool.None && !this.props.Document.isBackground) &&
-            ((this.props.Document.forceActive && this.props.rootSelected(outsideReaction)) || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false)
+            (this.props.rootSelected(outsideReaction) || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false)
         annotationsActive = (outsideReaction?: boolean) => (InkingControl.Instance.selectedTool !== InkTool.None || (this.props.Document.isBackground && this.props.active()) ||
             (this.props.Document.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false)
     }
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index ecebadd2a..92ba13d2f 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -13,7 +13,7 @@ export class PreviewCursor extends React.Component<{}> {
     static _getTransform: () => Transform;
     static _addLiveTextDoc: (doc: Doc) => void;
     static _addDocument: (doc: Doc) => boolean;
-    static _nudge: (x: number, y: number) => void;
+    static _nudge: (x: number, y: number) => boolean;
     @observable static _clickPoint = [0, 0];
     @observable public static Visible = false;
     constructor(props: any) {
@@ -91,17 +91,13 @@ export class PreviewCursor extends React.Component<{}> {
             }
         } else if (PreviewCursor.Visible) {
             if (e.key === "ArrowRight") {
-                PreviewCursor._nudge?.(1 * (e.shiftKey ? 2 : 1), 0);
-                e.stopPropagation();
+                PreviewCursor._nudge?.(1 * (e.shiftKey ? 2 : 1), 0) && e.stopPropagation();
             } else if (e.key === "ArrowLeft") {
-                PreviewCursor._nudge?.(-1 * (e.shiftKey ? 2 : 1), 0);
-                e.stopPropagation();
+                PreviewCursor._nudge?.(-1 * (e.shiftKey ? 2 : 1), 0) && e.stopPropagation();
             } else if (e.key === "ArrowUp") {
-                PreviewCursor._nudge?.(0, 1 * (e.shiftKey ? 2 : 1));
-                e.stopPropagation();
+                PreviewCursor._nudge?.(0, 1 * (e.shiftKey ? 2 : 1)) && e.stopPropagation();
             } else if (e.key === "ArrowDown") {
-                PreviewCursor._nudge?.(0, -1 * (e.shiftKey ? 2 : 1));
-                e.stopPropagation();
+                PreviewCursor._nudge?.(0, -1 * (e.shiftKey ? 2 : 1)) && e.stopPropagation();
             }
         }
     }
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 6b7c3de56..37cf942c6 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -100,7 +100,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
         }
 
         rootSelected = (outsideReaction?: boolean) => {
-            return this.props.isSelected(outsideReaction) || (this.props.Document.rootDocument || this.props.Document.forceActive ? this.props.rootSelected(outsideReaction) : false);
+            return this.props.isSelected(outsideReaction) || (this.rootDoc && this.props.rootSelected(outsideReaction));
         }
 
         // The data field for rendering this collection will be on the this.props.Document unless we're rendering a template in which case we try to use props.DataDoc.
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 821840e0b..86df9c080 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -102,7 +102,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
         return viewField as any as CollectionViewType;
     }
 
-    active = (outsideReaction?: boolean) => this.props.isSelected(outsideReaction) || (this.props.rootSelected(outsideReaction) && BoolCast(this.props.Document.forceActive)) || this._isChildActive || this.props.renderDepth === 0;
+    active = (outsideReaction?: boolean) => this.props.isSelected(outsideReaction) || this.props.rootSelected(outsideReaction) || this.props.Document.forceActive || this._isChildActive || this.props.renderDepth === 0;
 
     whenActiveChanged = (isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive);
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 7dca19073..a77f09163 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -15,7 +15,7 @@ import { ScriptField } from "../../../../new_fields/ScriptField";
 import { BoolCast, Cast, FieldValue, NumCast, ScriptCast, StrCast } from "../../../../new_fields/Types";
 import { TraceMobx } from "../../../../new_fields/util";
 import { GestureUtils } from "../../../../pen-gestures/GestureUtils";
-import { aggregateBounds, intersectRect, returnOne, Utils, returnZero } from "../../../../Utils";
+import { aggregateBounds, intersectRect, returnOne, Utils, returnZero, returnFalse } from "../../../../Utils";
 import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
 import { DocServer } from "../../../DocServer";
 import { Docs } from "../../../documents/Documents";
@@ -43,6 +43,7 @@ import "./CollectionFreeFormView.scss";
 import MarqueeOptionsMenu from "./MarqueeOptionsMenu";
 import { MarqueeView } from "./MarqueeView";
 import React = require("react");
+import { CollectionViewType } from "../CollectionView";
 
 library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload);
 
@@ -860,7 +861,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument, u
             LibraryPath: this.libraryPath,
             FreezeDimensions: this.props.freezeChildDimensions,
             layoutKey: undefined,
-            rootSelected: this.rootSelected,
+            rootSelected: childData ? this.rootSelected : returnFalse,
             dropAction: StrCast(this.props.Document.childDropAction) as dropActionType,
             //onClick: undefined, // this.props.onClick,  // bcz: check this out -- I don't think we want to inherit click handlers, or we at least need a way to ignore them
             onClick: this.onChildClickHandler,
@@ -1105,10 +1106,14 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument, u
 
     _nudgeTime = 0;
     nudge = action((x: number, y: number) => {
-        this.setPan(NumCast(this.layoutDoc._panX) + this.props.PanelWidth() / 2 * x / this.zoomScaling(),
-            NumCast(this.layoutDoc._panY) + this.props.PanelHeight() / 2 * (-y) / this.zoomScaling(), "Ease", true);
-        this._nudgeTime = Date.now();
-        setTimeout(() => (Date.now() - this._nudgeTime >= 500) && (this.Document.panTransformType = undefined), 500);
+        if (this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Freeform) { // bcz: this isn't ideal, but want to try it out...
+            this.setPan(NumCast(this.layoutDoc._panX) + this.props.PanelWidth() / 2 * x / this.zoomScaling(),
+                NumCast(this.layoutDoc._panY) + this.props.PanelHeight() / 2 * (-y) / this.zoomScaling(), "Ease", true);
+            this._nudgeTime = Date.now();
+            setTimeout(() => (Date.now() - this._nudgeTime >= 500) && (this.Document.panTransformType = undefined), 500);
+            return true;
+        }
+        return false;
     });
     @computed get marqueeView() {
         return <MarqueeView {...this.props} nudge={this.nudge} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments} addDocument={this.addDocument}
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 5a6c8573b..9dd3d640b 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -31,7 +31,7 @@ interface MarqueeViewProps {
     addLiveTextDocument: (doc: Doc) => void;
     isSelected: () => boolean;
     isAnnotationOverlay?: boolean;
-    nudge:(x:number, y:number) => void;
+    nudge: (x: number, y: number) => boolean;
     setPreviewCursor?: (func: (x: number, y: number, drag: boolean) => void) => void;
 }
 
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 3503af442..e7cb2c015 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -60,8 +60,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         return undefined;
     }
     nudge = (x: number, y: number) => {
-        this.layoutDoc.x = NumCast(this.layoutDoc.x) + x;
-        this.layoutDoc.y = NumCast(this.layoutDoc.y) + y;
+        this.props.Document.x = NumCast(this.props.Document.x) + x;
+        this.props.Document.y = NumCast(this.props.Document.y) + y;
     }
 
     contentScaling = () => this.nativeWidth > 0 && !this.props.fitToBox && !this.freezeDimensions ? this.width / this.nativeWidth : 1;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 3784c5c9c..cc3c39154 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -289,13 +289,15 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             let preventDefault = true;
             this.props.Document.isBackground === undefined && this.props.bringToFront(this.props.Document);
             if (this._doubleTap && this.props.renderDepth && !this.onClickHandler?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click
-                const fullScreenAlias = Doc.MakeAlias(this.props.Document);
-                if (StrCast(fullScreenAlias.layoutKey) !== "layout_fullScreen" && fullScreenAlias.layout_fullScreen) {
-                    fullScreenAlias.layoutKey = "layout_fullScreen";
+                if (!(e.nativeEvent as any).formattedHandled) {
+                    const fullScreenAlias = Doc.MakeAlias(this.props.Document);
+                    if (StrCast(fullScreenAlias.layoutKey) !== "layout_fullScreen" && fullScreenAlias.layout_fullScreen) {
+                        fullScreenAlias.layoutKey = "layout_fullScreen";
+                    }
+                    UndoManager.RunInBatch(() => this.props.addDocTab(fullScreenAlias, "inTab"), "double tap");
+                    SelectionManager.DeselectAll();
+                    Doc.UnBrushDoc(this.props.Document);
                 }
-                UndoManager.RunInBatch(() => this.props.addDocTab(fullScreenAlias, "inTab"), "double tap");
-                SelectionManager.DeselectAll();
-                Doc.UnBrushDoc(this.props.Document);
             } else if (this.onClickHandler?.script) {
                 SelectionManager.DeselectAll();
                 const func = () => this.onClickHandler.script.run({
@@ -978,7 +980,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         return typeof fallback === "string" ? fallback : "layout";
     }
     rootSelected = (outsideReaction?: boolean) => {
-        return this.isSelected(outsideReaction) || (this.props.Document.forceActive && this.props.rootSelected?.(outsideReaction) ? true : false);
+        return this.isSelected(outsideReaction) || (this.rootDoc && this.props.rootSelected?.(outsideReaction));
     }
     childScaling = () => (this.layoutDoc._fitWidth ? this.props.PanelWidth() / this.nativeWidth : this.props.ContentScaling());
     panelWidth = () => this.props.PanelWidth();
-- 
cgit v1.2.3-70-g09d2


From d5c0ed9a29593583a4180973f854106dc10d4b30 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 15 Apr 2020 02:32:47 -0400
Subject: moved filter to right side of collection.  got rid of back button in
 pivot view - click background.

---
 src/Utils.ts                                       | 10 +++--
 .../views/collections/CollectionTimeView.tsx       | 30 +++++++--------
 src/client/views/collections/CollectionView.scss   |  4 +-
 src/client/views/collections/CollectionView.tsx    | 43 +++++++++++++++-------
 .../views/collections/CollectionViewChromes.scss   |  8 +++-
 .../views/collections/CollectionViewChromes.tsx    | 12 +++---
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  2 +
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  3 +-
 8 files changed, 69 insertions(+), 43 deletions(-)

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

diff --git a/src/Utils.ts b/src/Utils.ts
index a8cde0624..58f272ba5 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -475,7 +475,9 @@ export function setupMoveUpEvents(
     e: React.PointerEvent,
     moveEvent: (e: PointerEvent, down: number[], delta: number[]) => boolean,
     upEvent: (e: PointerEvent) => void,
-    clickEvent: (e: PointerEvent) => void) {
+    clickEvent: (e: PointerEvent) => void,
+    stopPropagation: boolean = true
+) {
     (target as any)._downX = (target as any)._lastX = e.clientX;
     (target as any)._downY = (target as any)._lastY = e.clientY;
 
@@ -499,8 +501,10 @@ export function setupMoveUpEvents(
         document.removeEventListener("pointermove", _moveEvent);
         document.removeEventListener("pointerup", _upEvent);
     };
-    e.stopPropagation();
-    e.preventDefault();
+    if (stopPropagation) {
+        e.stopPropagation();
+        e.preventDefault();
+    }
     document.removeEventListener("pointermove", _moveEvent);
     document.removeEventListener("pointerup", _upEvent);
     document.addEventListener("pointermove", _moveEvent);
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 31b720b81..1760e2419 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -72,8 +72,22 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
         }), returnFalse, emptyFunction);
     }
 
+    contentsDown = (e: React.PointerEvent) => {
+        setupMoveUpEvents(this, e, returnFalse, returnFalse, action(() => {
+            let prevFilterIndex = NumCast(this.props.Document._prevFilterIndex);
+            if (prevFilterIndex > 0) {
+                prevFilterIndex--;
+                this.props.Document._docFilters = ObjectField.MakeCopy(this.props.Document["_prevDocFilter" + prevFilterIndex] as ObjectField);
+                this.props.Document._docRangeFilters = ObjectField.MakeCopy(this.props.Document["_prevDocRangeFilters" + prevFilterIndex] as ObjectField);
+                this.props.Document._prevFilterIndex = prevFilterIndex;
+            } else {
+                this.props.Document._docFilters = new List([]);
+            }
+        }), false);
+    }
+
     @computed get contents() {
-        return <div className="collectionTimeView-innards" key="timeline" style={{ width: "100%" }}>
+        return <div className="collectionTimeView-innards" key="timeline" style={{ width: "100%" }} onPointerDown={this.contentsDown}>
             <CollectionFreeFormView {...this.props} freezeChildDimensions={BoolCast(this.layoutDoc._freezeChildDimensions, true)} layoutEngine={this.layoutEngine} />
         </div>;
     }
@@ -132,20 +146,6 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
             color: "#f1efeb" // this.props.headingObject ? this.props.headingObject.color : "#f1efeb";
         };
         return <div className={"pivotKeyEntry"}>
-            <button className="collectionTimeView-backBtn"
-                onClick={action(() => {
-                    let prevFilterIndex = NumCast(this.props.Document._prevFilterIndex);
-                    if (prevFilterIndex > 0) {
-                        prevFilterIndex--;
-                        this.props.Document._docFilters = ObjectField.MakeCopy(this.props.Document["_prevDocFilter" + prevFilterIndex] as ObjectField);
-                        this.props.Document._docRangeFilters = ObjectField.MakeCopy(this.props.Document["_prevDocRangeFilters" + prevFilterIndex] as ObjectField);
-                        this.props.Document._prevFilterIndex = prevFilterIndex;
-                    } else {
-                        this.props.Document._docFilters = new List([]);
-                    }
-                })}>
-                back
-            </button>
             <EditableView {...newEditableViewProps} display={"inline"} menuCallback={this.menuCallback} />
         </div>;
     }
diff --git a/src/client/views/collections/CollectionView.scss b/src/client/views/collections/CollectionView.scss
index a0b73b90f..d43dd387a 100644
--- a/src/client/views/collections/CollectionView.scss
+++ b/src/client/views/collections/CollectionView.scss
@@ -20,7 +20,7 @@
         top: 55%;
         border: 1px black solid;
         z-index: 2;
-        left: -10px;
+        right: -10px;
     }
     .collectionTimeView-treeView {
         display: flex;
@@ -28,7 +28,7 @@
         width: 200px;
         height: 100%;
         position: absolute;
-        left: 0;
+        right: 0;
         top: 0;
 
         .collectionTimeView-addfacet {
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 831691f8c..0a3577bc7 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -13,7 +13,7 @@ import { List } from '../../../new_fields/List';
 import { BoolCast, Cast, NumCast, StrCast, ScriptCast } from '../../../new_fields/Types';
 import { ImageField } from '../../../new_fields/URLField';
 import { TraceMobx } from '../../../new_fields/util';
-import { Utils, setupMoveUpEvents, returnFalse, returnZero } from '../../../Utils';
+import { Utils, setupMoveUpEvents, returnFalse, returnZero, emptyPath, emptyFunction, returnOne } from '../../../Utils';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { DocumentManager } from '../../util/DocumentManager';
 import { ImageUtils } from '../../util/Import & Export/ImageUtils';
@@ -45,6 +45,7 @@ import { ScriptField, ComputedField } from '../../../new_fields/ScriptField';
 import { InteractionUtils } from '../../util/InteractionUtils';
 import { ObjectField } from '../../../new_fields/ObjectField';
 import CollectionMapView from './CollectionMapView';
+import { Transform } from 'prosemirror-transform';
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -281,7 +282,6 @@ export class CollectionView extends Touchable<FieldViewProps> {
     set _facetWidth(value) { this.props.Document._facetWidth = value; }
 
     bodyPanelWidth = () => this.props.PanelWidth() - this.facetWidth();
-    getTransform = () => this.props.ScreenToLocalTransform().translate(-this.facetWidth(), 0);
     facetWidth = () => Math.max(0, Math.min(this.props.PanelWidth() - 25, this._facetWidth));
 
     @computed get dataDoc() {
@@ -389,7 +389,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
 
     onPointerDown = (e: React.PointerEvent) => {
         setupMoveUpEvents(this, e, action((e: PointerEvent, down: number[], delta: number[]) => {
-            this._facetWidth = Math.max(this.props.ScreenToLocalTransform().transformPoint(e.clientX, 0)[0], 0);
+            this._facetWidth = this.props.PanelWidth() - Math.max(this.props.ScreenToLocalTransform().transformPoint(e.clientX, 0)[0], 0);
             return false;
         }), returnFalse, action(() => this._facetWidth = this.facetWidth() < 15 ? Math.min(this.props.PanelWidth() - 25, 200) : 0));
     }
@@ -415,27 +415,44 @@ export class CollectionView extends Touchable<FieldViewProps> {
                 <div className="collectionTimeView-addFacet" style={{ width: `${this.facetWidth()}px` }} onPointerDown={e => e.stopPropagation()}>
                     <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={flyout}>
                         <div className="collectionTimeView-button">
-                            <span className="collectionTimeView-span">Facet Filters</span>
                             <FontAwesomeIcon icon={faEdit} size={"lg"} />
+                            <span className="collectionTimeView-span">Facet Filters</span>
                         </div>
                     </Flyout>
                 </div>
                 <div className="collectionTimeView-tree" key="tree">
-                    <CollectionTreeView {...this.props}
+                    <CollectionTreeView
+                        Document={facetCollection}
+                        DataDoc={facetCollection}
+                        fieldKey={`${this.props.fieldKey}-filter`}
                         CollectionView={this}
-                        treeViewHideTitle={true}
+                        ContainingCollectionDoc={this.props.ContainingCollectionDoc}
+                        ContainingCollectionView={this.props.ContainingCollectionView}
+                        PanelWidth={this.facetWidth}
+                        PanelHeight={this.props.PanelHeight}
                         NativeHeight={returnZero}
                         NativeWidth={returnZero}
+                        LibraryPath={emptyPath}
+                        rootSelected={this.props.rootSelected}
+                        renderDepth={1}
+                        dropAction={this.props.dropAction}
+                        ScreenToLocalTransform={this.props.ScreenToLocalTransform}
+                        addDocTab={returnFalse}
+                        pinToPres={returnFalse}
+                        isSelected={returnFalse}
+                        select={returnFalse}
+                        bringToFront={emptyFunction}
+                        active={this.props.active}
+                        whenActiveChanged={returnFalse}
+                        treeViewHideTitle={true}
+                        ContentScaling={returnOne}
+                        focus={returnFalse}
                         treeViewHideHeaderFields={true}
                         onCheckedClick={this.scriptField!}
                         ignoreFields={this.ignoreFields}
                         annotationsKey={""}
                         dontRegisterView={true}
-                        PanelWidth={this.facetWidth}
-                        DataDoc={facetCollection}
-                        Document={facetCollection}
                         backgroundColor={this.filterBackground}
-                        fieldKey={`${this.props.fieldKey}-filter`}
                         moveDocument={returnFalse}
                         removeDocument={returnFalse}
                         addDocument={returnFalse} />
@@ -461,7 +478,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
             }}
             onContextMenu={this.onContextMenu}>
             {this.showIsTagged()}
-            <div style={{ width: `calc(100% - ${this.facetWidth()}px)`, marginLeft: `${this.facetWidth()}px` }}>
+            <div style={{ width: `calc(100% - ${this.facetWidth()}px)` }}>
                 {this.collectionViewType !== undefined ? this.SubView(this.collectionViewType, props) : (null)}
             </div>
             {this.lightbox(DocListCast(this.props.Document[this.props.fieldKey]).filter(d => d.type === DocumentType.IMG).map(d =>
@@ -471,9 +488,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
                     :
                     ""))}
             {!this.props.isSelected() || this.props.PanelHeight() < 100 || this.props.Document.hideFilterView ? (null) :
-                <div className="collectionTimeView-dragger" key="dragger" onPointerDown={this.onPointerDown} style={{ transform: `translate(${this.facetWidth()}px, 0px)` }} >
-                    <span title="library View Dragger" style={{ width: "5px", position: "absolute", top: "0" }} />
-                </div>
+                <div className="collectionTimeView-dragger" title="library View Dragger" onPointerDown={this.onPointerDown} style={{ right: this.facetWidth() - 10 }} />
             }
             {this.filterView}
         </div>);
diff --git a/src/client/views/collections/CollectionViewChromes.scss b/src/client/views/collections/CollectionViewChromes.scss
index 9529ef525..5203eb55f 100644
--- a/src/client/views/collections/CollectionViewChromes.scss
+++ b/src/client/views/collections/CollectionViewChromes.scss
@@ -61,7 +61,8 @@
                 pointer-events: all;
                 // margin-top: 10px;
             }
-            .collectionViewBaseChrome-template {
+            .collectionViewBaseChrome-template,
+            .collectionViewBaseChrome-viewModes {
                 display: grid;
                 background: rgb(238, 238, 238);
                 color:grey;
@@ -69,9 +70,12 @@
                 margin-bottom:auto;
                 margin-left: 5px;
             }
+            .collectionViewBaseChrome-viewModes {
+                margin-left: 25px;
+            }
 
             .collectionViewBaseChrome-viewSpecs {
-                margin-left: 25px;
+                margin-left: 5px;
                 display: grid;
                 
                 .collectionViewBaseChrome-filterIcon {
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index cef85e148..7315d2c4e 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -276,12 +276,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
                             title="Collapse collection chrome" onClick={this.toggleCollapse}>
                             <FontAwesomeIcon icon="caret-up" size="2x" />
                         </button>
-                        <div className="collectionViewBaseChrome-viewSpecs" title="filter documents to show" style={{ display: collapsed ? "none" : "grid" }}>
-                            <div className="collectionViewBaseChrome-filterIcon" onPointerDown={this.toggleViewSpecs} >
-                                <FontAwesomeIcon icon="filter" size="2x" />
-                            </div>
-                        </div>
-                        <div className="collectionViewBaseChrome-template" style={{ display: collapsed ? "none" : undefined }}>
+                        <div className="collectionViewBaseChrome-viewModes" style={{ display: collapsed ? "none" : undefined }}>
                             <div className="commandEntry-outerDiv" title="drop document to apply or drag to create button" ref={this._viewRef} onPointerDown={this.dragViewDown}>
                                 <div className="commandEntry-drop">
                                     <FontAwesomeIcon icon="bullseye" size="2x"></FontAwesomeIcon>
@@ -303,6 +298,11 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
                                 </select>
                             </div>
                         </div>
+                        <div className="collectionViewBaseChrome-viewSpecs" title="filter documents to show" style={{ display: collapsed ? "none" : "grid" }}>
+                            <div className="collectionViewBaseChrome-filterIcon" onPointerDown={this.toggleViewSpecs} >
+                                <FontAwesomeIcon icon="filter" size="2x" />
+                            </div>
+                        </div>
                         <div className="collectionViewBaseChrome-template" ref={this.createDropTarget} style={{ display: collapsed ? "none" : undefined }}>
                             <div className="commandEntry-outerDiv" title="drop document to apply or drag to create button" ref={this._commandRef} onPointerDown={this.dragCommandDown}>
                                 <div className="commandEntry-drop">
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index a77f09163..d98fe190c 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -907,6 +907,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument, u
 
     onViewDefDivClick = (e: React.MouseEvent, payload: any) => {
         (this.props.Document.onViewDefDivClick as ScriptField)?.script.run({ this: this.props.Document, payload });
+        e.stopPropagation();
     }
     private viewDefToJSX(viewDef: ViewDefBounds): Opt<ViewDefResult> {
         const { x, y, z } = viewDef;
@@ -991,6 +992,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument, u
                     {...this.getChildDocumentViewProps(pair.layout, pair.data)}
                     dataProvider={this.childDataProvider}
                     LayoutDoc={this.childLayoutDocFunc}
+                    pointerEvents={this.props.layoutEngine?.() !== undefined ? "none" : undefined}
                     jitterRotation={NumCast(this.props.Document.jitterRotation)}
                     fitToBox={this.props.fitToBox || BoolCast(this.props.freezeChildDimensions)}
                     FreezeDimensions={BoolCast(this.props.freezeChildDimensions)}
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index e7cb2c015..05ad98c43 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -22,6 +22,7 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
     width?: number;
     height?: number;
     jitterRotation: number;
+    pointerEvents?: "none";
     transition?: string;
     fitToBox?: boolean;
 }
@@ -92,7 +93,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                 height: this.height,
                 zIndex: this.ZInd,
                 display: this.ZInd === -99 ? "none" : undefined,
-                pointerEvents: this.props.Document.isBackground ? "none" : undefined
+                pointerEvents: this.props.Document.isBackground ? "none" : this.props.pointerEvents
             }} >
 
             {!this.props.fitToBox ?
-- 
cgit v1.2.3-70-g09d2


From a6d17ef79fb94334735998d3d11f7ea068dea93a Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 15 Apr 2020 03:15:25 -0400
Subject: cleaned up some more of the pivot view event handling api

---
 .../views/collections/CollectionTimeView.tsx       | 23 ++++++++++------------
 src/client/views/collections/CollectionView.tsx    |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  7 +++++--
 3 files changed, 16 insertions(+), 16 deletions(-)

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

diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 1760e2419..e05223ca0 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -1,6 +1,6 @@
 import { action, computed, observable, runInAction } from "mobx";
 import { observer } from "mobx-react";
-import { Doc } from "../../../new_fields/Doc";
+import { Doc, Opt, DocCastAsync } from "../../../new_fields/Doc";
 import { List } from "../../../new_fields/List";
 import { ObjectField } from "../../../new_fields/ObjectField";
 import { RichTextField } from "../../../new_fields/RichTextField";
@@ -26,18 +26,15 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
     _changing = false;
     @observable _layoutEngine = "pivot";
     @observable _collapsed: boolean = false;
-    componentWillUnmount() {
-        this.props.Document.onChildClick = undefined;
-    }
+    @observable _childClickedScript: Opt<ScriptField>;
+    @observable _viewDefDivClick: Opt<ScriptField>;
     async componentDidMount() {
-        const childText = "const alias = getAlias(this); switchView(alias, thisContainer.childDetailView); alias.dropAction='alias'; alias.removeDropProperties=new List<string>(['dropAction']); useRightSplit(alias, shiftKey); ";
-        this.props.Document.onChildClick = ScriptField.MakeScript(childText, { this: Doc.name, heading: "string", thisContainer: Doc.name, shiftKey: "boolean" });
-        this.props.Document._fitToBox = true;
-        if (!this.props.Document.onViewDefClick) {
-            this.props.Document.onViewDefDivClick = ScriptField.MakeScript("pivotColumnClick(this,payload)", { payload: "any" });
-        }
-        this.props.Document.childDetailView = Cast(this.props.Document.childDetailView, Doc, null) ||// bcz: needs to be here to make sure the childDetailView layout template has been loaded when the first item is clicked;
-            DocumentView.findTemplate("detailView", StrCast(this.props.Document.type), "");
+        const detailView = (await DocCastAsync(this.props.Document.childDetailView)) || DocumentView.findTemplate("detailView", StrCast(this.props.Document.type), "");
+        const childText = "const alias = getAlias(this); switchView(alias, detailView); alias.dropAction='alias'; alias.removeDropProperties=new List<string>(['dropAction']); useRightSplit(alias, shiftKey); ";
+        runInAction(() => {
+            this._childClickedScript = ScriptField.MakeScript(childText, { this: Doc.name, shiftKey: "boolean" }, { detailView: detailView! });
+            this._viewDefDivClick = ScriptField.MakeScript("pivotColumnClick(this,payload)", { payload: "any" });
+        });
     }
 
     layoutEngine = () => this._layoutEngine;
@@ -88,7 +85,7 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
 
     @computed get contents() {
         return <div className="collectionTimeView-innards" key="timeline" style={{ width: "100%" }} onPointerDown={this.contentsDown}>
-            <CollectionFreeFormView {...this.props} freezeChildDimensions={BoolCast(this.layoutDoc._freezeChildDimensions, true)} layoutEngine={this.layoutEngine} />
+            <CollectionFreeFormView {...this.props} childClickScript={this._childClickedScript} viewDefDivClick={this._viewDefDivClick} fitToBox={true} freezeChildDimensions={BoolCast(this.layoutDoc._freezeChildDimensions, true)} layoutEngine={this.layoutEngine} />
         </div>;
     }
 
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 0a3577bc7..19e235ff2 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -278,7 +278,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
             onMovePrevRequest={action(() => this._curLightboxImg = (this._curLightboxImg + images.length - 1) % images.length)}
             onMoveNextRequest={action(() => this._curLightboxImg = (this._curLightboxImg + 1) % images.length)} />);
     }
-    get _facetWidth() { return NumCast(this.props.Document._facetWidth) }
+    get _facetWidth() { return NumCast(this.props.Document._facetWidth); }
     set _facetWidth(value) { this.props.Document._facetWidth = value; }
 
     bodyPanelWidth = () => this.props.PanelWidth() - this.facetWidth();
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index d98fe190c..f19181a20 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -44,6 +44,7 @@ import MarqueeOptionsMenu from "./MarqueeOptionsMenu";
 import { MarqueeView } from "./MarqueeView";
 import React = require("react");
 import { CollectionViewType } from "../CollectionView";
+import { Script } from "vm";
 
 library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload);
 
@@ -69,6 +70,8 @@ type PanZoomDocument = makeInterface<[typeof panZoomSchema, typeof documentSchem
 const PanZoomDocument = makeInterface(panZoomSchema, documentSchema, positionSchema, pageSchema);
 export type collectionFreeformViewProps = {
     forceScaling?: boolean; // whether to force scaling of content (needed by ImageBox)
+    childClickScript?: ScriptField;
+    viewDefDivClick?: ScriptField;
 };
 
 @observer
@@ -847,7 +850,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument, u
     }
 
     @computed get libraryPath() { return this.props.LibraryPath ? [...this.props.LibraryPath, this.props.Document] : []; }
-    @computed get onChildClickHandler() { return ScriptCast(this.Document.onChildClick); }
+    @computed get onChildClickHandler() { return this.props.childClickScript || ScriptCast(this.Document.onChildClick); }
     backgroundHalo = () => BoolCast(this.Document.useClusters);
 
     getChildDocumentViewProps(childLayout: Doc, childData?: Doc): DocumentViewProps {
@@ -906,7 +909,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument, u
     }
 
     onViewDefDivClick = (e: React.MouseEvent, payload: any) => {
-        (this.props.Document.onViewDefDivClick as ScriptField)?.script.run({ this: this.props.Document, payload });
+        (this.props.viewDefDivClick || ScriptCast(this.props.Document.onViewDefDivClick))?.script.run({ this: this.props.Document, payload });
         e.stopPropagation();
     }
     private viewDefToJSX(viewDef: ViewDefBounds): Opt<ViewDefResult> {
-- 
cgit v1.2.3-70-g09d2


From ac40232e5bbbb98680912eb3c505bab994079074 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 17 Apr 2020 09:39:25 -0400
Subject: fixed auto focus to work on clicked pt

---
 .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx   | 4 ++--
 src/client/views/nodes/DocumentView.tsx                               | 4 ++++
 2 files changed, 6 insertions(+), 2 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index f19181a20..02f9bd487 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -31,7 +31,7 @@ import { ContextMenu } from "../../ContextMenu";
 import { ContextMenuProps } from "../../ContextMenuItem";
 import { InkingControl } from "../../InkingControl";
 import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView";
-import { DocumentViewProps } from "../../nodes/DocumentView";
+import { DocumentViewProps, DocumentView } from "../../nodes/DocumentView";
 import { FormattedTextBox } from "../../nodes/FormattedTextBox";
 import { pageSchema } from "../../nodes/ImageBox";
 import PDFMenu from "../../pdf/PDFMenu";
@@ -822,7 +822,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument, u
 
             if (!willZoom) {
                 Doc.BrushDoc(this.props.Document);
-                !doc.z && this.scaleAtPt([NumCast(doc.x), NumCast(doc.y)], 1);
+                !doc.z && this.scaleAtPt(DocumentView._focusHack, 1); // [NumCast(doc.x), NumCast(doc.y)], 1);
             } else {
                 if (DocListCast(this.dataDoc[this.props.fieldKey]).includes(doc)) {
                     if (!doc.z) this.setPan(newPanX, newPanY, "Ease", true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index c0d530160..fb0b1d4f4 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -317,6 +317,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 if ((this.props.Document.onDragStart || (this.props.Document.rootDocument && this.props.Document.isTemplateForField)) && !(e.ctrlKey || e.button > 0)) {  // onDragStart implies a button doc that we don't want to select when clicking.   RootDocument & isTEmplaetForField implies we're clicking on part of a template instance and we want to select the whole template, not the part
                     stopPropagate = false; // don't stop propagation for field templates -- want the selection to propagate up to the root document of the template
                 } else {
+                    DocumentView._focusHack = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY) || [0, 0];
+                    DocumentView._focusHack = [DocumentView._focusHack[0] + NumCast(this.props.Document.x), DocumentView._focusHack[1] + NumCast(this.props.Document.y)];
+
                     this.props.focus(this.props.Document, false);
                     SelectionManager.SelectDoc(this, e.ctrlKey);
                 }
@@ -326,6 +329,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             preventDefault && e.preventDefault();
         }
     }
+    static _focusHack: number[] = []; // bcz :this will get fixed...
 
     // follows a link - if the target is on screen, it highlights/pans to it.
     // if the target isn't onscreen, then it will open up the target in a tab, on the right, or in place
-- 
cgit v1.2.3-70-g09d2


From d3aebc8404685496673a8611a6d72ff7695ad191 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 18 Apr 2020 13:42:03 -0400
Subject: several pointerevent fixes broken by last change.  added scroll to
 target for webBox links.

---
 src/client/util/DocumentManager.ts                 |  13 ++-
 .../views/collections/CollectionTreeView.tsx       |   1 -
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   6 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |   1 -
 src/client/views/nodes/DocumentView.scss           |  10 --
 src/client/views/nodes/DocumentView.tsx            | 118 ++++++++++-----------
 src/client/views/nodes/FormattedTextBoxComment.tsx |   4 +-
 src/client/views/nodes/LinkAnchorBox.scss          |   1 +
 src/client/views/nodes/LinkAnchorBox.tsx           |   2 +
 src/client/views/nodes/LinkBox.tsx                 |   1 -
 src/client/views/nodes/WebBox.tsx                  |  22 ++--
 src/client/views/pdf/Annotation.tsx                |   4 +-
 src/client/views/pdf/PDFViewer.tsx                 |   8 +-
 13 files changed, 100 insertions(+), 91 deletions(-)

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

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 2d6078cf3..e66e67723 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -156,7 +156,12 @@ export class DocumentManager {
         let annotatedDoc = await Cast(targetDoc.annotationOn, Doc);
         if (annotatedDoc) {
             const first = getFirstDocView(annotatedDoc);
-            if (first) annotatedDoc = first.props.Document;
+            if (first) {
+                annotatedDoc = first.props.Document;
+                if (docView) {
+                    docView.props.focus(annotatedDoc, false);
+                }
+            }
         }
         if (docView) {  // we have a docView already and aren't forced to create a new one ... just focus on the document.  TODO move into view if necessary otherwise just highlight?
             docView.props.focus(docView.props.Document, willZoom, undefined, focusAndFinish);
@@ -219,9 +224,9 @@ export class DocumentManager {
         if (linkDoc) {
             const target = (doc === linkDoc.anchor1 ? linkDoc.anchor2 : doc === linkDoc.anchor2 ? linkDoc.anchor1 :
                 (Doc.AreProtosEqual(doc, linkDoc.anchor1 as Doc) ? linkDoc.anchor2 : linkDoc.anchor1)) as Doc;
-            const targetTimecode = (doc === linkDoc.anchor1 ? Cast(linkDoc.anchor2_timecode, "number") : 
-                                    doc === linkDoc.anchor2 ? Cast(linkDoc.anchor1_timecode, "number"):
-                (Doc.AreProtosEqual(doc, linkDoc.anchor1 as Doc) ? Cast(linkDoc.anchor2_timecode, "number"):Cast(linkDoc.anchor1_timecode, "number")));
+            const targetTimecode = (doc === linkDoc.anchor1 ? Cast(linkDoc.anchor2_timecode, "number") :
+                doc === linkDoc.anchor2 ? Cast(linkDoc.anchor1_timecode, "number") :
+                    (Doc.AreProtosEqual(doc, linkDoc.anchor1 as Doc) ? Cast(linkDoc.anchor2_timecode, "number") : Cast(linkDoc.anchor1_timecode, "number")));
             if (target) {
                 const containerDoc = (await Cast(target.annotationOn, Doc)) || target;
                 containerDoc.currentTimecode = targetTimecode;
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 510c9924b..5ff8c11ee 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -145,7 +145,6 @@ class TreeView extends React.Component<TreeViewProps> {
         ele && (this._treedropDisposer = DragManager.MakeDropTarget(ele, this.treeDrop.bind(this)));
     }
 
-    onPointerDown = (e: React.PointerEvent) => e.stopPropagation();
     onPointerEnter = (e: React.PointerEvent): void => {
         this.props.active(true) && Doc.BrushDoc(this.dataDoc);
         if (e.buttons === 1 && SelectionManager.GetIsDragging()) {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 02f9bd487..b3409bd57 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -804,7 +804,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument, u
             if (!annotOn) {
                 this.props.focus(doc);
             } else {
-                const contextHgt = Doc.AreProtosEqual(annotOn, this.props.Document) && this.props.VisibleHeight ? this.props.VisibleHeight() : NumCast(annotOn.height);
+                const contextHgt = Doc.AreProtosEqual(annotOn, this.props.Document) && this.props.VisibleHeight ? this.props.VisibleHeight() : NumCast(annotOn._height);
                 const offset = annotOn && (contextHgt / 2 * 96 / 72);
                 this.props.Document.scrollY = NumCast(doc.y) - offset;
             }
@@ -820,7 +820,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument, u
 
             const savedState = { px: this.Document._panX, py: this.Document._panY, s: this.Document.scale, pt: this.Document.panTransformType };
 
-            if (!willZoom) {
+            if (!willZoom && DocumentView._focusHack.length) {
                 Doc.BrushDoc(this.props.Document);
                 !doc.z && this.scaleAtPt(DocumentView._focusHack, 1); // [NumCast(doc.x), NumCast(doc.y)], 1);
             } else {
@@ -995,7 +995,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument, u
                     {...this.getChildDocumentViewProps(pair.layout, pair.data)}
                     dataProvider={this.childDataProvider}
                     LayoutDoc={this.childLayoutDocFunc}
-                    pointerEvents={this.props.layoutEngine?.() !== undefined ? "none" : undefined}
+                    pointerEvents={this.props.layoutEngine?.() !== undefined ? false : undefined}
                     jitterRotation={NumCast(this.props.Document.jitterRotation)}
                     fitToBox={this.props.fitToBox || BoolCast(this.props.freezeChildDimensions)}
                     FreezeDimensions={BoolCast(this.props.freezeChildDimensions)}
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 05ad98c43..e325a70c0 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -22,7 +22,6 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
     width?: number;
     height?: number;
     jitterRotation: number;
-    pointerEvents?: "none";
     transition?: string;
     fitToBox?: boolean;
 }
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index fc9ee1201..81ae36cc0 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -38,16 +38,6 @@
         display:flex;
         overflow: hidden;
     }
-    .documentView-linkAnchorBoxWrapper {
-        pointer-events: none;
-        position: absolute;
-        transform-origin: top left;
-        width: 100%;
-        height: 100%;
-        top:0;
-        left:0;
-        z-index: 1;
-    }
 
     .documentView-lock {
         width: 20; 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 2f22488e4..2bae2fa96 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -80,6 +80,7 @@ export interface DocumentViewProps {
     ContentScaling: () => number;
     PanelWidth: () => number;
     PanelHeight: () => number;
+    pointerEvents?: boolean;
     focus: (doc: Doc, willZoom: boolean, scale?: number, afterFocus?: DocFocusFunc) => void;
     parentActive: (outsideReaction: boolean) => boolean;
     whenActiveChanged: (isActive: boolean) => void;
@@ -994,38 +995,42 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     screenToLocalTransform = () => this.props.ScreenToLocalTransform();
     @computed get contents() {
         TraceMobx();
-        return (<DocumentContentsView ContainingCollectionView={this.props.ContainingCollectionView}
-            ContainingCollectionDoc={this.props.ContainingCollectionDoc}
-            NativeWidth={this.NativeWidth}
-            NativeHeight={this.NativeHeight}
-            Document={this.props.Document}
-            DataDoc={this.props.DataDoc}
-            LayoutDoc={this.props.LayoutDoc}
-            makeLink={this.makeLink}
-            rootSelected={this.rootSelected}
-            dontRegisterView={this.props.dontRegisterView}
-            fitToBox={this.props.fitToBox}
-            LibraryPath={this.props.LibraryPath}
-            addDocument={this.props.addDocument}
-            removeDocument={this.props.removeDocument}
-            moveDocument={this.props.moveDocument}
-            ScreenToLocalTransform={this.screenToLocalTransform}
-            renderDepth={this.props.renderDepth}
-            PanelWidth={this.panelWidth}
-            PanelHeight={this.panelHeight}
-            focus={this.props.focus}
-            parentActive={this.props.parentActive}
-            whenActiveChanged={this.props.whenActiveChanged}
-            bringToFront={this.props.bringToFront}
-            addDocTab={this.props.addDocTab}
-            pinToPres={this.props.pinToPres}
-            backgroundColor={this.props.backgroundColor}
-            ContentScaling={this.childScaling}
-            ChromeHeight={this.chromeHeight}
-            isSelected={this.isSelected}
-            select={this.select}
-            onClick={this.onClickHandler}
-            layoutKey={this.finalLayoutKey} />);
+        return (<>
+            <DocumentContentsView key={1} ContainingCollectionView={this.props.ContainingCollectionView}
+                ContainingCollectionDoc={this.props.ContainingCollectionDoc}
+                NativeWidth={this.NativeWidth}
+                NativeHeight={this.NativeHeight}
+                Document={this.props.Document}
+                DataDoc={this.props.DataDoc}
+                LayoutDoc={this.props.LayoutDoc}
+                makeLink={this.makeLink}
+                rootSelected={this.rootSelected}
+                dontRegisterView={this.props.dontRegisterView}
+                fitToBox={this.props.fitToBox}
+                LibraryPath={this.props.LibraryPath}
+                addDocument={this.props.addDocument}
+                removeDocument={this.props.removeDocument}
+                moveDocument={this.props.moveDocument}
+                ScreenToLocalTransform={this.screenToLocalTransform}
+                renderDepth={this.props.renderDepth}
+                PanelWidth={this.panelWidth}
+                PanelHeight={this.panelHeight}
+                focus={this.props.focus}
+                parentActive={this.props.parentActive}
+                whenActiveChanged={this.props.whenActiveChanged}
+                bringToFront={this.props.bringToFront}
+                addDocTab={this.props.addDocTab}
+                pinToPres={this.props.pinToPres}
+                backgroundColor={this.props.backgroundColor}
+                ContentScaling={this.childScaling}
+                ChromeHeight={this.chromeHeight}
+                isSelected={this.isSelected}
+                select={this.select}
+                onClick={this.onClickHandler}
+                layoutKey={this.finalLayoutKey} />
+            {this.anchors}
+        </>
+        );
     }
     linkEndpoint = (linkDoc: Doc) => Doc.LinkEndpoint(linkDoc, this.props.Document);
 
@@ -1049,20 +1054,19 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     @computed get anchors() {
         TraceMobx();
         return this.layoutDoc.presBox ? (null) : DocListCast(this.Document.links).filter(d => !d.hidden && this.isNonTemporalLink).map((d, i) =>
-            <div className="documentView-linkAnchorBoxWrapper" key={d[Id]}>
-                <DocumentView {...this.props}
-                    Document={d}
-                    ContainingCollectionView={this.props.ContainingCollectionView}
-                    ContainingCollectionDoc={this.props.Document} // bcz: hack this.props.Document is not a collection  Need a better prop for passing the containing document to the LinkAnchorBox
-                    PanelWidth={this.anchorPanelWidth}
-                    PanelHeight={this.anchorPanelHeight}
-                    layoutKey={this.linkEndpoint(d)}
-                    ContentScaling={returnOne}
-                    backgroundColor={returnTransparent}
-                    removeDocument={this.hideLinkAnchor}
-                    LayoutDoc={undefined}
-                />
-            </div>);
+            <DocumentView {...this.props} key={i + 1}
+                Document={d}
+                ContainingCollectionView={this.props.ContainingCollectionView}
+                ContainingCollectionDoc={this.props.Document} // bcz: hack this.props.Document is not a collection  Need a better prop for passing the containing document to the LinkAnchorBox
+                PanelWidth={this.anchorPanelWidth}
+                PanelHeight={this.anchorPanelHeight}
+                layoutKey={this.linkEndpoint(d)}
+                ContentScaling={returnOne}
+                backgroundColor={returnTransparent}
+                removeDocument={this.hideLinkAnchor}
+                pointerEvents={false}
+                LayoutDoc={undefined}
+            />);
     }
     @computed get innards() {
         TraceMobx();
@@ -1101,22 +1105,18 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     SetValue={undoBatch((value: string) => (Doc.GetProto(this.props.DataDoc || this.props.Document)[showTitle] = value) ? true : true)}
                 />
             </div>);
-        return <>
-            {this.anchors}
-            {!showTitle && !showCaption ?
-                this.contents :
-                <div className="documentView-styleWrapper" >
-                    <div className="documentView-styleContentWrapper" style={{ height: showTextTitle ? `calc(100% - ${this.chromeHeight()}px)` : "100%", top: showTextTitle ? this.chromeHeight() : undefined }}>
-                        {this.contents}
-                    </div>
-                    {titleView}
-                    {captionView}
+        return !showTitle && !showCaption ?
+            this.contents :
+            <div className="documentView-styleWrapper" >
+                <div className="documentView-styleContentWrapper" style={{ height: showTextTitle ? `calc(100% - ${this.chromeHeight()}px)` : "100%", top: showTextTitle ? this.chromeHeight() : undefined }}>
+                    {this.contents}
                 </div>
-            }
-        </>;
+                {titleView}
+                {captionView}
+            </div>;
     }
     @computed get ignorePointerEvents() {
-        return (this.Document.isBackground && !this.isSelected() && !SelectionManager.GetIsDragging()) || this.props.layoutKey?.includes("layout_key") || (this.Document.type === DocumentType.INK && InkingControl.Instance.selectedTool !== InkTool.None);
+        return this.props.pointerEvents === false || (this.Document.isBackground && !this.isSelected() && !SelectionManager.GetIsDragging()) || (this.Document.type === DocumentType.INK && InkingControl.Instance.selectedTool !== InkTool.None);
     }
 
     @observable _animate = 0;
diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx
index 35304033f..41df5b3c1 100644
--- a/src/client/views/nodes/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/FormattedTextBoxComment.tsx
@@ -16,6 +16,7 @@ import React = require("react");
 import { Docs } from "../../documents/Documents";
 import wiki from "wikijs";
 import { DocumentType } from "../../documents/DocumentTypes";
+import { DocumentView } from "./DocumentView";
 
 export let formattedTextBoxCommentPlugin = new Plugin({
     view(editorView) { return new FormattedTextBoxComment(editorView); }
@@ -85,8 +86,9 @@ export class FormattedTextBoxComment {
                 const textBox = FormattedTextBoxComment.textBox;
                 if (FormattedTextBoxComment.linkDoc && !keep && textBox) {
                     if (FormattedTextBoxComment.linkDoc.type !== DocumentType.LINK) {
-                        textBox.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "inTab":"onRight");
+                        textBox.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "inTab" : "onRight");
                     } else {
+                        DocumentView._focusHack = [];
                         DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document,
                             (doc: Doc, followLinkLocation: string) => textBox.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation));
                     }
diff --git a/src/client/views/nodes/LinkAnchorBox.scss b/src/client/views/nodes/LinkAnchorBox.scss
index 24f9c1ea0..710f2178b 100644
--- a/src/client/views/nodes/LinkAnchorBox.scss
+++ b/src/client/views/nodes/LinkAnchorBox.scss
@@ -5,6 +5,7 @@
     height: 15;
     border-radius: 20px;
     user-select: none;
+    pointer-events: all;
 
     .linkAnchorBox-linkCloser {
         position: absolute;
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index 13ffc6956..3b1ced815 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -17,6 +17,7 @@ import { LinkEditor } from "../linking/LinkEditor";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { SelectionManager } from "../../util/SelectionManager";
 import { TraceMobx } from "../../../new_fields/util";
+import { DocumentView } from "./DocumentView";
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -74,6 +75,7 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps, LinkAnch
             anchorContainerDoc && this.props.bringToFront(anchorContainerDoc, false);
             if (anchorContainerDoc && !this.layoutDoc.onClick && !this._isOpen) {
                 this._timeout = setTimeout(action(() => {
+                    DocumentView._focusHack = [];
                     DocumentManager.Instance.FollowLink(this.rootDoc, anchorContainerDoc, document => this.props.addDocTab(document, StrCast(this.layoutDoc.linkOpenLocation, "inTab")), false);
                     this._editing = false;
                 }), 300 - (Date.now() - this._lastTap));
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index af4bf420f..740f2ef04 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -17,7 +17,6 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps, LinkDocument>(
     public static LayoutString(fieldKey: string) { return FieldView.LayoutString(LinkBox, fieldKey); }
     render() {
         return <div className={`linkBox-container${this.active() ? "-interactive" : ""}`}
-            onPointerDown={e => e.button === 0 && !e.ctrlKey && e.stopPropagation()}
             style={{ background: this.props.backgroundColor?.(this.props.Document) }} >
 
             <CollectionTreeView {...this.props}
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 26e947f5b..510e6be0c 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -1,6 +1,6 @@
 import { library } from "@fortawesome/fontawesome-svg-core";
 import { faStickyNote, faLock, faUnlock } from '@fortawesome/free-solid-svg-icons';
-import { action, computed, observable, trace } from "mobx";
+import { action, computed, observable, trace, IReactionDisposer, reaction } from "mobx";
 import { observer } from "mobx-react";
 import { Doc, FieldResult } from "../../../new_fields/Doc";
 import { documentSchema } from "../../../new_fields/documentSchemas";
@@ -13,7 +13,6 @@ import { Utils, returnOne, emptyFunction, returnZero } from "../../../Utils";
 import { Docs } from "../../documents/Documents";
 import { DragManager } from "../../util/DragManager";
 import { ImageUtils } from "../../util/Import & Export/ImageUtils";
-import { SelectionManager } from "../../util/SelectionManager";
 import { ViewBoxAnnotatableComponent } from "../DocComponent";
 import { DocumentDecorations } from "../DocumentDecorations";
 import { InkingControl } from "../InkingControl";
@@ -37,15 +36,15 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
     public static LayoutString(fieldKey: string) { return FieldView.LayoutString(WebBox, fieldKey); }
     @observable private collapsed: boolean = true;
     @observable private url: string = "hello";
+    @observable private _pressX: number = 0;
+    @observable private _pressY: number = 0;
 
     private _longPressSecondsHack?: NodeJS.Timeout;
     private _outerRef = React.createRef<HTMLDivElement>();
     private _iframeRef = React.createRef<HTMLIFrameElement>();
     private _iframeIndicatorRef = React.createRef<HTMLDivElement>();
     private _iframeDragRef = React.createRef<HTMLDivElement>();
-    @observable private _pressX: number = 0;
-    @observable private _pressY: number = 0;
-    private _scrollTop = 0;
+    private _reactionDisposer?: IReactionDisposer;
     private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean) => void);
 
     iframeLoaded = action((e: any) => {
@@ -53,6 +52,16 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
         this._iframeRef.current!.contentDocument?.addEventListener('scroll', this.iframeScrolled, false);
         this.layoutDoc.scrollHeight = this._iframeRef.current!.contentDocument?.children?.[0].scrollHeight || 1000;
         this._iframeRef.current!.contentDocument!.children[0].scrollTop = NumCast(this.layoutDoc.scrollTop);
+        this._reactionDisposer?.();
+        this._reactionDisposer = reaction(() => this.layoutDoc.scrollY,
+            (scrollY) => {
+                if (scrollY !== undefined) {
+                    this._outerRef.current!.scrollTop = scrollY;
+                    this.layoutDoc.scrollY = undefined;
+                }
+            },
+            { fireImmediately: true }
+        );
     });
     setPreviewCursor = (func?: (x: number, y: number, drag: boolean) => void) => this._setPreviewCursor = func;
     iframedown = (e: PointerEvent) => {
@@ -60,7 +69,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
     }
     iframeScrolled = (e: any) => {
         const scroll = (e.target as any)?.children?.[0].scrollTop;
-        this.layoutDoc.scrollTop = this._outerRef.current!.scrollTop = this._scrollTop = scroll;
+        this.layoutDoc.scrollTop = this._outerRef.current!.scrollTop = scroll;
     }
     async componentDidMount() {
 
@@ -87,6 +96,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
     }
 
     componentWillUnmount() {
+        this._reactionDisposer?.();
         document.removeEventListener("pointerup", this.onLongPressUp);
         document.removeEventListener("pointermove", this.onLongPressMove);
         this._iframeRef.current!.contentDocument?.removeEventListener('pointerdown', this.iframedown);
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index 71b19f3a6..eaf80d252 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -8,6 +8,7 @@ import { Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types";
 import { DocumentManager } from "../../util/DocumentManager";
 import PDFMenu from "./PDFMenu";
 import "./Annotation.scss";
+import { DocumentView } from "../nodes/DocumentView";
 
 interface IAnnotationProps {
     anno: Doc;
@@ -97,7 +98,8 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
         else if (e.button === 0) {
             const annoGroup = await Cast(this.props.document.group, Doc);
             if (annoGroup) {
-                DocumentManager.Instance.FollowLink(undefined, annoGroup, (doc, followLinkLocation) => this.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation), false,  undefined);
+                DocumentView._focusHack = [];
+                DocumentManager.Instance.FollowLink(undefined, annoGroup, (doc, followLinkLocation) => this.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation), false, undefined);
                 e.stopPropagation();
             }
         }
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 948d2300d..75be08e9f 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -9,7 +9,7 @@ import { List } from "../../../new_fields/List";
 import { makeInterface, createSchema } from "../../../new_fields/Schema";
 import { ScriptField, ComputedField } from "../../../new_fields/ScriptField";
 import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
-import { smoothScroll, Utils, emptyFunction, returnOne, intersectRect, addStyleSheet, addStyleSheetRule, clearStyleSheetRules, returnZero } from "../../../Utils";
+import { smoothScroll, Utils, emptyFunction, returnOne, intersectRect, addStyleSheet, addStyleSheetRule, clearStyleSheetRules, returnZero, emptyPath } from "../../../Utils";
 import { Docs, DocUtils } from "../../documents/Documents";
 import { DragManager } from "../../util/DragManager";
 import { CompiledScript, CompileScript } from "../../util/Scripting";
@@ -607,7 +607,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
 
 
     getCoverImage = () => {
-        if (!this.props.Document[HeightSym]() || !this.props.Document.nativeHeight) {
+        if (!this.props.Document[HeightSym]() || !this.props.Document._nativeHeight) {
             setTimeout((() => {
                 this.Document._height = this.Document[WidthSym]() * this._coverPath.height / this._coverPath.width;
                 this.Document._nativeHeight = (this.Document._nativeWidth || 0) * this._coverPath.height / this._coverPath.width;
@@ -632,7 +632,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
 
     @computed get annotationLayer() {
         TraceMobx();
-        return <div className="pdfViewer-annotationLayer" style={{ height: NumCast(this.Document.nativeHeight), transform: `scale(${this._zoomed})` }} ref={this._annotationLayer}>
+        return <div className="pdfViewer-annotationLayer" style={{ height: NumCast(this.Document._nativeHeight), transform: `scale(${this._zoomed})` }} ref={this._annotationLayer}>
             {this.nonDocAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map((anno, index) =>
                 <Annotation {...this.props} focus={this.props.focus} dataDoc={this.dataDoc} fieldKey={this.props.fieldKey} anno={anno} key={`${anno[Id]}-annotation`} />)}
         </div>;
@@ -643,7 +643,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
     @computed get overlayLayer() {
         return <div className={`pdfViewer-overlay${InkingControl.Instance.selectedTool !== InkTool.None ? "-inking" : ""}`} id="overlay" style={{ transform: `scale(${this._zoomed})` }}>
             <CollectionFreeFormView {...this.props}
-                LibraryPath={this.props.ContainingCollectionView?.props.LibraryPath ?? []}
+                LibraryPath={this.props.ContainingCollectionView?.props.LibraryPath ?? emptyPath}
                 annotationsKey={this.annotationKey}
                 setPreviewCursor={this.setPreviewCursor}
                 PanelHeight={this.panelWidth}
-- 
cgit v1.2.3-70-g09d2


From 9fcde485c294c1cfae84874df162c8f8da5fb1f5 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 18 Apr 2020 17:42:09 -0400
Subject: cleaned up webbox - fit width by default. native dimensions by
 default.  toggle buttons for annotate/interact.

---
 src/client/documents/Documents.ts                  |  4 +-
 .../views/collections/CollectionDockingView.tsx    | 30 ++++++---
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  2 +-
 src/client/views/nodes/PDFBox.scss                 |  3 -
 src/client/views/nodes/WebBox.scss                 | 21 +++---
 src/client/views/nodes/WebBox.tsx                  | 78 ++++++++++++----------
 src/client/views/pdf/PDFViewer.scss                |  4 --
 src/client/views/pdf/PDFViewer.tsx                 |  7 +-
 8 files changed, 77 insertions(+), 72 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 968e3d4dc..baaee09ea 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -590,7 +590,7 @@ export namespace Docs {
         }
 
         export function WebDocument(url: string, options: DocumentOptions = {}) {
-            return InstanceFromProto(Prototypes.get(DocumentType.WEB), new WebField(new URL(url)), options);
+            return InstanceFromProto(Prototypes.get(DocumentType.WEB), new WebField(new URL(url)), { _fitWidth: true, ...options });
         }
 
         export function HtmlDocument(html: string, options: DocumentOptions = {}) {
@@ -914,7 +914,7 @@ export namespace Docs {
                     });
                 }
                 ctor = Docs.Create.WebDocument;
-                options = { _height: options._width, ...options, title: path, _nativeWidth: undefined };
+                options = { ...options, _nativeWidth: 850, _nativeHeight: 962, _width: 500, _height: 566, title: path, };
             }
             return ctor ? ctor(path, options) : undefined;
         }
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index d77ef812f..c74f5555b 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -739,19 +739,27 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
     nativeHeight = () => !this.layoutDoc!._fitWidth ? NumCast(this.layoutDoc!._nativeHeight) || this._panelHeight : 0;
 
     contentScaling = () => {
-        if (this.layoutDoc!.type === DocumentType.PDF) {
-            if ((this.layoutDoc && this.layoutDoc._fitWidth) ||
-                this._panelHeight / NumCast(this.layoutDoc!._nativeHeight) > this._panelWidth / NumCast(this.layoutDoc!._nativeWidth)) {
-                return this._panelWidth / NumCast(this.layoutDoc!._nativeWidth);
-            } else {
-                return this._panelHeight / NumCast(this.layoutDoc!._nativeHeight);
-            }
-        }
         const nativeH = this.nativeHeight();
         const nativeW = this.nativeWidth();
-        if (!nativeW || !nativeH) return 1;
-        const wscale = this.panelWidth() / nativeW;
-        return wscale * nativeH > this._panelHeight ? this._panelHeight / nativeH : wscale;
+        let scaling = 1;
+        if (!this.layoutDoc?._fitWidth && (!nativeW || !nativeH)) {
+            scaling = 1;
+        } else if ((this.layoutDoc?._fitWidth) ||
+            this._panelHeight / NumCast(this.layoutDoc!._nativeHeight) > this._panelWidth / NumCast(this.layoutDoc!._nativeWidth)) {
+            scaling = this._panelWidth / NumCast(this.layoutDoc!._nativeWidth);
+        } else {
+            // if (this.layoutDoc!.type === DocumentType.PDF || this.layoutDoc!.type === DocumentType.WEB) {
+            //     if ((this.layoutDoc?._fitWidth) ||
+            //         this._panelHeight / NumCast(this.layoutDoc!._nativeHeight) > this._panelWidth / NumCast(this.layoutDoc!._nativeWidth)) {
+            //         return this._panelWidth / NumCast(this.layoutDoc!._nativeWidth);
+            //     } else {
+            //         return this._panelHeight / NumCast(this.layoutDoc!._nativeHeight);
+            //     }
+            // }
+            const wscale = this.panelWidth() / nativeW;
+            scaling = wscale * nativeH > this._panelHeight ? this._panelHeight / nativeH : wscale;
+        }
+        return scaling;
     }
 
     ScreenToLocalTransform = () => {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index b3409bd57..87cf716e5 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -822,7 +822,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument, u
 
             if (!willZoom && DocumentView._focusHack.length) {
                 Doc.BrushDoc(this.props.Document);
-                !doc.z && this.scaleAtPt(DocumentView._focusHack, 1); // [NumCast(doc.x), NumCast(doc.y)], 1);
+                !doc.z && NumCast(this.layoutDoc.scale) < 1 && this.scaleAtPt(DocumentView._focusHack, 1); // [NumCast(doc.x), NumCast(doc.y)], 1);
             } else {
                 if (DocListCast(this.dataDoc[this.props.fieldKey]).includes(doc)) {
                     if (!doc.z) this.setPan(newPanX, newPanY, "Ease", true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow
diff --git a/src/client/views/nodes/PDFBox.scss b/src/client/views/nodes/PDFBox.scss
index 7a3d2e92b..bccf0f291 100644
--- a/src/client/views/nodes/PDFBox.scss
+++ b/src/client/views/nodes/PDFBox.scss
@@ -199,9 +199,6 @@
 
 .pdfBox {
     pointer-events: none;
-    .collectionFreeFormView-none {
-        pointer-events: none;
-    }
     .pdfViewer-text {
         .textLayer {
             span {
diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss
index cdeac4bd1..af84a7d95 100644
--- a/src/client/views/nodes/WebBox.scss
+++ b/src/client/views/nodes/WebBox.scss
@@ -48,11 +48,17 @@
     position: absolute;
 }
 
-.webBox-button {
-    padding: 0vw;
-    border: none;
+.webBox-buttons {
+    margin-left: 44;
+    background:lightGray;
     width: 100%;
-    height: 100%;
+}
+.webBox-freeze {
+    display: flex;
+    align-items: center; 
+    justify-content: center;
+    margin-right: 5px;
+    width: 30px;
 }
 
 .webBox-urlEditor {
@@ -60,7 +66,6 @@
     opacity: 0.9;
     z-index: 9001;
     transition: top .5s;
-    background: lightgrey;
     padding: 10px;
 
 
@@ -70,12 +75,6 @@
         padding-bottom: 10px;
         overflow: hidden;
 
-        .webBox-freeze {
-            display: flex;
-            align-items: center; 
-            justify-content: center;
-        }
-
         .editorBase {
             display: flex;
 
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 510e6be0c..0a7772044 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -1,5 +1,5 @@
 import { library } from "@fortawesome/fontawesome-svg-core";
-import { faStickyNote, faLock, faUnlock } from '@fortawesome/free-solid-svg-icons';
+import { faStickyNote, faPen, faMousePointer } from '@fortawesome/free-solid-svg-icons';
 import { action, computed, observable, trace, IReactionDisposer, reaction } from "mobx";
 import { observer } from "mobx-react";
 import { Doc, FieldResult } from "../../../new_fields/Doc";
@@ -34,8 +34,8 @@ const WebDocument = makeInterface(documentSchema);
 export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocument>(WebDocument) {
 
     public static LayoutString(fieldKey: string) { return FieldView.LayoutString(WebBox, fieldKey); }
-    @observable private collapsed: boolean = true;
-    @observable private url: string = "hello";
+    @observable private _collapsed: boolean = true;
+    @observable private _url: string = "hello";
     @observable private _pressX: number = 0;
     @observable private _pressY: number = 0;
 
@@ -105,19 +105,19 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
 
     @action
     onURLChange = (e: React.ChangeEvent<HTMLInputElement>) => {
-        this.url = e.target.value;
+        this._url = e.target.value;
     }
 
     @action
     submitURL = () => {
-        this.dataDoc[this.props.fieldKey] = new WebField(new URL(this.url));
+        this.dataDoc[this.props.fieldKey] = new WebField(new URL(this._url));
     }
 
     @action
     setURL() {
         const urlField: FieldResult<WebField> = Cast(this.dataDoc[this.props.fieldKey], WebField);
-        if (urlField) this.url = urlField.url.toString();
-        else this.url = "";
+        if (urlField) this._url = urlField.url.toString();
+        else this._url = "";
     }
 
     onValueKeyDown = async (e: React.KeyboardEvent) => {
@@ -128,36 +128,44 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
     }
 
     toggleNativeDimensions = () => {
-        if (this.Document._nativeWidth || this.Document._nativeHeight) {
-            DocumentView.unfreezeNativeDimensions(this.layoutDoc);
+        if (!this.layoutDoc.isAnnotating) {
+            //DocumentView.unfreezeNativeDimensions(this.layoutDoc);
             this.layoutDoc.lockedTransform = false;
+            this.layoutDoc.isAnnotating = true;
         }
         else {
-            Doc.freezeNativeDimensions(this.layoutDoc, this.props.PanelWidth(), this.props.PanelHeight());
+            //Doc.freezeNativeDimensions(this.layoutDoc, this.props.PanelWidth(), this.props.PanelHeight());
             this.layoutDoc.lockedTransform = true;
+            this.layoutDoc.isAnnotating = false;
         }
     }
 
     urlEditor() {
-        const frozen = this.layoutDoc._nativeWidth && this.layoutDoc._nativeHeight;
+        const frozen = this.layoutDoc._nativeWidth && this.layoutDoc.isAnnotating;
         return (
-            <div className="webBox-urlEditor" style={{ top: this.collapsed ? -70 : 0 }}>
+            <div className="webBox-urlEditor" style={{ top: this._collapsed ? -70 : 0 }}>
                 <div className="urlEditor">
                     <div className="editorBase">
                         <button className="editor-collapse"
                             style={{
-                                top: this.collapsed ? 70 : 10,
-                                transform: `rotate(${this.collapsed ? 180 : 0}deg) scale(${this.collapsed ? 0.5 : 1}) translate(${this.collapsed ? "-100%, -100%" : "0, 0"})`,
-                                opacity: (this.collapsed && !this.props.isSelected()) ? 0 : 0.9,
-                                left: (this.collapsed ? 0 : "unset"),
+                                top: this._collapsed ? 70 : 10,
+                                transform: `rotate(${this._collapsed ? 180 : 0}deg) scale(${this._collapsed ? 0.5 : 1}) translate(${this._collapsed ? "-100%, -100%" : "0, 0"})`,
+                                opacity: (this._collapsed && !this.props.isSelected()) ? 0 : 0.9,
+                                left: (this._collapsed ? 0 : "unset"),
                             }}
                             title="Collapse Url Editor" onClick={this.toggleCollapse}>
                             <FontAwesomeIcon icon="caret-up" size="2x" />
                         </button>
-                        <div style={{ marginLeft: 54, width: "100%", display: this.collapsed ? "none" : "flex" }}>
+                        <div className="webBox-buttons" style={{ display: this._collapsed ? "none" : "flex" }}>
+                            <div className="webBox-freeze" title={"Annotate"} style={{ background: frozen ? "lightBlue" : "gray" }} onClick={this.toggleNativeDimensions} >
+                                <FontAwesomeIcon icon={faPen} size={"2x"} />
+                            </div>
+                            <div className="webBox-freeze" title={"Select"} style={{ background: !frozen ? "lightBlue" : "gray" }} onClick={this.toggleNativeDimensions} >
+                                <FontAwesomeIcon icon={faMousePointer} size={"2x"} />
+                            </div>
                             <input className="webpage-urlInput"
                                 placeholder="ENTER URL"
-                                value={this.url}
+                                value={this._url}
                                 onChange={this.onURLChange}
                                 onKeyDown={this.onValueKeyDown}
                             />
@@ -170,9 +178,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
                                 <button className="submitUrl" onClick={this.submitURL}>
                                     SUBMIT
                                 </button>
-                                <div className="webBox-freeze" title={frozen ? "Unfreeze" : "Freeze Dimensions"} onClick={this.toggleNativeDimensions} >
-                                    <FontAwesomeIcon icon={frozen ? faUnlock : faLock} size={"lg"} />
-                                </div>
                             </div>
                         </div>
                     </div>
@@ -183,7 +188,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
 
     @action
     toggleCollapse = () => {
-        this.collapsed = !this.collapsed;
+        this._collapsed = !this._collapsed;
     }
 
     _ignore = 0;
@@ -326,20 +331,19 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
 
         const frozen = !this.props.isSelected() || decInteracting;
 
-        return (
-            <>
-                <div className={"webBox-cont" + (this.props.isSelected() && InkingControl.Instance.selectedTool === InkTool.None && !decInteracting ? "-interactive" : "")}  >
-                    {content}
-                </div>
-                {!frozen ? (null) :
-                    <div className="webBox-overlay" style={{ pointerEvents: this.layoutDoc.isBackground ? undefined : "all" }}
-                        onWheel={this.onPreWheel} onPointerDown={this.onPrePointer} onPointerMove={this.onPrePointer} onPointerUp={this.onPrePointer}>
-                        <div className="touch-iframe-overlay" onPointerDown={this.onLongPressDown} >
-                            <div className="indicator" ref={this._iframeIndicatorRef}></div>
-                            <div className="dragger" ref={this._iframeDragRef}></div>
-                        </div>
-                    </div>}
-            </>);
+        return (<>
+            <div className={"webBox-cont" + (this.props.isSelected() && InkingControl.Instance.selectedTool === InkTool.None && !decInteracting ? "-interactive" : "")}  >
+                {content}
+            </div>
+            {!frozen ? (null) :
+                <div className="webBox-overlay" style={{ pointerEvents: this.layoutDoc.isBackground ? undefined : "all" }}
+                    onWheel={this.onPreWheel} onPointerDown={this.onPrePointer} onPointerMove={this.onPrePointer} onPointerUp={this.onPrePointer}>
+                    <div className="touch-iframe-overlay" onPointerDown={this.onLongPressDown} >
+                        <div className="indicator" ref={this._iframeIndicatorRef}></div>
+                        <div className="dragger" ref={this._iframeDragRef}></div>
+                    </div>
+                </div>}
+        </>);
     }
     scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.props.Document.scrollTop))
     render() {
@@ -352,7 +356,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
             }} >
             {this.content}
             <div className={"webBox-outerContent"} ref={this._outerRef}
-                style={{ pointerEvents: this.layoutDoc._nativeHeight && !this.layoutDoc.isBackground ? "all" : "none" }}
+                style={{ pointerEvents: this.layoutDoc.isAnnotating && !this.layoutDoc.isBackground ? "all" : "none" }}
                 onWheel={e => e.stopPropagation()}
                 onScroll={e => {
                     if (this._iframeRef.current!.contentDocument!.children[0].scrollTop !== this._outerRef.current!.scrollTop) {
diff --git a/src/client/views/pdf/PDFViewer.scss b/src/client/views/pdf/PDFViewer.scss
index 5cd2c4fe4..26c31a80e 100644
--- a/src/client/views/pdf/PDFViewer.scss
+++ b/src/client/views/pdf/PDFViewer.scss
@@ -30,10 +30,6 @@
     .page {
         position: relative;
     }
-    .collectionfreeformview-container {
-        pointer-events: none;
-    }
-
     .pdfViewer-text-selected {
         .textLayer{
            pointer-events: all;
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 75be08e9f..d54390236 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -282,7 +282,6 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
             if (anno.style.height) annoDoc._height = parseInt(anno.style.height);
             if (anno.style.width) annoDoc._width = parseInt(anno.style.width);
             annoDoc.group = mainAnnoDoc;
-            annoDoc.isLinkButton = true;
             annoDocs.push(annoDoc);
             anno.remove();
             mainAnnoDoc = annoDoc;
@@ -554,7 +553,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
     highlight = (color: string) => {
         // creates annotation documents for current highlights
         const annotationDoc = this.makeAnnotationDocument(color);
-        annotationDoc && this.props.addDocument && this.props.addDocument(annotationDoc);
+        annotationDoc && this.props?.addDocument(annotationDoc);
         return annotationDoc;
     }
 
@@ -585,6 +584,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
                 dragComplete: e => {
                     if (!e.aborted && e.annoDragData && !e.annoDragData.linkedToDoc) {
                         const link = DocUtils.MakeLink({ doc: annotationDoc }, { doc: e.annoDragData.dropDocument }, "Annotation");
+                        annotationDoc.isLinkButton = true;
                         if (link) link.followLinkLocation = "onRight";
                     }
                 }
@@ -641,7 +641,8 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
     panelWidth = () => (this.Document.scrollHeight || this.Document._nativeHeight || 0);
     panelHeight = () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : (this.Document._nativeWidth || 0);
     @computed get overlayLayer() {
-        return <div className={`pdfViewer-overlay${InkingControl.Instance.selectedTool !== InkTool.None ? "-inking" : ""}`} id="overlay" style={{ transform: `scale(${this._zoomed})` }}>
+        return <div className={`pdfViewer-overlay${InkingControl.Instance.selectedTool !== InkTool.None ? "-inking" : ""}`} id="overlay"
+            style={{ transform: `scale(${this._zoomed})`, pointerEvents: this.active(false) ? "all" : undefined }}>
             <CollectionFreeFormView {...this.props}
                 LibraryPath={this.props.ContainingCollectionView?.props.LibraryPath ?? emptyPath}
                 annotationsKey={this.annotationKey}
-- 
cgit v1.2.3-70-g09d2


From 821bd7bb1b2a59459c63ecfbe9c513e5f36a7e43 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 20 Apr 2020 01:39:15 -0400
Subject: fixed runtime errors for AntimodeMenu and from current_user_utils not
 initializing properly

---
 src/client/util/RichTextMenu.tsx                    |  8 ++++----
 src/client/views/AntimodeMenu.tsx                   | 21 +++++++++++++--------
 src/client/views/collections/CollectionTreeView.tsx |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx   |  2 +-
 .../authentication/models/current_user_utils.ts     | 13 +++++++------
 5 files changed, 26 insertions(+), 20 deletions(-)

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

diff --git a/src/client/util/RichTextMenu.tsx b/src/client/util/RichTextMenu.tsx
index 3f0ec7aa5..4a9a4c10f 100644
--- a/src/client/util/RichTextMenu.tsx
+++ b/src/client/util/RichTextMenu.tsx
@@ -445,8 +445,8 @@ export default class RichTextMenu extends AntimodeMenu {
         }
 
         const button =
-            <button className="antimodeMenu-button" title="" onPointerDown={onBrushClick} style={this.brushMarks && this.brushMarks.size > 0 ? { backgroundColor: "121212" } : {}}>
-                <FontAwesomeIcon icon="paint-roller" size="lg" style={{ transition: "transform 0.1s", transform: this.brushMarks && this.brushMarks.size > 0 ? "rotate(45deg)" : "" }} />
+            <button className="antimodeMenu-button" title="" onPointerDown={onBrushClick} style={this.brushMarks?.size > 0 ? { backgroundColor: "121212" } : {}}>
+                <FontAwesomeIcon icon="paint-roller" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.1s", transform: `rotate(${this.brushMarks?.size > 0 ? 45 : 0}deg)` }} />
             </button>;
 
         const dropdownContent =
@@ -790,11 +790,11 @@ export default class RichTextMenu extends AntimodeMenu {
             <div key="button">
                 <div key="collapser">
                     <button className="antimodeMenu-button" key="collapse menu" title="Collapse menu" onClick={this.toggleCollapse} style={{ backgroundColor: this.collapsed ? "#121212" : "", width: 25 }}>
-                        <FontAwesomeIcon icon="chevron-left" size="lg" style={{ transition: "transform 0.3s", transform: this.collapsed ? "rotate(180deg)" : "" }} />
+                        <FontAwesomeIcon icon="chevron-left" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.3s", transform: `rotate(${this.collapsed ? 180 : 0}deg)` }} />
                     </button>
                 </div>
                 <button className="antimodeMenu-button" key="pin menu" title="Pin menu" onClick={this.toggleMenuPin} style={{ backgroundColor: this.Pinned ? "#121212" : "", display: this.collapsed ? "none" : undefined }}>
-                    <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transition: "transform 0.1s", transform: this.Pinned ? "rotate(45deg)" : "" }} />
+                    <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.1s", transform: `rotate(${this.Pinned ? 45 : 0}deg)` }} />
                 </button>
                 {this.getDragger()}
             </div>
diff --git a/src/client/views/AntimodeMenu.tsx b/src/client/views/AntimodeMenu.tsx
index fba2fb5c6..f810361c6 100644
--- a/src/client/views/AntimodeMenu.tsx
+++ b/src/client/views/AntimodeMenu.tsx
@@ -16,7 +16,8 @@ export default abstract class AntimodeMenu extends React.Component {
     @observable protected _top: number = -300;
     @observable protected _left: number = -300;
     @observable protected _opacity: number = 1;
-    @observable protected _transition: string = "opacity 0.5s";
+    @observable protected _transitionProperty: string = "opacity";
+    @observable protected _transitionDuration: string = "0.5s";
     @observable protected _transitionDelay: string = "";
     @observable protected _canFade: boolean = true;
 
@@ -34,7 +35,7 @@ export default abstract class AntimodeMenu extends React.Component {
      */
     public jumpTo = (x: number, y: number, forceJump: boolean = false) => {
         if (!this.Pinned || forceJump) {
-            this._transition = this._transitionDelay = "";
+            this._transitionProperty = this._transitionDuration = this._transitionDelay = "";
             this._opacity = 1;
             this._left = x;
             this._top = y;
@@ -49,14 +50,16 @@ export default abstract class AntimodeMenu extends React.Component {
     public fadeOut = (forceOut: boolean) => {
         if (!this.Pinned) {
             if (this._opacity === 0.2) {
-                this._transition = "opacity 0.1s";
+                this._transitionProperty = "opacity";
+                this._transitionDuration = "0.1s";
                 this._transitionDelay = "";
                 this._opacity = 0;
                 this._left = this._top = -300;
             }
 
             if (forceOut) {
-                this._transition = "";
+                this._transitionProperty = "";
+                this._transitionDuration = "";
                 this._transitionDelay = "";
                 this._opacity = 0;
                 this._left = this._top = -300;
@@ -67,7 +70,8 @@ export default abstract class AntimodeMenu extends React.Component {
     @action
     protected pointerLeave = (e: React.PointerEvent) => {
         if (!this.Pinned && this._canFade) {
-            this._transition = "opacity 0.5s";
+            this._transitionProperty = "opacity";
+            this._transitionDuration = "0.5s";
             this._transitionDelay = "1s";
             this._opacity = 0.2;
             setTimeout(() => this.fadeOut(false), 3000);
@@ -76,7 +80,8 @@ export default abstract class AntimodeMenu extends React.Component {
 
     @action
     protected pointerEntered = (e: React.PointerEvent) => {
-        this._transition = "opacity 0.1s";
+        this._transitionProperty = "opacity";
+        this._transitionDuration = "0.1s";
         this._transitionDelay = "";
         this._opacity = 1;
     }
@@ -133,7 +138,7 @@ export default abstract class AntimodeMenu extends React.Component {
     protected getElement(buttons: JSX.Element[]) {
         return (
             <div className="antimodeMenu-cont" onPointerLeave={this.pointerLeave} onPointerEnter={this.pointerEntered} ref={this._mainCont} onContextMenu={this.handleContextMenu}
-                style={{ left: this._left, top: this._top, opacity: this._opacity, transition: this._transition, transitionDelay: this._transitionDelay }}>
+                style={{ left: this._left, top: this._top, opacity: this._opacity, transitionProperty: this._transitionProperty, transitionDuration: this._transitionDuration, transitionDelay: this._transitionDelay }}>
                 {buttons}
                 <div className="antimodeMenu-dragger" onPointerDown={this.dragStart} style={{ width: this.Pinned ? "20px" : "0px" }} />
             </div>
@@ -143,7 +148,7 @@ export default abstract class AntimodeMenu extends React.Component {
     protected getElementWithRows(rows: JSX.Element[], numRows: number, hasDragger: boolean = true) {
         return (
             <div className="antimodeMenu-cont with-rows" onPointerLeave={this.pointerLeave} onPointerEnter={this.pointerEntered} ref={this._mainCont} onContextMenu={this.handleContextMenu}
-                style={{ left: this._left, top: this._top, opacity: this._opacity, transition: this._transition, transitionDelay: this._transitionDelay, height: "auto" }}>
+                style={{ left: this._left, top: this._top, opacity: this._opacity, transitionProperty: this._transitionProperty, transitionDuration: this._transitionDuration, transitionDelay: this._transitionDelay, height: "auto" }}>
                 {rows}
                 {hasDragger ? <div className="antimodeMenu-dragger" onPointerDown={this.dragStart} style={{ width: this.Pinned ? "20px" : "0px" }} /> : <></>}
             </div>
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 83e536167..011e07287 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -650,7 +650,7 @@ export type collectionTreeViewProps = {
 };
 
 @observer
-export class CollectionTreeView extends CollectionSubView(Document, undefined as any as collectionTreeViewProps) {
+export class CollectionTreeView extends CollectionSubView<Document, Partial<collectionTreeViewProps>>(Document) {
     private treedropDisposer?: DragManager.DragDropDisposer;
     private _mainEle?: HTMLDivElement;
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 87cf716e5..d2106808e 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -75,7 +75,7 @@ export type collectionFreeformViewProps = {
 };
 
 @observer
-export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument, undefined as any as collectionFreeformViewProps) {
+export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, Partial<collectionFreeformViewProps>>(PanZoomDocument) {
     private _lastX: number = 0;
     private _lastY: number = 0;
     private _downX: number = 0;
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index a2f016f94..85938e026 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -536,12 +536,13 @@ export class CurrentUserUtils {
         new InkingControl();
         doc.title = Doc.CurrentUserEmail;
         doc.activePen = doc;
-        CurrentUserUtils.setupDefaultIconTemplates(doc);  // creates a set of icon templates triggered by the document deoration icon
-        CurrentUserUtils.setupRightSidebar(doc);  // sets up the right sidebar collection for mobile upload documents and sharing
-        CurrentUserUtils.setupOverlays(doc);  // documents in overlay layer 
-        CurrentUserUtils.setupDockedButtons(doc);  // the bottom bar of font icons
-        CurrentUserUtils.setupDefaultPresentation(doc); // presentation that's initially triggered
-        await CurrentUserUtils.setupSidebarButtons(doc); // the pop-out left sidebar of tools/panels
+        this.setupDefaultIconTemplates(doc);  // creates a set of icon templates triggered by the document deoration icon
+        this.setupDocTemplates(doc); // sets up the template menu of templates
+        this.setupRightSidebar(doc);  // sets up the right sidebar collection for mobile upload documents and sharing
+        this.setupOverlays(doc);  // documents in overlay layer 
+        this.setupDockedButtons(doc);  // the bottom bar of font icons
+        this.setupDefaultPresentation(doc); // presentation that's initially triggered
+        await this.setupSidebarButtons(doc); // the pop-out left sidebar of tools/panels
         doc.globalLinkDatabase = Docs.Prototypes.MainLinkDocument();
 
         // setup reactions to change the highlights on the undo/redo buttons -- would be better to encode this in the undo/redo buttons, but the undo/redo stacks are not wired up that way yet
-- 
cgit v1.2.3-70-g09d2


From 682782a1337003de1694d1625d262a1efddcb02d Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 21 Apr 2020 23:50:20 -0400
Subject: tweaked HTMLtag syntax slightly for onClick functions to be specified
 without quotes

---
 package-lock.json                                  | 27 ++++++++++++++++++
 package.json                                       |  2 ++
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  1 -
 src/client/views/nodes/DocumentContentsView.tsx    | 32 ++++++++++++++++------
 src/client/views/search/SearchBox.tsx              |  4 +--
 5 files changed, 54 insertions(+), 12 deletions(-)

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

diff --git a/package-lock.json b/package-lock.json
index 940321132..176d1f27e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -43,6 +43,15 @@
                 "regenerator-runtime": "^0.13.4"
             }
         },
+        "@babel/runtime-corejs3": {
+            "version": "7.9.2",
+            "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.9.2.tgz",
+            "integrity": "sha512-HHxmgxbIzOfFlZ+tdeRKtaxWOMUoCG5Mu3wKeUmOxjYrwb3AAHgnmtCUbPPK11/raIWLIBK250t8E2BPO0p7jA==",
+            "requires": {
+                "core-js-pure": "^3.0.0",
+                "regenerator-runtime": "^0.13.4"
+            }
+        },
         "@babel/types": {
             "version": "7.9.5",
             "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.5.tgz",
@@ -1194,6 +1203,11 @@
             "resolved": "https://registry.npmjs.org/@types/word-extractor/-/word-extractor-0.3.0.tgz",
             "integrity": "sha512-XcVsLXMpPT6Lv4Qvsc2yPoWg3zPVKEIyQ21eQ1ka58zZjtd7vUZ2kx0KydRbsNRNGopEIiwlemGzgo46dytR2Q=="
         },
+        "@types/xregexp": {
+            "version": "4.3.0",
+            "resolved": "https://registry.npmjs.org/@types/xregexp/-/xregexp-4.3.0.tgz",
+            "integrity": "sha512-3gJTS9gt27pS7U9q5IVqo4YvKSlkf2ck8ish6etuDj6LIRxkL/2Y8RMUtK/QzvE1Yv2zwWV5yemI2BS0GGGFnA=="
+        },
         "@types/yargs": {
             "version": "13.0.8",
             "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.8.tgz",
@@ -3456,6 +3470,11 @@
             "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
             "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY="
         },
+        "core-js-pure": {
+            "version": "3.6.5",
+            "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.6.5.tgz",
+            "integrity": "sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA=="
+        },
         "core-util-is": {
             "version": "1.0.2",
             "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
@@ -18092,6 +18111,14 @@
             "resolved": "https://registry.npmjs.org/xoauth2/-/xoauth2-1.2.0.tgz",
             "integrity": "sha1-8u76wRRyyXHqO8RuVU60sSMhRuU="
         },
+        "xregexp": {
+            "version": "4.3.0",
+            "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.3.0.tgz",
+            "integrity": "sha512-7jXDIFXh5yJ/orPn4SXjuVrWWoi4Cr8jfV1eHv9CixKSbU+jY4mxfrBwAuDvupPNKpMUY+FeIqsVw/JLT9+B8g==",
+            "requires": {
+                "@babel/runtime-corejs3": "^7.8.3"
+            }
+        },
         "xtend": {
             "version": "4.0.2",
             "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
diff --git a/package.json b/package.json
index ed846fcff..f1b4be520 100644
--- a/package.json
+++ b/package.json
@@ -127,6 +127,7 @@
         "@types/uuid": "^3.4.6",
         "@types/webpack": "^4.41.3",
         "@types/word-extractor": "^0.3.0",
+        "@types/xregexp": "^4.3.0",
         "@types/youtube": "0.0.38",
         "adm-zip": "^0.4.13",
         "archiver": "^3.1.1",
@@ -258,6 +259,7 @@
         "word-extractor": "^0.3.0",
         "words-to-numbers": "^1.5.1",
         "xoauth2": "^1.2.0",
+        "xregexp": "^4.3.0",
         "youtube": "^0.1.0"
     }
 }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index d2106808e..3b5101a4d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -44,7 +44,6 @@ import MarqueeOptionsMenu from "./MarqueeOptionsMenu";
 import { MarqueeView } from "./MarqueeView";
 import React = require("react");
 import { CollectionViewType } from "../CollectionView";
-import { Script } from "vm";
 
 library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload);
 
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index d577068b7..b5af68ba1 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -35,10 +35,10 @@ import { WebBox } from "./WebBox";
 import { InkingStroke } from "../InkingStroke";
 import React = require("react");
 import { RecommendationsBox } from "../RecommendationsBox";
-
 import { TraceMobx } from "../../../new_fields/util";
 import { ScriptField } from "../../../new_fields/ScriptField";
-import ReactTable from "react-table";
+import XRegExp = require("xregexp");
+
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
 type BindingProps = Without<FieldViewProps, 'fieldKey'>;
@@ -59,13 +59,13 @@ const ObserverJsxParser: typeof JsxParser = ObserverJsxParser1 as any;
 interface HTMLtagProps {
     Document: Doc;
     htmltag: string;
+    onClick?: ScriptField;
 }
-//"<HTMLdiv borderRadius='100px' onClick={() => this.bannerColor=this.bannerColor==='red'?'green':'red'} width='100%' height='100%' transform='rotate({2*this.x+this.y}deg)' backgroundColor='{this.bannerColor}'><ImageBox {...props} fieldKey={'data'}/><HTMLspan width='100%'  marginTop='50%'  height='10%'  position='absolute' backgroundColor='green'>{this.title}</HTMLspan></HTMLdiv>"
-@observer
+//"<HTMLdiv borderRadius='100px' onClick={this.bannerColor=this.bannerColor==='red'?'green':'red'} width='100%' height='100%' transform='rotate({2*this.x+this.y}deg)'><ImageBox {...props} fieldKey={'data'}/><HTMLspan width='100%'  marginTop='50%'  height='10%'  position='absolute' backgroundColor='{this.bannerColor===`green`?`dark`:`light`}grey'>{this.title}</HTMLspan></HTMLdiv>"@observer
 export class HTMLtag extends React.Component<HTMLtagProps> {
     click = (e: React.MouseEvent) => {
-        const clickScript = (this.props as any).onClick;
-        ScriptField.MakeScript(clickScript, { self: Doc.name, this: Doc.name })?.script.run({ this: this.props.Document });
+        const clickScript = (this.props as any).onClick as Opt<ScriptField>;
+        clickScript?.script.run({ this: this.props.Document });
     }
     render() {
         const style: { [key: string]: any } = {};
@@ -129,11 +129,12 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
         return Doc.expandTemplateLayout(template, this.props.Document, params ? "(" + params + ")" : this.props.layoutKey);
     }
 
-    CreateBindings(): JsxBindings {
+    CreateBindings(onClick: Opt<ScriptField>): JsxBindings {
         const list = {
             ...OmitKeys(this.props, ['parentActive'], (obj: any) => obj.active = this.props.parentActive).omit,
             Document: this.layoutDoc,
             DataDoc: this.dataDoc,
+            onClick: onClick
         };
         return { props: list };
     }
@@ -141,6 +142,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
     render() {
         TraceMobx();
         let layoutFrame = this.layout;
+
         // replace code content with a script  >{content}<   as in  <HTMLdiv>{this.title}</HTMLdiv>
         const replacer = (match: any, expr: string, offset: any, string: any) => {
             return ">" + (ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name })?.script.run({ this: this.props.Document }).result as string || "") + "<";
@@ -159,9 +161,21 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
         };
         layoutFrame = layoutFrame.replace(/<\/HTML([a-zA-Z0-9_-]+)/g, replacer3);
 
+        // add onClick function to props
+        const splits = layoutFrame.split("onClick=");
+        let onClick: Opt<ScriptField>;
+        if (splits.length > 1) {
+            const code = XRegExp.matchRecursive(splits[1], "{", "}", "", { valueNames: ["between", "left", "match", "right", "between"] });
+            layoutFrame = splits[0] + " onClick={props.onClick} " + splits[1].substring(code[1].end + 1);
+            onClick = ScriptField.MakeScript(code[1].value, { this: Doc.name, self: Doc.name });
+        }
+
+        const bindings = this.CreateBindings(onClick);
+        //  layoutFrame = splits.length > 1 ? splits[0] + splits[1].replace(/{([^{}]|(?R))*}/, replacer4) : ""; // might have been more elegant if javascript supported recursive patterns
+
         return (this.props.renderDepth > 12 || !layoutFrame || !this.layoutDoc) ? (null) :
             this.props.forceLayout === "FormattedTextBox" && this.props.forceFieldKey ?
-                <FormattedTextBox {...this.CreateBindings().props} fieldKey={this.props.forceFieldKey} />
+                <FormattedTextBox {...bindings.props} fieldKey={this.props.forceFieldKey} />
                 :
                 <ObserverJsxParser
                     key={42}
@@ -174,7 +188,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
                         ColorBox, DashWebRTCVideo, LinkAnchorBox, InkingStroke, DocHolderBox, LinkBox, ScriptingBox,
                         RecommendationsBox, ScreenshotBox, HTMLtag
                     }}
-                    bindings={this.CreateBindings()}
+                    bindings={bindings}
                     jsx={layoutFrame}
                     showWarnings={true}
 
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index b0a49f750..e41b725b1 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -29,7 +29,7 @@ export interface SearchProps {
     filterQquery?: string;
     setSearchQuery: (q: string) => {};
     searchFileTypes: string[];
-    setSearchFileTypes: (types: string[]) => {}
+    setSearchFileTypes: (types: string[]) => {};
 }
 
 export enum Keys {
@@ -86,7 +86,7 @@ export class SearchBox extends React.Component<SearchProps> {
             this._searchString = this.props.searchQuery;
             this.submitSearch();
         }
-    })
+    });
 
 
     @action
-- 
cgit v1.2.3-70-g09d2


From 72f614f023a4bac6a2f0b63a1b1a16e6f4545c7a Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 23 Apr 2020 00:53:08 -0400
Subject: fixed browser back after link navigation. simplified menus more.
 fixed jitter mode to be seeded. fixed menu gear for  collections. .

---
 src/client/util/History.ts                         |  6 +++-
 src/client/util/ProsemirrorExampleTransfer.ts      |  4 +--
 src/client/views/DocumentDecorations.tsx           |  6 +++-
 .../views/collections/CollectionDockingView.tsx    |  6 +++-
 .../views/collections/CollectionStackingView.tsx   |  2 +-
 .../views/collections/CollectionTimeView.tsx       |  2 +-
 .../views/collections/CollectionTreeView.tsx       |  2 +-
 src/client/views/collections/CollectionView.tsx    |  4 +--
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 21 +++++------
 src/client/views/nodes/AudioBox.tsx                |  2 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  9 +++--
 src/client/views/nodes/DocumentBox.tsx             |  2 +-
 src/client/views/nodes/DocumentView.tsx            |  2 +-
 src/client/views/nodes/FormattedTextBox.tsx        | 41 ++++++++++++++++++++--
 src/client/views/nodes/KeyValueBox.tsx             | 23 ++++++++----
 src/client/views/nodes/LinkAnchorBox.tsx           |  2 +-
 src/client/views/nodes/PDFBox.tsx                  |  2 +-
 src/client/views/nodes/ScreenshotBox.tsx           |  2 +-
 src/client/views/nodes/SliderBox.tsx               |  2 +-
 src/client/views/nodes/VideoBox.tsx                |  2 +-
 20 files changed, 103 insertions(+), 39 deletions(-)

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

diff --git a/src/client/util/History.ts b/src/client/util/History.ts
index 545e8acb4..2c53d7e52 100644
--- a/src/client/util/History.ts
+++ b/src/client/util/History.ts
@@ -40,8 +40,12 @@ export namespace HistoryUtil {
         // }
     }
 
+    let _lastStatePush = 0;
     export function pushState(state: ParsedUrl) {
-        history.pushState(state, "", createUrl(state));
+        if (Date.now() - _lastStatePush > 1000) {
+            history.pushState(state, "", createUrl(state));
+        }
+        _lastStatePush = Date.now();
     }
 
     export function replaceState(state: ParsedUrl) {
diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts
index 42247f177..680f48f70 100644
--- a/src/client/util/ProsemirrorExampleTransfer.ts
+++ b/src/client/util/ProsemirrorExampleTransfer.ts
@@ -154,7 +154,7 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, props: any
         const originalDoc = layoutDoc.rootDocument || layoutDoc;
         if (originalDoc instanceof Doc) {
             const newDoc = Docs.Create.TextDocument("", {
-                title: "", layout: Cast(originalDoc.layout, Doc, null) || FormattedTextBox.DefaultLayout, _singleLine: BoolCast(originalDoc._singleLine),
+                layout: Cast(originalDoc.layout, Doc, null) || FormattedTextBox.DefaultLayout, _singleLine: BoolCast(originalDoc._singleLine),
                 x: NumCast(originalDoc.x), y: NumCast(originalDoc.y) + NumCast(originalDoc._height) + 10, _width: NumCast(layoutDoc._width), _height: NumCast(layoutDoc._height)
             });
             FormattedTextBox.SelectOnLoad = newDoc[Id];
@@ -172,7 +172,7 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, props: any
         const originalDoc = layoutDoc.rootDocument || layoutDoc;
         if (force || props.Document._singleLine) {
             const newDoc = Docs.Create.TextDocument("", {
-                title: "", layout: Cast(originalDoc.layout, Doc, null) || FormattedTextBox.DefaultLayout, _singleLine: BoolCast(originalDoc._singleLine),
+                layout: Cast(originalDoc.layout, Doc, null) || FormattedTextBox.DefaultLayout, _singleLine: BoolCast(originalDoc._singleLine),
                 x: NumCast(originalDoc.x) + NumCast(originalDoc._width) + 10, y: NumCast(originalDoc.y), _width: NumCast(layoutDoc._width), _height: NumCast(layoutDoc._height)
             });
             FormattedTextBox.SelectOnLoad = newDoc[Id];
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 43c7751fa..6e699bc68 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -134,7 +134,11 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
     @action onSettingsClick = (e: PointerEvent): void => {
         if (e.button === 0 && !e.altKey && !e.ctrlKey) {
             let child = SelectionManager.SelectedDocuments()[0].ContentDiv!.children[0];
-            while (child.children.length) child = child.children[0];
+            while (child.children.length) {
+                const next = Array.from(child.children).find(c => !c.className.includes("collectionViewChrome"));
+                if (next) child = next;
+                else break;
+            }
             simulateMouseClick(child, e.clientX, e.clientY + 30, e.screenX, e.screenY + 30);
         }
     }
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 5e77bc0bb..0d859c3f1 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -29,6 +29,7 @@ import "./CollectionDockingView.scss";
 import { SubCollectionViewProps } from "./CollectionSubView";
 import { DockingViewButtonSelector } from './ParentDocumentSelector';
 import React = require("react");
+import { CollectionViewType } from './CollectionView';
 library.add(faFile);
 const _global = (window /* browser */ || global /* node */) as any;
 
@@ -93,6 +94,9 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
     @undoBatch
     @action
     public OpenFullScreen(docView: DocumentView, libraryPath?: Doc[]) {
+        if (docView.props.Document._viewType === CollectionViewType.Docking && docView.props.Document.layoutKey === "layout") {
+            return MainView.Instance.openWorkspace(docView.props.Document);
+        }
         const document = Doc.MakeAlias(docView.props.Document);
         const newItemStackConfig = {
             type: 'stack',
@@ -771,7 +775,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
 
     addDocTab = (doc: Doc, location: string, libraryPath?: Doc[]) => {
         SelectionManager.DeselectAll();
-        if (doc.dockingConfig) {
+        if (doc._viewType === CollectionViewType.Docking && doc.layoutKey === "layout") {
             return MainView.Instance.openWorkspace(doc);
         } else if (location === "onRight") {
             return CollectionDockingView.AddRightSplit(doc, libraryPath);
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index b15649d83..24a3119cc 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -397,7 +397,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         if (!e.isPropagationStopped()) {
             const subItems: ContextMenuProps[] = [];
             subItems.push({ description: `${this.props.Document.fillColumn ? "Variable Size" : "Autosize"} Column`, event: () => this.props.Document.fillColumn = !this.props.Document.fillColumn, icon: "plus" });
-            ContextMenu.Instance.addItem({ description: "Stacking Options ...", subitems: subItems, icon: "eye" });
+            ContextMenu.Instance.addItem({ description: "Options...", subitems: subItems, icon: "eye" });
         }
     }
 
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 742a818bc..06ebf6d2d 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -102,7 +102,7 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
         layoutItems.push({ description: "Auto Time/Pivot layout", event: () => { doc._forceRenderEngine = undefined; }, icon: "compress-arrows-alt" });
         layoutItems.push({ description: "Sync with presentation", event: () => CollectionTimeView.SyncTimelineToPresentation(doc), icon: "compress-arrows-alt" });
 
-        ContextMenu.Instance.addItem({ description: "Pivot/Time Options ...", subitems: layoutItems, icon: "eye" });
+        ContextMenu.Instance.addItem({ description: "Options...", subitems: layoutItems, icon: "eye" });
     }
     @computed get _allFacets() {
         const facets = new Set<string>();
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index f589c2c76..a052d045c 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -711,7 +711,7 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
             layoutItems.push({ description: (this.props.Document.treeViewPreventOpen ? "Persist" : "Abandon") + "Treeview State", event: () => this.props.Document.treeViewPreventOpen = !this.props.Document.treeViewPreventOpen, icon: "paint-brush" });
             layoutItems.push({ description: (this.props.Document.treeViewHideHeaderFields ? "Show" : "Hide") + " Header Fields", event: () => this.props.Document.treeViewHideHeaderFields = !this.props.Document.treeViewHideHeaderFields, icon: "paint-brush" });
             layoutItems.push({ description: (this.props.Document.treeViewHideTitle ? "Show" : "Hide") + " Title", event: () => this.props.Document.treeViewHideTitle = !this.props.Document.treeViewHideTitle, icon: "paint-brush" });
-            ContextMenu.Instance.addItem({ description: "Treeview Options ...", subitems: layoutItems, icon: "eye" });
+            ContextMenu.Instance.addItem({ description: "Options...", subitems: layoutItems, icon: "eye" });
         }
         ContextMenu.Instance.addItem({
             description: "Buxton Layout", icon: "eye", event: () => {
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index ade54f2c9..4f1242c33 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -229,7 +229,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
                 return newRendition;
             }, false);
 
-            const existing = ContextMenu.Instance.findByDescription("Layout...");
+            const existing = ContextMenu.Instance.findByDescription("Options...");
             const layoutItems = existing && "subitems" in existing ? existing.subitems : [];
             layoutItems.push({ description: `${this.props.Document.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.props.Document.forceActive = !this.props.Document.forceActive, icon: "project-diagram" });
             if (this.props.Document.childLayout instanceof Doc) {
@@ -240,7 +240,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
             }
             layoutItems.push({ description: `${this.props.Document.isInPlaceContainer ? "Unset" : "Set"} inPlace Container`, event: () => this.props.Document.isInPlaceContainer = !this.props.Document.isInPlaceContainer, icon: "project-diagram" });
 
-            !existing && ContextMenu.Instance.addItem({ description: "Layout...", subitems: layoutItems, icon: "hand-point-right" });
+            !existing && ContextMenu.Instance.addItem({ description: "Options...", subitems: layoutItems, icon: "hand-point-right" });
 
             const existingOnClick = ContextMenu.Instance.findByDescription("OnClick...");
             const onClicks = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 3b5101a4d..55e55e56c 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1048,16 +1048,17 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
 
     onContextMenu = (e: React.MouseEvent) => {
         if (this.props.children && this.props.annotationsKey) return;
-        const layoutItems: ContextMenuProps[] = [];
-
-        layoutItems.push({ description: "reset view", event: () => { this.props.Document._panX = this.props.Document._panY = 0; this.props.Document.scale = 1; }, icon: "compress-arrows-alt" });
-        layoutItems.push({ description: `${this.Document._LODdisable ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document._LODdisable = !this.Document._LODdisable, icon: "table" });
-        layoutItems.push({ description: `${this.fitToContent ? "Unset" : "Set"} Fit To Container`, event: () => this.Document._fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" });
-        layoutItems.push({ description: `${this.Document.useClusters ? "Uncluster" : "Use Clusters"}`, event: () => this.updateClusters(!this.Document.useClusters), icon: "braille" });
-        layoutItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" });
+        const options = ContextMenu.Instance.findByDescription("Options...");
+        const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : [];
+
+        optionItems.push({ description: "reset view", event: () => { this.props.Document._panX = this.props.Document._panY = 0; this.props.Document.scale = 1; }, icon: "compress-arrows-alt" });
+        optionItems.push({ description: `${this.Document._LODdisable ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document._LODdisable = !this.Document._LODdisable, icon: "table" });
+        optionItems.push({ description: `${this.fitToContent ? "Unset" : "Set"} Fit To Container`, event: () => this.Document._fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" });
+        optionItems.push({ description: `${this.Document.useClusters ? "Uncluster" : "Use Clusters"}`, event: () => this.updateClusters(!this.Document.useClusters), icon: "braille" });
+        optionItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" });
         // layoutItems.push({ description: "Analyze Strokes", event: this.analyzeStrokes, icon: "paint-brush" });
-        layoutItems.push({ description: "Jitter Rotation", event: action(() => this.props.Document.jitterRotation = 10), icon: "paint-brush" });
-        layoutItems.push({
+        optionItems.push({ description: "Jitter Rotation", event: action(() => this.props.Document.jitterRotation = 10), icon: "paint-brush" });
+        optionItems.push({
             description: "Import document", icon: "upload", event: ({ x, y }) => {
                 const input = document.createElement("input");
                 input.type = "file";
@@ -1085,7 +1086,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             }
         });
 
-        ContextMenu.Instance.addItem({ description: "Freeform Options ...", subitems: layoutItems, icon: "eye" });
+        ContextMenu.Instance.addItem({ description: "Options...", subitems: optionItems, icon: "eye" });
     }
 
     private childViews = () => {
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 8f40ea2be..6ff6d1b42 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -162,7 +162,7 @@ export class AudioBox extends ViewBoxBaseComponent<FieldViewProps, AudioDocument
         const funcs: ContextMenuProps[] = [];
         funcs.push({ description: (this.layoutDoc.playOnSelect ? "Don't play" : "Play") + " when document selected", event: () => this.layoutDoc.playOnSelect = !this.layoutDoc.playOnSelect, icon: "expand-arrows-alt" });
 
-        ContextMenu.Instance.addItem({ description: "Audio Funcs...", subitems: funcs, icon: "asterisk" });
+        ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
     }
 
     stopRecording = action(() => {
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 3a7e005ac..615b05e43 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -1,4 +1,3 @@
-import anime from "animejs";
 import { computed, IReactionDisposer, observable, reaction, trace } from "mobx";
 import { observer } from "mobx-react";
 import { Doc, HeightSym, WidthSym } from "../../../new_fields/Doc";
@@ -29,8 +28,14 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
 @observer
 export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps, PositionDocument>(PositionDocument) {
     @observable _animPos: number[] | undefined = undefined;
+    random(min: number, max: number) { // min should not be equal to max
+        const mseed = Math.abs(this.X * this.Y);
+        const seed = (mseed * 9301 + 49297) % 233280;
+        var rnd = seed / 233280;
+        return min + rnd * (max - min);
+    }
     get displayName() { return "CollectionFreeFormDocumentView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive
-    get transform() { return `scale(${this.props.ContentScaling()}) translate(${this.X}px, ${this.Y}px) rotate(${anime.random(-1, 1) * this.props.jitterRotation}deg)`; }
+    get transform() { return `scale(${this.props.ContentScaling()}) translate(${this.X}px, ${this.Y}px) rotate(${this.random(-1, 1) * this.props.jitterRotation}deg)`; }
     get X() { return this.renderScriptDim ? this.renderScriptDim.x : this.props.x !== undefined ? this.props.x : this.dataProvider ? this.dataProvider.x : (this.Document.x || 0); }
     get Y() { return this.renderScriptDim ? this.renderScriptDim.y : this.props.y !== undefined ? this.props.y : this.dataProvider ? this.dataProvider.y : (this.Document.y || 0); }
     get ZInd() { return this.dataProvider ? this.dataProvider.zIndex : (this.Document.zIndex || 0); }
diff --git a/src/client/views/nodes/DocumentBox.tsx b/src/client/views/nodes/DocumentBox.tsx
index ac562f19a..7583aa070 100644
--- a/src/client/views/nodes/DocumentBox.tsx
+++ b/src/client/views/nodes/DocumentBox.tsx
@@ -45,7 +45,7 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent<FieldViewProps, Do
         funcs.push({ description: (this.props.Document.excludeCollections ? "Include" : "Exclude") + " Collections", event: () => Doc.GetProto(this.props.Document).excludeCollections = !this.props.Document.excludeCollections, icon: "expand-arrows-alt" });
         funcs.push({ description: `${this.props.Document.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.props.Document.forceActive = !this.props.Document.forceActive, icon: "project-diagram" });
 
-        ContextMenu.Instance.addItem({ description: "DocumentBox Funcs...", subitems: funcs, icon: "asterisk" });
+        ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
     }
     @computed get contentDoc() {
         return (this.props.Document.isTemplateDoc || this.props.Document.isTemplateForField ? this.props.Document : Doc.GetProto(this.props.Document));
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index a19ba6506..cab7b965a 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -741,7 +741,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         const existingOnClick = cm.findByDescription("OnClick...");
         const onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
         onClicks.push({ description: "Enter Portal", event: this.makeIntoPortal, icon: "window-restore" });
-        onClicks.push({ description: "Toggle Detail", event: () => this.Document.onClick = ScriptField.MakeScript(`toggleDetail(this, "${this.props.Document.layoutKey}")`), icon: "window-restore" });
+        onClicks.push({ description: "Toggle Detail", event: () => this.Document.onClick = ScriptField.MakeScript(`toggleDetail(self, "${this.props.Document.layoutKey}")`), icon: "window-restore" });
         onClicks.push({ description: this.Document.ignoreClick ? "Select" : "Do Nothing", event: () => this.Document.ignoreClick = !this.Document.ignoreClick, icon: this.Document.ignoreClick ? "unlock" : "lock" });
         onClicks.push({ description: this.Document.isLinkButton ? "Remove Follow Behavior" : "Follow Link in Place", event: this.toggleFollowInPlace, icon: "concierge-bell" });
         onClicks.push({ description: this.Document.isLinkButton || this.Document.onClick ? "Remove Click Behavior" : "Follow Link", event: this.toggleLinkButtonBehavior, icon: "concierge-bell" });
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 2c89d53d8..1329f7dcf 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -13,7 +13,7 @@ import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from "
 import { ReplaceStep } from 'prosemirror-transform';
 import { EditorView } from "prosemirror-view";
 import { DateField } from '../../../new_fields/DateField';
-import { DataSym, Doc, DocListCastAsync, Field, HeightSym, Opt, WidthSym } from "../../../new_fields/Doc";
+import { DataSym, Doc, DocListCastAsync, Field, HeightSym, Opt, WidthSym, DocListCast } from "../../../new_fields/Doc";
 import { documentSchema } from '../../../new_fields/documentSchemas';
 import { Id } from '../../../new_fields/FieldSymbols';
 import { InkTool } from '../../../new_fields/InkField';
@@ -48,6 +48,7 @@ import { FormattedTextBoxComment, formattedTextBoxCommentPlugin } from './Format
 import React = require("react");
 import { PrefetchProxy } from '../../../new_fields/Proxy';
 import { makeTemplate } from '../../util/DropConverter';
+import { DocumentView } from './DocumentView';
 
 library.add(faEdit);
 library.add(faSmile, faTextHeight, faUpload);
@@ -396,6 +397,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         return Cast(Doc.UserDoc().defaultTextLayout, Doc, null) || StrCast(Doc.UserDoc().defaultTextLayout, null);
     }
     specificContextMenu = (e: React.MouseEvent): void => {
+        const cm = ContextMenu.Instance;
+
         const funcs: ContextMenuProps[] = [];
         this.props.Document.isTemplateDoc && funcs.push({ description: "Make Default Layout", event: async () => Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.props.Document), icon: "eye" });
         funcs.push({ description: "Reset Default Layout", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" });
@@ -407,10 +410,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         });
         funcs.push({ description: "Toggle Single Line", event: () => this.props.Document._singleLine = !this.props.Document._singleLine, icon: "expand-arrows-alt" });
         funcs.push({ description: "Toggle Sidebar", event: () => this.props.Document._showSidebar = !this.props.Document._showSidebar, icon: "expand-arrows-alt" });
-        funcs.push({ description: "Toggle Audio", event: () => this.props.Document._showAudio = !this.props.Document._showAudio, icon: "expand-arrows-alt" });
+        funcs.push({ description: "Toggle Dictation Icon", event: () => this.props.Document._showAudio = !this.props.Document._showAudio, icon: "expand-arrows-alt" });
         funcs.push({ description: "Toggle Menubar", event: () => this.toggleMenubar(), icon: "expand-arrows-alt" });
+
+        const highlighting: ContextMenuProps[] = [];
         ["My Text", "Text from Others", "Todo Items", "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"].forEach(option =>
-            funcs.push({
+            highlighting.push({
                 description: (FormattedTextBox._highlights.indexOf(option) === -1 ? "Highlight " : "Unhighlight ") + option, event: () => {
                     e.stopPropagation();
                     if (FormattedTextBox._highlights.indexOf(option) === -1) {
@@ -421,8 +426,38 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                     this.updateHighlights();
                 }, icon: "expand-arrows-alt"
             }));
+        funcs.push({ description: "highlighting...", subitems: highlighting, icon: "hand-point-right" });
 
         ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
+
+        const change = cm.findByDescription("Change Perspective...");
+        const changeItems: ContextMenuProps[] = change && "subitems" in change ? change.subitems : [];
+
+        const noteTypesDoc = Cast(Doc.UserDoc().noteTypes, Doc, null);
+        const noteTypes = DocListCast(noteTypesDoc?.data);
+        noteTypes.forEach(note => {
+            changeItems.push({
+                description: StrCast(note.title), event: () => {
+                    Doc.setNativeView(this.props.Document);
+                    DocumentView.makeCustomViewClicked(this.rootDoc, Docs.Create.TreeDocument, StrCast(note.title), note);
+                }, icon: "eye"
+            })
+        });
+        changeItems.push({ description: "FreeForm", event: () => DocumentView.makeCustomViewClicked(this.rootDoc, Docs.Create.FreeformDocument, "freeform"), icon: "eye" })
+        !change && cm.addItem({ description: "Change Perspective...", subitems: changeItems, icon: "external-link-alt" });
+
+        const open = cm.findByDescription("Open New Perspective...");
+        const openItems: ContextMenuProps[] = open && "subitems" in open ? open.subitems : [];
+
+        openItems.push({
+            description: "FreeForm", event: () => {
+                const alias = Doc.MakeAlias(this.rootDoc);
+                DocumentView.makeCustomViewClicked(alias, Docs.Create.FreeformDocument, "freeform");
+                this.props.addDocTab(alias, "onRight");
+            }, icon: "eye"
+        })
+        !open && cm.addItem({ description: "Open New Perspective...", subitems: openItems, icon: "external-link-alt" });
+
     }
 
     recordDictation = () => {
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 7aad6f90e..2970674a2 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -16,6 +16,8 @@ import { FieldView, FieldViewProps } from './FieldView';
 import "./KeyValueBox.scss";
 import { KeyValuePair } from "./KeyValuePair";
 import React = require("react");
+import { ContextMenu } from "../ContextMenu";
+import { ContextMenuProps } from "../ContextMenuItem";
 
 export type KVPScript = {
     script: CompiledScript;
@@ -34,11 +36,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
     @observable private _keyInput: string = "";
     @observable private _valueInput: string = "";
     @computed get splitPercentage() { return NumCast(this.props.Document.schemaSplitPercentage, 50); }
-    get fieldDocToLayout() { return this.props.fieldKey ? FieldValue(Cast(this.props.Document[this.props.fieldKey], Doc)) : this.props.Document; }
-
-    constructor(props: FieldViewProps) {
-        super(props);
-    }
+    get fieldDocToLayout() { return this.props.fieldKey ? Cast(this.props.Document[this.props.fieldKey], Doc, null) : this.props.Document; }
 
     @action
     onEnterKey = (e: React.KeyboardEvent): void => {
@@ -234,13 +232,26 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
         return new Doc;
     }
 
+    specificContextMenu = (e: React.MouseEvent): void => {
+        const cm = ContextMenu.Instance;
+        const open = cm.findByDescription("Change Perspective...");
+        const openItems: ContextMenuProps[] = open && "subitems" in open ? open.subitems : [];
+        openItems.push({
+            description: "Default Perspective", event: () => {
+                this.props.addDocTab(this.fieldDocToLayout, "inTab");
+                this.props.addDocTab(this.props.Document, "close");
+            }, icon: "image"
+        });
+        !open && cm.addItem({ description: "Change Perspective...", subitems: openItems, icon: "external-link-alt" });
+    }
+
     render() {
         const dividerDragger = this.splitPercentage === 0 ? (null) :
             <div className="keyValueBox-dividerDragger" style={{ transform: `translate(calc(${100 - this.splitPercentage}% - 5px), 0px)` }}>
                 <div className="keyValueBox-dividerDraggerThumb" onPointerDown={this.onDividerDown} />
             </div>;
 
-        return (<div className="keyValueBox-cont" onWheel={this.onPointerWheel} ref={this._mainCont}>
+        return (<div className="keyValueBox-cont" onWheel={this.onPointerWheel} onContextMenu={this.specificContextMenu} ref={this._mainCont}>
             <table className="keyValueBox-table">
                 <tbody className="keyValueBox-tbody">
                     <tr className="keyValueBox-header">
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index 8e3c15908..eb647d0e4 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -109,7 +109,7 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps, LinkAnch
         funcs.push({ description: "Open Link Editor", event: () => this.openLinkEditor(e), icon: "eye" });
         funcs.push({ description: "Toggle Always Show Link", event: () => this.props.Document.linkDisplay = !this.props.Document.linkDisplay, icon: "eye" });
 
-        ContextMenu.Instance.addItem({ description: "Link Funcs...", subitems: funcs, icon: "asterisk" });
+        ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
     }
 
     render() {
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 6db36e43c..3712c648e 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -210,7 +210,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum
         pdfUrl && funcs.push({ description: "Copy path", event: () => Utils.CopyText(pdfUrl.url.pathname), icon: "expand-arrows-alt" });
         funcs.push({ description: "Toggle Fit Width " + (this.Document._fitWidth ? "Off" : "On"), event: () => this.Document._fitWidth = !this.Document._fitWidth, icon: "expand-arrows-alt" });
 
-        ContextMenu.Instance.addItem({ description: "Pdf Funcs...", subitems: funcs, icon: "asterisk" });
+        ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
     }
 
     @computed get contentScaling() { return this.props.ContentScaling(); }
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index 11b24b059..125690dc7 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -120,7 +120,7 @@ export class ScreenshotBox extends ViewBoxBaseComponent<FieldViewProps, Screensh
                     this._videoRef!.srcObject = !this._screenCapture ? undefined : await (navigator.mediaDevices as any).getDisplayMedia({ video: true });
                 }), icon: "expand-arrows-alt"
             });
-            ContextMenu.Instance.addItem({ description: "Screenshot Funcs...", subitems: subitems, icon: "video" });
+            ContextMenu.Instance.addItem({ description: "Options...", subitems: subitems, icon: "video" });
         }
     }
 
diff --git a/src/client/views/nodes/SliderBox.tsx b/src/client/views/nodes/SliderBox.tsx
index 746ea0b64..b2d451ea8 100644
--- a/src/client/views/nodes/SliderBox.tsx
+++ b/src/client/views/nodes/SliderBox.tsx
@@ -40,7 +40,7 @@ export class SliderBox extends ViewBoxBaseComponent<FieldViewProps, SliderDocume
     specificContextMenu = (e: React.MouseEvent): void => {
         const funcs: ContextMenuProps[] = [];
         funcs.push({ description: "Edit Thumb Change Script", icon: "edit", event: (obj: any) => ScriptBox.EditButtonScript("On Thumb Change ...", this.props.Document, "onThumbChange", obj.x, obj.y) });
-        ContextMenu.Instance.addItem({ description: "Slider Funcs...", subitems: funcs, icon: "asterisk" });
+        ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
     }
     onChange = (values: readonly number[]) => runInAction(() => {
         this.dataDoc[this.minThumbKey] = values[0];
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 588068334..613929bca 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -209,7 +209,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
                     this._videoRef!.srcObject = !this._screenCapture ? undefined : await (navigator.mediaDevices as any).getDisplayMedia({ video: true });
                 }), icon: "expand-arrows-alt"
             });
-            ContextMenu.Instance.addItem({ description: "Video Funcs...", subitems: subitems, icon: "video" });
+            ContextMenu.Instance.addItem({ description: "Options...", subitems: subitems, icon: "video" });
         }
     }
 
-- 
cgit v1.2.3-70-g09d2


From 5bd4d4804f9feea0fd16d8aabf040ca14cf205d1 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 23 Apr 2020 02:09:10 -0400
Subject: fixed performance of panning with something selected by making
 TemplateMenu ot render unless it has explicitly been activated.

---
 src/client/views/DocumentButtonBar.tsx             | 62 ++++++----------------
 src/client/views/DocumentDecorations.tsx           |  2 +-
 src/client/views/TemplateMenu.tsx                  |  9 ++--
 src/client/views/Templates.tsx                     | 45 ++--------------
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  2 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  2 +-
 src/client/views/nodes/DocumentView.tsx            |  8 +--
 src/client/views/nodes/FormattedTextBox.tsx        |  6 +--
 src/client/views/webcam/DashWebRTCVideo.tsx        |  2 +-
 9 files changed, 37 insertions(+), 101 deletions(-)

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

diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 93987b751..5936bbfb8 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -6,7 +6,7 @@ import { observer } from "mobx-react";
 import { Doc, DocListCast } from "../../new_fields/Doc";
 import { RichTextField } from '../../new_fields/RichTextField';
 import { NumCast, StrCast } from "../../new_fields/Types";
-import { emptyFunction } from "../../Utils";
+import { emptyFunction, setupMoveUpEvents } from "../../Utils";
 import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils';
 import { UndoManager } from "../util/UndoManager";
 import { CollectionDockingView, DockedFrameRenderer } from './collections/CollectionDockingView';
@@ -108,10 +108,8 @@ export class DocumentButtonBar extends React.Component<{ views: (DocumentView |
     get view0() { return this.props.views?.[0]; }
 
     @action
-    onLinkButtonMoved = (e: PointerEvent): void => {
-        if (this._linkButton.current !== null && (Math.abs(e.clientX - this._downX) > 3 || Math.abs(e.clientY - this._downY) > 3)) {
-            document.removeEventListener("pointermove", this.onLinkButtonMoved);
-            document.removeEventListener("pointerup", this.onLinkButtonUp);
+    onLinkButtonMoved = (e: PointerEvent) => {
+        if (this._linkButton.current !== null) {
             const linkDrag = UndoManager.StartBatch("Drag Link");
             this.view0 && DragManager.StartLinkDrag(this._linkButton.current, this.view0.props.Document, e.pageX, e.pageY, {
                 dragComplete: dropEv => {
@@ -131,26 +129,16 @@ export class DocumentButtonBar extends React.Component<{ views: (DocumentView |
                 },
                 hideSource: false
             });
+            return true;
         }
-        e.stopPropagation();
+        return false;
     }
 
 
     onLinkButtonDown = (e: React.PointerEvent): void => {
-        this._downX = e.clientX;
-        this._downY = e.clientY;
-        document.removeEventListener("pointermove", this.onLinkButtonMoved);
-        document.addEventListener("pointermove", this.onLinkButtonMoved);
-        document.removeEventListener("pointerup", this.onLinkButtonUp);
-        document.addEventListener("pointerup", this.onLinkButtonUp);
-        e.stopPropagation();
+        setupMoveUpEvents(this, e, this.onLinkButtonMoved, emptyFunction, emptyFunction);
     }
 
-    onLinkButtonUp = (e: PointerEvent): void => {
-        document.removeEventListener("pointermove", this.onLinkButtonMoved);
-        document.removeEventListener("pointerup", this.onLinkButtonUp);
-        e.stopPropagation();
-    }
 
     @computed
     get considerGoogleDocsPush() {
@@ -257,29 +245,12 @@ export class DocumentButtonBar extends React.Component<{ views: (DocumentView |
         }} />;
     }
 
-    private _downx = 0;
-    private _downy = 0;
-    onAliasButtonUp = (e: PointerEvent): void => {
-        document.removeEventListener("pointermove", this.onAliasButtonMoved);
-        document.removeEventListener("pointerup", this.onAliasButtonUp);
-        e.stopPropagation();
-    }
-
+    @observable _aliasDown = false;
     onAliasButtonDown = (e: React.PointerEvent): void => {
-        this._downx = e.clientX;
-        this._downy = e.clientY;
-        e.stopPropagation();
-        e.preventDefault();
-        document.removeEventListener("pointermove", this.onAliasButtonMoved);
-        document.addEventListener("pointermove", this.onAliasButtonMoved);
-        document.removeEventListener("pointerup", this.onAliasButtonUp);
-        document.addEventListener("pointerup", this.onAliasButtonUp);
-    }
-    onAliasButtonMoved = (e: PointerEvent): void => {
-        if (this._dragRef.current !== null && (Math.abs(e.clientX - this._downx) > 4 || Math.abs(e.clientY - this._downy) > 4)) {
-            document.removeEventListener("pointermove", this.onAliasButtonMoved);
-            document.removeEventListener("pointerup", this.onAliasButtonUp);
-
+        setupMoveUpEvents(this, e, this.onAliasButtonMoved, emptyFunction, emptyFunction);
+    };
+    onAliasButtonMoved = () => {
+        if (this._dragRef.current) {
             const dragDocView = this.props.views[0]!;
             const dragData = new DragManager.DocumentDragData([dragDocView.props.Document]);
             const [left, top] = dragDocView.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
@@ -290,9 +261,10 @@ export class DocumentButtonBar extends React.Component<{ views: (DocumentView |
                 offsetY: dragData.offset[1],
                 hideSource: false
             });
+            return true;
         }
-        e.stopPropagation();
-    }
+        return false;
+    };
 
     @computed
     get templateButton() {
@@ -301,9 +273,9 @@ export class DocumentButtonBar extends React.Component<{ views: (DocumentView |
         Array.from(Object.values(Templates.TemplateList)).map(template =>
             templates.set(template, this.props.views.reduce((checked, doc) => checked || doc?.props.Document["_show" + template.Name] ? true : false, false as boolean)));
         return !view0 ? (null) : <div title="Tap: Customize layout.  Drag: Create alias" className="documentButtonBar-linkFlyout" ref={this._dragRef}>
-            <Flyout anchorPoint={anchorPoints.LEFT_TOP}
-                content={<TemplateMenu docViews={this.props.views.filter(v => v).map(v => v as DocumentView)} templates={templates} />}>
-                <div className={"documentButtonBar-linkButton-" + "empty"} ref={this._dragRef} onPointerDown={this.onAliasButtonDown} >
+            <Flyout anchorPoint={anchorPoints.LEFT_TOP} onOpen={action(() => this._aliasDown = true)} onClose={action(() => this._aliasDown = false)}
+                content={!this._aliasDown ? (null) : <TemplateMenu docViews={this.props.views.filter(v => v).map(v => v as DocumentView)} templates={templates} />}>
+                <div className={"documentButtonBar-linkButton-empty"} ref={this._dragRef} onPointerDown={this.onAliasButtonDown} >
                     {<FontAwesomeIcon className="documentdecorations-icon" icon="edit" size="sm" />}
                 </div>
             </Flyout>
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 6e699bc68..e1348a317 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -191,7 +191,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         if (e.button === 0) {
             const selectedDocs = SelectionManager.SelectedDocuments();
             if (selectedDocs.length) {
-                CollectionDockingView.Instance?.OpenFullScreen(selectedDocs[0], selectedDocs[0].props.LibraryPath)
+                CollectionDockingView.Instance?.OpenFullScreen(selectedDocs[0], selectedDocs[0].props.LibraryPath);
             }
         }
         SelectionManager.DeselectAll();
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index b76137f06..6894500dd 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -124,7 +124,7 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
         templateMenu.push(<OtherToggle key={"chrome"} name={"Chrome"} checked={layout._chromeStatus !== "disabled"} toggle={this.toggleChrome} />);
         templateMenu.push(<OtherToggle key={"default"} name={"Default"} checked={templateName === "layout"} toggle={this.toggleDefault} />);
         if (noteTypesDoc) {
-            addedTypes.concat(noteTypes).map(template => template.treeViewChecked = ComputedField.MakeFunction(`templateIsUsed(self)`));
+            addedTypes.concat(noteTypes).map(template => template.treeViewChecked = ComputedField.MakeFunction(`templateIsUsed(self,firstDoc)`, {}, { firstDoc }));
             this._addedKeys && Array.from(this._addedKeys).filter(key => !noteTypes.some(nt => nt.title === key)).forEach(template => templateMenu.push(
                 <OtherToggle key={template} name={template} checked={templateName === template} toggle={e => this.toggleLayout(e, template)} />));
         }
@@ -175,11 +175,10 @@ Scripting.addGlobal(function switchView(doc: Doc, template: Doc | undefined) {
     return templateTitle && DocumentView.makeCustomViewClicked(doc, Docs.Create.FreeformDocument, templateTitle, template);
 });
 
-Scripting.addGlobal(function templateIsUsed(templateDoc: Doc) {
-    const firstDoc = SelectionManager.SelectedDocuments().length ? SelectionManager.SelectedDocuments()[0].props.Document : undefined;
-    if (firstDoc) {
+Scripting.addGlobal(function templateIsUsed(templateDoc: Doc, selDoc: Doc) {
+    if (selDoc) {
         const template = StrCast(templateDoc.dragFactory ? Cast(templateDoc.dragFactory, Doc, null)?.title : templateDoc.title);
-        return StrCast(firstDoc.layoutKey) === "layout_" + template ? 'check' : 'unchecked';
+        return StrCast(selDoc.layoutKey) === "layout_" + template ? 'check' : 'unchecked';
     }
     return false;
 });
\ No newline at end of file
diff --git a/src/client/views/Templates.tsx b/src/client/views/Templates.tsx
index 8c60f1c36..a6dbaa650 100644
--- a/src/client/views/Templates.tsx
+++ b/src/client/views/Templates.tsx
@@ -1,45 +1,23 @@
-import React = require("react");
-
-export enum TemplatePosition {
-    InnerTop,
-    InnerBottom,
-    InnerRight,
-    InnerLeft,
-    TopRight,
-    OutterTop,
-    OutterBottom,
-    OutterRight,
-    OutterLeft,
-}
-
 export class Template {
-    constructor(name: string, position: TemplatePosition, layout: string) {
+    constructor(name: string, 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 Caption = new Template("Caption", TemplatePosition.OutterBottom,
+    export const Caption = new Template("Caption",
         `<div>
             <div style="height:100%; width:100%;">{layout}</div>
             <div style="bottom: 0; font-size:14px; width:100%; position:absolute">
@@ -47,16 +25,7 @@ export namespace Templates {
             </div>
         </div>` );
 
-    export const Title = new Template("Title", TemplatePosition.InnerTop,
-        `<div>
-            <div style="height:25px; width:100%; background-color: rgba(0, 0, 0, .4); color: white; z-index: 100">
-                <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 style="height:calc(100% - 25px);">
-                <div style="width:100%;overflow:auto">{layout}</div>
-            </div>
-        </div>` );
-    export const TitleHover = new Template("TitleHover", TemplatePosition.InnerTop,
+    export const Title = new Template("Title",
         `<div>
             <div style="height:25px; width:100%; background-color: rgba(0, 0, 0, .4); color: white; z-index: 100">
                 <span style="text-align:center;width:100%;font-size:20px;position:absolute;overflow:hidden;white-space:nowrap;text-overflow:ellipsis">{props.Document.title}</span>
@@ -65,14 +34,8 @@ export namespace Templates {
                 <div style="width:100%;overflow:auto">{layout}</div>
             </div>
         </div>` );
+    export const TitleHover = new Template("TitleHover", Title.Layout);
 
     export const TemplateList: Template[] = [Title, TitleHover, Caption];
-
-    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/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 55e55e56c..8ead1c300 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1057,7 +1057,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         optionItems.push({ description: `${this.Document.useClusters ? "Uncluster" : "Use Clusters"}`, event: () => this.updateClusters(!this.Document.useClusters), icon: "braille" });
         optionItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" });
         // layoutItems.push({ description: "Analyze Strokes", event: this.analyzeStrokes, icon: "paint-brush" });
-        optionItems.push({ description: "Jitter Rotation", event: action(() => this.props.Document.jitterRotation = 10), icon: "paint-brush" });
+        optionItems.push({ description: "Jitter Rotation", event: action(() => this.props.Document.jitterRotation = (this.props.Document.jitterRotation ? 0 : 10)), icon: "paint-brush" });
         optionItems.push({
             description: "Import document", icon: "upload", event: ({ x, y }) => {
                 const input = document.createElement("input");
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 615b05e43..4b282b0c9 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -31,7 +31,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     random(min: number, max: number) { // min should not be equal to max
         const mseed = Math.abs(this.X * this.Y);
         const seed = (mseed * 9301 + 49297) % 233280;
-        var rnd = seed / 233280;
+        const rnd = seed / 233280;
         return min + rnd * (max - min);
     }
     get displayName() { return "CollectionFreeFormDocumentView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index cab7b965a..f05366a15 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -315,10 +315,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 if ((this.props.Document.onDragStart || (this.props.Document.rootDocument)) && !(e.ctrlKey || e.button > 0)) {  // onDragStart implies a button doc that we don't want to select when clicking.   RootDocument & isTEmplaetForField implies we're clicking on part of a template instance and we want to select the whole template, not the part
                     stopPropagate = false; // don't stop propagation for field templates -- want the selection to propagate up to the root document of the template
                 } else {
-                    DocumentView._focusHack = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY) || [0, 0];
-                    DocumentView._focusHack = [DocumentView._focusHack[0] + NumCast(this.props.Document.x), DocumentView._focusHack[1] + NumCast(this.props.Document.y)];
+                    if (this.props.Document.type === DocumentType.RTF) {
+                        DocumentView._focusHack = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY) || [0, 0];
+                        DocumentView._focusHack = [DocumentView._focusHack[0] + NumCast(this.props.Document.x), DocumentView._focusHack[1] + NumCast(this.props.Document.y)];
 
-                    this.props.focus(this.props.Document, false);
+                        this.props.focus(this.props.Document, false);
+                    }
                     SelectionManager.SelectDoc(this, e.ctrlKey);
                 }
                 preventDefault = false;
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 1329f7dcf..fd7462a10 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -441,9 +441,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                     Doc.setNativeView(this.props.Document);
                     DocumentView.makeCustomViewClicked(this.rootDoc, Docs.Create.TreeDocument, StrCast(note.title), note);
                 }, icon: "eye"
-            })
+            });
         });
-        changeItems.push({ description: "FreeForm", event: () => DocumentView.makeCustomViewClicked(this.rootDoc, Docs.Create.FreeformDocument, "freeform"), icon: "eye" })
+        changeItems.push({ description: "FreeForm", event: () => DocumentView.makeCustomViewClicked(this.rootDoc, Docs.Create.FreeformDocument, "freeform"), icon: "eye" });
         !change && cm.addItem({ description: "Change Perspective...", subitems: changeItems, icon: "external-link-alt" });
 
         const open = cm.findByDescription("Open New Perspective...");
@@ -455,7 +455,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                 DocumentView.makeCustomViewClicked(alias, Docs.Create.FreeformDocument, "freeform");
                 this.props.addDocTab(alias, "onRight");
             }, icon: "eye"
-        })
+        });
         !open && cm.addItem({ description: "Open New Perspective...", subitems: openItems, icon: "external-link-alt" });
 
     }
diff --git a/src/client/views/webcam/DashWebRTCVideo.tsx b/src/client/views/webcam/DashWebRTCVideo.tsx
index 1d52ba38f..2ea011316 100644
--- a/src/client/views/webcam/DashWebRTCVideo.tsx
+++ b/src/client/views/webcam/DashWebRTCVideo.tsx
@@ -3,7 +3,7 @@ import React = require("react");
 import { CollectionFreeFormDocumentViewProps } from "../nodes/CollectionFreeFormDocumentView";
 import { FieldViewProps, FieldView } from "../nodes/FieldView";
 import { observable, action } from "mobx";
-import { DocumentDecorations, CloseCall } from "../DocumentDecorations";
+import { DocumentDecorations } from "../DocumentDecorations";
 import { InkingControl } from "../InkingControl";
 import "../../views/nodes/WebBox.scss";
 import "./DashWebRTCVideo.scss";
-- 
cgit v1.2.3-70-g09d2


From 8a8f2c8848fe76e26188666790accaf236267e78 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 23 Apr 2020 13:11:51 -0400
Subject: fixed color box interactions.  added starburst layout engine.

---
 src/client/views/DocComponent.tsx                  |  6 ++--
 src/client/views/DocumentDecorations.tsx           |  4 ++-
 .../CollectionFreeFormLayoutEngines.tsx            | 28 ++++++++++++++-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 42 +++++++++++++++-------
 src/client/views/nodes/ColorBox.tsx                |  2 +-
 5 files changed, 64 insertions(+), 18 deletions(-)

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

diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index f19f9308a..0a8f0c9a7 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -1,7 +1,7 @@
 import { Doc, Opt, DataSym } from '../../new_fields/Doc';
 import { Touchable } from './Touchable';
 import { computed, action, observable } from 'mobx';
-import { Cast } from '../../new_fields/Types';
+import { Cast, BoolCast } from '../../new_fields/Types';
 import { listSpec } from '../../new_fields/Schema';
 import { InkingControl } from './InkingControl';
 import { InkTool } from '../../new_fields/InkField';
@@ -53,7 +53,7 @@ export function ViewBoxBaseComponent<P extends ViewBoxBaseProps, T>(schemaCtor:
         // key where data is stored
         @computed get fieldKey() { return this.props.fieldKey; }
 
-        active = (outsideReaction?: boolean) => !this.props.Document.isBackground && (this.props.rootSelected(outsideReaction) || this.props.isSelected(outsideReaction) || this.props.renderDepth === 0);//  && !InkingControl.Instance.selectedTool;  // bcz: inking state shouldn't affect static tools 
+        active = (outsideReaction?: boolean) => !this.props.Document.isBackground && (this.props.rootSelected(outsideReaction) || this.props.isSelected(outsideReaction) || this.props.renderDepth === 0 || this.layoutDoc.forceActive);//  && !InkingControl.Instance.selectedTool;  // bcz: inking state shouldn't affect static tools 
         protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
     }
     return Component;
@@ -114,7 +114,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
 
         whenActiveChanged = action((isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive));
         active = (outsideReaction?: boolean) => ((InkingControl.Instance.selectedTool === InkTool.None && !this.props.Document.isBackground) &&
-            (this.props.rootSelected(outsideReaction) || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false)
+            (this.props.rootSelected(outsideReaction) || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0 || BoolCast((this.layoutDoc as any).forceActive)) ? true : false)
         annotationsActive = (outsideReaction?: boolean) => (InkingControl.Instance.selectedTool !== InkTool.None || (this.props.Document.isBackground && this.props.active()) ||
             (this.props.Document.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false)
     }
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index e1348a317..a4ebde3b3 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -136,6 +136,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
             let child = SelectionManager.SelectedDocuments()[0].ContentDiv!.children[0];
             while (child.children.length) {
                 const next = Array.from(child.children).find(c => !c.className.includes("collectionViewChrome"));
+                if (next?.className.includes("documentView-node")) break;
                 if (next) child = next;
                 else break;
             }
@@ -191,7 +192,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         if (e.button === 0) {
             const selectedDocs = SelectionManager.SelectedDocuments();
             if (selectedDocs.length) {
-                CollectionDockingView.Instance?.OpenFullScreen(selectedDocs[0], selectedDocs[0].props.LibraryPath);
+                //CollectionDockingView.Instance?.OpenFullScreen(selectedDocs[0], selectedDocs[0].props.LibraryPath);
+                CollectionDockingView.AddRightSplit(selectedDocs[0].props.Document, selectedDocs[0].props.LibraryPath);
             }
         }
         SelectionManager.DeselectAll();
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index bd4db89ec..0a5ea3baf 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -1,4 +1,4 @@
-import { Doc, Field, FieldResult } from "../../../../new_fields/Doc";
+import { Doc, Field, FieldResult, WidthSym, HeightSym } from "../../../../new_fields/Doc";
 import { NumCast, StrCast, Cast } from "../../../../new_fields/Types";
 import { ScriptBox } from "../../ScriptBox";
 import { CompileScript } from "../../../util/Scripting";
@@ -76,6 +76,32 @@ interface PivotColumn {
 }
 
 
+export function computerStarburstLayout(
+    poolData: Map<string, PoolData>,
+    pivotDoc: Doc,
+    childDocs: Doc[],
+    filterDocs: Doc[],
+    childPairs: { layout: Doc, data?: Doc }[],
+    panelDim: number[],
+    viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[]
+) {
+    const docMap = new Map<Doc, ViewDefBounds>();
+    const burstDim = [NumCast(pivotDoc.starburstRadius, panelDim[0]), NumCast(pivotDoc.starburstRadius, panelDim[1])]
+    childDocs.forEach((doc, i) => {
+        const deg = i / childDocs.length * Math.PI * 2;
+        docMap.set(doc, {
+            type: "doc",
+            x: Math.sin(deg) * burstDim[0] / 3 - NumCast(pivotDoc.starburstX),
+            y: Math.cos(deg) * burstDim[1] / 3 - NumCast(pivotDoc.starburstY),
+            width: doc[WidthSym](),
+            height: doc[HeightSym](),
+            payload: undefined
+        });
+    });
+    return normalizeResults(burstDim, 12, childPairs, docMap, poolData, viewDefsToJSX, [], 0, [], childDocs.filter(c => !filterDocs.includes(c)));
+}
+
+
 export function computePivotLayout(
     poolData: Map<string, PoolData>,
     pivotDoc: Doc,
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 8ead1c300..5967f36f9 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -37,7 +37,7 @@ import { pageSchema } from "../../nodes/ImageBox";
 import PDFMenu from "../../pdf/PDFMenu";
 import { CollectionDockingView } from "../CollectionDockingView";
 import { CollectionSubView } from "../CollectionSubView";
-import { computePivotLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult } from "./CollectionFreeFormLayoutEngines";
+import { computePivotLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult, computerStarburstLayout } from "./CollectionFreeFormLayoutEngines";
 import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors";
 import "./CollectionFreeFormView.scss";
 import MarqueeOptionsMenu from "./MarqueeOptionsMenu";
@@ -941,13 +941,16 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         return this._layoutPoolData.get(doc[Id]);
     }.bind(this));
 
-    doTimelineLayout(poolData: Map<string, PoolData>) {
-        return computeTimelineLayout(poolData, this.props.Document, this.childDocs, this.childDocs,
-            this.childLayoutPairs, [this.props.PanelWidth(), this.props.PanelHeight()], this.viewDefsToJSX);
-    }
-
-    doPivotLayout(poolData: Map<string, PoolData>) {
-        return computePivotLayout(poolData, this.props.Document, this.childDocs, this.childDocs,
+    doEngineLayout(poolData: Map<string, PoolData>,
+        engine: (
+            poolData: Map<string, PoolData>,
+            pivotDoc: Doc,
+            childDocs: Doc[],
+            filterDocs: Doc[],
+            childPairs: { layout: Doc, data?: Doc }[],
+            panelDim: number[],
+            viewDefsToJSX: ((views: ViewDefBounds[]) => ViewDefResult[])) => ViewDefResult[]) {
+        return engine(poolData, this.props.Document, this.childDocs, this.childDocs,
             this.childLayoutPairs, [this.props.PanelWidth(), this.props.PanelHeight()], this.viewDefsToJSX);
     }
 
@@ -966,9 +969,12 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
 
     @computed get doInternalLayoutComputation() {
         const newPool = new Map<string, any>();
-        switch (this.props.layoutEngine?.()) {
-            case "timeline": return { newPool, computedElementData: this.doTimelineLayout(newPool) };
-            case "pivot": return { newPool, computedElementData: this.doPivotLayout(newPool) };
+        const engine = StrCast(this.layoutDoc._layoutEngine) || this.props.layoutEngine?.();
+        switch (engine) {
+            case "pass": break;
+            case "timeline": return { newPool, computedElementData: this.doEngineLayout(newPool, computeTimelineLayout) };
+            case "pivot": return { newPool, computedElementData: this.doEngineLayout(newPool, computePivotLayout) };
+            case "starburst": return { newPool, computedElementData: this.doEngineLayout(newPool, computerStarburstLayout) };
         }
         return { newPool, computedElementData: this.doFreeformLayout(newPool) };
     }
@@ -994,7 +1000,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                     {...this.getChildDocumentViewProps(pair.layout, pair.data)}
                     dataProvider={this.childDataProvider}
                     LayoutDoc={this.childLayoutDocFunc}
-                    pointerEvents={this.props.layoutEngine?.() !== undefined ? false : undefined}
+                    pointerEvents={this.props.layoutEngine?.() || this.props.Document._layoutEngine ? false : undefined}
                     jitterRotation={NumCast(this.props.Document.jitterRotation)}
                     fitToBox={this.props.fitToBox || BoolCast(this.props.freezeChildDimensions)}
                     FreezeDimensions={BoolCast(this.props.freezeChildDimensions)}
@@ -1022,6 +1028,17 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY));
     }
 
+    layoutStarburst = action(() => {
+        if (this.layoutDoc._layoutEngine === undefined) {
+            this.layoutDoc._layoutEngine = "starburst";
+            this.layoutDoc.overflow = "visible";
+        } else {
+
+            this.layoutDoc._layoutEngine = "pass";
+            this.layoutDoc.overflow = "hidden";
+        }
+
+    });
     layoutDocsInGrid = () => {
         UndoManager.RunInBatch(() => {
             const docs = this.childLayoutPairs;
@@ -1055,6 +1072,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         optionItems.push({ description: `${this.Document._LODdisable ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document._LODdisable = !this.Document._LODdisable, icon: "table" });
         optionItems.push({ description: `${this.fitToContent ? "Unset" : "Set"} Fit To Container`, event: () => this.Document._fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" });
         optionItems.push({ description: `${this.Document.useClusters ? "Uncluster" : "Use Clusters"}`, event: () => this.updateClusters(!this.Document.useClusters), icon: "braille" });
+        optionItems.push({ description: "Arrange Starburst", event: this.layoutStarburst, icon: "table" });
         optionItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" });
         // layoutItems.push({ description: "Analyze Strokes", event: this.analyzeStrokes, icon: "paint-brush" });
         optionItems.push({ description: "Jitter Rotation", event: action(() => this.props.Document.jitterRotation = (this.props.Document.jitterRotation ? 0 : 10)), icon: "paint-brush" });
diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx
index 877dfec2d..6e4341b27 100644
--- a/src/client/views/nodes/ColorBox.tsx
+++ b/src/client/views/nodes/ColorBox.tsx
@@ -22,7 +22,7 @@ export class ColorBox extends ViewBoxBaseComponent<FieldViewProps, ColorDocument
         const selDoc = SelectionManager.SelectedDocuments()?.[0]?.rootDoc;
         return <div className={`colorBox-container${this.active() ? "-interactive" : ""}`}
             onPointerDown={e => e.button === 0 && !e.ctrlKey && e.stopPropagation()}
-            style={{ transformOrigin: "top left", transform: `scale(${this.props.ContentScaling()})`, width: `${100 / this.props.ContentScaling()}%`, height: `${100 / this.props.ContentScaling()}%` }} >
+            style={{ transform: `scale(${this.props.ContentScaling()})`, width: `${100 / this.props.ContentScaling()}%`, height: `${100 / this.props.ContentScaling()}%` }} >
 
             <SketchPicker onChange={InkingControl.Instance.switchColor}
                 color={StrCast(CurrentUserUtils.ActivePen ? CurrentUserUtils.ActivePen.backgroundColor : undefined,
-- 
cgit v1.2.3-70-g09d2


From a37559a417b8a19ee56ac3b2f5dd46ef70ca53a6 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 23 Apr 2020 20:52:09 -0400
Subject: fixed loading of notes with template types.  changed noteTypes to
 ["template-notes"].  Added pass through free form layout engine.

---
 src/client/documents/Documents.ts                  |  2 +-
 src/client/views/DocumentDecorations.scss          |  2 -
 src/client/views/DocumentDecorations.tsx           | 14 ++---
 src/client/views/TemplateMenu.tsx                  | 11 ++--
 .../CollectionFreeFormLayoutEngines.tsx            | 22 ++++++++
 .../collectionFreeForm/CollectionFreeFormView.scss |  2 +
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 30 ++++++-----
 .../collections/collectionFreeForm/MarqueeView.tsx | 25 ++++++++-
 src/client/views/nodes/DocumentView.tsx            |  2 +-
 src/client/views/nodes/FormattedTextBox.tsx        |  7 ++-
 src/client/views/nodes/WebBox.tsx                  |  3 +-
 src/new_fields/Doc.ts                              | 18 ++++++-
 .../authentication/models/current_user_utils.ts    | 59 ++++++++++++++--------
 13 files changed, 140 insertions(+), 57 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 5ff8f29ec..578d337d6 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -973,7 +973,7 @@ export namespace DocUtils {
     export function addDocumentCreatorMenuItems(docTextAdder: (d: Doc) => void, docAdder: (d: Doc) => void, x: number, y: number): void {
         ContextMenu.Instance.addItem({
             description: "Add Note ...",
-            subitems: DocListCast((Doc.UserDoc().noteTypes as Doc).data).map((note, i) => ({
+            subitems: DocListCast((Doc.UserDoc()["template-notes"] as Doc).data).map((note, i) => ({
                 description: ":" + StrCast(note.title),
                 event: (args: { x: number, y: number }) => {
                     const textDoc = Docs.Create.TextDocument("", {
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index 353520026..28cf9fd47 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -143,7 +143,6 @@ $linkGap : 3px;
     pointer-events: all;
     text-align: center;
     cursor: pointer;
-    padding-right: 10px;
 }
 
 .documentDecorations-minimizeButton {
@@ -157,7 +156,6 @@ $linkGap : 3px;
     position: absolute;
     left: 0px;
     top: 0px;
-    padding-top: 5px;
     width: $MINIMIZED_ICON_SIZE;
     height: $MINIMIZED_ICON_SIZE;
     max-height: 20px;
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index a4ebde3b3..e4ceb75bd 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -165,8 +165,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         return true;
     }
 
-    onCloseDown = (e: React.PointerEvent): void => {
-        setupMoveUpEvents(this, e, (e, d) => false, (e) => { }, this.onMinimizeClick);
+    onIconifyDown = (e: React.PointerEvent): void => {
+        setupMoveUpEvents(this, e, (e, d) => false, (e) => { }, this.onIconifyClick);
     }
     @undoBatch
     @action
@@ -200,7 +200,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
     }
     @undoBatch
     @action
-    onMinimizeClick = (e: PointerEvent): void => {
+    onIconifyClick = (e: PointerEvent): void => {
         if (e.button === 0) {
             const selectedDocs = SelectionManager.SelectedDocuments().map(sd => sd);
             selectedDocs.map(dv => {
@@ -408,9 +408,9 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
             <div className="documentDecorations-contextMenu" title="Show context menu" onPointerDown={this.onSettingsDown}>
                 <FontAwesomeIcon size="lg" icon="cog" />
             </div>) : (
-                <div className="documentDecorations-minimizeButton" title="Iconify" onPointerDown={this.onMaximizeDown}>
+                <div className="documentDecorations-minimizeButton" title="Iconify" onPointerDown={this.onIconifyDown}>
                     {/* Currently, this is set to be enabled if there is no ink selected. It might be interesting to think about minimizing ink if it's useful? -syip2*/}
-                    {SelectionManager.SelectedDocuments().length === 1 ? DocumentDecorations.DocumentIcon(StrCast(seldoc.props.Document.layout, "...")) : "..."}
+                    <FontAwesomeIcon className="documentdecorations-times" icon={faTimes} size="lg" />
                 </div>);
 
         const titleArea = this._edtingTitle ?
@@ -465,8 +465,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
             }}>
                 {maximizeIcon}
                 {titleArea}
-                <div className="documentDecorations-closeButton" title="Close Document" onPointerDown={this.onCloseDown}>
-                    <FontAwesomeIcon className="documentdecorations-times" icon={faTimes} size="lg" />
+                <div className="documentDecorations-closeButton" title="Open Document in Tab" onPointerDown={this.onMaximizeDown}>
+                    {SelectionManager.SelectedDocuments().length === 1 ? DocumentDecorations.DocumentIcon(StrCast(seldoc.props.Document.layout, "...")) : "..."}
                 </div>
                 <div id="documentDecorations-topLeftResizer" className="documentDecorations-resizer"
                     onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 6894500dd..e3c749a4d 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -112,8 +112,7 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
     render() {
         const firstDoc = this.props.docViews[0].props.Document;
         const templateName = StrCast(firstDoc.layoutKey, "layout").replace("layout_", "");
-        const noteTypesDoc = Cast(Doc.UserDoc().noteTypes, Doc, null);
-        const noteTypes = DocListCast(noteTypesDoc?.data);
+        const noteTypes = DocListCast(Cast(Doc.UserDoc()["template-notes"], Doc, null));
         const addedTypes = DocListCast(Cast(Doc.UserDoc().templateButtons, Doc, null)?.data);
         const layout = Doc.Layout(firstDoc);
         const templateMenu: Array<JSX.Element> = [];
@@ -123,11 +122,9 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
         templateMenu.push(<OtherToggle key={"float"} name={"Float"} checked={firstDoc.z ? true : false} toggle={this.toggleFloat} />);
         templateMenu.push(<OtherToggle key={"chrome"} name={"Chrome"} checked={layout._chromeStatus !== "disabled"} toggle={this.toggleChrome} />);
         templateMenu.push(<OtherToggle key={"default"} name={"Default"} checked={templateName === "layout"} toggle={this.toggleDefault} />);
-        if (noteTypesDoc) {
-            addedTypes.concat(noteTypes).map(template => template.treeViewChecked = ComputedField.MakeFunction(`templateIsUsed(self,firstDoc)`, {}, { firstDoc }));
-            this._addedKeys && Array.from(this._addedKeys).filter(key => !noteTypes.some(nt => nt.title === key)).forEach(template => templateMenu.push(
-                <OtherToggle key={template} name={template} checked={templateName === template} toggle={e => this.toggleLayout(e, template)} />));
-        }
+        addedTypes.concat(noteTypes).map(template => template.treeViewChecked = ComputedField.MakeFunction(`templateIsUsed(self,firstDoc)`, {}, { firstDoc }));
+        this._addedKeys && Array.from(this._addedKeys).filter(key => !noteTypes.some(nt => nt.title === key)).forEach(template => templateMenu.push(
+            <OtherToggle key={template} name={template} checked={templateName === template} toggle={e => this.toggleLayout(e, template)} />));
         return <ul className="template-list" style={{ display: "block" }}>
             <input placeholder="+ layout" ref={this._customRef} onKeyPress={this.onCustomKeypress} />
             {templateMenu}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 0a5ea3baf..1ec0542a3 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -75,6 +75,28 @@ interface PivotColumn {
     filters: string[];
 }
 
+export function computerPassLayout(
+    poolData: Map<string, PoolData>,
+    pivotDoc: Doc,
+    childDocs: Doc[],
+    filterDocs: Doc[],
+    childPairs: { layout: Doc, data?: Doc }[],
+    panelDim: number[],
+    viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[]
+) {
+    const docMap = new Map<Doc, ViewDefBounds>();
+    childDocs.forEach((doc, i) => {
+        docMap.set(doc, {
+            type: "doc",
+            x: NumCast(doc.x),
+            y: NumCast(doc.y),
+            width: doc[WidthSym](),
+            height: doc[HeightSym](),
+            payload: undefined
+        });
+    });
+    return normalizeResults(panelDim, 12, childPairs, docMap, poolData, viewDefsToJSX, [], 0, [], childDocs.filter(c => !filterDocs.includes(c)));
+}
 
 export function computerStarburstLayout(
     poolData: Map<string, PoolData>,
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index a00311a9c..4e4e85e13 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -9,6 +9,8 @@
     height: 100%;
     transform-origin: left top;
     border-radius: inherit;
+    touch-action: none;
+    border-radius: inherit;
 }
 
 .collectionfreeformview-ease {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 5967f36f9..e8738b292 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -37,7 +37,7 @@ import { pageSchema } from "../../nodes/ImageBox";
 import PDFMenu from "../../pdf/PDFMenu";
 import { CollectionDockingView } from "../CollectionDockingView";
 import { CollectionSubView } from "../CollectionSubView";
-import { computePivotLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult, computerStarburstLayout } from "./CollectionFreeFormLayoutEngines";
+import { computePivotLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult, computerStarburstLayout, computerPassLayout } from "./CollectionFreeFormLayoutEngines";
 import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors";
 import "./CollectionFreeFormView.scss";
 import MarqueeOptionsMenu from "./MarqueeOptionsMenu";
@@ -94,6 +94,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     @observable.shallow _layoutElements: ViewDefResult[] = []; // shallow because some layout items (eg pivot labels) are just generated 'divs' and can't be frozen as observables
     @observable _clusterSets: (Doc[])[] = [];
 
+    @computed get fitToContentScaling() { return this.fitToContent ? NumCast(this.layoutDoc.fitToContentScaling, 1) : 1; }
     @computed get fitToContent() { return (this.props.fitToBox || this.Document._fitToBox) && !this.isAnnotationOverlay; }
     @computed get parentScaling() { return this.props.ContentScaling && this.fitToContent && !this.isAnnotationOverlay ? this.props.ContentScaling() : 1; }
     @computed get contentBounds() { return aggregateBounds(this._layoutElements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!), NumCast(this.layoutDoc._xPadding, 10), NumCast(this.layoutDoc._yPadding, 10)); }
@@ -104,8 +105,9 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     private easing = () => this.props.Document.panTransformType === "Ease";
     private panX = () => this.fitToContent ? (this.contentBounds.x + this.contentBounds.r) / 2 : this.Document._panX || 0;
     private panY = () => this.fitToContent ? (this.contentBounds.y + this.contentBounds.b) / 2 : this.Document._panY || 0;
-    private zoomScaling = () => (1 / this.parentScaling) * (this.fitToContent ?
-        Math.min(this.props.PanelHeight() / (this.contentBounds.b - this.contentBounds.y), this.props.PanelWidth() / (this.contentBounds.r - this.contentBounds.x)) :
+    private zoomScaling = () => (this.fitToContentScaling / this.parentScaling) * (this.fitToContent ?
+        Math.min(this.props.PanelHeight() / (this.contentBounds.b - this.contentBounds.y),
+            this.props.PanelWidth() / (this.contentBounds.r - this.contentBounds.x)) :
         this.Document.scale || 1)
 
     private centeringShiftX = () => !this.nativeWidth && !this.isAnnotationOverlay ? this.props.PanelWidth() / 2 / this.parentScaling : 0;  // shift so pan position is at center of window for non-overlay collections
@@ -971,7 +973,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         const newPool = new Map<string, any>();
         const engine = StrCast(this.layoutDoc._layoutEngine) || this.props.layoutEngine?.();
         switch (engine) {
-            case "pass": break;
+            case "pass": return { newPool, computedElementData: this.doEngineLayout(newPool, computerPassLayout) }
             case "timeline": return { newPool, computedElementData: this.doEngineLayout(newPool, computeTimelineLayout) };
             case "pivot": return { newPool, computedElementData: this.doEngineLayout(newPool, computePivotLayout) };
             case "starburst": return { newPool, computedElementData: this.doEngineLayout(newPool, computerStarburstLayout) };
@@ -993,6 +995,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         this._cachedPool.clear();
         Array.from(newPool.keys()).forEach(k => this._cachedPool.set(k, newPool.get(k)));
         const elements: ViewDefResult[] = computedElementData.slice();
+        const engine = this.props.layoutEngine?.() || this.props.Document._layoutEngine;
         this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).forEach(pair =>
             elements.push({
                 ele: <CollectionFreeFormDocumentView
@@ -1000,7 +1003,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                     {...this.getChildDocumentViewProps(pair.layout, pair.data)}
                     dataProvider={this.childDataProvider}
                     LayoutDoc={this.childLayoutDocFunc}
-                    pointerEvents={this.props.layoutEngine?.() || this.props.Document._layoutEngine ? false : undefined}
+                    pointerEvents={engine && engine !== "starburst" ? false : undefined}
                     jitterRotation={NumCast(this.props.Document.jitterRotation)}
                     fitToBox={this.props.fitToBox || BoolCast(this.props.freezeChildDimensions)}
                     FreezeDimensions={BoolCast(this.props.freezeChildDimensions)}
@@ -1030,14 +1033,12 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
 
     layoutStarburst = action(() => {
         if (this.layoutDoc._layoutEngine === undefined) {
-            this.layoutDoc._layoutEngine = "starburst";
-            this.layoutDoc.overflow = "visible";
+            Doc.makeStarburst(this.layoutDoc);
         } else {
-
-            this.layoutDoc._layoutEngine = "pass";
+            this.layoutDoc._layoutEngine = undefined;
             this.layoutDoc.overflow = "hidden";
+            this.layoutDoc._fitToBox = undefined;
         }
-
     });
     layoutDocsInGrid = () => {
         UndoManager.RunInBatch(() => {
@@ -1141,7 +1142,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     @computed get marqueeView() {
         return <MarqueeView {...this.props} nudge={this.nudge} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments} addDocument={this.addDocument}
             addLiveTextDocument={this.addLiveTextBox} getContainerTransform={this.getContainerTransform} getTransform={this.getTransform} isAnnotationOverlay={this.isAnnotationOverlay}>
-            <CollectionFreeFormViewPannableContents centeringShiftX={this.centeringShiftX} centeringShiftY={this.centeringShiftY}
+            <CollectionFreeFormViewPannableContents centeringShiftX={this.centeringShiftX} centeringShiftY={this.centeringShiftY} shifted={!this.nativeHeight && !this.isAnnotationOverlay}
                 easing={this.easing} zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}>
                 {this.children}
             </CollectionFreeFormViewPannableContents>
@@ -1216,6 +1217,7 @@ interface CollectionFreeFormViewPannableContentsProps {
     zoomScaling: () => number;
     easing: () => boolean;
     children: () => JSX.Element[];
+    shifted: boolean;
 }
 
 @observer
@@ -1227,7 +1229,11 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF
         const panx = -this.props.panX();
         const pany = -this.props.panY();
         const zoom = this.props.zoomScaling();
-        return <div className={freeformclass} style={{ touchAction: "none", borderRadius: "inherit", transform: `translate(${cenx}px, ${ceny}px) scale(${zoom}) translate(${panx}px, ${pany}px)` }}>
+        return <div className={freeformclass}
+            style={{
+                width: this.props.shifted ? 0 : undefined, height: this.props.shifted ? 0 : undefined,
+                transform: `translate(${cenx}px, ${ceny}px) scale(${zoom}) translate(${panx}px, ${pany}px)`
+            }}>
             {this.props.children()}
         </div>;
     }
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 454c3a5f2..2518ac629 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -20,6 +20,7 @@ import { CognitiveServices } from "../../../cognitive_services/CognitiveServices
 import { RichTextField } from "../../../../new_fields/RichTextField";
 import { CollectionView } from "../CollectionView";
 import { FormattedTextBox } from "../../nodes/FormattedTextBox";
+import { ScriptField } from "../../../../new_fields/ScriptField";
 
 interface MarqueeViewProps {
     getContainerTransform: () => Transform;
@@ -332,6 +333,25 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         return newCollection;
     }
 
+    @action
+    pileup = (e: KeyboardEvent | React.PointerEvent | undefined) => {
+        const bounds = this.Bounds;
+        const selected = this.marqueeSelect(false);
+        selected.map(d => {
+            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.displayTimecode = undefined;  // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
+            return d;
+        });
+        const newCollection = this.getCollection(selected, false);
+        this.props.addDocument(newCollection);
+        this.props.selectDocuments([newCollection], []);
+        MarqueeOptionsMenu.Instance.fadeOut(true);
+        this.hideMarquee();
+        Doc.makeStarburst(newCollection);
+    }
+
     @action
     collection = (e: KeyboardEvent | React.PointerEvent | undefined) => {
         const bounds = this.Bounds;
@@ -476,7 +496,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
             this.delete();
             e.stopPropagation();
         }
-        if (e.key === "c" || e.key === "b" || e.key === "t" || e.key === "s" || e.key === "S") {
+        if (e.key === "c" || e.key === "b" || e.key === "t" || e.key === "s" || e.key === "S" || e.key === "p") {
             this._commandExecuted = true;
             e.stopPropagation();
             e.preventDefault();
@@ -490,6 +510,9 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
             if (e.key === "b") {
                 this.background(e);
             }
+            if (e.key === "p") {
+                this.pileup(e);
+            }
             this.cleanupInteractions(false);
         }
     }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index f05366a15..6c8e44e95 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -562,7 +562,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         let docLayoutTemplate: Opt<Doc>;
         const iconViews = DocListCast(Cast(Doc.UserDoc()["template-icons"], Doc, null)?.data);
         const templBtns = DocListCast(Cast(Doc.UserDoc()["template-buttons"], Doc, null)?.data);
-        const noteTypes = DocListCast(Cast(Doc.UserDoc().noteTypes, Doc, null)?.data);
+        const noteTypes = DocListCast(Cast(Doc.UserDoc()["template-notes"], Doc, null)?.data);
         const clickFuncs = DocListCast(Cast(Doc.UserDoc().clickFuncs, Doc, null)?.data);
         const allTemplates = iconViews.concat(templBtns).concat(noteTypes).concat(clickFuncs).map(btnDoc => (btnDoc.dragFactory as Doc) || btnDoc).filter(doc => doc.isTemplateDoc);
         // bcz: this is hacky -- want to have different templates be applied depending on the "type" of a document.  but type is not reliable and there could be other types of template searches so this should be generalized
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index fd7462a10..6ed2a1b9e 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -405,7 +405,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         !this.props.Document.rootDocument && funcs.push({
             description: "Make Template", event: () => {
                 this.props.Document.isTemplateDoc = makeTemplate(this.props.Document, true);
-                Doc.AddDocToList(Cast(Doc.UserDoc().noteTypes, Doc, null), "data", this.props.Document);
+                Doc.AddDocToList(Cast(Doc.UserDoc()["template-notes"], Doc, null), "data", this.props.Document);
             }, icon: "eye"
         });
         funcs.push({ description: "Toggle Single Line", event: () => this.props.Document._singleLine = !this.props.Document._singleLine, icon: "expand-arrows-alt" });
@@ -433,9 +433,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         const change = cm.findByDescription("Change Perspective...");
         const changeItems: ContextMenuProps[] = change && "subitems" in change ? change.subitems : [];
 
-        const noteTypesDoc = Cast(Doc.UserDoc().noteTypes, Doc, null);
-        const noteTypes = DocListCast(noteTypesDoc?.data);
-        noteTypes.forEach(note => {
+        const noteTypesDoc = Cast(Doc.UserDoc()["template-notes"], Doc, null);
+        DocListCast(noteTypesDoc?.data).forEach(note => {
             changeItems.push({
                 description: StrCast(note.title), event: () => {
                     Doc.setNativeView(this.props.Document);
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 66ddf64c9..4e383e468 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -317,7 +317,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
         if (field instanceof HtmlField) {
             view = <span id="webBox-htmlSpan" dangerouslySetInnerHTML={{ __html: field.html }} />;
         } else if (field instanceof WebField) {
-            view = <iframe ref={this._iframeRef} onLoad={this.iframeLoaded} src={Utils.CorsProxy(field.url.href)} style={{ position: "absolute", width: "100%", height: "100%", top: 0 }} />;
+            const url = this.layoutDoc.UseCors ? Utils.CorsProxy(field.url.href) : field.url.href;
+            view = <iframe ref={this._iframeRef} onLoad={this.iframeLoaded} src={url} style={{ position: "absolute", width: "100%", height: "100%", top: 0 }} />;
         } else {
             view = <iframe ref={this._iframeRef} src={"https://crossorigin.me/https://cs.brown.edu"} style={{ position: "absolute", width: "100%", height: "100%", top: 0 }} />;
         }
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 5f790f886..1373431b8 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -14,7 +14,7 @@ import { PrefetchProxy, ProxyField } from "./Proxy";
 import { FieldId, RefField } from "./RefField";
 import { RichTextField } from "./RichTextField";
 import { listSpec } from "./Schema";
-import { ComputedField } from "./ScriptField";
+import { ComputedField, ScriptField } from "./ScriptField";
 import { Cast, FieldValue, NumCast, StrCast, ToConstructor, ScriptCast } from "./Types";
 import { deleteProperty, getField, getter, makeEditable, makeReadOnly, setter, updateFunction } from "./util";
 import { Docs } from "../client/documents/Documents";
@@ -913,6 +913,22 @@ export namespace Doc {
         return false;
     }
 
+    export function makeStarburst(newCollection: Doc) {
+        newCollection._layoutEngine = "starburst";
+        newCollection._fitToBox = true;
+        newCollection.overflow = "visible";
+        const script = "if (self._layoutEngine === 'starburst') {" +
+            "     self._layoutEngine = 'pass';" +
+            "     self.overflow = undefined;" +
+            "     self.fitToContentScaling=undefined;" +
+            " } else {" +
+            "    self._layoutEngine = 'starburst';" +
+            "    self.overflow = 'visible';" +
+            "    self.fitToContentScaling=10;" +
+            " };";
+        newCollection.onClick = ScriptField.MakeScript(script, { self: Doc.name });
+    }
+
 
     export async function addFieldEnumerations(doc: Opt<Doc>, enumeratedFieldKey: string, enumerations: { title: string, _backgroundColor?: string, color?: string }[]) {
         let optionsCollection = await DocServer.GetRefField(enumeratedFieldKey);
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 82efe5d4e..afde524b6 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -96,31 +96,50 @@ export class CurrentUserUtils {
 
     // setup the different note type skins
     static setupNoteTemplates(doc: Doc) {
-        if (doc.noteTypes === undefined) {
-            const taskStatusValues = [
-                { title: "todo", _backgroundColor: "blue", color: "white" },
-                { title: "in progress", _backgroundColor: "yellow", color: "black" },
-                { title: "completed", _backgroundColor: "green", color: "white" }
-            ];
-            const noteTemplates = [
-                Docs.Create.TextDocument("", { title: "text", style: "Note", isTemplateDoc: true, backgroundColor: "yellow" }),
-                Docs.Create.TextDocument("", { title: "text", style: "Idea", isTemplateDoc: true, backgroundColor: "pink" }),
-                Docs.Create.TextDocument("", { title: "text", style: "Topic", isTemplateDoc: true, backgroundColor: "lightBlue" }),
-                Docs.Create.TextDocument("", { title: "text", style: "Person", isTemplateDoc: true, backgroundColor: "lightGreen" }),
-                Docs.Create.TextDocument("", {
-                    title: "text", style: "Todo", isTemplateDoc: true, backgroundColor: "orange", _autoHeight: false, _height: 100, _showCaption: "caption",
-                    layout: FormattedTextBox.LayoutString("Todo"), caption: RichTextField.DashField("taskStatus")
-                })
-            ];
+        if (doc["template-note-Note"] === undefined) {
+            const noteView = Docs.Create.TextDocument("", { title: "text", style: "Note", isTemplateDoc: true, backgroundColor: "yellow" });
+            noteView.isTemplateDoc = makeTemplate(noteView, true, "Note");
+            doc["template-note-Note"] = new PrefetchProxy(noteView);
+        }
+        if (doc["template-note-Idea"] === undefined) {
+            const noteView = Docs.Create.TextDocument("", { title: "text", style: "Idea", backgroundColor: "pink" });
+            noteView.isTemplateDoc = makeTemplate(noteView, true, "Idea");
+            doc["template-note-Idea"] = new PrefetchProxy(noteView);
+        }
+        if (doc["template-note-Topic"] === undefined) {
+            const noteView = Docs.Create.TextDocument("", { title: "text", style: "Topic", backgroundColor: "lightBlue" });
+            noteView.isTemplateDoc = makeTemplate(noteView, true, "Topic");
+            doc["template-note-Topic"] = new PrefetchProxy(noteView);
+        }
+        if (doc["template-note-Todo"] === undefined) {
+            const noteView = Docs.Create.TextDocument("", {
+                title: "text", style: "Todo", backgroundColor: "orange", _autoHeight: false, _height: 100, _showCaption: "caption",
+                layout: FormattedTextBox.LayoutString("Todo"), caption: RichTextField.DashField("taskStatus")
+            });
+            noteView.isTemplateDoc = makeTemplate(noteView, true, "Todo");
+            doc["template-note-Todo"] = new PrefetchProxy(noteView);
+        }
+        const taskStatusValues = [
+            { title: "todo", _backgroundColor: "blue", color: "white" },
+            { title: "in progress", _backgroundColor: "yellow", color: "black" },
+            { title: "completed", _backgroundColor: "green", color: "white" }
+        ];
+        if (doc.fieldTypes === undefined) {
             doc.fieldTypes = Docs.Create.TreeDocument([], { title: "field enumerations" });
-            Doc.addFieldEnumerations(Doc.GetProto(noteTemplates[4]), "taskStatus", taskStatusValues);
-            doc.noteTypes = new PrefetchProxy(Docs.Create.TreeDocument(noteTemplates.map(nt => makeTemplate(nt, true, StrCast(nt.style)) ? nt : nt),
+            Doc.addFieldEnumerations(Doc.GetProto(doc["template-note-Todo"] as any as Doc), "taskStatus", taskStatusValues);
+        }
+
+        if (doc["template-notes"] === undefined) {
+            doc["template-notes"] = new PrefetchProxy(Docs.Create.TreeDocument([doc["template-note-Note"] as any as Doc,
+            doc["template-note-Idea"] as any as Doc, doc["template-note-Topic"] as any as Doc, doc["template-note-Todo"] as any as Doc],
                 { title: "Note Layouts", _height: 75 }));
         } else {
-            DocListCast(Cast(doc.noteTypes, Doc, null)?.data); // prefetch templates
+            const noteTypes = Cast(doc["template-notes"], Doc, null);
+            DocListCastAsync(noteTypes).then(list => noteTypes.data = new List<Doc>([doc["template-note-Note"] as any as Doc,
+            doc["template-note-Idea"] as any as Doc, doc["template-note-Topic"] as any as Doc, doc["template-note-Todo"] as any as Doc]));
         }
 
-        return doc.noteTypes as Doc;
+        return doc["template-notes"] as Doc;
     }
 
     // creates Note templates, and initial "user" templates
-- 
cgit v1.2.3-70-g09d2


From 08fbeaf2ebf4ee2466fa156e6e8c663d2bef7864 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 24 Apr 2020 00:06:48 -0400
Subject: moved iconify code into Doc from DocumentView.  fixed up starburst a
 bit.

---
 .../apis/google_docs/GooglePhotosClientUtils.ts    | 21 +++---
 src/client/util/RichTextSchema.tsx                 |  2 +-
 src/client/views/DocumentDecorations.tsx           | 16 +---
 src/client/views/TemplateMenu.tsx                  |  2 +-
 .../views/collections/CollectionTreeView.tsx       | 15 ++--
 .../CollectionFreeFormLayoutEngines.tsx            |  4 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 31 +++++---
 .../collections/collectionFreeForm/MarqueeView.tsx | 24 ++++--
 src/client/views/nodes/DocumentView.tsx            | 85 ++++++----------------
 src/client/views/nodes/FormattedTextBox.tsx        | 25 +++----
 src/client/views/pdf/PDFViewer.tsx                 | 43 ++++++-----
 src/new_fields/Doc.ts                              | 85 ++++++++++++++++++++--
 src/new_fields/RichTextUtils.ts                    | 32 ++++----
 13 files changed, 208 insertions(+), 177 deletions(-)

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

diff --git a/src/client/apis/google_docs/GooglePhotosClientUtils.ts b/src/client/apis/google_docs/GooglePhotosClientUtils.ts
index 7c4137f59..8c0149a89 100644
--- a/src/client/apis/google_docs/GooglePhotosClientUtils.ts
+++ b/src/client/apis/google_docs/GooglePhotosClientUtils.ts
@@ -1,19 +1,18 @@
-import { Utils } from "../../../Utils";
-import { ImageField } from "../../../new_fields/URLField";
-import { Cast, StrCast } from "../../../new_fields/Types";
-import { Doc, Opt, DocListCastAsync } from "../../../new_fields/Doc";
+import { AssertionError } from "assert";
+import { EditorState } from "prosemirror-state";
+import { Doc, DocListCastAsync, Opt } from "../../../new_fields/Doc";
 import { Id } from "../../../new_fields/FieldSymbols";
-import Photos = require('googlephotos');
 import { RichTextField } from "../../../new_fields/RichTextField";
 import { RichTextUtils } from "../../../new_fields/RichTextUtils";
-import { EditorState } from "prosemirror-state";
-import { FormattedTextBox } from "../../views/nodes/FormattedTextBox";
+import { Cast, StrCast } from "../../../new_fields/Types";
+import { ImageField } from "../../../new_fields/URLField";
+import { MediaItem, NewMediaItemResult } from "../../../server/apis/google/SharedTypes";
+import { Utils } from "../../../Utils";
 import { Docs, DocumentOptions } from "../../documents/Documents";
-import { NewMediaItemResult, MediaItem } from "../../../server/apis/google/SharedTypes";
-import { AssertionError } from "assert";
-import { DocumentView } from "../../views/nodes/DocumentView";
 import { Networking } from "../../Network";
+import { FormattedTextBox } from "../../views/nodes/FormattedTextBox";
 import GoogleAuthenticationManager from "../GoogleAuthenticationManager";
+import Photos = require('googlephotos');
 
 export namespace GooglePhotos {
 
@@ -340,7 +339,7 @@ export namespace GooglePhotos {
                 const url = data.url.href;
                 const target = Doc.MakeAlias(source);
                 const description = parseDescription(target, descriptionKey);
-                await DocumentView.makeCustomViewClicked(target, Docs.Create.FreeformDocument);
+                await Doc.makeCustomViewClicked(target, Docs.Create.FreeformDocument);
                 media.push({ url, description });
             }
             if (media.length) {
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index d23962d5c..b88a7b017 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -780,7 +780,7 @@ export class DashDocView {
                     if (dashDocBase instanceof Doc) {
                         const aliasedDoc = Doc.MakeAlias(dashDocBase, docid + alias);
                         aliasedDoc.layoutKey = "layout";
-                        node.attrs.fieldKey && DocumentView.makeCustomViewClicked(aliasedDoc, Docs.Create.StackingDocument, node.attrs.fieldKey, undefined);
+                        node.attrs.fieldKey && Doc.makeCustomViewClicked(aliasedDoc, Docs.Create.StackingDocument, node.attrs.fieldKey, undefined);
                         self.doRender(aliasedDoc, removeDoc, node, view, getPos);
                     }
                 });
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index e4ceb75bd..312acd5b2 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -193,7 +193,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
             const selectedDocs = SelectionManager.SelectedDocuments();
             if (selectedDocs.length) {
                 //CollectionDockingView.Instance?.OpenFullScreen(selectedDocs[0], selectedDocs[0].props.LibraryPath);
-                CollectionDockingView.AddRightSplit(selectedDocs[0].props.Document, selectedDocs[0].props.LibraryPath);
+                CollectionDockingView.AddRightSplit(Doc.MakeAlias(selectedDocs[0].props.Document), selectedDocs[0].props.LibraryPath);
             }
         }
         SelectionManager.DeselectAll();
@@ -202,19 +202,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
     @action
     onIconifyClick = (e: PointerEvent): void => {
         if (e.button === 0) {
-            const selectedDocs = SelectionManager.SelectedDocuments().map(sd => sd);
-            selectedDocs.map(dv => {
-                const layoutKey = Cast(dv.props.Document.layoutKey, "string", null);
-                const collapse = layoutKey !== "layout_icon";
-                if (collapse) {
-                    dv.switchViews(collapse, "icon");
-                    if (layoutKey && layoutKey !== "layout") dv.props.Document.deiconifyLayout = layoutKey.replace("layout_", "");
-                } else {
-                    const deiconifyLayout = Cast(dv.props.Document.deiconifyLayout, "string", null);
-                    dv.switchViews(deiconifyLayout ? true : false, deiconifyLayout);
-                    dv.props.Document.deiconifyLayout = undefined;
-                }
-            });
+            SelectionManager.SelectedDocuments().forEach(dv => dv?.iconify());
         }
         SelectionManager.DeselectAll();
     }
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index e3c749a4d..4c84a22ad 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -169,7 +169,7 @@ Scripting.addGlobal(function switchView(doc: Doc, template: Doc | undefined) {
         template = Cast(template.dragFactory, Doc, null);
     }
     const templateTitle = StrCast(template?.title);
-    return templateTitle && DocumentView.makeCustomViewClicked(doc, Docs.Create.FreeformDocument, templateTitle, template);
+    return templateTitle && Doc.makeCustomViewClicked(doc, Docs.Create.FreeformDocument, templateTitle, template);
 });
 
 Scripting.addGlobal(function templateIsUsed(templateDoc: Doc, selDoc: Doc) {
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index a052d045c..ca2004b79 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -3,13 +3,14 @@ import { faAngleRight, faArrowsAltH, faBell, faCamera, faCaretDown, faCaretRight
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, observable, runInAction, untracked } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, DocListCast, Field, HeightSym, WidthSym, DataSym, Opt } from '../../../new_fields/Doc';
+import { DataSym, Doc, DocListCast, Field, HeightSym, Opt, WidthSym } from '../../../new_fields/Doc';
 import { Id } from '../../../new_fields/FieldSymbols';
 import { List } from '../../../new_fields/List';
+import { RichTextField } from '../../../new_fields/RichTextField';
 import { Document, listSpec } from '../../../new_fields/Schema';
 import { ComputedField, ScriptField } from '../../../new_fields/ScriptField';
 import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../new_fields/Types';
-import { emptyFunction, emptyPath, returnFalse, Utils, returnOne, returnZero, returnTransparent, returnTrue, simulateMouseClick } from '../../../Utils';
+import { emptyFunction, emptyPath, returnFalse, returnOne, returnTrue, returnZero, simulateMouseClick, Utils } from '../../../Utils';
 import { Docs, DocUtils } from '../../documents/Documents';
 import { DocumentType } from "../../documents/DocumentTypes";
 import { DocumentManager } from '../../util/DocumentManager';
@@ -24,16 +25,14 @@ import { ContextMenuProps } from '../ContextMenuItem';
 import { EditableView } from "../EditableView";
 import { MainView } from '../MainView';
 import { ContentFittingDocumentView } from '../nodes/ContentFittingDocumentView';
+import { DocumentView } from '../nodes/DocumentView';
 import { ImageBox } from '../nodes/ImageBox';
 import { KeyValueBox } from '../nodes/KeyValueBox';
-import { ScriptBox } from '../ScriptBox';
 import { Templates } from '../Templates';
-import { CollectionSubView, SubCollectionViewProps } from "./CollectionSubView";
+import { CollectionSubView } from "./CollectionSubView";
 import "./CollectionTreeView.scss";
+import { CollectionViewType } from './CollectionView';
 import React = require("react");
-import { CollectionViewType, CollectionView } from './CollectionView';
-import { RichTextField } from '../../../new_fields/RichTextField';
-import { DocumentView } from '../nodes/DocumentView';
 
 
 export interface TreeViewProps {
@@ -768,7 +767,7 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
         const existingOnClick = ContextMenu.Instance.findByDescription("OnClick...");
         const onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
         onClicks.push({
-            description: "Edit onChecked Script", event: () => UndoManager.RunInBatch(() => DocumentView.makeCustomViewClicked(this.props.Document, undefined, "onCheckedClick"), "edit onCheckedClick"), icon: "edit"
+            description: "Edit onChecked Script", event: () => UndoManager.RunInBatch(() => Doc.makeCustomViewClicked(this.props.Document, undefined, "onCheckedClick"), "edit onCheckedClick"), icon: "edit"
         });
         !existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
     }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 1ec0542a3..57f62347a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -113,8 +113,8 @@ export function computerStarburstLayout(
         const deg = i / childDocs.length * Math.PI * 2;
         docMap.set(doc, {
             type: "doc",
-            x: Math.sin(deg) * burstDim[0] / 3 - NumCast(pivotDoc.starburstX),
-            y: Math.cos(deg) * burstDim[1] / 3 - NumCast(pivotDoc.starburstY),
+            x: Math.cos(deg) * (burstDim[0] / 3) - doc[WidthSym]() / 2,
+            y: Math.sin(deg) * (burstDim[1] / 3) - doc[HeightSym]() / 2,
             width: doc[WidthSym](),
             height: doc[HeightSym](),
             payload: undefined
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index e8738b292..63199ccbb 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -885,13 +885,20 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         };
     }
 
-    addDocTab = (doc: Doc, where: string) => {
+    addDocTab = action((doc: Doc, where: string) => {
+        if (where === "inParent") {
+            const pt = this.getTransform().transformPoint(NumCast(doc.x), NumCast(doc.y));
+            doc.x = pt[0];
+            doc.y = pt[1];
+            this.props.addDocument(doc);
+            return true;
+        }
         if (where === "inPlace" && this.layoutDoc.isInPlaceContainer) {
             this.dataDoc[this.props.fieldKey] = new List<Doc>([doc]);
             return true;
         }
         return this.props.addDocTab(doc, where);
-    }
+    })
     getCalculatedPositions(params: { doc: Doc, index: number, collection: Doc, docs: Doc[], state: any }): PoolData {
         const result = this.Document.arrangeScript?.script.run(params, console.log);
         if (result?.success) {
@@ -1031,15 +1038,15 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY));
     }
 
-    layoutStarburst = action(() => {
-        if (this.layoutDoc._layoutEngine === undefined) {
-            Doc.makeStarburst(this.layoutDoc);
-        } else {
-            this.layoutDoc._layoutEngine = undefined;
-            this.layoutDoc.overflow = "hidden";
-            this.layoutDoc._fitToBox = undefined;
-        }
-    });
+    promoteCollection = undoBatch(action(() => {
+        this.childDocs.forEach(doc => {
+            const scr = this.getTransform().inverse().transformPoint(NumCast(doc.x), NumCast(doc.y));
+            doc.x = scr?.[0];
+            doc.y = scr?.[1];
+            this.props.addDocTab(doc, "inParent") && this.props.removeDocument(doc);
+        })
+        this.props.ContainingCollectionView?.removeDocument(this.props.Document);
+    }));
     layoutDocsInGrid = () => {
         UndoManager.RunInBatch(() => {
             const docs = this.childLayoutPairs;
@@ -1073,7 +1080,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         optionItems.push({ description: `${this.Document._LODdisable ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document._LODdisable = !this.Document._LODdisable, icon: "table" });
         optionItems.push({ description: `${this.fitToContent ? "Unset" : "Set"} Fit To Container`, event: () => this.Document._fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" });
         optionItems.push({ description: `${this.Document.useClusters ? "Uncluster" : "Use Clusters"}`, event: () => this.updateClusters(!this.Document.useClusters), icon: "braille" });
-        optionItems.push({ description: "Arrange Starburst", event: this.layoutStarburst, icon: "table" });
+        this.props.ContainingCollectionView && optionItems.push({ description: "Promote Collection", event: this.promoteCollection, icon: "table" });
         optionItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" });
         // layoutItems.push({ description: "Analyze Strokes", event: this.analyzeStrokes, icon: "paint-brush" });
         optionItems.push({ description: "Jitter Rotation", event: action(() => this.props.Document.jitterRotation = (this.props.Document.jitterRotation ? 0 : 10)), icon: "paint-brush" });
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 2518ac629..d8e73a820 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -335,21 +335,33 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
 
     @action
     pileup = (e: KeyboardEvent | React.PointerEvent | undefined) => {
-        const bounds = this.Bounds;
         const selected = this.marqueeSelect(false);
-        selected.map(d => {
+        SelectionManager.DeselectAll();
+
+        let w = 0, h = 0;
+        selected.forEach((d, i) => {
+            Doc.iconify(d);
+            w = Math.max(d[WidthSym](), w);
+            h = Math.max(d[HeightSym](), h);
+        });
+        selected.forEach((d, i) => {
             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.x = Math.cos(Math.PI * 2 * i / selected.length) * 10 - w / 2;
+            d.y = Math.sin(Math.PI * 2 * i / selected.length) * 10 - h / 2;
             d.displayTimecode = undefined;  // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
-            return d;
         });
         const newCollection = this.getCollection(selected, false);
+        newCollection.x = NumCast(newCollection.x) + NumCast(newCollection._width) / 2 - 55;
+        newCollection.y = NumCast(newCollection.y) + NumCast(newCollection._height) / 2 - 55;
+        newCollection._width = newCollection._height = 110;
+        //newCollection.borderRounding = "40px";
+        newCollection.jitterRotation = 10;
+        newCollection._backgroundColor = "yellow";
         this.props.addDocument(newCollection);
         this.props.selectDocuments([newCollection], []);
         MarqueeOptionsMenu.Instance.fadeOut(true);
         this.hideMarquee();
-        Doc.makeStarburst(newCollection);
+        Doc.pileup(newCollection);
     }
 
     @action
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 6c8e44e95..cc8773ed5 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -307,7 +307,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     UndoManager.RunInBatch(func, "on click");
                 } else func();
             } else if (this.Document["onClick-rawScript"] && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes("ScriptingBox")) {// bcz: hack? don't edit a script if you're clicking on a scripting box itself
-                UndoManager.RunInBatch(() => DocumentView.makeCustomViewClicked(this.props.Document, undefined, "onClick"), "edit onClick");
+                UndoManager.RunInBatch(() => Doc.makeCustomViewClicked(this.props.Document, undefined, "onClick"), "edit onClick");
                 //ScriptBox.EditButtonScript("On Button Clicked ...", this.props.Document, "onClick", e.clientX, e.clientY), "on button click");
             } else if (this.Document.isLinkButton) {
                 DocListCast(this.props.Document.links).length && this.followLinkClick(e.altKey, e.ctrlKey, e.shiftKey);
@@ -403,6 +403,19 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         }
     }
 
+    public iconify() {
+        const layoutKey = Cast(this.props.Document.layoutKey, "string", null);
+        const collapse = layoutKey !== "layout_icon";
+        if (collapse) {
+            this.switchViews(collapse, "icon");
+            if (layoutKey && layoutKey !== "layout") this.props.Document.deiconifyLayout = layoutKey.replace("layout_", "");
+        } else {
+            const deiconifyLayout = Cast(this.props.Document.deiconifyLayout, "string", null);
+            this.switchViews(deiconifyLayout ? true : false, deiconifyLayout);
+            this.props.Document.deiconifyLayout = undefined;
+        }
+    }
+
     @action
     handle2PointersMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent<TouchEvent>) => {
         const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true);
@@ -547,56 +560,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     @undoBatch
     deleteClicked = (): void => { SelectionManager.DeselectAll(); this.props.removeDocument?.(this.props.Document); }
 
-    // applies a custom template to a document.  the template is identified by it's short name (e.g, slideView not layout_slideView)
-    static makeCustomViewClicked = (doc: Doc, creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, templateSignature: string = "custom", docLayoutTemplate?: Doc) => {
-        const batch = UndoManager.StartBatch("makeCustomViewClicked");
-        runInAction(() => {
-            doc.layoutKey = "layout_" + templateSignature;
-            if (doc[doc.layoutKey] === undefined) {
-                DocumentView.createCustomView(doc, creator, templateSignature, docLayoutTemplate);
-            }
-        });
-        batch.end();
-    }
-    static findTemplate(templateName: string, type: string, signature: string) {
-        let docLayoutTemplate: Opt<Doc>;
-        const iconViews = DocListCast(Cast(Doc.UserDoc()["template-icons"], Doc, null)?.data);
-        const templBtns = DocListCast(Cast(Doc.UserDoc()["template-buttons"], Doc, null)?.data);
-        const noteTypes = DocListCast(Cast(Doc.UserDoc()["template-notes"], Doc, null)?.data);
-        const clickFuncs = DocListCast(Cast(Doc.UserDoc().clickFuncs, Doc, null)?.data);
-        const allTemplates = iconViews.concat(templBtns).concat(noteTypes).concat(clickFuncs).map(btnDoc => (btnDoc.dragFactory as Doc) || btnDoc).filter(doc => doc.isTemplateDoc);
-        // bcz: this is hacky -- want to have different templates be applied depending on the "type" of a document.  but type is not reliable and there could be other types of template searches so this should be generalized
-        // first try to find a template that matches the specific document type (<typeName>_<templateName>).  otherwise, fallback to a general match on <templateName>
-        !docLayoutTemplate && allTemplates.forEach(tempDoc => StrCast(tempDoc.title) === templateName + "_" + type && (docLayoutTemplate = tempDoc));
-        !docLayoutTemplate && allTemplates.forEach(tempDoc => StrCast(tempDoc.title) === templateName && (docLayoutTemplate = tempDoc));
-        return docLayoutTemplate;
-    }
-    static createCustomView = (doc: Doc, creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, templateSignature: string = "custom", docLayoutTemplate?: Doc) => {
-        const templateName = templateSignature.replace(/\(.*\)/, "");
-        docLayoutTemplate = docLayoutTemplate || DocumentView.findTemplate(templateName, StrCast(doc.type), templateSignature);
-
-        const customName = "layout_" + templateSignature;
-        const _width = NumCast(doc._width);
-        const _height = NumCast(doc._height);
-        const options = { title: "data", backgroundColor: StrCast(doc.backgroundColor), _autoHeight: true, _width, x: -_width / 2, y: - _height / 2, _showSidebar: false };
-
-        let fieldTemplate: Opt<Doc>;
-        if (doc.data instanceof RichTextField || typeof (doc.data) === "string") {
-            fieldTemplate = Docs.Create.TextDocument("", options);
-        } else if (doc.data instanceof PdfField) {
-            fieldTemplate = Docs.Create.PdfDocument("http://www.msn.com", options);
-        } else if (doc.data instanceof VideoField) {
-            fieldTemplate = Docs.Create.VideoDocument("http://www.cs.brown.edu", options);
-        } else if (doc.data instanceof AudioField) {
-            fieldTemplate = Docs.Create.AudioDocument("http://www.cs.brown.edu", options);
-        } else if (doc.data instanceof ImageField) {
-            fieldTemplate = Docs.Create.ImageDocument("http://www.cs.brown.edu", options);
-        }
-        const docTemplate = docLayoutTemplate || creator?.(fieldTemplate ? [fieldTemplate] : [], { title: customName + "(" + doc.title + ")", isTemplateDoc: true, _width: _width + 20, _height: Math.max(100, _height + 45) });
-
-        fieldTemplate && Doc.MakeMetadataFieldTemplate(fieldTemplate, docTemplate ? Doc.GetProto(docTemplate) : docTemplate);
-        docTemplate && Doc.ApplyTemplateTo(docTemplate, doc, customName, undefined);
-    }
 
     @undoBatch
     toggleLinkButtonBehavior = (): void => {
@@ -667,15 +630,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         this.Document.isLinkButton = true;
     }
 
-    @undoBatch
-    @action
-    setCustomView = (custom: boolean, layout: string): void => {
-        Doc.setNativeView(this.props.Document);
-        if (custom) {
-            DocumentView.makeCustomViewClicked(this.props.Document, Docs.Create.StackingDocument, layout, undefined);
-        }
-    }
-
     @undoBatch
     @action
     toggleBackground = (temporary: boolean): void => {
@@ -747,7 +701,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         onClicks.push({ description: this.Document.ignoreClick ? "Select" : "Do Nothing", event: () => this.Document.ignoreClick = !this.Document.ignoreClick, icon: this.Document.ignoreClick ? "unlock" : "lock" });
         onClicks.push({ description: this.Document.isLinkButton ? "Remove Follow Behavior" : "Follow Link in Place", event: this.toggleFollowInPlace, icon: "concierge-bell" });
         onClicks.push({ description: this.Document.isLinkButton || this.Document.onClick ? "Remove Click Behavior" : "Follow Link", event: this.toggleLinkButtonBehavior, icon: "concierge-bell" });
-        onClicks.push({ description: "Edit onClick Script", event: () => UndoManager.RunInBatch(() => DocumentView.makeCustomViewClicked(this.props.Document, undefined, "onClick"), "edit onClick"), icon: "edit" });
+        onClicks.push({ description: "Edit onClick Script", event: () => UndoManager.RunInBatch(() => Doc.makeCustomViewClicked(this.props.Document, undefined, "onClick"), "edit onClick"), icon: "edit" });
         !existingOnClick && cm.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
 
         const funcs: ContextMenuProps[] = [];
@@ -1114,7 +1068,14 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     @computed get ignorePointerEvents() {
         return this.props.pointerEvents === false || (this.Document.isBackground && !this.isSelected() && !SelectionManager.GetIsDragging()) || (this.Document.type === DocumentType.INK && InkingControl.Instance.selectedTool !== InkTool.None);
     }
-
+    @undoBatch
+    @action
+    setCustomView = (custom: boolean, layout: string): void => {
+        Doc.setNativeView(this.props.Document);
+        if (custom) {
+            Doc.makeCustomViewClicked(this.props.Document, Docs.Create.StackingDocument, layout, undefined);
+        }
+    }
     @observable _animate = 0;
     switchViews = action((custom: boolean, view: string) => {
         SelectionManager.SetIsDragging(true);
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 6ed2a1b9e..f71343176 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -13,22 +13,24 @@ import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from "
 import { ReplaceStep } from 'prosemirror-transform';
 import { EditorView } from "prosemirror-view";
 import { DateField } from '../../../new_fields/DateField';
-import { DataSym, Doc, DocListCastAsync, Field, HeightSym, Opt, WidthSym, DocListCast } from "../../../new_fields/Doc";
+import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym } from "../../../new_fields/Doc";
 import { documentSchema } from '../../../new_fields/documentSchemas';
 import { Id } from '../../../new_fields/FieldSymbols';
 import { InkTool } from '../../../new_fields/InkField';
+import { PrefetchProxy } from '../../../new_fields/Proxy';
 import { RichTextField } from "../../../new_fields/RichTextField";
 import { RichTextUtils } from '../../../new_fields/RichTextUtils';
 import { createSchema, makeInterface } from "../../../new_fields/Schema";
-import { Cast, NumCast, StrCast, BoolCast, DateCast } from "../../../new_fields/Types";
+import { Cast, DateCast, NumCast, StrCast } from "../../../new_fields/Types";
 import { TraceMobx } from '../../../new_fields/util';
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnOne, Utils, returnTrue, returnZero } from '../../../Utils';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnOne, returnZero, Utils } from '../../../Utils';
 import { GoogleApiClientUtils, Pulls, Pushes } from '../../apis/google_docs/GoogleApiClientUtils';
 import { DocServer } from "../../DocServer";
 import { Docs, DocUtils } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { DictationManager } from '../../util/DictationManager';
 import { DragManager } from "../../util/DragManager";
+import { makeTemplate } from '../../util/DropConverter';
 import buildKeymap from "../../util/ProsemirrorExampleTransfer";
 import RichTextMenu from '../../util/RichTextMenu';
 import { RichTextRules } from "../../util/RichTextRules";
@@ -46,9 +48,6 @@ import { FieldView, FieldViewProps } from "./FieldView";
 import "./FormattedTextBox.scss";
 import { FormattedTextBoxComment, formattedTextBoxCommentPlugin } from './FormattedTextBoxComment';
 import React = require("react");
-import { PrefetchProxy } from '../../../new_fields/Proxy';
-import { makeTemplate } from '../../util/DropConverter';
-import { DocumentView } from './DocumentView';
 
 library.add(faEdit);
 library.add(faSmile, faTextHeight, faUpload);
@@ -436,24 +435,24 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         const noteTypesDoc = Cast(Doc.UserDoc()["template-notes"], Doc, null);
         DocListCast(noteTypesDoc?.data).forEach(note => {
             changeItems.push({
-                description: StrCast(note.title), event: () => {
+                description: StrCast(note.title), event: undoBatch(() => {
                     Doc.setNativeView(this.props.Document);
-                    DocumentView.makeCustomViewClicked(this.rootDoc, Docs.Create.TreeDocument, StrCast(note.title), note);
-                }, icon: "eye"
+                    Doc.makeCustomViewClicked(this.rootDoc, Docs.Create.TreeDocument, StrCast(note.title), note);
+                }), icon: "eye"
             });
         });
-        changeItems.push({ description: "FreeForm", event: () => DocumentView.makeCustomViewClicked(this.rootDoc, Docs.Create.FreeformDocument, "freeform"), icon: "eye" });
+        changeItems.push({ description: "FreeForm", event: undoBatch(() => Doc.makeCustomViewClicked(this.rootDoc, Docs.Create.FreeformDocument, "freeform"), "change view"), icon: "eye" });
         !change && cm.addItem({ description: "Change Perspective...", subitems: changeItems, icon: "external-link-alt" });
 
         const open = cm.findByDescription("Open New Perspective...");
         const openItems: ContextMenuProps[] = open && "subitems" in open ? open.subitems : [];
 
         openItems.push({
-            description: "FreeForm", event: () => {
+            description: "FreeForm", event: undoBatch(() => {
                 const alias = Doc.MakeAlias(this.rootDoc);
-                DocumentView.makeCustomViewClicked(alias, Docs.Create.FreeformDocument, "freeform");
+                Doc.makeCustomViewClicked(alias, Docs.Create.FreeformDocument, "freeform");
                 this.props.addDocTab(alias, "onRight");
-            }, icon: "eye"
+            }), icon: "eye"
         });
         !open && cm.addItem({ description: "Open New Perspective...", subitems: openItems, icon: "external-link-alt" });
 
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 46b60b554..acaa4363e 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -1,37 +1,36 @@
-import { action, computed, IReactionDisposer, observable, reaction, trace, runInAction } from "mobx";
+import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import * as Pdfjs from "pdfjs-dist";
 import "pdfjs-dist/web/pdf_viewer.css";
+import * as rp from "request-promise";
 import { Dictionary } from "typescript-collections";
-import { Doc, DocListCast, FieldResult, WidthSym, Opt, HeightSym } from "../../../new_fields/Doc";
-import { Id, Copy } from "../../../new_fields/FieldSymbols";
+import { Doc, DocListCast, FieldResult, HeightSym, Opt, WidthSym } from "../../../new_fields/Doc";
+import { documentSchema } from "../../../new_fields/documentSchemas";
+import { Id } from "../../../new_fields/FieldSymbols";
+import { InkTool } from "../../../new_fields/InkField";
 import { List } from "../../../new_fields/List";
-import { makeInterface, createSchema } from "../../../new_fields/Schema";
-import { ScriptField, ComputedField } from "../../../new_fields/ScriptField";
-import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
-import { smoothScroll, Utils, emptyFunction, returnOne, intersectRect, addStyleSheet, addStyleSheetRule, clearStyleSheetRules, returnZero, emptyPath } from "../../../Utils";
+import { createSchema, makeInterface } from "../../../new_fields/Schema";
+import { ScriptField } from "../../../new_fields/ScriptField";
+import { Cast, NumCast } from "../../../new_fields/Types";
+import { PdfField } from "../../../new_fields/URLField";
+import { TraceMobx } from "../../../new_fields/util";
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, emptyPath, intersectRect, returnZero, smoothScroll, Utils } from "../../../Utils";
 import { Docs, DocUtils } from "../../documents/Documents";
+import { DocumentType } from "../../documents/DocumentTypes";
 import { DragManager } from "../../util/DragManager";
 import { CompiledScript, CompileScript } from "../../util/Scripting";
-import { Transform } from "../../util/Transform";
-import PDFMenu from "./PDFMenu";
-import "./PDFViewer.scss";
-import React = require("react");
-import * as rp from "request-promise";
-import { CollectionView } from "../collections/CollectionView";
-import Annotation from "./Annotation";
-import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
 import { SelectionManager } from "../../util/SelectionManager";
+import { Transform } from "../../util/Transform";
 import { undoBatch } from "../../util/UndoManager";
+import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
+import { CollectionView } from "../collections/CollectionView";
 import { ViewBoxAnnotatableComponent } from "../DocComponent";
-import { DocumentType } from "../../documents/DocumentTypes";
-import { documentSchema } from "../../../new_fields/documentSchemas";
 import { DocumentDecorations } from "../DocumentDecorations";
 import { InkingControl } from "../InkingControl";
-import { InkTool } from "../../../new_fields/InkField";
-import { TraceMobx } from "../../../new_fields/util";
-import { PdfField } from "../../../new_fields/URLField";
-import { DocumentView } from "../nodes/DocumentView";
+import Annotation from "./Annotation";
+import PDFMenu from "./PDFMenu";
+import "./PDFViewer.scss";
+import React = require("react");
 const PDFJSViewer = require("pdfjs-dist/web/pdf_viewer");
 const pdfjsLib = require("pdfjs-dist");
 
@@ -574,7 +573,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
         const targetDoc = Docs.Create.TextDocument("", { _width: 200, _height: 200, title: "Note linked to " + this.props.Document.title });
         Doc.GetProto(targetDoc).data = new List<Doc>([clipDoc]);
         clipDoc.rootDocument = targetDoc;
-        DocumentView.makeCustomViewClicked(targetDoc, Docs.Create.StackingDocument, "slideView", undefined);
+        Doc.makeCustomViewClicked(targetDoc, Docs.Create.StackingDocument, "slideView", undefined);
         targetDoc.layoutKey = "layout";
         // const targetDoc = Docs.Create.TextDocument("", { _width: 200, _height: 200, title: "Note linked to " + this.props.Document.title });
         // Doc.GetProto(targetDoc).snipped = this.dataDoc[this.props.fieldKey][Copy]();
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 1373431b8..cc929c040 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -17,7 +17,8 @@ import { listSpec } from "./Schema";
 import { ComputedField, ScriptField } from "./ScriptField";
 import { Cast, FieldValue, NumCast, StrCast, ToConstructor, ScriptCast } from "./Types";
 import { deleteProperty, getField, getter, makeEditable, makeReadOnly, setter, updateFunction } from "./util";
-import { Docs } from "../client/documents/Documents";
+import { Docs, DocumentOptions } from "../client/documents/Documents";
+import { PdfField, VideoField, AudioField, ImageField } from "./URLField";
 
 export namespace Field {
     export function toKeyValueString(doc: Doc, key: string): string {
@@ -913,18 +914,86 @@ export namespace Doc {
         return false;
     }
 
-    export function makeStarburst(newCollection: Doc) {
-        newCollection._layoutEngine = "starburst";
-        newCollection._fitToBox = true;
+    // applies a custom template to a document.  the template is identified by it's short name (e.g, slideView not layout_slideView)
+    export function makeCustomViewClicked(doc: Doc, creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, templateSignature: string = "custom", docLayoutTemplate?: Doc) {
+        const batch = UndoManager.StartBatch("makeCustomViewClicked");
+        runInAction(() => {
+            doc.layoutKey = "layout_" + templateSignature;
+            if (doc[doc.layoutKey] === undefined) {
+                createCustomView(doc, creator, templateSignature, docLayoutTemplate);
+            }
+        });
+        batch.end();
+    }
+    export function findTemplate(templateName: string, type: string, signature: string) {
+        let docLayoutTemplate: Opt<Doc>;
+        const iconViews = DocListCast(Cast(Doc.UserDoc()["template-icons"], Doc, null)?.data);
+        const templBtns = DocListCast(Cast(Doc.UserDoc()["template-buttons"], Doc, null)?.data);
+        const noteTypes = DocListCast(Cast(Doc.UserDoc()["template-notes"], Doc, null)?.data);
+        const clickFuncs = DocListCast(Cast(Doc.UserDoc().clickFuncs, Doc, null)?.data);
+        const allTemplates = iconViews.concat(templBtns).concat(noteTypes).concat(clickFuncs).map(btnDoc => (btnDoc.dragFactory as Doc) || btnDoc).filter(doc => doc.isTemplateDoc);
+        // bcz: this is hacky -- want to have different templates be applied depending on the "type" of a document.  but type is not reliable and there could be other types of template searches so this should be generalized
+        // first try to find a template that matches the specific document type (<typeName>_<templateName>).  otherwise, fallback to a general match on <templateName>
+        !docLayoutTemplate && allTemplates.forEach(tempDoc => StrCast(tempDoc.title) === templateName + "_" + type && (docLayoutTemplate = tempDoc));
+        !docLayoutTemplate && allTemplates.forEach(tempDoc => StrCast(tempDoc.title) === templateName && (docLayoutTemplate = tempDoc));
+        return docLayoutTemplate;
+    }
+    export function createCustomView(doc: Doc, creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, templateSignature: string = "custom", docLayoutTemplate?: Doc) {
+        const templateName = templateSignature.replace(/\(.*\)/, "");
+        docLayoutTemplate = docLayoutTemplate || findTemplate(templateName, StrCast(doc.type), templateSignature);
+
+        const customName = "layout_" + templateSignature;
+        const _width = NumCast(doc._width);
+        const _height = NumCast(doc._height);
+        const options = { title: "data", backgroundColor: StrCast(doc.backgroundColor), _autoHeight: true, _width, x: -_width / 2, y: - _height / 2, _showSidebar: false };
+
+        let fieldTemplate: Opt<Doc>;
+        if (doc.data instanceof RichTextField || typeof (doc.data) === "string") {
+            fieldTemplate = Docs.Create.TextDocument("", options);
+        } else if (doc.data instanceof PdfField) {
+            fieldTemplate = Docs.Create.PdfDocument("http://www.msn.com", options);
+        } else if (doc.data instanceof VideoField) {
+            fieldTemplate = Docs.Create.VideoDocument("http://www.cs.brown.edu", options);
+        } else if (doc.data instanceof AudioField) {
+            fieldTemplate = Docs.Create.AudioDocument("http://www.cs.brown.edu", options);
+        } else if (doc.data instanceof ImageField) {
+            fieldTemplate = Docs.Create.ImageDocument("http://www.cs.brown.edu", options);
+        }
+        const docTemplate = docLayoutTemplate || creator?.(fieldTemplate ? [fieldTemplate] : [], { title: customName + "(" + doc.title + ")", isTemplateDoc: true, _width: _width + 20, _height: Math.max(100, _height + 45) });
+
+        fieldTemplate && Doc.MakeMetadataFieldTemplate(fieldTemplate, docTemplate ? Doc.GetProto(docTemplate) : docTemplate);
+        docTemplate && Doc.ApplyTemplateTo(docTemplate, doc, customName, undefined);
+    }
+    export function makeCustomView(doc: Doc, custom: boolean, layout: string) {
+        Doc.setNativeView(doc);
+        if (custom) {
+            makeCustomViewClicked(doc, Docs.Create.StackingDocument, layout, undefined);
+        }
+    }
+    export function iconify(doc: Doc) {
+        const layoutKey = Cast(doc.layoutKey, "string", null);
+        Doc.makeCustomViewClicked(doc, Docs.Create.StackingDocument, "icon", undefined);
+        if (layoutKey && layoutKey !== "layout") doc.deiconifyLayout = layoutKey.replace("layout_", "");
+    }
+
+    export function pileup(newCollection: Doc) {
+        newCollection._layoutEngine = "pass";
         newCollection.overflow = "visible";
         const script = "if (self._layoutEngine === 'starburst') {" +
-            "     self._layoutEngine = 'pass';" +
             "     self.overflow = undefined;" +
-            "     self.fitToContentScaling=undefined;" +
+            "     self.x = self.x + self._width/2 - 55; " +
+            "     self.y = self.y + self._height/2 - 55; " +
+            "     self._width = self._height = 110;" +
+            "     self._layoutEngine = 'pass';" +
             " } else {" +
-            "    self._layoutEngine = 'starburst';" +
             "    self.overflow = 'visible';" +
-            "    self.fitToContentScaling=10;" +
+            "    !self.starburstRadius && (self.starburstRadius = 500);" +
+            "    if (self._layoutEngine === 'pass') { " +
+            "       self.x = self.x + self._width/2 - 12.5; " +
+            "       self.y = self.y + self._height/2 - 12.5; " +
+            "    }; " +
+            "    self._width = self._height = 25;" +
+            "    self._layoutEngine = 'starburst';" +
             " };";
         newCollection.onClick = ScriptField.MakeScript(script, { self: Doc.name });
     }
diff --git a/src/new_fields/RichTextUtils.ts b/src/new_fields/RichTextUtils.ts
index c211b3d3c..635fd053d 100644
--- a/src/new_fields/RichTextUtils.ts
+++ b/src/new_fields/RichTextUtils.ts
@@ -1,23 +1,21 @@
-import { EditorState, Transaction, TextSelection } from "prosemirror-state";
-import { Node, Fragment, Mark } from "prosemirror-model";
-import { RichTextField } from "./RichTextField";
+import { AssertionError } from "assert";
 import { docs_v1 } from "googleapis";
-import { GoogleApiClientUtils } from "../client/apis/google_docs/GoogleApiClientUtils";
-import { FormattedTextBox } from "../client/views/nodes/FormattedTextBox";
-import { Opt, Doc } from "./Doc";
-import Color = require('color');
+import { Fragment, Mark, Node } from "prosemirror-model";
 import { sinkListItem } from "prosemirror-schema-list";
-import { Utils } from "../Utils";
-import { Docs } from "../client/documents/Documents";
-import { schema } from "../client/util/RichTextSchema";
+import { EditorState, TextSelection, Transaction } from "prosemirror-state";
+import { GoogleApiClientUtils } from "../client/apis/google_docs/GoogleApiClientUtils";
 import { GooglePhotos } from "../client/apis/google_docs/GooglePhotosClientUtils";
 import { DocServer } from "../client/DocServer";
-import { Cast, StrCast } from "./Types";
-import { Id } from "./FieldSymbols";
-import { DocumentView } from "../client/views/nodes/DocumentView";
-import { AssertionError } from "assert";
+import { Docs } from "../client/documents/Documents";
 import { Networking } from "../client/Network";
-import { extname } from "path";
+import { schema } from "../client/util/RichTextSchema";
+import { FormattedTextBox } from "../client/views/nodes/FormattedTextBox";
+import { Utils } from "../Utils";
+import { Doc, Opt } from "./Doc";
+import { Id } from "./FieldSymbols";
+import { RichTextField } from "./RichTextField";
+import { Cast, StrCast } from "./Types";
+import Color = require('color');
 
 export namespace RichTextUtils {
 
@@ -274,7 +272,7 @@ export namespace RichTextUtils {
             const backingDocId = StrCast(textNote[guid]);
             if (!backingDocId) {
                 const backingDoc = Docs.Create.ImageDocument(agnostic, { _width: 300, _height: 300 });
-                DocumentView.makeCustomViewClicked(backingDoc, Docs.Create.FreeformDocument);
+                Doc.makeCustomViewClicked(backingDoc, Docs.Create.FreeformDocument);
                 docid = backingDoc[Id];
                 textNote[guid] = docid;
             } else {
@@ -403,7 +401,7 @@ export namespace RichTextUtils {
                                     let exported = (await Cast(linkDoc.anchor2, Doc))!;
                                     if (!exported.customLayout) {
                                         exported = Doc.MakeAlias(exported);
-                                        DocumentView.makeCustomViewClicked(exported, Docs.Create.FreeformDocument);
+                                        Doc.makeCustomViewClicked(exported, Docs.Create.FreeformDocument);
                                         linkDoc.anchor2 = exported;
                                     }
                                     url = Utils.shareUrl(exported[Id]);
-- 
cgit v1.2.3-70-g09d2


From edf82f7a686169ea279627bac1c661aaa3459ec9 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 24 Apr 2020 11:44:03 -0400
Subject: more starburst tweaks.

---
 .../views/collections/CollectionTimeView.tsx       |  2 +-
 .../CollectionFreeFormLayoutEngines.tsx            |  5 ++-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  6 +--
 src/client/views/nodes/DocumentBox.tsx             |  2 +-
 src/new_fields/Doc.ts                              | 43 +++++++++++++---------
 5 files changed, 33 insertions(+), 25 deletions(-)

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

diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 06ebf6d2d..63498cea1 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -29,7 +29,7 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
     @observable _childClickedScript: Opt<ScriptField>;
     @observable _viewDefDivClick: Opt<ScriptField>;
     async componentDidMount() {
-        const detailView = (await DocCastAsync(this.props.Document.childDetailView)) || DocumentView.findTemplate("detailView", StrCast(this.props.Document.type), "");
+        const detailView = (await DocCastAsync(this.props.Document.childDetailView)) || Doc.findTemplate("detailView", StrCast(this.props.Document.type), "");
         const childText = "const alias = getAlias(self); switchView(alias, detailView); alias.dropAction='alias'; alias.removeDropProperties=new List<string>(['dropAction']); useRightSplit(alias, shiftKey); ";
         runInAction(() => {
             this._childClickedScript = ScriptField.MakeScript(childText, { this: Doc.name, shiftKey: "boolean" }, { detailView: detailView! });
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 57f62347a..7152ff242 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -108,7 +108,8 @@ export function computerStarburstLayout(
     viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[]
 ) {
     const docMap = new Map<Doc, ViewDefBounds>();
-    const burstDim = [NumCast(pivotDoc.starburstRadius, panelDim[0]), NumCast(pivotDoc.starburstRadius, panelDim[1])]
+    const burstDim = [NumCast(pivotDoc.starburstRadius, panelDim[0]), NumCast(pivotDoc.starburstRadius, panelDim[1])];
+    const scaleDim = [NumCast(pivotDoc._starburstWidth, burstDim[0]) * 5, NumCast(pivotDoc._starburstHeight, burstDim[1]) * 5];
     childDocs.forEach((doc, i) => {
         const deg = i / childDocs.length * Math.PI * 2;
         docMap.set(doc, {
@@ -120,7 +121,7 @@ export function computerStarburstLayout(
             payload: undefined
         });
     });
-    return normalizeResults(burstDim, 12, childPairs, docMap, poolData, viewDefsToJSX, [], 0, [], childDocs.filter(c => !filterDocs.includes(c)));
+    return normalizeResults(scaleDim, 12, childPairs, docMap, poolData, viewDefsToJSX, [], 0, [], childDocs.filter(c => !filterDocs.includes(c)));
 }
 
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 63199ccbb..1f0bc89c5 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -898,7 +898,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             return true;
         }
         return this.props.addDocTab(doc, where);
-    })
+    });
     getCalculatedPositions(params: { doc: Doc, index: number, collection: Doc, docs: Doc[], state: any }): PoolData {
         const result = this.Document.arrangeScript?.script.run(params, console.log);
         if (result?.success) {
@@ -980,7 +980,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         const newPool = new Map<string, any>();
         const engine = StrCast(this.layoutDoc._layoutEngine) || this.props.layoutEngine?.();
         switch (engine) {
-            case "pass": return { newPool, computedElementData: this.doEngineLayout(newPool, computerPassLayout) }
+            case "pass": return { newPool, computedElementData: this.doEngineLayout(newPool, computerPassLayout) };
             case "timeline": return { newPool, computedElementData: this.doEngineLayout(newPool, computeTimelineLayout) };
             case "pivot": return { newPool, computedElementData: this.doEngineLayout(newPool, computePivotLayout) };
             case "starburst": return { newPool, computedElementData: this.doEngineLayout(newPool, computerStarburstLayout) };
@@ -1044,7 +1044,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             doc.x = scr?.[0];
             doc.y = scr?.[1];
             this.props.addDocTab(doc, "inParent") && this.props.removeDocument(doc);
-        })
+        });
         this.props.ContainingCollectionView?.removeDocument(this.props.Document);
     }));
     layoutDocsInGrid = () => {
diff --git a/src/client/views/nodes/DocumentBox.tsx b/src/client/views/nodes/DocumentBox.tsx
index 7583aa070..0111cadd5 100644
--- a/src/client/views/nodes/DocumentBox.tsx
+++ b/src/client/views/nodes/DocumentBox.tsx
@@ -111,7 +111,7 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent<FieldViewProps, Do
         const childTemplateName = StrCast(this.props.Document.childTemplateName);
         if (containedDoc && childTemplateName && !containedDoc["layout_" + childTemplateName]) {
             setTimeout(() => {
-                DocumentView.createCustomView(containedDoc, Docs.Create.StackingDocument, childTemplateName);
+                Doc.createCustomView(containedDoc, Docs.Create.StackingDocument, childTemplateName);
                 Doc.expandTemplateLayout(Cast(containedDoc["layout_" + childTemplateName], Doc, null), containedDoc, undefined);
             }, 0);
         }
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 52c3ce141..48606833c 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -998,23 +998,7 @@ export namespace Doc {
 
         newCollection._layoutEngine = "pass";
         newCollection.overflow = "visible";
-        const script = "if (self._layoutEngine === 'starburst') {" +
-            "     self.overflow = undefined;" +
-            "     self.x = self.x + self._width/2 - 55; " +
-            "     self.y = self.y + self._height/2 - 55; " +
-            "     self._width = self._height = 110;" +
-            "     self._layoutEngine = 'pass';" +
-            " } else {" +
-            "    self.overflow = 'visible';" +
-            "    !self.starburstRadius && (self.starburstRadius = 500);" +
-            "    if (self._layoutEngine === 'pass') { " +
-            "       self.x = self.x + self._width/2 - 12.5; " +
-            "       self.y = self.y + self._height/2 - 12.5; " +
-            "    }; " +
-            "    self._width = self._height = 25;" +
-            "    self._layoutEngine = 'starburst';" +
-            " };";
-        newCollection.onClick = ScriptField.MakeScript(script, { self: Doc.name });
+        newCollection.onClick = ScriptField.MakeScript("toggleStarburst(self)", { self: Doc.name });
     }
 
 
@@ -1071,4 +1055,27 @@ Scripting.addGlobal(function selectedDocs(container: Doc, excludeCollections: bo
     return docs.length ? new List(docs) : prevValue;
 });
 Scripting.addGlobal(function setDocFilter(container: Doc, key: string, value: any, modifiers?: "check" | "x" | undefined) { Doc.setDocFilter(container, key, value, modifiers); });
-Scripting.addGlobal(function setDocFilterRange(container: Doc, key: string, range: number[]) { Doc.setDocFilterRange(container, key, range); });
\ No newline at end of file
+Scripting.addGlobal(function setDocFilterRange(container: Doc, key: string, range: number[]) { Doc.setDocFilterRange(container, key, range); });
+Scripting.addGlobal(function toggleStarburst(self: Doc) {
+    if (self._layoutEngine === 'starburst') {
+        const defaultSize = 110;
+        self.overflow = undefined;
+        self.x = NumCast(self.x) + self[WidthSym]() / 2 - NumCast(self._starburstWidth, defaultSize) / 2;
+        self.y = NumCast(self.y) + self[HeightSym]() / 2 - NumCast(self._starburstHeight, defaultSize) / 2;
+        self._width = NumCast(self._starburstWidth, defaultSize);
+        self._height = NumCast(self._starburstHeight, defaultSize);
+        self._layoutEngine = 'pass';
+    } else {
+        const defaultSize = 25;
+        self.overflow = 'visible';
+        !self.starburstRadius && (self.starburstRadius = 200);
+        if (self._layoutEngine === 'pass') {
+            self.x = NumCast(self.x) + self[WidthSym]() / 2 - defaultSize / 2;
+            self.y = NumCast(self.y) + self[HeightSym]() / 2 - defaultSize / 2;
+            self._starburstWidth = self._width;
+            self._starburstHeight = self._height;
+        }
+        self._width = self._height = defaultSize;
+        self._layoutEngine = 'starburst';
+    }
+});
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From f5b6837dba732780930594e76a3676a4264639d7 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 24 Apr 2020 19:56:02 -0400
Subject: fixed problems with PivotView -- docs that show up when they
 shouldn't and not being able to click the pivot background sometimes.

---
 .../CollectionFreeFormLayoutEngines.tsx                   | 15 ++++++++++++---
 .../collectionFreeForm/CollectionFreeFormView.scss        |  9 +++++++++
 .../collectionFreeForm/CollectionFreeFormView.tsx         |  7 ++++---
 3 files changed, 25 insertions(+), 6 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 566a6788b..9f7919ada 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -136,6 +136,7 @@ export function computePivotLayout(
     panelDim: number[],
     viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[]
 ) {
+    const docMap = new Map<Doc, ViewDefBounds>();
     const fieldKey = "data";
     const pivotColumnGroups = new Map<FieldResult<Field>, PivotColumn>();
 
@@ -145,6 +146,16 @@ export function computePivotLayout(
         if (val) {
             !pivotColumnGroups.get(val) && pivotColumnGroups.set(val, { docs: [], filters: [val] });
             pivotColumnGroups.get(val)!.docs.push(doc);
+        } else {
+            docMap.set(doc, {
+                type: "doc",
+                x: 0,
+                y: 0,
+                zIndex: -99,
+                width: 0,
+                height: 0,
+                payload: undefined
+            })
         }
     }
     let nonNumbers = 0;
@@ -193,7 +204,6 @@ export function computePivotLayout(
         }
     }
 
-    const docMap = new Map<Doc, ViewDefBounds>();
     const groupNames: ViewDefBounds[] = [];
 
     const expander = 1.05;
@@ -366,10 +376,9 @@ export function computeTimelineLayout(
 function normalizeResults(panelDim: number[], fontHeight: number, childPairs: { data?: Doc, layout: Doc }[], docMap: Map<Doc, ViewDefBounds>,
     poolData: Map<string, PoolData>, viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], groupNames: ViewDefBounds[], minWidth: number, extras: ViewDefBounds[],
     extraDocs: Doc[]): ViewDefResult[] {
-
     const grpEles = groupNames.map(gn => ({ x: gn.x, y: gn.y, width: gn.width, height: gn.height }) as ViewDefBounds);
     const docEles = childPairs.filter(d => docMap.get(d.layout)).map(pair => docMap.get(pair.layout) as ViewDefBounds);
-    const aggBounds = aggregateBounds(docEles.concat(grpEles), 0, 0);
+    const aggBounds = aggregateBounds(docEles.concat(grpEles).filter(e => e.zIndex !== -99), 0, 0);
     aggBounds.r = Math.max(minWidth, aggBounds.r - aggBounds.x);
     const wscale = panelDim[0] / (aggBounds.r - aggBounds.x);
     let scale = wscale * (aggBounds.b - aggBounds.y) > panelDim[1] ? (panelDim[1]) / (aggBounds.b - aggBounds.y) : wscale;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index 4e4e85e13..60c39c825 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -13,6 +13,15 @@
     border-radius: inherit;
 }
 
+.collectionfreeformview-viewdef {
+    > .collectionFreeFormDocumentView-container {
+        pointer-events: none;
+        .contentFittingDocumentDocumentView-previewDoc {
+            pointer-events: all;
+        }
+    }
+}
+
 .collectionfreeformview-ease {
     transition: transform 500ms;
 }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 1f0bc89c5..b3cf2b97a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1010,7 +1010,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                     {...this.getChildDocumentViewProps(pair.layout, pair.data)}
                     dataProvider={this.childDataProvider}
                     LayoutDoc={this.childLayoutDocFunc}
-                    pointerEvents={engine && engine !== "starburst" ? false : undefined}
+                    pointerEvents={this.props.viewDefDivClick ? false : undefined}
                     jitterRotation={NumCast(this.props.Document.jitterRotation)}
                     fitToBox={this.props.fitToBox || BoolCast(this.props.freezeChildDimensions)}
                     FreezeDimensions={BoolCast(this.props.freezeChildDimensions)}
@@ -1150,7 +1150,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         return <MarqueeView {...this.props} nudge={this.nudge} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments} addDocument={this.addDocument}
             addLiveTextDocument={this.addLiveTextBox} getContainerTransform={this.getContainerTransform} getTransform={this.getTransform} isAnnotationOverlay={this.isAnnotationOverlay}>
             <CollectionFreeFormViewPannableContents centeringShiftX={this.centeringShiftX} centeringShiftY={this.centeringShiftY} shifted={!this.nativeHeight && !this.isAnnotationOverlay}
-                easing={this.easing} zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}>
+                easing={this.easing} viewDefDivClick={this.props.viewDefDivClick} zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}>
                 {this.children}
             </CollectionFreeFormViewPannableContents>
         </MarqueeView>;
@@ -1223,6 +1223,7 @@ interface CollectionFreeFormViewPannableContentsProps {
     panY: () => number;
     zoomScaling: () => number;
     easing: () => boolean;
+    viewDefDivClick?: ScriptField;
     children: () => JSX.Element[];
     shifted: boolean;
 }
@@ -1230,7 +1231,7 @@ interface CollectionFreeFormViewPannableContentsProps {
 @observer
 class CollectionFreeFormViewPannableContents extends React.Component<CollectionFreeFormViewPannableContentsProps>{
     render() {
-        const freeformclass = "collectionfreeformview" + (this.props.easing() ? "-ease" : "-none");
+        const freeformclass = "collectionfreeformview" + (this.props.viewDefDivClick ? "-viewDef" : (this.props.easing() ? "-ease" : "-none"));
         const cenx = this.props.centeringShiftX();
         const ceny = this.props.centeringShiftY();
         const panx = -this.props.panX();
-- 
cgit v1.2.3-70-g09d2


From 896c0fef965458bd23ff098c7c95b09799528033 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 24 Apr 2020 23:00:26 -0400
Subject: changed pivot view to support lists of values for keys (docs can
 appear mulltiple times as replicas)

---
 .../CollectionFreeFormLayoutEngines.tsx            | 122 +++++++++++++--------
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  47 ++++----
 .../views/nodes/CollectionFreeFormDocumentView.tsx |   5 +-
 3 files changed, 100 insertions(+), 74 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 9f7919ada..d8bd18ec7 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -9,6 +9,7 @@ import React = require("react");
 import { Id, ToString } from "../../../../new_fields/FieldSymbols";
 import { ObjectField } from "../../../../new_fields/ObjectField";
 import { RefField } from "../../../../new_fields/RefField";
+import { listSpec } from "../../../../new_fields/Schema";
 
 export interface ViewDefBounds {
     type: string;
@@ -24,6 +25,8 @@ export interface ViewDefBounds {
     highlight?: boolean;
     color?: string;
     payload: any;
+    replica?: string;
+    pair?: { layout: Doc, data?: Doc };
 }
 
 export interface PoolData {
@@ -36,6 +39,8 @@ export interface PoolData {
     color?: string;
     transition?: string;
     highlight?: boolean;
+    replica?: string;
+    pair: { layout: Doc, data?: Doc }
 }
 
 export interface ViewDefResult {
@@ -72,100 +77,111 @@ function getTextWidth(text: string, font: string): number {
 
 interface PivotColumn {
     docs: Doc[];
+    replicas: string[];
     filters: string[];
 }
 
 export function computerPassLayout(
     poolData: Map<string, PoolData>,
     pivotDoc: Doc,
-    childDocs: Doc[],
     filterDocs: Doc[],
     childPairs: { layout: Doc, data?: Doc }[],
     panelDim: number[],
     viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[]
 ) {
-    const docMap = new Map<Doc, ViewDefBounds>();
-    childDocs.forEach((doc, i) => {
-        docMap.set(doc, {
+    const docMap = new Map<string, ViewDefBounds>();
+    childPairs.forEach(({ layout, data }, i) => {
+        docMap.set(layout[Id], {
             type: "doc",
-            x: NumCast(doc.x),
-            y: NumCast(doc.y),
-            width: doc[WidthSym](),
-            height: doc[HeightSym](),
-            payload: undefined
+            x: NumCast(layout.x),
+            y: NumCast(layout.y),
+            width: layout[WidthSym](),
+            height: layout[HeightSym](),
+            payload: undefined,
+            pair: { layout, data },
+            replica: ""
         });
     });
-    return normalizeResults(panelDim, 12, childPairs, docMap, poolData, viewDefsToJSX, [], 0, [], childDocs.filter(c => !filterDocs.includes(c)));
+    return normalizeResults(panelDim, 12, childPairs, docMap, poolData, viewDefsToJSX, [], 0, [], childPairs.filter(c => !filterDocs.includes(c.layout)));
 }
 
 export function computerStarburstLayout(
     poolData: Map<string, PoolData>,
     pivotDoc: Doc,
-    childDocs: Doc[],
     filterDocs: Doc[],
     childPairs: { layout: Doc, data?: Doc }[],
     panelDim: number[],
     viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[]
 ) {
-    const docMap = new Map<Doc, ViewDefBounds>();
+    const docMap = new Map<string, ViewDefBounds>();
     const burstRadius = [NumCast(pivotDoc._starburstRadius, panelDim[0]), NumCast(pivotDoc._starburstRadius, panelDim[1])];
     const docScale = NumCast(pivotDoc._starburstDocScale);
     const docSize = docScale * 100; // assume a icon sized at 100
     const scaleDim = [burstRadius[0] + docSize, burstRadius[1] + docSize];
-    childDocs.forEach((doc, i) => {
-        const deg = i / childDocs.length * Math.PI * 2;
-        docMap.set(doc, {
+    childPairs.forEach((pair, i) => {
+        const deg = i / childPairs.length * Math.PI * 2;
+        docMap.set(pair.layout[Id], {
             type: "doc",
-            x: Math.cos(deg) * (burstRadius[0] / 3) - docScale * doc[WidthSym]() / 2,
-            y: Math.sin(deg) * (burstRadius[1] / 3) - docScale * doc[HeightSym]() / 2,
-            width: docScale * doc[WidthSym](),
-            height: docScale * doc[HeightSym](),
-            payload: undefined
+            x: Math.cos(deg) * (burstRadius[0] / 3) - docScale * pair.layout[WidthSym]() / 2,
+            y: Math.sin(deg) * (burstRadius[1] / 3) - docScale * pair.layout[HeightSym]() / 2,
+            width: docScale * pair.layout[WidthSym](),
+            height: docScale * pair.layout[HeightSym](),
+            payload: undefined,
+            replica: ""
         });
     });
-    return normalizeResults(scaleDim, 12, childPairs, docMap, poolData, viewDefsToJSX, [], 0, [], childDocs.filter(c => !filterDocs.includes(c)));
+    return normalizeResults(scaleDim, 12, childPairs, docMap, poolData, viewDefsToJSX, [], 0, [], childPairs.filter(c => !filterDocs.includes(c.layout)));
 }
 
 
 export function computePivotLayout(
     poolData: Map<string, PoolData>,
     pivotDoc: Doc,
-    childDocs: Doc[],
     filterDocs: Doc[],
     childPairs: { layout: Doc, data?: Doc }[],
     panelDim: number[],
     viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[]
 ) {
-    const docMap = new Map<Doc, ViewDefBounds>();
+    const docMap = new Map<string, ViewDefBounds>();
     const fieldKey = "data";
     const pivotColumnGroups = new Map<FieldResult<Field>, PivotColumn>();
 
     const pivotFieldKey = toLabel(pivotDoc._pivotField);
     for (const doc of filterDocs) {
+        const lval = Cast(doc[pivotFieldKey], listSpec("string"), null);
         const val = Field.toString(doc[pivotFieldKey] as Field);
-        if (val) {
-            !pivotColumnGroups.get(val) && pivotColumnGroups.set(val, { docs: [], filters: [val] });
+        if (lval) {
+            lval.forEach((val, i) => {
+                !pivotColumnGroups.get(val) && pivotColumnGroups.set(val, { docs: [], filters: [val], replicas: [] });
+                pivotColumnGroups.get(val)!.docs.push(doc);
+                pivotColumnGroups.get(val)!.replicas.push(i.toString());
+            });
+        } else if (val) {
+            !pivotColumnGroups.get(val) && pivotColumnGroups.set(val, { docs: [], filters: [val], replicas: [] });
             pivotColumnGroups.get(val)!.docs.push(doc);
+            pivotColumnGroups.get(val)!.replicas.push("");
         } else {
-            docMap.set(doc, {
+            docMap.set(doc[Id], {
                 type: "doc",
                 x: 0,
                 y: 0,
                 zIndex: -99,
                 width: 0,
                 height: 0,
-                payload: undefined
+                payload: undefined,
+                pair: { layout: doc },
+                replica: ""
             })
         }
     }
     let nonNumbers = 0;
-    childDocs.map(doc => {
-        const num = toNumber(doc[pivotFieldKey]);
+    childPairs.map(pair => {
+        const num = toNumber(pair.layout[pivotFieldKey]);
         if (num === undefined || Number.isNaN(num)) {
             nonNumbers++;
         }
     });
-    const pivotNumbers = nonNumbers / childDocs.length < .1;
+    const pivotNumbers = nonNumbers / childPairs.length < .1;
     if (pivotColumnGroups.size > 10) {
         const arrayofKeys = Array.from(pivotColumnGroups.keys());
         const sortedKeys = pivotNumbers ? arrayofKeys.sort((n1: FieldResult, n2: FieldResult) => toNumber(n1)! - toNumber(n2)!) : arrayofKeys.sort();
@@ -177,6 +193,7 @@ export function computePivotLayout(
                 const newgrp = pivotColumnGroups.get(sortedKeys[j])!;
                 curgrp.docs.push(...newgrp.docs);
                 curgrp.filters.push(...newgrp.filters);
+                curgrp.replicas.push(...newgrp.replicas);
                 pivotColumnGroups.delete(sortedKeys[j]);
             }
         }
@@ -226,7 +243,7 @@ export function computePivotLayout(
             fontSize,
             payload: val
         });
-        for (const doc of val.docs) {
+        val.docs.forEach((doc, i) => {
             const layoutDoc = Doc.Layout(doc);
             let wid = pivotAxisWidth;
             let hgt = layoutDoc._nativeWidth ? (NumCast(layoutDoc._nativeHeight) / NumCast(layoutDoc._nativeWidth)) * pivotAxisWidth : pivotAxisWidth;
@@ -234,27 +251,29 @@ export function computePivotLayout(
                 hgt = pivotAxisWidth;
                 wid = layoutDoc._nativeHeight ? (NumCast(layoutDoc._nativeWidth) / NumCast(layoutDoc._nativeHeight)) * pivotAxisWidth : pivotAxisWidth;
             }
-            docMap.set(doc, {
+            docMap.set(doc[Id] + (val.replicas || ""), {
                 type: "doc",
                 x: x + xCount * pivotAxisWidth * expander + (pivotAxisWidth - wid) / 2 + (val.docs.length < numCols ? (numCols - val.docs.length) * pivotAxisWidth / 2 : 0),
                 y: -y + (pivotAxisWidth - hgt) / 2,
                 width: wid,
                 height: hgt,
-                payload: undefined
+                payload: undefined,
+                pair: { layout: doc },
+                replica: val.replicas[i]
             });
             xCount++;
             if (xCount >= numCols) {
                 xCount = 0;
                 y += pivotAxisWidth * expander;
             }
-        }
+        });
         x += pivotAxisWidth * (numCols * expander + gap);
     });
 
     const dividers = sortedPivotKeys.map((key, i) =>
         ({ type: "div", color: "lightGray", x: i * pivotAxisWidth * (numCols * expander + gap) - pivotAxisWidth * (expander - 1) / 2, y: -maxColHeight + pivotAxisWidth, width: pivotAxisWidth * numCols * expander, height: maxColHeight, payload: pivotColumnGroups.get(key)!.filters }));
     groupNames.push(...dividers);
-    return normalizeResults(panelDim, max_text, childPairs, docMap, poolData, viewDefsToJSX, groupNames, 0, [], childDocs.filter(c => !filterDocs.includes(c)));
+    return normalizeResults(panelDim, max_text, childPairs, docMap, poolData, viewDefsToJSX, groupNames, 0, [], childPairs.filter(c => !filterDocs.includes(c.layout)));
 }
 
 function toNumber(val: FieldResult<Field>) {
@@ -264,7 +283,6 @@ function toNumber(val: FieldResult<Field>) {
 export function computeTimelineLayout(
     poolData: Map<string, PoolData>,
     pivotDoc: Doc,
-    childDocs: Doc[],
     filterDocs: Doc[],
     childPairs: { layout: Doc, data?: Doc }[],
     panelDim: number[],
@@ -272,7 +290,7 @@ export function computeTimelineLayout(
 ) {
     const fieldKey = "data";
     const pivotDateGroups = new Map<number, Doc[]>();
-    const docMap = new Map<Doc, ViewDefBounds>();
+    const docMap = new Map<string, ViewDefBounds>();
     const groupNames: ViewDefBounds[] = [];
     const timelineFieldKey = Field.toString(pivotDoc._pivotField as Field);
     const curTime = toNumber(pivotDoc[fieldKey + "-timelineCur"]);
@@ -351,7 +369,7 @@ export function computeTimelineLayout(
     }
 
     const divider = { type: "div", color: Cast(Doc.UserDoc().activeWorkspace, Doc, null)?.darkScheme ? "dimGray" : "black", x: 0, y: 0, width: panelDim[0], height: -1, payload: undefined };
-    return normalizeResults(panelDim, fontHeight, childPairs, docMap, poolData, viewDefsToJSX, groupNames, (maxTime - minTime) * scaling, [divider], childDocs.filter(c => !filterDocs.includes(c)));
+    return normalizeResults(panelDim, fontHeight, childPairs, docMap, poolData, viewDefsToJSX, groupNames, (maxTime - minTime) * scaling, [divider], childPairs.filter(c => !filterDocs.includes(c.layout)));
 
     function layoutDocsAtTime(keyDocs: Doc[], key: number) {
         keyDocs.forEach(doc => {
@@ -363,43 +381,51 @@ export function computeTimelineLayout(
                 hgt = pivotAxisWidth;
                 wid = layoutDoc._nativeHeight ? (NumCast(layoutDoc._nativeWidth) / NumCast(layoutDoc._nativeHeight)) * pivotAxisWidth : pivotAxisWidth;
             }
-            docMap.set(doc, {
+            docMap.set(doc[Id], {
                 type: "doc",
                 x: x, y: -Math.sqrt(stack) * pivotAxisWidth / 2 - pivotAxisWidth + (pivotAxisWidth - hgt) / 2,
-                zIndex: (curTime === key ? 1000 : zind++), highlight: curTime === key, width: wid / (Math.max(stack, 1)), height: hgt / (Math.max(stack, 1)), payload: undefined
+                zIndex: (curTime === key ? 1000 : zind++),
+                highlight: curTime === key,
+                width: wid / (Math.max(stack, 1)),
+                height: hgt / (Math.max(stack, 1)),
+                payload: undefined,
+                pair: { layout: doc },
+                replica: ""
             });
             stacking[stack] = x + pivotAxisWidth;
         });
     }
 }
 
-function normalizeResults(panelDim: number[], fontHeight: number, childPairs: { data?: Doc, layout: Doc }[], docMap: Map<Doc, ViewDefBounds>,
+function normalizeResults(panelDim: number[], fontHeight: number, childPairs: { data?: Doc, layout: Doc }[], docMap: Map<string, ViewDefBounds>,
     poolData: Map<string, PoolData>, viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], groupNames: ViewDefBounds[], minWidth: number, extras: ViewDefBounds[],
-    extraDocs: Doc[]): ViewDefResult[] {
+    extraDocs: { data?: Doc, layout: Doc }[]): ViewDefResult[] {
     const grpEles = groupNames.map(gn => ({ x: gn.x, y: gn.y, width: gn.width, height: gn.height }) as ViewDefBounds);
-    const docEles = childPairs.filter(d => docMap.get(d.layout)).map(pair => docMap.get(pair.layout) as ViewDefBounds);
+    const docEles = Array.from(docMap.entries()).map(ele => ele[1]);
     const aggBounds = aggregateBounds(docEles.concat(grpEles).filter(e => e.zIndex !== -99), 0, 0);
     aggBounds.r = Math.max(minWidth, aggBounds.r - aggBounds.x);
     const wscale = panelDim[0] / (aggBounds.r - aggBounds.x);
     let scale = wscale * (aggBounds.b - aggBounds.y) > panelDim[1] ? (panelDim[1]) / (aggBounds.b - aggBounds.y) : wscale;
     if (Number.isNaN(scale)) scale = 1;
 
-    childPairs.filter(d => docMap.get(d.layout)).map(pair => {
-        const newPosRaw = docMap.get(pair.layout);
+    Array.from(docMap.entries()).filter(ele => ele[1].pair).map(ele => {
+        const newPosRaw = ele[1];
         if (newPosRaw) {
             const newPos = {
                 x: newPosRaw.x * scale,
                 y: newPosRaw.y * scale,
                 z: newPosRaw.z,
+                replica: newPosRaw.replica,
                 highlight: newPosRaw.highlight,
                 zIndex: newPosRaw.zIndex,
                 width: (newPosRaw.width || 0) * scale,
-                height: newPosRaw.height! * scale
+                height: newPosRaw.height! * scale,
+                pair: ele[1].pair!
             };
-            poolData.set(pair.layout[Id], { transition: "transform 1s", ...newPos });
+            poolData.set(newPos.pair.layout[Id] + (newPos.replica || ""), { transition: "transform 1s", ...newPos });
         }
     });
-    extraDocs.map(ed => poolData.set(ed[Id], { x: 0, y: 0, zIndex: -99 }));
+    extraDocs.map(ed => poolData.set(ed.layout[Id], { x: 0, y: 0, zIndex: -99, pair: ed }));
 
     return viewDefsToJSX(extras.concat(groupNames).map(gname => ({
         type: gname.type,
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index b3cf2b97a..5555d9e84 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -85,8 +85,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     private _clusterDistance: number = 75;
     private _hitCluster = false;
     private _layoutComputeReaction: IReactionDisposer | undefined;
-    private _layoutPoolData = new ObservableMap<string, any>();
-    private _cachedPool: Map<string, any> = new Map();
+    private _layoutPoolData = new ObservableMap<string, PoolData>();
+    private _cachedPool: Map<string, PoolData> = new Map();
     @observable private _pullCoords: number[] = [0, 0];
     @observable private _pullDirection: string = "";
 
@@ -728,7 +728,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         if (!this.isAnnotationOverlay && clamp) {
             // this section wraps the pan position, horizontally and/or vertically whenever the content is panned out of the viewing bounds
             const docs = this.childLayoutPairs.filter(pair => pair.layout instanceof Doc).map(pair => pair.layout);
-            const measuredDocs = docs.filter(doc => doc && this.childDataProvider(doc)).map(doc => this.childDataProvider(doc));
+            const measuredDocs = docs.filter(doc => doc && this.childDataProvider(doc, "")).map(doc => this.childDataProvider(doc, ""));
             if (measuredDocs.length) {
                 const ranges = measuredDocs.reduce(({ xrange, yrange }, { x, y, width, height }) =>  // computes range of content
                     ({
@@ -899,16 +899,16 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         }
         return this.props.addDocTab(doc, where);
     });
-    getCalculatedPositions(params: { doc: Doc, index: number, collection: Doc, docs: Doc[], state: any }): PoolData {
+    getCalculatedPositions(params: { pair: { layout: Doc, data?: Doc }, index: number, collection: Doc, docs: Doc[], state: any }): PoolData {
         const result = this.Document.arrangeScript?.script.run(params, console.log);
         if (result?.success) {
-            return { ...result, transition: "transform 1s" };
+            return { ...result, pair: params.pair, transition: "transform 1s" };
         }
-        const layoutDoc = Doc.Layout(params.doc);
-        const { x, y, z, color, zIndex } = params.doc;
+        const layoutDoc = Doc.Layout(params.pair.layout);
+        const { x, y, z, color, zIndex } = params.pair.layout;
         return {
             x: NumCast(x), y: NumCast(y), z: Cast(z, "number"), color: StrCast(color), zIndex: Cast(zIndex, "number"),
-            width: Cast(layoutDoc._width, "number"), height: Cast(layoutDoc._height, "number")
+            width: Cast(layoutDoc._width, "number"), height: Cast(layoutDoc._height, "number"), pair: params.pair
         };
     }
 
@@ -946,20 +946,19 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         }
     }
 
-    childDataProvider = computedFn(function childDataProvider(this: any, doc: Doc) {
-        return this._layoutPoolData.get(doc[Id]);
+    childDataProvider = computedFn(function childDataProvider(this: any, doc: Doc, replica: Opt<string>) {
+        return this._layoutPoolData.get(doc[Id] + (replica || ""));
     }.bind(this));
 
     doEngineLayout(poolData: Map<string, PoolData>,
         engine: (
             poolData: Map<string, PoolData>,
             pivotDoc: Doc,
-            childDocs: Doc[],
             filterDocs: Doc[],
             childPairs: { layout: Doc, data?: Doc }[],
             panelDim: number[],
             viewDefsToJSX: ((views: ViewDefBounds[]) => ViewDefResult[])) => ViewDefResult[]) {
-        return engine(poolData, this.props.Document, this.childDocs, this.childDocs,
+        return engine(poolData, this.props.Document, this.childDocs,
             this.childLayoutPairs, [this.props.PanelWidth(), this.props.PanelHeight()], this.viewDefsToJSX);
     }
 
@@ -970,14 +969,14 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         const elements = initResult && initResult.success ? this.viewDefsToJSX(initResult.result.views) : [];
 
         this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map((pair, i) => {
-            const pos = this.getCalculatedPositions({ doc: pair.layout, index: i, collection: this.Document, docs: layoutDocs, state });
+            const pos = this.getCalculatedPositions({ pair, index: i, collection: this.Document, docs: layoutDocs, state });
             poolData.set(pair.layout[Id], pos);
         });
         return elements;
     }
 
     @computed get doInternalLayoutComputation() {
-        const newPool = new Map<string, any>();
+        const newPool = new Map<string, PoolData>();
         const engine = StrCast(this.layoutDoc._layoutEngine) || this.props.layoutEngine?.();
         switch (engine) {
             case "pass": return { newPool, computedElementData: this.doEngineLayout(newPool, computerPassLayout) };
@@ -992,22 +991,22 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     get doLayoutComputation() {
         const { newPool, computedElementData } = this.doInternalLayoutComputation;
         runInAction(() =>
-            Array.from(newPool.keys()).map(key => {
-                const lastPos = this._cachedPool.get(key); // last computed pos
-                const newPos = newPool.get(key);
+            Array.from(newPool.entries()).map(entry => {
+                const lastPos = this._cachedPool.get(entry[0]); // last computed pos
+                const newPos = entry[1];
                 if (!lastPos || newPos.x !== lastPos.x || newPos.y !== lastPos.y || newPos.z !== lastPos.z || newPos.zIndex !== lastPos.zIndex || newPos.width !== lastPos.width || newPos.height !== lastPos.height) {
-                    this._layoutPoolData.set(key, newPos);
+                    this._layoutPoolData.set(entry[0], newPos);
                 }
             }));
         this._cachedPool.clear();
-        Array.from(newPool.keys()).forEach(k => this._cachedPool.set(k, newPool.get(k)));
+        Array.from(newPool.entries()).forEach(k => this._cachedPool.set(k[0], k[1]));
         const elements: ViewDefResult[] = computedElementData.slice();
-        const engine = this.props.layoutEngine?.() || this.props.Document._layoutEngine;
-        this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).forEach(pair =>
+        Array.from(newPool.entries()).filter(entry => this.isCurrent(entry[1].pair.layout)).forEach(entry =>
             elements.push({
                 ele: <CollectionFreeFormDocumentView
-                    key={pair.layout[Id]}
-                    {...this.getChildDocumentViewProps(pair.layout, pair.data)}
+                    key={entry[1].pair.layout[Id] + (entry[1].replica || "")}
+                    {...this.getChildDocumentViewProps(entry[1].pair.layout, entry[1].pair.data)}
+                    replica={entry[1].replica}
                     dataProvider={this.childDataProvider}
                     LayoutDoc={this.childLayoutDocFunc}
                     pointerEvents={this.props.viewDefDivClick ? false : undefined}
@@ -1015,7 +1014,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                     fitToBox={this.props.fitToBox || BoolCast(this.props.freezeChildDimensions)}
                     FreezeDimensions={BoolCast(this.props.freezeChildDimensions)}
                 />,
-                bounds: this.childDataProvider(pair.layout)
+                bounds: this.childDataProvider(entry[1].pair.layout, entry[1].replica)
             }));
 
         return elements;
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 4b282b0c9..baeac311e 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -12,7 +12,7 @@ import { TraceMobx } from "../../../new_fields/util";
 import { ContentFittingDocumentView } from "./ContentFittingDocumentView";
 
 export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
-    dataProvider?: (doc: Doc) => { x: number, y: number, zIndex?: number, highlight?: boolean, width: number, height: number, z: number, transition?: string } | undefined;
+    dataProvider?: (doc: Doc, replica?: string) => { x: number, y: number, zIndex?: number, highlight?: boolean, width: number, height: number, z: number, transition?: string, replica: string } | undefined;
     x?: number;
     y?: number;
     z?: number;
@@ -23,6 +23,7 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
     jitterRotation: number;
     transition?: string;
     fitToBox?: boolean;
+    replica?: string;
 }
 
 @observer
@@ -46,7 +47,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         return (hgt === undefined && this.nativeWidth && this.nativeHeight) ? this.width * this.nativeHeight / this.nativeWidth : hgt;
     }
     @computed get freezeDimensions() { return this.props.FreezeDimensions; }
-    @computed get dataProvider() { return this.props.dataProvider && this.props.dataProvider(this.props.Document) ? this.props.dataProvider(this.props.Document) : undefined; }
+    @computed get dataProvider() { return this.props.dataProvider?.(this.props.Document, this.props.replica); }
     @computed get nativeWidth() { return NumCast(this.layoutDoc._nativeWidth, this.props.NativeWidth() || (this.freezeDimensions ? this.layoutDoc[WidthSym]() : 0)); }
     @computed get nativeHeight() { return NumCast(this.layoutDoc._nativeHeight, this.props.NativeHeight() || (this.freezeDimensions ? this.layoutDoc[HeightSym]() : 0)); }
 
-- 
cgit v1.2.3-70-g09d2


From efd038bd12de35abc80141b130be523e3fea077a Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 25 Apr 2020 03:42:51 -0400
Subject: hopefully final cleanup of pile up collection view.   added doubleTap
 support in setupMoveUpEvents

---
 src/Utils.ts                                       | 12 ++-
 src/client/documents/Documents.ts                  |  4 +
 .../views/collections/CollectionPileView.scss      |  8 ++
 .../views/collections/CollectionPileView.tsx       | 97 ++++++++++++++++++++++
 src/client/views/collections/CollectionSubView.tsx |  4 +-
 .../views/collections/CollectionTimeView.tsx       |  3 +-
 src/client/views/collections/CollectionView.tsx    |  5 +-
 .../CollectionFreeFormLayoutEngines.tsx            | 95 ++++++++++-----------
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 19 +++--
 .../collections/collectionFreeForm/MarqueeView.tsx | 16 ++--
 src/new_fields/Doc.ts                              | 35 ++------
 11 files changed, 189 insertions(+), 109 deletions(-)
 create mode 100644 src/client/views/collections/CollectionPileView.scss
 create mode 100644 src/client/views/collections/CollectionPileView.tsx

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

diff --git a/src/Utils.ts b/src/Utils.ts
index 9acdc8731..ad12c68a1 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -504,8 +504,9 @@ export function setupMoveUpEvents(
     e: React.PointerEvent,
     moveEvent: (e: PointerEvent, down: number[], delta: number[]) => boolean,
     upEvent: (e: PointerEvent) => void,
-    clickEvent: (e: PointerEvent) => void,
-    stopPropagation: boolean = true
+    clickEvent: (e: PointerEvent, doubleTap?: boolean) => void,
+    stopPropagation: boolean = true,
+    stopMovePropagation: boolean = true
 ) {
     (target as any)._downX = (target as any)._lastX = e.clientX;
     (target as any)._downY = (target as any)._lastY = e.clientY;
@@ -520,12 +521,15 @@ export function setupMoveUpEvents(
         }
         (target as any)._lastX = e.clientX;
         (target as any)._lastY = e.clientY;
-        e.stopPropagation();
+        stopMovePropagation && e.stopPropagation();
     };
+    (target as any)._doubleTap = false;
     const _upEvent = (e: PointerEvent): void => {
+        (target as any)._doubleTap = (Date.now() - (target as any)._lastTap < 300);
+        (target as any)._lastTap = Date.now();
         upEvent(e);
         if (Math.abs(e.clientX - (target as any)._downX) < 4 && Math.abs(e.clientY - (target as any)._downY) < 4) {
-            clickEvent(e);
+            clickEvent(e, (target as any)._doubleTap);
         }
         document.removeEventListener("pointermove", _moveEvent);
         document.removeEventListener("pointerup", _upEvent);
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 578d337d6..1268c97b0 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -612,6 +612,10 @@ export namespace Docs {
             return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, _viewType: CollectionViewType.Freeform }, id);
         }
 
+        export function PileDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
+            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", backgroundColor: "black", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, _viewType: CollectionViewType.Pile }, id);
+        }
+
         export function LinearDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
             return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", backgroundColor: "black", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, _viewType: CollectionViewType.Linear }, id);
         }
diff --git a/src/client/views/collections/CollectionPileView.scss b/src/client/views/collections/CollectionPileView.scss
new file mode 100644
index 000000000..ac874b663
--- /dev/null
+++ b/src/client/views/collections/CollectionPileView.scss
@@ -0,0 +1,8 @@
+.collectionPileView {
+    display: flex;
+    flex-direction: row;
+    position: absolute;
+    height: 100%;
+    width: 100%;
+    overflow: visible;
+}
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
new file mode 100644
index 000000000..511efe0a7
--- /dev/null
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -0,0 +1,97 @@
+import { action, computed, observable, runInAction } from "mobx";
+import { observer } from "mobx-react";
+import { HeightSym, Opt, WidthSym } from "../../../new_fields/Doc";
+import { ScriptField } from "../../../new_fields/ScriptField";
+import { BoolCast, NumCast, StrCast } from "../../../new_fields/Types";
+import { ContextMenu } from "../ContextMenu";
+import { ContextMenuProps } from "../ContextMenuItem";
+import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView";
+import { CollectionSubView } from "./CollectionSubView";
+import "./CollectionPileView.scss";
+import React = require("react");
+import { setupMoveUpEvents, emptyFunction, returnFalse } from "../../../Utils";
+import { SelectionManager } from "../../util/SelectionManager";
+
+@observer
+export class CollectionPileView extends CollectionSubView(doc => doc) {
+    _lastTap = 0;
+    _doubleTap: boolean | undefined = false;
+    _originalChrome: string = "";
+    @observable _contentsActive = true;
+    @observable _layoutEngine = "pass";
+    @observable _collapsed: boolean = false;
+    @observable _childClickedScript: Opt<ScriptField>;
+    componentDidMount() {
+        this._originalChrome = StrCast(this.layoutDoc._chromeStatus);
+        this.layoutDoc._chromeStatus = "disabled";
+        this.layoutDoc.hideFilterView = true;
+    }
+    componentWillUnmount() {
+        this.layoutDoc.hideFilterView = false;
+        this.layoutDoc._chromeStatus = this._originalChrome;
+    }
+
+    layoutEngine = () => this._layoutEngine;
+
+    @computed get contents() {
+        return <div className="collectionPileView-innards" style={{ width: "100%", pointerEvents: this._contentsActive && (this.props.active() || this.layoutEngine() === "starburst") ? undefined : "none" }} >
+            <CollectionFreeFormView {...this.props} layoutEngine={this.layoutEngine} />
+        </div>;
+    }
+
+    specificMenu = (e: React.MouseEvent) => {
+        const layoutItems: ContextMenuProps[] = [];
+        const doc = this.props.Document;
+
+        ContextMenu.Instance.addItem({ description: "Options...", subitems: layoutItems, icon: "eye" });
+    }
+
+    toggleStarburst = action(() => {
+        if (this._layoutEngine === 'starburst') {
+            const defaultSize = 110;
+            this.layoutDoc.overflow = undefined;
+            this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[WidthSym]() / 2 - NumCast(this.layoutDoc._starburstPileWidth, defaultSize) / 2;
+            this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[HeightSym]() / 2 - NumCast(this.layoutDoc._starburstPileHeight, defaultSize) / 2;
+            this.layoutDoc._width = NumCast(this.layoutDoc._starburstPileWidth, defaultSize);
+            this.layoutDoc._height = NumCast(this.layoutDoc._starburstPileHeight, defaultSize);
+            this._layoutEngine = 'pass';
+        } else {
+            const defaultSize = 25;
+            this.layoutDoc.overflow = 'visible';
+            !this.layoutDoc._starburstRadius && (this.layoutDoc._starburstRadius = 500);
+            !this.layoutDoc._starburstDocScale && (this.layoutDoc._starburstDocScale = 2.5);
+            if (this._layoutEngine === 'pass') {
+                this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[WidthSym]() / 2 - defaultSize / 2;
+                this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[HeightSym]() / 2 - defaultSize / 2;
+                this.layoutDoc._starburstPileWidth = this.layoutDoc[WidthSym]();
+                this.layoutDoc._starburstPileHeight = this.layoutDoc[HeightSym]();
+            }
+            this.layoutDoc._width = this.layoutDoc._height = defaultSize;
+            this._layoutEngine = 'starburst';
+        }
+    });
+
+    pointerDown = (e: React.PointerEvent) => {
+        // this._lastTap should be set to 0, and this._doubleTap should be set to false in the class header
+        setupMoveUpEvents(this, e, returnFalse, emptyFunction, emptyFunction, false, false); // this sets _doubleTap
+    }
+
+    onClick = (e: React.MouseEvent) => {
+        if (e.button === 0 && (this._doubleTap || this.layoutEngine() === "starburst")) {
+            SelectionManager.DeselectAll();
+            this.toggleStarburst();
+            e.stopPropagation();
+        } else if (this.layoutEngine() === "pass") {
+            runInAction(() => this._contentsActive = false);
+            setTimeout(action(() => this._contentsActive = true), 300);
+        }
+    }
+
+    render() {
+
+        return <div className={"collectionPileView"} onContextMenu={this.specificMenu} onClick={this.onClick} onPointerDown={this.pointerDown}
+            style={{ width: this.props.PanelWidth(), height: `calc(100%  - ${this.props.Document._chromeStatus === "enabled" ? 51 : 0}px)` }}>
+            {this.contents}
+        </div>;
+    }
+}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 78ba3d6e6..c530b24fb 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -400,9 +400,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
             if (generatedDocuments.length) {
                 const set = generatedDocuments.length > 1 && generatedDocuments.map(d => Doc.iconify(d));
                 if (set) {
-                    const pile = Docs.Create.FreeformDocument(generatedDocuments, { ...options, title: "pile", _LODdisable: true, });
-                    Doc.pileup(pile, generatedDocuments);
-                    addDocument(pile);
+                    addDocument(Doc.pileup(generatedDocuments, options.x!, options.y!));
                 } else {
                     generatedDocuments.forEach(addDocument);
                 }
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 63498cea1..045134225 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -19,7 +19,6 @@ const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
 import React = require("react");
-import { DocumentView } from "../nodes/DocumentView";
 
 @observer
 export class CollectionTimeView extends CollectionSubView(doc => doc) {
@@ -84,7 +83,7 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
     }
 
     @computed get contents() {
-        return <div className="collectionTimeView-innards" key="timeline" style={{ width: "100%" }} onPointerDown={this.contentsDown}>
+        return <div className="collectionTimeView-innards" key="timeline" style={{ width: "100%", pointerEvents: this.props.active() ? undefined : "none" }} onPointerDown={this.contentsDown}>
             <CollectionFreeFormView {...this.props} childClickScript={this._childClickedScript} viewDefDivClick={this._viewDefDivClick} fitToBox={true} freezeChildDimensions={BoolCast(this.layoutDoc._freezeChildDimensions, true)} layoutEngine={this.layoutEngine} />
         </div>;
     }
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 47e79f088..f82c6d8a6 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -46,6 +46,7 @@ import { InteractionUtils } from '../../util/InteractionUtils';
 import { ObjectField } from '../../../new_fields/ObjectField';
 import CollectionMapView from './CollectionMapView';
 import { Transform } from 'prosemirror-transform';
+import { CollectionPileView } from './CollectionPileView';
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -67,7 +68,8 @@ export enum CollectionViewType {
     Carousel = "carousel",
     Linear = "linear",
     Staff = "staff",
-    Map = "map"
+    Map = "map",
+    Pile = "pileup"
 }
 
 export interface CollectionRenderProps {
@@ -169,6 +171,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
             case CollectionViewType.Multicolumn: return (<CollectionMulticolumnView key="collview" {...props} />);
             case CollectionViewType.Multirow: return (<CollectionMultirowView key="rpwview" {...props} />);
             case CollectionViewType.Linear: { return (<CollectionLinearView key="collview" {...props} />); }
+            case CollectionViewType.Pile: { return (<CollectionPileView key="collview" {...props} />); }
             case CollectionViewType.Carousel: { return (<CollectionCarouselView key="collview" {...props} />); }
             case CollectionViewType.Stacking: { this.props.Document.singleColumn = true; return (<CollectionStackingView key="collview" {...props} />); }
             case CollectionViewType.Masonry: { this.props.Document.singleColumn = false; return (<CollectionStackingView key="collview" {...props} />); }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index d8bd18ec7..9ca747113 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -13,10 +13,11 @@ import { listSpec } from "../../../../new_fields/Schema";
 
 export interface ViewDefBounds {
     type: string;
-    text?: string;
+    payload: any;
     x: number;
     y: number;
     z?: number;
+    text?: string;
     zIndex?: number;
     width?: number;
     height?: number;
@@ -24,14 +25,13 @@ export interface ViewDefBounds {
     fontSize?: number;
     highlight?: boolean;
     color?: string;
-    payload: any;
     replica?: string;
     pair?: { layout: Doc, data?: Doc };
 }
 
 export interface PoolData {
-    x?: number;
-    y?: number;
+    x: number;
+    y: number;
     z?: number;
     zIndex?: number;
     width?: number;
@@ -40,7 +40,7 @@ export interface PoolData {
     transition?: string;
     highlight?: boolean;
     replica?: string;
-    pair: { layout: Doc, data?: Doc }
+    pair: { layout: Doc, data?: Doc };
 }
 
 export interface ViewDefResult {
@@ -84,96 +84,88 @@ interface PivotColumn {
 export function computerPassLayout(
     poolData: Map<string, PoolData>,
     pivotDoc: Doc,
-    filterDocs: Doc[],
     childPairs: { layout: Doc, data?: Doc }[],
     panelDim: number[],
     viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[]
 ) {
-    const docMap = new Map<string, ViewDefBounds>();
+    const docMap = new Map<string, PoolData>();
     childPairs.forEach(({ layout, data }, i) => {
         docMap.set(layout[Id], {
-            type: "doc",
             x: NumCast(layout.x),
             y: NumCast(layout.y),
             width: layout[WidthSym](),
             height: layout[HeightSym](),
-            payload: undefined,
             pair: { layout, data },
             replica: ""
         });
     });
-    return normalizeResults(panelDim, 12, childPairs, docMap, poolData, viewDefsToJSX, [], 0, [], childPairs.filter(c => !filterDocs.includes(c.layout)));
+    return normalizeResults(panelDim, 12, docMap, poolData, viewDefsToJSX, [], 0, []);
 }
 
 export function computerStarburstLayout(
     poolData: Map<string, PoolData>,
     pivotDoc: Doc,
-    filterDocs: Doc[],
     childPairs: { layout: Doc, data?: Doc }[],
     panelDim: number[],
     viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[]
 ) {
-    const docMap = new Map<string, ViewDefBounds>();
+    const docMap = new Map<string, PoolData>();
     const burstRadius = [NumCast(pivotDoc._starburstRadius, panelDim[0]), NumCast(pivotDoc._starburstRadius, panelDim[1])];
     const docScale = NumCast(pivotDoc._starburstDocScale);
     const docSize = docScale * 100; // assume a icon sized at 100
     const scaleDim = [burstRadius[0] + docSize, burstRadius[1] + docSize];
-    childPairs.forEach((pair, i) => {
+    childPairs.forEach(({ layout, data }, i) => {
         const deg = i / childPairs.length * Math.PI * 2;
-        docMap.set(pair.layout[Id], {
-            type: "doc",
-            x: Math.cos(deg) * (burstRadius[0] / 3) - docScale * pair.layout[WidthSym]() / 2,
-            y: Math.sin(deg) * (burstRadius[1] / 3) - docScale * pair.layout[HeightSym]() / 2,
-            width: docScale * pair.layout[WidthSym](),
-            height: docScale * pair.layout[HeightSym](),
-            payload: undefined,
+        docMap.set(layout[Id], {
+            x: Math.cos(deg) * (burstRadius[0] / 3) - docScale * layout[WidthSym]() / 2,
+            y: Math.sin(deg) * (burstRadius[1] / 3) - docScale * layout[HeightSym]() / 2,
+            width: docScale * layout[WidthSym](),
+            height: docScale * layout[HeightSym](),
+            pair: { layout, data },
             replica: ""
         });
     });
-    return normalizeResults(scaleDim, 12, childPairs, docMap, poolData, viewDefsToJSX, [], 0, [], childPairs.filter(c => !filterDocs.includes(c.layout)));
+    return normalizeResults(scaleDim, 12, docMap, poolData, viewDefsToJSX, [], 0, []);
 }
 
 
 export function computePivotLayout(
     poolData: Map<string, PoolData>,
     pivotDoc: Doc,
-    filterDocs: Doc[],
     childPairs: { layout: Doc, data?: Doc }[],
     panelDim: number[],
     viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[]
 ) {
-    const docMap = new Map<string, ViewDefBounds>();
+    const docMap = new Map<string, PoolData>();
     const fieldKey = "data";
     const pivotColumnGroups = new Map<FieldResult<Field>, PivotColumn>();
 
     const pivotFieldKey = toLabel(pivotDoc._pivotField);
-    for (const doc of filterDocs) {
-        const lval = Cast(doc[pivotFieldKey], listSpec("string"), null);
-        const val = Field.toString(doc[pivotFieldKey] as Field);
+    childPairs.map(pair => {
+        const lval = Cast(pair.layout[pivotFieldKey], listSpec("string"), null);
+        const val = Field.toString(pair.layout[pivotFieldKey] as Field);
         if (lval) {
             lval.forEach((val, i) => {
                 !pivotColumnGroups.get(val) && pivotColumnGroups.set(val, { docs: [], filters: [val], replicas: [] });
-                pivotColumnGroups.get(val)!.docs.push(doc);
+                pivotColumnGroups.get(val)!.docs.push(pair.layout);
                 pivotColumnGroups.get(val)!.replicas.push(i.toString());
             });
         } else if (val) {
             !pivotColumnGroups.get(val) && pivotColumnGroups.set(val, { docs: [], filters: [val], replicas: [] });
-            pivotColumnGroups.get(val)!.docs.push(doc);
+            pivotColumnGroups.get(val)!.docs.push(pair.layout);
             pivotColumnGroups.get(val)!.replicas.push("");
         } else {
-            docMap.set(doc[Id], {
-                type: "doc",
+            docMap.set(pair.layout[Id], {
                 x: 0,
                 y: 0,
                 zIndex: -99,
                 width: 0,
                 height: 0,
-                payload: undefined,
-                pair: { layout: doc },
+                pair,
                 replica: ""
-            })
+            });
         }
-    }
+    });
     let nonNumbers = 0;
     childPairs.map(pair => {
         const num = toNumber(pair.layout[pivotFieldKey]);
@@ -252,12 +244,10 @@ export function computePivotLayout(
                 wid = layoutDoc._nativeHeight ? (NumCast(layoutDoc._nativeWidth) / NumCast(layoutDoc._nativeHeight)) * pivotAxisWidth : pivotAxisWidth;
             }
             docMap.set(doc[Id] + (val.replicas || ""), {
-                type: "doc",
                 x: x + xCount * pivotAxisWidth * expander + (pivotAxisWidth - wid) / 2 + (val.docs.length < numCols ? (numCols - val.docs.length) * pivotAxisWidth / 2 : 0),
                 y: -y + (pivotAxisWidth - hgt) / 2,
                 width: wid,
                 height: hgt,
-                payload: undefined,
                 pair: { layout: doc },
                 replica: val.replicas[i]
             });
@@ -273,7 +263,7 @@ export function computePivotLayout(
     const dividers = sortedPivotKeys.map((key, i) =>
         ({ type: "div", color: "lightGray", x: i * pivotAxisWidth * (numCols * expander + gap) - pivotAxisWidth * (expander - 1) / 2, y: -maxColHeight + pivotAxisWidth, width: pivotAxisWidth * numCols * expander, height: maxColHeight, payload: pivotColumnGroups.get(key)!.filters }));
     groupNames.push(...dividers);
-    return normalizeResults(panelDim, max_text, childPairs, docMap, poolData, viewDefsToJSX, groupNames, 0, [], childPairs.filter(c => !filterDocs.includes(c.layout)));
+    return normalizeResults(panelDim, max_text, docMap, poolData, viewDefsToJSX, groupNames, 0, []);
 }
 
 function toNumber(val: FieldResult<Field>) {
@@ -283,14 +273,13 @@ function toNumber(val: FieldResult<Field>) {
 export function computeTimelineLayout(
     poolData: Map<string, PoolData>,
     pivotDoc: Doc,
-    filterDocs: Doc[],
     childPairs: { layout: Doc, data?: Doc }[],
     panelDim: number[],
     viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[]
 ) {
     const fieldKey = "data";
     const pivotDateGroups = new Map<number, Doc[]>();
-    const docMap = new Map<string, ViewDefBounds>();
+    const docMap = new Map<string, PoolData>();
     const groupNames: ViewDefBounds[] = [];
     const timelineFieldKey = Field.toString(pivotDoc._pivotField as Field);
     const curTime = toNumber(pivotDoc[fieldKey + "-timelineCur"]);
@@ -306,11 +295,11 @@ export function computeTimelineLayout(
 
     let minTime = minTimeReq === undefined ? Number.MAX_VALUE : minTimeReq;
     let maxTime = maxTimeReq === undefined ? -Number.MAX_VALUE : maxTimeReq;
-    filterDocs.map(doc => {
-        const num = NumCast(doc[timelineFieldKey], Number(StrCast(doc[timelineFieldKey])));
+    childPairs.forEach(pair => {
+        const num = NumCast(pair.layout[timelineFieldKey], Number(StrCast(pair.layout[timelineFieldKey])));
         if (!Number.isNaN(num) && (!minTimeReq || num >= minTimeReq) && (!maxTimeReq || num <= maxTimeReq)) {
             !pivotDateGroups.get(num) && pivotDateGroups.set(num, []);
-            pivotDateGroups.get(num)!.push(doc);
+            pivotDateGroups.get(num)!.push(pair.layout);
             minTime = Math.min(num, minTime);
             maxTime = Math.max(num, maxTime);
         }
@@ -369,7 +358,7 @@ export function computeTimelineLayout(
     }
 
     const divider = { type: "div", color: Cast(Doc.UserDoc().activeWorkspace, Doc, null)?.darkScheme ? "dimGray" : "black", x: 0, y: 0, width: panelDim[0], height: -1, payload: undefined };
-    return normalizeResults(panelDim, fontHeight, childPairs, docMap, poolData, viewDefsToJSX, groupNames, (maxTime - minTime) * scaling, [divider], childPairs.filter(c => !filterDocs.includes(c.layout)));
+    return normalizeResults(panelDim, fontHeight, docMap, poolData, viewDefsToJSX, groupNames, (maxTime - minTime) * scaling, [divider]);
 
     function layoutDocsAtTime(keyDocs: Doc[], key: number) {
         keyDocs.forEach(doc => {
@@ -382,13 +371,11 @@ export function computeTimelineLayout(
                 wid = layoutDoc._nativeHeight ? (NumCast(layoutDoc._nativeWidth) / NumCast(layoutDoc._nativeHeight)) * pivotAxisWidth : pivotAxisWidth;
             }
             docMap.set(doc[Id], {
-                type: "doc",
                 x: x, y: -Math.sqrt(stack) * pivotAxisWidth / 2 - pivotAxisWidth + (pivotAxisWidth - hgt) / 2,
                 zIndex: (curTime === key ? 1000 : zind++),
                 highlight: curTime === key,
                 width: wid / (Math.max(stack, 1)),
                 height: hgt / (Math.max(stack, 1)),
-                payload: undefined,
                 pair: { layout: doc },
                 replica: ""
             });
@@ -397,12 +384,19 @@ export function computeTimelineLayout(
     }
 }
 
-function normalizeResults(panelDim: number[], fontHeight: number, childPairs: { data?: Doc, layout: Doc }[], docMap: Map<string, ViewDefBounds>,
-    poolData: Map<string, PoolData>, viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], groupNames: ViewDefBounds[], minWidth: number, extras: ViewDefBounds[],
-    extraDocs: { data?: Doc, layout: Doc }[]): ViewDefResult[] {
+function normalizeResults(
+    panelDim: number[],
+    fontHeight: number,
+    docMap: Map<string, PoolData>,
+    poolData: Map<string, PoolData>,
+    viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[],
+    groupNames: ViewDefBounds[],
+    minWidth: number,
+    extras: ViewDefBounds[]
+): ViewDefResult[] {
     const grpEles = groupNames.map(gn => ({ x: gn.x, y: gn.y, width: gn.width, height: gn.height }) as ViewDefBounds);
     const docEles = Array.from(docMap.entries()).map(ele => ele[1]);
-    const aggBounds = aggregateBounds(docEles.concat(grpEles).filter(e => e.zIndex !== -99), 0, 0);
+    const aggBounds = aggregateBounds(grpEles.concat(docEles.map(de => ({ ...de, type: "doc", payload: "" }))).filter(e => e.zIndex !== -99), 0, 0);
     aggBounds.r = Math.max(minWidth, aggBounds.r - aggBounds.x);
     const wscale = panelDim[0] / (aggBounds.r - aggBounds.x);
     let scale = wscale * (aggBounds.b - aggBounds.y) > panelDim[1] ? (panelDim[1]) / (aggBounds.b - aggBounds.y) : wscale;
@@ -420,12 +414,11 @@ function normalizeResults(panelDim: number[], fontHeight: number, childPairs: {
                 zIndex: newPosRaw.zIndex,
                 width: (newPosRaw.width || 0) * scale,
                 height: newPosRaw.height! * scale,
-                pair: ele[1].pair!
+                pair: ele[1].pair
             };
             poolData.set(newPos.pair.layout[Id] + (newPos.replica || ""), { transition: "transform 1s", ...newPos });
         }
     });
-    extraDocs.map(ed => poolData.set(ed.layout[Id], { x: 0, y: 0, zIndex: -99, pair: ed }));
 
     return viewDefsToJSX(extras.concat(groupNames).map(gname => ({
         type: gname.type,
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 5555d9e84..af9d3c5be 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -902,7 +902,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     getCalculatedPositions(params: { pair: { layout: Doc, data?: Doc }, index: number, collection: Doc, docs: Doc[], state: any }): PoolData {
         const result = this.Document.arrangeScript?.script.run(params, console.log);
         if (result?.success) {
-            return { ...result, pair: params.pair, transition: "transform 1s" };
+            return { x: 0, y: 0, transition: "transform 1s", ...result, pair: params.pair };
         }
         const layoutDoc = Doc.Layout(params.pair.layout);
         const { x, y, z, color, zIndex } = params.pair.layout;
@@ -954,12 +954,11 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         engine: (
             poolData: Map<string, PoolData>,
             pivotDoc: Doc,
-            filterDocs: Doc[],
             childPairs: { layout: Doc, data?: Doc }[],
             panelDim: number[],
-            viewDefsToJSX: ((views: ViewDefBounds[]) => ViewDefResult[])) => ViewDefResult[]) {
-        return engine(poolData, this.props.Document, this.childDocs,
-            this.childLayoutPairs, [this.props.PanelWidth(), this.props.PanelHeight()], this.viewDefsToJSX);
+            viewDefsToJSX: ((views: ViewDefBounds[]) => ViewDefResult[])) => ViewDefResult[]
+    ) {
+        return engine(poolData, this.props.Document, this.childLayoutPairs, [this.props.PanelWidth(), this.props.PanelHeight()], this.viewDefsToJSX);
     }
 
     doFreeformLayout(poolData: Map<string, PoolData>) {
@@ -977,7 +976,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
 
     @computed get doInternalLayoutComputation() {
         const newPool = new Map<string, PoolData>();
-        const engine = StrCast(this.layoutDoc._layoutEngine) || this.props.layoutEngine?.();
+        const engine = this.props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine);
         switch (engine) {
             case "pass": return { newPool, computedElementData: this.doEngineLayout(newPool, computerPassLayout) };
             case "timeline": return { newPool, computedElementData: this.doEngineLayout(newPool, computeTimelineLayout) };
@@ -1001,6 +1000,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         this._cachedPool.clear();
         Array.from(newPool.entries()).forEach(k => this._cachedPool.set(k[0], k[1]));
         const elements: ViewDefResult[] = computedElementData.slice();
+        const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine);
         Array.from(newPool.entries()).filter(entry => this.isCurrent(entry[1].pair.layout)).forEach(entry =>
             elements.push({
                 ele: <CollectionFreeFormDocumentView
@@ -1009,8 +1009,9 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                     replica={entry[1].replica}
                     dataProvider={this.childDataProvider}
                     LayoutDoc={this.childLayoutDocFunc}
-                    pointerEvents={this.props.viewDefDivClick ? false : undefined}
-                    jitterRotation={NumCast(this.props.Document.jitterRotation)}
+                    pointerEvents={this.props.viewDefDivClick || (engine === "pass" && !this.props.isSelected(true)) ?
+                        false : undefined}
+                    jitterRotation={NumCast(this.props.Document._jitterRotation)}
                     fitToBox={this.props.fitToBox || BoolCast(this.props.freezeChildDimensions)}
                     FreezeDimensions={BoolCast(this.props.freezeChildDimensions)}
                 />,
@@ -1082,7 +1083,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         this.props.ContainingCollectionView && optionItems.push({ description: "Promote Collection", event: this.promoteCollection, icon: "table" });
         optionItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" });
         // layoutItems.push({ description: "Analyze Strokes", event: this.analyzeStrokes, icon: "paint-brush" });
-        optionItems.push({ description: "Jitter Rotation", event: action(() => this.props.Document.jitterRotation = (this.props.Document.jitterRotation ? 0 : 10)), icon: "paint-brush" });
+        optionItems.push({ description: "Jitter Rotation", event: action(() => this.props.Document._jitterRotation = (this.props.Document._jitterRotation ? 0 : 10)), icon: "paint-brush" });
         optionItems.push({
             description: "Import document", icon: "upload", event: ({ x, y }) => {
                 const input = document.createElement("input");
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index ab8174ba1..cd8166309 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -1,12 +1,12 @@
 import { action, computed, observable } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, DocListCast, DataSym, WidthSym, HeightSym } from "../../../../new_fields/Doc";
+import { Doc, DocListCast, DataSym, WidthSym, HeightSym, Opt } from "../../../../new_fields/Doc";
 import { InkField, InkData } from "../../../../new_fields/InkField";
 import { List } from "../../../../new_fields/List";
 import { SchemaHeaderField } from "../../../../new_fields/SchemaHeaderField";
 import { Cast, NumCast, FieldValue, StrCast } from "../../../../new_fields/Types";
 import { Utils } from "../../../../Utils";
-import { Docs, DocUtils } from "../../../documents/Documents";
+import { Docs, DocUtils, DocumentOptions } from "../../../documents/Documents";
 import { SelectionManager } from "../../../util/SelectionManager";
 import { Transform } from "../../../util/Transform";
 import { undoBatch } from "../../../util/UndoManager";
@@ -310,11 +310,10 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         this.hideMarquee();
     }
 
-    getCollection = (selected: Doc[], asTemplate: boolean, isBackground?: boolean) => {
+    getCollection = (selected: Doc[], creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, isBackground?: boolean) => {
         const bounds = this.Bounds;
         // const inkData = this.ink ? this.ink.inkData : undefined;
-        const creator = asTemplate ? Docs.Create.StackingDocument : Docs.Create.FreeformDocument;
-        const newCollection = creator(selected, {
+        const newCollection = (creator || Docs.Create.FreeformDocument)(selected, {
             x: bounds.left,
             y: bounds.top,
             _panX: 0,
@@ -338,8 +337,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         const selected = this.marqueeSelect(false);
         SelectionManager.DeselectAll();
         selected.forEach(d => this.props.removeDocument(d));
-        const newCollection = this.getCollection(selected, false);
-        Doc.pileup(newCollection, selected);
+        const newCollection = Doc.pileup(selected, this.Bounds.left + this.Bounds.width / 2, this.Bounds.top + this.Bounds.height / 2);
         this.props.addDocument(newCollection);
         this.props.selectDocuments([newCollection], []);
         MarqueeOptionsMenu.Instance.fadeOut(true);
@@ -359,7 +357,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
                 return d;
             });
         }
-        const newCollection = this.getCollection(selected, (e as KeyboardEvent)?.key === "t");
+        const newCollection = this.getCollection(selected, (e as KeyboardEvent)?.key === "t" ? Docs.Create.StackingDocument : undefined);
         this.props.addDocument(newCollection);
         this.props.selectDocuments([newCollection], []);
         MarqueeOptionsMenu.Instance.fadeOut(true);
@@ -470,7 +468,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
     }
     @action
     background = (e: KeyboardEvent | React.PointerEvent | undefined) => {
-        const newCollection = this.getCollection([], false, true);
+        const newCollection = this.getCollection([], undefined, true);
         this.props.addDocument(newCollection);
         MarqueeOptionsMenu.Instance.fadeOut(true);
         this.hideMarquee();
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 18ffc38f5..337274774 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -976,7 +976,8 @@ export namespace Doc {
         if (layoutKey && layoutKey !== "layout" && layoutKey !== "layout_icon") doc.deiconifyLayout = layoutKey.replace("layout_", "");
     }
 
-    export function pileup(newCollection: Doc, selected: Doc[]) {
+    export function pileup(selected: Doc[], x: number, y: number) {
+        const newCollection = Docs.Create.PileDocument(selected, { title: "pileup", x: x - 55, y: y - 55, _width: 110, _height: 100, _LODdisable: true });
         let w = 0, h = 0;
         selected.forEach((d, i) => {
             Doc.iconify(d);
@@ -993,12 +994,10 @@ export namespace Doc {
         newCollection.y = NumCast(newCollection.y) + NumCast(newCollection._height) / 2 - 55;
         newCollection._width = newCollection._height = 110;
         //newCollection.borderRounding = "40px";
-        newCollection.jitterRotation = 10;
+        newCollection._jitterRotation = 10;
         newCollection._backgroundColor = "gray";
-
-        newCollection._layoutEngine = "pass";
         newCollection.overflow = "visible";
-        newCollection.onClick = ScriptField.MakeScript("toggleStarburst(self, this)", { self: Doc.name, this: Doc.name });
+        return newCollection;
     }
 
 
@@ -1055,28 +1054,4 @@ Scripting.addGlobal(function selectedDocs(container: Doc, excludeCollections: bo
     return docs.length ? new List(docs) : prevValue;
 });
 Scripting.addGlobal(function setDocFilter(container: Doc, key: string, value: any, modifiers?: "check" | "x" | undefined) { Doc.setDocFilter(container, key, value, modifiers); });
-Scripting.addGlobal(function setDocFilterRange(container: Doc, key: string, range: number[]) { Doc.setDocFilterRange(container, key, range); });
-Scripting.addGlobal(function toggleStarburst(self: Doc, tLayout: Doc) {
-    if (self._layoutEngine === 'starburst') {
-        const defaultSize = 110;
-        tLayout.overflow = undefined;
-        self.x = NumCast(self.x) + tLayout[WidthSym]() / 2 - NumCast(tLayout._starburstPileWidth, defaultSize) / 2;
-        self.y = NumCast(self.y) + tLayout[HeightSym]() / 2 - NumCast(tLayout._starburstPileHeight, defaultSize) / 2;
-        tLayout._width = NumCast(tLayout._starburstPileWidth, defaultSize);
-        tLayout._height = NumCast(tLayout._starburstPileHeight, defaultSize);
-        self._layoutEngine = 'pass';
-    } else {
-        const defaultSize = 25;
-        tLayout.overflow = 'visible';
-        !tLayout._starburstRadius && (tLayout._starburstRadius = 500);
-        !tLayout._starburstDocScale && (tLayout._starburstDocScale = 2.5);
-        if (self._layoutEngine === 'pass') {
-            self.x = NumCast(self.x) + tLayout[WidthSym]() / 2 - defaultSize / 2;
-            self.y = NumCast(self.y) + tLayout[HeightSym]() / 2 - defaultSize / 2;
-            tLayout._starburstPileWidth = tLayout[WidthSym]();
-            tLayout._starburstPileHeight = tLayout[HeightSym]();
-        }
-        tLayout._width = tLayout._height = defaultSize;
-        self._layoutEngine = 'starburst';
-    }
-});
\ No newline at end of file
+Scripting.addGlobal(function setDocFilterRange(container: Doc, key: string, range: number[]) { Doc.setDocFilterRange(container, key, range); });
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From 308ae1521cbd58126d35239cfdd5a138f6fa1fe0 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 25 Apr 2020 15:40:07 -0400
Subject: got rid of focus mechanism on click.  fixed shift-click to
 multiselect.  fixed pile view interactions.  added FollowLink on click menu
 items.

---
 src/client/util/DragManager.ts                     |  9 +++-
 src/client/util/ProsemirrorExampleTransfer.ts      | 18 +++++--
 src/client/views/ContextMenu.tsx                   |  9 ++++
 src/client/views/TemplateMenu.tsx                  |  5 +-
 .../views/collections/CollectionPileView.tsx       | 38 +++++++++++--
 src/client/views/collections/CollectionView.tsx    |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 20 +++----
 src/client/views/nodes/DocumentView.tsx            | 63 +++++++++++++++-------
 src/client/views/nodes/FormattedTextBox.tsx        |  4 +-
 src/client/views/nodes/FormattedTextBoxComment.tsx |  1 -
 src/client/views/nodes/LinkAnchorBox.tsx           |  1 -
 src/client/views/pdf/Annotation.tsx                |  1 -
 12 files changed, 124 insertions(+), 47 deletions(-)

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

diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 42a78a4bf..6bb44f7d6 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -83,6 +83,7 @@ export namespace DragManager {
     }
     export let AbortDrag: () => void = emptyFunction;
     export type MoveFunction = (document: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => boolean;
+    export type RemoveFunction = (document: Doc) => boolean;
 
     export interface DragDropDisposer { (): void; }
     export interface DragOptions {
@@ -138,6 +139,7 @@ export namespace DragManager {
         userDropAction: dropActionType;
         embedDoc?: boolean;
         moveDocument?: MoveFunction;
+        removeDocument?: RemoveFunction;
         isSelectionMove?: boolean; // indicates that an explicitly selected Document is being dragged.  this will suppress onDragStart scripts
     }
     export class LinkDragData {
@@ -351,12 +353,17 @@ export namespace DragManager {
 
         let lastX = downX;
         let lastY = downY;
+        let alias = "alias";
         const moveHandler = (e: PointerEvent) => {
             e.preventDefault(); // required or dragging text menu link item ends up dragging the link button as native drag/drop
             if (dragData instanceof DocumentDragData) {
                 dragData.userDropAction = e.ctrlKey && e.altKey ? "copy" : e.ctrlKey ? "alias" : undefined;
             }
             if (e.shiftKey && CollectionDockingView.Instance && dragData.droppedDocuments.length === 1) {
+                !dragData.dropAction && (dragData.dropAction = alias);
+                if (dragData.dropAction === "move") {
+                    dragData.removeDocument?.(dragData.draggedDocuments[0]);
+                }
                 AbortDrag();
                 finishDrag?.(new DragCompleteEvent(true, dragData));
                 CollectionDockingView.Instance.StartOtherDrag({
@@ -366,7 +373,7 @@ export namespace DragManager {
                     button: 0
                 }, dragData.droppedDocuments);
             }
-            //TODO: Why can't we use e.movementX and e.movementY?
+            alias = "move";
             const moveX = e.pageX - lastX;
             const moveY = e.pageY - lastY;
             lastX = e.pageX;
diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts
index 680f48f70..356f20ce6 100644
--- a/src/client/util/ProsemirrorExampleTransfer.ts
+++ b/src/client/util/ProsemirrorExampleTransfer.ts
@@ -7,7 +7,7 @@ import { splitListItem, wrapInList, } from "prosemirror-schema-list";
 import { EditorState, Transaction, TextSelection } from "prosemirror-state";
 import { SelectionManager } from "./SelectionManager";
 import { Docs } from "../documents/Documents";
-import { NumCast, BoolCast, Cast } from "../../new_fields/Types";
+import { NumCast, BoolCast, Cast, StrCast } from "../../new_fields/Types";
 import { Doc } from "../../new_fields/Doc";
 import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
 import { Id } from "../../new_fields/FieldSymbols";
@@ -153,10 +153,16 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, props: any
         const layoutDoc = props.Document;
         const originalDoc = layoutDoc.rootDocument || layoutDoc;
         if (originalDoc instanceof Doc) {
+            const layoutKey = StrCast(originalDoc.layoutKey);
             const newDoc = Docs.Create.TextDocument("", {
-                layout: Cast(originalDoc.layout, Doc, null) || FormattedTextBox.DefaultLayout, _singleLine: BoolCast(originalDoc._singleLine),
+                layout: Cast(originalDoc.layout, Doc, null) || FormattedTextBox.DefaultLayout,
+                layoutKey,
+                _singleLine: BoolCast(originalDoc._singleLine),
                 x: NumCast(originalDoc.x), y: NumCast(originalDoc.y) + NumCast(originalDoc._height) + 10, _width: NumCast(layoutDoc._width), _height: NumCast(layoutDoc._height)
             });
+            if (layoutKey !== "layout" && originalDoc[layoutKey] instanceof Doc) {
+                newDoc[layoutKey] = originalDoc[layoutKey];
+            }
             FormattedTextBox.SelectOnLoad = newDoc[Id];
             props.addDocument(newDoc);
         }
@@ -171,10 +177,16 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, props: any
         const layoutDoc = props.Document;
         const originalDoc = layoutDoc.rootDocument || layoutDoc;
         if (force || props.Document._singleLine) {
+            const layoutKey = StrCast(originalDoc.layoutKey);
             const newDoc = Docs.Create.TextDocument("", {
-                layout: Cast(originalDoc.layout, Doc, null) || FormattedTextBox.DefaultLayout, _singleLine: BoolCast(originalDoc._singleLine),
+                layout: Cast(originalDoc.layout, Doc, null) || FormattedTextBox.DefaultLayout,
+                layoutKey,
+                _singleLine: BoolCast(originalDoc._singleLine),
                 x: NumCast(originalDoc.x) + NumCast(originalDoc._width) + 10, y: NumCast(originalDoc.y), _width: NumCast(layoutDoc._width), _height: NumCast(layoutDoc._height)
             });
+            if (layoutKey !== "layout" && originalDoc[layoutKey] instanceof Doc) {
+                newDoc[layoutKey] = originalDoc[layoutKey];
+            }
             FormattedTextBox.SelectOnLoad = newDoc[Id];
             props.addDocument(newDoc);
             return true;
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index 4d04d4e89..5b66b63ed 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -99,6 +99,15 @@ export class ContextMenu extends React.Component {
         }
     }
     @action
+    moveAfter(item: ContextMenuProps, after: ContextMenuProps) {
+        if (this.findByDescription(after.description)) {
+            const curInd = this._items.findIndex((i) => i.description === item.description);
+            this._items.splice(curInd, 1);
+            const afterInd = this._items.findIndex((i) => i.description === after.description);
+            this._items.splice(afterInd + 1, 0, item);
+        }
+    }
+    @action
     setDefaultItem(prefix: string, item: (name: string) => void) {
         this._defaultPrefix = prefix;
         this._defaultItem = item;
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 4c84a22ad..665ab4e41 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -14,6 +14,7 @@ import { returnTrue, emptyFunction, returnFalse, returnOne, emptyPath, returnZer
 import { Transform } from "../util/Transform";
 import { ScriptField, ComputedField } from "../../new_fields/ScriptField";
 import { Scripting } from "../util/Scripting";
+import { List } from "../../new_fields/List";
 
 @observer
 class TemplateToggle extends React.Component<{ template: Template, checked: boolean, toggle: (event: React.ChangeEvent<HTMLInputElement>, template: Template) => void }> {
@@ -106,8 +107,8 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
 
     return100 = () => 100;
     @computed get scriptField() {
-        return ScriptField.MakeScript("switchView(firstDoc, this)", { this: Doc.name, heading: "string", checked: "string", containingTreeView: Doc.name, firstDoc: Doc.name },
-            { firstDoc: this.props.docViews[0].props.Document });
+        return ScriptField.MakeScript("docs.map(d => switchView(d, this))", { this: Doc.name, heading: "string", checked: "string", containingTreeView: Doc.name, firstDoc: Doc.name },
+            { docs: new List<Doc>(this.props.docViews.map(dv => dv.props.Document)) });
     }
     render() {
         const firstDoc = this.props.docViews[0].props.Document;
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index 511efe0a7..410b16ec7 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -11,6 +11,7 @@ import "./CollectionPileView.scss";
 import React = require("react");
 import { setupMoveUpEvents, emptyFunction, returnFalse } from "../../../Utils";
 import { SelectionManager } from "../../util/SelectionManager";
+import { UndoManager } from "../../util/UndoManager";
 
 @observer
 export class CollectionPileView extends CollectionSubView(doc => doc) {
@@ -34,7 +35,10 @@ export class CollectionPileView extends CollectionSubView(doc => doc) {
     layoutEngine = () => this._layoutEngine;
 
     @computed get contents() {
-        return <div className="collectionPileView-innards" style={{ width: "100%", pointerEvents: this._contentsActive && (this.props.active() || this.layoutEngine() === "starburst") ? undefined : "none" }} >
+        return <div className="collectionPileView-innards" style={{
+            width: "100%",
+            pointerEvents: this.layoutEngine() !== "pass" && (this.props.active() || this.layoutEngine() === "starburst") ? undefined : "none"
+        }} >
             <CollectionFreeFormView {...this.props} layoutEngine={this.layoutEngine} />
         </div>;
     }
@@ -71,9 +75,32 @@ export class CollectionPileView extends CollectionSubView(doc => doc) {
         }
     });
 
+    _undoBatch: UndoManager.Batch | undefined;
     pointerDown = (e: React.PointerEvent) => {
+        let dist = 0;
         // this._lastTap should be set to 0, and this._doubleTap should be set to false in the class header
-        setupMoveUpEvents(this, e, returnFalse, emptyFunction, emptyFunction, false, false); // this sets _doubleTap
+        setupMoveUpEvents(this, e, (e: PointerEvent, down: number[], delta: number[]) => {
+            if (this.layoutEngine() === "pass" && this.childDocs.length && this.props.isSelected(true)) {
+                dist += Math.sqrt(delta[0] * delta[0] + delta[1] * delta[1]);
+                if (dist > 100) {
+                    if (!this._undoBatch) {
+                        this._undoBatch = UndoManager.StartBatch("layout pile");
+                    }
+                    const doc = this.childDocs[0];
+                    doc.x = e.clientX;
+                    doc.y = e.clientY;
+                    this.props.addDocTab(doc, "inParent") && this.props.removeDocument(doc);
+                    dist = 0;
+                }
+            }
+            return false;
+        }, () => {
+            this._undoBatch?.end();
+            this._undoBatch = undefined;
+            if (!this.childDocs.length) {
+                this.props.ContainingCollectionView?.removeDocument(this.props.Document);
+            }
+        }, emptyFunction, false, this.layoutEngine() === "pass" && this.props.isSelected(true)); // this sets _doubleTap
     }
 
     onClick = (e: React.MouseEvent) => {
@@ -81,10 +108,11 @@ export class CollectionPileView extends CollectionSubView(doc => doc) {
             SelectionManager.DeselectAll();
             this.toggleStarburst();
             e.stopPropagation();
-        } else if (this.layoutEngine() === "pass") {
-            runInAction(() => this._contentsActive = false);
-            setTimeout(action(() => this._contentsActive = true), 300);
         }
+        // else if (this.layoutEngine() === "pass") {
+        //     runInAction(() => this._contentsActive = false);
+        //     setTimeout(action(() => this._contentsActive = true), 300);
+        // }
     }
 
     render() {
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index f82c6d8a6..2bd37bda5 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -225,7 +225,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
         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
 
             this.setupViewTypes("Change Perspective...", (vtype => { this.props.Document._viewType = vtype; return this.props.Document; }), true);
-            this.setupViewTypes("New Perspective...", vtype => {
+            this.setupViewTypes("Add a Perspective...", vtype => {
                 const newRendition = Doc.MakeAlias(this.props.Document);
                 newRendition._viewType = vtype;
                 this.props.addDocTab(newRendition, "onRight");
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index af9d3c5be..6a3764bb7 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -821,18 +821,18 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
 
             const savedState = { px: this.Document._panX, py: this.Document._panY, s: this.Document.scale, pt: this.Document.panTransformType };
 
-            if (!willZoom && DocumentView._focusHack.length) {
-                Doc.BrushDoc(this.props.Document);
-                !doc.z && NumCast(this.layoutDoc.scale) < 1 && this.scaleAtPt(DocumentView._focusHack, 1); // [NumCast(doc.x), NumCast(doc.y)], 1);
-            } else {
-                if (DocListCast(this.dataDoc[this.props.fieldKey]).includes(doc)) {
-                    if (!doc.z) this.setPan(newPanX, newPanY, "Ease", true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow
-                }
-                Doc.BrushDoc(this.props.Document);
-                this.props.focus(this.props.Document);
-                willZoom && this.setScaleToZoom(layoutdoc, scale);
+            // if (!willZoom && DocumentView._focusHack.length) {
+            //     Doc.BrushDoc(this.props.Document);
+            //     !doc.z && NumCast(this.layoutDoc.scale) < 1 && this.scaleAtPt(DocumentView._focusHack, 1); // [NumCast(doc.x), NumCast(doc.y)], 1);
+            // } else {
+            if (DocListCast(this.dataDoc[this.props.fieldKey]).includes(doc)) {
+                if (!doc.z) this.setPan(newPanX, newPanY, "Ease", true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow
             }
+            Doc.BrushDoc(this.props.Document);
+            this.props.focus(this.props.Document);
+            willZoom && this.setScaleToZoom(layoutdoc, scale);
             Doc.linkFollowHighlight(doc);
+            //}
 
             afterFocus && setTimeout(() => {
                 if (afterFocus?.()) {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 06bd40992..f9d9ca9a1 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -234,6 +234,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             const [left, top] = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint(0, 0);
             dragData.offset = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).transformDirection(x - left, y - top);
             dragData.dropAction = dropAction;
+            dragData.removeDocument = this.props.removeDocument;
             dragData.moveDocument = this.props.moveDocument;//  this.Document.onDragStart ? undefined : this.props.moveDocument;
             dragData.dragDivName = this.props.dragDivName;
             DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, { hideSource: !dropAction && !this.Document.onDragStart });
@@ -309,19 +310,19 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             } else if (this.Document["onClick-rawScript"] && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes("ScriptingBox")) {// bcz: hack? don't edit a script if you're clicking on a scripting box itself
                 UndoManager.RunInBatch(() => Doc.makeCustomViewClicked(this.props.Document, undefined, "onClick"), "edit onClick");
                 //ScriptBox.EditButtonScript("On Button Clicked ...", this.props.Document, "onClick", e.clientX, e.clientY), "on button click");
-            } else if (this.Document.isLinkButton) {
+            } else if (this.Document.isLinkButton && !e.shiftKey && !e.ctrlKey) {
                 DocListCast(this.props.Document.links).length && this.followLinkClick(e.altKey, e.ctrlKey, e.shiftKey);
             } else {
                 if ((this.props.Document.onDragStart || (this.props.Document.rootDocument)) && !(e.ctrlKey || e.button > 0)) {  // onDragStart implies a button doc that we don't want to select when clicking.   RootDocument & isTEmplaetForField implies we're clicking on part of a template instance and we want to select the whole template, not the part
                     stopPropagate = false; // don't stop propagation for field templates -- want the selection to propagate up to the root document of the template
                 } else {
-                    if (this.props.Document.type === DocumentType.RTF) {
-                        DocumentView._focusHack = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY) || [0, 0];
-                        DocumentView._focusHack = [DocumentView._focusHack[0] + NumCast(this.props.Document.x), DocumentView._focusHack[1] + NumCast(this.props.Document.y)];
+                    // if (this.props.Document.type === DocumentType.RTF) {
+                    //     DocumentView._focusHack = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY) || [0, 0];
+                    //     DocumentView._focusHack = [DocumentView._focusHack[0] + NumCast(this.props.Document.x), DocumentView._focusHack[1] + NumCast(this.props.Document.y)];
 
-                        this.props.focus(this.props.Document, false);
-                    }
-                    SelectionManager.SelectDoc(this, e.ctrlKey);
+                    //     this.props.focus(this.props.Document, false);
+                    // }
+                    SelectionManager.SelectDoc(this, e.ctrlKey || e.shiftKey);
                 }
                 preventDefault = false;
             }
@@ -329,7 +330,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             preventDefault && e.preventDefault();
         }
     }
-    static _focusHack: number[] = []; // bcz :this will get fixed...
 
     // follows a link - if the target is on screen, it highlights/pans to it.
     // if the target isn't onscreen, then it will open up the target in a tab, on the right, or in place
@@ -344,11 +344,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     finished && setTimeout(finished, 0); // finished() needs to be called right after hackToCallFinishAfterFocus(), but there's no callback for that so we use the hacky timeout.  
                     return false; // we must return false here so that the zoom to the document is not reversed.  If it weren't for needing to call finished(), we wouldn't need this function at all since not having it is equivalent to returning false
                 };
-                this.props.addDocTab(doc, where) && this.props.focus(doc, true, undefined, hackToCallFinishAfterFocus); //  add the target and focus on it.
+                this.props.addDocTab(doc, where) && this.props.focus(doc, BoolCast(this.Document.followLinkZoom, true), undefined, hackToCallFinishAfterFocus); //  add the target and focus on it.
                 return where !== "inPlace"; // return true to reset the initial focus&zoom (return false for 'inPlace' since resetting the initial focus&zoom will negate the zoom into the target)
             };
             // first focus & zoom onto this (the clicked document).  Then execute the function to focus on the target
-            this.props.focus(this.props.Document, true, 1, targetFocusAfterDocFocus);
+            this.props.focus(this.props.Document, BoolCast(this.Document.followLinkZoom, true), 1, targetFocusAfterDocFocus);
         };
         await DocumentManager.Instance.FollowLink(undefined, this.props.Document, createViewFunc, shiftKey, this.props.ContainingCollectionDoc, batch.end, altKey ? true : undefined);
     }
@@ -569,6 +569,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             this.Document.onClick = undefined;
         } else {
             this.Document.isLinkButton = true;
+            this.Document.followLinkZoom = false;
             this.Document.followLinkLocation = undefined;
         }
     }
@@ -579,10 +580,24 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             this.Document.isLinkButton = false;
         } else {
             this.Document.isLinkButton = true;
+            this.Document.followLinkZoom = true;
             this.Document.followLinkLocation = "inPlace";
         }
     }
 
+    @undoBatch
+    toggleFollowOnRight = (): void => {
+        if (this.Document.isLinkButton) {
+            this.Document.isLinkButton = false;
+        } else {
+            this.Document.isLinkButton = true;
+            this.Document.followLinkZoom = false;
+            const first = DocListCast(this.Document.links).find(d => d instanceof Doc);
+            first && (first.hidden = true);
+            this.Document.followLinkLocation = "onRight";
+        }
+    }
+
     @undoBatch
     @action
     drop = async (e: Event, de: DragManager.DropEvent) => {
@@ -679,20 +694,27 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             cm.addItem({ description: item.label, event: () => item.script.script.run({ this: this.layoutDoc, self: this.rootDoc }), icon: "sticky-note" }));
 
 
-        const existing = cm.findByDescription("Options...");
-        const layoutItems: ContextMenuProps[] = existing && "subitems" in existing ? existing.subitems : [];
-        layoutItems.push({ description: `${this.Document._chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.Document._chromeStatus = (this.Document._chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" });
-        layoutItems.push({ description: `${this.Document._autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" });
-        layoutItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" });
-        layoutItems.push({ description: this.Document.lockedTransform ? "Unlock Transform" : "Lock Transform", event: this.toggleLockTransform, icon: BoolCast(this.Document.lockedTransform) ? "unlock" : "lock" });
-        !existing && cm.addItem({ description: "Options...", subitems: layoutItems, icon: "compass" });
-
-        const open = cm.findByDescription("New Perspective...");
+        let open = cm.findByDescription("Add a Perspective...");
         const openItems: ContextMenuProps[] = open && "subitems" in open ? open.subitems : [];
         openItems.push({ description: "Open Fields     ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "onRight"), icon: "layer-group" });
         templateDoc && openItems.push({ description: "Open Template   ", event: () => this.props.addDocTab(templateDoc, "onRight"), icon: "eye" });
-        !open && cm.addItem({ description: "New Perspective...", subitems: openItems, icon: "external-link-alt" });
+        if (!open) {
+            open = { description: "Add a Perspective....", subitems: openItems, icon: "external-link-alt" };
+            cm.addItem(open);
+        }
+
+        let options = cm.findByDescription("Options...");
+        const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : [];
+        optionItems.push({ description: `${this.Document._chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.Document._chromeStatus = (this.Document._chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" });
+        optionItems.push({ description: `${this.Document._autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" });
+        optionItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" });
+        optionItems.push({ description: this.Document.lockedTransform ? "Unlock Transform" : "Lock Transform", event: this.toggleLockTransform, icon: BoolCast(this.Document.lockedTransform) ? "unlock" : "lock" });
+        if (!options) {
+            options = { description: "Options...", subitems: optionItems, icon: "compass" };
+            cm.addItem(options);
+        }
 
+        cm.moveAfter(options, open);
 
         const existingOnClick = cm.findByDescription("OnClick...");
         const onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
@@ -700,6 +722,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         onClicks.push({ description: "Toggle Detail", event: () => this.Document.onClick = ScriptField.MakeScript(`toggleDetail(self, "${this.props.Document.layoutKey}")`), icon: "window-restore" });
         onClicks.push({ description: this.Document.ignoreClick ? "Select" : "Do Nothing", event: () => this.Document.ignoreClick = !this.Document.ignoreClick, icon: this.Document.ignoreClick ? "unlock" : "lock" });
         onClicks.push({ description: this.Document.isLinkButton ? "Remove Follow Behavior" : "Follow Link in Place", event: this.toggleFollowInPlace, icon: "concierge-bell" });
+        onClicks.push({ description: this.Document.isLinkButton ? "Remove Follow Behavior" : "Follow Link on Right", event: this.toggleFollowOnRight, icon: "concierge-bell" });
         onClicks.push({ description: this.Document.isLinkButton || this.Document.onClick ? "Remove Click Behavior" : "Follow Link", event: this.toggleLinkButtonBehavior, icon: "concierge-bell" });
         onClicks.push({ description: "Edit onClick Script", event: () => UndoManager.RunInBatch(() => Doc.makeCustomViewClicked(this.props.Document, undefined, "onClick"), "edit onClick"), icon: "edit" });
         !existingOnClick && cm.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index a018f17e8..d7cfbae9f 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -444,7 +444,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         changeItems.push({ description: "FreeForm", event: undoBatch(() => Doc.makeCustomViewClicked(this.rootDoc, Docs.Create.FreeformDocument, "freeform"), "change view"), icon: "eye" });
         !change && cm.addItem({ description: "Change Perspective...", subitems: changeItems, icon: "external-link-alt" });
 
-        const open = cm.findByDescription("New Perspective...");
+        const open = cm.findByDescription("Add a Perspective...");
         const openItems: ContextMenuProps[] = open && "subitems" in open ? open.subitems : [];
 
         openItems.push({
@@ -454,7 +454,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                 this.props.addDocTab(alias, "onRight");
             }), icon: "eye"
         });
-        !open && cm.addItem({ description: "New Perspective...", subitems: openItems, icon: "external-link-alt" });
+        !open && cm.addItem({ description: "Add a Perspective...", subitems: openItems, icon: "external-link-alt" });
 
     }
 
diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx
index 41df5b3c1..a9f76ae8f 100644
--- a/src/client/views/nodes/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/FormattedTextBoxComment.tsx
@@ -88,7 +88,6 @@ export class FormattedTextBoxComment {
                     if (FormattedTextBoxComment.linkDoc.type !== DocumentType.LINK) {
                         textBox.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "inTab" : "onRight");
                     } else {
-                        DocumentView._focusHack = [];
                         DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document,
                             (doc: Doc, followLinkLocation: string) => textBox.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation));
                     }
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index eb647d0e4..707b9d12a 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -75,7 +75,6 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps, LinkAnch
             anchorContainerDoc && this.props.bringToFront(anchorContainerDoc, false);
             if (anchorContainerDoc && !this.layoutDoc.onClick && !this._isOpen) {
                 this._timeout = setTimeout(action(() => {
-                    DocumentView._focusHack = [];
                     DocumentManager.Instance.FollowLink(this.rootDoc, anchorContainerDoc, document => this.props.addDocTab(document, StrCast(this.layoutDoc.linkOpenLocation, "inTab")), false);
                     this._editing = false;
                 }), 300 - (Date.now() - this._lastTap));
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index eaf80d252..672d3adb8 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -98,7 +98,6 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
         else if (e.button === 0) {
             const annoGroup = await Cast(this.props.document.group, Doc);
             if (annoGroup) {
-                DocumentView._focusHack = [];
                 DocumentManager.Instance.FollowLink(undefined, annoGroup, (doc, followLinkLocation) => this.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation), false, undefined);
                 e.stopPropagation();
             }
-- 
cgit v1.2.3-70-g09d2


From c6d8ac7eef3480a7e19687b066456d9d34519587 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 25 Apr 2020 20:25:50 -0400
Subject: several fixes for performance and for working with isBackground
 objects.

---
 .../CollectionStackingViewFieldColumn.tsx           |  2 +-
 src/client/views/collections/CollectionView.tsx     |  2 +-
 .../CollectionFreeFormLayoutEngines.tsx             |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx   | 21 ++++++++++++++-------
 .../views/nodes/CollectionFreeFormDocumentView.tsx  |  4 ++--
 src/client/views/nodes/DocumentView.tsx             | 21 ++++++++++++++-------
 src/client/views/nodes/ImageBox.scss                |  9 +--------
 src/client/views/nodes/ImageBox.tsx                 |  8 ++++----
 src/new_fields/util.ts                              |  2 +-
 9 files changed, 39 insertions(+), 32 deletions(-)

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

diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 5d926b7c7..8992de9d1 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -364,7 +364,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
             <div className="collectionStackingViewFieldColumn" key={heading}
                 style={{
                     width: `${100 / ((uniqueHeadings.length + ((chromeStatus !== 'view-mode' && chromeStatus !== 'disabled') ? 1 : 0)) || 1)}%`,
-                    height: SelectionManager.GetIsDragging() ? "100%" : undefined,
+                    height: undefined, // SelectionManager.GetIsDragging() ? "100%" : undefined,
                     background: this._background
                 }}
                 ref={this.createColumnDropRef} onPointerEnter={this.pointerEntered} onPointerLeave={this.pointerLeave}>
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 2bd37bda5..801704673 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -493,7 +493,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
             {!this.props.isSelected() || this.props.PanelHeight() < 100 || this.props.Document.hideFilterView ? (null) :
                 <div className="collectionTimeView-dragger" title="library View Dragger" onPointerDown={this.onPointerDown} style={{ right: this.facetWidth() - 10 }} />
             }
-            {this.filterView}
+            {this.facetWidth() < 10 ? (null) : this.filterView}
         </div>);
     }
 }
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 9ca747113..9a864078a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -39,7 +39,7 @@ export interface PoolData {
     color?: string;
     transition?: string;
     highlight?: boolean;
-    replica?: string;
+    replica: string;
     pair: { layout: Doc, data?: Doc };
 }
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 6a3764bb7..a13c81a15 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -508,7 +508,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         if (this.layoutDoc.targetScale && (Math.abs(e.pageX - this._downX) < 3 && Math.abs(e.pageY - this._downY) < 3)) {
             if (Date.now() - this._lastTap < 300) {
                 const docpt = this.getTransform().transformPoint(e.clientX, e.clientY);
-                this.scaleAtPt(docpt, NumCast(this.layoutDoc.targetScale, NumCast(this.layoutDoc.scale)));
+                this.scaleAtPt(docpt, 1);
                 e.stopPropagation();
                 e.preventDefault();
             }
@@ -853,7 +853,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     @computed get libraryPath() { return this.props.LibraryPath ? [...this.props.LibraryPath, this.props.Document] : []; }
     @computed get onChildClickHandler() { return this.props.childClickScript || ScriptCast(this.Document.onChildClick); }
     backgroundHalo = () => BoolCast(this.Document.useClusters);
-
+    @computed get backgroundActive() { return this.layoutDoc.isBackground && (this.props.ContainingCollectionView?.active() || this.props.active()); }
+    parentActive = () => this.props.active() || this.backgroundActive ? true : false;
     getChildDocumentViewProps(childLayout: Doc, childData?: Doc): DocumentViewProps {
         return {
             ...this.props,
@@ -879,7 +880,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             focus: this.focusDocument,
             backgroundColor: this.getClusterColor,
             backgroundHalo: this.backgroundHalo,
-            parentActive: this.props.active,
+            parentActive: this.parentActive,
             bringToFront: this.bringToFront,
             addDocTab: this.addDocTab,
         };
@@ -946,7 +947,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         }
     }
 
-    childDataProvider = computedFn(function childDataProvider(this: any, doc: Doc, replica: Opt<string>) {
+    childDataProvider = computedFn(function childDataProvider(this: any, doc: Doc, replica: string) {
         return this._layoutPoolData.get(doc[Id] + (replica || ""));
     }.bind(this));
 
@@ -975,6 +976,9 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     }
 
     @computed get doInternalLayoutComputation() {
+        TraceMobx();
+
+
         const newPool = new Map<string, PoolData>();
         const engine = this.props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine);
         switch (engine) {
@@ -1009,8 +1013,10 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                     replica={entry[1].replica}
                     dataProvider={this.childDataProvider}
                     LayoutDoc={this.childLayoutDocFunc}
-                    pointerEvents={this.props.viewDefDivClick || (engine === "pass" && !this.props.isSelected(true)) ?
-                        false : undefined}
+                    pointerEvents={
+                        this.backgroundActive ?
+                            true :
+                            (this.props.viewDefDivClick || (engine === "pass" && !this.props.isSelected(true))) ? false : undefined}
                     jitterRotation={NumCast(this.props.Document._jitterRotation)}
                     fitToBox={this.props.fitToBox || BoolCast(this.props.freezeChildDimensions)}
                     FreezeDimensions={BoolCast(this.props.freezeChildDimensions)}
@@ -1164,6 +1170,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         const wscale = nw ? this.props.PanelWidth() / nw : 1;
         return wscale < hscale ? wscale : hscale;
     }
+    @computed get backgroundEvents() { return this.layoutDoc.isBackground && SelectionManager.GetIsDragging(); }
     render() {
         TraceMobx();
         const clientRect = this._mainCont?.getBoundingClientRect();
@@ -1179,7 +1186,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             onWheel={this.onPointerWheel} onClick={this.onClick}  //pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined,
             onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onExternalDrop.bind(this)} onContextMenu={this.onContextMenu}
             style={{
-                pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined,
+                pointerEvents: this.backgroundEvents ? "all" : undefined,
                 transform: this.contentScaling ? `scale(${this.contentScaling})` : "",
                 transformOrigin: this.contentScaling ? "left top" : "",
                 width: this.contentScaling ? `${100 / this.contentScaling}%` : "",
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index baeac311e..7ef8e43f7 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -12,7 +12,7 @@ import { TraceMobx } from "../../../new_fields/util";
 import { ContentFittingDocumentView } from "./ContentFittingDocumentView";
 
 export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
-    dataProvider?: (doc: Doc, replica?: string) => { x: number, y: number, zIndex?: number, highlight?: boolean, width: number, height: number, z: number, transition?: string, replica: string } | undefined;
+    dataProvider?: (doc: Doc, replica: string) => { x: number, y: number, zIndex?: number, highlight?: boolean, width: number, height: number, z: number, transition?: string, replica: string } | undefined;
     x?: number;
     y?: number;
     z?: number;
@@ -23,7 +23,7 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
     jitterRotation: number;
     transition?: string;
     fitToBox?: boolean;
-    replica?: string;
+    replica: string;
 }
 
 @observer
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index f9d9ca9a1..7ae77fc65 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -281,12 +281,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         }
     }
 
-    onClick = (e: React.MouseEvent | React.PointerEvent) => {
+    onClick = action((e: React.MouseEvent | React.PointerEvent) => {
         if (!e.nativeEvent.cancelBubble && !this.Document.ignoreClick &&
             (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) {
             let stopPropagate = true;
             let preventDefault = true;
-            this.props.Document.isBackground === undefined && this.props.bringToFront(this.props.Document);
+            !this.props.Document.isBackground && this.props.bringToFront(this.props.Document);
             if (this._doubleTap && this.props.renderDepth && !this.onClickHandler?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click
                 if (!(e.nativeEvent as any).formattedHandled) {
                     const fullScreenAlias = Doc.MakeAlias(this.props.Document);
@@ -329,7 +329,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             stopPropagate && e.stopPropagation();
             preventDefault && e.preventDefault();
         }
-    }
+    });
 
     // follows a link - if the target is on screen, it highlights/pans to it.
     // if the target isn't onscreen, then it will open up the target in a tab, on the right, or in place
@@ -347,8 +347,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 this.props.addDocTab(doc, where) && this.props.focus(doc, BoolCast(this.Document.followLinkZoom, true), undefined, hackToCallFinishAfterFocus); //  add the target and focus on it.
                 return where !== "inPlace"; // return true to reset the initial focus&zoom (return false for 'inPlace' since resetting the initial focus&zoom will negate the zoom into the target)
             };
-            // first focus & zoom onto this (the clicked document).  Then execute the function to focus on the target
-            this.props.focus(this.props.Document, BoolCast(this.Document.followLinkZoom, true), 1, targetFocusAfterDocFocus);
+            if (!this.Document.followLinkZoom) {
+                targetFocusAfterDocFocus();
+            } else {
+                // first focus & zoom onto this (the clicked document).  Then execute the function to focus on the target
+                this.props.focus(this.props.Document, BoolCast(this.Document.followLinkZoom, true), 1, targetFocusAfterDocFocus);
+            }
         };
         await DocumentManager.Instance.FollowLink(undefined, this.props.Document, createViewFunc, shiftKey, this.props.ContainingCollectionDoc, batch.end, altKey ? true : undefined);
     }
@@ -642,6 +646,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             const portal = Docs.Create.FreeformDocument([], { _width: NumCast(this.layoutDoc._width) + 10, _height: NumCast(this.layoutDoc._height), title: StrCast(this.props.Document.title) + ".portal" });
             DocUtils.MakeLink({ doc: this.props.Document }, { doc: portal }, "portal to");
         }
+        this.Document.followLinkZoom = true;
         this.Document.isLinkButton = true;
     }
 
@@ -1069,7 +1074,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         const titleView = (!showTitle ? (null) :
             <div className={`documentView-titleWrapper${showTitleHover ? "-hover" : ""}`} style={{
                 position: showTextTitle ? "relative" : "absolute",
-                pointerEvents: SelectionManager.GetIsDragging() || this.onClickHandler || this.Document.ignoreClick ? "none" : undefined,
+                pointerEvents: this.onClickHandler || this.Document.ignoreClick ? "none" : undefined,
             }}>
                 <EditableView ref={this._titleRef}
                     contents={(this.props.DataDoc || this.props.Document)[showTitle]?.toString()}
@@ -1089,7 +1094,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             </div>;
     }
     @computed get ignorePointerEvents() {
-        return this.props.pointerEvents === false || (this.Document.isBackground && !this.isSelected() && !SelectionManager.GetIsDragging()) || (this.Document.type === DocumentType.INK && InkingControl.Instance.selectedTool !== InkTool.None);
+        return this.props.pointerEvents === false ||
+            (this.Document.isBackground && !this.isSelected() && !SelectionManager.GetIsDragging()) ||
+            (this.Document.type === DocumentType.INK && InkingControl.Instance.selectedTool !== InkTool.None);
     }
     @undoBatch
     @action
diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss
index 49425c2c2..15148d01d 100644
--- a/src/client/views/nodes/ImageBox.scss
+++ b/src/client/views/nodes/ImageBox.scss
@@ -1,5 +1,4 @@
-.imageBox,
-.imageBox-dragging {
+.imageBox {
     border-radius: inherit;
     width: 100%;
     height: 100%;
@@ -11,12 +10,6 @@
     }
 }
 
-.imageBox-dragging {
-    .imageBox-fader {
-        pointer-events: none;
-    }
-}
-
 #upload-icon {
     position: absolute;
     bottom: 0;
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index e189f5814..14b367868 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -360,7 +360,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
     }
 
     @computed get nativeSize() {
-        const pw = typeof this.props.PanelWidth === "function" ? this.props.PanelWidth() : typeof this.props.PanelWidth === "number" ? (this.props.PanelWidth as any) as number : 50;
+        TraceMobx();
+        const pw = this.props.PanelWidth?.() || 50;
         const nativeWidth = NumCast(this.dataDoc[this.fieldKey + "-nativeWidth"], pw);
         const nativeHeight = NumCast(this.dataDoc[this.fieldKey + "-nativeHeight"], 1);
         return { nativeWidth, nativeHeight };
@@ -374,7 +375,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
     @computed get paths() {
         const field = Cast(this.dataDoc[this.fieldKey], ImageField, null); // retrieve the primary image URL that is being rendered from the data doc
         const alts = DocListCast(this.dataDoc[this.fieldKey + "-alternates"]); // retrieve alternate documents that may be rendered as alternate images
-        const altpaths = alts.map(doc => Cast(doc[Doc.LayoutFieldKey(doc)], ImageField, null)?.url.href).filter(url => url); // access the primary layout data of the alternate documents
+        const altpaths = alts.map(doc => Cast(doc[Doc.LayoutFieldKey(doc)], ImageField, null)?.url).filter(url => url).map(url => this.choosePath(url)); // access the primary layout data of the alternate documents
         const paths = field ? [this.choosePath(field.url), ...altpaths] : altpaths;
         return paths.length ? paths : [Utils.CorsProxy("http://www.cs.brown.edu/~bcz/noImage.png")];
     }
@@ -438,8 +439,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
     contentFunc = () => [this.content];
     render() {
         TraceMobx();
-        const dragging = !SelectionManager.GetIsDragging() ? "" : "-dragging";
-        return (<div className={`imageBox${dragging}`} onContextMenu={this.specificContextMenu}
+        return (<div className={`imageBox`} onContextMenu={this.specificContextMenu}
             style={{
                 transform: this.props.PanelWidth() ? `translate(0px, ${this.ycenter}px)` : `scale(${this.props.ContentScaling()})`,
                 width: this.props.PanelWidth() ? undefined : `${100 / this.props.ContentScaling()}%`,
diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts
index 8c719ccd8..480a55da0 100644
--- a/src/new_fields/util.ts
+++ b/src/new_fields/util.ts
@@ -12,7 +12,7 @@ function _readOnlySetter(): never {
     throw new Error("Documents can't be modified in read-only mode");
 }
 
-const tracing = false;
+const tracing = true;
 export function TraceMobx() {
     tracing && trace();
 }
-- 
cgit v1.2.3-70-g09d2


From e66812a1e19023d777d90ee382cb3849c5f63fc0 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 25 Apr 2020 20:59:28 -0400
Subject: added a sizeProvider computedFn for efficiency since size doesn't
 change as often as position

---
 .../collectionFreeForm/CollectionFreeFormView.tsx          | 14 +++++++++++---
 src/client/views/nodes/CollectionFreeFormDocumentView.tsx  | 12 +++++++-----
 src/new_fields/util.ts                                     |  2 +-
 3 files changed, 19 insertions(+), 9 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index a13c81a15..6a6879fd4 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -86,6 +86,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     private _hitCluster = false;
     private _layoutComputeReaction: IReactionDisposer | undefined;
     private _layoutPoolData = new ObservableMap<string, PoolData>();
+    private _layoutSizeData = new ObservableMap<string, { width?: number, height?: number }>();
     private _cachedPool: Map<string, PoolData> = new Map();
     @observable private _pullCoords: number[] = [0, 0];
     @observable private _pullDirection: string = "";
@@ -903,13 +904,13 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     getCalculatedPositions(params: { pair: { layout: Doc, data?: Doc }, index: number, collection: Doc, docs: Doc[], state: any }): PoolData {
         const result = this.Document.arrangeScript?.script.run(params, console.log);
         if (result?.success) {
-            return { x: 0, y: 0, transition: "transform 1s", ...result, pair: params.pair };
+            return { x: 0, y: 0, transition: "transform 1s", ...result, pair: params.pair, replica: "" };
         }
         const layoutDoc = Doc.Layout(params.pair.layout);
         const { x, y, z, color, zIndex } = params.pair.layout;
         return {
             x: NumCast(x), y: NumCast(y), z: Cast(z, "number"), color: StrCast(color), zIndex: Cast(zIndex, "number"),
-            width: Cast(layoutDoc._width, "number"), height: Cast(layoutDoc._height, "number"), pair: params.pair
+            width: Cast(layoutDoc._width, "number"), height: Cast(layoutDoc._height, "number"), pair: params.pair, replica: ""
         };
     }
 
@@ -950,6 +951,9 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     childDataProvider = computedFn(function childDataProvider(this: any, doc: Doc, replica: string) {
         return this._layoutPoolData.get(doc[Id] + (replica || ""));
     }.bind(this));
+    childSizeProvider = computedFn(function childSizeProvider(this: any, doc: Doc, replica: string) {
+        return this._layoutSizeData.get(doc[Id] + (replica || ""));
+    }.bind(this));
 
     doEngineLayout(poolData: Map<string, PoolData>,
         engine: (
@@ -997,9 +1001,12 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             Array.from(newPool.entries()).map(entry => {
                 const lastPos = this._cachedPool.get(entry[0]); // last computed pos
                 const newPos = entry[1];
-                if (!lastPos || newPos.x !== lastPos.x || newPos.y !== lastPos.y || newPos.z !== lastPos.z || newPos.zIndex !== lastPos.zIndex || newPos.width !== lastPos.width || newPos.height !== lastPos.height) {
+                if (!lastPos || newPos.x !== lastPos.x || newPos.y !== lastPos.y || newPos.z !== lastPos.z || newPos.zIndex !== lastPos.zIndex) {
                     this._layoutPoolData.set(entry[0], newPos);
                 }
+                if (!lastPos || newPos.height !== lastPos.height) {
+                    this._layoutSizeData.set(entry[0], { width: newPos.width, height: newPos.height });
+                }
             }));
         this._cachedPool.clear();
         Array.from(newPool.entries()).forEach(k => this._cachedPool.set(k[0], k[1]));
@@ -1012,6 +1019,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                     {...this.getChildDocumentViewProps(entry[1].pair.layout, entry[1].pair.data)}
                     replica={entry[1].replica}
                     dataProvider={this.childDataProvider}
+                    sizeProvider={this.childSizeProvider}
                     LayoutDoc={this.childLayoutDocFunc}
                     pointerEvents={
                         this.backgroundActive ?
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 7ef8e43f7..1c7d116c5 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -12,7 +12,8 @@ import { TraceMobx } from "../../../new_fields/util";
 import { ContentFittingDocumentView } from "./ContentFittingDocumentView";
 
 export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
-    dataProvider?: (doc: Doc, replica: string) => { x: number, y: number, zIndex?: number, highlight?: boolean, width: number, height: number, z: number, transition?: string, replica: string } | undefined;
+    dataProvider?: (doc: Doc, replica: string) => { x: number, y: number, zIndex?: number, highlight?: boolean, z: number, transition?: string } | undefined;
+    sizeProvider?: (doc: Doc, replica: string) => { width: number, height: number } | undefined;
     x?: number;
     y?: number;
     z?: number;
@@ -41,13 +42,14 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     get Y() { return this.renderScriptDim ? this.renderScriptDim.y : this.props.y !== undefined ? this.props.y : this.dataProvider ? this.dataProvider.y : (this.Document.y || 0); }
     get ZInd() { return this.dataProvider ? this.dataProvider.zIndex : (this.Document.zIndex || 0); }
     get Highlight() { return this.dataProvider?.highlight; }
-    get width() { return this.renderScriptDim ? this.renderScriptDim.width : this.props.width !== undefined ? this.props.width : this.props.dataProvider && this.dataProvider ? this.dataProvider.width : this.layoutDoc[WidthSym](); }
+    get width() { return this.renderScriptDim ? this.renderScriptDim.width : this.props.width !== undefined ? this.props.width : this.props.sizeProvider && this.sizeProvider ? this.sizeProvider.width : this.layoutDoc[WidthSym](); }
     get height() {
-        const hgt = this.renderScriptDim ? this.renderScriptDim.height : this.props.height !== undefined ? this.props.height : this.props.dataProvider && this.dataProvider ? this.dataProvider.height : this.layoutDoc[HeightSym]();
+        const hgt = this.renderScriptDim ? this.renderScriptDim.height : this.props.height !== undefined ? this.props.height : this.props.sizeProvider && this.sizeProvider ? this.sizeProvider.height : this.layoutDoc[HeightSym]();
         return (hgt === undefined && this.nativeWidth && this.nativeHeight) ? this.width * this.nativeHeight / this.nativeWidth : hgt;
     }
     @computed get freezeDimensions() { return this.props.FreezeDimensions; }
     @computed get dataProvider() { return this.props.dataProvider?.(this.props.Document, this.props.replica); }
+    @computed get sizeProvider() { return this.props.sizeProvider?.(this.props.Document, this.props.replica); }
     @computed get nativeWidth() { return NumCast(this.layoutDoc._nativeWidth, this.props.NativeWidth() || (this.freezeDimensions ? this.layoutDoc[WidthSym]() : 0)); }
     @computed get nativeHeight() { return NumCast(this.layoutDoc._nativeHeight, this.props.NativeHeight() || (this.freezeDimensions ? this.layoutDoc[HeightSym]() : 0)); }
 
@@ -71,8 +73,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     }
 
     contentScaling = () => this.nativeWidth > 0 && !this.props.fitToBox && !this.freezeDimensions ? this.width / this.nativeWidth : 1;
-    panelWidth = () => (this.dataProvider?.width || this.props.PanelWidth?.());
-    panelHeight = () => (this.dataProvider?.height || this.props.PanelHeight?.());
+    panelWidth = () => (this.sizeProvider?.width || this.props.PanelWidth?.());
+    panelHeight = () => (this.sizeProvider?.height || this.props.PanelHeight?.());
     getTransform = (): Transform => this.props.ScreenToLocalTransform()
         .translate(-this.X, -this.Y)
         .scale(1 / this.contentScaling())
diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts
index 480a55da0..8c719ccd8 100644
--- a/src/new_fields/util.ts
+++ b/src/new_fields/util.ts
@@ -12,7 +12,7 @@ function _readOnlySetter(): never {
     throw new Error("Documents can't be modified in read-only mode");
 }
 
-const tracing = true;
+const tracing = false;
 export function TraceMobx() {
     tracing && trace();
 }
-- 
cgit v1.2.3-70-g09d2


From 94ecd9c794ca27760e7885d2dc3bc33b928968e7 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 25 Apr 2020 21:53:40 -0400
Subject: from last

---
 .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx     | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 6a6879fd4..852442fd4 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1004,7 +1004,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                 if (!lastPos || newPos.x !== lastPos.x || newPos.y !== lastPos.y || newPos.z !== lastPos.z || newPos.zIndex !== lastPos.zIndex) {
                     this._layoutPoolData.set(entry[0], newPos);
                 }
-                if (!lastPos || newPos.height !== lastPos.height) {
+                if (!lastPos || newPos.height !== lastPos.height || newPos.width !== lastPos.width) {
                     this._layoutSizeData.set(entry[0], { width: newPos.width, height: newPos.height });
                 }
             }));
-- 
cgit v1.2.3-70-g09d2


From e6549d43be83b50d364c4a3ab1e8e7381ce2fd59 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sun, 26 Apr 2020 01:56:41 -0400
Subject: fixed title layout for text.  fixed documentBox for nested
 collections.  fixed tree view for text boxes. changed labelbox formatting.

---
 src/client/util/DragManager.ts                              |  8 ++++----
 src/client/views/collections/CollectionTreeView.tsx         | 13 +++++++++----
 .../collectionFreeForm/CollectionFreeFormView.tsx           |  3 ++-
 src/client/views/nodes/ColorBox.scss                        |  1 +
 src/client/views/nodes/DocumentBox.tsx                      |  4 ++--
 src/client/views/nodes/DocumentView.tsx                     |  7 ++-----
 src/client/views/nodes/FormattedTextBox.scss                |  1 -
 src/client/views/nodes/FormattedTextBox.tsx                 |  4 ++--
 src/client/views/nodes/LabelBox.scss                        |  2 --
 src/client/views/nodes/LabelBox.tsx                         |  8 +++++++-
 src/server/authentication/models/current_user_utils.ts      |  4 ++--
 11 files changed, 31 insertions(+), 24 deletions(-)

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

diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 577366cd4..35694a6bd 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -180,7 +180,7 @@ export namespace DragManager {
     export function MakeDropTarget(
         element: HTMLElement,
         dropFunc: (e: Event, de: DropEvent) => void,
-        doc?:Doc
+        doc?: Doc
     ): DragDropDisposer {
         if ("canDrop" in element.dataset) {
             throw new Error(
@@ -189,12 +189,12 @@ export namespace DragManager {
         }
         element.dataset.canDrop = "true";
         const handler = (e: Event) => dropFunc(e, (e as CustomEvent<DropEvent>).detail);
-        const preDropHandler = (e:Event) => {
+        const preDropHandler = (e: Event) => {
             const de = (e as CustomEvent<DropEvent>).detail;
             if (de.complete.docDragData && doc?.targetDropAction) {
-                de.complete.docDragData!.dropAction = StrCast(doc.targetDropAction) as dropActionType;
+                de.complete.docDragData.dropAction = StrCast(doc.targetDropAction) as dropActionType;
             }
-        }
+        };
         element.addEventListener("dashOnDrop", handler);
         doc && element.addEventListener("dashPreDrop", preDropHandler);
         return () => {
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 8c5a4538e..ddd7474bd 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -261,7 +261,7 @@ class TreeView extends React.Component<TreeViewProps> {
     docHeight = () => {
         const layoutDoc = Doc.Layout(this.props.document);
         const bounds = this.boundsOfCollectionDocument;
-        return Math.min(this.MAX_EMBED_HEIGHT, (() => {
+        return Math.max(70, Math.min(this.MAX_EMBED_HEIGHT, (() => {
             const aspect = NumCast(layoutDoc._nativeHeight, layoutDoc._fitWidth ? 0 : layoutDoc[HeightSym]()) / NumCast(layoutDoc._nativeWidth, layoutDoc._fitWidth ? 1 : layoutDoc[WidthSym]());
             if (aspect) return this.docWidth() * aspect;
             if (bounds) return this.docWidth() * (bounds.b - bounds.y) / (bounds.r - bounds.x);
@@ -269,7 +269,7 @@ class TreeView extends React.Component<TreeViewProps> {
                 Math.min(this.docWidth() * NumCast(layoutDoc.scrollHeight, NumCast(layoutDoc._nativeHeight)) / NumCast(layoutDoc._nativeWidth,
                     NumCast(this.props.containingCollection._height)))) :
                 NumCast(layoutDoc._height) ? NumCast(layoutDoc._height) : 50;
-        })());
+        })()));
     }
 
     @computed get expandedField() {
@@ -321,6 +321,9 @@ class TreeView extends React.Component<TreeViewProps> {
         return rows;
     }
 
+    rtfWidth = () => Math.min(Doc.Layout(this.props.document)?.[WidthSym](), this.props.panelWidth() - 20);
+    rtfHeight = () => this.rtfWidth() < Doc.Layout(this.props.document)?.[WidthSym]() ? Math.min(Doc.Layout(this.props.document)?.[HeightSym](), this.MAX_EMBED_HEIGHT) : this.MAX_EMBED_HEIGHT;
+
     @computed get renderContent() {
         const expandKey = this.treeViewExpandedView === this.fieldKey ? this.fieldKey : this.treeViewExpandedView === "links" ? "links" : undefined;
         if (expandKey !== undefined) {
@@ -355,8 +358,10 @@ class TreeView extends React.Component<TreeViewProps> {
                     backgroundColor={this.props.backgroundColor}
                     fitToBox={this.boundsOfCollectionDocument !== undefined}
                     FreezeDimensions={true}
-                    PanelWidth={this.docWidth}
-                    PanelHeight={this.docHeight}
+                    NativeWidth={layoutDoc.type === DocumentType.RTF ? this.rtfWidth : undefined}
+                    NativeHeight={layoutDoc.type === DocumentType.RTF ? this.rtfHeight : undefined}
+                    PanelWidth={layoutDoc.type === DocumentType.RTF ? this.rtfWidth : this.docWidth}
+                    PanelHeight={layoutDoc.type === DocumentType.RTF ? this.rtfHeight : this.docHeight}
                     getTransform={this.docTransform}
                     CollectionDoc={this.props.containingCollection}
                     CollectionView={undefined}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 852442fd4..28b461313 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1026,7 +1026,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                             true :
                             (this.props.viewDefDivClick || (engine === "pass" && !this.props.isSelected(true))) ? false : undefined}
                     jitterRotation={NumCast(this.props.Document._jitterRotation)}
-                    fitToBox={this.props.fitToBox || BoolCast(this.props.freezeChildDimensions)}
+                    //fitToBox={this.props.fitToBox || BoolCast(this.props.freezeChildDimensions)} // bcz: check this
+                    fitToBox={BoolCast(this.props.freezeChildDimensions)} // bcz: check this
                     FreezeDimensions={BoolCast(this.props.freezeChildDimensions)}
                 />,
                 bounds: this.childDataProvider(entry[1].pair.layout, entry[1].replica)
diff --git a/src/client/views/nodes/ColorBox.scss b/src/client/views/nodes/ColorBox.scss
index bf334c939..da3266dc1 100644
--- a/src/client/views/nodes/ColorBox.scss
+++ b/src/client/views/nodes/ColorBox.scss
@@ -3,6 +3,7 @@
      height:100%;
      position: relative;
      pointer-events: none;
+     transform-origin: top left;
      
     .sketch-picker {
         margin:auto;
diff --git a/src/client/views/nodes/DocumentBox.tsx b/src/client/views/nodes/DocumentBox.tsx
index 0111cadd5..0d18baaed 100644
--- a/src/client/views/nodes/DocumentBox.tsx
+++ b/src/client/views/nodes/DocumentBox.tsx
@@ -120,8 +120,8 @@ export class DocHolderBox extends ViewBoxAnnotatableComponent<FieldViewProps, Do
             DataDocument={undefined}
             LibraryPath={emptyPath}
             CollectionView={this as any} // bcz: hack!  need to pass a prop that can be used to select the container (ie, 'this') when the up selector in document decorations is clicked.  currently, the up selector allows only a containing collection to be selected
-            fitToBox={this.props.fitToBox}
-            layoutKey={"layout_" + childTemplateName}
+            fitToBox={true}
+            layoutKey={childTemplateName ? "layout_" + childTemplateName : "layout"}
             rootSelected={this.props.isSelected}
             addDocument={this.props.addDocument}
             moveDocument={this.props.moveDocument}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 2aee49ad3..f8bf9bf64 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1072,7 +1072,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     layoutKey={this.finalLayoutKey} />
             </div>);
         const titleView = (!showTitle ? (null) :
-            <div className={`documentView-titleWrapper${showTitleHover ? "-hover" : ""}`} style={{
+            <div className={`documentView-titleWrapper${showTitleHover ? "-hover" : ""}`} key="title" style={{
                 position: showTextTitle ? "relative" : "absolute",
                 pointerEvents: this.onClickHandler || this.Document.ignoreClick ? "none" : undefined,
             }}>
@@ -1086,10 +1086,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         return !showTitle && !showCaption ?
             this.contents :
             <div className="documentView-styleWrapper" >
-                <div className="documentView-styleContentWrapper" style={{ height: showTextTitle ? `calc(100% - ${this.chromeHeight()}px)` : "100%", top: showTextTitle ? this.chromeHeight() : undefined }}>
-                    {this.contents}
-                </div>
-                {titleView}
+                {this.Document.type !== DocumentType.RTF ? <> {this.contents} {titleView} </> : <> {titleView} {this.contents} </>}
                 {captionView}
             </div>;
     }
diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss
index 7a05ec3a3..3bedb7127 100644
--- a/src/client/views/nodes/FormattedTextBox.scss
+++ b/src/client/views/nodes/FormattedTextBox.scss
@@ -24,7 +24,6 @@
     overflow-y: auto;
     overflow-x: hidden;
     color: initial;
-    height: 100%;
     max-height: 100%;
     display: flex;
     flex-direction: row;
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 815b8b3ae..425af8ccf 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -194,7 +194,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
 
             const tsel = this._editorView.state.selection.$from;
             tsel.marks().filter(m => m.type === this._editorView!.state.schema.marks.user_mark).map(m => AudioBox.SetScrubTime(Math.max(0, m.attrs.modified * 1000)));
-            const curText = state.doc.textBetween(0, state.doc.content.size, "\n\n");
+            const curText = state.doc.textBetween(0, state.doc.content.size, " \n");
             const curTemp = Cast(this.props.Document[this.props.fieldKey + "-textTemplate"], RichTextField);
             if (!this._applyingChange) {
                 this._applyingChange = true;
@@ -1211,7 +1211,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         return (
             <div className={`formattedTextBox-cont`} ref={this._ref}
                 style={{
-                    height: this.layoutDoc._autoHeight && this.props.renderDepth ? "max-content" : undefined,
+                    height: this.layoutDoc._autoHeight && this.props.renderDepth ? "max-content" : `calc(100% - ${this.props.ChromeHeight?.() || 0}px`,
                     background: this.props.hideOnLeave ? "rgba(0,0,0 ,0.4)" : StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"]),
                     opacity: this.props.hideOnLeave ? (this._entered ? 1 : 0.1) : 1,
                     color: this.props.hideOnLeave ? "white" : "inherit",
diff --git a/src/client/views/nodes/LabelBox.scss b/src/client/views/nodes/LabelBox.scss
index 56dd86ff9..7c7e92379 100644
--- a/src/client/views/nodes/LabelBox.scss
+++ b/src/client/views/nodes/LabelBox.scss
@@ -18,8 +18,6 @@
 
 .labelBox-mainButtonCenter {
     overflow: hidden;
-    text-overflow: ellipsis;
-    white-space: nowrap;
     display: inline;
     align-items: center;
     margin: auto;
diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index a41e4fac4..59c695f21 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -74,7 +74,13 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps, LabelDocument
                     color: StrCast(this.layoutDoc.color, "inherit"),
                     fontSize: NumCast(this.layoutDoc.fontSize) || "inherit",
                     letterSpacing: StrCast(this.layoutDoc.letterSpacing),
-                    textTransform: StrCast(this.layoutDoc.textTransform) as any
+                    textTransform: StrCast(this.layoutDoc.textTransform) as any,
+                    paddingLeft: NumCast(this.layoutDoc._xPadding),
+                    paddingRight: NumCast(this.layoutDoc._xPadding),
+                    paddingTop: NumCast(this.layoutDoc._yPadding),
+                    paddingBottom: NumCast(this.layoutDoc._yPadding),
+                    textOverflow: this.layoutDoc._singleLine ? "ellipsis" : undefined,
+                    whiteSpace: this.layoutDoc._singleLine ? "nowrap" : "pre-wrap"
                 }} >
                     <div className="labelBox-mainButtonCenter">
                         {StrCast(this.rootDoc.text, StrCast(this.rootDoc.title))}
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index afde524b6..25775edef 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -167,7 +167,7 @@ export class CurrentUserUtils {
             doc["template-icon-view"] = new PrefetchProxy(iconView);
         }
         if (doc["template-icon-view-rtf"] === undefined) {
-            const iconRtfView = Docs.Create.LabelDocument({ title: "icon_" + DocumentType.RTF, textTransform: "unset", letterSpacing: "unset", _width: 150, _height: 30, isTemplateDoc: true, onClick: ScriptField.MakeScript("deiconifyView(self)") });
+            const iconRtfView = Docs.Create.LabelDocument({ title: "icon_" + DocumentType.RTF, textTransform: "unset", letterSpacing: "unset", _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, isTemplateDoc: true, onClick: ScriptField.MakeScript("deiconifyView(self)") });
             iconRtfView.isTemplateDoc = makeTemplate(iconRtfView, true, "icon_" + DocumentType.RTF);
             doc["template-icon-view-rtf"] = new PrefetchProxy(iconRtfView);
         }
@@ -214,7 +214,7 @@ export class CurrentUserUtils {
             { title: "Drag a screenshot", label: "Grab", icon: "photo-video", ignoreClick: true, drag: 'Docs.Create.ScreenshotDocument("", { _width: 400, _height: 200, title: "screen snapshot" })' },
             { title: "Drag a webcam", label: "Cam", icon: "video", ignoreClick: true, drag: 'Docs.Create.WebCamDocument("", { _width: 400, _height: 400, title: "a test cam" })' },
             { title: "Drag a audio recorder", label: "Audio", icon: "microphone", ignoreClick: true, drag: `Docs.Create.AudioDocument("${nullAudio}", { _width: 200, title: "ready to record audio" })` },
-            { title: "Drag a clickable button", label: "Btn", icon: "bolt", ignoreClick: true, drag: 'Docs.Create.ButtonDocument({ _width: 150, _height: 50, title: "Button" })' },
+            { title: "Drag a clickable button", label: "Btn", icon: "bolt", ignoreClick: true, drag: 'Docs.Create.ButtonDocument({ _width: 150, _height: 50, _xPadding:10, _yPadding: 10, title: "Button" })' },
             { title: "Drag a presentation view", label: "Prezi", icon: "tv", click: 'openOnRight(Doc.UserDoc().activePresentation = getCopy(this.dragFactory, true))', drag: `Doc.UserDoc().activePresentation = getCopy(this.dragFactory,true)`, dragFactory: doc.emptyPresentation as Doc },
             { title: "Drag a search box", label: "Query", icon: "search", ignoreClick: true, drag: 'Docs.Create.QueryDocument({ _width: 200, title: "an image of a cat" })' },
             { title: "Drag a scripting box", label: "Script", icon: "terminal", ignoreClick: true, drag: 'Docs.Create.ScriptingDocument(undefined, { _width: 200, _height: 250 title: "untitled script" })' },
-- 
cgit v1.2.3-70-g09d2


From 69de8c235a6580ac222ef3f5b31746f6bc144659 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 28 Apr 2020 10:48:16 -0400
Subject: rearranged text files

---
 .../apis/google_docs/GooglePhotosClientUtils.ts    |    2 +-
 src/client/documents/Documents.ts                  |    2 +-
 src/client/util/DashDocCommentView.tsx             |   95 --
 src/client/util/DashDocView.tsx                    |  269 ----
 src/client/util/DashFieldView.scss                 |   36 -
 src/client/util/DashFieldView.tsx                  |  211 ----
 src/client/util/FootnoteView.tsx                   |  162 ---
 src/client/util/ImageResizeView.tsx                |  138 ---
 src/client/util/ParagraphNodeSpec.ts               |  143 ---
 src/client/util/ProsemirrorExampleTransfer.ts      |  241 ----
 src/client/util/RichTextMenu.scss                  |  121 --
 src/client/util/RichTextMenu.tsx                   |  875 -------------
 src/client/util/RichTextRules.ts                   |    4 +-
 src/client/util/RichTextSchema.tsx                 |  718 -----------
 src/client/util/SummaryView.tsx                    |   81 --
 src/client/util/TooltipTextMenu.scss               |  372 ------
 src/client/util/marks_rts.ts                       |  296 -----
 src/client/util/nodes_rts.ts                       |  264 ----
 src/client/util/prosemirrorPatches.js              |  139 ---
 src/client/util/schema_rts.ts                      |   26 -
 src/client/views/DocumentButtonBar.tsx             |    2 +-
 src/client/views/InkingControl.tsx                 |    2 +-
 src/client/views/MainView.tsx                      |    2 +-
 .../views/collections/CollectionCarouselView.tsx   |    2 +-
 src/client/views/collections/CollectionSubView.tsx |    2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |    2 +-
 .../collections/collectionFreeForm/MarqueeView.tsx |    2 +-
 src/client/views/nodes/DocumentContentsView.tsx    |    2 +-
 src/client/views/nodes/FormattedTextBox.scss       |  265 ----
 src/client/views/nodes/FormattedTextBox.tsx        | 1306 --------------------
 .../views/nodes/FormattedTextBoxComment.scss       |   33 -
 src/client/views/nodes/FormattedTextBoxComment.tsx |  236 ----
 .../nodes/formattedText/DashDocCommentView.tsx     |   95 ++
 .../views/nodes/formattedText/DashDocView.tsx      |  269 ++++
 .../views/nodes/formattedText/DashFieldView.scss   |   36 +
 .../views/nodes/formattedText/DashFieldView.tsx    |  211 ++++
 .../views/nodes/formattedText/FootnoteView.tsx     |  162 +++
 .../nodes/formattedText/FormattedTextBox.scss      |  265 ++++
 .../views/nodes/formattedText/FormattedTextBox.tsx | 1303 +++++++++++++++++++
 .../formattedText/FormattedTextBoxComment.scss     |   33 +
 .../formattedText/FormattedTextBoxComment.tsx      |  236 ++++
 .../views/nodes/formattedText/ImageResizeView.tsx  |  138 +++
 .../views/nodes/formattedText/ParagraphNodeSpec.ts |  143 +++
 .../formattedText/ProsemirrorExampleTransfer.ts    |  241 ++++
 .../views/nodes/formattedText/RichTextMenu.scss    |  121 ++
 .../views/nodes/formattedText/RichTextMenu.tsx     |  875 +++++++++++++
 .../views/nodes/formattedText/RichTextRules.ts     |  319 +++++
 .../views/nodes/formattedText/RichTextSchema.tsx   |  718 +++++++++++
 .../views/nodes/formattedText/SummaryView.tsx      |   81 ++
 .../views/nodes/formattedText/TooltipTextMenu.scss |  372 ++++++
 src/client/views/nodes/formattedText/marks_rts.ts  |  296 +++++
 src/client/views/nodes/formattedText/nodes_rts.ts  |  264 ++++
 .../nodes/formattedText/prosemirrorPatches.js      |  139 +++
 src/client/views/nodes/formattedText/schema_rts.ts |   26 +
 src/mobile/MobileInterface.tsx                     |    2 +-
 src/new_fields/RichTextUtils.ts                    |    4 +-
 .../authentication/models/current_user_utils.ts    |    2 +-
 57 files changed, 6359 insertions(+), 6043 deletions(-)
 delete mode 100644 src/client/util/DashDocCommentView.tsx
 delete mode 100644 src/client/util/DashDocView.tsx
 delete mode 100644 src/client/util/DashFieldView.scss
 delete mode 100644 src/client/util/DashFieldView.tsx
 delete mode 100644 src/client/util/FootnoteView.tsx
 delete mode 100644 src/client/util/ImageResizeView.tsx
 delete mode 100644 src/client/util/ParagraphNodeSpec.ts
 delete mode 100644 src/client/util/ProsemirrorExampleTransfer.ts
 delete mode 100644 src/client/util/RichTextMenu.scss
 delete mode 100644 src/client/util/RichTextMenu.tsx
 delete mode 100644 src/client/util/RichTextSchema.tsx
 delete mode 100644 src/client/util/SummaryView.tsx
 delete mode 100644 src/client/util/TooltipTextMenu.scss
 delete mode 100644 src/client/util/marks_rts.ts
 delete mode 100644 src/client/util/nodes_rts.ts
 delete mode 100644 src/client/util/prosemirrorPatches.js
 delete mode 100644 src/client/util/schema_rts.ts
 delete mode 100644 src/client/views/nodes/FormattedTextBox.scss
 delete mode 100644 src/client/views/nodes/FormattedTextBox.tsx
 delete mode 100644 src/client/views/nodes/FormattedTextBoxComment.scss
 delete mode 100644 src/client/views/nodes/FormattedTextBoxComment.tsx
 create mode 100644 src/client/views/nodes/formattedText/DashDocCommentView.tsx
 create mode 100644 src/client/views/nodes/formattedText/DashDocView.tsx
 create mode 100644 src/client/views/nodes/formattedText/DashFieldView.scss
 create mode 100644 src/client/views/nodes/formattedText/DashFieldView.tsx
 create mode 100644 src/client/views/nodes/formattedText/FootnoteView.tsx
 create mode 100644 src/client/views/nodes/formattedText/FormattedTextBox.scss
 create mode 100644 src/client/views/nodes/formattedText/FormattedTextBox.tsx
 create mode 100644 src/client/views/nodes/formattedText/FormattedTextBoxComment.scss
 create mode 100644 src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
 create mode 100644 src/client/views/nodes/formattedText/ImageResizeView.tsx
 create mode 100644 src/client/views/nodes/formattedText/ParagraphNodeSpec.ts
 create mode 100644 src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
 create mode 100644 src/client/views/nodes/formattedText/RichTextMenu.scss
 create mode 100644 src/client/views/nodes/formattedText/RichTextMenu.tsx
 create mode 100644 src/client/views/nodes/formattedText/RichTextRules.ts
 create mode 100644 src/client/views/nodes/formattedText/RichTextSchema.tsx
 create mode 100644 src/client/views/nodes/formattedText/SummaryView.tsx
 create mode 100644 src/client/views/nodes/formattedText/TooltipTextMenu.scss
 create mode 100644 src/client/views/nodes/formattedText/marks_rts.ts
 create mode 100644 src/client/views/nodes/formattedText/nodes_rts.ts
 create mode 100644 src/client/views/nodes/formattedText/prosemirrorPatches.js
 create mode 100644 src/client/views/nodes/formattedText/schema_rts.ts

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

diff --git a/src/client/apis/google_docs/GooglePhotosClientUtils.ts b/src/client/apis/google_docs/GooglePhotosClientUtils.ts
index 8c0149a89..e3f801c46 100644
--- a/src/client/apis/google_docs/GooglePhotosClientUtils.ts
+++ b/src/client/apis/google_docs/GooglePhotosClientUtils.ts
@@ -10,7 +10,7 @@ import { MediaItem, NewMediaItemResult } from "../../../server/apis/google/Share
 import { Utils } from "../../../Utils";
 import { Docs, DocumentOptions } from "../../documents/Documents";
 import { Networking } from "../../Network";
-import { FormattedTextBox } from "../../views/nodes/FormattedTextBox";
+import { FormattedTextBox } from "../../views/nodes/formattedText/FormattedTextBox";
 import GoogleAuthenticationManager from "../GoogleAuthenticationManager";
 import Photos = require('googlephotos');
 
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index c64916897..f96e3bcd1 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1,7 +1,7 @@
 import { CollectionView } from "../views/collections/CollectionView";
 import { CollectionViewType } from "../views/collections/CollectionView";
 import { AudioBox } from "../views/nodes/AudioBox";
-import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
+import { FormattedTextBox } from "../views/nodes/formattedText/FormattedTextBox";
 import { ImageBox } from "../views/nodes/ImageBox";
 import { KeyValueBox } from "../views/nodes/KeyValueBox";
 import { PDFBox } from "../views/nodes/PDFBox";
diff --git a/src/client/util/DashDocCommentView.tsx b/src/client/util/DashDocCommentView.tsx
deleted file mode 100644
index e716fac53..000000000
--- a/src/client/util/DashDocCommentView.tsx
+++ /dev/null
@@ -1,95 +0,0 @@
-import { IReactionDisposer, observable, reaction, runInAction } from "mobx";
-import { baseKeymap, toggleMark } from "prosemirror-commands";
-import { redo, undo } from "prosemirror-history";
-import { keymap } from "prosemirror-keymap";
-import { DOMOutputSpecArray, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model";
-import { bulletList, listItem, orderedList } from 'prosemirror-schema-list';
-import { EditorState, NodeSelection, Plugin, TextSelection } from "prosemirror-state";
-import { StepMap } from "prosemirror-transform";
-import { EditorView } from "prosemirror-view";
-import * as ReactDOM from 'react-dom';
-import { Doc, DocListCast, Field, HeightSym, WidthSym } from "../../new_fields/Doc";
-import { Id } from "../../new_fields/FieldSymbols";
-import { List } from "../../new_fields/List";
-import { ObjectField } from "../../new_fields/ObjectField";
-import { listSpec } from "../../new_fields/Schema";
-import { SchemaHeaderField } from "../../new_fields/SchemaHeaderField";
-import { ComputedField } from "../../new_fields/ScriptField";
-import { BoolCast, Cast, NumCast, StrCast } from "../../new_fields/Types";
-import { emptyFunction, returnEmptyString, returnFalse, returnOne, Utils, returnZero } from "../../Utils";
-import { DocServer } from "../DocServer";
-
-import React = require("react");
-
-import { schema } from "./schema_rts";
-
-interface IDashDocCommentView {
-    node: any;
-    view: any;
-    getPos: any;
-}
-
-export class DashDocCommentView extends React.Component<IDashDocCommentView>{
-    constructor(props: IDashDocCommentView) {
-        super(props);
-    }
-
-    targetNode = () => {  // search forward in the prosemirror doc for the attached dashDocNode that is the target of the comment anchor
-        for (let i = this.props.getPos() + 1; i < this.props.view.state.doc.content.size; i++) {
-            const m = this.props.view.state.doc.nodeAt(i);
-            if (m && m.type === this.props.view.state.schema.nodes.dashDoc && m.attrs.docid === this.props.node.attrs.docid) {
-                return { node: m, pos: i, hidden: m.attrs.hidden } as { node: any, pos: number, hidden: boolean };
-            }
-        }
-        const dashDoc = this.props.view.state.schema.nodes.dashDoc.create({ width: 75, height: 35, title: "dashDoc", docid: this.props.node.attrs.docid, float: "right" });
-        this.props.view.dispatch(this.props.view.state.tr.insert(this.props.getPos() + 1, dashDoc));
-        setTimeout(() => { try { this.props.view.dispatch(this.props.view.state.tr.setSelection(TextSelection.create(this.props.view.state.tr.doc, this.props.getPos() + 2))); } catch (e) { } }, 0);
-        return undefined;
-    }
-
-    onPointerDownCollapse = (e: any) => e.stopPropagation();
-
-    onPointerUpCollapse = (e: any) => {
-        const target = this.targetNode();
-        if (target) {
-            const expand = target.hidden;
-            const tr = this.props.view.state.tr.setNodeMarkup(target.pos, undefined, { ...target.node.attrs, hidden: target.node.attrs.hidden ? false : true });
-            this.props.view.dispatch(tr.setSelection(TextSelection.create(tr.doc, this.props.getPos() + (expand ? 2 : 1)))); // update the attrs
-            setTimeout(() => {
-                expand && DocServer.GetRefField(this.props.node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc));
-                try { this.props.view.dispatch(this.props.view.state.tr.setSelection(TextSelection.create(this.props.view.state.tr.doc, this.props.getPos() + (expand ? 2 : 1)))); } catch (e) { }
-            }, 0);
-        }
-        e.stopPropagation();
-    }
-
-    onPointerEnterCollapse = (e: any) => {
-        DocServer.GetRefField(this.props.node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc, false));
-        e.preventDefault();
-        e.stopPropagation();
-    }
-
-    onPointerLeaveCollapse = (e: any) => {
-        DocServer.GetRefField(this.props.node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowUnhighlight());
-        e.preventDefault();
-        e.stopPropagation();
-    }
-
-    render() {
-
-        const collapsedId = "DashDocCommentView-" + this.props.node.attrs.docid;
-
-        return (
-            <span
-                className="formattedTextBox-inlineComment"
-                id={collapsedId}
-                onPointerDown={this.onPointerDownCollapse}
-                onPointerUp={this.onPointerUpCollapse}
-                onPointerEnter={this.onPointerEnterCollapse}
-                onPointerLeave={this.onPointerLeaveCollapse}
-            >
-
-            </span >
-        );
-    }
-}
\ No newline at end of file
diff --git a/src/client/util/DashDocView.tsx b/src/client/util/DashDocView.tsx
deleted file mode 100644
index 39809187f..000000000
--- a/src/client/util/DashDocView.tsx
+++ /dev/null
@@ -1,269 +0,0 @@
-import { IReactionDisposer, reaction } from "mobx";
-import { NodeSelection } from "prosemirror-state";
-import { Doc, HeightSym, WidthSym } from "../../new_fields/Doc";
-import { Id } from "../../new_fields/FieldSymbols";
-import { ObjectField } from "../../new_fields/ObjectField";
-import { ComputedField } from "../../new_fields/ScriptField";
-import { BoolCast, Cast, NumCast, StrCast } from "../../new_fields/Types";
-import { emptyFunction, returnEmptyString, returnFalse, Utils, returnZero } from "../../Utils";
-import { DocServer } from "../DocServer";
-import { Docs } from "../documents/Documents";
-import { DocumentView } from "../views/nodes/DocumentView";
-import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
-import { Transform } from "./Transform";
-import React = require("react");
-
-interface IDashDocView {
-    node: any;
-    view: any;
-    getPos: any;
-    tbox?: FormattedTextBox;
-    self: any;
-}
-
-export class DashDocView extends React.Component<IDashDocView> {
-
-    _dashDoc: Doc | undefined;
-    _reactionDisposer: IReactionDisposer | undefined;
-    _renderDisposer: IReactionDisposer | undefined;
-    _textBox: FormattedTextBox;
-    _finalLayout: any;
-    _resolvedDataDoc: any;
-
-
-    //    constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
-
-    constructor(props: IDashDocView) {
-        super(props);
-
-        const node = this.props.node;
-        this._textBox = this.props.tbox as FormattedTextBox;
-
-        const alias = node.attrs.alias;
-        const docid = node.attrs.docid || this._textBox.props.Document[Id];
-
-        DocServer.GetRefField(docid + alias).then(async dashDoc => {
-            if (!(dashDoc instanceof Doc)) {
-                alias && DocServer.GetRefField(docid).then(async dashDocBase => {
-                    if (dashDocBase instanceof Doc) {
-                        const aliasedDoc = Doc.MakeAlias(dashDocBase, docid + alias);
-                        aliasedDoc.layoutKey = "layout";
-                        node.attrs.fieldKey && DocumentView.makeCustomViewClicked(aliasedDoc, Docs.Create.StackingDocument, node.attrs.fieldKey, undefined);
-                        this._dashDoc = aliasedDoc;
-                        //                        self.doRender(aliasedDoc, removeDoc, node, view, getPos);
-                    }
-                });
-            } else {
-                this._dashDoc = dashDoc;
-                //                self.doRender(dashDoc, removeDoc, node, view, getPos);
-            }
-        });
-
-        this.onPointerLeave = this.onPointerLeave.bind(this);
-        this.onPointerEnter = this.onPointerEnter.bind(this);
-        this.onKeyDown = this.onKeyDown.bind(this);
-        this.onKeyPress = this.onKeyPress.bind(this);
-        this.onKeyUp = this.onKeyUp.bind(this);
-        this.onWheel = this.onWheel.bind(this);
-    }
-    /* #region Internal functions */
-
-    removeDoc = () => {
-        const view = this.props.view;
-        const pos = this.props.getPos();
-        const ns = new NodeSelection(view.state.doc.resolve(pos));
-        view.dispatch(view.state.tr.setSelection(ns).deleteSelection());
-        return true;
-    }
-
-    getDocTransform = () => {
-        const outerElement = document.getElementById('dash-document-view-outer') as HTMLElement;
-        const { scale, translateX, translateY } = Utils.GetScreenTransform(outerElement);
-        return new Transform(-translateX, -translateY, 1).scale(1 / this.contentScaling() / scale);
-    }
-    contentScaling = () => NumCast(this._dashDoc!._nativeWidth) > 0 ? this._dashDoc![WidthSym]() / NumCast(this._dashDoc!._nativeWidth) : 1;
-
-    outerFocus = (target: Doc) => this._textBox.props.focus(this._textBox.props.Document);  // ideally, this would scroll to show the focus target
-
-    onKeyPress = (e: any) => {
-        e.stopPropagation();
-    }
-    onWheel = (e: any) => {
-        e.preventDefault();
-    }
-    onKeyUp = (e: any) => {
-        e.stopPropagation();
-    }
-    onKeyDown = (e: any) => {
-        e.stopPropagation();
-        if (e.key === "Tab" || e.key === "Enter") {
-            e.preventDefault();
-        }
-    }
-    onPointerLeave = () => {
-        const ele = document.getElementById("DashDocCommentView-" + this.props.node.attrs.docid);
-        if (ele) {
-            (ele as HTMLDivElement).style.backgroundColor = "";
-        }
-    }
-    onPointerEnter = () => {
-        const ele = document.getElementById("DashDocCommentView-" + this.props.node.attrs.docid);
-        if (ele) {
-            (ele as HTMLDivElement).style.backgroundColor = "orange";
-        }
-    }
-    /*endregion*/
-
-    componentWillMount = () => {
-        this._reactionDisposer?.();
-    }
-
-    componentDidUpdate = () => {
-
-        this._renderDisposer?.();
-        this._renderDisposer = reaction(() => {
-
-            const dashDoc = this._dashDoc as Doc;
-            const dashLayoutDoc = Doc.Layout(dashDoc);
-            const finalLayout = this.props.node.attrs.docid ? dashDoc : Doc.expandTemplateLayout(dashLayoutDoc, dashDoc, this.props.node.attrs.fieldKey);
-
-            if (finalLayout) {
-                if (!Doc.AreProtosEqual(finalLayout, dashDoc)) {
-                    finalLayout.rootDocument = dashDoc.aliasOf;
-                }
-                const layoutKey = StrCast(finalLayout.layoutKey);
-                const finalKey = layoutKey && StrCast(finalLayout[layoutKey]).split("'")?.[1];
-                if (finalLayout !== dashDoc && finalKey) {
-                    const finalLayoutField = finalLayout[finalKey];
-                    if (finalLayoutField instanceof ObjectField) {
-                        finalLayout[finalKey + "-textTemplate"] = ComputedField.MakeFunction(`copyField(this.${finalKey})`, { this: Doc.name });
-                    }
-                }
-                this._finalLayout = finalLayout;
-                this._resolvedDataDoc = Cast(finalLayout.resolvedDataDoc, Doc, null);
-                return { finalLayout, resolvedDataDoc: Cast(finalLayout.resolvedDataDoc, Doc, null) };
-            }
-        },
-            (res) => {
-
-                if (res) {
-                    this._finalLayout = res.finalLayout;
-                    this._resolvedDataDoc = res.resolvedDataDoc;
-
-                    this.forceUpdate(); // doReactRender(res.finalLayout, res.resolvedDataDoc),
-                }
-            },
-            { fireImmediately: true });
-
-    }
-
-    render() {
-        // doRender(dashDoc: Doc, removeDoc: any, node: any, view: any, getPos: any) {
-
-        const node = this.props.node;
-        const view = this.props.view;
-        const getPos = this.props.getPos;
-
-        const spanStyle = {
-            width: this.props.node.props.width,
-            height: this.props.node.props.height,
-            position: 'absolute' as 'absolute',
-            display: 'inline-block'
-        };
-
-
-        const outerStyle = {
-            position: "relative" as "relative",
-            textIndent: "0",
-            border: "1px solid " + StrCast(this._textBox.Document.color, (Cast(Doc.UserDoc().activeWorkspace, Doc, null).darkScheme ? "dimGray" : "lightGray")),
-            width: this.props.node.props.width,
-            height: this.props.node.props.height,
-            display: this.props.node.props.hidden ? "none" : "inline-block",
-            float: this.props.node.props.float,
-        };
-
-        const dashDoc = this._dashDoc as Doc;
-        const self = this;
-        const dashLayoutDoc = Doc.Layout(dashDoc);
-        const finalLayout = node.attrs.docid ? dashDoc : Doc.expandTemplateLayout(dashLayoutDoc, dashDoc, node.attrs.fieldKey);
-        const resolvedDataDoc = this._resolvedDataDoc; //Added this
-
-        if (!finalLayout) {
-            return <div></div>;
-            // if (!finalLayout) setTimeout(() => self.doRender(dashDoc, removeDoc, node, view, getPos), 0);
-        } else {
-
-            this._reactionDisposer?.();
-            this._reactionDisposer = reaction(() =>
-                ({
-                    dim: [finalLayout[WidthSym](), finalLayout[HeightSym]()],
-                    color: finalLayout.color
-                }),
-                ({ dim, color }) => {
-                    spanStyle.width = outerStyle.width = Math.max(20, dim[0]) + "px";
-                    spanStyle.height = outerStyle.height = Math.max(20, dim[1]) + "px";
-                    outerStyle.border = "1px solid " + StrCast(finalLayout.color, (Cast(Doc.UserDoc().activeWorkspace, Doc, null).darkScheme ? "dimGray" : "lightGray"));
-                }, { fireImmediately: true });
-
-            if (node.attrs.width !== dashDoc._width + "px" || node.attrs.height !== dashDoc._height + "px") {
-                try { // bcz: an exception will be thrown if two aliases are open at the same time when a doc view comment is made
-                    view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, { ...node.attrs, width: dashDoc._width + "px", height: dashDoc._height + "px" }));
-                } catch (e) {
-                    console.log(e);
-                }
-            }
-
-
-            //const doReactRender = (finalLayout: Doc, resolvedDataDoc: Doc) => {
-            //    ReactDOM.unmountComponentAtNode(this._dashSpan);
-
-            return (
-                <span id="dash-document-view-outer"
-                    className="outer"
-                    style={outerStyle}
-                >
-                    <div id="dashSpan"
-                        className="dash-span"
-                        style={spanStyle}
-                        onPointerLeave={this.onPointerLeave}
-                        onPointerEnter={this.onPointerEnter}
-                        onKeyDown={this.onKeyDown}
-                        onKeyPress={this.onKeyPress}
-                        onKeyUp={this.onKeyUp}
-                        onWheel={this.onWheel}
-                    >
-                        <DocumentView
-                            Document={finalLayout}
-                            DataDoc={resolvedDataDoc}
-                            LibraryPath={this._textBox.props.LibraryPath}
-                            fitToBox={BoolCast(dashDoc._fitToBox)}
-                            addDocument={returnFalse}
-                            rootSelected={this._textBox.props.isSelected}
-                            removeDocument={this.removeDoc}
-                            ScreenToLocalTransform={this.getDocTransform}
-                            addDocTab={this._textBox.props.addDocTab}
-                            pinToPres={returnFalse}
-                            renderDepth={self._textBox.props.renderDepth + 1}
-                            NativeHeight={returnZero}
-                            NativeWidth={returnZero}
-                            PanelWidth={finalLayout[WidthSym]}
-                            PanelHeight={finalLayout[HeightSym]}
-                            focus={this.outerFocus}
-                            backgroundColor={returnEmptyString}
-                            parentActive={returnFalse}
-                            whenActiveChanged={returnFalse}
-                            bringToFront={emptyFunction}
-                            dontRegisterView={false}
-                            ContainingCollectionView={this._textBox.props.ContainingCollectionView}
-                            ContainingCollectionDoc={this._textBox.props.ContainingCollectionDoc}
-                            ContentScaling={this.contentScaling}
-                        />
-
-                    </div>
-                </span>
-            );
-
-        }
-    }
-
-}
\ No newline at end of file
diff --git a/src/client/util/DashFieldView.scss b/src/client/util/DashFieldView.scss
deleted file mode 100644
index 35ff9c1e6..000000000
--- a/src/client/util/DashFieldView.scss
+++ /dev/null
@@ -1,36 +0,0 @@
-.dashFieldView {
-    position: relative;
-    display: inline-block;
-
-    .dashFieldView-enumerables {
-        width: 10px;
-        height: 10px;
-        position: relative;
-        display: inline-block;
-        background: dimGray;
-    }
-    .dashFieldView-fieldCheck {
-        min-width: 12px;
-        position: relative;
-        display: inline-block;
-        background-color: rgba(155, 155, 155, 0.24);
-    }
-    .dashFieldView-labelSpan {
-        position: relative;
-        display: inline-block;
-        font-size: small;
-    }
-    .dashFieldView-fieldSpan {
-        min-width: 20px;
-        margin-left: 2px;
-        margin-right: 5px;
-        position: relative;
-        display: inline-block;
-        background-color: rgba(155, 155, 155, 0.24);
-        span {
-            min-width: 100%;
-            display: inline-block;
-        }
-    }
-}
-        
\ No newline at end of file
diff --git a/src/client/util/DashFieldView.tsx b/src/client/util/DashFieldView.tsx
deleted file mode 100644
index cf09e0132..000000000
--- a/src/client/util/DashFieldView.tsx
+++ /dev/null
@@ -1,211 +0,0 @@
-import { IReactionDisposer, observable, runInAction, computed, action } from "mobx";
-import { Doc, DocListCast, Field } from "../../new_fields/Doc";
-import { List } from "../../new_fields/List";
-import { listSpec } from "../../new_fields/Schema";
-import { SchemaHeaderField } from "../../new_fields/SchemaHeaderField";
-import { ComputedField } from "../../new_fields/ScriptField";
-import { Cast, StrCast } from "../../new_fields/Types";
-import { DocServer } from "../DocServer";
-import { CollectionViewType } from "../views/collections/CollectionView";
-import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
-import React = require("react");
-import * as ReactDOM from 'react-dom';
-import "./DashFieldView.scss";
-import { observer } from "mobx-react";
-
-
-export class DashFieldView {
-    _fieldWrapper: HTMLDivElement; // container for label and value
-
-    constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
-        this._fieldWrapper = document.createElement("div");
-        this._fieldWrapper.style.width = node.attrs.width;
-        this._fieldWrapper.style.height = node.attrs.height;
-        this._fieldWrapper.style.fontWeight = "bold";
-        this._fieldWrapper.style.position = "relative";
-        this._fieldWrapper.style.display = "inline-block";
-        this._fieldWrapper.onkeypress = function (e: any) { e.stopPropagation(); };
-        this._fieldWrapper.onkeydown = function (e: any) { e.stopPropagation(); };
-        this._fieldWrapper.onkeyup = function (e: any) { e.stopPropagation(); };
-        this._fieldWrapper.onmousedown = function (e: any) { e.stopPropagation(); };
-
-        ReactDOM.render(<DashFieldViewInternal
-            fieldKey={node.attrs.fieldKey}
-            docid={node.attrs.docid}
-            width={node.attrs.width}
-            height={node.attrs.height}
-            view={view}
-            getPos={getPos}
-            tbox={tbox}
-        />, this._fieldWrapper);
-        (this as any).dom = this._fieldWrapper;
-    }
-    destroy() {
-        ReactDOM.unmountComponentAtNode(this._fieldWrapper);
-    }
-    selectNode() { }
-
-}
-interface IDashFieldViewInternal {
-    fieldKey: string;
-    docid: string;
-    view: any;
-    getPos: any;
-    tbox: FormattedTextBox;
-    width: number;
-    height: number;
-}
-
-@observer
-export class DashFieldViewInternal extends React.Component<IDashFieldViewInternal> {
-    _reactionDisposer: IReactionDisposer | undefined;
-    _textBoxDoc: Doc;
-    _fieldKey: string;
-    _fieldStringRef = React.createRef<HTMLSpanElement>();
-    @observable _showEnumerables: boolean = false;
-    @observable _dashDoc: Doc | undefined;
-
-    constructor(props: IDashFieldViewInternal) {
-        super(props);
-        this._fieldKey = this.props.fieldKey;
-        this._textBoxDoc = this.props.tbox.props.Document;
-
-        if (this.props.docid) {
-            DocServer.GetRefField(this.props.docid).
-                then(action(async dashDoc => dashDoc instanceof Doc && (this._dashDoc = dashDoc)));
-        } else {
-            this._dashDoc = this.props.tbox.props.DataDoc || this.props.tbox.dataDoc;
-        }
-    }
-    componentWillUnmount() {
-        this._reactionDisposer?.();
-    }
-
-    // set the display of the field's value (checkbox for booleans, span of text for strings)
-    @computed get fieldValueContent() {
-        if (this._dashDoc) {
-            const dashVal = this._dashDoc[this._fieldKey];
-            const fval = StrCast(dashVal).startsWith(":=") || dashVal === "" ? Doc.Layout(this._textBoxDoc)[this._fieldKey] : dashVal;
-            const boolVal = Cast(fval, "boolean", null);
-            const strVal = Field.toString(fval as Field) || "";
-
-            // field value is a boolean, so use a checkbox or similar widget to display it
-            if (boolVal === true || boolVal === false) {
-                return <input
-                    className="dashFieldView-fieldCheck"
-                    type="checkbox" checked={boolVal}
-                    onChange={e => this._dashDoc![this._fieldKey] = e.target.checked}
-                />;
-            }
-            else // field value is a string, so display it as an editable span
-            {
-                // bcz: this is unfortunate, but since this React component is nested within a non-React text box (prosemirror), we can't
-                // use React events.  Essentially, React events occur after native events have been processed, so corresponding React events
-                // will never fire because Prosemirror has handled the native events.  So we add listeners for native events here.
-                return <span contentEditable={true} ref={r => {
-                    r?.addEventListener("keydown", e => this.fieldSpanKeyDown(e, r));
-                    r?.addEventListener("blur", e => r && this.updateText(r.textContent!, false));
-                    r?.addEventListener("pointerdown", action((e) => this._showEnumerables = true));
-                }}>
-                    {strVal}
-                </span>
-            }
-        }
-    }
-
-    // we need to handle all key events on the input span or else they will propagate to prosemirror.
-    @action
-    fieldSpanKeyDown = (e: KeyboardEvent, span: HTMLSpanElement) => {
-        if (e.key === "Enter") {  // handle the enter key by "submitting" the current text to Dash's database. 
-            e.ctrlKey && Doc.addFieldEnumerations(this._textBoxDoc, this._fieldKey, [{ title: span.textContent! }]);
-            this.updateText(span.textContent!, true);
-            e.preventDefault();// prevent default to avoid a newline from being generated and wiping out this field view
-        }
-        if (e.key === "a" && (e.ctrlKey || e.metaKey)) { // handle ctrl-A to select all the text within the span
-            if (window.getSelection) {
-                const range = document.createRange();
-                range.selectNodeContents(span);
-                window.getSelection()!.removeAllRanges();
-                window.getSelection()!.addRange(range);
-            }
-            e.preventDefault(); //prevent default so that all the text in the prosemirror text box isn't selected
-        }
-        e.stopPropagation();  // we need to handle all events or else they will propagate to prosemirror.
-    }
-
-    @action
-    updateText = (nodeText: string, forceMatch: boolean) => {
-        this._showEnumerables = false;
-        if (nodeText) {
-            const newText = nodeText.startsWith(":=") || nodeText.startsWith("=:=") ? ":=-computed-" : nodeText;
-
-            // look for a document whose id === the fieldKey being displayed.  If there's a match, then that document
-            // holds the different enumerated values for the field in the titles of its collected documents.
-            // if there's a partial match from the start of the input text, complete the text --- TODO: make this an auto suggest box and select from a drop down.
-            DocServer.GetRefField(this._fieldKey).then(options => {
-                let modText = "";
-                (options instanceof Doc) && DocListCast(options.data).forEach(opt => (forceMatch ? StrCast(opt.title).startsWith(newText) : StrCast(opt.title) === newText) && (modText = StrCast(opt.title)));
-                if (modText) {
-                    //  elementfieldSpan.innerHTML = this._dashDoc![this._fieldKey as string] = modText;
-                    Doc.addFieldEnumerations(this._textBoxDoc, this._fieldKey, []);
-                    this._dashDoc![this._fieldKey] = modText;
-                } // if the text starts with a ':=' then treat it as an expression by making a computed field from its value storing it in the key
-                else if (nodeText.startsWith(":=")) {
-                    this._dashDoc![this._fieldKey] = ComputedField.MakeFunction(nodeText.substring(2));
-                } else if (nodeText.startsWith("=:=")) {
-                    Doc.Layout(this._textBoxDoc)[this._fieldKey] = ComputedField.MakeFunction(nodeText.substring(3));
-                } else {
-                    this._dashDoc![this._fieldKey] = newText;
-                }
-            });
-        }
-    }
-
-    // display a collection of all the enumerable values for this field
-    onPointerDownEnumerables = async (e: any) => {
-        e.stopPropagation();
-        const collview = await Doc.addFieldEnumerations(this._textBoxDoc, this._fieldKey, [{ title: this._fieldKey }]);
-        collview instanceof Doc && this.props.tbox.props.addDocTab(collview, "onRight");
-    }
-
-
-    // clicking on the label creates a pivot view collection of all documents
-    // in the same collection.  The pivot field is the fieldKey of this label
-    onPointerDownLabelSpan = (e: any) => {
-        e.stopPropagation();
-        let container = this.props.tbox.props.ContainingCollectionView;
-        while (container?.props.Document.isTemplateForField || container?.props.Document.isTemplateDoc) {
-            container = container.props.ContainingCollectionView;
-        }
-        if (container) {
-            const alias = Doc.MakeAlias(container.props.Document);
-            alias.viewType = CollectionViewType.Time;
-            let list = Cast(alias.schemaColumns, listSpec(SchemaHeaderField));
-            if (!list) {
-                alias.schemaColumns = list = new List<SchemaHeaderField>();
-            }
-            list.map(c => c.heading).indexOf(this._fieldKey) === -1 && list.push(new SchemaHeaderField(this._fieldKey, "#f1efeb"));
-            list.map(c => c.heading).indexOf("text") === -1 && list.push(new SchemaHeaderField("text", "#f1efeb"));
-            alias._pivotField = this._fieldKey;
-            this.props.tbox.props.addDocTab(alias, "onRight");
-        }
-    }
-
-    render() {
-        return <div className="dashFieldView" style={{
-            width: this.props.width,
-            height: this.props.height,
-        }}>
-            <span className="dashFieldView-labelSpan" title="click to see related tags" onPointerDown={this.onPointerDownLabelSpan}>
-                {this._fieldKey}
-            </span>
-
-            <div className="dashFieldView-fieldSpan">
-                {this.fieldValueContent}
-            </div>
-
-            {!this._showEnumerables ? (null) : <div className="dashFieldView-enumerables" onPointerDown={this.onPointerDownEnumerables} />}
-
-        </div >;
-    }
-}
\ No newline at end of file
diff --git a/src/client/util/FootnoteView.tsx b/src/client/util/FootnoteView.tsx
deleted file mode 100644
index ee21fb765..000000000
--- a/src/client/util/FootnoteView.tsx
+++ /dev/null
@@ -1,162 +0,0 @@
-import { EditorView } from "prosemirror-view";
-import { EditorState } from "prosemirror-state";
-import { keymap } from "prosemirror-keymap";
-import { baseKeymap, toggleMark } from "prosemirror-commands";
-import { schema } from "./schema_rts";
-import { redo, undo } from "prosemirror-history";
-import { StepMap } from "prosemirror-transform";
-
-import React = require("react");
-
-interface IFootnoteView {
-    innerView: any;
-    outerView: any;
-    node: any;
-    dom: any;
-    getPos: any;
-}
-
-export class FootnoteView extends React.Component<IFootnoteView>  {
-    _innerView: any;
-    _node: any;
-
-    constructor(props: IFootnoteView) {
-        super(props);
-        const node = this.props.node;
-        const outerView = this.props.outerView;
-        const _innerView = this.props.innerView;
-        const getPos = this.props.getPos;
-    }
-
-    selectNode() {
-        const attrs = { ...this.props.node.attrs };
-        attrs.visibility = true;
-        this.dom.classList.add("ProseMirror-selectednode");
-        if (!this.props.innerView) this.open();
-    }
-
-    deselectNode() {
-        const attrs = { ...this.props.node.attrs };
-        attrs.visibility = false;
-        this.dom.classList.remove("ProseMirror-selectednode");
-        if (this.props.innerView) this.close();
-    }
-    open() {
-        // Append a tooltip to the outer node
-        const tooltip = this.dom.appendChild(document.createElement("div"));
-        tooltip.className = "footnote-tooltip";
-        // And put a sub-ProseMirror into that
-        this.props.innerView.defineProperty(new EditorView(tooltip, {
-            // You can use any node as an editor document
-            state: EditorState.create({
-                doc: this.props.node,
-                plugins: [keymap(baseKeymap),
-                keymap({
-                    "Mod-z": () => undo(this.props.outerView.state, this.props.outerView.dispatch),
-                    "Mod-y": () => redo(this.props.outerView.state, this.props.outerView.dispatch),
-                    "Mod-b": toggleMark(schema.marks.strong)
-                }),
-                    // new Plugin({
-                    //     view(newView) {
-                    //         // TODO -- make this work with RichTextMenu
-                    //         // return FormattedTextBox.getToolTip(newView);
-                    //     }
-                    // })
-                ],
-
-            }),
-            // This is the magic part
-            dispatchTransaction: this.dispatchInner.bind(this),
-            handleDOMEvents: {
-                pointerdown: ((view: any, e: PointerEvent) => {
-                    // Kludge to prevent issues due to the fact that the whole
-                    // footnote is node-selected (and thus DOM-selected) when
-                    // the parent editor is focused.
-                    e.stopPropagation();
-                    document.addEventListener("pointerup", this.ignore, true);
-                    if (this.props.outerView.hasFocus()) this.props.innerView.focus();
-                }) as any
-            }
-        }));
-        setTimeout(() => this.props.innerView && this.props.innerView.docView.setSelection(0, 0, this.props.innerView.root, true), 0);
-    }
-
-    ignore = (e: PointerEvent) => {
-        e.stopPropagation();
-        document.removeEventListener("pointerup", this.ignore, true);
-    }
-
-    dispatchInner(tr: any) {
-        const { state, transactions } = this.props.innerView.state.applyTransaction(tr);
-        this.props.innerView.updateState(state);
-
-        if (!tr.getMeta("fromOutside")) {
-            const outerTr = this.props.outerView.state.tr, offsetMap = StepMap.offset(this.props.getPos() + 1);
-            for (const transaction of transactions) {
-                const steps = transaction.steps;
-                for (const step of steps) {
-                    outerTr.step(step.map(offsetMap));
-                }
-            }
-            if (outerTr.docChanged) this.props.outerView.dispatch(outerTr);
-        }
-    }
-    update(node: any) {
-        if (!node.sameMarkup(this.props.node)) return false;
-        this._node = node; //not sure
-        if (this.props.innerView) {
-            const state = this.props.innerView.state;
-            const start = node.content.findDiffStart(state.doc.content);
-            if (start !== null) {
-                let { a: endA, b: endB } = node.content.findDiffEnd(state.doc.content);
-                const overlap = start - Math.min(endA, endB);
-                if (overlap > 0) { endA += overlap; endB += overlap; }
-                this.props.innerView.dispatch(
-                    state.tr
-                        .replace(start, endB, node.slice(start, endA))
-                        .setMeta("fromOutside", true));
-            }
-        }
-        return true;
-    }
-    onPointerUp = (e: any) => {
-        this.toggle(e);
-    }
-
-    toggle = (e: any) => {
-        e.preventDefault();
-        if (this.props.innerView) this.close();
-        else {
-            this.open();
-        }
-    }
-
-    close() {
-        this.props.innerView && this.props.innerView.destroy();
-        this._innerView = null;
-        this.dom.textContent = "";
-    }
-
-    destroy() {
-        if (this.props.innerView) this.close();
-    }
-
-    stopEvent(event: any) {
-        return this.props.innerView && this.props.innerView.dom.contains(event.target);
-    }
-
-    ignoreMutation() { return true; }
-
-
-    render() {
-        return (
-            <div
-                className="footnote"
-                onPointerUp={this.onPointerUp}>
-                <div className="footnote-tooltip" >
-
-                </div >
-            </div>
-        );
-    }
-}
diff --git a/src/client/util/ImageResizeView.tsx b/src/client/util/ImageResizeView.tsx
deleted file mode 100644
index 4f66475fb..000000000
--- a/src/client/util/ImageResizeView.tsx
+++ /dev/null
@@ -1,138 +0,0 @@
-import { NodeSelection } from "prosemirror-state";
-import { Doc } from "../../new_fields/Doc";
-import { DocServer } from "../DocServer";
-import { DocumentManager } from "./DocumentManager";
-import React = require("react");
-
-import { schema } from "./schema_rts";
-
-interface IImageResizeView {
-    node: any;
-    view: any;
-    getPos: any;
-    addDocTab: any;
-}
-
-export class ImageResizeView extends React.Component<IImageResizeView> {
-    constructor(props: IImageResizeView) {
-        super(props);
-    }
-
-    onClickImg = (e: any) => {
-        e.stopPropagation();
-        e.preventDefault();
-        if (this.props.view.state.selection.node && this.props.view.state.selection.node.type !== this.props.view.state.schema.nodes.image) {
-            this.props.view.dispatch(this.props.view.state.tr.setSelection(new NodeSelection(this.props.view.state.doc.resolve(this.props.view.state.selection.from - 2))));
-        }
-    }
-
-    onPointerDownImg = (e: any) => {
-        if (e.ctrlKey) {
-            e.preventDefault();
-            e.stopPropagation();
-            DocServer.GetRefField(this.props.node.attrs.docid).then(async linkDoc =>
-                (linkDoc instanceof Doc) &&
-                DocumentManager.Instance.FollowLink(linkDoc, this.props.view.state.schema.Document,
-                    document => this.props.addDocTab(document, this.props.node.attrs.location ? this.props.node.attrs.location : "inTab"), false));
-        }
-    }
-
-    onPointerDownHandle = (e: any) => {
-        e.preventDefault();
-        e.stopPropagation();
-        const elementImage = document.getElementById("imageId") as HTMLElement;
-        const wid = Number(getComputedStyle(elementImage).width.replace(/px/, ""));
-        const hgt = Number(getComputedStyle(elementImage).height.replace(/px/, ""));
-        const startX = e.pageX;
-        const startWidth = parseFloat(this.props.node.attrs.width);
-
-        const onpointermove = (e: any) => {
-            const elementOuter = document.getElementById("outerId") as HTMLElement;
-
-            const currentX = e.pageX;
-            const diffInPx = currentX - startX;
-            elementOuter.style.width = `${startWidth + diffInPx}`;
-            elementOuter.style.height = `${(startWidth + diffInPx) * hgt / wid}`;
-        };
-
-        const onpointerup = () => {
-            document.removeEventListener("pointermove", onpointermove);
-            document.removeEventListener("pointerup", onpointerup);
-            const pos = this.props.view.state.selection.from;
-            const elementOuter = document.getElementById("outerId") as HTMLElement;
-            this.props.view.dispatch(this.props.view.state.tr.setNodeMarkup(this.props.getPos(), null, { ...this.props.node.attrs, width: elementOuter.style.width, height: elementOuter.style.height }));
-            this.props.view.dispatch(this.props.view.state.tr.setSelection(new NodeSelection(this.props.view.state.doc.resolve(pos))));
-        };
-
-        document.addEventListener("pointermove", onpointermove);
-        document.addEventListener("pointerup", onpointerup);
-    }
-
-    selectNode() {
-        const elementImage = document.getElementById("imageId") as HTMLElement;
-        const elementHandle = document.getElementById("handleId") as HTMLElement;
-
-        elementImage.classList.add("ProseMirror-selectednode");
-        elementHandle.style.display = "";
-    }
-
-    deselectNode() {
-        const elementImage = document.getElementById("imageId") as HTMLElement;
-        const elementHandle = document.getElementById("handleId") as HTMLElement;
-
-        elementImage.classList.remove("ProseMirror-selectednode");
-        elementHandle.style.display = "none";
-    }
-
-
-    render() {
-
-        const outerStyle = {
-            width: this.props.node.attrs.width,
-            height: this.props.node.attrs.height,
-            display: "inline-block",
-            overflow: "hidden",
-            float: this.props.node.attrs.float
-        };
-
-        const imageStyle = {
-            width: "100%",
-        };
-
-        const handleStyle = {
-            position: "absolute",
-            width: "20px",
-            heiht: "20px",
-            backgroundColor: "blue",
-            borderRadius: "15px",
-            display: "none",
-            bottom: "-10px",
-            right: "-10px"
-
-        };
-
-
-
-        return (
-            <div id="outer"
-                style={outerStyle}
-            >
-                <img
-                    id="imageId"
-                    style={imageStyle}
-                    src={this.props.node.src}
-                    onClick={this.onClickImg}
-                    onPointerDown={this.onPointerDownImg}
-
-                >
-                </img>
-                <span
-                    id="handleId"
-                    onPointerDown={this.onPointerDownHandle}
-                >
-
-                </span>
-            </div >
-        );
-    }
-}
\ No newline at end of file
diff --git a/src/client/util/ParagraphNodeSpec.ts b/src/client/util/ParagraphNodeSpec.ts
deleted file mode 100644
index 0a3b68217..000000000
--- a/src/client/util/ParagraphNodeSpec.ts
+++ /dev/null
@@ -1,143 +0,0 @@
-import clamp from './clamp';
-import convertToCSSPTValue from './convertToCSSPTValue';
-import toCSSLineSpacing from './toCSSLineSpacing';
-import { Node, DOMOutputSpec } from 'prosemirror-model';
-
-//import type { NodeSpec } from './Types';
-type NodeSpec = {
-    attrs?: { [key: string]: any },
-    content?: string,
-    draggable?: boolean,
-    group?: string,
-    inline?: boolean,
-    name?: string,
-    parseDOM?: Array<any>,
-    toDOM?: (node: any) => DOMOutputSpec,
-};
-
-// This assumes that every 36pt maps to one indent level.
-export const INDENT_MARGIN_PT_SIZE = 36;
-export const MIN_INDENT_LEVEL = 0;
-export const MAX_INDENT_LEVEL = 7;
-export const ATTRIBUTE_INDENT = 'data-indent';
-
-export const EMPTY_CSS_VALUE = new Set(['', '0%', '0pt', '0px']);
-
-const ALIGN_PATTERN = /(left|right|center|justify)/;
-
-// https://github.com/ProseMirror/prosemirror-schema-basic/blob/master/src/schema-basic.js
-// :: NodeSpec A plain paragraph textblock. Represented in the DOM
-// as a `<p>` element.
-const ParagraphNodeSpec: NodeSpec = {
-    attrs: {
-        align: { default: null },
-        color: { default: null },
-        id: { default: null },
-        indent: { default: null },
-        inset: { default: null },
-        lineSpacing: { default: null },
-        // TODO: Add UI to let user edit / clear padding.
-        paddingBottom: { default: null },
-        // TODO: Add UI to let user edit / clear padding.
-        paddingTop: { default: null },
-    },
-    content: 'inline*',
-    group: 'block',
-    parseDOM: [{ tag: 'p', getAttrs }],
-    toDOM,
-};
-
-function getAttrs(dom: HTMLElement): Object {
-    const {
-        lineHeight,
-        textAlign,
-        marginLeft,
-        paddingTop,
-        paddingBottom,
-    } = dom.style;
-
-    let align = dom.getAttribute('align') || textAlign || '';
-    align = ALIGN_PATTERN.test(align) ? align : "";
-
-    let indent = parseInt(dom.getAttribute(ATTRIBUTE_INDENT) || "", 10);
-
-    if (!indent && marginLeft) {
-        indent = convertMarginLeftToIndentValue(marginLeft);
-    }
-
-    indent = indent || MIN_INDENT_LEVEL;
-
-    const lineSpacing = lineHeight ? toCSSLineSpacing(lineHeight) : null;
-
-    const id = dom.getAttribute('id') || '';
-    return { align, indent, lineSpacing, paddingTop, paddingBottom, id };
-}
-
-function toDOM(node: Node): DOMOutputSpec {
-    const {
-        align,
-        indent,
-        inset,
-        lineSpacing,
-        paddingTop,
-        paddingBottom,
-        id,
-    } = node.attrs;
-    const attrs: { [key: string]: any } | null = {};
-
-    let style = '';
-    if (align && align !== 'left') {
-        style += `text-align: ${align};`;
-    }
-
-    if (lineSpacing) {
-        const cssLineSpacing = toCSSLineSpacing(lineSpacing);
-        style +=
-            `line-height: ${cssLineSpacing};` +
-            // This creates the local css variable `--czi-content-line-height`
-            // that its children may apply.
-            `--czi-content-line-height: ${cssLineSpacing}`;
-    }
-
-    if (paddingTop && !EMPTY_CSS_VALUE.has(paddingTop)) {
-        style += `padding-top: ${paddingTop};`;
-    }
-
-    if (paddingBottom && !EMPTY_CSS_VALUE.has(paddingBottom)) {
-        style += `padding-bottom: ${paddingBottom};`;
-    }
-
-    if (indent) {
-        style += `text-indent: ${indent}; padding-left: ${indent < 0 ? -indent : undefined};`;
-    }
-
-    if (inset) {
-        style += `margin-left: ${inset}; margin-right: ${inset};`;
-    }
-
-    style && (attrs.style = style);
-
-    if (indent) {
-        attrs[ATTRIBUTE_INDENT] = String(indent);
-    }
-
-    if (id) {
-        attrs.id = id;
-    }
-
-    return ['p', attrs, 0];
-}
-
-export const toParagraphDOM = toDOM;
-export const getParagraphNodeAttrs = getAttrs;
-
-export function convertMarginLeftToIndentValue(marginLeft: string): number {
-    const ptValue = convertToCSSPTValue(marginLeft);
-    return clamp(
-        MIN_INDENT_LEVEL,
-        Math.floor(ptValue / INDENT_MARGIN_PT_SIZE),
-        MAX_INDENT_LEVEL
-    );
-}
-
-export default ParagraphNodeSpec;
\ No newline at end of file
diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts
deleted file mode 100644
index 356f20ce6..000000000
--- a/src/client/util/ProsemirrorExampleTransfer.ts
+++ /dev/null
@@ -1,241 +0,0 @@
-import { chainCommands, exitCode, joinDown, joinUp, lift, selectParentNode, setBlockType, splitBlockKeepMarks, toggleMark, wrapIn } from "prosemirror-commands";
-import { redo, undo } from "prosemirror-history";
-import { undoInputRule } from "prosemirror-inputrules";
-import { Schema } from "prosemirror-model";
-import { liftListItem, sinkListItem } from "./prosemirrorPatches.js";
-import { splitListItem, wrapInList, } from "prosemirror-schema-list";
-import { EditorState, Transaction, TextSelection } from "prosemirror-state";
-import { SelectionManager } from "./SelectionManager";
-import { Docs } from "../documents/Documents";
-import { NumCast, BoolCast, Cast, StrCast } from "../../new_fields/Types";
-import { Doc } from "../../new_fields/Doc";
-import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
-import { Id } from "../../new_fields/FieldSymbols";
-
-const mac = typeof navigator !== "undefined" ? /Mac/.test(navigator.platform) : false;
-
-export type KeyMap = { [key: string]: any };
-
-export let updateBullets = (tx2: Transaction, schema: Schema, mapStyle?: string) => {
-    let fontSize: number | undefined = undefined;
-    tx2.doc.descendants((node: any, offset: any, index: any) => {
-        if (node.type === schema.nodes.ordered_list || node.type === schema.nodes.list_item) {
-            const path = (tx2.doc.resolve(offset) as any).path;
-            let depth = Array.from(path).reduce((p: number, c: any) => p + (c.hasOwnProperty("type") && c.type === schema.nodes.ordered_list ? 1 : 0), 0);
-            if (node.type === schema.nodes.ordered_list) depth++;
-            fontSize = depth === 1 && node.attrs.setFontSize ? Number(node.attrs.setFontSize) : fontSize;
-            const fsize = fontSize && node.type === schema.nodes.ordered_list ? Math.max(6, fontSize - (depth - 1) * 4) : undefined;
-            tx2.setNodeMarkup(offset, node.type, { ...node.attrs, mapStyle: mapStyle ? mapStyle : node.attrs.mapStyle, bulletStyle: depth, inheritedFontSize: fsize }, node.marks);
-        }
-    });
-    return tx2;
-};
-export default function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKeys?: KeyMap): KeyMap {
-    const keys: { [key: string]: any } = {};
-
-    function bind(key: string, cmd: any) {
-        if (mapKeys) {
-            const mapped = mapKeys[key];
-            if (mapped === false) return;
-            if (mapped) key = mapped;
-        }
-        keys[key] = cmd;
-    }
-
-    bind("Mod-z", undo);
-    bind("Shift-Mod-z", redo);
-    bind("Backspace", undoInputRule);
-
-    !mac && bind("Mod-y", redo);
-
-    bind("Alt-ArrowUp", joinUp);
-    bind("Alt-ArrowDown", joinDown);
-    bind("Mod-BracketLeft", lift);
-    bind("Escape", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
-        dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from)));
-        (document.activeElement as any).blur?.();
-        SelectionManager.DeselectAll();
-    });
-
-    bind("Mod-b", toggleMark(schema.marks.strong));
-    bind("Mod-B", toggleMark(schema.marks.strong));
-
-    bind("Mod-e", toggleMark(schema.marks.em));
-    bind("Mod-E", toggleMark(schema.marks.em));
-
-    bind("Mod-u", toggleMark(schema.marks.underline));
-    bind("Mod-U", toggleMark(schema.marks.underline));
-
-    bind("Mod-`", toggleMark(schema.marks.code));
-
-    bind("Ctrl-.", wrapInList(schema.nodes.bullet_list));
-
-    bind("Ctrl-n", wrapInList(schema.nodes.ordered_list));
-
-    bind("Ctrl->", wrapIn(schema.nodes.blockquote));
-
-    // bind("^", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
-    //     let newNode = schema.nodes.footnote.create({});
-    //     if (dispatch && state.selection.from === state.selection.to) {
-    //         let tr = state.tr;
-    //         tr.replaceSelectionWith(newNode); // replace insertion with a footnote.
-    //         dispatch(tr.setSelection(new NodeSelection( // select the footnote node to open its display
-    //             tr.doc.resolve(  // get the location of the footnote node by subtracting the nodesize of the footnote from the current insertion point anchor (which will be immediately after the footnote node)
-    //                 tr.selection.anchor - tr.selection.$anchor.nodeBefore!.nodeSize))));
-    //         return true;
-    //     }
-    //     return false;
-    // });
-
-
-    const cmd = chainCommands(exitCode, (state, dispatch) => {
-        if (dispatch) {
-            dispatch(state.tr.replaceSelectionWith(schema.nodes.hard_break.create()).scrollIntoView());
-            return true;
-        }
-        return false;
-    });
-    bind("Mod-Enter", cmd);
-    bind("Shift-Enter", cmd);
-    mac && bind("Ctrl-Enter", cmd);
-
-
-    bind("Shift-Ctrl-0", setBlockType(schema.nodes.paragraph));
-
-    bind("Shift-Ctrl-\\", setBlockType(schema.nodes.code_block));
-
-    for (let i = 1; i <= 6; i++) {
-        bind("Shift-Ctrl-" + i, setBlockType(schema.nodes.heading, { level: i }));
-    }
-
-    const hr = schema.nodes.horizontal_rule;
-    bind("Mod-_", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
-        dispatch(state.tr.replaceSelectionWith(hr.create()).scrollIntoView());
-        return true;
-    });
-
-    bind("Tab", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
-        const ref = state.selection;
-        const range = ref.$from.blockRange(ref.$to);
-        const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
-        if (!sinkListItem(schema.nodes.list_item)(state, (tx2: Transaction) => {
-            const tx3 = updateBullets(tx2, schema);
-            marks && tx3.ensureMarks([...marks]);
-            marks && tx3.setStoredMarks([...marks]);
-            dispatch(tx3);
-        })) { // couldn't sink into an existing list, so wrap in a new one
-            const newstate = state.applyTransaction(state.tr.setSelection(TextSelection.create(state.doc, range!.start, range!.end)));
-            if (!wrapInList(schema.nodes.ordered_list)(newstate.state, (tx2: Transaction) => {
-                const tx3 = updateBullets(tx2, schema);
-                // when promoting to a list, assume list will format things so don't copy the stored marks.
-                marks && tx3.ensureMarks([...marks]);
-                marks && tx3.setStoredMarks([...marks]);
-                dispatch(tx3);
-            })) {
-                console.log("bullet promote fail");
-            }
-        }
-    });
-
-    bind("Shift-Tab", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
-        const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
-
-        if (!liftListItem(schema.nodes.list_item)(state.tr, (tx2: Transaction) => {
-            const tx3 = updateBullets(tx2, schema);
-            marks && tx3.ensureMarks([...marks]);
-            marks && tx3.setStoredMarks([...marks]);
-            dispatch(tx3);
-        })) {
-            console.log("bullet demote fail");
-        }
-    });
-    bind("Ctrl-Enter", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
-        const layoutDoc = props.Document;
-        const originalDoc = layoutDoc.rootDocument || layoutDoc;
-        if (originalDoc instanceof Doc) {
-            const layoutKey = StrCast(originalDoc.layoutKey);
-            const newDoc = Docs.Create.TextDocument("", {
-                layout: Cast(originalDoc.layout, Doc, null) || FormattedTextBox.DefaultLayout,
-                layoutKey,
-                _singleLine: BoolCast(originalDoc._singleLine),
-                x: NumCast(originalDoc.x), y: NumCast(originalDoc.y) + NumCast(originalDoc._height) + 10, _width: NumCast(layoutDoc._width), _height: NumCast(layoutDoc._height)
-            });
-            if (layoutKey !== "layout" && originalDoc[layoutKey] instanceof Doc) {
-                newDoc[layoutKey] = originalDoc[layoutKey];
-            }
-            FormattedTextBox.SelectOnLoad = newDoc[Id];
-            props.addDocument(newDoc);
-        }
-    });
-
-    const splitMetadata = (marks: any, tx: Transaction) => {
-        marks && tx.ensureMarks(marks.filter((val: any) => val.type !== schema.marks.metadata && val.type !== schema.marks.metadataKey && val.type !== schema.marks.metadataVal));
-        marks && tx.setStoredMarks(marks.filter((val: any) => val.type !== schema.marks.metadata && val.type !== schema.marks.metadataKey && val.type !== schema.marks.metadataVal));
-        return tx;
-    };
-    const addTextOnRight = (force: boolean) => {
-        const layoutDoc = props.Document;
-        const originalDoc = layoutDoc.rootDocument || layoutDoc;
-        if (force || props.Document._singleLine) {
-            const layoutKey = StrCast(originalDoc.layoutKey);
-            const newDoc = Docs.Create.TextDocument("", {
-                layout: Cast(originalDoc.layout, Doc, null) || FormattedTextBox.DefaultLayout,
-                layoutKey,
-                _singleLine: BoolCast(originalDoc._singleLine),
-                x: NumCast(originalDoc.x) + NumCast(originalDoc._width) + 10, y: NumCast(originalDoc.y), _width: NumCast(layoutDoc._width), _height: NumCast(layoutDoc._height)
-            });
-            if (layoutKey !== "layout" && originalDoc[layoutKey] instanceof Doc) {
-                newDoc[layoutKey] = originalDoc[layoutKey];
-            }
-            FormattedTextBox.SelectOnLoad = newDoc[Id];
-            props.addDocument(newDoc);
-            return true;
-        }
-        return false;
-    };
-    bind("Alt-Enter", (state: EditorState<S>, dispatch: (tx: Transaction<Schema<any, any>>) => void) => {
-        return addTextOnRight(true);
-    });
-    bind("Enter", (state: EditorState<S>, dispatch: (tx: Transaction<Schema<any, any>>) => void) => {
-        if (addTextOnRight(false)) return true;
-        const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
-        if (!splitListItem(schema.nodes.list_item)(state, dispatch)) {
-            if (!splitBlockKeepMarks(state, (tx3: Transaction) => {
-                splitMetadata(marks, tx3);
-                if (!liftListItem(schema.nodes.list_item)(tx3, dispatch as ((tx: Transaction<Schema<any, any>>) => void))) {
-                    dispatch(tx3);
-                }
-            })) {
-                return false;
-            }
-        }
-        return true;
-    });
-    bind("Space", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
-        const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
-        dispatch(splitMetadata(marks, state.tr));
-        return false;
-    });
-    bind(":", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
-        const range = state.selection.$from.blockRange(state.selection.$to, (node: any) => {
-            return !node.marks || !node.marks.find((m: any) => m.type === schema.marks.metadata);
-        });
-        const path = (state.doc.resolve(state.selection.from - 1) as any).path;
-        const spaceSeparator = path[path.length - 3].childCount > 1 ? 0 : -1;
-        const anchor = range!.end - path[path.length - 3].lastChild.nodeSize + spaceSeparator;
-        if (anchor >= 0) {
-            const textsel = TextSelection.create(state.doc, anchor, range!.end);
-            const text = range ? state.doc.textBetween(textsel.from, textsel.to) : "";
-            let whitespace = text.length - 1;
-            for (; whitespace >= 0 && text[whitespace] !== " "; whitespace--) { }
-            if (text.endsWith(":")) {
-                dispatch(state.tr.addMark(textsel.from + whitespace + 1, textsel.to, schema.marks.metadata.create() as any).
-                    addMark(textsel.from + whitespace + 1, textsel.to - 2, schema.marks.metadataKey.create() as any));
-            }
-        }
-        return false;
-    });
-
-
-    return keys;
-}
diff --git a/src/client/util/RichTextMenu.scss b/src/client/util/RichTextMenu.scss
deleted file mode 100644
index 43cc23ecd..000000000
--- a/src/client/util/RichTextMenu.scss
+++ /dev/null
@@ -1,121 +0,0 @@
-@import "../views/globalCssVariables";
-
-.button-dropdown-wrapper {
-    position: relative;
-
-    .dropdown-button {
-        width: 15px;
-        padding-left: 5px;
-        padding-right: 5px;
-    }
-
-    .dropdown-button-combined {
-        width: 50px;
-        display: flex;
-        justify-content: space-between;
-
-        svg:nth-child(2) {
-            margin-top: 2px;
-        }
-    }
-
-    .dropdown {
-        position: absolute;
-        top: 35px;
-        left: 0;
-        background-color: #323232;
-        color: $light-color-secondary;
-        border: 1px solid #4d4d4d;
-        border-radius: 0 6px 6px 6px;
-        box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25);
-        min-width: 150px;
-        padding: 5px;
-        font-size: 12px;
-        z-index: 10001;
-
-        button {
-            background-color: #323232;
-            border: 1px solid black;
-            border-radius: 1px;
-            padding: 6px;
-            margin: 5px 0;
-            font-size: 10px;
-
-            &:hover {
-                background-color: black;
-            }
-
-            &:last-child {
-                margin-bottom: 0;
-            }
-        }
-    }
-
-    input {
-        color: black;
-    }
-}
-
-.link-menu {
-    .divider {
-        background-color: white;
-        height: 1px;
-        width: 100%;
-    }
-}
-
-.color-preview-button {
-    .color-preview {
-        width: 100%;
-        height: 3px;
-        margin-top: 3px;
-    }
-}
-
-.color-wrapper {
-    display: flex;
-    flex-wrap: wrap;
-    justify-content: space-between;
-
-    button.color-button {
-        width: 20px;
-        height: 20px;
-        border-radius: 15px !important;
-        margin: 3px;
-        border: 2px solid transparent !important;
-        padding: 3px;
-
-        &.active {
-            border: 2px solid white !important;
-        }
-    }
-}
-
-select {
-    background-color: #323232;
-    color: white;
-    border: 1px solid black;
-    // border-top: none;
-    // border-bottom: none;
-    font-size: 12px;
-    height: 100%;
-    margin-right: 3px;
-
-    &:focus,
-    &:hover {
-        background-color: black;
-    }
-
-    &::-ms-expand {
-        color: white;
-    }
-}
-
-.row-2 {
-    display: flex;
-    justify-content: space-between;
-
-    >div {
-        display: flex;
-    }
-}
\ No newline at end of file
diff --git a/src/client/util/RichTextMenu.tsx b/src/client/util/RichTextMenu.tsx
deleted file mode 100644
index 140635a1d..000000000
--- a/src/client/util/RichTextMenu.tsx
+++ /dev/null
@@ -1,875 +0,0 @@
-import React = require("react");
-import AntimodeMenu from "../views/AntimodeMenu";
-import { observable, action, } from "mobx";
-import { observer } from "mobx-react";
-import { Mark, MarkType, Node as ProsNode, NodeType, ResolvedPos, Schema } from "prosemirror-model";
-import { schema } from "./schema_rts";
-import { EditorView } from "prosemirror-view";
-import { EditorState, NodeSelection, TextSelection } from "prosemirror-state";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
-import { faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSubscript, faSuperscript, faIndent, faEyeDropper, faCaretDown, faPalette, faHighlighter, faLink, faPaintRoller, faSleigh } from "@fortawesome/free-solid-svg-icons";
-import { updateBullets } from "./ProsemirrorExampleTransfer";
-import { FieldViewProps } from "../views/nodes/FieldView";
-import { Cast, StrCast } from "../../new_fields/Types";
-import { FormattedTextBoxProps } from "../views/nodes/FormattedTextBox";
-import { unimplementedFunction, Utils } from "../../Utils";
-import { wrapInList } from "prosemirror-schema-list";
-import { PastelSchemaPalette, DarkPastelSchemaPalette } from '../../new_fields/SchemaHeaderField';
-import "./RichTextMenu.scss";
-import { DocServer } from "../DocServer";
-import { Doc } from "../../new_fields/Doc";
-import { SelectionManager } from "./SelectionManager";
-import { LinkManager } from "./LinkManager";
-const { toggleMark, setBlockType } = require("prosemirror-commands");
-
-library.add(faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper, faCaretDown, faPalette, faHighlighter, faLink, faPaintRoller);
-
-@observer
-export default class RichTextMenu extends AntimodeMenu {
-    static Instance: RichTextMenu;
-    public overMenu: boolean = false; // kind of hacky way to prevent selects not being selectable
-
-    private view?: EditorView;
-    public editorProps: FieldViewProps & FormattedTextBoxProps | undefined;
-
-    public _brushMap: Map<string, Set<Mark>> = new Map();
-    private fontSizeOptions: { mark: Mark | null, title: string, label: string, command: any, hidden?: boolean, style?: {} }[];
-    private fontFamilyOptions: { mark: Mark | null, title: string, label: string, command: any, hidden?: boolean, style?: {} }[];
-    private listTypeOptions: { node: NodeType | any | null, title: string, label: string, command: any, style?: {} }[];
-    private fontColors: (string | undefined)[];
-    private highlightColors: (string | undefined)[];
-
-    @observable private collapsed: boolean = false;
-    @observable private boldActive: boolean = false;
-    @observable private italicsActive: boolean = false;
-    @observable private underlineActive: boolean = false;
-    @observable private strikethroughActive: boolean = false;
-    @observable private subscriptActive: boolean = false;
-    @observable private superscriptActive: boolean = false;
-
-    @observable private activeFontSize: string = "";
-    @observable private activeFontFamily: string = "";
-    @observable private activeListType: string = "";
-
-    @observable private brushIsEmpty: boolean = true;
-    @observable private brushMarks: Set<Mark> = new Set();
-    @observable private showBrushDropdown: boolean = false;
-
-    @observable private activeFontColor: string = "black";
-    @observable private showColorDropdown: boolean = false;
-
-    @observable private activeHighlightColor: string = "transparent";
-    @observable private showHighlightDropdown: boolean = false;
-
-    @observable private currentLink: string | undefined = "";
-    @observable private showLinkDropdown: boolean = false;
-
-    constructor(props: Readonly<{}>) {
-        super(props);
-        RichTextMenu.Instance = this;
-        this._canFade = false;
-
-        this.fontSizeOptions = [
-            { mark: schema.marks.pFontSize.create({ fontSize: 7 }), title: "Set font size", label: "7pt", command: this.changeFontSize },
-            { mark: schema.marks.pFontSize.create({ fontSize: 8 }), title: "Set font size", label: "8pt", command: this.changeFontSize },
-            { mark: schema.marks.pFontSize.create({ fontSize: 9 }), title: "Set font size", label: "9pt", command: this.changeFontSize },
-            { mark: schema.marks.pFontSize.create({ fontSize: 10 }), title: "Set font size", label: "10pt", command: this.changeFontSize },
-            { mark: schema.marks.pFontSize.create({ fontSize: 12 }), title: "Set font size", label: "12pt", command: this.changeFontSize },
-            { mark: schema.marks.pFontSize.create({ fontSize: 14 }), title: "Set font size", label: "14pt", command: this.changeFontSize },
-            { mark: schema.marks.pFontSize.create({ fontSize: 16 }), title: "Set font size", label: "16pt", command: this.changeFontSize },
-            { mark: schema.marks.pFontSize.create({ fontSize: 18 }), title: "Set font size", label: "18pt", command: this.changeFontSize },
-            { mark: schema.marks.pFontSize.create({ fontSize: 20 }), title: "Set font size", label: "20pt", command: this.changeFontSize },
-            { mark: schema.marks.pFontSize.create({ fontSize: 24 }), title: "Set font size", label: "24pt", command: this.changeFontSize },
-            { mark: schema.marks.pFontSize.create({ fontSize: 32 }), title: "Set font size", label: "32pt", command: this.changeFontSize },
-            { mark: schema.marks.pFontSize.create({ fontSize: 48 }), title: "Set font size", label: "48pt", command: this.changeFontSize },
-            { mark: schema.marks.pFontSize.create({ fontSize: 72 }), title: "Set font size", label: "72pt", command: this.changeFontSize },
-            { mark: null, title: "", label: "various", command: unimplementedFunction, hidden: true },
-            { mark: null, title: "", label: "13pt", command: unimplementedFunction, hidden: true }, // this is here because the default size is 13, but there is no actual 13pt option
-        ];
-
-        this.fontFamilyOptions = [
-            { mark: schema.marks.pFontFamily.create({ family: "Times New Roman" }), title: "Set font family", label: "Times New Roman", command: this.changeFontFamily, style: { fontFamily: "Times New Roman" } },
-            { mark: schema.marks.pFontFamily.create({ family: "Arial" }), title: "Set font family", label: "Arial", command: this.changeFontFamily, style: { fontFamily: "Arial" } },
-            { mark: schema.marks.pFontFamily.create({ family: "Georgia" }), title: "Set font family", label: "Georgia", command: this.changeFontFamily, style: { fontFamily: "Georgia" } },
-            { mark: schema.marks.pFontFamily.create({ family: "Comic Sans MS" }), title: "Set font family", label: "Comic Sans MS", command: this.changeFontFamily, style: { fontFamily: "Comic Sans MS" } },
-            { mark: schema.marks.pFontFamily.create({ family: "Tahoma" }), title: "Set font family", label: "Tahoma", command: this.changeFontFamily, style: { fontFamily: "Tahoma" } },
-            { mark: schema.marks.pFontFamily.create({ family: "Impact" }), title: "Set font family", label: "Impact", command: this.changeFontFamily, style: { fontFamily: "Impact" } },
-            { mark: schema.marks.pFontFamily.create({ family: "Crimson Text" }), title: "Set font family", label: "Crimson Text", command: this.changeFontFamily, style: { fontFamily: "Crimson Text" } },
-            { mark: null, title: "", label: "various", command: unimplementedFunction, hidden: true },
-            // { mark: null, title: "", label: "default", command: unimplementedFunction, hidden: true },
-        ];
-
-        this.listTypeOptions = [
-            { node: schema.nodes.ordered_list.create({ mapStyle: "bullet" }), title: "Set list type", label: ":", command: this.changeListType },
-            { node: schema.nodes.ordered_list.create({ mapStyle: "decimal" }), title: "Set list type", label: "1.1", command: this.changeListType },
-            { node: schema.nodes.ordered_list.create({ mapStyle: "multi" }), title: "Set list type", label: "1.A", command: this.changeListType },
-            { node: undefined, title: "Set list type", label: "Remove", command: this.changeListType },
-        ];
-
-        this.fontColors = [
-            DarkPastelSchemaPalette.get("pink2"),
-            DarkPastelSchemaPalette.get("purple4"),
-            DarkPastelSchemaPalette.get("bluegreen1"),
-            DarkPastelSchemaPalette.get("yellow4"),
-            DarkPastelSchemaPalette.get("red2"),
-            DarkPastelSchemaPalette.get("bluegreen7"),
-            DarkPastelSchemaPalette.get("bluegreen5"),
-            DarkPastelSchemaPalette.get("orange1"),
-            "#757472",
-            "#000"
-        ];
-
-        this.highlightColors = [
-            PastelSchemaPalette.get("pink2"),
-            PastelSchemaPalette.get("purple4"),
-            PastelSchemaPalette.get("bluegreen1"),
-            PastelSchemaPalette.get("yellow4"),
-            PastelSchemaPalette.get("red2"),
-            PastelSchemaPalette.get("bluegreen7"),
-            PastelSchemaPalette.get("bluegreen5"),
-            PastelSchemaPalette.get("orange1"),
-            "white",
-            "transparent"
-        ];
-    }
-
-    @action
-    changeView(view: EditorView) {
-        this.view = view;
-    }
-
-    update(view: EditorView, lastState: EditorState | undefined) {
-        this.updateFromDash(view, lastState, this.editorProps);
-    }
-
-
-    public MakeLinkToSelection = (linkDocId: string, title: string, location: string, targetDocId: string): string => {
-        if (this.view) {
-            const link = this.view.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + linkDocId), title: title, location: location, linkId: linkDocId, targetId: targetDocId });
-            this.view.dispatch(this.view.state.tr.removeMark(this.view.state.selection.from, this.view.state.selection.to, this.view.state.schema.marks.link).
-                addMark(this.view.state.selection.from, this.view.state.selection.to, link));
-            return this.view.state.selection.$from.nodeAfter?.text || "";
-        }
-        return "";
-    }
-
-    @action
-    public async updateFromDash(view: EditorView, lastState: EditorState | undefined, props: any) {
-        if (!view) {
-            console.log("no editor?  why?");
-            return;
-        }
-        this.view = view;
-        const state = view.state;
-        props && (this.editorProps = props);
-
-        // Don't do anything if the document/selection didn't change
-        if (lastState && lastState.doc.eq(state.doc) && lastState.selection.eq(state.selection)) return;
-
-        // update active marks
-        const activeMarks = this.getActiveMarksOnSelection();
-        this.setActiveMarkButtons(activeMarks);
-
-        // update active font family and size
-        const active = this.getActiveFontStylesOnSelection();
-        const activeFamilies = active && active.get("families");
-        const activeSizes = active && active.get("sizes");
-
-        this.activeFontFamily = !activeFamilies || activeFamilies.length === 0 ? "Arial" : activeFamilies.length === 1 ? String(activeFamilies[0]) : "various";
-        this.activeFontSize = !activeSizes || activeSizes.length === 0 ? "13pt" : activeSizes.length === 1 ? String(activeSizes[0]) + "pt" : "various";
-
-        // update link in current selection
-        const targetTitle = await this.getTextLinkTargetTitle();
-        this.setCurrentLink(targetTitle);
-    }
-
-    setMark = (mark: Mark, state: EditorState<any>, dispatch: any) => {
-        if (mark) {
-            const node = (state.selection as NodeSelection).node;
-            if (node?.type === schema.nodes.ordered_list) {
-                let attrs = node.attrs;
-                if (mark.type === schema.marks.pFontFamily) attrs = { ...attrs, setFontFamily: mark.attrs.family };
-                if (mark.type === schema.marks.pFontSize) attrs = { ...attrs, setFontSize: mark.attrs.fontSize };
-                if (mark.type === schema.marks.pFontColor) attrs = { ...attrs, setFontColor: mark.attrs.color };
-                const tr = updateBullets(state.tr.setNodeMarkup(state.selection.from, node.type, attrs), state.schema);
-                dispatch(tr.setSelection(new NodeSelection(tr.doc.resolve(state.selection.from))));
-            } else {
-                toggleMark(mark.type, mark.attrs)(state, (tx: any) => {
-                    const { from, $from, to, empty } = tx.selection;
-                    if (!tx.doc.rangeHasMark(from, to, mark.type)) {
-                        toggleMark(mark.type, mark.attrs)({ tr: tx, doc: tx.doc, selection: tx.selection, storedMarks: tx.storedMarks }, dispatch);
-                    } else dispatch(tx);
-                });
-            }
-        }
-    }
-
-    // finds font sizes and families in selection
-    getActiveFontStylesOnSelection() {
-        if (!this.view) return;
-
-        const activeFamilies: string[] = [];
-        const activeSizes: string[] = [];
-        const state = this.view.state;
-        const pos = this.view.state.selection.$from;
-        const ref_node = this.reference_node(pos);
-        if (ref_node && ref_node !== this.view.state.doc && ref_node.isText) {
-            ref_node.marks.forEach(m => {
-                m.type === state.schema.marks.pFontFamily && activeFamilies.push(m.attrs.family);
-                m.type === state.schema.marks.pFontSize && activeSizes.push(String(m.attrs.fontSize) + "pt");
-            });
-        }
-
-        const styles = new Map<String, String[]>();
-        styles.set("families", activeFamilies);
-        styles.set("sizes", activeSizes);
-        return styles;
-    }
-
-    getMarksInSelection(state: EditorState<any>) {
-        const found = new Set<Mark>();
-        const { from, to } = state.selection as TextSelection;
-        state.doc.nodesBetween(from, to, (node) => node.marks.forEach(m => found.add(m)));
-        return found;
-    }
-
-    //finds all active marks on selection in given group
-    getActiveMarksOnSelection() {
-        if (!this.view) return;
-
-        const markGroup = [schema.marks.strong, schema.marks.em, schema.marks.underline, schema.marks.strikethrough, schema.marks.superscript, schema.marks.subscript];
-        if (this.view.state.storedMarks) return this.view.state.storedMarks.map(mark => mark.type);
-        //current selection
-        const { empty, ranges, $to } = this.view.state.selection as TextSelection;
-        const state = this.view.state;
-        let activeMarks: MarkType[] = [];
-        if (!empty) {
-            activeMarks = markGroup.filter(mark => {
-                const has = false;
-                for (let i = 0; !has && i < ranges.length; i++) {
-                    return state.doc.rangeHasMark(ranges[i].$from.pos, ranges[i].$to.pos, mark);
-                }
-                return false;
-            });
-        }
-        else {
-            const pos = this.view.state.selection.$from;
-            const ref_node: ProsNode | null = this.reference_node(pos);
-            if (ref_node !== null && ref_node !== this.view.state.doc) {
-                if (ref_node.isText) {
-                }
-                else {
-                    return [];
-                }
-                activeMarks = markGroup.filter(mark_type => {
-                    if (mark_type === state.schema.marks.pFontSize) {
-                        return ref_node.marks.some(m => m.type.name === state.schema.marks.pFontSize.name);
-                    }
-                    const mark = state.schema.mark(mark_type);
-                    return ref_node.marks.includes(mark);
-                });
-            }
-        }
-        return activeMarks;
-    }
-
-    destroy() {
-        this.fadeOut(true);
-    }
-
-    @action
-    setActiveMarkButtons(activeMarks: MarkType[] | undefined) {
-        if (!activeMarks) return;
-
-        this.boldActive = false;
-        this.italicsActive = false;
-        this.underlineActive = false;
-        this.strikethroughActive = false;
-        this.subscriptActive = false;
-        this.superscriptActive = false;
-
-        activeMarks.forEach(mark => {
-            switch (mark.name) {
-                case "strong": this.boldActive = true; break;
-                case "em": this.italicsActive = true; break;
-                case "underline": this.underlineActive = true; break;
-                case "strikethrough": this.strikethroughActive = true; break;
-                case "subscript": this.subscriptActive = true; break;
-                case "superscript": this.superscriptActive = true; break;
-            }
-        });
-    }
-
-    createButton(faIcon: string, title: string, isActive: boolean = false, command?: any, onclick?: any) {
-        const self = this;
-        function onClick(e: React.PointerEvent) {
-            e.preventDefault();
-            e.stopPropagation();
-            self.view && self.view.focus();
-            self.view && command && command(self.view.state, self.view.dispatch, self.view);
-            self.view && onclick && onclick(self.view.state, self.view.dispatch, self.view);
-            self.setActiveMarkButtons(self.getActiveMarksOnSelection());
-        }
-
-        return (
-            <button className={"antimodeMenu-button" + (isActive ? " active" : "")} key={title} title={title} onPointerDown={onClick}>
-                <FontAwesomeIcon icon={faIcon as IconProp} size="lg" />
-            </button>
-        );
-    }
-
-    createMarksDropdown(activeOption: string, options: { mark: Mark | null, title: string, label: string, command: (mark: Mark, view: EditorView) => void, hidden?: boolean, style?: {} }[], key: string): JSX.Element {
-        const items = options.map(({ title, label, hidden, style }) => {
-            if (hidden) {
-                return label === activeOption ?
-                    <option value={label} title={title} key={label} style={style ? style : {}} selected hidden>{label}</option> :
-                    <option value={label} title={title} key={label} style={style ? style : {}} hidden>{label}</option>;
-            }
-            return label === activeOption ?
-                <option value={label} title={title} key={label} style={style ? style : {}} selected>{label}</option> :
-                <option value={label} title={title} key={label} style={style ? style : {}}>{label}</option>;
-        });
-
-        const self = this;
-        function onChange(e: React.ChangeEvent<HTMLSelectElement>) {
-            e.stopPropagation();
-            e.preventDefault();
-            options.forEach(({ label, mark, command }) => {
-                if (e.target.value === label) {
-                    self.view && mark && command(mark, self.view);
-                }
-            });
-        }
-        return <select onChange={onChange} key={key}>{items}</select>;
-    }
-
-    createNodesDropdown(activeOption: string, options: { node: NodeType | any | null, title: string, label: string, command: (node: NodeType | any) => void, hidden?: boolean, style?: {} }[], key: string): JSX.Element {
-        const items = options.map(({ title, label, hidden, style }) => {
-            if (hidden) {
-                return label === activeOption ?
-                    <option value={label} title={title} key={label} style={style ? style : {}} selected hidden>{label}</option> :
-                    <option value={label} title={title} key={label} style={style ? style : {}} hidden>{label}</option>;
-            }
-            return label === activeOption ?
-                <option value={label} title={title} key={label} style={style ? style : {}} selected>{label}</option> :
-                <option value={label} title={title} key={label} style={style ? style : {}}>{label}</option>;
-        });
-
-        const self = this;
-        function onChange(val: string) {
-            options.forEach(({ label, node, command }) => {
-                if (val === label) {
-                    self.view && node && command(node);
-                }
-            });
-        }
-        return <select onChange={e => onChange(e.target.value)} key={key}>{items}</select>;
-    }
-
-    changeFontSize = (mark: Mark, view: EditorView) => {
-        this.setMark(view.state.schema.marks.pFontSize.create({ fontSize: mark.attrs.fontSize }), view.state, view.dispatch);
-    }
-
-    changeFontFamily = (mark: Mark, view: EditorView) => {
-        this.setMark(view.state.schema.marks.pFontFamily.create({ family: mark.attrs.family }), view.state, view.dispatch);
-    }
-
-    // TODO: remove doesn't work
-    //remove all node type and apply the passed-in one to the selected text
-    changeListType = (nodeType: NodeType | undefined) => {
-        if (!this.view) return;
-
-        if (nodeType === schema.nodes.bullet_list) {
-            wrapInList(nodeType)(this.view.state, this.view.dispatch);
-        } else {
-            const marks = this.view.state.storedMarks || (this.view.state.selection.$to.parentOffset && this.view.state.selection.$from.marks());
-            if (!wrapInList(schema.nodes.ordered_list)(this.view.state, (tx2: any) => {
-                const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle);
-                marks && tx3.ensureMarks([...marks]);
-                marks && tx3.setStoredMarks([...marks]);
-
-                this.view!.dispatch(tx2);
-            })) {
-                const tx2 = this.view.state.tr;
-                const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle);
-                marks && tx3.ensureMarks([...marks]);
-                marks && tx3.setStoredMarks([...marks]);
-
-                this.view.dispatch(tx3);
-            }
-        }
-    }
-
-    insertSummarizer(state: EditorState<any>, dispatch: any) {
-        if (state.selection.empty) return false;
-        const mark = state.schema.marks.summarize.create();
-        const tr = state.tr;
-        tr.addMark(state.selection.from, state.selection.to, mark);
-        const content = tr.selection.content();
-        const newNode = state.schema.nodes.summary.create({ visibility: false, text: content, textslice: content.toJSON() });
-        dispatch && dispatch(tr.replaceSelectionWith(newNode).removeMark(tr.selection.from - 1, tr.selection.from, mark));
-        return true;
-    }
-
-    @action toggleBrushDropdown() { this.showBrushDropdown = !this.showBrushDropdown; }
-
-    // todo: add brushes to brushMap to save with a style name
-    onBrushNameKeyPress = (e: React.KeyboardEvent) => {
-        if (e.key === "Enter") {
-            RichTextMenu.Instance.brushMarks && RichTextMenu.Instance._brushMap.set(this._brushNameRef.current!.value, RichTextMenu.Instance.brushMarks);
-            this._brushNameRef.current!.style.background = "lightGray";
-        }
-    }
-    _brushNameRef = React.createRef<HTMLInputElement>();
-
-    createBrushButton() {
-        const self = this;
-        function onBrushClick(e: React.PointerEvent) {
-            e.preventDefault();
-            e.stopPropagation();
-            self.view && self.view.focus();
-            self.view && self.fillBrush(self.view.state, self.view.dispatch);
-        }
-
-        let label = "Stored marks: ";
-        if (this.brushMarks && this.brushMarks.size > 0) {
-            this.brushMarks.forEach((mark: Mark) => {
-                const markType = mark.type;
-                label += markType.name;
-                label += ", ";
-            });
-            label = label.substring(0, label.length - 2);
-        } else {
-            label = "No marks are currently stored";
-        }
-
-        const button =
-            <button className="antimodeMenu-button" title="" onPointerDown={onBrushClick} style={this.brushMarks?.size > 0 ? { backgroundColor: "121212" } : {}}>
-                <FontAwesomeIcon icon="paint-roller" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.1s", transform: `rotate(${this.brushMarks?.size > 0 ? 45 : 0}deg)` }} />
-            </button>;
-
-        const dropdownContent =
-            <div className="dropdown">
-                <p>{label}</p>
-                <button onPointerDown={this.clearBrush}>Clear brush</button>
-                <input placeholder="-brush name-" ref={this._brushNameRef} onKeyPress={this.onBrushNameKeyPress}></input>
-            </div>;
-
-        return (
-            <ButtonDropdown view={this.view} key={"brush dropdown"} button={button} dropdownContent={dropdownContent} />
-        );
-    }
-
-    @action
-    clearBrush() {
-        RichTextMenu.Instance.brushIsEmpty = true;
-        RichTextMenu.Instance.brushMarks = new Set();
-    }
-
-    @action
-    fillBrush(state: EditorState<any>, dispatch: any) {
-        if (!this.view) return;
-
-        if (this.brushIsEmpty) {
-            const selected_marks = this.getMarksInSelection(this.view.state);
-            if (selected_marks.size >= 0) {
-                this.brushMarks = selected_marks;
-                this.brushIsEmpty = !this.brushIsEmpty;
-            }
-        }
-        else {
-            const { from, to, $from } = this.view.state.selection;
-            if (!this.view.state.selection.empty && $from && $from.nodeAfter) {
-                if (this.brushMarks && to - from > 0) {
-                    this.view.dispatch(this.view.state.tr.removeMark(from, to));
-                    Array.from(this.brushMarks).filter(m => m.type !== schema.marks.user_mark).forEach((mark: Mark) => {
-                        this.setMark(mark, this.view!.state, this.view!.dispatch);
-                    });
-                }
-            }
-            else {
-                this.brushIsEmpty = !this.brushIsEmpty;
-            }
-        }
-    }
-
-    @action toggleColorDropdown() { this.showColorDropdown = !this.showColorDropdown; }
-    @action setActiveColor(color: string) { this.activeFontColor = color; }
-
-    createColorButton() {
-        const self = this;
-        function onColorClick(e: React.PointerEvent) {
-            e.preventDefault();
-            e.stopPropagation();
-            self.view && self.view.focus();
-            self.view && self.insertColor(self.activeFontColor, self.view.state, self.view.dispatch);
-        }
-        function changeColor(e: React.PointerEvent, color: string) {
-            e.preventDefault();
-            e.stopPropagation();
-            self.view && self.view.focus();
-            self.setActiveColor(color);
-            self.view && self.insertColor(self.activeFontColor, self.view.state, self.view.dispatch);
-        }
-
-        const button =
-            <button className="antimodeMenu-button color-preview-button" title="" onPointerDown={onColorClick}>
-                <FontAwesomeIcon icon="palette" size="lg" />
-                <div className="color-preview" style={{ backgroundColor: this.activeFontColor }}></div>
-            </button>;
-
-        const dropdownContent =
-            <div className="dropdown" >
-                <p>Change font color:</p>
-                <div className="color-wrapper">
-                    {this.fontColors.map(color => {
-                        if (color) {
-                            return this.activeFontColor === color ?
-                                <button className="color-button active" key={"active" + color} style={{ backgroundColor: color }} onPointerDown={e => changeColor(e, color)}></button> :
-                                <button className="color-button" key={"other" + color} style={{ backgroundColor: color }} onPointerDown={e => changeColor(e, color)}></button>;
-                        }
-                    })}
-                </div>
-            </div>;
-
-        return (
-            <ButtonDropdown view={this.view} key={"color dropdown"} button={button} dropdownContent={dropdownContent} />
-        );
-    }
-
-    public insertColor(color: String, state: EditorState<any>, dispatch: any) {
-        const colorMark = state.schema.mark(state.schema.marks.pFontColor, { color: color });
-        if (state.selection.empty) {
-            dispatch(state.tr.addStoredMark(colorMark));
-            return false;
-        }
-        this.setMark(colorMark, state, dispatch);
-    }
-
-    @action toggleHighlightDropdown() { this.showHighlightDropdown = !this.showHighlightDropdown; }
-    @action setActiveHighlight(color: string) { this.activeHighlightColor = color; }
-
-    createHighlighterButton() {
-        const self = this;
-        function onHighlightClick(e: React.PointerEvent) {
-            e.preventDefault();
-            e.stopPropagation();
-            self.view && self.view.focus();
-            self.view && self.insertHighlight(self.activeHighlightColor, self.view.state, self.view.dispatch);
-        }
-        function changeHighlight(e: React.PointerEvent, color: string) {
-            e.preventDefault();
-            e.stopPropagation();
-            self.view && self.view.focus();
-            self.setActiveHighlight(color);
-            self.view && self.insertHighlight(self.activeHighlightColor, self.view.state, self.view.dispatch);
-        }
-
-        const button =
-            <button className="antimodeMenu-button color-preview-button" title="" key="highilghter-button" onPointerDown={onHighlightClick}>
-                <FontAwesomeIcon icon="highlighter" size="lg" />
-                <div className="color-preview" style={{ backgroundColor: this.activeHighlightColor }}></div>
-            </button>;
-
-        const dropdownContent =
-            <div className="dropdown">
-                <p>Change highlight color:</p>
-                <div className="color-wrapper">
-                    {this.highlightColors.map(color => {
-                        if (color) {
-                            return this.activeHighlightColor === color ?
-                                <button className="color-button active" key={`active ${color}`} style={{ backgroundColor: color }} onPointerDown={e => changeHighlight(e, color)}>{color === "transparent" ? "X" : ""}</button> :
-                                <button className="color-button" key={`inactive ${color}`} style={{ backgroundColor: color }} onPointerDown={e => changeHighlight(e, color)}>{color === "transparent" ? "X" : ""}</button>;
-                        }
-                    })}
-                </div>
-            </div>;
-
-        return (
-            <ButtonDropdown view={this.view} key={"highlighter"} button={button} dropdownContent={dropdownContent} />
-        );
-    }
-
-    insertHighlight(color: String, state: EditorState<any>, dispatch: any) {
-        if (state.selection.empty) return false;
-        toggleMark(state.schema.marks.marker, { highlight: color })(state, dispatch);
-    }
-
-    @action toggleLinkDropdown() { this.showLinkDropdown = !this.showLinkDropdown; }
-    @action setCurrentLink(link: string) { this.currentLink = link; }
-
-    createLinkButton() {
-        const self = this;
-
-        function onLinkChange(e: React.ChangeEvent<HTMLInputElement>) {
-            self.setCurrentLink(e.target.value);
-        }
-
-        const link = this.currentLink ? this.currentLink : "";
-
-        const button = <FontAwesomeIcon icon="link" size="lg" />;
-
-        const dropdownContent =
-            <div className="dropdown link-menu">
-                <p>Linked to:</p>
-                <input value={link} placeholder="Enter URL" onChange={onLinkChange} />
-                <button className="make-button" onPointerDown={e => this.makeLinkToURL(link, "onRight")}>Apply hyperlink</button>
-                <div className="divider"></div>
-                <button className="remove-button" onPointerDown={e => this.deleteLink()}>Remove link</button>
-            </div>;
-
-        return (
-            <ButtonDropdown view={this.view} key={"link button"} button={button} dropdownContent={dropdownContent} openDropdownOnButton={true} />
-        );
-    }
-
-    async getTextLinkTargetTitle() {
-        if (!this.view) return;
-
-        const node = this.view.state.selection.$from.nodeAfter;
-        const link = node && node.marks.find(m => m.type.name === "link");
-        if (link) {
-            const href = link.attrs.href;
-            if (href) {
-                if (href.indexOf(Utils.prepend("/doc/")) === 0) {
-                    const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
-                    if (linkclicked) {
-                        const linkDoc = await DocServer.GetRefField(linkclicked);
-                        if (linkDoc instanceof Doc) {
-                            const anchor1 = await Cast(linkDoc.anchor1, Doc);
-                            const anchor2 = await Cast(linkDoc.anchor2, Doc);
-                            const currentDoc = SelectionManager.SelectedDocuments().length && SelectionManager.SelectedDocuments()[0].props.Document;
-                            if (currentDoc && anchor1 && anchor2) {
-                                if (Doc.AreProtosEqual(currentDoc, anchor1)) {
-                                    return StrCast(anchor2.title);
-                                }
-                                if (Doc.AreProtosEqual(currentDoc, anchor2)) {
-                                    return StrCast(anchor1.title);
-                                }
-                            }
-                        }
-                    }
-                } else {
-                    return href;
-                }
-            } else {
-                return link.attrs.title;
-            }
-        }
-    }
-
-    // TODO: should check for valid URL
-    makeLinkToURL = (target: String, lcoation: string) => {
-        if (!this.view) return;
-
-        let node = this.view.state.selection.$from.nodeAfter;
-        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;
-        link = node && node.marks.find(m => m.type.name === "link");
-    }
-
-    deleteLink = () => {
-        if (!this.view) return;
-
-        const node = this.view.state.selection.$from.nodeAfter;
-        const link = node && node.marks.find(m => m.type === this.view!.state.schema.marks.link);
-        const href = link!.attrs.href;
-        if (href) {
-            if (href.indexOf(Utils.prepend("/doc/")) === 0) {
-                const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
-                if (linkclicked) {
-                    DocServer.GetRefField(linkclicked).then(async linkDoc => {
-                        if (linkDoc instanceof Doc) {
-                            LinkManager.Instance.deleteLink(linkDoc);
-                            this.view!.dispatch(this.view!.state.tr.removeMark(this.view!.state.selection.from, this.view!.state.selection.to, this.view!.state.schema.marks.link));
-                        }
-                    });
-                }
-            } else {
-                if (node) {
-                    const { tr, schema, selection } = this.view.state;
-                    const extension = this.linkExtend(selection.$anchor, href);
-                    this.view.dispatch(tr.removeMark(extension.from, extension.to, schema.marks.link));
-                }
-            }
-        }
-    }
-
-    linkExtend($start: ResolvedPos, href: string) {
-        const mark = this.view!.state.schema.marks.link;
-
-        let startIndex = $start.index();
-        let endIndex = $start.indexAfter();
-
-        while (startIndex > 0 && $start.parent.child(startIndex - 1).marks.filter(m => m.type === mark && m.attrs.href === href).length) startIndex--;
-        while (endIndex < $start.parent.childCount && $start.parent.child(endIndex).marks.filter(m => m.type === mark && m.attrs.href === href).length) endIndex++;
-
-        let startPos = $start.start();
-        let endPos = startPos;
-        for (let i = 0; i < endIndex; i++) {
-            const size = $start.parent.child(i).nodeSize;
-            if (i < startIndex) startPos += size;
-            endPos += size;
-        }
-        return { from: startPos, to: endPos };
-    }
-
-    reference_node(pos: ResolvedPos<any>): ProsNode | null {
-        if (!this.view) return null;
-
-        let ref_node: ProsNode = this.view.state.doc;
-        if (pos.nodeBefore !== null && pos.nodeBefore !== undefined) {
-            ref_node = pos.nodeBefore;
-        }
-        else if (pos.nodeAfter !== null && pos.nodeAfter !== undefined) {
-            ref_node = pos.nodeAfter;
-        }
-        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) => {
-                    if (node.isLeaf && !skip) {
-                        ref_node = node;
-                        skip = true;
-                    }
-
-                });
-            }
-        }
-        if (!ref_node.isLeaf && ref_node.childCount > 0) {
-            ref_node = ref_node.child(0);
-        }
-        return ref_node;
-    }
-
-    @action onPointerEnter(e: React.PointerEvent) { RichTextMenu.Instance.overMenu = true; }
-    @action onPointerLeave(e: React.PointerEvent) { RichTextMenu.Instance.overMenu = false; }
-
-    @action
-    toggleMenuPin = (e: React.MouseEvent) => {
-        this.Pinned = !this.Pinned;
-        if (!this.Pinned) {
-            this.fadeOut(true);
-        }
-    }
-
-    @action
-    protected toggleCollapse = (e: React.MouseEvent) => {
-        this.collapsed = !this.collapsed;
-        setTimeout(() => {
-            const x = Math.min(this._left, window.innerWidth - RichTextMenu.Instance.width);
-            RichTextMenu.Instance.jumpTo(x, this._top);
-        }, 0);
-    }
-
-    render() {
-
-        const row1 = <div className="antimodeMenu-row" key="row1" style={{ display: this.collapsed ? "none" : undefined }}>{[
-            this.createButton("bold", "Bold", this.boldActive, toggleMark(schema.marks.strong)),
-            this.createButton("italic", "Italic", this.italicsActive, toggleMark(schema.marks.em)),
-            this.createButton("underline", "Underline", this.underlineActive, toggleMark(schema.marks.underline)),
-            this.createButton("strikethrough", "Strikethrough", this.strikethroughActive, toggleMark(schema.marks.strikethrough)),
-            this.createButton("superscript", "Superscript", this.superscriptActive, toggleMark(schema.marks.superscript)),
-            this.createButton("subscript", "Subscript", this.subscriptActive, toggleMark(schema.marks.subscript)),
-            this.createColorButton(),
-            this.createHighlighterButton(),
-            this.createLinkButton(),
-            this.createBrushButton(),
-            this.createButton("indent", "Summarize", undefined, this.insertSummarizer),
-        ]}</div>;
-
-        const row2 = <div className="antimodeMenu-row row-2" key="antimodemenu row2">
-            <div key="row" style={{ display: this.collapsed ? "none" : undefined }}>
-                {[this.createMarksDropdown(this.activeFontSize, this.fontSizeOptions, "font size"),
-                this.createMarksDropdown(this.activeFontFamily, this.fontFamilyOptions, "font family"),
-                this.createNodesDropdown(this.activeListType, this.listTypeOptions, "nodes")]}
-            </div>
-            <div key="button">
-                <div key="collapser">
-                    <button className="antimodeMenu-button" key="collapse menu" title="Collapse menu" onClick={this.toggleCollapse} style={{ backgroundColor: this.collapsed ? "#121212" : "", width: 25 }}>
-                        <FontAwesomeIcon icon="chevron-left" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.3s", transform: `rotate(${this.collapsed ? 180 : 0}deg)` }} />
-                    </button>
-                </div>
-                <button className="antimodeMenu-button" key="pin menu" title="Pin menu" onClick={this.toggleMenuPin} style={{ backgroundColor: this.Pinned ? "#121212" : "", display: this.collapsed ? "none" : undefined }}>
-                    <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.1s", transform: `rotate(${this.Pinned ? 45 : 0}deg)` }} />
-                </button>
-                {this.getDragger()}
-            </div>
-        </div>;
-
-        return (
-            <div className="richTextMenu" onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}>
-                {this.getElementWithRows([row1, row2], 2, false)}
-            </div>
-        );
-    }
-}
-
-interface ButtonDropdownProps {
-    view?: EditorView;
-    button: JSX.Element;
-    dropdownContent: JSX.Element;
-    openDropdownOnButton?: boolean;
-}
-
-@observer
-class ButtonDropdown extends React.Component<ButtonDropdownProps> {
-
-    @observable private showDropdown: boolean = false;
-    private ref: HTMLDivElement | null = null;
-
-    componentDidMount() {
-        document.addEventListener("pointerdown", this.onBlur);
-    }
-
-    componentWillUnmount() {
-        document.removeEventListener("pointerdown", this.onBlur);
-    }
-
-    @action
-    setShowDropdown(show: boolean) {
-        this.showDropdown = show;
-    }
-    @action
-    toggleDropdown() {
-        this.showDropdown = !this.showDropdown;
-    }
-
-    onDropdownClick = (e: React.PointerEvent) => {
-        e.preventDefault();
-        e.stopPropagation();
-        this.props.view && this.props.view.focus();
-        this.toggleDropdown();
-    }
-
-    onBlur = (e: PointerEvent) => {
-        setTimeout(() => {
-            if (this.ref !== null && !this.ref.contains(e.target as Node)) {
-                this.setShowDropdown(false);
-            }
-        }, 0);
-    }
-
-    render() {
-        return (
-            <div className="button-dropdown-wrapper" ref={node => this.ref = node}>
-                {this.props.openDropdownOnButton ?
-                    <button className="antimodeMenu-button dropdown-button-combined" onPointerDown={this.onDropdownClick}>
-                        {this.props.button}
-                        <FontAwesomeIcon icon="caret-down" size="sm" />
-                    </button> :
-                    <>
-                        {this.props.button}
-                        <button className="dropdown-button antimodeMenu-button" key="antimodebutton" onPointerDown={this.onDropdownClick}>
-                            <FontAwesomeIcon icon="caret-down" size="sm" />
-                        </button>
-                    </>}
-
-                {this.showDropdown ? this.props.dropdownContent : (null)}
-            </div>
-        );
-    }
-}
\ No newline at end of file
diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts
index 63a3815ea..7ce6bbf85 100644
--- a/src/client/util/RichTextRules.ts
+++ b/src/client/util/RichTextRules.ts
@@ -7,9 +7,9 @@ import { Cast, NumCast } from "../../new_fields/Types";
 import { returnFalse, Utils } from "../../Utils";
 import { DocServer } from "../DocServer";
 import { Docs, DocUtils } from "../documents/Documents";
-import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
+import { FormattedTextBox } from "../views/nodes/formattedText/FormattedTextBox";
 import { wrappingInputRule } from "./prosemirrorPatches";
-import RichTextMenu from "./RichTextMenu";
+import RichTextMenu from "../views/nodes/formattedText/RichTextMenu";
 import { schema } from "./schema_rts";
 
 export class RichTextRules {
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
deleted file mode 100644
index 3e45d5de5..000000000
--- a/src/client/util/RichTextSchema.tsx
+++ /dev/null
@@ -1,718 +0,0 @@
-import { IReactionDisposer, observable, reaction, runInAction } from "mobx";
-import { baseKeymap, toggleMark } from "prosemirror-commands";
-import { redo, undo } from "prosemirror-history";
-import { keymap } from "prosemirror-keymap";
-import { DOMOutputSpecArray, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model";
-import { bulletList, listItem, orderedList } from 'prosemirror-schema-list';
-import { EditorState, NodeSelection, Plugin, TextSelection } from "prosemirror-state";
-import { StepMap } from "prosemirror-transform";
-import { EditorView } from "prosemirror-view";
-import * as ReactDOM from 'react-dom';
-import { Doc, DocListCast, Field, HeightSym, WidthSym } from "../../new_fields/Doc";
-import { Id } from "../../new_fields/FieldSymbols";
-import { List } from "../../new_fields/List";
-import { ObjectField } from "../../new_fields/ObjectField";
-import { listSpec } from "../../new_fields/Schema";
-import { SchemaHeaderField } from "../../new_fields/SchemaHeaderField";
-import { ComputedField } from "../../new_fields/ScriptField";
-import { BoolCast, Cast, NumCast, StrCast, FieldValue } from "../../new_fields/Types";
-import { emptyFunction, returnEmptyString, returnFalse, returnOne, Utils, returnZero } from "../../Utils";
-import { DocServer } from "../DocServer";
-import { Docs } from "../documents/Documents";
-import { CollectionViewType } from "../views/collections/CollectionView";
-import { DocumentView } from "../views/nodes/DocumentView";
-import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
-import { DocumentManager } from "./DocumentManager";
-import { Transform } from "./Transform";
-import React = require("react");
-
-import { schema } from "./schema_rts";
-
-export class OrderedListView {
-    update(node: any) {
-        return false; // if attr's of an ordered_list (e.g., bulletStyle) change, return false forces the dom node to be recreated which is necessary for the bullet labels to update
-    }
-}
-
-export class ImageResizeView {
-    _handle: HTMLElement;
-    _img: HTMLElement;
-    _outer: HTMLElement;
-    constructor(node: any, view: any, getPos: any, addDocTab: any) {
-        //moved
-        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.height = node.attrs.height;
-        this._outer.style.display = "inline-block";
-        this._outer.style.overflow = "hidden";
-        (this._outer.style as any).float = node.attrs.float;
-        //moved
-        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";
-        const self = this;
-        //moved
-        this._img.onclick = function (e: any) {
-            e.stopPropagation();
-            e.preventDefault();
-            if (view.state.selection.node && view.state.selection.node.type !== view.state.schema.nodes.image) {
-                view.dispatch(view.state.tr.setSelection(new NodeSelection(view.state.doc.resolve(view.state.selection.from - 2))));
-            }
-        };
-        //moved
-        this._img.onpointerdown = function (e: any) {
-            if (e.ctrlKey) {
-                e.preventDefault();
-                e.stopPropagation();
-                DocServer.GetRefField(node.attrs.docid).then(async linkDoc =>
-                    (linkDoc instanceof Doc) &&
-                    DocumentManager.Instance.FollowLink(linkDoc, view.state.schema.Document,
-                        document => addDocTab(document, node.attrs.location ? node.attrs.location : "inTab"), false));
-            }
-        };
-        //moved
-        this._handle.onpointerdown = function (e: any) {
-            e.preventDefault();
-            e.stopPropagation();
-            const wid = Number(getComputedStyle(self._img).width.replace(/px/, ""));
-            const hgt = Number(getComputedStyle(self._img).height.replace(/px/, ""));
-            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}`;
-                self._outer.style.height = `${(startWidth + diffInPx) * hgt / wid}`;
-            };
-
-            const onpointerup = () => {
-                document.removeEventListener("pointermove", onpointermove);
-                document.removeEventListener("pointerup", onpointerup);
-                const pos = view.state.selection.from;
-                view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, { ...node.attrs, width: self._outer.style.width, height: self._outer.style.height }));
-                view.dispatch(view.state.tr.setSelection(new NodeSelection(view.state.doc.resolve(pos))));
-            };
-
-            document.addEventListener("pointermove", onpointermove);
-            document.addEventListener("pointerup", onpointerup);
-        };
-        //Moved
-        this._outer.appendChild(this._img);
-        this._outer.appendChild(this._handle);
-        (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";
-    }
-}
-
-export class DashDocCommentView {
-    _collapsed: HTMLElement;
-    _view: any;
-    constructor(node: any, view: any, getPos: any) {
-        //moved
-        this._collapsed = document.createElement("span");
-        this._collapsed.className = "formattedTextBox-inlineComment";
-        this._collapsed.id = "DashDocCommentView-" + node.attrs.docid;
-        this._view = view;
-        //moved
-        const targetNode = () => {  // search forward in the prosemirror doc for the attached dashDocNode that is the target of the comment anchor
-            for (let i = getPos() + 1; i < view.state.doc.content.size; i++) {
-                const m = view.state.doc.nodeAt(i);
-                if (m && m.type === view.state.schema.nodes.dashDoc && m.attrs.docid === node.attrs.docid) {
-                    return { node: m, pos: i, hidden: m.attrs.hidden } as { node: any, pos: number, hidden: boolean };
-                }
-            }
-            const dashDoc = view.state.schema.nodes.dashDoc.create({ width: 75, height: 35, title: "dashDoc", docid: node.attrs.docid, float: "right" });
-            view.dispatch(view.state.tr.insert(getPos() + 1, dashDoc));
-            setTimeout(() => { try { view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.tr.doc, getPos() + 2))); } catch (e) { } }, 0);
-            return undefined;
-        };
-        //moved
-        this._collapsed.onpointerdown = (e: any) => {
-            e.stopPropagation();
-        };
-        //moved
-        this._collapsed.onpointerup = (e: any) => {
-            const target = targetNode();
-            if (target) {
-                const expand = target.hidden;
-                const tr = view.state.tr.setNodeMarkup(target.pos, undefined, { ...target.node.attrs, hidden: target.node.attrs.hidden ? false : true });
-                view.dispatch(tr.setSelection(TextSelection.create(tr.doc, getPos() + (expand ? 2 : 1)))); // update the attrs
-                setTimeout(() => {
-                    expand && DocServer.GetRefField(node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc));
-                    try { view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.tr.doc, getPos() + (expand ? 2 : 1)))); } catch (e) { }
-                }, 0);
-            }
-            e.stopPropagation();
-        };
-        //moved
-        this._collapsed.onpointerenter = (e: any) => {
-            DocServer.GetRefField(node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc, false));
-            e.preventDefault();
-            e.stopPropagation();
-        };
-        //moved
-        this._collapsed.onpointerleave = (e: any) => {
-            DocServer.GetRefField(node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowUnhighlight());
-            e.preventDefault();
-            e.stopPropagation();
-        };
-
-        (this as any).dom = this._collapsed;
-    }
-    //moved
-    selectNode() { }
-}
-
-export class DashDocView {
-    _dashSpan: HTMLDivElement;
-    _outer: HTMLElement;
-    _dashDoc: Doc | undefined;
-    _reactionDisposer: IReactionDisposer | undefined;
-    _renderDisposer: IReactionDisposer | undefined;
-    _textBox: FormattedTextBox;
-
-    getDocTransform = () => {
-        const { scale, translateX, translateY } = Utils.GetScreenTransform(this._outer);
-        return new Transform(-translateX, -translateY, 1).scale(1 / this.contentScaling() / scale);
-    }
-    contentScaling = () => NumCast(this._dashDoc!._nativeWidth) > 0 ? this._dashDoc![WidthSym]() / NumCast(this._dashDoc!._nativeWidth) : 1;
-
-    outerFocus = (target: Doc) => this._textBox.props.focus(this._textBox.props.Document);  // ideally, this would scroll to show the focus target
-
-    constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
-        this._textBox = tbox;
-        this._dashSpan = document.createElement("div");
-        this._outer = document.createElement("span");
-        this._outer.style.position = "relative";
-        this._outer.style.textIndent = "0";
-        this._outer.style.border = "1px solid " + StrCast(tbox.layoutDoc.color, (Cast(Doc.UserDoc().activeWorkspace, Doc, null).darkScheme ? "dimGray" : "lightGray"));
-        this._outer.style.width = node.attrs.width;
-        this._outer.style.height = node.attrs.height;
-        this._outer.style.display = node.attrs.hidden ? "none" : "inline-block";
-        // this._outer.style.overflow = "hidden";  // bcz: not sure if this is needed.  if it's used, then the doc doesn't highlight when you hover over a docComment
-        (this._outer.style as any).float = node.attrs.float;
-
-        this._dashSpan.style.width = node.attrs.width;
-        this._dashSpan.style.height = node.attrs.height;
-        this._dashSpan.style.position = "absolute";
-        this._dashSpan.style.display = "inline-block";
-        this._dashSpan.onpointerleave = () => {
-            const ele = document.getElementById("DashDocCommentView-" + node.attrs.docid);
-            if (ele) {
-                (ele as HTMLDivElement).style.backgroundColor = "";
-            }
-        };
-        this._dashSpan.onpointerenter = () => {
-            const ele = document.getElementById("DashDocCommentView-" + node.attrs.docid);
-            if (ele) {
-                (ele as HTMLDivElement).style.backgroundColor = "orange";
-            }
-        };
-        const removeDoc = () => {
-            const pos = getPos();
-            const ns = new NodeSelection(view.state.doc.resolve(pos));
-            view.dispatch(view.state.tr.setSelection(ns).deleteSelection());
-            return true;
-        };
-        const alias = node.attrs.alias;
-
-        const docid = node.attrs.docid || tbox.props.Document[Id];// tbox.props.DataDoc?.[Id] || tbox.dataDoc?.[Id];
-        DocServer.GetRefField(docid + alias).then(async dashDoc => {
-            if (!(dashDoc instanceof Doc)) {
-                alias && DocServer.GetRefField(docid).then(async dashDocBase => {
-                    if (dashDocBase instanceof Doc) {
-                        const aliasedDoc = Doc.MakeAlias(dashDocBase, docid + alias);
-                        aliasedDoc.layoutKey = "layout";
-                        node.attrs.fieldKey && Doc.makeCustomViewClicked(aliasedDoc, Docs.Create.StackingDocument, node.attrs.fieldKey, undefined);
-                        self.doRender(aliasedDoc, removeDoc, node, view, getPos);
-                    }
-                });
-            } else {
-                self.doRender(dashDoc, removeDoc, node, view, getPos);
-            }
-        });
-        const self = this;
-        this._dashSpan.onkeydown = function (e: any) {
-            e.stopPropagation();
-            if (e.key === "Tab" || e.key === "Enter") {
-                e.preventDefault();
-            }
-        };
-        this._dashSpan.onkeypress = function (e: any) { e.stopPropagation(); };
-        this._dashSpan.onwheel = function (e: any) { e.preventDefault(); };
-        this._dashSpan.onkeyup = function (e: any) { e.stopPropagation(); };
-        this._outer.appendChild(this._dashSpan);
-        (this as any).dom = this._outer;
-    }
-
-    doRender(dashDoc: Doc, removeDoc: any, node: any, view: any, getPos: any) {
-        this._dashDoc = dashDoc;
-        const self = this;
-        const dashLayoutDoc = Doc.Layout(dashDoc);
-        const finalLayout = node.attrs.docid ? dashDoc : Doc.expandTemplateLayout(dashLayoutDoc, dashDoc, node.attrs.fieldKey);
-
-        if (!finalLayout) setTimeout(() => self.doRender(dashDoc, removeDoc, node, view, getPos), 0);
-        else {
-            this._reactionDisposer?.();
-            this._reactionDisposer = reaction(() => ({ dim: [finalLayout[WidthSym](), finalLayout[HeightSym]()], color: finalLayout.color }), ({ dim, color }) => {
-                this._dashSpan.style.width = this._outer.style.width = Math.max(20, dim[0]) + "px";
-                this._dashSpan.style.height = this._outer.style.height = Math.max(20, dim[1]) + "px";
-                this._outer.style.border = "1px solid " + StrCast(finalLayout.color, (Cast(Doc.UserDoc().activeWorkspace, Doc, null).darkScheme ? "dimGray" : "lightGray"));
-            }, { fireImmediately: true });
-            const doReactRender = (finalLayout: Doc, resolvedDataDoc: Doc) => {
-                ReactDOM.unmountComponentAtNode(this._dashSpan);
-
-                ReactDOM.render(<DocumentView
-                    Document={finalLayout}
-                    DataDoc={resolvedDataDoc}
-                    LibraryPath={this._textBox.props.LibraryPath}
-                    fitToBox={BoolCast(dashDoc._fitToBox)}
-                    addDocument={returnFalse}
-                    rootSelected={this._textBox.props.isSelected}
-                    removeDocument={removeDoc}
-                    ScreenToLocalTransform={this.getDocTransform}
-                    addDocTab={this._textBox.props.addDocTab}
-                    pinToPres={returnFalse}
-                    renderDepth={self._textBox.props.renderDepth + 1}
-                    NativeHeight={returnZero}
-                    NativeWidth={returnZero}
-                    PanelWidth={finalLayout[WidthSym]}
-                    PanelHeight={finalLayout[HeightSym]}
-                    focus={this.outerFocus}
-                    backgroundColor={returnEmptyString}
-                    parentActive={returnFalse}
-                    whenActiveChanged={returnFalse}
-                    bringToFront={emptyFunction}
-                    dontRegisterView={false}
-                    ContainingCollectionView={this._textBox.props.ContainingCollectionView}
-                    ContainingCollectionDoc={this._textBox.props.ContainingCollectionDoc}
-                    ContentScaling={this.contentScaling}
-                />, this._dashSpan);
-                if (node.attrs.width !== dashDoc._width + "px" || node.attrs.height !== dashDoc._height + "px") {
-                    try { // bcz: an exception will be thrown if two aliases are open at the same time when a doc view comment is made
-                        view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, { ...node.attrs, width: dashDoc._width + "px", height: dashDoc._height + "px" }));
-                    } catch (e) {
-                        console.log(e);
-                    }
-                }
-            };
-            this._renderDisposer?.();
-            this._renderDisposer = reaction(() => {
-                // if (!Doc.AreProtosEqual(finalLayout, dashDoc)) {
-                //     finalLayout.rootDocument = dashDoc.aliasOf; // bcz: check on this ... why is it here?
-                // }
-                const layoutKey = StrCast(finalLayout.layoutKey);
-                const finalKey = layoutKey && StrCast(finalLayout[layoutKey]).split("'")?.[1];
-                if (finalLayout !== dashDoc && finalKey) {
-                    const finalLayoutField = finalLayout[finalKey];
-                    if (finalLayoutField instanceof ObjectField) {
-                        finalLayout[finalKey + "-textTemplate"] = ComputedField.MakeFunction(`copyField(this.${finalKey})`, { this: Doc.name });
-                    }
-                }
-                return { finalLayout, resolvedDataDoc: Cast(finalLayout.resolvedDataDoc, Doc, null) };
-            },
-                (res) => doReactRender(res.finalLayout, res.resolvedDataDoc),
-                { fireImmediately: true });
-        }
-    }
-    destroy() {
-        ReactDOM.unmountComponentAtNode(this._dashSpan);
-        this._reactionDisposer?.();
-    }
-}
-
-export class DashFieldView {
-    _fieldWrapper: HTMLDivElement; // container for label and value
-    _labelSpan: HTMLSpanElement; // field label
-    _fieldSpan: HTMLSpanElement;  // field value
-    _fieldCheck: HTMLInputElement;
-    _enumerables: HTMLDivElement;  // field value
-    _reactionDisposer: IReactionDisposer | undefined;
-    _textBoxDoc: Doc;
-    @observable _dashDoc: Doc | undefined;
-    _fieldKey: string;
-    _options: Doc[] = [];
-
-    constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
-        this._fieldKey = node.attrs.fieldKey;
-        this._textBoxDoc = tbox.props.Document;
-        this._fieldWrapper = document.createElement("p");
-        this._fieldWrapper.style.width = node.attrs.width;
-        this._fieldWrapper.style.height = node.attrs.height;
-        this._fieldWrapper.style.fontWeight = "bold";
-        this._fieldWrapper.style.position = "relative";
-        this._fieldWrapper.style.display = "inline-block";
-
-        const self = this;
-
-        this._enumerables = document.createElement("div");
-        this._enumerables.style.width = "10px";
-        this._enumerables.style.height = "10px";
-        this._enumerables.style.position = "relative";
-        this._enumerables.style.display = "none";
-
-        //Moved
-        this._enumerables.onpointerdown = async (e) => {
-            e.stopPropagation();
-            const collview = await Doc.addFieldEnumerations(self._textBoxDoc, self._fieldKey, [{ title: self._fieldSpan.innerText }]);
-            collview instanceof Doc && tbox.props.addDocTab(collview, "onRight");
-        };
-        //Moved
-        const updateText = (forceMatch: boolean) => {
-            self._enumerables.style.display = "none";
-            const newText = self._fieldSpan.innerText.startsWith(":=") || self._fieldSpan.innerText.startsWith("=:=") ? ":=-computed-" : self._fieldSpan.innerText;
-
-            // look for a document whose id === the fieldKey being displayed.  If there's a match, then that document
-            // holds the different enumerated values for the field in the titles of its collected documents.
-            // if there's a partial match from the start of the input text, complete the text --- TODO: make this an auto suggest box and select from a drop down.
-            DocServer.GetRefField(self._fieldKey).then(options => {
-                let modText = "";
-                (options instanceof Doc) && DocListCast(options.data).forEach(opt => (forceMatch ? StrCast(opt.title).startsWith(newText) : StrCast(opt.title) === newText) && (modText = StrCast(opt.title)));
-                if (modText) {
-                    self._fieldSpan.innerHTML = self._dashDoc![self._fieldKey] = modText;
-                    Doc.addFieldEnumerations(self._textBoxDoc, self._fieldKey, []);
-                } // if the text starts with a ':=' then treat it as an expression by making a computed field from its value storing it in the key
-                else if (self._fieldSpan.innerText.startsWith(":=")) {
-                    self._dashDoc![self._fieldKey] = ComputedField.MakeFunction(self._fieldSpan.innerText.substring(2));
-                } else if (self._fieldSpan.innerText.startsWith("=:=")) {
-                    Doc.Layout(tbox.props.Document)[self._fieldKey] = ComputedField.MakeFunction(self._fieldSpan.innerText.substring(3));
-                } else {
-                    self._dashDoc![self._fieldKey] = newText;
-                }
-            });
-        };
-
-        //Moved
-        this._fieldCheck = document.createElement("input");
-        this._fieldCheck.id = Utils.GenerateGuid();
-        this._fieldCheck.type = "checkbox";
-        this._fieldCheck.style.position = "relative";
-        this._fieldCheck.style.display = "none";
-        this._fieldCheck.style.minWidth = "12px";
-        this._fieldCheck.style.backgroundColor = "rgba(155, 155, 155, 0.24)";
-        this._fieldCheck.onchange = function (e: any) {
-            self._dashDoc![self._fieldKey] = e.target.checked;
-        };
-
-        this._fieldSpan = document.createElement("span");
-        this._fieldSpan.id = Utils.GenerateGuid();
-        this._fieldSpan.contentEditable = "true";
-        this._fieldSpan.style.position = "relative";
-        this._fieldSpan.style.display = "none";
-        this._fieldSpan.style.minWidth = "12px";
-        this._fieldSpan.style.fontSize = "large";
-        this._fieldSpan.onkeypress = function (e: any) { e.stopPropagation(); };
-        this._fieldSpan.onkeyup = function (e: any) { e.stopPropagation(); };
-        this._fieldSpan.onmousedown = function (e: any) { e.stopPropagation(); self._enumerables.style.display = "inline-block"; };
-        this._fieldSpan.onblur = function (e: any) { updateText(false); };
-
-        // MOVED 
-        const setDashDoc = (doc: Doc) => {
-            self._dashDoc = doc;
-            if (self._options?.length && !self._dashDoc[self._fieldKey]) {
-                self._dashDoc[self._fieldKey] = StrCast(self._options[0].title);
-            }
-            this._labelSpan.innerHTML = `${self._fieldKey}: `;
-            const fieldVal = Cast(this._dashDoc?.[self._fieldKey], "boolean", null);
-            this._fieldCheck.style.display = (fieldVal === true || fieldVal === false) ? "inline-block" : "none";
-            this._fieldSpan.style.display = !(fieldVal === true || fieldVal === false) ? StrCast(this._dashDoc?.[self._fieldKey]) ? "" : "inline-block" : "none";
-        };
-
-        //Moved
-        this._fieldSpan.onkeydown = function (e: any) {
-            e.stopPropagation();
-            if ((e.key === "a" && e.ctrlKey) || (e.key === "a" && e.metaKey)) {
-                if (window.getSelection) {
-                    const range = document.createRange();
-                    range.selectNodeContents(self._fieldSpan);
-                    window.getSelection()!.removeAllRanges();
-                    window.getSelection()!.addRange(range);
-                }
-                e.preventDefault();
-            }
-            if (e.key === "Enter") {
-                e.preventDefault();
-                e.ctrlKey && Doc.addFieldEnumerations(self._textBoxDoc, self._fieldKey, [{ title: self._fieldSpan.innerText }]);
-                updateText(true);
-            }
-        };
-
-        this._labelSpan = document.createElement("span");
-        this._labelSpan.style.position = "relative";
-        this._labelSpan.style.fontSize = "small";
-        this._labelSpan.title = "click to see related tags";
-        this._labelSpan.style.fontSize = "x-small";
-        this._labelSpan.onpointerdown = function (e: any) {
-            e.stopPropagation();
-            let container = tbox.props.ContainingCollectionView;
-            while (container?.props.Document.isTemplateForField || container?.props.Document.isTemplateDoc) {
-                container = container.props.ContainingCollectionView;
-            }
-            if (container) {
-                const alias = Doc.MakeAlias(container.props.Document);
-                alias.viewType = CollectionViewType.Time;
-                let list = Cast(alias.schemaColumns, listSpec(SchemaHeaderField));
-                if (!list) {
-                    alias.schemaColumns = list = new List<SchemaHeaderField>();
-                }
-                list.map(c => c.heading).indexOf(self._fieldKey) === -1 && list.push(new SchemaHeaderField(self._fieldKey, "#f1efeb"));
-                list.map(c => c.heading).indexOf("text") === -1 && list.push(new SchemaHeaderField("text", "#f1efeb"));
-                alias._pivotField = self._fieldKey;
-                tbox.props.addDocTab(alias, "onRight");
-            }
-        };
-        this._labelSpan.innerHTML = `${self._fieldKey}: `;
-        //MOVED
-        if (node.attrs.docid) {
-            DocServer.GetRefField(node.attrs.docid).
-                then(async dashDoc => dashDoc instanceof Doc && runInAction(() => setDashDoc(dashDoc)));
-        } else {
-            setDashDoc(tbox.props.DataDoc || tbox.dataDoc);
-        }
-
-        //Moved
-        this._reactionDisposer?.();
-        this._reactionDisposer = reaction(() => { // this reaction will update the displayed text whenever the document's fieldKey's value changes
-            const dashVal = this._dashDoc?.[self._fieldKey];
-            return StrCast(dashVal).startsWith(":=") || dashVal === "" ? Doc.Layout(tbox.props.Document)[self._fieldKey] : dashVal;
-        }, fval => {
-            const boolVal = Cast(fval, "boolean", null);
-            if (boolVal === true || boolVal === false) {
-                this._fieldCheck.checked = boolVal;
-            } else {
-                this._fieldSpan.innerHTML = Field.toString(fval as Field) || "";
-            }
-            this._fieldCheck.style.display = (boolVal === true || boolVal === false) ? "inline-block" : "none";
-            this._fieldSpan.style.display = !(fval === true || fval === false) ? (StrCast(fval) ? "" : "inline-block") : "none";
-        }, { fireImmediately: true });
-
-        //MOVED IN ORDER
-        this._fieldWrapper.appendChild(this._labelSpan);
-        this._fieldWrapper.appendChild(this._fieldCheck);
-        this._fieldWrapper.appendChild(this._fieldSpan);
-        this._fieldWrapper.appendChild(this._enumerables);
-        (this as any).dom = this._fieldWrapper;
-        //updateText(false);
-    }
-    //MOVED
-    destroy() {
-        this._reactionDisposer?.();
-    }
-    //moved
-    selectNode() { }
-}
-
-export class FootnoteView {
-    innerView: any;
-    outerView: any;
-    node: any;
-    dom: any;
-    getPos: any;
-
-    constructor(node: any, view: any, getPos: any) {
-        // We'll need these later
-        this.node = node;
-        this.outerView = view;
-        this.getPos = getPos;
-
-        // The node's representation in the editor (empty, for now)
-        this.dom = document.createElement("footnote");
-        this.dom.addEventListener("pointerup", this.toggle, true);
-        // These are used when the footnote is selected
-        this.innerView = null;
-    }
-    selectNode() {
-        const attrs = { ...this.node.attrs };
-        attrs.visibility = true;
-        this.dom.classList.add("ProseMirror-selectednode");
-        if (!this.innerView) this.open();
-    }
-
-    deselectNode() {
-        const attrs = { ...this.node.attrs };
-        attrs.visibility = false;
-        this.dom.classList.remove("ProseMirror-selectednode");
-        if (this.innerView) this.close();
-    }
-    open() {
-        // Append a tooltip to the outer node
-        const tooltip = this.dom.appendChild(document.createElement("div"));
-        tooltip.className = "footnote-tooltip";
-        // And put a sub-ProseMirror into that
-        this.innerView = new EditorView(tooltip, {
-            // You can use any node as an editor document
-            state: EditorState.create({
-                doc: this.node,
-                plugins: [keymap(baseKeymap),
-                keymap({
-                    "Mod-z": () => undo(this.outerView.state, this.outerView.dispatch),
-                    "Mod-y": () => redo(this.outerView.state, this.outerView.dispatch),
-                    "Mod-b": toggleMark(schema.marks.strong)
-                }),
-                    // new Plugin({
-                    //     view(newView) {
-                    //         // TODO -- make this work with RichTextMenu
-                    //         // return FormattedTextBox.getToolTip(newView);
-                    //     }
-                    // })
-                ],
-
-            }),
-            // This is the magic part
-            dispatchTransaction: this.dispatchInner.bind(this),
-            handleDOMEvents: {
-                pointerdown: ((view: any, e: PointerEvent) => {
-                    // Kludge to prevent issues due to the fact that the whole
-                    // footnote is node-selected (and thus DOM-selected) when
-                    // the parent editor is focused.
-                    e.stopPropagation();
-                    document.addEventListener("pointerup", this.ignore, true);
-                    if (this.outerView.hasFocus()) this.innerView.focus();
-                }) as any
-            }
-
-        });
-        setTimeout(() => this.innerView && this.innerView.docView.setSelection(0, 0, this.innerView.root, true), 0);
-    }
-
-    ignore = (e: PointerEvent) => {
-        e.stopPropagation();
-        document.removeEventListener("pointerup", this.ignore, true);
-    }
-
-    toggle = () => {
-        if (this.innerView) this.close();
-        else {
-            this.open();
-        }
-    }
-    close() {
-        this.innerView && this.innerView.destroy();
-        this.innerView = null;
-        this.dom.textContent = "";
-    }
-
-    dispatchInner(tr: any) {
-        const { state, transactions } = this.innerView.state.applyTransaction(tr);
-        this.innerView.updateState(state);
-
-        if (!tr.getMeta("fromOutside")) {
-            const outerTr = this.outerView.state.tr, offsetMap = StepMap.offset(this.getPos() + 1);
-            for (const transaction of transactions) {
-                const steps = transaction.steps;
-                for (const step of steps) {
-                    outerTr.step(step.map(offsetMap));
-                }
-            }
-            if (outerTr.docChanged) this.outerView.dispatch(outerTr);
-        }
-    }
-    update(node: any) {
-        if (!node.sameMarkup(this.node)) return false;
-        this.node = node;
-        if (this.innerView) {
-            const state = this.innerView.state;
-            const start = node.content.findDiffStart(state.doc.content);
-            if (start !== null) {
-                let { a: endA, b: endB } = node.content.findDiffEnd(state.doc.content);
-                const overlap = start - Math.min(endA, endB);
-                if (overlap > 0) { endA += overlap; endB += overlap; }
-                this.innerView.dispatch(
-                    state.tr
-                        .replace(start, endB, node.slice(start, endA))
-                        .setMeta("fromOutside", true));
-            }
-        }
-        return true;
-    }
-
-    destroy() {
-        if (this.innerView) this.close();
-    }
-
-    stopEvent(event: any) {
-        return this.innerView && this.innerView.dom.contains(event.target);
-    }
-
-    ignoreMutation() { return true; }
-}
-
-export class SummaryView {
-    _collapsed: HTMLElement;
-    _view: any;
-    constructor(node: any, view: any, getPos: any) {
-        this._collapsed = document.createElement("span");
-        this._collapsed.className = this.className(node.attrs.visibility);
-        this._view = view;
-        const js = node.toJSON;
-        node.toJSON = function () {
-            return js.apply(this, arguments);
-        };
-
-        this._collapsed.onpointerdown = (e: any) => {
-            const visible = !node.attrs.visibility;
-            const attrs = { ...node.attrs, visibility: visible };
-            let textSelection = TextSelection.create(view.state.doc, getPos() + 1);
-            if (!visible) { // update summarized text and save in attrs
-                textSelection = this.updateSummarizedText(getPos() + 1);
-                attrs.text = textSelection.content();
-                attrs.textslice = attrs.text.toJSON();
-            }
-            view.dispatch(view.state.tr.
-                setSelection(textSelection). // select the current summarized text (or where it will be if its collapsed)
-                replaceSelection(!visible ? new Slice(Fragment.fromArray([]), 0, 0) : node.attrs.text). // collapse/expand it
-                setNodeMarkup(getPos(), undefined, attrs)); // update the attrs
-            e.preventDefault();
-            e.stopPropagation();
-            this._collapsed.className = this.className(visible);
-        };
-        (this as any).dom = this._collapsed;
-    }
-    selectNode() { }
-
-    deselectNode() { }
-
-    className = (visible: boolean) => "formattedTextBox-summarizer" + (visible ? "" : "-collapsed");
-
-    updateSummarizedText(start?: any) {
-        const mtype = this._view.state.schema.marks.summarize;
-        const mtypeInc = this._view.state.schema.marks.summarizeInclusive;
-        let endPos = start;
-
-        const visited = new Set();
-        for (let i: number = start + 1; i < this._view.state.doc.nodeSize - 1; 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.find((m: any) => m.type === mtype || m.type === mtypeInc)) {
-                        visited.add(node);
-                        endPos = i + node.nodeSize - 1;
-                    }
-                    else skip = true;
-                }
-            });
-        }
-        return TextSelection.create(this._view.state.doc, start, endPos);
-    }
-}
\ No newline at end of file
diff --git a/src/client/util/SummaryView.tsx b/src/client/util/SummaryView.tsx
deleted file mode 100644
index 89908d8ee..000000000
--- a/src/client/util/SummaryView.tsx
+++ /dev/null
@@ -1,81 +0,0 @@
-import { TextSelection } from "prosemirror-state";
-import { Fragment, Node, Slice } from "prosemirror-model";
-
-import React = require("react");
-
-interface ISummaryView {
-    node: any;
-    view: any;
-    getPos: any;
-    self: any;
-}
-export class SummaryView extends React.Component<ISummaryView> {
-
-    onPointerDown = (e: any) => {
-        const visible = !this.props.node.attrs.visibility;
-        const attrs = { ...this.props.node.attrs, visibility: visible };
-        let textSelection = TextSelection.create(this.props.view.state.doc, this.props.getPos() + 1);
-        if (!visible) { // update summarized text and save in attrs
-            textSelection = this.updateSummarizedText(this.props.getPos() + 1);
-            attrs.text = textSelection.content();
-            attrs.textslice = attrs.text.toJSON();
-        }
-        this.props.view.dispatch(this.props.view.state.tr.
-            setSelection(textSelection). // select the current summarized text (or where it will be if its collapsed)
-            replaceSelection(!visible ? new Slice(Fragment.fromArray([]), 0, 0) : this.props.node.attrs.text). // collapse/expand it
-            setNodeMarkup(this.props.getPos(), undefined, attrs)); // update the attrs
-        e.preventDefault();
-        e.stopPropagation();
-        const _collapsed = document.getElementById('collapse') as HTMLElement;
-        _collapsed.className = this.className(visible);
-    }
-
-    updateSummarizedText(start?: any) {
-        const mtype = this.props.view.state.schema.marks.summarize;
-        const mtypeInc = this.props.view.state.schema.marks.summarizeInclusive;
-        let endPos = start;
-
-        const visited = new Set();
-        for (let i: number = start + 1; i < this.props.view.state.doc.nodeSize - 1; i++) {
-            let skip = false;
-            this.props.view.state.doc.nodesBetween(start, i, (node: Node, pos: number, parent: Node, index: number) => {
-                if (this.props.node.isLeaf && !visited.has(node) && !skip) {
-                    if (this.props.node.marks.find((m: any) => m.type === mtype || m.type === mtypeInc)) {
-                        visited.add(node);
-                        endPos = i + this.props.node.nodeSize - 1;
-                    }
-                    else skip = true;
-                }
-            });
-        }
-        return TextSelection.create(this.props.view.state.doc, start, endPos);
-    }
-
-    className = (visible: boolean) => "formattedTextBox-summarizer" + (visible ? "" : "-collapsed");
-
-    selectNode() { }
-
-    deselectNode() { }
-
-    render() {
-        const _view = this.props.node.view;
-        const js = this.props.node.toJSon;
-
-        this.props.node.toJSON = function () {
-            return js.apply(this, arguments);
-        };
-
-        const spanCollapsedClassName = this.className(this.props.node.attrs.visibility);
-
-        return (
-            <span
-                className={spanCollapsedClassName}
-                id='collapse'
-                onPointerDown={this.onPointerDown}
-            >
-
-            </span>
-        );
-
-    }
-}
\ No newline at end of file
diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss
deleted file mode 100644
index e2149e9c1..000000000
--- a/src/client/util/TooltipTextMenu.scss
+++ /dev/null
@@ -1,372 +0,0 @@
-@import "../views/globalCssVariables";
-.ProseMirror-menu-dropdown-wrap {
-    display: inline-block;
-    position: relative;
-}
-  
-.ProseMirror-menu-dropdown {
-    vertical-align: 1px;
-    cursor: pointer;
-    position: relative;
-    padding: 0 15px 0 4px;
-    background: white;
-    border-radius: 2px;
-    text-align: left;
-    font-size: 12px;
-    white-space: nowrap;
-    margin-right: 4px;
-
-    &:after {
-        content: "";
-        border-left: 4px solid transparent;
-        border-right: 4px solid transparent;
-        border-top: 4px solid currentColor;
-        opacity: .6;
-        position: absolute;
-        right: 4px;
-        top: calc(50% - 2px);
-    }
-}
-
-.ProseMirror-menu-submenu-wrap {
-    position: relative;
-    margin-right: -4px;
-}
-
-.ProseMirror-menu-dropdown-menu, 
-.ProseMirror-menu-submenu {
-    font-size: 12px;
-    background: white;
-    border: 1px solid rgb(223, 223, 223);
-    min-width: 40px;
-    z-index: 50000;
-    position: absolute;
-    box-sizing: content-box;
-
-    .ProseMirror-menu-dropdown-item {
-        cursor: pointer;
-        z-index: 100000;
-        text-align: left;
-        padding: 3px;
-
-        &:hover {
-            background-color: $light-color-secondary;
-        }
-    }
-}
-
-
-.ProseMirror-menu-submenu-label:after {
-    content: "";
-    border-top: 4px solid transparent;
-    border-bottom: 4px solid transparent;
-    border-left: 4px solid currentColor;
-    opacity: .6;
-    position: absolute;
-    right: 4px;
-    top: calc(50% - 4px);
-}
-  
-  .ProseMirror-icon {
-    display: inline-block;
-    // line-height: .8;
-    // vertical-align: -2px; /* Compensate for padding */
-    // padding: 2px 8px;
-    cursor: pointer;
-
-    &.ProseMirror-menu-disabled {
-        cursor: default;
-    }
-
-    svg {
-        fill:white;
-        height: 1em;
-    }
-
-    span {
-        vertical-align: text-top;
-      }  
-  }
-
-.wrapper {
-    position: absolute;
-    pointer-events: all;
-    display: flex;
-    align-items: center;
-    transform: translateY(-85px);
-
-    height: 35px;
-    background: #323232;
-    border-radius: 6px;
-    box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25);
-
-}
-
-.tooltipMenu, .basic-tools {
-    z-index: 20000;
-    pointer-events: all;
-    padding: 3px;
-    padding-bottom: 5px;
-    display: flex;
-    align-items: center;
-
-    .ProseMirror-example-setup-style hr {
-      padding: 2px 10px;
-      border: none;
-      margin: 1em 0;
-    }
-    
-    .ProseMirror-example-setup-style hr:after {
-      content: "";
-      display: block;
-      height: 1px;
-      background-color: silver;
-      line-height: 2px;
-    }
-}
-
-.menuicon {
-    width: 25px;
-    height: 25px;
-    cursor: pointer;
-    text-align: center;
-    line-height: 25px;
-    margin: 0 2px;
-    border-radius: 3px;
-
-    &:hover {
-        background-color: black;
-
-        #link-drag {
-            background-color: black;
-        }
-    }
-
-    &> * {
-        margin-top: 50%;
-        margin-left: 50%;
-        transform: translate(-50%, -50%);
-    }
-
-    svg {
-        fill: white;
-        width: 18px;
-        height: 18px;
-    }
-}
-
-.menuicon-active {
-    width: 25px;
-    height: 25px;
-    cursor: pointer;
-    text-align: center;
-    line-height: 25px;
-    margin: 0 2px;
-    border-radius: 3px;
-
-    &:hover {
-        background-color: black;
-    }
-
-    &> * {
-        margin-top: 50%;
-        margin-left: 50%;
-        transform: translate(-50%, -50%);
-    }
-
-    svg {
-        fill: greenyellow;
-        width: 18px;
-        height: 18px;
-    }
-}
-
-#colorPicker {
-    position: relative;
-
-    svg {
-        width: 18px;
-        height: 18px;
-        // margin-top: 11px;
-    }
-
-    .buttonColor {
-        position: absolute;
-        top: 24px;
-        left: 1px;
-        width: 24px;
-        height: 4px;
-        margin-top: 0;
-    }
-}
-
-#link-drag {
-    background-color: #323232;
-}
-
-.underline svg {
-    margin-top: 13px;
-}
-
-  .font-size-indicator {
-      font-size: 12px;
-      padding-right: 0px;
-  }
-  .summarize{
-      color: white;
-      height: 20px;
-      text-align: center;
-  }
-  
-
-.brush{
-    display: inline-block;
-    width: 1em;
-    height: 1em;
-    stroke-width: 0;
-    stroke: currentColor;
-    fill: currentColor;
-    margin-right: 15px;
-}
-
-.brush-active{
-    display: inline-block;
-    width: 1em;
-    height: 1em;
-    stroke-width: 3;
-    fill: greenyellow;
-    margin-right: 15px;
-}
-
-.dragger-wrapper {
-    color: #eee;
-    height: 22px;
-    padding: 0 5px;
-    box-sizing: content-box;
-    cursor: grab;
-
-    .dragger {
-        width: 18px;
-        height: 100%;
-        display: flex;
-        justify-content: space-evenly;
-    }
-
-    .dragger-line {
-        width: 2px;
-        height: 100%;
-        background-color: black;
-    }
-}
-
-.button-dropdown-wrapper {
-    display: flex;
-    align-content: center;
-
-    &:hover {
-        background-color: black;
-    }
-}
-
-.buttonSettings-dropdown {
-
-    &.ProseMirror-menu-dropdown {
-        width: 10px;
-        height: 25px;
-        margin: 0;
-        padding: 0 2px;
-        background-color: #323232;
-        text-align: center;
-
-        &:after {
-            border-top: 4px solid white;
-            right: 2px;
-        }
-
-        &:hover {
-            background-color: black;
-        }
-    }
-
-    &.ProseMirror-menu-dropdown-menu {
-        min-width: 150px;
-        left: -27px;
-        top: 31px;
-        background-color: #323232;
-        border: 1px solid #4d4d4d;
-        color: $light-color-secondary;
-        // border: none;
-        // border: 1px solid $light-color-secondary;
-        border-radius: 0 6px 6px 6px;
-        padding: 3px;
-        box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25);
-
-        .ProseMirror-menu-dropdown-item{
-            cursor: default;
-
-            &:last-child {
-                border-bottom: none;
-            }
-
-            &:hover {
-                background-color: #323232;
-            }
-
-            .button-setting, .button-setting-disabled {
-                padding: 2px;
-                border-radius: 2px;
-            }
-
-            .button-setting:hover {
-                cursor: pointer;
-                background-color: black;
-            }
-
-            .separated-button {
-                border-top: 1px solid $light-color-secondary;
-                padding-top: 6px;
-            }
-
-            input {
-               color: black;
-               border: none;
-               border-radius: 1px;
-               padding: 3px;
-            }
-
-            button {
-               padding: 6px;
-               background-color: #323232;
-               border: 1px solid black;
-               border-radius: 1px;
-
-                &:hover {
-                   background-color: black;
-                }
-            }
-        }
-
-
-    }
-}
-
-.colorPicker-wrapper {
-    display: flex;
-    flex-wrap: wrap;
-    justify-content: space-between;
-    margin-top: 3px;
-    margin-left: -3px;
-    width: calc(100% + 6px);
-}
-
-button.colorPicker {
-    width: 20px;
-    height: 20px;
-    border-radius: 15px !important;
-    margin: 3px;
-    border: none !important;
-
-    &.active {
-        border: 2px solid white !important;
-    }
-}
diff --git a/src/client/util/marks_rts.ts b/src/client/util/marks_rts.ts
deleted file mode 100644
index 75d7109e4..000000000
--- a/src/client/util/marks_rts.ts
+++ /dev/null
@@ -1,296 +0,0 @@
-import React = require("react");
-import { DOMOutputSpecArray, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model";
-import { Doc } from "../../new_fields/Doc";
-
-
-const emDOM: DOMOutputSpecArray = ["em", 0];
-const strongDOM: DOMOutputSpecArray = ["strong", 0];
-const codeDOM: DOMOutputSpecArray = ["code", 0];
-
-// :: Object [Specs](#model.MarkSpec) for the marks in the schema.
-export const marks: { [index: string]: MarkSpec } = {
-    // :: MarkSpec A link. Has `href` and `title` attributes. `title`
-    // defaults to the empty string. Rendered and parsed as an `<a>`
-    // element.
-    link: {
-        attrs: {
-            href: {},
-            targetId: { default: "" },
-            linkId: { default: "" },
-            showPreview: { default: true },
-            location: { default: null },
-            title: { default: null },
-            docref: { default: false } // flags whether the linked text comes from a document within Dash.  If so, an attribution label is appended after the text
-        },
-        inclusive: false,
-        parseDOM: [{
-            tag: "a[href]", getAttrs(dom: any) {
-                return { href: dom.getAttribute("href"), location: dom.getAttribute("location"), title: dom.getAttribute("title"), targetId: dom.getAttribute("id") };
-            }
-        }],
-        toDOM(node: any) {
-            return node.attrs.docref && node.attrs.title ?
-                ["div", ["span", `"`], ["span", 0], ["span", `"`], ["br"], ["a", { ...node.attrs, class: "prosemirror-attribution", title: `${node.attrs.title}` }, node.attrs.title], ["br"]] :
-                ["a", { ...node.attrs, id: node.attrs.linkId + node.attrs.targetId, title: `${node.attrs.title}` }, 0];
-        }
-    },
-
-
-    // :: MarkSpec Coloring on text. Has `color` attribute that defined the color of the marked text.
-    pFontColor: {
-        attrs: {
-            color: { default: "#000" }
-        },
-        inclusive: true,
-        parseDOM: [{
-            tag: "span", getAttrs(dom: any) {
-                return { color: dom.getAttribute("color") };
-            }
-        }],
-        toDOM(node: any) {
-            return node.attrs.color ? ['span', { style: 'color:' + node.attrs.color }] : ['span', 0];
-        }
-    },
-
-    marker: {
-        attrs: {
-            highlight: { default: "transparent" }
-        },
-        inclusive: true,
-        parseDOM: [{
-            tag: "span", getAttrs(dom: any) {
-                return { highlight: dom.getAttribute("backgroundColor") };
-            }
-        }],
-        toDOM(node: any) {
-            return node.attrs.highlight ? ['span', { style: 'background-color:' + node.attrs.highlight }] : ['span', { style: 'background-color: transparent' }];
-        }
-    },
-
-    // :: MarkSpec An emphasis mark. Rendered as an `<em>` element.
-    // Has parse rules that also match `<i>` and `font-style: italic`.
-    em: {
-        parseDOM: [{ tag: "i" }, { tag: "em" }, { style: "font-style: italic" }],
-        toDOM() { return emDOM; }
-    },
-
-    // :: MarkSpec A strong mark. Rendered as `<strong>`, parse rules
-    // also match `<b>` and `font-weight: bold`.
-    strong: {
-        parseDOM: [{ tag: "strong" },
-        { tag: "b" },
-        { style: "font-weight" }],
-        toDOM() { return strongDOM; }
-    },
-
-    strikethrough: {
-        parseDOM: [
-            { tag: 'strike' },
-            { style: 'text-decoration=line-through' },
-            { style: 'text-decoration-line=line-through' }
-        ],
-        toDOM: () => ['span', {
-            style: 'text-decoration-line:line-through'
-        }]
-    },
-
-    subscript: {
-        excludes: 'superscript',
-        parseDOM: [
-            { tag: 'sub' },
-            { style: 'vertical-align=sub' }
-        ],
-        toDOM: () => ['sub']
-    },
-
-    superscript: {
-        excludes: 'subscript',
-        parseDOM: [
-            { tag: 'sup' },
-            { style: 'vertical-align=super' }
-        ],
-        toDOM: () => ['sup']
-    },
-
-    mbulletType: {
-        attrs: {
-            bulletType: { default: "decimal" }
-        },
-        toDOM(node: any) {
-            return ['span', {
-                style: `background: ${node.attrs.bulletType === "decimal" ? "yellow" : node.attrs.bulletType === "upper-alpha" ? "blue" : "green"}`
-            }];
-        }
-    },
-
-    metadata: {
-        toDOM() {
-            return ['span', { style: 'font-size:75%; background:rgba(100, 100, 100, 0.2); ' }];
-        }
-    },
-    metadataKey: {
-        toDOM() {
-            return ['span', { style: 'font-style:italic; ' }];
-        }
-    },
-    metadataVal: {
-        toDOM() {
-            return ['span'];
-        }
-    },
-
-    summarizeInclusive: {
-        parseDOM: [
-            {
-                tag: "span",
-                getAttrs: (p: any) => {
-                    if (typeof (p) !== "string") {
-                        const style = getComputedStyle(p);
-                        if (style.textDecoration === "underline") return null;
-                        if (p.parentElement.outerHTML.indexOf("text-decoration: underline") !== -1 &&
-                            p.parentElement.outerHTML.indexOf("text-decoration-style: solid") !== -1) {
-                            return null;
-                        }
-                    }
-                    return false;
-                }
-            },
-        ],
-        inclusive: true,
-        toDOM() {
-            return ['span', {
-                style: 'text-decoration: underline; text-decoration-style: solid; text-decoration-color: rgba(204, 206, 210, 0.92)'
-            }];
-        }
-    },
-
-    summarize: {
-        inclusive: false,
-        parseDOM: [
-            {
-                tag: "span",
-                getAttrs: (p: any) => {
-                    if (typeof (p) !== "string") {
-                        const style = getComputedStyle(p);
-                        if (style.textDecoration === "underline") return null;
-                        if (p.parentElement.outerHTML.indexOf("text-decoration: underline") !== -1 &&
-                            p.parentElement.outerHTML.indexOf("text-decoration-style: dotted") !== -1) {
-                            return null;
-                        }
-                    }
-                    return false;
-                }
-            },
-        ],
-        toDOM() {
-            return ['span', {
-                style: 'text-decoration: underline; text-decoration-style: dotted; text-decoration-color: rgba(204, 206, 210, 0.92)'
-            }];
-        }
-    },
-
-    underline: {
-        parseDOM: [
-            {
-                tag: "span",
-                getAttrs: (p: any) => {
-                    if (typeof (p) !== "string") {
-                        const style = getComputedStyle(p);
-                        if (style.textDecoration === "underline" || p.parentElement.outerHTML.indexOf("text-decoration-style:line") !== -1) {
-                            return null;
-                        }
-                    }
-                    return false;
-                }
-            }
-            // { style: "text-decoration=underline" }
-        ],
-        toDOM: () => ['span', {
-            style: 'text-decoration:underline;text-decoration-style:line'
-        }]
-    },
-
-    search_highlight: {
-        attrs: {
-            selected: { default: false }
-        },
-        parseDOM: [{ style: 'background: yellow' }],
-        toDOM(node: any) {
-            return ['span', {
-                style: `background: ${node.attrs.selected ? "orange" : "yellow"}`
-            }];
-        }
-    },
-
-    // the id of the user who entered the text
-    user_mark: {
-        attrs: {
-            userid: { default: "" },
-            modified: { default: "when?" }, // 1 second intervals since 1970
-        },
-        group: "inline",
-        toDOM(node: any) {
-            const uid = node.attrs.userid.replace(".", "").replace("@", "");
-            const min = Math.round(node.attrs.modified / 12);
-            const hr = Math.round(min / 60);
-            const day = Math.round(hr / 60 / 24);
-            const remote = node.attrs.userid !== Doc.CurrentUserEmail ? " userMark-remote" : "";
-            return ['span', { class: "userMark-" + uid + remote + " userMark-min-" + min + " userMark-hr-" + hr + " userMark-day-" + day }, 0];
-        }
-    },
-    // the id of the user who entered the text
-    user_tag: {
-        attrs: {
-            userid: { default: "" },
-            modified: { default: "when?" }, // 1 second intervals since 1970
-            tag: { default: "" }
-        },
-        group: "inline",
-        inclusive: false,
-        toDOM(node: any) {
-            const uid = node.attrs.userid.replace(".", "").replace("@", "");
-            return ['span', { class: "userTag-" + uid + " userTag-" + node.attrs.tag }, 0];
-        }
-    },
-
-
-    // :: MarkSpec Code font mark. Represented as a `<code>` element.
-    code: {
-        parseDOM: [{ tag: "code" }],
-        toDOM() { return codeDOM; }
-    },
-
-    /* FONTS */
-    pFontFamily: {
-        attrs: {
-            family: { default: "Crimson Text" },
-        },
-        parseDOM: [{
-            tag: "span", getAttrs(dom: any) {
-                const cstyle = getComputedStyle(dom);
-                if (cstyle.font) {
-                    if (cstyle.font.indexOf("Times New Roman") !== -1) return { family: "Times New Roman" };
-                    if (cstyle.font.indexOf("Arial") !== -1) return { family: "Arial" };
-                    if (cstyle.font.indexOf("Georgia") !== -1) return { family: "Georgia" };
-                    if (cstyle.font.indexOf("Comic Sans") !== -1) return { family: "Comic Sans MS" };
-                    if (cstyle.font.indexOf("Tahoma") !== -1) return { family: "Tahoma" };
-                    if (cstyle.font.indexOf("Crimson") !== -1) return { family: "Crimson Text" };
-                }
-            }
-        }],
-        toDOM: (node) => ['span', {
-            style: `font-family: "${node.attrs.family}";`
-        }]
-    },
-
-    /** FONT SIZES */
-    pFontSize: {
-        attrs: {
-            fontSize: { default: 10 }
-        },
-        parseDOM: [{ style: 'font-size: 10px;' }],
-        toDOM: (node) => ['span', {
-            style: `font-size: ${node.attrs.fontSize}px;`
-        }]
-    },
-};
diff --git a/src/client/util/nodes_rts.ts b/src/client/util/nodes_rts.ts
deleted file mode 100644
index e7bcf444a..000000000
--- a/src/client/util/nodes_rts.ts
+++ /dev/null
@@ -1,264 +0,0 @@
-import React = require("react");
-import { DOMOutputSpecArray, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model";
-import { bulletList, listItem, orderedList } from 'prosemirror-schema-list';
-import ParagraphNodeSpec from "./ParagraphNodeSpec";
-
-const blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"],
-    preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0];
-
-// :: Object
-// [Specs](#model.NodeSpec) for the nodes defined in this schema.
-export const nodes: { [index: string]: NodeSpec } = {
-    // :: NodeSpec The top level document node.
-    doc: {
-        content: "block+"
-    },
-
-    footnote: {
-        group: "inline",
-        content: "inline*",
-        inline: true,
-        attrs: {
-            visibility: { default: false }
-        },
-        // This makes the view treat the node as a leaf, even though it
-        // technically has content
-        atom: true,
-        toDOM: () => ["footnote", 0],
-        parseDOM: [{ tag: "footnote" }]
-    },
-
-    paragraph: ParagraphNodeSpec,
-
-    // :: NodeSpec A blockquote (`<blockquote>`) wrapping one or more blocks.
-    blockquote: {
-        content: "block+",
-        group: "block",
-        defining: true,
-        parseDOM: [{ tag: "blockquote" }],
-        toDOM() { return blockquoteDOM; }
-    },
-
-    // :: NodeSpec A horizontal rule (`<hr>`).
-    horizontal_rule: {
-        group: "block",
-        parseDOM: [{ tag: "hr" }],
-        toDOM() { return hrDOM; }
-    },
-
-    // :: NodeSpec A heading textblock, with a `level` attribute that
-    // should hold the number 1 to 6. Parsed and serialized as `<h1>` to
-    // `<h6>` elements.
-    heading: {
-        attrs: { level: { default: 1 } },
-        content: "inline*",
-        group: "block",
-        defining: true,
-        parseDOM: [{ tag: "h1", attrs: { level: 1 } },
-        { tag: "h2", attrs: { level: 2 } },
-        { tag: "h3", attrs: { level: 3 } },
-        { tag: "h4", attrs: { level: 4 } },
-        { tag: "h5", attrs: { level: 5 } },
-        { tag: "h6", attrs: { level: 6 } }],
-        toDOM(node: any) { return ["h" + node.attrs.level, 0]; }
-    },
-
-    // :: NodeSpec A code listing. Disallows marks or non-text inline
-    // nodes by default. Represented as a `<pre>` element with a
-    // `<code>` element inside of it.
-    code_block: {
-        content: "text*",
-        marks: "",
-        group: "block",
-        code: true,
-        defining: true,
-        parseDOM: [{ tag: "pre", preserveWhitespace: "full" }],
-        toDOM() { return preDOM; }
-    },
-
-    // :: NodeSpec The text node.
-    text: {
-        group: "inline"
-    },
-
-    dashComment: {
-        attrs: {
-            docid: { default: "" },
-        },
-        inline: true,
-        group: "inline",
-        toDOM(node) {
-            const attrs = { style: `width: 40px` };
-            return ["span", { ...node.attrs, ...attrs }, "←"];
-        },
-    },
-
-    summary: {
-        inline: true,
-        attrs: {
-            visibility: { default: false },
-            text: { default: undefined },
-            textslice: { default: undefined },
-        },
-        group: "inline",
-        toDOM(node) {
-            const attrs = { style: `width: 40px` };
-            return ["span", { ...node.attrs, ...attrs }];
-        },
-    },
-
-    // :: NodeSpec An inline image (`<img>`) node. Supports `src`,
-    // `alt`, and `href` attributes. The latter two default to the empty
-    // string.
-    image: {
-        inline: true,
-        attrs: {
-            src: {},
-            agnostic: { default: null },
-            width: { default: 100 },
-            alt: { default: null },
-            title: { default: null },
-            float: { default: "left" },
-            location: { default: "onRight" },
-            docid: { default: "" }
-        },
-        group: "inline",
-        draggable: true,
-        parseDOM: [{
-            tag: "img[src]", getAttrs(dom: any) {
-                return {
-                    src: dom.getAttribute("src"),
-                    title: dom.getAttribute("title"),
-                    alt: dom.getAttribute("alt"),
-                    width: Math.min(100, Number(dom.getAttribute("width"))),
-                };
-            }
-        }],
-        // TODO if we don't define toDom, dragging the image crashes. Why?
-        toDOM(node) {
-            const attrs = { style: `width: ${node.attrs.width}` };
-            return ["img", { ...node.attrs, ...attrs }];
-        }
-    },
-
-    dashDoc: {
-        inline: true,
-        attrs: {
-            width: { default: 200 },
-            height: { default: 100 },
-            title: { default: null },
-            float: { default: "right" },
-            location: { default: "onRight" },
-            hidden: { default: false },
-            fieldKey: { default: "" },
-            docid: { default: "" },
-            alias: { default: "" }
-        },
-        group: "inline",
-        draggable: false,
-        toDOM(node) {
-            const attrs = { style: `width: ${node.attrs.width}, height: ${node.attrs.height}` };
-            return ["div", { ...node.attrs, ...attrs }];
-        }
-    },
-
-    dashField: {
-        inline: true,
-        attrs: {
-            fieldKey: { default: "" },
-            docid: { default: "" }
-        },
-        group: "inline",
-        draggable: false,
-        toDOM(node) {
-            const attrs = { style: `width: ${node.attrs.width}, height: ${node.attrs.height}` };
-            return ["div", { ...node.attrs, ...attrs }];
-        }
-    },
-
-    video: {
-        inline: true,
-        attrs: {
-            src: {},
-            width: { default: "100px" },
-            alt: { default: null },
-            title: { default: null }
-        },
-        group: "inline",
-        draggable: true,
-        parseDOM: [{
-            tag: "video[src]", getAttrs(dom: any) {
-                return {
-                    src: dom.getAttribute("src"),
-                    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 }];
-        }
-    },
-
-    // :: NodeSpec A hard line break, represented in the DOM as `<br>`.
-    hard_break: {
-        inline: true,
-        group: "inline",
-        selectable: false,
-        parseDOM: [{ tag: "br" }],
-        toDOM() { return brDOM; }
-    },
-
-    ordered_list: {
-        ...orderedList,
-        content: 'list_item+',
-        group: 'block',
-        attrs: {
-            bulletStyle: { default: 0 },
-            mapStyle: { default: "decimal" },
-            setFontSize: { default: undefined },
-            setFontFamily: { default: "inherit" },
-            setFontColor: { default: "inherit" },
-            inheritedFontSize: { default: undefined },
-            visibility: { default: true },
-            indent: { default: undefined }
-        },
-        toDOM(node: Node<any>) {
-            if (node.attrs.mapStyle === "bullet") return ['ul', 0];
-            const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : "";
-            const fsize = node.attrs.setFontSize ? node.attrs.setFontSize : node.attrs.inheritedFontSize;
-            const ffam = node.attrs.setFontFamily;
-            const color = node.attrs.setFontColor;
-            return node.attrs.visibility ?
-                ['ol', { class: `${map}-ol`, style: `list-style: none; font-size: ${fsize}; font-family: ${ffam}; color:${color}; margin-left: ${node.attrs.indent}` }, 0] :
-                ['ol', { class: `${map}-ol`, style: `list-style: none;` }];
-        }
-    },
-
-    bullet_list: {
-        ...bulletList,
-        content: 'list_item+',
-        group: 'block',
-        // parseDOM: [{ tag: "ul" }, { style: 'list-style-type=disc' }],
-        toDOM(node: Node<any>) {
-            return ['ul', 0];
-        }
-    },
-
-    list_item: {
-        attrs: {
-            bulletStyle: { default: 0 },
-            mapStyle: { default: "decimal" },
-            visibility: { default: true }
-        },
-        ...listItem,
-        content: 'paragraph block*',
-        toDOM(node: any) {
-            const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : "";
-            return node.attrs.visibility ? ["li", { class: `${map}` }, 0] : ["li", { class: `${map}` }, "..."];
-            //return ["li", { class: `${map}` }, 0];
-        }
-    },
-};
\ No newline at end of file
diff --git a/src/client/util/prosemirrorPatches.js b/src/client/util/prosemirrorPatches.js
deleted file mode 100644
index 269423482..000000000
--- a/src/client/util/prosemirrorPatches.js
+++ /dev/null
@@ -1,139 +0,0 @@
-'use strict';
-
-Object.defineProperty(exports, '__esModule', { value: true });
-
-var prosemirrorInputRules = require('prosemirror-inputrules');
-var prosemirrorTransform = require('prosemirror-transform');
-var prosemirrorModel = require('prosemirror-model');
-
-exports.liftListItem = liftListItem;
-exports.sinkListItem = sinkListItem;
-exports.wrappingInputRule = wrappingInputRule;
-// :: (NodeType) → (state: EditorState, dispatch: ?(tr: Transaction)) → bool
-// Create a command to lift the list item around the selection up into
-// a wrapping list.
-function liftListItem(itemType) {
-    return function (tx, dispatch) {
-        var ref = tx.selection;
-        var $from = ref.$from;
-        var $to = ref.$to;
-        var range = $from.blockRange($to, function (node) { return node.childCount && node.firstChild.type == itemType; });
-        if (!range) { return false }
-        if (!dispatch) { return true }
-        if ($from.node(range.depth - 1).type == itemType) // Inside a parent list
-        { return liftToOuterList(tx, dispatch, itemType, range) }
-        else // Outer list node
-        { return liftOutOfList(tx, dispatch, range) }
-    }
-}
-
-function liftToOuterList(tr, dispatch, itemType, range) {
-    var end = range.end, endOfList = range.$to.end(range.depth);
-    if (end < endOfList) {
-        // There are siblings after the lifted items, which must become
-        // children of the last item
-        tr.step(new prosemirrorTransform.ReplaceAroundStep(end - 1, endOfList, end, endOfList,
-            new prosemirrorModel.Slice(prosemirrorModel.Fragment.from(itemType.create(null, range.parent.copy())), 1, 0), 1, true));
-        range = new prosemirrorModel.NodeRange(tr.doc.resolve(range.$from.pos), tr.doc.resolve(endOfList), range.depth);
-    }
-    dispatch(tr.lift(range, prosemirrorTransform.liftTarget(range)).scrollIntoView());
-    return true
-}
-
-function liftOutOfList(tr, dispatch, range) {
-    var list = range.parent;
-    // Merge the list items into a single big item
-    for (var pos = range.end, i = range.endIndex - 1, e = range.startIndex; i > e; i--) {
-        pos -= list.child(i).nodeSize;
-        tr.delete(pos - 1, pos + 1);
-    }
-    var $start = tr.doc.resolve(range.start), item = $start.nodeAfter;
-    var atStart = range.startIndex == 0, atEnd = range.endIndex == list.childCount;
-    var parent = $start.node(-1), indexBefore = $start.index(-1);
-    if (!parent.canReplace(indexBefore + (atStart ? 0 : 1), indexBefore + 1,
-        item.content.append(atEnd ? prosemirrorModel.Fragment.empty : prosemirrorModel.Fragment.from(list)))) { return false }
-    var start = $start.pos, end = start + item.nodeSize;
-    // Strip off the surrounding list. At the sides where we're not at
-    // the end of the list, the existing list is closed. At sides where
-    // this is the end, it is overwritten to its end.
-    tr.step(new prosemirrorTransform.ReplaceAroundStep(start - (atStart ? 1 : 0), end + (atEnd ? 1 : 0), start + 1, end - 1,
-        new prosemirrorModel.Slice((atStart ? prosemirrorModel.Fragment.empty : prosemirrorModel.Fragment.from(list.copy(prosemirrorModel.Fragment.empty)))
-            .append(atEnd ? prosemirrorModel.Fragment.empty : prosemirrorModel.Fragment.from(list.copy(prosemirrorModel.Fragment.empty))),
-            atStart ? 0 : 1, atEnd ? 0 : 1), atStart ? 0 : 1));
-    dispatch(tr.scrollIntoView());
-    return true
-}
-
-// :: (NodeType) → (state: EditorState, dispatch: ?(tr: Transaction)) → bool
-// Create a command to sink the list item around the selection down
-// into an inner list.
-function sinkListItem(itemType) {
-    return function (state, dispatch) {
-        var ref = state.selection;
-        var $from = ref.$from;
-        var $to = ref.$to;
-        var range = $from.blockRange($to, function (node) { return node.childCount && node.firstChild.type == itemType; });
-        if (!range) { return false }
-        var startIndex = range.startIndex;
-        if (startIndex == 0) { return false }
-        var parent = range.parent, nodeBefore = parent.child(startIndex - 1);
-        if (nodeBefore.type != itemType) { return false; }
-
-        if (dispatch) {
-            var nestedBefore = nodeBefore.lastChild && nodeBefore.lastChild.type == parent.type;
-            var inner = prosemirrorModel.Fragment.from(nestedBefore ? itemType.create() : null);
-            let slice = new prosemirrorModel.Slice(prosemirrorModel.Fragment.from(itemType.create(null, prosemirrorModel.Fragment.from(parent.type.create({ ...parent.attrs, fontSize: parent.attrs.fontSize ? parent.attrs.fontSize - 4 : undefined }, inner)))),
-                nestedBefore ? 3 : 1, 0);
-            var before = range.start, after = range.end;
-            dispatch(state.tr.step(new prosemirrorTransform.ReplaceAroundStep(before - (nestedBefore ? 3 : 1), after,
-                before, after, slice, 1, true))
-                .scrollIntoView());
-        }
-        return true
-    }
-}
-
-function findWrappingOutside(range, type) {
-    var parent = range.parent;
-    var startIndex = range.startIndex;
-    var endIndex = range.endIndex;
-    var around = parent.contentMatchAt(startIndex).findWrapping(type);
-    if (!around) { return null }
-    var outer = around.length ? around[0] : type;
-    return parent.canReplaceWith(startIndex, endIndex, outer) ? around : null
-}
-
-function findWrappingInside(range, type) {
-    var parent = range.parent;
-    var startIndex = range.startIndex;
-    var endIndex = range.endIndex;
-    var inner = parent.child(startIndex);
-    var inside = type.contentMatch.findWrapping(inner.type);
-    if (!inside) { return null }
-    var lastType = inside.length ? inside[inside.length - 1] : type;
-    var innerMatch = lastType.contentMatch;
-    for (var i = startIndex; innerMatch && i < endIndex; i++) { innerMatch = innerMatch.matchType(parent.child(i).type); }
-    if (!innerMatch || !innerMatch.validEnd) { return null }
-    return inside
-}
-function findWrapping(range, nodeType, attrs, innerRange, customWithAttrs = null) {
-    if (innerRange === void 0) innerRange = range;
-    let withAttrs = (type) => ({ type: type, attrs: null });
-    var around = findWrappingOutside(range, nodeType);
-    var inner = around && findWrappingInside(innerRange, nodeType);
-    if (!inner) { return null }
-    return around.map(withAttrs).concat({ type: nodeType, attrs: attrs }).concat(inner.map(customWithAttrs ? customWithAttrs : withAttrs))
-}
-function wrappingInputRule(regexp, nodeType, getAttrs, joinPredicate, customWithAttrs = null) {
-    return new prosemirrorInputRules.InputRule(regexp, function (state, match, start, end) {
-        var attrs = getAttrs instanceof Function ? getAttrs(match) : getAttrs;
-        var tr = state.tr.delete(start, end);
-        var $start = tr.doc.resolve(start), range = $start.blockRange(), wrapping = range && findWrapping(range, nodeType, attrs, undefined, customWithAttrs);
-        if (!wrapping) { return null }
-        tr.wrap(range, wrapping);
-        var before = tr.doc.resolve(start - 1).nodeBefore;
-        if (before && before.type == nodeType && prosemirrorTransform.canJoin(tr.doc, start - 1) &&
-            (!joinPredicate || joinPredicate(match, before))) { tr.join(start - 1); }
-        return tr
-    })
-}
\ No newline at end of file
diff --git a/src/client/util/schema_rts.ts b/src/client/util/schema_rts.ts
deleted file mode 100644
index 83561073c..000000000
--- a/src/client/util/schema_rts.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import { Schema, Slice } from "prosemirror-model";
-
-import { nodes } from "./nodes_rts";
-import { marks } from "./marks_rts";
-
-
-// :: Schema
-// This schema rougly corresponds to the document schema used by
-// [CommonMark](http://commonmark.org/), minus the list elements,
-// which are defined in the [`prosemirror-schema-list`](#schema-list)
-// module.
-//
-// To reuse elements from this schema, extend or read from its
-// `spec.nodes` and `spec.marks` [properties](#model.Schema.spec).
-
-export const schema = new Schema({ nodes, marks });
-
-const fromJson = schema.nodeFromJSON;
-
-schema.nodeFromJSON = (json: any) => {
-    const node = fromJson(json);
-    if (json.type === schema.nodes.summary.name) {
-        node.attrs.text = Slice.fromJSON(schema, node.attrs.textslice);
-    }
-    return node;
-};
\ No newline at end of file
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index c02f79187..3624cdb6d 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -15,7 +15,7 @@ import './collections/ParentDocumentSelector.scss';
 import './DocumentButtonBar.scss';
 import { LinkMenu } from "./linking/LinkMenu";
 import { DocumentView } from './nodes/DocumentView';
-import { GoogleRef } from "./nodes/FormattedTextBox";
+import { GoogleRef } from "./nodes/formattedText/FormattedTextBox";
 import { TemplateMenu } from "./TemplateMenu";
 import { Template, Templates } from "./Templates";
 import React = require("react");
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index 172c1864a..70ea955e1 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -8,7 +8,7 @@ import { Scripting } from "../util/Scripting";
 import { SelectionManager } from "../util/SelectionManager";
 import { undoBatch } from "../util/UndoManager";
 import GestureOverlay from "./GestureOverlay";
-import { FormattedTextBox } from "./nodes/FormattedTextBox";
+import { FormattedTextBox } from "./nodes/formattedText/FormattedTextBox";
 
 export class InkingControl {
     @observable static Instance: InkingControl;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 8fb67c435..20238985d 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -19,7 +19,7 @@ import { DocServer } from '../DocServer';
 import { Docs, DocumentOptions } from '../documents/Documents';
 import { DocumentType } from '../documents/DocumentTypes';
 import { HistoryUtil } from '../util/History';
-import RichTextMenu from '../util/RichTextMenu';
+import RichTextMenu from './nodes/formattedText/RichTextMenu';
 import { Scripting } from '../util/Scripting';
 import SettingsManager from '../util/SettingsManager';
 import SharingManager from '../util/SharingManager';
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index f4250e96d..eda8e5684 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -11,7 +11,7 @@ import "./CollectionCarouselView.scss";
 import { CollectionSubView } from './CollectionSubView';
 import { faCaretLeft, faCaretRight } from '@fortawesome/free-solid-svg-icons';
 import { Doc } from '../../../new_fields/Doc';
-import { FormattedTextBox } from '../nodes/FormattedTextBox';
+import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import { ContextMenu } from '../ContextMenu';
 import { ObjectField } from '../../../new_fields/ObjectField';
 
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 49abc6ee6..fb7535d9f 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -15,7 +15,7 @@ import { DragManager, dropActionType } from "../../util/DragManager";
 import { undoBatch, UndoManager } from "../../util/UndoManager";
 import { DocComponent } from "../DocComponent";
 import { FieldViewProps } from "../nodes/FieldView";
-import { FormattedTextBox, GoogleRef } from "../nodes/FormattedTextBox";
+import { FormattedTextBox, GoogleRef } from "../nodes/formattedText/FormattedTextBox";
 import { CollectionView } from "./CollectionView";
 import React = require("react");
 import { basename } from 'path';
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 28b461313..92b27a0c6 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -32,7 +32,7 @@ import { ContextMenuProps } from "../../ContextMenuItem";
 import { InkingControl } from "../../InkingControl";
 import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView";
 import { DocumentViewProps, DocumentView } from "../../nodes/DocumentView";
-import { FormattedTextBox } from "../../nodes/FormattedTextBox";
+import { FormattedTextBox } from "../../nodes/formattedText/FormattedTextBox";
 import { pageSchema } from "../../nodes/ImageBox";
 import PDFMenu from "../../pdf/PDFMenu";
 import { CollectionDockingView } from "../CollectionDockingView";
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index cd8166309..2d3bb6f3c 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -19,7 +19,7 @@ import React = require("react");
 import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
 import { RichTextField } from "../../../../new_fields/RichTextField";
 import { CollectionView } from "../CollectionView";
-import { FormattedTextBox } from "../../nodes/FormattedTextBox";
+import { FormattedTextBox } from "../../nodes/formattedText/FormattedTextBox";
 import { ScriptField } from "../../../../new_fields/ScriptField";
 
 interface MarqueeViewProps {
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index cd78ac7b3..4d20d3e2c 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -19,7 +19,7 @@ import { DocumentViewProps } from "./DocumentView";
 import "./DocumentView.scss";
 import { FontIconBox } from "./FontIconBox";
 import { FieldView, FieldViewProps } from "./FieldView";
-import { FormattedTextBox } from "./FormattedTextBox";
+import { FormattedTextBox } from "./formattedText/FormattedTextBox";
 import { ImageBox } from "./ImageBox";
 import { KeyValueBox } from "./KeyValueBox";
 import { PDFBox } from "./PDFBox";
diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss
deleted file mode 100644
index 3bedb7127..000000000
--- a/src/client/views/nodes/FormattedTextBox.scss
+++ /dev/null
@@ -1,265 +0,0 @@
-@import "../globalCssVariables";
-
-.ProseMirror {
-    width: 100%;
-    height: 100%;
-    min-height: 100%;
-}
-
-.ProseMirror:focus {
-    outline: none !important;
-}
-
-.formattedTextBox-cont {
-    touch-action: none;
-    cursor: text;
-    background: inherit;
-    padding: 0;
-    border-width: 0px;
-    border-radius: inherit;
-    border-color: $intermediate-color;
-    box-sizing: border-box;
-    background-color: inherit;
-    border-style: solid;
-    overflow-y: auto;
-    overflow-x: hidden;
-    color: initial;
-    max-height: 100%;
-    display: flex;
-    flex-direction: row;
-    transition: opacity 1s;
-
-    .formattedTextBox-dictation {
-        height: 12px;
-        width: 10px;
-        top: 0px;
-        left: 0px;
-        position: absolute;
-    }
-}
-.formattedTextBox-outer {
-    position: relative;
-    overflow: auto;
-    display: inline-block;
-    width: 100%;
-    height: 100%;
-}
-
-.formattedTextBox-sidebar-handle {
-    position: absolute;
-    top: calc(50% - 17.5px);
-    width: 10px;
-    height: 35px;
-    background: lightgray;
-    border-radius: 20px;
-    cursor:grabbing;
-}
-
-.formattedTextBox-cont>.formattedTextBox-sidebar-handle {
-    right: 0;
-    left: unset;
-}
-
-.formattedTextBox-sidebar,
-.formattedTextBox-sidebar-inking {
-    border-left: dashed 1px black;
-    height: 100%;
-    display: inline-block;
-    position: absolute;
-    right: 0;
-
-    .collectionfreeformview-container {
-        position: relative;
-    }
-    
-    >.formattedTextBox-sidebar-handle {
-        right: unset;
-        left: -5;
-    }
-}
-
-.formattedTextBox-sidebar-inking {
-    pointer-events: all;
-}
-
-.formattedTextBox-inner-rounded {
-    height: 70%;
-    width: 85%;
-    position: absolute;
-    overflow: auto;
-    top: 15%;
-    left: 10%;
-}
-
-.formattedTextBox-inner-rounded,
-.formattedTextBox-inner {
-    height: 100%;
-    white-space: pre-wrap; 
-}
-
-// .menuicon {
-//     display: inline-block;
-//     border-right: 1px solid rgba(0, 0, 0, 0.2);
-//     color: #888;
-//     line-height: 1;
-//     padding: 0 7px;
-//     margin: 1px;
-//     cursor: pointer;
-//     text-align: center;
-//     min-width: 1.4em;
-// }
-
-.strong,
-.heading {
-    font-weight: bold;
-}
-
-.em {
-    font-style: italic;
-}
-
-.userMarkOpen {
-    background: rgba(255, 255, 0, 0.267);
-    display: inline;
-}
-
-.userMark {
-    background: rgba(255, 255, 0, 0.267);
-    font-size: 2px;
-    display: inline-grid;
-    overflow: hidden;
-    text-overflow: ellipsis;
-    white-space: nowrap;
-    width: 10px;
-    min-height: 10px;
-    text-align: center;
-    align-content: center;
-}
-
-footnote {
-    display: inline-block;
-    position: relative;
-    cursor: pointer;
-
-    div {
-        padding: 0 !important;
-    }
-}
-
-footnote::after {
-    content: counter(prosemirror-footnote);
-    vertical-align: super;
-    font-size: 75%;
-    counter-increment: prosemirror-footnote;
-}
-
-.ProseMirror {
-    counter-reset: prosemirror-footnote;
-}
-
-.footnote-tooltip {
-    cursor: auto;
-    font-size: 75%;
-    position: absolute;
-    left: -30px;
-    top: calc(100% + 10px);
-    background: silver;
-    padding: 3px;
-    border-radius: 2px;
-    max-width: 100px;
-    min-width: 50px;
-    width: max-content;
-}
-
-.prosemirror-attribution {
-    font-size: 8px;
-}
-
-.footnote-tooltip::before {
-    border: 5px solid silver;
-    border-top-width: 0px;
-    border-left-color: transparent;
-    border-right-color: transparent;
-    position: absolute;
-    top: -5px;
-    left: 27px;
-    content: " ";
-    height: 0;
-    width: 0;
-}
-
-
-.formattedTextBox-inlineComment {
-    position: relative;
-    width: 40px;
-    height: 20px;
-    &::before {
-        content: "→";
-    }
-    &:hover {
-        background: orange;
-    }
-}
-
-.formattedTextBox-summarizer {
-    opacity: 0.5;
-    position: relative;
-    width: 40px;
-    height: 20px;
-    &::after {
-        content: "←";
-    }
-}
-
-.formattedTextBox-summarizer-collapsed {
-    opacity: 0.5;
-    position: relative;
-    width: 40px;
-    height: 20px;
-    &::after {
-        content: "...";
-    }
-}
-
-.ProseMirror {
-    touch-action: none;
-    span {
-        font-family: inherit;
-    }
-
-    ol, ul {
-        counter-reset: deci1 0 multi1 0;
-        padding-left: 1em;
-        font-family: inherit;
-    }
-    ol {
-        margin-left: 1em;
-        font-family: inherit;
-    }
-
-    .decimal1-ol { counter-reset: deci1; p {display: inline; font-family: inherit} margin-left: 0; }
-    .decimal2-ol { counter-reset: deci2; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 1em;}
-    .decimal3-ol { counter-reset: deci3; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 2em;}
-    .decimal4-ol { counter-reset: deci4; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 3em;}
-    .decimal5-ol { counter-reset: deci5; p {display: inline; font-family: inherit} font-size: smaller; }
-    .decimal6-ol { counter-reset: deci6; p {display: inline; font-family: inherit} font-size: smaller; }
-    .decimal7-ol { counter-reset: deci7; p {display: inline; font-family: inherit} font-size: smaller; }
-
-    .multi1-ol { counter-reset: multi1; p {display: inline; font-family: inherit} margin-left: 0;     padding-left: 1.2em }
-    .multi2-ol { counter-reset: multi2; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 1.4em;}
-    .multi3-ol { counter-reset: multi3; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 2em;}
-    .multi4-ol { counter-reset: multi4; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 3.4em;}
-
-    .decimal1:before { transition: 0.5s;counter-increment: deci1; display: inline-block; margin-left: -1em;   width:  1em;   content: counter(deci1) ". ";  }
-    .decimal2:before { transition: 0.5s;counter-increment: deci2; display: inline-block; margin-left: -2.1em; width: 2.1em;  content: counter(deci1) "."counter(deci2) ". ";  }
-    .decimal3:before { transition: 0.5s;counter-increment: deci3; display: inline-block; margin-left: -2.85em;width: 2.85em; content: counter(deci1) "."counter(deci2) "."counter(deci3) ". "; }
-    .decimal4:before { transition: 0.5s;counter-increment: deci4; display: inline-block; margin-left: -3.85em;width: 3.85em; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) ". "; }
-    .decimal5:before { transition: 0.5s;counter-increment: deci5; display: inline-block; margin-left: -2em;   width:  5em;   content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) ". "; }
-    .decimal6:before { transition: 0.5s;counter-increment: deci6; display: inline-block; margin-left: -2em;   width:  6em;   content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) "."counter(deci6) ". "; }
-    .decimal7:before { transition: 0.5s;counter-increment: deci7; display: inline-block; margin-left: -2em;   width:  7em;   content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) "."counter(deci6) "."counter(deci7) ". "; }
-    
-    .multi1:before { transition: 0.5s;counter-increment: multi1; display: inline-block; margin-left: -1em;    width: 1.2em;  content: counter(multi1, upper-alpha) ". ";  }
-    .multi2:before { transition: 0.5s;counter-increment: multi2; display: inline-block; margin-left: -2em;    width:   2em;  content: counter(multi1, upper-alpha) "."counter(multi2, decimal) ". "; }
-    .multi3:before { transition: 0.5s;counter-increment: multi3; display: inline-block; margin-left: -2.85em; width:2.85em;  content: counter(multi1, upper-alpha) "."counter(multi2, decimal) "."counter(multi3, lower-alpha) ". "; }
-    .multi4:before { transition: 0.5s;counter-increment: multi4; display: inline-block; margin-left:   -4.2em;  width: 4.2em;    content: counter(multi1, upper-alpha) "."counter(multi2, decimal) "."counter(multi3, lower-alpha) "."counter(multi4, lower-roman) ". "; }
-}
\ No newline at end of file
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
deleted file mode 100644
index e65453aa0..000000000
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ /dev/null
@@ -1,1306 +0,0 @@
-import { library } from '@fortawesome/fontawesome-svg-core';
-import { faEdit, faSmile, faTextHeight, faUpload } from '@fortawesome/free-solid-svg-icons';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { isEqual } from "lodash";
-import { action, computed, IReactionDisposer, Lambda, observable, reaction, runInAction } from "mobx";
-import { observer } from "mobx-react";
-import { baseKeymap } from "prosemirror-commands";
-import { history } from "prosemirror-history";
-import { inputRules } from 'prosemirror-inputrules';
-import { keymap } from "prosemirror-keymap";
-import { Fragment, Mark, Node, Slice } from "prosemirror-model";
-import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from "prosemirror-state";
-import { ReplaceStep } from 'prosemirror-transform';
-import { EditorView } from "prosemirror-view";
-import { DateField } from '../../../new_fields/DateField';
-import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym } from "../../../new_fields/Doc";
-import { documentSchema } from '../../../new_fields/documentSchemas';
-import { Id } from '../../../new_fields/FieldSymbols';
-import { InkTool } from '../../../new_fields/InkField';
-import { PrefetchProxy } from '../../../new_fields/Proxy';
-import { RichTextField } from "../../../new_fields/RichTextField";
-import { RichTextUtils } from '../../../new_fields/RichTextUtils';
-import { createSchema, makeInterface } from "../../../new_fields/Schema";
-import { Cast, DateCast, NumCast, StrCast } from "../../../new_fields/Types";
-import { TraceMobx } from '../../../new_fields/util';
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnOne, returnZero, Utils } from '../../../Utils';
-import { GoogleApiClientUtils, Pulls, Pushes } from '../../apis/google_docs/GoogleApiClientUtils';
-import { DocServer } from "../../DocServer";
-import { Docs, DocUtils } from '../../documents/Documents';
-import { DocumentType } from '../../documents/DocumentTypes';
-import { DictationManager } from '../../util/DictationManager';
-import { DragManager } from "../../util/DragManager";
-import { makeTemplate } from '../../util/DropConverter';
-import buildKeymap from "../../util/ProsemirrorExampleTransfer";
-import RichTextMenu from '../../util/RichTextMenu';
-import { RichTextRules } from "../../util/RichTextRules";
-// import { DashDocCommentView, DashDocView, DashFieldView, FootnoteView, ImageResizeView, OrderedListView, SummaryView } from "../../util/RichTextSchema";
-// import { DashDocCommentView, DashDocView, DashFieldView, FootnoteView, SummaryView } from "../../util/RichTextSchema";
-import { OrderedListView } from "../../util/RichTextSchema";
-import { ImageResizeView } from "../../util/ImageResizeView";
-
-import { DashDocCommentView } from "../../util/DashDocCommentView";
-import { DashFieldView } from "../../util/DashFieldView";
-import { FootnoteView } from "../../util/FootnoteView";
-import { SummaryView } from "../../util/SummaryView";
-import { DashDocView } from "../../util/DashDocView";
-
-import { schema } from "../../util/schema_rts";
-import { SelectionManager } from "../../util/SelectionManager";
-import { undoBatch, UndoManager } from "../../util/UndoManager";
-import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
-import { ContextMenu } from '../ContextMenu';
-import { ContextMenuProps } from '../ContextMenuItem';
-import { ViewBoxAnnotatableComponent } from "../DocComponent";
-import { DocumentButtonBar } from '../DocumentButtonBar';
-import { InkingControl } from "../InkingControl";
-import { AudioBox } from './AudioBox';
-import { FieldView, FieldViewProps } from "./FieldView";
-import "./FormattedTextBox.scss";
-import { FormattedTextBoxComment, formattedTextBoxCommentPlugin } from './FormattedTextBoxComment';
-import React = require("react");
-
-library.add(faEdit);
-library.add(faSmile, faTextHeight, faUpload);
-
-export interface FormattedTextBoxProps {
-    hideOnLeave?: boolean;
-    makeLink?: () => Opt<Doc>;
-    xMargin?: number;
-    yMargin?: number;
-}
-
-const richTextSchema = createSchema({
-    documentText: "string"
-});
-
-export const GoogleRef = "googleDocId";
-
-type RichTextDocument = makeInterface<[typeof richTextSchema, typeof documentSchema]>;
-const RichTextDocument = makeInterface(richTextSchema, documentSchema);
-
-type PullHandler = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => void;
-
-@observer
-export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProps & FormattedTextBoxProps), RichTextDocument>(RichTextDocument) {
-    public static LayoutString(fieldStr: string) { return FieldView.LayoutString(FormattedTextBox, fieldStr); }
-    public static blankState = () => EditorState.create(FormattedTextBox.Instance.config);
-    public static Instance: FormattedTextBox;
-    public ProseRef?: HTMLDivElement;
-    private _ref: React.RefObject<HTMLDivElement> = React.createRef();
-    private _scrollRef: React.RefObject<HTMLDivElement> = React.createRef();
-    private _editorView: Opt<EditorView>;
-    private _applyingChange: boolean = false;
-    private _searchIndex = 0;
-    private _sidebarMovement = 0;
-    private _lastX = 0;
-    private _lastY = 0;
-    private _undoTyping?: UndoManager.Batch;
-    private _disposers: { [name: string]: IReactionDisposer } = {};
-    private dropDisposer?: DragManager.DragDropDisposer;
-
-    @computed get _recording() { return this.dataDoc.audioState === "recording"; }
-    set _recording(value) { this.dataDoc.audioState = value ? "recording" : undefined; }
-
-    @observable private _entered = false;
-
-    public static FocusedBox: FormattedTextBox | undefined;
-    public static SelectOnLoad = "";
-    public static SelectOnLoadChar = "";
-    public static IsFragment(html: string) {
-        return html.indexOf("data-pm-slice") !== -1;
-    }
-    public static GetHref(html: string): string {
-        const parser = new DOMParser();
-        const parsedHtml = parser.parseFromString(html, 'text/html');
-        if (parsedHtml.body.childNodes.length === 1 && parsedHtml.body.childNodes[0].childNodes.length === 1 &&
-            (parsedHtml.body.childNodes[0].childNodes[0] as any).href) {
-            return (parsedHtml.body.childNodes[0].childNodes[0] as any).href;
-        }
-        return "";
-    }
-    public static GetDocFromUrl(url: string) {
-        if (url.startsWith(document.location.origin)) {
-            const split = new URL(url).pathname.split("doc/");
-            const docid = split[split.length - 1];
-            return docid;
-        }
-        return "";
-    }
-
-    @undoBatch
-    public setFontColor(color: string) {
-        const view = this._editorView!;
-        if (view.state.selection.from === view.state.selection.to) return false;
-        if (view.state.selection.to - view.state.selection.from > view.state.doc.nodeSize - 3) {
-            this.layoutDoc.color = color;
-        }
-        const colorMark = view.state.schema.mark(view.state.schema.marks.pFontColor, { color: color });
-        view.dispatch(view.state.tr.addMark(view.state.selection.from, view.state.selection.to, colorMark));
-        return true;
-    }
-
-    constructor(props: any) {
-        super(props);
-        FormattedTextBox.Instance = this;
-        this.updateHighlights();
-    }
-
-    public get CurrentDiv(): HTMLDivElement { return this._ref.current!; }
-
-    linkOnDeselect: Map<string, string> = new Map();
-
-    doLinkOnDeselect() {
-        Array.from(this.linkOnDeselect.entries()).map(entry => {
-            const key = entry[0];
-            const value = entry[1];
-            const id = Utils.GenerateDeterministicGuid(this.dataDoc[Id] + key);
-            DocServer.GetRefField(value).then(doc => {
-                DocServer.GetRefField(id).then(linkDoc => {
-                    this.dataDoc[key] = doc || Docs.Create.FreeformDocument([], { title: value, _width: 500, _height: 500 }, value);
-                    DocUtils.Publish(this.dataDoc[key] as Doc, value, this.props.addDocument, this.props.removeDocument);
-                    if (linkDoc) { (linkDoc as Doc).anchor2 = this.dataDoc[key] as Doc; }
-                    else DocUtils.MakeLink({ doc: this.props.Document }, { doc: this.dataDoc[key] as Doc }, "link to named target", id);
-                });
-            });
-        });
-        this.linkOnDeselect.clear();
-    }
-
-    dispatchTransaction = (tx: Transaction) => {
-        if (this._editorView) {
-            const metadata = tx.selection.$from.marks().find((m: Mark) => m.type === schema.marks.metadata);
-            if (metadata) {
-                const range = tx.selection.$from.blockRange(tx.selection.$to);
-                let text = range ? tx.doc.textBetween(range.start, range.end) : "";
-                let textEndSelection = tx.selection.to;
-                for (; textEndSelection < range!.end && text[textEndSelection - range!.start] !== " "; textEndSelection++) { }
-                text = text.substr(0, textEndSelection - range!.start);
-                text = text.split(" ")[text.split(" ").length - 1];
-                const split = text.split("::");
-                if (split.length > 1 && split[1]) {
-                    const key = split[0];
-                    const value = split[split.length - 1];
-                    this.linkOnDeselect.set(key, value);
-
-                    const id = Utils.GenerateDeterministicGuid(this.dataDoc[Id] + key);
-                    const link = this._editorView.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + id), location: "onRight", title: value });
-                    const mval = this._editorView.state.schema.marks.metadataVal.create();
-                    const offset = (tx.selection.to === range!.end - 1 ? -1 : 0);
-                    tx = tx.addMark(textEndSelection - value.length + offset, textEndSelection, link).addMark(textEndSelection - value.length + offset, textEndSelection, mval);
-                    this.dataDoc[key] = value;
-                }
-            }
-            const state = this._editorView.state.apply(tx);
-            this._editorView.updateState(state);
-            (tx.storedMarks && !this._editorView.state.storedMarks) && (this._editorView.state.storedMarks = tx.storedMarks);
-
-            const tsel = this._editorView.state.selection.$from;
-            tsel.marks().filter(m => m.type === this._editorView!.state.schema.marks.user_mark).map(m => AudioBox.SetScrubTime(Math.max(0, m.attrs.modified * 1000)));
-            const curText = state.doc.textBetween(0, state.doc.content.size, " \n");
-            const curTemp = Cast(this.props.Document[this.props.fieldKey + "-textTemplate"], RichTextField);
-            if (!this._applyingChange) {
-                this._applyingChange = true;
-                this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()));
-                if (!curTemp || curText) { // if no template, or there's text, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended)
-                    this.dataDoc[this.props.fieldKey] = new RichTextField(JSON.stringify(state.toJSON()), curText);
-                    this.dataDoc[this.props.fieldKey + "-noTemplate"] = (curTemp?.Text || "") !== curText; // mark the data field as being split from the template if it has been edited
-                } else { // if we've deleted all the text in a note driven by a template, then restore the template data
-                    this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse(curTemp.Data)));
-                    this.dataDoc[this.props.fieldKey + "-noTemplate"] = undefined; // mark the data field as not being split from any template it might have
-                }
-                this._applyingChange = false;
-            }
-            this.updateTitle();
-            this.tryUpdateHeight();
-        }
-    }
-
-    updateTitle = () => {
-        if ((this.props.Document.isTemplateForField === "text" || !this.props.Document.isTemplateForField) && // only update the title if the data document's data field is changing
-            StrCast(this.dataDoc.title).startsWith("-") && this._editorView && !this.rootDoc.customTitle) {
-            const str = this._editorView.state.doc.textContent;
-            const titlestr = str.substr(0, Math.min(40, str.length));
-            this.dataDoc.title = "-" + titlestr + (str.length > 40 ? "..." : "");
-        }
-    }
-
-    // needs a better API for taking in a set of words with target documents instead of just one target
-    public hyperlinkTerms = (terms: string[], target: Doc) => {
-        if (this._editorView && (this._editorView as any).docView && terms.some(t => t)) {
-            const res = terms.filter(t => t).map(term => this.findInNode(this._editorView!, this._editorView!.state.doc, term));
-            const tr = this._editorView.state.tr;
-            const flattened: TextSelection[] = [];
-            res.map(r => r.map(h => flattened.push(h)));
-            const lastSel = Math.min(flattened.length - 1, this._searchIndex);
-            this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex;
-            const alink = DocUtils.MakeLink({ doc: this.props.Document }, { doc: target }, "automatic")!;
-            const link = this._editorView.state.schema.marks.link.create({
-                href: Utils.prepend("/doc/" + alink[Id]),
-                title: "a link", location: location, linkId: alink[Id], targetId: target[Id]
-            });
-            this._editorView.dispatch(tr.addMark(flattened[lastSel].from, flattened[lastSel].to, link));
-        }
-    }
-    public highlightSearchTerms = (terms: string[]) => {
-        if (this._editorView && (this._editorView as any).docView && terms.some(t => t)) {
-            const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight);
-            const activeMark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight, { selected: true });
-            const res = terms.filter(t => t).map(term => this.findInNode(this._editorView!, this._editorView!.state.doc, term));
-            let tr = this._editorView.state.tr;
-            const flattened: TextSelection[] = [];
-            res.map(r => r.map(h => flattened.push(h)));
-            const lastSel = Math.min(flattened.length - 1, this._searchIndex);
-            flattened.forEach((h: TextSelection, ind: number) => tr = tr.addMark(h.from, h.to, ind === lastSel ? activeMark : mark));
-            this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex;
-            this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(flattened[lastSel].from), tr.doc.resolve(flattened[lastSel].to))).scrollIntoView());
-        }
-    }
-
-    public unhighlightSearchTerms = () => {
-        if (this._editorView && (this._editorView as any).docView) {
-            const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight);
-            const activeMark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight, { selected: true });
-            const end = this._editorView.state.doc.nodeSize - 2;
-            this._editorView.dispatch(this._editorView.state.tr.removeMark(0, end, mark).removeMark(0, end, activeMark));
-        }
-    }
-    adoptAnnotation = (start: number, end: number, mark: Mark) => {
-        const view = this._editorView!;
-        const nmark = view.state.schema.marks.user_mark.create({ ...mark.attrs, userid: Doc.CurrentUserEmail });
-        view.dispatch(view.state.tr.removeMark(start, end, nmark).addMark(start, end, nmark));
-    }
-    protected createDropTarget = (ele: HTMLDivElement) => {
-        this.ProseRef = ele;
-        this.dropDisposer?.();
-        ele && (this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.props.Document));
-    }
-
-    @undoBatch
-    @action
-    drop = async (e: Event, de: DragManager.DropEvent) => {
-        if (de.complete.docDragData) {
-            const draggedDoc = de.complete.docDragData.draggedDocuments.length && de.complete.docDragData.draggedDocuments[0];
-            // replace text contents whend dragging with Alt
-            if (draggedDoc && draggedDoc.type === DocumentType.RTF && !Doc.AreProtosEqual(draggedDoc, this.props.Document) && de.altKey) {
-                if (draggedDoc.data instanceof RichTextField) {
-                    Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new RichTextField(draggedDoc.data.Data, draggedDoc.data.Text);
-                    e.stopPropagation();
-                }
-                // embed document when dragging with a userDropAction or an embedDoc flag set
-            } else if (de.complete.docDragData.userDropAction || de.complete.docDragData.embedDoc) {
-                const target = de.complete.docDragData.droppedDocuments[0];
-                // const link = DocUtils.MakeLink({ doc: this.dataDoc, ctx: this.props.ContainingCollectionDoc }, { doc: target }, "Embedded Doc:" + target.title);
-                // if (link) {
-                target._fitToBox = true;
-                const node = schema.nodes.dashDoc.create({
-                    width: target[WidthSym](), height: target[HeightSym](),
-                    title: "dashDoc", docid: target[Id],
-                    float: "right"
-                });
-                const view = this._editorView!;
-                view.dispatch(view.state.tr.insert(view.posAtCoords({ left: de.x, top: de.y })!.pos, node));
-                this.tryUpdateHeight();
-                e.stopPropagation();
-                // }
-            } // otherwise, fall through to outer collection to handle drop
-        } else if (de.complete.linkDragData) {
-            de.complete.linkDragData.linkDropCallback = this.linkDrop;
-        }
-    }
-    linkDrop = (data: DragManager.LinkDragData) => {
-        const linkDoc = data.linkDocument!;
-        const anchor1Title = linkDoc.anchor1 instanceof Doc ? StrCast(linkDoc.anchor1.title) : "-untitled-";
-        const anchor1Id = linkDoc.anchor1 instanceof Doc ? linkDoc.anchor1[Id] : "";
-        this.makeLinkToSelection(linkDoc[Id], anchor1Title, "onRight", anchor1Id);
-    }
-
-    getNodeEndpoints(context: Node, node: Node): { from: number, to: number } | null {
-        let offset = 0;
-
-        if (context === node) return { from: offset, to: offset + node.nodeSize };
-
-        if (node.isBlock) {
-            // tslint:disable-next-line: prefer-for-of
-            for (let i = 0; i < (context.content as any).content.length; i++) {
-                const result = this.getNodeEndpoints((context.content as any).content[i], node);
-                if (result) {
-                    return {
-                        from: result.from + offset + (context.type.name === "doc" ? 0 : 1),
-                        to: result.to + offset + (context.type.name === "doc" ? 0 : 1)
-                    };
-                }
-                offset += (context.content as any).content[i].nodeSize;
-            }
-            return null;
-        } else {
-            return null;
-        }
-    }
-
-
-    //Recursively finds matches within a given node
-    findInNode(pm: EditorView, node: Node, find: string) {
-        let ret: TextSelection[] = [];
-
-        if (node.isTextblock) {
-            let index = 0, foundAt;
-            const ep = this.getNodeEndpoints(pm.state.doc, node);
-            while (ep && (foundAt = node.textContent.slice(index).search(RegExp(find, "i"))) > -1) {
-                const sel = new TextSelection(pm.state.doc.resolve(ep.from + index + foundAt + 1), pm.state.doc.resolve(ep.from + index + foundAt + find.length + 1));
-                ret.push(sel);
-                index = index + foundAt + find.length;
-            }
-        } else {
-            node.content.forEach((child, i) => ret = ret.concat(this.findInNode(pm, child, find)));
-        }
-        return ret;
-    }
-    static _highlights: string[] = ["Text from Others", "Todo Items", "Important Items", "Disagree Items", "Ignore Items"];
-
-    updateHighlights = () => {
-        clearStyleSheetRules(FormattedTextBox._userStyleSheet);
-        if (FormattedTextBox._highlights.indexOf("Text from Others") !== -1) {
-            addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-remote", { background: "yellow" });
-        }
-        if (FormattedTextBox._highlights.indexOf("My Text") !== -1) {
-            addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { background: "moccasin" });
-        }
-        if (FormattedTextBox._highlights.indexOf("Todo Items") !== -1) {
-            addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "todo", { outline: "black solid 1px" });
-        }
-        if (FormattedTextBox._highlights.indexOf("Important Items") !== -1) {
-            addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "important", { "font-size": "larger" });
-        }
-        if (FormattedTextBox._highlights.indexOf("Disagree Items") !== -1) {
-            addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "disagree", { "text-decoration": "line-through" });
-        }
-        if (FormattedTextBox._highlights.indexOf("Ignore Items") !== -1) {
-            addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "ignore", { "font-size": "1" });
-        }
-        if (FormattedTextBox._highlights.indexOf("By Recent Minute") !== -1) {
-            addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" });
-            const min = Math.round(Date.now() / 1000 / 60);
-            numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-min-" + (min - i), { opacity: ((10 - i - 1) / 10).toString() }));
-            setTimeout(() => this.updateHighlights());
-        }
-        if (FormattedTextBox._highlights.indexOf("By Recent Hour") !== -1) {
-            addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" });
-            const hr = Math.round(Date.now() / 1000 / 60 / 60);
-            numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-hr-" + (hr - i), { opacity: ((10 - i - 1) / 10).toString() }));
-        }
-    }
-
-    sidebarDown = (e: React.PointerEvent) => {
-        this._lastX = e.clientX;
-        this._lastY = e.clientY;
-        this._sidebarMovement = 0;
-        document.addEventListener("pointermove", this.sidebarMove);
-        document.addEventListener("pointerup", this.sidebarUp);
-        e.stopPropagation();
-        e.preventDefault(); // prevents text from being selected during drag
-    }
-    sidebarMove = (e: PointerEvent) => {
-        const bounds = this.CurrentDiv.getBoundingClientRect();
-        this._sidebarMovement += Math.sqrt((e.clientX - this._lastX) * (e.clientX - this._lastX) + (e.clientY - this._lastY) * (e.clientY - this._lastY));
-        this.props.Document.sidebarWidthPercent = "" + 100 * (1 - (e.clientX - bounds.left) / bounds.width) + "%";
-    }
-    sidebarUp = (e: PointerEvent) => {
-        document.removeEventListener("pointermove", this.sidebarMove);
-        document.removeEventListener("pointerup", this.sidebarUp);
-    }
-
-    toggleSidebar = () => this._sidebarMovement < 5 && (this.props.Document.sidebarWidthPercent = StrCast(this.props.Document.sidebarWidthPercent, "0%") === "0%" ? "25%" : "0%");
-
-    public static get DefaultLayout(): Doc | string | undefined {
-        return Cast(Doc.UserDoc().defaultTextLayout, Doc, null) || StrCast(Doc.UserDoc().defaultTextLayout, null);
-    }
-    specificContextMenu = (e: React.MouseEvent): void => {
-        const cm = ContextMenu.Instance;
-
-        const funcs: ContextMenuProps[] = [];
-        this.props.Document.isTemplateDoc && funcs.push({ description: "Make Default Layout", event: async () => Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.props.Document), icon: "eye" });
-        funcs.push({ description: "Reset Default Layout", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" });
-        !this.props.Document.rootDocument && funcs.push({
-            description: "Make Template", event: () => {
-                this.props.Document.isTemplateDoc = makeTemplate(this.props.Document);
-                Doc.AddDocToList(Cast(Doc.UserDoc()["template-notes"], Doc, null), "data", this.props.Document);
-            }, icon: "eye"
-        });
-        funcs.push({ description: "Toggle Single Line", event: () => this.props.Document._singleLine = !this.props.Document._singleLine, icon: "expand-arrows-alt" });
-        funcs.push({ description: "Toggle Sidebar", event: () => this.props.Document._showSidebar = !this.props.Document._showSidebar, icon: "expand-arrows-alt" });
-        funcs.push({ description: "Toggle Dictation Icon", event: () => this.props.Document._showAudio = !this.props.Document._showAudio, icon: "expand-arrows-alt" });
-        funcs.push({ description: "Toggle Menubar", event: () => this.toggleMenubar(), icon: "expand-arrows-alt" });
-
-        const highlighting: ContextMenuProps[] = [];
-        ["My Text", "Text from Others", "Todo Items", "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"].forEach(option =>
-            highlighting.push({
-                description: (FormattedTextBox._highlights.indexOf(option) === -1 ? "Highlight " : "Unhighlight ") + option, event: () => {
-                    e.stopPropagation();
-                    if (FormattedTextBox._highlights.indexOf(option) === -1) {
-                        FormattedTextBox._highlights.push(option);
-                    } else {
-                        FormattedTextBox._highlights.splice(FormattedTextBox._highlights.indexOf(option), 1);
-                    }
-                    this.updateHighlights();
-                }, icon: "expand-arrows-alt"
-            }));
-        funcs.push({ description: "highlighting...", subitems: highlighting, icon: "hand-point-right" });
-
-        ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
-
-        const change = cm.findByDescription("Change Perspective...");
-        const changeItems: ContextMenuProps[] = change && "subitems" in change ? change.subitems : [];
-
-        const noteTypesDoc = Cast(Doc.UserDoc()["template-notes"], Doc, null);
-        DocListCast(noteTypesDoc?.data).forEach(note => {
-            changeItems.push({
-                description: StrCast(note.title), event: undoBatch(() => {
-                    Doc.setNativeView(this.props.Document);
-                    Doc.makeCustomViewClicked(this.rootDoc, Docs.Create.TreeDocument, StrCast(note.title), note);
-                }), icon: "eye"
-            });
-        });
-        changeItems.push({ description: "FreeForm", event: undoBatch(() => Doc.makeCustomViewClicked(this.rootDoc, Docs.Create.FreeformDocument, "freeform"), "change view"), icon: "eye" });
-        !change && cm.addItem({ description: "Change Perspective...", subitems: changeItems, icon: "external-link-alt" });
-
-        const open = cm.findByDescription("Add a Perspective...");
-        const openItems: ContextMenuProps[] = open && "subitems" in open ? open.subitems : [];
-
-        openItems.push({
-            description: "FreeForm", event: undoBatch(() => {
-                const alias = Doc.MakeAlias(this.rootDoc);
-                Doc.makeCustomViewClicked(alias, Docs.Create.FreeformDocument, "freeform");
-                this.props.addDocTab(alias, "onRight");
-            }), icon: "eye"
-        });
-        !open && cm.addItem({ description: "Add a Perspective...", subitems: openItems, icon: "external-link-alt" });
-
-    }
-
-    recordDictation = () => {
-        DictationManager.Controls.listen({
-            interimHandler: this.setCurrentBulletContent,
-            continuous: { indefinite: false },
-        }).then(results => {
-            if (results && [DictationManager.Controls.Infringed].includes(results)) {
-                DictationManager.Controls.stop();
-            }
-            //this._editorView!.focus();
-        });
-    }
-    stopDictation = (abort: boolean) => { DictationManager.Controls.stop(!abort); };
-
-    @action
-    toggleMenubar = () => {
-        this.props.Document._chromeStatus = this.props.Document._chromeStatus === "disabled" ? "enabled" : "disabled";
-    }
-
-    recordBullet = async () => {
-        const completedCue = "end session";
-        const results = await DictationManager.Controls.listen({
-            interimHandler: this.setCurrentBulletContent,
-            continuous: { indefinite: false },
-            terminators: [completedCue, "bullet", "next"]
-        });
-        if (results && [DictationManager.Controls.Infringed, completedCue].includes(results)) {
-            DictationManager.Controls.stop();
-            return;
-        }
-        this.nextBullet(this._editorView!.state.selection.to);
-        setTimeout(this.recordBullet, 2000);
-    }
-
-    setCurrentBulletContent = (value: string) => {
-        if (this._editorView) {
-            const state = this._editorView.state;
-            const now = Date.now();
-            let mark = schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(now / 1000) });
-            if (!this._break && state.selection.to !== state.selection.from) {
-                for (let i = state.selection.from; i <= state.selection.to; i++) {
-                    const pos = state.doc.resolve(i);
-                    const um = Array.from(pos.marks()).find(m => m.type === schema.marks.user_mark);
-                    if (um) {
-                        mark = um;
-                        break;
-                    }
-                }
-            }
-            const recordingStart = DateCast(this.props.Document.recordingStart).date.getTime();
-            this._break = false;
-            value = "" + (mark.attrs.modified * 1000 - recordingStart) / 1000 + value;
-            const from = state.selection.from;
-            const inserted = state.tr.insertText(value).addMark(from, from + value.length + 1, mark);
-            this._editorView.dispatch(inserted.setSelection(TextSelection.create(inserted.doc, from, from + value.length + 1)));
-        }
-    }
-
-    nextBullet = (pos: number) => {
-        if (this._editorView) {
-            const frag = Fragment.fromArray(this.newListItems(2));
-            if (this._editorView.state.doc.resolve(pos).depth >= 2) {
-                const slice = new Slice(frag, 2, 2);
-                let state = this._editorView.state;
-                this._editorView.dispatch(state.tr.step(new ReplaceStep(pos, pos, slice)));
-                pos += 4;
-                state = this._editorView.state;
-                this._editorView.dispatch(state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos, pos)));
-            }
-        }
-    }
-
-    private newListItems = (count: number) => {
-        return numberRange(count).map(x => schema.nodes.list_item.create(undefined, schema.nodes.paragraph.create()));
-    }
-
-    _keymap: any = undefined;
-    _rules: RichTextRules | undefined;
-    @computed get config() {
-        this._keymap = buildKeymap(schema, this.props);
-        this._rules = new RichTextRules(this.props.Document, this);
-        return {
-            schema,
-            plugins: [
-                inputRules(this._rules.inpRules),
-                this.richTextMenuPlugin(),
-                history(),
-                keymap(this._keymap),
-                keymap(baseKeymap),
-                new Plugin({
-                    props: {
-                        attributes: { class: "ProseMirror-example-setup-style" }
-                    }
-                }),
-                formattedTextBoxCommentPlugin
-            ]
-        };
-    }
-
-    makeLinkToSelection(linkDocId: string, title: string, location: string, targetDocId: string) {
-        if (this._editorView) {
-            const link = this._editorView.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + linkDocId), title: title, location: location, linkId: linkDocId, targetId: targetDocId });
-            this._editorView.dispatch(this._editorView.state.tr.removeMark(this._editorView.state.selection.from, this._editorView.state.selection.to, this._editorView.state.schema.marks.link).
-                addMark(this._editorView.state.selection.from, this._editorView.state.selection.to, link));
-        }
-    }
-    componentDidMount() {
-        this._disposers.buttonBar = reaction(
-            () => DocumentButtonBar.Instance,
-            instance => {
-                if (instance) {
-                    this.pullFromGoogleDoc(this.checkState);
-                    this.dataDoc[GoogleRef] && this.dataDoc.unchanged && runInAction(() => instance.isAnimatingFetch = true);
-                }
-            }
-        );
-        this._disposers.linkMaker = reaction(
-            () => this.props.makeLink?.(),
-            (linkDoc: Opt<Doc>) => {
-                if (linkDoc) {
-                    const anchor2Title = linkDoc.anchor2 instanceof Doc ? StrCast(linkDoc.anchor2.title) : "-untitled-";
-                    const anchor2Id = linkDoc.anchor2 instanceof Doc ? linkDoc.anchor2[Id] : "";
-                    this.makeLinkToSelection(linkDoc[Id], anchor2Title, "onRight", anchor2Id);
-                }
-            },
-            { fireImmediately: true }
-        );
-        this._disposers.editorState = reaction(
-            () => {
-                if (this.dataDoc[this.props.fieldKey + "-noTemplate"] || !this.props.Document[this.props.fieldKey + "-textTemplate"]) {
-                    return Cast(this.dataDoc[this.props.fieldKey], RichTextField, null)?.Data;
-                }
-                return Cast(this.props.Document[this.props.fieldKey + "-textTemplate"], RichTextField, null)?.Data;
-            },
-            incomingValue => {
-                if (incomingValue !== undefined && this._editorView && !this._applyingChange) {
-                    const updatedState = JSON.parse(incomingValue);
-                    this._editorView.updateState(EditorState.fromJSON(this.config, updatedState));
-                    this.tryUpdateHeight();
-                }
-            }
-        );
-        this._disposers.pullDoc = reaction(
-            () => this.props.Document[Pulls],
-            () => {
-                if (!DocumentButtonBar.hasPulledHack) {
-                    DocumentButtonBar.hasPulledHack = true;
-                    const unchanged = this.dataDoc.unchanged;
-                    this.pullFromGoogleDoc(unchanged ? this.checkState : this.updateState);
-                }
-            }
-        );
-        this._disposers.pushDoc = reaction(
-            () => this.props.Document[Pushes],
-            () => {
-                if (!DocumentButtonBar.hasPushedHack) {
-                    DocumentButtonBar.hasPushedHack = true;
-                    this.pushToGoogleDoc();
-                }
-            }
-        );
-        this._disposers.height = reaction(
-            () => [this.layoutDoc[WidthSym](), this.layoutDoc._autoHeight],
-            () => this.tryUpdateHeight()
-        );
-
-        this.setupEditor(this.config, this.props.fieldKey);
-
-        this._disposers.search = reaction(() => this.rootDoc.searchMatch,
-            search => search ? this.highlightSearchTerms([Doc.SearchQuery()]) : this.unhighlightSearchTerms(),
-            { fireImmediately: true });
-
-        this._disposers.record = reaction(() => this._recording,
-            () => {
-                if (this._recording) {
-                    setTimeout(action(() => {
-                        this.stopDictation(true);
-                        setTimeout(() => this.recordDictation(), 500);
-                    }), 500);
-                } else setTimeout(() => this.stopDictation(true), 0);
-            }
-        );
-        this._disposers.scrollToRegion = reaction(
-            () => StrCast(this.layoutDoc.scrollToLinkID),
-            async (scrollToLinkID) => {
-                const findLinkFrag = (frag: Fragment, editor: EditorView) => {
-                    const nodes: Node[] = [];
-                    frag.forEach((node, index) => {
-                        const examinedNode = findLinkNode(node, editor);
-                        if (examinedNode && examinedNode.textContent) {
-                            nodes.push(examinedNode);
-                            start += index;
-                        }
-                    });
-                    return { frag: Fragment.fromArray(nodes), start: start };
-                };
-                const findLinkNode = (node: Node, editor: EditorView) => {
-                    if (!node.isText) {
-                        const content = findLinkFrag(node.content, editor);
-                        return node.copy(content.frag);
-                    }
-                    const marks = [...node.marks];
-                    const linkIndex = marks.findIndex(mark => mark.type === editor.state.schema.marks.link);
-                    return linkIndex !== -1 && scrollToLinkID === marks[linkIndex].attrs.href.replace(/.*\/doc\//, "") ? node : undefined;
-                };
-
-                let start = -1;
-                if (this._editorView && scrollToLinkID) {
-                    const editor = this._editorView;
-                    const ret = findLinkFrag(editor.state.doc.content, editor);
-
-                    if (ret.frag.size > 2 && ret.start >= 0) {
-                        let selection = TextSelection.near(editor.state.doc.resolve(ret.start)); // default to near the start
-                        if (ret.frag.firstChild) {
-                            selection = TextSelection.between(editor.state.doc.resolve(ret.start), editor.state.doc.resolve(ret.start + ret.frag.firstChild.nodeSize)); // bcz: looks better to not have the target selected
-                        }
-                        editor.dispatch(editor.state.tr.setSelection(new TextSelection(selection.$from, selection.$from)).scrollIntoView());
-                        const mark = editor.state.schema.mark(this._editorView.state.schema.marks.search_highlight);
-                        setTimeout(() => editor.dispatch(editor.state.tr.addMark(selection.from, selection.to, mark)), 0);
-                        setTimeout(() => this.unhighlightSearchTerms(), 2000);
-                    }
-                    Doc.SetInPlace(this.layoutDoc, "scrollToLinkID", undefined, false);
-                }
-
-            },
-            { fireImmediately: true }
-        );
-        this._disposers.scroll = reaction(() => NumCast(this.props.Document.scrollPos),
-            pos => this._scrollRef.current && this._scrollRef.current.scrollTo({ top: pos }), { fireImmediately: true }
-        );
-
-        setTimeout(() => this.tryUpdateHeight(NumCast(this.layoutDoc.limitHeight, 0)));
-    }
-
-    pushToGoogleDoc = async () => {
-        this.pullFromGoogleDoc(async (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => {
-            const modes = GoogleApiClientUtils.Docs.WriteMode;
-            let mode = modes.Replace;
-            let reference: Opt<GoogleApiClientUtils.Docs.Reference> = Cast(this.dataDoc[GoogleRef], "string");
-            if (!reference) {
-                mode = modes.Insert;
-                reference = { title: StrCast(this.dataDoc.title) };
-            }
-            const redo = async () => {
-                if (this._editorView && reference) {
-                    const content = await RichTextUtils.GoogleDocs.Export(this._editorView.state);
-                    const response = await GoogleApiClientUtils.Docs.write({ reference, content, mode });
-                    response && (this.dataDoc[GoogleRef] = response.documentId);
-                    const pushSuccess = response !== undefined && !("errors" in response);
-                    dataDoc.unchanged = pushSuccess;
-                    DocumentButtonBar.Instance.startPushOutcome(pushSuccess);
-                }
-            };
-            const undo = () => {
-                if (!exportState) {
-                    return;
-                }
-                const content: GoogleApiClientUtils.Docs.Content = {
-                    text: exportState.text,
-                    requests: []
-                };
-                if (reference && content) {
-                    GoogleApiClientUtils.Docs.write({ reference, content, mode });
-                }
-            };
-            UndoManager.AddEvent({ undo, redo });
-            redo();
-        });
-    }
-
-    pullFromGoogleDoc = async (handler: PullHandler) => {
-        const dataDoc = this.dataDoc;
-        const documentId = StrCast(dataDoc[GoogleRef]);
-        let exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>;
-        if (documentId) {
-            exportState = await RichTextUtils.GoogleDocs.Import(documentId, dataDoc);
-        }
-        UndoManager.RunInBatch(() => handler(exportState, dataDoc), Pulls);
-    }
-
-    updateState = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => {
-        let pullSuccess = false;
-        if (exportState !== undefined) {
-            pullSuccess = true;
-            dataDoc.data = new RichTextField(JSON.stringify(exportState.state.toJSON()));
-            setTimeout(() => {
-                if (this._editorView) {
-                    const state = this._editorView.state;
-                    const end = state.doc.content.size - 1;
-                    this._editorView.dispatch(state.tr.setSelection(TextSelection.create(state.doc, end, end)));
-                }
-            }, 0);
-            dataDoc.title = exportState.title;
-            this.rootDoc.customTitle = true;
-            dataDoc.unchanged = true;
-        } else {
-            delete dataDoc[GoogleRef];
-        }
-        DocumentButtonBar.Instance.startPullOutcome(pullSuccess);
-    }
-
-    checkState = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => {
-        if (exportState && this._editorView) {
-            const equalContent = isEqual(this._editorView.state.doc, exportState.state.doc);
-            const equalTitles = dataDoc.title === exportState.title;
-            const unchanged = equalContent && equalTitles;
-            dataDoc.unchanged = unchanged;
-            DocumentButtonBar.Instance.setPullState(unchanged);
-        }
-    }
-
-    clipboardTextSerializer = (slice: Slice): string => {
-        let text = "", separated = true;
-        const from = 0, to = slice.content.size;
-        slice.content.nodesBetween(from, to, (node, pos) => {
-            if (node.isText) {
-                text += node.text!.slice(Math.max(from, pos) - pos, to - pos);
-                separated = false;
-            } else if (!separated && node.isBlock) {
-                text += "\n";
-                separated = true;
-            } else if (node.type.name === "hard_break") {
-                text += "\n";
-            }
-        }, 0);
-        return text;
-    }
-
-    sliceSingleNode(slice: Slice) {
-        return slice.openStart === 0 && slice.openEnd === 0 && slice.content.childCount === 1 ? slice.content.firstChild : null;
-    }
-
-    handlePaste = (view: EditorView, event: Event, slice: Slice): boolean => {
-        const cbe = event as ClipboardEvent;
-        const pdfDocId = cbe.clipboardData && cbe.clipboardData.getData("dash/pdfOrigin");
-        const pdfRegionId = cbe.clipboardData && cbe.clipboardData.getData("dash/pdfRegion");
-        if (pdfDocId && pdfRegionId) {
-            DocServer.GetRefField(pdfDocId).then(pdfDoc => {
-                DocServer.GetRefField(pdfRegionId).then(pdfRegion => {
-                    if ((pdfDoc instanceof Doc) && (pdfRegion instanceof Doc)) {
-                        setTimeout(async () => {
-                            const targetField = Doc.LayoutFieldKey(pdfDoc);
-                            const targetAnnotations = await DocListCastAsync(pdfDoc[DataSym][targetField + "-annotations"]);// bcz: better to have the PDF's view handle updating its own annotations
-                            targetAnnotations?.push(pdfRegion);
-                        });
-
-                        const link = DocUtils.MakeLink({ doc: this.props.Document }, { doc: pdfRegion }, "PDF pasted");
-                        if (link) {
-                            cbe.clipboardData!.setData("dash/linkDoc", link[Id]);
-                            const linkId = link[Id];
-                            const frag = addMarkToFrag(slice.content, (node: Node) => addLinkMark(node, StrCast(pdfDoc.title), linkId));
-                            slice = new Slice(frag, slice.openStart, slice.openEnd);
-                            const tr = view.state.tr.replaceSelection(slice);
-                            view.dispatch(tr.scrollIntoView().setMeta("paste", true).setMeta("uiEvent", "paste"));
-                        }
-                    }
-                });
-            });
-            return true;
-        }
-        return false;
-
-
-        function addMarkToFrag(frag: Fragment, marker: (node: Node) => Node) {
-            const nodes: Node[] = [];
-            frag.forEach(node => nodes.push(marker(node)));
-            return Fragment.fromArray(nodes);
-        }
-        function addLinkMark(node: Node, title: string, linkId: string) {
-            if (!node.isText) {
-                const content = addMarkToFrag(node.content, (node: Node) => addLinkMark(node, title, linkId));
-                return node.copy(content);
-            }
-            const marks = [...node.marks];
-            const linkIndex = marks.findIndex(mark => mark.type.name === "link");
-            const link = view.state.schema.mark(view.state.schema.marks.link, { href: `http://localhost:1050/doc/${linkId}`, location: "onRight", title: title, docref: true });
-            marks.splice(linkIndex === -1 ? 0 : linkIndex, 1, link);
-            return node.mark(marks);
-        }
-    }
-
-    private setupEditor(config: any, fieldKey: string) {
-        const curText = Cast(this.dataDoc[this.props.fieldKey], RichTextField, null);
-        const useTemplate = !curText?.Text && this.props.Document[this.props.fieldKey + "-textTemplate"];
-        const rtfField = Cast((useTemplate && this.props.Document[this.props.fieldKey + "-textTemplate"]) || this.dataDoc[fieldKey], RichTextField);
-        if (this.ProseRef) {
-            const self = this;
-            this._editorView?.destroy();
-            this._editorView = new EditorView(this.ProseRef, {
-                state: rtfField?.Data ? EditorState.fromJSON(config, JSON.parse(rtfField.Data)) : EditorState.create(config),
-                handleScrollToSelection: (editorView) => {
-                    const docPos = editorView.coordsAtPos(editorView.state.selection.from);
-                    const viewRect = self._ref.current!.getBoundingClientRect();
-                    if (docPos.top < viewRect.top || docPos.top > viewRect.bottom) {
-                        docPos && (self._scrollRef.current!.scrollTop += (docPos.top - viewRect.top) * self.props.ScreenToLocalTransform().Scale);
-                    }
-                    return true;
-                },
-                dispatchTransaction: this.dispatchTransaction,
-                nodeViews: {
-                    //dashComment(node, view, getPos) { return new DashDocCommentView({ node, view, getPos }); },
-                    dashField(node, view, getPos) { return new DashFieldView(node, view, getPos, self); },
-                    //dashDoc(node, view, getPos) { return new DashDocView(node, view, getPos, self); },
-                    // dashDoc(node, view, getPos) { return new DashDocView({ node, view, getPos, self }); },
-
-                    // image(node, view, getPos) {
-                    //     //const addDocTab = this.props.addDocTab;
-                    //     return new ImageResizeView({ node, view, getPos, addDocTab: this.props.addDocTab });
-                    // },
-
-
-                    // // WAS : 
-                    // //image(node, view, getPos) { return new ImageResizeView(node, view, getPos, this.props.addDocTab); },
-
-                    // summary(node, view, getPos) { return new SummaryView({ node, view, getPos }); },
-                    // ordered_list(node, view, getPos) { return new OrderedListView(); },
-                    // footnote(node, view, getPos) { return new FootnoteView({ node, outerView, getPos }); }
-                },
-                clipboardTextSerializer: this.clipboardTextSerializer,
-                handlePaste: this.handlePaste,
-            });
-            const startupText = !rtfField && this._editorView && Field.toString(this.dataDoc[fieldKey] as Field);
-            if (startupText) {
-                const { state: { tr }, dispatch } = this._editorView;
-                dispatch(tr.insertText(startupText));
-            }
-        }
-
-        const selectOnLoad = this.rootDoc[Id] === FormattedTextBox.SelectOnLoad;
-        if (selectOnLoad && !this.props.dontRegisterView) {
-            FormattedTextBox.SelectOnLoad = "";
-            this.props.select(false);
-            FormattedTextBox.SelectOnLoadChar && this._editorView!.dispatch(this._editorView!.state.tr.insertText(FormattedTextBox.SelectOnLoadChar));
-            FormattedTextBox.SelectOnLoadChar = "";
-
-        }
-        (selectOnLoad /* || !rtfField?.Text*/) && this._editorView!.focus();
-        // add user mark for any first character that was typed since the user mark that gets set in KeyPress won't have been called yet.
-        this._editorView!.state.storedMarks = [...(this._editorView!.state.storedMarks ? this._editorView!.state.storedMarks : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })];
-    }
-    getFont(font: string) {
-        switch (font) {
-            case "Arial": return schema.marks.arial.create();
-            case "Times New Roman": return schema.marks.timesNewRoman.create();
-            case "Georgia": return schema.marks.georgia.create();
-            case "Comic Sans MS": return schema.marks.comicSans.create();
-            case "Tahoma": return schema.marks.tahoma.create();
-            case "Impact": return schema.marks.impact.create();
-            case "ACrimson Textrial": return schema.marks.crimson.create();
-        }
-        return schema.marks.arial.create();
-    }
-
-    componentWillUnmount() {
-        Object.values(this._disposers).forEach(disposer => disposer?.());
-        this._editorView?.destroy();
-    }
-
-    static _downEvent: any;
-    _downX = 0;
-    _downY = 0;
-    _break = false;
-    onPointerDown = (e: React.PointerEvent): void => {
-        if (this._recording && !e.ctrlKey && e.button === 0) {
-            this.stopDictation(true);
-            this._break = true;
-            const state = this._editorView!.state;
-            const to = state.selection.to;
-            const updated = TextSelection.create(state.doc, to, to);
-            this._editorView!.dispatch(this._editorView!.state.tr.setSelection(updated).insertText("\n", to));
-            e.preventDefault();
-            e.stopPropagation();
-            if (this._recording) setTimeout(() => this.recordDictation(), 500);
-        }
-        this._downX = e.clientX;
-        this._downY = e.clientY;
-        this.doLinkOnDeselect();
-        FormattedTextBox._downEvent = true;
-        FormattedTextBoxComment.textBox = this;
-        if (this.props.onClick && e.button === 0 && !this.props.isSelected(false)) {
-            e.preventDefault();
-        }
-        if (e.button === 0 && this.active(true) && !e.altKey && !e.ctrlKey && !e.metaKey) {
-            if (e.clientX < this.ProseRef!.getBoundingClientRect().right) { // don't stop propagation if clicking in the sidebar
-                e.stopPropagation();
-            }
-        }
-        if (e.button === 2 || (e.button === 0 && e.ctrlKey)) {
-            e.preventDefault();
-        }
-    }
-
-    onPointerUp = (e: React.PointerEvent): void => {
-        if (!FormattedTextBox._downEvent) return;
-        FormattedTextBox._downEvent = false;
-        if (!(e.nativeEvent as any).formattedHandled) {
-            FormattedTextBoxComment.textBox = this;
-            FormattedTextBoxComment.update(this._editorView!);
-        }
-        (e.nativeEvent as any).formattedHandled = true;
-
-        if (e.buttons === 1 && this.props.isSelected(true) && !e.altKey) {
-            e.stopPropagation();
-        }
-        this._downX = this._downY = Number.NaN;
-    }
-
-    @action
-    onFocused = (e: React.FocusEvent): void => {
-        FormattedTextBox.FocusedBox = this;
-        this.tryUpdateHeight();
-
-        // see if we need to preserve the insertion point
-        const prosediv = this.ProseRef?.children?.[0] as any;
-        const keeplocation = prosediv?.keeplocation;
-        prosediv && (prosediv.keeplocation = undefined);
-        const pos = this._editorView?.state.selection.$from.pos || 1;
-        keeplocation && setTimeout(() => this._editorView?.dispatch(this._editorView?.state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos))));
-        const coords = !Number.isNaN(this._downX) ? { left: this._downX, top: this._downY, bottom: this._downY, right: this._downX } : this._editorView?.coordsAtPos(pos);
-
-        // jump rich text menu to this textbox
-        const bounds = this._ref.current?.getBoundingClientRect();
-        if (bounds && this.props.Document._chromeStatus !== "disabled") {
-            const x = Math.min(Math.max(bounds.left, 0), window.innerWidth - RichTextMenu.Instance.width);
-            let y = Math.min(Math.max(0, bounds.top - RichTextMenu.Instance.height - 50), window.innerHeight - RichTextMenu.Instance.height);
-            if (coords && coords.left > x && coords.left < x + RichTextMenu.Instance.width && coords.top > y && coords.top < y + RichTextMenu.Instance.height + 50) {
-                y = Math.min(bounds.bottom, window.innerHeight - RichTextMenu.Instance.height);
-            }
-            RichTextMenu.Instance.jumpTo(x, y);
-        }
-    }
-    onPointerWheel = (e: React.WheelEvent): void => {
-        // if a text note is not selected and scrollable, this prevents us from being able to scroll and zoom out at the same time
-        if (this.props.isSelected(true) || e.currentTarget.scrollHeight > e.currentTarget.clientHeight) {
-            e.stopPropagation();
-        }
-    }
-
-    static _bulletStyleSheet: any = addStyleSheet();
-    static _userStyleSheet: any = addStyleSheet();
-
-    onClick = (e: React.MouseEvent): void => {
-        if ((this._editorView!.root as any).getSelection().isCollapsed) { // this is a hack to allow the cursor to be placed at the end of a document when the document ends in an inline dash comment.  Apparently Chrome on Windows has a bug/feature which breaks this when clicking after the end of the text.
-            const pcords = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY });
-            const node = pcords && this._editorView!.state.doc.nodeAt(pcords.pos); // get what prosemirror thinks the clicked node is (if it's null, then we didn't click on any text)
-            if (pcords && node?.type === this._editorView!.state.schema.nodes.dashComment) {
-                this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, pcords.pos + 2)));
-                e.preventDefault();
-            }
-            if (!node && this.ProseRef) {
-                const lastNode = this.ProseRef.children[this.ProseRef.children.length - 1].children[this.ProseRef.children[this.ProseRef.children.length - 1].children.length - 1]; // get the last prosemirror div
-                if (e.clientY > lastNode?.getBoundingClientRect().bottom) { // if we clicked below the last prosemirror div, then set the selection to be the end of the document
-                    this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, this._editorView!.state.doc.content.size)));
-                }
-            }
-        }
-        if ((e.nativeEvent as any).formattedHandled) { e.stopPropagation(); return; }
-        (e.nativeEvent as any).formattedHandled = true;
-        // if (e.button === 0 && ((!this.props.isSelected(true) && !e.ctrlKey) || (this.props.isSelected(true) && e.ctrlKey)) && !e.metaKey && e.target) {
-        //     let href = (e.target as any).href;
-        //     let location: string;
-        //     if ((e.target as any).attributes.location) {
-        //         location = (e.target as any).attributes.location.value;
-        //     }
-        //     let pcords = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY });
-        //     let node = pcords && this._editorView!.state.doc.nodeAt(pcords.pos);
-        //     if (node) {
-        //         let link = node.marks.find(m => m.type === this._editorView!.state.schema.marks.link);
-        //         if (link && !(link.attrs.docref && link.attrs.title)) {  // bcz: getting hacky.  this indicates that we clicked on a PDF excerpt quotation.  In this case, we don't want to follow the link (we follow only the actual hyperlink for the quotation which is handled above).
-        //             href = link && link.attrs.href;
-        //             location = link && link.attrs.location;
-        //         }
-        //     }
-        //     if (href) {
-        //         if (href.indexOf(Utils.prepend("/doc/")) === 0) {
-        //             let linkClicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
-        //             if (linkClicked) {
-        //                 DocServer.GetRefField(linkClicked).then(async linkDoc => {
-        //                     (linkDoc instanceof Doc) &&
-        //                         DocumentManager.Instance.FollowLink(linkDoc, this.props.Document, document => this.props.addDocTab(document, location ? location : "inTab"), false);
-        //                 });
-        //             }
-        //         } else {
-        //             let webDoc = Docs.Create.WebDocument(href, { x: NumCast(this.layoutDoc.x, 0) + NumCast(this.layoutDoc.width, 0), y: NumCast(this.layoutDoc.y) });
-        //             this.props.addDocument && this.props.addDocument(webDoc);
-        //         }
-        //         e.stopPropagation();
-        //         e.preventDefault();
-        //     }
-        // }
-
-        if (Math.abs(e.clientX - this._downX) < 4 && Math.abs(e.clientX - this._downX) < 4) {
-            this.props.select(e.ctrlKey);
-            this.hitBulletTargets(e.clientX, e.clientY, e.shiftKey, false);
-        }
-    }
-
-    // this hackiness handles clicking on the list item bullets to do expand/collapse.  the bullets are ::before pseudo elements so there's no real way to hit test against them.
-    hitBulletTargets(x: number, y: number, select: boolean, highlightOnly: boolean) {
-        clearStyleSheetRules(FormattedTextBox._bulletStyleSheet);
-        const pos = this._editorView!.posAtCoords({ left: x, top: y });
-        if (pos && this.props.isSelected(true)) {
-            // let beforeEle = document.querySelector("." + hit.className) as Element; // const before = hit ? window.getComputedStyle(hit, ':before') : undefined;
-            //const node = this._editorView!.state.doc.nodeAt(pos.pos);
-            const $pos = this._editorView!.state.doc.resolve(pos.pos);
-            let list_node = $pos.node().type === schema.nodes.list_item ? $pos.node() : undefined;
-            if ($pos.node().type === schema.nodes.ordered_list) {
-                for (let off = 1; off < 100; off++) {
-                    const pos = this._editorView!.posAtCoords({ left: x + off, top: y });
-                    const node = pos && this._editorView!.state.doc.nodeAt(pos.pos);
-                    if (node?.type === schema.nodes.list_item) {
-                        list_node = node;
-                        break;
-                    }
-                }
-            }
-            if (list_node && pos.inside >= 0 && this._editorView!.state.doc.nodeAt(pos.inside)!.attrs.bulletStyle === list_node.attrs.bulletStyle) {
-                if (select) {
-                    const $olist_pos = this._editorView!.state.doc.resolve($pos.pos - $pos.parentOffset - 1);
-                    if (!highlightOnly) {
-                        this._editorView!.dispatch(this._editorView!.state.tr.setSelection(new NodeSelection($olist_pos)));
-                    }
-                    addStyleSheetRule(FormattedTextBox._bulletStyleSheet, list_node.attrs.mapStyle + list_node.attrs.bulletStyle + ":hover:before", { background: "lightgray" });
-                } else if (Math.abs(pos.pos - pos.inside) < 2) {
-                    if (!highlightOnly) {
-                        const offset = this._editorView!.state.doc.nodeAt(pos.inside)?.type === schema.nodes.ordered_list ? 1 : 0;
-                        this._editorView!.dispatch(this._editorView!.state.tr.setNodeMarkup(pos.inside + offset, list_node.type, { ...list_node.attrs, visibility: !list_node.attrs.visibility }));
-                        this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, pos.inside + offset)));
-                    }
-                    addStyleSheetRule(FormattedTextBox._bulletStyleSheet, list_node.attrs.mapStyle + list_node.attrs.bulletStyle + ":hover:before", { background: "lightgray" });
-                }
-            }
-        }
-    }
-    onMouseUp = (e: React.MouseEvent): void => {
-        e.stopPropagation();
-
-        const view = this._editorView as any;
-        // this interposes on prosemirror's upHandler to prevent prosemirror's up from invoked multiple times when there 
-        // are nested prosemirrors.  We only want the lowest level prosemirror to be invoked.
-        if (view.mouseDown) {
-            const originalUpHandler = view.mouseDown.up;
-            view.root.removeEventListener("mouseup", originalUpHandler);
-            view.mouseDown.up = (e: MouseEvent) => {
-                !(e as any).formattedHandled && originalUpHandler(e);
-                // e.stopPropagation();
-                (e as any).formattedHandled = true;
-            };
-            view.root.addEventListener("mouseup", view.mouseDown.up);
-        }
-    }
-
-    richTextMenuPlugin() {
-        return new Plugin({
-            view(newView) {
-                RichTextMenu.Instance && RichTextMenu.Instance.changeView(newView);
-                return RichTextMenu.Instance;
-            }
-        });
-    }
-
-    public static HadSelection: boolean = false;
-    onBlur = (e: any) => {
-        FormattedTextBox.HadSelection = window.getSelection()?.toString() !== "";
-        //DictationManager.Controls.stop(false);
-        if (this._undoTyping) {
-            this._undoTyping.end();
-            this._undoTyping = undefined;
-        }
-        this.doLinkOnDeselect();
-
-        // move the richtextmenu offscreen
-        if (!RichTextMenu.Instance.Pinned && !RichTextMenu.Instance.overMenu) RichTextMenu.Instance.jumpTo(-300, -300);
-    }
-
-    _lastTimedMark: Mark | undefined = undefined;
-    onKeyPress = (e: React.KeyboardEvent) => {
-        if (e.altKey) {
-            e.preventDefault();
-            return;
-        }
-        const state = this._editorView!.state;
-        if (!state.selection.empty && e.key === "%") {
-            this._rules!.EnteringStyle = true;
-            e.preventDefault();
-            e.stopPropagation();
-            return;
-        }
-
-        if (state.selection.empty || !this._rules!.EnteringStyle) {
-            this._rules!.EnteringStyle = false;
-        }
-        if (e.key === "Escape") {
-            this._editorView!.dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from)));
-            (document.activeElement as any).blur?.();
-            SelectionManager.DeselectAll();
-        }
-        e.stopPropagation();
-        if (e.key === "Tab" || e.key === "Enter") {
-            e.preventDefault();
-        }
-        const mark = e.key !== " " && this._lastTimedMark ? this._lastTimedMark : schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) });
-        this._lastTimedMark = mark;
-        this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(mark));
-
-        if (!this._undoTyping) {
-            this._undoTyping = UndoManager.StartBatch("undoTyping");
-        }
-    }
-
-    onscrolled = (ev: React.UIEvent) => {
-        this.props.Document.scrollPos = this._scrollRef.current!.scrollTop;
-    }
-    @action
-    tryUpdateHeight(limitHeight?: number) {
-        let scrollHeight = this._ref.current?.scrollHeight;
-        if (this.layoutDoc._autoHeight && scrollHeight &&
-            getComputedStyle(this._ref.current!.parentElement!).top === "0px") {  // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
-            if (limitHeight && scrollHeight > limitHeight) {
-                scrollHeight = limitHeight;
-                this.layoutDoc.limitHeight = undefined;
-                this.layoutDoc._autoHeight = false;
-            }
-            const nh = this.layoutDoc.isTemplateForField ? 0 : NumCast(this.dataDoc._nativeHeight, 0);
-            const dh = NumCast(this.layoutDoc._height, 0);
-            const newHeight = Math.max(10, (nh ? dh / nh * scrollHeight : scrollHeight) + (this.props.ChromeHeight ? this.props.ChromeHeight() : 0));
-            if (Math.abs(newHeight - dh) > 1) { // bcz: Argh!  without this, we get into a React crash if the same document is opened in a freeform view and in the treeview.  no idea why, but after dragging the freeform document, selecting it, and selecting text, it will compute to 1 pixel higher than the treeview which causes a cycle
-                this.layoutDoc._height = newHeight;
-                this.dataDoc._nativeHeight = nh ? scrollHeight : undefined;
-            }
-        }
-    }
-
-    @computed get sidebarWidthPercent() { return StrCast(this.props.Document.sidebarWidthPercent, "0%"); }
-    sidebarWidth = () => Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100 * this.props.PanelWidth();
-    sidebarScreenToLocal = () => this.props.ScreenToLocalTransform().translate(-(this.props.PanelWidth() - this.sidebarWidth()), 0);
-    @computed get sidebarColor() { return StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], "transparent")); }
-    render() {
-        TraceMobx();
-        const rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : "";
-        const interactive = InkingControl.Instance.selectedTool || this.layoutDoc.isBackground;
-        if (this.props.isSelected()) {
-            this._editorView && RichTextMenu.Instance.updateFromDash(this._editorView, undefined, this.props);
-        } else if (FormattedTextBoxComment.textBox === this) {
-            FormattedTextBoxComment.Hide();
-        }
-        return (
-
-            <div className={`formattedTextBox-cont`} ref={this._ref}
-                style={{
-                    height: this.layoutDoc._autoHeight && this.props.renderDepth ? "max-content" : `calc(100% - ${this.props.ChromeHeight?.() || 0}px`,
-                    background: StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], this.props.hideOnLeave ? "rgba(0,0,0 ,0.4)" : ""),
-                    opacity: this.props.hideOnLeave ? (this._entered ? 1 : 0.1) : 1,
-                    color: StrCast(this.layoutDoc[this.props.fieldKey + "-color"], this.props.hideOnLeave ? "white" : "inherit"),
-                    pointerEvents: interactive ? "none" : undefined,
-                    fontSize: Cast(this.layoutDoc._fontSize, "number", null),
-                    fontFamily: StrCast(this.layoutDoc._fontFamily, "inherit"),
-                }}
-                onContextMenu={this.specificContextMenu}
-                onKeyDown={this.onKeyPress}
-                onFocus={this.onFocused}
-                onClick={this.onClick}
-                onPointerMove={e => this.hitBulletTargets(e.clientX, e.clientY, e.shiftKey, true)}
-                onBlur={this.onBlur}
-                onPointerUp={this.onPointerUp}
-                onPointerDown={this.onPointerDown}
-                onMouseUp={this.onMouseUp}
-                onWheel={this.onPointerWheel}
-                onPointerEnter={action(() => this._entered = true)}
-                onPointerLeave={action((e: React.PointerEvent<HTMLDivElement>) => {
-                    this._entered = false;
-                    const target = document.elementFromPoint(e.nativeEvent.x, e.nativeEvent.y);
-                    for (let child: any = target; child; child = child?.parentElement) {
-                        if (child === this._ref.current!) {
-                            this._entered = true;
-                        }
-                    }
-                })}
-            >
-                <div className={`formattedTextBox-outer`} style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, }} onScroll={this.onscrolled} ref={this._scrollRef}>
-                    <div className={`formattedTextBox-inner${rounded}`} ref={this.createDropTarget}
-                        style={{
-                            padding: `${NumCast(this.layoutDoc._yMargin, this.props.yMargin || 0)}px  ${NumCast(this.layoutDoc._xMargin, this.props.xMargin || 0)}px`,
-                            pointerEvents: ((this.layoutDoc.isLinkButton || this.props.onClick) && !this.props.isSelected()) ? "none" : undefined
-                        }} />
-                </div>
-                {!this.props.Document._showSidebar ? (null) : this.sidebarWidthPercent === "0%" ?
-                    <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown} onClick={e => this.toggleSidebar()} /> :
-                    <div className={"formattedTextBox-sidebar" + (InkingControl.Instance.selectedTool !== InkTool.None ? "-inking" : "")}
-                        style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
-                        <CollectionFreeFormView {...this.props}
-                            PanelHeight={this.props.PanelHeight}
-                            PanelWidth={this.sidebarWidth}
-                            NativeHeight={returnZero}
-                            NativeWidth={returnZero}
-                            annotationsKey={this.annotationKey}
-                            isAnnotationOverlay={false}
-                            focus={this.props.focus}
-                            isSelected={this.props.isSelected}
-                            select={emptyFunction}
-                            active={this.annotationsActive}
-                            ContentScaling={returnOne}
-                            whenActiveChanged={this.whenActiveChanged}
-                            removeDocument={this.removeDocument}
-                            moveDocument={this.moveDocument}
-                            addDocument={this.addDocument}
-                            CollectionView={undefined}
-                            ScreenToLocalTransform={this.sidebarScreenToLocal}
-                            renderDepth={this.props.renderDepth + 1}
-                            ContainingCollectionDoc={this.props.ContainingCollectionDoc}>
-                        </CollectionFreeFormView>
-                        <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown} onClick={e => this.toggleSidebar()} />
-                    </div>}
-                {!this.props.Document._showAudio ? (null) :
-                    <div className="formattedTextBox-dictation"
-                        onPointerDown={e => {
-                            runInAction(() => this._recording = !this._recording);
-                            setTimeout(() => this._editorView!.focus(), 500);
-                            e.stopPropagation();
-                        }} >
-                        <FontAwesomeIcon className="formattedTExtBox-audioFont"
-                            style={{ color: this._recording ? "red" : "blue", opacity: this._recording ? 1 : 0.5, display: this.props.isSelected() ? "" : "none" }} icon={"microphone"} size="sm" />
-                    </div>}
-            </div>
-        );
-    }
-}
diff --git a/src/client/views/nodes/FormattedTextBoxComment.scss b/src/client/views/nodes/FormattedTextBoxComment.scss
deleted file mode 100644
index 2dd63ec21..000000000
--- a/src/client/views/nodes/FormattedTextBoxComment.scss
+++ /dev/null
@@ -1,33 +0,0 @@
-.FormattedTextBox-tooltip {
-    position: absolute;
-    pointer-events: none;
-    z-index: 20;
-    background: white;
-    border: 1px solid silver;
-    border-radius: 2px;
-    margin-bottom: 7px;
-    -webkit-transform: translateX(-50%);
-    transform: translateX(-50%);
-  }
-  .FormattedTextBox-tooltip: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;
-  }
-  .FormattedTextBox-tooltip: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: white;
-  }
\ No newline at end of file
diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx
deleted file mode 100644
index dfea0f6bb..000000000
--- a/src/client/views/nodes/FormattedTextBoxComment.tsx
+++ /dev/null
@@ -1,236 +0,0 @@
-import { Mark, ResolvedPos } from "prosemirror-model";
-import { EditorState, Plugin } from "prosemirror-state";
-import { EditorView } from "prosemirror-view";
-import * as ReactDOM from 'react-dom';
-import { Doc, DocCastAsync } from "../../../new_fields/Doc";
-import { Cast, FieldValue, NumCast } from "../../../new_fields/Types";
-import { emptyFunction, returnEmptyString, returnFalse, Utils, emptyPath } from "../../../Utils";
-import { DocServer } from "../../DocServer";
-import { DocumentManager } from "../../util/DocumentManager";
-import { schema } from "../../util/schema_rts";
-import { Transform } from "../../util/Transform";
-import { ContentFittingDocumentView } from "./ContentFittingDocumentView";
-import { FormattedTextBox } from "./FormattedTextBox";
-import './FormattedTextBoxComment.scss';
-import React = require("react");
-import { Docs } from "../../documents/Documents";
-import wiki from "wikijs";
-import { DocumentType } from "../../documents/DocumentTypes";
-
-export let formattedTextBoxCommentPlugin = new Plugin({
-    view(editorView) { return new FormattedTextBoxComment(editorView); }
-});
-export function findOtherUserMark(marks: Mark[]): Mark | undefined {
-    return marks.find(m => m.attrs.userid && m.attrs.userid !== Doc.CurrentUserEmail);
-}
-export function findUserMark(marks: Mark[]): Mark | undefined {
-    return marks.find(m => m.attrs.userid);
-}
-export function findLinkMark(marks: Mark[]): Mark | undefined {
-    return marks.find(m => m.type === schema.marks.link);
-}
-export function findStartOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: Mark[]) => Mark | undefined) {
-    let before = 0;
-    let nbef = rpos.nodeBefore;
-    while (nbef && finder(nbef.marks)) {
-        before += nbef.nodeSize;
-        rpos = view.state.doc.resolve(rpos.pos - nbef.nodeSize);
-        rpos && (nbef = rpos.nodeBefore);
-    }
-    return before;
-}
-export function findEndOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: Mark[]) => Mark | undefined) {
-    let after = 0;
-    let naft = rpos.nodeAfter;
-    while (naft && finder(naft.marks)) {
-        after += naft.nodeSize;
-        rpos = view.state.doc.resolve(rpos.pos + naft.nodeSize);
-        rpos && (naft = rpos.nodeAfter);
-    }
-    return after;
-}
-
-
-export class FormattedTextBoxComment {
-    static tooltip: HTMLElement;
-    static tooltipText: HTMLElement;
-    static tooltipInput: HTMLInputElement;
-    static start: number;
-    static end: number;
-    static mark: Mark;
-    static textBox: FormattedTextBox | undefined;
-    static linkDoc: Doc | undefined;
-    constructor(view: any) {
-        if (!FormattedTextBoxComment.tooltip) {
-            const root = document.getElementById("root");
-            FormattedTextBoxComment.tooltipInput = document.createElement("input");
-            FormattedTextBoxComment.tooltipInput.type = "checkbox";
-            FormattedTextBoxComment.tooltip = document.createElement("div");
-            FormattedTextBoxComment.tooltipText = document.createElement("div");
-            FormattedTextBoxComment.tooltipText.style.width = "100%";
-            FormattedTextBoxComment.tooltipText.style.height = "100%";
-            FormattedTextBoxComment.tooltipText.style.textOverflow = "ellipsis";
-            FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipText);
-            FormattedTextBoxComment.tooltip.className = "FormattedTextBox-tooltip";
-            FormattedTextBoxComment.tooltip.style.pointerEvents = "all";
-            FormattedTextBoxComment.tooltip.style.maxWidth = "350px";
-            FormattedTextBoxComment.tooltip.style.maxHeight = "250px";
-            FormattedTextBoxComment.tooltip.style.width = "100%";
-            FormattedTextBoxComment.tooltip.style.height = "100%";
-            FormattedTextBoxComment.tooltip.style.overflow = "hidden";
-            FormattedTextBoxComment.tooltip.style.display = "none";
-            FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipInput);
-            FormattedTextBoxComment.tooltip.onpointerdown = (e: PointerEvent) => {
-                const keep = e.target && (e.target as any).type === "checkbox" ? true : false;
-                const textBox = FormattedTextBoxComment.textBox;
-                if (FormattedTextBoxComment.linkDoc && !keep && textBox) {
-                    if (FormattedTextBoxComment.linkDoc.type !== DocumentType.LINK) {
-                        textBox.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "inTab" : "onRight");
-                    } else {
-                        DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document,
-                            (doc: Doc, followLinkLocation: string) => textBox.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation));
-                    }
-                } else if (textBox && (FormattedTextBoxComment.tooltipText as any).href) {
-                    textBox.props.addDocTab(Docs.Create.WebDocument((FormattedTextBoxComment.tooltipText as any).href, { title: (FormattedTextBoxComment.tooltipText as any).href, _width: 200, _height: 400 }), "onRight");
-                }
-                keep && textBox && FormattedTextBoxComment.start !== undefined && textBox.adoptAnnotation(
-                    FormattedTextBoxComment.start, FormattedTextBoxComment.end, FormattedTextBoxComment.mark);
-                e.stopPropagation();
-                e.preventDefault();
-            };
-            root && root.appendChild(FormattedTextBoxComment.tooltip);
-        }
-    }
-
-    public static Hide() {
-        FormattedTextBoxComment.textBox = undefined;
-        FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = "none");
-        ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText);
-    }
-    public static SetState(textBox: any, start: number, end: number, mark: Mark) {
-        FormattedTextBoxComment.textBox = textBox;
-        FormattedTextBoxComment.start = start;
-        FormattedTextBoxComment.end = end;
-        FormattedTextBoxComment.mark = mark;
-        FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = "");
-    }
-
-    static update(view: EditorView, lastState?: EditorState) {
-        const state = view.state;
-        // Don't do anything if the document/selection didn't change
-        if (lastState && lastState.doc.eq(state.doc) &&
-            lastState.selection.eq(state.selection)) {
-            return;
-        }
-        FormattedTextBoxComment.linkDoc = undefined;
-
-        const textBox = FormattedTextBoxComment.textBox;
-        if (!textBox || !textBox.props) {
-            return;
-        }
-        let set = "none";
-        let nbef = 0;
-        FormattedTextBoxComment.tooltipInput.style.display = "none";
-        FormattedTextBoxComment.tooltip.style.width = "";
-        FormattedTextBoxComment.tooltip.style.height = "";
-        (FormattedTextBoxComment.tooltipText as any).href = "";
-        FormattedTextBoxComment.tooltipText.style.whiteSpace = "";
-        FormattedTextBoxComment.tooltipText.style.overflow = "";
-        // this section checks to see if the insertion point is over text entered by a different user.  If so, it sets ths comment text to indicate the user and the modification date
-        if (state.selection.$from) {
-            nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark);
-            const naft = findEndOfMark(state.selection.$from, view, findOtherUserMark);
-            const noselection = view.state.selection.$from === view.state.selection.$to;
-            let child: any = null;
-            state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any, pos: number, parent: any) => !child && node.marks.length && (child = node));
-            const mark = child && findOtherUserMark(child.marks);
-            if (mark && child && (nbef || naft) && (!mark.attrs.opened || noselection)) {
-                FormattedTextBoxComment.SetState(FormattedTextBoxComment.textBox, state.selection.$from.pos - nbef, state.selection.$from.pos + naft, mark);
-            }
-            if (mark && child && ((nbef && naft) || !noselection)) {
-                FormattedTextBoxComment.tooltipText.textContent = mark.attrs.userid + " date=" + (new Date(mark.attrs.modified * 5000)).toDateString();
-                set = "";
-                FormattedTextBoxComment.tooltipInput.style.display = "";
-            }
-        }
-        // this checks if the selection is a hyperlink.  If so, it displays the target doc's text for internal links, and the url of the target for external links. 
-        if (set === "none" && state.selection.$from) {
-            nbef = findStartOfMark(state.selection.$from, view, findLinkMark);
-            const naft = findEndOfMark(state.selection.$from, view, findLinkMark);
-            let child: any = null;
-            state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any, pos: number, parent: any) => !child && node.marks.length && (child = node));
-            const mark = child && findLinkMark(child.marks);
-            if (mark && child && nbef && naft && mark.attrs.showPreview) {
-                FormattedTextBoxComment.tooltipText.textContent = "external => " + mark.attrs.href;
-                (FormattedTextBoxComment.tooltipText as any).href = mark.attrs.href;
-                if (mark.attrs.href.startsWith("https://en.wikipedia.org/wiki/")) {
-                    wiki().page(mark.attrs.href.replace("https://en.wikipedia.org/wiki/", "")).then(page => page.summary().then(summary => FormattedTextBoxComment.tooltipText.textContent = summary.substring(0, 500)));
-                } else {
-                    FormattedTextBoxComment.tooltipText.style.whiteSpace = "pre";
-                    FormattedTextBoxComment.tooltipText.style.overflow = "hidden";
-                }
-                if (mark.attrs.href.indexOf(Utils.prepend("/doc/")) === 0) {
-                    FormattedTextBoxComment.tooltipText.textContent = "target not found...";
-                    (FormattedTextBoxComment.tooltipText as any).href = "";
-                    const docTarget = mark.attrs.href.replace(Utils.prepend("/doc/"), "").split("?")[0];
-                    try {
-                        ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText);
-                    } catch (e) { }
-                    docTarget && DocServer.GetRefField(docTarget).then(async linkDoc => {
-                        if (linkDoc instanceof Doc) {
-                            (FormattedTextBoxComment.tooltipText as any).href = mark.attrs.href;
-                            FormattedTextBoxComment.linkDoc = linkDoc;
-                            const anchor = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(linkDoc.anchor1, Doc)), textBox.dataDoc) ? Cast(linkDoc.anchor2, Doc) : (Cast(linkDoc.anchor1, Doc)) || linkDoc);
-                            const target = anchor?.annotationOn ? await DocCastAsync(anchor.annotationOn) : anchor;
-                            if (anchor !== target && anchor && target) {
-                                target.scrollY = NumCast(anchor?.y);
-                            }
-                            if (target) {
-                                ReactDOM.render(<ContentFittingDocumentView
-                                    Document={target}
-                                    LibraryPath={emptyPath}
-                                    fitToBox={true}
-                                    moveDocument={returnFalse}
-                                    rootSelected={returnFalse}
-                                    getTransform={Transform.Identity}
-                                    active={returnFalse}
-                                    addDocument={returnFalse}
-                                    removeDocument={returnFalse}
-                                    addDocTab={returnFalse}
-                                    pinToPres={returnFalse}
-                                    dontRegisterView={true}
-                                    renderDepth={1}
-                                    PanelWidth={() => Math.min(350, NumCast(target._width, 350))}
-                                    PanelHeight={() => Math.min(250, NumCast(target._height, 250))}
-                                    focus={emptyFunction}
-                                    whenActiveChanged={returnFalse}
-                                />, FormattedTextBoxComment.tooltipText);
-                                FormattedTextBoxComment.tooltip.style.width = NumCast(target.width) ? `${NumCast(target.width)}` : "100%";
-                                FormattedTextBoxComment.tooltip.style.height = NumCast(target.height) ? `${NumCast(target.height)}` : "100%";
-                            }
-                            // let ext = (target && target.type !== DocumentType.PDFANNO && Doc.fieldExtensionDoc(target, "data")) || target; // try guessing that the target doc's data is in the 'data' field.  probably need an 'overviewLayout' and then just display the target Document ....
-                            // let text = ext && StrCast(ext.text);
-                            // ext && (FormattedTextBoxComment.tooltipText.textContent = (target && target.type === DocumentType.PDFANNO ? "Quoted from " : "") + "=> " + (text || StrCast(ext.title)));
-                        }
-                    });
-                }
-                set = "";
-            }
-        }
-        if (set !== "none") {
-            // These are in screen coordinates
-            // let start = view.coordsAtPos(state.selection.from), end = view.coordsAtPos(state.selection.to);
-            const start = view.coordsAtPos(state.selection.from - nbef), end = view.coordsAtPos(state.selection.from - nbef);
-            // The box in which the tooltip is positioned, to use as base
-            const box = (document.getElementsByClassName("mainView-container") as any)[0].getBoundingClientRect();
-            // Find a center-ish x position from the selection endpoints (when
-            // crossing lines, end may be more to the left)
-            const left = Math.max((start.left + end.left) / 2, start.left + 3);
-            FormattedTextBoxComment.tooltip.style.left = (left - box.left) + "px";
-            FormattedTextBoxComment.tooltip.style.bottom = (box.bottom - start.top) + "px";
-        }
-        FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = set);
-    }
-
-    destroy() { }
-}
diff --git a/src/client/views/nodes/formattedText/DashDocCommentView.tsx b/src/client/views/nodes/formattedText/DashDocCommentView.tsx
new file mode 100644
index 000000000..d94fe7fc6
--- /dev/null
+++ b/src/client/views/nodes/formattedText/DashDocCommentView.tsx
@@ -0,0 +1,95 @@
+import { IReactionDisposer, observable, reaction, runInAction } from "mobx";
+import { baseKeymap, toggleMark } from "prosemirror-commands";
+import { redo, undo } from "prosemirror-history";
+import { keymap } from "prosemirror-keymap";
+import { DOMOutputSpecArray, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model";
+import { bulletList, listItem, orderedList } from 'prosemirror-schema-list';
+import { EditorState, NodeSelection, Plugin, TextSelection } from "prosemirror-state";
+import { StepMap } from "prosemirror-transform";
+import { EditorView } from "prosemirror-view";
+import * as ReactDOM from 'react-dom';
+import { Doc, DocListCast, Field, HeightSym, WidthSym } from "../../../../new_fields/Doc";
+import { Id } from "../../../../new_fields/FieldSymbols";
+import { List } from "../../../../new_fields/List";
+import { ObjectField } from "../../../../new_fields/ObjectField";
+import { listSpec } from "../../../../new_fields/Schema";
+import { SchemaHeaderField } from "../../../../new_fields/SchemaHeaderField";
+import { ComputedField } from "../../../../new_fields/ScriptField";
+import { BoolCast, Cast, NumCast, StrCast } from "../../../../new_fields/Types";
+import { emptyFunction, returnEmptyString, returnFalse, returnOne, Utils, returnZero } from "../../../../Utils";
+import { DocServer } from "../../../DocServer";
+
+import React = require("react");
+
+import { schema } from "./schema_rts";
+
+interface IDashDocCommentView {
+    node: any;
+    view: any;
+    getPos: any;
+}
+
+export class DashDocCommentView extends React.Component<IDashDocCommentView>{
+    constructor(props: IDashDocCommentView) {
+        super(props);
+    }
+
+    targetNode = () => {  // search forward in the prosemirror doc for the attached dashDocNode that is the target of the comment anchor
+        for (let i = this.props.getPos() + 1; i < this.props.view.state.doc.content.size; i++) {
+            const m = this.props.view.state.doc.nodeAt(i);
+            if (m && m.type === this.props.view.state.schema.nodes.dashDoc && m.attrs.docid === this.props.node.attrs.docid) {
+                return { node: m, pos: i, hidden: m.attrs.hidden } as { node: any, pos: number, hidden: boolean };
+            }
+        }
+        const dashDoc = this.props.view.state.schema.nodes.dashDoc.create({ width: 75, height: 35, title: "dashDoc", docid: this.props.node.attrs.docid, float: "right" });
+        this.props.view.dispatch(this.props.view.state.tr.insert(this.props.getPos() + 1, dashDoc));
+        setTimeout(() => { try { this.props.view.dispatch(this.props.view.state.tr.setSelection(TextSelection.create(this.props.view.state.tr.doc, this.props.getPos() + 2))); } catch (e) { } }, 0);
+        return undefined;
+    }
+
+    onPointerDownCollapse = (e: any) => e.stopPropagation();
+
+    onPointerUpCollapse = (e: any) => {
+        const target = this.targetNode();
+        if (target) {
+            const expand = target.hidden;
+            const tr = this.props.view.state.tr.setNodeMarkup(target.pos, undefined, { ...target.node.attrs, hidden: target.node.attrs.hidden ? false : true });
+            this.props.view.dispatch(tr.setSelection(TextSelection.create(tr.doc, this.props.getPos() + (expand ? 2 : 1)))); // update the attrs
+            setTimeout(() => {
+                expand && DocServer.GetRefField(this.props.node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc));
+                try { this.props.view.dispatch(this.props.view.state.tr.setSelection(TextSelection.create(this.props.view.state.tr.doc, this.props.getPos() + (expand ? 2 : 1)))); } catch (e) { }
+            }, 0);
+        }
+        e.stopPropagation();
+    }
+
+    onPointerEnterCollapse = (e: any) => {
+        DocServer.GetRefField(this.props.node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc, false));
+        e.preventDefault();
+        e.stopPropagation();
+    }
+
+    onPointerLeaveCollapse = (e: any) => {
+        DocServer.GetRefField(this.props.node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowUnhighlight());
+        e.preventDefault();
+        e.stopPropagation();
+    }
+
+    render() {
+
+        const collapsedId = "DashDocCommentView-" + this.props.node.attrs.docid;
+
+        return (
+            <span
+                className="formattedTextBox-inlineComment"
+                id={collapsedId}
+                onPointerDown={this.onPointerDownCollapse}
+                onPointerUp={this.onPointerUpCollapse}
+                onPointerEnter={this.onPointerEnterCollapse}
+                onPointerLeave={this.onPointerLeaveCollapse}
+            >
+
+            </span >
+        );
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx
new file mode 100644
index 000000000..9fe8fa320
--- /dev/null
+++ b/src/client/views/nodes/formattedText/DashDocView.tsx
@@ -0,0 +1,269 @@
+import { IReactionDisposer, reaction } from "mobx";
+import { NodeSelection } from "prosemirror-state";
+import { Doc, HeightSym, WidthSym } from "../../../../new_fields/Doc";
+import { Id } from "../../../../new_fields/FieldSymbols";
+import { ObjectField } from "../../../../new_fields/ObjectField";
+import { ComputedField } from "../../../../new_fields/ScriptField";
+import { BoolCast, Cast, NumCast, StrCast } from "../../../../new_fields/Types";
+import { emptyFunction, returnEmptyString, returnFalse, Utils, returnZero } from "../../../../Utils";
+import { DocServer } from "../../../DocServer";
+import { Docs } from "../../../documents/Documents";
+import { DocumentView } from "../DocumentView";
+import { FormattedTextBox } from "./FormattedTextBox";
+import { Transform } from "../../../util/Transform";
+import React = require("react");
+
+interface IDashDocView {
+    node: any;
+    view: any;
+    getPos: any;
+    tbox?: FormattedTextBox;
+    self: any;
+}
+
+export class DashDocView extends React.Component<IDashDocView> {
+
+    _dashDoc: Doc | undefined;
+    _reactionDisposer: IReactionDisposer | undefined;
+    _renderDisposer: IReactionDisposer | undefined;
+    _textBox: FormattedTextBox;
+    _finalLayout: any;
+    _resolvedDataDoc: any;
+
+
+    //    constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
+
+    constructor(props: IDashDocView) {
+        super(props);
+
+        const node = this.props.node;
+        this._textBox = this.props.tbox as FormattedTextBox;
+
+        const alias = node.attrs.alias;
+        const docid = node.attrs.docid || this._textBox.props.Document[Id];
+
+        DocServer.GetRefField(docid + alias).then(async dashDoc => {
+            if (!(dashDoc instanceof Doc)) {
+                alias && DocServer.GetRefField(docid).then(async dashDocBase => {
+                    if (dashDocBase instanceof Doc) {
+                        const aliasedDoc = Doc.MakeAlias(dashDocBase, docid + alias);
+                        aliasedDoc.layoutKey = "layout";
+                        node.attrs.fieldKey && DocumentView.makeCustomViewClicked(aliasedDoc, Docs.Create.StackingDocument, node.attrs.fieldKey, undefined);
+                        this._dashDoc = aliasedDoc;
+                        //                        self.doRender(aliasedDoc, removeDoc, node, view, getPos);
+                    }
+                });
+            } else {
+                this._dashDoc = dashDoc;
+                //                self.doRender(dashDoc, removeDoc, node, view, getPos);
+            }
+        });
+
+        this.onPointerLeave = this.onPointerLeave.bind(this);
+        this.onPointerEnter = this.onPointerEnter.bind(this);
+        this.onKeyDown = this.onKeyDown.bind(this);
+        this.onKeyPress = this.onKeyPress.bind(this);
+        this.onKeyUp = this.onKeyUp.bind(this);
+        this.onWheel = this.onWheel.bind(this);
+    }
+    /* #region Internal functions */
+
+    removeDoc = () => {
+        const view = this.props.view;
+        const pos = this.props.getPos();
+        const ns = new NodeSelection(view.state.doc.resolve(pos));
+        view.dispatch(view.state.tr.setSelection(ns).deleteSelection());
+        return true;
+    }
+
+    getDocTransform = () => {
+        const outerElement = document.getElementById('dash-document-view-outer') as HTMLElement;
+        const { scale, translateX, translateY } = Utils.GetScreenTransform(outerElement);
+        return new Transform(-translateX, -translateY, 1).scale(1 / this.contentScaling() / scale);
+    }
+    contentScaling = () => NumCast(this._dashDoc!._nativeWidth) > 0 ? this._dashDoc![WidthSym]() / NumCast(this._dashDoc!._nativeWidth) : 1;
+
+    outerFocus = (target: Doc) => this._textBox.props.focus(this._textBox.props.Document);  // ideally, this would scroll to show the focus target
+
+    onKeyPress = (e: any) => {
+        e.stopPropagation();
+    }
+    onWheel = (e: any) => {
+        e.preventDefault();
+    }
+    onKeyUp = (e: any) => {
+        e.stopPropagation();
+    }
+    onKeyDown = (e: any) => {
+        e.stopPropagation();
+        if (e.key === "Tab" || e.key === "Enter") {
+            e.preventDefault();
+        }
+    }
+    onPointerLeave = () => {
+        const ele = document.getElementById("DashDocCommentView-" + this.props.node.attrs.docid);
+        if (ele) {
+            (ele as HTMLDivElement).style.backgroundColor = "";
+        }
+    }
+    onPointerEnter = () => {
+        const ele = document.getElementById("DashDocCommentView-" + this.props.node.attrs.docid);
+        if (ele) {
+            (ele as HTMLDivElement).style.backgroundColor = "orange";
+        }
+    }
+    /*endregion*/
+
+    componentWillMount = () => {
+        this._reactionDisposer?.();
+    }
+
+    componentDidUpdate = () => {
+
+        this._renderDisposer?.();
+        this._renderDisposer = reaction(() => {
+
+            const dashDoc = this._dashDoc as Doc;
+            const dashLayoutDoc = Doc.Layout(dashDoc);
+            const finalLayout = this.props.node.attrs.docid ? dashDoc : Doc.expandTemplateLayout(dashLayoutDoc, dashDoc, this.props.node.attrs.fieldKey);
+
+            if (finalLayout) {
+                if (!Doc.AreProtosEqual(finalLayout, dashDoc)) {
+                    finalLayout.rootDocument = dashDoc.aliasOf;
+                }
+                const layoutKey = StrCast(finalLayout.layoutKey);
+                const finalKey = layoutKey && StrCast(finalLayout[layoutKey]).split("'")?.[1];
+                if (finalLayout !== dashDoc && finalKey) {
+                    const finalLayoutField = finalLayout[finalKey];
+                    if (finalLayoutField instanceof ObjectField) {
+                        finalLayout[finalKey + "-textTemplate"] = ComputedField.MakeFunction(`copyField(this.${finalKey})`, { this: Doc.name });
+                    }
+                }
+                this._finalLayout = finalLayout;
+                this._resolvedDataDoc = Cast(finalLayout.resolvedDataDoc, Doc, null);
+                return { finalLayout, resolvedDataDoc: Cast(finalLayout.resolvedDataDoc, Doc, null) };
+            }
+        },
+            (res) => {
+
+                if (res) {
+                    this._finalLayout = res.finalLayout;
+                    this._resolvedDataDoc = res.resolvedDataDoc;
+
+                    this.forceUpdate(); // doReactRender(res.finalLayout, res.resolvedDataDoc),
+                }
+            },
+            { fireImmediately: true });
+
+    }
+
+    render() {
+        // doRender(dashDoc: Doc, removeDoc: any, node: any, view: any, getPos: any) {
+
+        const node = this.props.node;
+        const view = this.props.view;
+        const getPos = this.props.getPos;
+
+        const spanStyle = {
+            width: this.props.node.props.width,
+            height: this.props.node.props.height,
+            position: 'absolute' as 'absolute',
+            display: 'inline-block'
+        };
+
+
+        const outerStyle = {
+            position: "relative" as "relative",
+            textIndent: "0",
+            border: "1px solid " + StrCast(this._textBox.Document.color, (Cast(Doc.UserDoc().activeWorkspace, Doc, null).darkScheme ? "dimGray" : "lightGray")),
+            width: this.props.node.props.width,
+            height: this.props.node.props.height,
+            display: this.props.node.props.hidden ? "none" : "inline-block",
+            float: this.props.node.props.float,
+        };
+
+        const dashDoc = this._dashDoc as Doc;
+        const self = this;
+        const dashLayoutDoc = Doc.Layout(dashDoc);
+        const finalLayout = node.attrs.docid ? dashDoc : Doc.expandTemplateLayout(dashLayoutDoc, dashDoc, node.attrs.fieldKey);
+        const resolvedDataDoc = this._resolvedDataDoc; //Added this
+
+        if (!finalLayout) {
+            return <div></div>;
+            // if (!finalLayout) setTimeout(() => self.doRender(dashDoc, removeDoc, node, view, getPos), 0);
+        } else {
+
+            this._reactionDisposer?.();
+            this._reactionDisposer = reaction(() =>
+                ({
+                    dim: [finalLayout[WidthSym](), finalLayout[HeightSym]()],
+                    color: finalLayout.color
+                }),
+                ({ dim, color }) => {
+                    spanStyle.width = outerStyle.width = Math.max(20, dim[0]) + "px";
+                    spanStyle.height = outerStyle.height = Math.max(20, dim[1]) + "px";
+                    outerStyle.border = "1px solid " + StrCast(finalLayout.color, (Cast(Doc.UserDoc().activeWorkspace, Doc, null).darkScheme ? "dimGray" : "lightGray"));
+                }, { fireImmediately: true });
+
+            if (node.attrs.width !== dashDoc._width + "px" || node.attrs.height !== dashDoc._height + "px") {
+                try { // bcz: an exception will be thrown if two aliases are open at the same time when a doc view comment is made
+                    view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, { ...node.attrs, width: dashDoc._width + "px", height: dashDoc._height + "px" }));
+                } catch (e) {
+                    console.log(e);
+                }
+            }
+
+
+            //const doReactRender = (finalLayout: Doc, resolvedDataDoc: Doc) => {
+            //    ReactDOM.unmountComponentAtNode(this._dashSpan);
+
+            return (
+                <span id="dash-document-view-outer"
+                    className="outer"
+                    style={outerStyle}
+                >
+                    <div id="dashSpan"
+                        className="dash-span"
+                        style={spanStyle}
+                        onPointerLeave={this.onPointerLeave}
+                        onPointerEnter={this.onPointerEnter}
+                        onKeyDown={this.onKeyDown}
+                        onKeyPress={this.onKeyPress}
+                        onKeyUp={this.onKeyUp}
+                        onWheel={this.onWheel}
+                    >
+                        <DocumentView
+                            Document={finalLayout}
+                            DataDoc={resolvedDataDoc}
+                            LibraryPath={this._textBox.props.LibraryPath}
+                            fitToBox={BoolCast(dashDoc._fitToBox)}
+                            addDocument={returnFalse}
+                            rootSelected={this._textBox.props.isSelected}
+                            removeDocument={this.removeDoc}
+                            ScreenToLocalTransform={this.getDocTransform}
+                            addDocTab={this._textBox.props.addDocTab}
+                            pinToPres={returnFalse}
+                            renderDepth={self._textBox.props.renderDepth + 1}
+                            NativeHeight={returnZero}
+                            NativeWidth={returnZero}
+                            PanelWidth={finalLayout[WidthSym]}
+                            PanelHeight={finalLayout[HeightSym]}
+                            focus={this.outerFocus}
+                            backgroundColor={returnEmptyString}
+                            parentActive={returnFalse}
+                            whenActiveChanged={returnFalse}
+                            bringToFront={emptyFunction}
+                            dontRegisterView={false}
+                            ContainingCollectionView={this._textBox.props.ContainingCollectionView}
+                            ContainingCollectionDoc={this._textBox.props.ContainingCollectionDoc}
+                            ContentScaling={this.contentScaling}
+                        />
+
+                    </div>
+                </span>
+            );
+
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/DashFieldView.scss b/src/client/views/nodes/formattedText/DashFieldView.scss
new file mode 100644
index 000000000..35ff9c1e6
--- /dev/null
+++ b/src/client/views/nodes/formattedText/DashFieldView.scss
@@ -0,0 +1,36 @@
+.dashFieldView {
+    position: relative;
+    display: inline-block;
+
+    .dashFieldView-enumerables {
+        width: 10px;
+        height: 10px;
+        position: relative;
+        display: inline-block;
+        background: dimGray;
+    }
+    .dashFieldView-fieldCheck {
+        min-width: 12px;
+        position: relative;
+        display: inline-block;
+        background-color: rgba(155, 155, 155, 0.24);
+    }
+    .dashFieldView-labelSpan {
+        position: relative;
+        display: inline-block;
+        font-size: small;
+    }
+    .dashFieldView-fieldSpan {
+        min-width: 20px;
+        margin-left: 2px;
+        margin-right: 5px;
+        position: relative;
+        display: inline-block;
+        background-color: rgba(155, 155, 155, 0.24);
+        span {
+            min-width: 100%;
+            display: inline-block;
+        }
+    }
+}
+        
\ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
new file mode 100644
index 000000000..82c3185e7
--- /dev/null
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -0,0 +1,211 @@
+import { IReactionDisposer, observable, runInAction, computed, action } from "mobx";
+import { Doc, DocListCast, Field } from "../../../../new_fields/Doc";
+import { List } from "../../../../new_fields/List";
+import { listSpec } from "../../../../new_fields/Schema";
+import { SchemaHeaderField } from "../../../../new_fields/SchemaHeaderField";
+import { ComputedField } from "../../../../new_fields/ScriptField";
+import { Cast, StrCast } from "../../../../new_fields/Types";
+import { DocServer } from "../../../DocServer";
+import { CollectionViewType } from "../../collections/CollectionView";
+import { FormattedTextBox } from "./FormattedTextBox";
+import React = require("react");
+import * as ReactDOM from 'react-dom';
+import "./DashFieldView.scss";
+import { observer } from "mobx-react";
+
+
+export class DashFieldView {
+    _fieldWrapper: HTMLDivElement; // container for label and value
+
+    constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
+        this._fieldWrapper = document.createElement("div");
+        this._fieldWrapper.style.width = node.attrs.width;
+        this._fieldWrapper.style.height = node.attrs.height;
+        this._fieldWrapper.style.fontWeight = "bold";
+        this._fieldWrapper.style.position = "relative";
+        this._fieldWrapper.style.display = "inline-block";
+        this._fieldWrapper.onkeypress = function (e: any) { e.stopPropagation(); };
+        this._fieldWrapper.onkeydown = function (e: any) { e.stopPropagation(); };
+        this._fieldWrapper.onkeyup = function (e: any) { e.stopPropagation(); };
+        this._fieldWrapper.onmousedown = function (e: any) { e.stopPropagation(); };
+
+        ReactDOM.render(<DashFieldViewInternal
+            fieldKey={node.attrs.fieldKey}
+            docid={node.attrs.docid}
+            width={node.attrs.width}
+            height={node.attrs.height}
+            view={view}
+            getPos={getPos}
+            tbox={tbox}
+        />, this._fieldWrapper);
+        (this as any).dom = this._fieldWrapper;
+    }
+    destroy() {
+        ReactDOM.unmountComponentAtNode(this._fieldWrapper);
+    }
+    selectNode() { }
+
+}
+interface IDashFieldViewInternal {
+    fieldKey: string;
+    docid: string;
+    view: any;
+    getPos: any;
+    tbox: FormattedTextBox;
+    width: number;
+    height: number;
+}
+
+@observer
+export class DashFieldViewInternal extends React.Component<IDashFieldViewInternal> {
+    _reactionDisposer: IReactionDisposer | undefined;
+    _textBoxDoc: Doc;
+    _fieldKey: string;
+    _fieldStringRef = React.createRef<HTMLSpanElement>();
+    @observable _showEnumerables: boolean = false;
+    @observable _dashDoc: Doc | undefined;
+
+    constructor(props: IDashFieldViewInternal) {
+        super(props);
+        this._fieldKey = this.props.fieldKey;
+        this._textBoxDoc = this.props.tbox.props.Document;
+
+        if (this.props.docid) {
+            DocServer.GetRefField(this.props.docid).
+                then(action(async dashDoc => dashDoc instanceof Doc && (this._dashDoc = dashDoc)));
+        } else {
+            this._dashDoc = this.props.tbox.props.DataDoc || this.props.tbox.dataDoc;
+        }
+    }
+    componentWillUnmount() {
+        this._reactionDisposer?.();
+    }
+
+    // set the display of the field's value (checkbox for booleans, span of text for strings)
+    @computed get fieldValueContent() {
+        if (this._dashDoc) {
+            const dashVal = this._dashDoc[this._fieldKey];
+            const fval = StrCast(dashVal).startsWith(":=") || dashVal === "" ? Doc.Layout(this._textBoxDoc)[this._fieldKey] : dashVal;
+            const boolVal = Cast(fval, "boolean", null);
+            const strVal = Field.toString(fval as Field) || "";
+
+            // field value is a boolean, so use a checkbox or similar widget to display it
+            if (boolVal === true || boolVal === false) {
+                return <input
+                    className="dashFieldView-fieldCheck"
+                    type="checkbox" checked={boolVal}
+                    onChange={e => this._dashDoc![this._fieldKey] = e.target.checked}
+                />;
+            }
+            else // field value is a string, so display it as an editable span
+            {
+                // bcz: this is unfortunate, but since this React component is nested within a non-React text box (prosemirror), we can't
+                // use React events.  Essentially, React events occur after native events have been processed, so corresponding React events
+                // will never fire because Prosemirror has handled the native events.  So we add listeners for native events here.
+                return <span contentEditable={true} ref={r => {
+                    r?.addEventListener("keydown", e => this.fieldSpanKeyDown(e, r));
+                    r?.addEventListener("blur", e => r && this.updateText(r.textContent!, false));
+                    r?.addEventListener("pointerdown", action((e) => this._showEnumerables = true));
+                }}>
+                    {strVal}
+                </span>;
+            }
+        }
+    }
+
+    // we need to handle all key events on the input span or else they will propagate to prosemirror.
+    @action
+    fieldSpanKeyDown = (e: KeyboardEvent, span: HTMLSpanElement) => {
+        if (e.key === "Enter") {  // handle the enter key by "submitting" the current text to Dash's database. 
+            e.ctrlKey && Doc.addFieldEnumerations(this._textBoxDoc, this._fieldKey, [{ title: span.textContent! }]);
+            this.updateText(span.textContent!, true);
+            e.preventDefault();// prevent default to avoid a newline from being generated and wiping out this field view
+        }
+        if (e.key === "a" && (e.ctrlKey || e.metaKey)) { // handle ctrl-A to select all the text within the span
+            if (window.getSelection) {
+                const range = document.createRange();
+                range.selectNodeContents(span);
+                window.getSelection()!.removeAllRanges();
+                window.getSelection()!.addRange(range);
+            }
+            e.preventDefault(); //prevent default so that all the text in the prosemirror text box isn't selected
+        }
+        e.stopPropagation();  // we need to handle all events or else they will propagate to prosemirror.
+    }
+
+    @action
+    updateText = (nodeText: string, forceMatch: boolean) => {
+        this._showEnumerables = false;
+        if (nodeText) {
+            const newText = nodeText.startsWith(":=") || nodeText.startsWith("=:=") ? ":=-computed-" : nodeText;
+
+            // look for a document whose id === the fieldKey being displayed.  If there's a match, then that document
+            // holds the different enumerated values for the field in the titles of its collected documents.
+            // if there's a partial match from the start of the input text, complete the text --- TODO: make this an auto suggest box and select from a drop down.
+            DocServer.GetRefField(this._fieldKey).then(options => {
+                let modText = "";
+                (options instanceof Doc) && DocListCast(options.data).forEach(opt => (forceMatch ? StrCast(opt.title).startsWith(newText) : StrCast(opt.title) === newText) && (modText = StrCast(opt.title)));
+                if (modText) {
+                    //  elementfieldSpan.innerHTML = this._dashDoc![this._fieldKey as string] = modText;
+                    Doc.addFieldEnumerations(this._textBoxDoc, this._fieldKey, []);
+                    this._dashDoc![this._fieldKey] = modText;
+                } // if the text starts with a ':=' then treat it as an expression by making a computed field from its value storing it in the key
+                else if (nodeText.startsWith(":=")) {
+                    this._dashDoc![this._fieldKey] = ComputedField.MakeFunction(nodeText.substring(2));
+                } else if (nodeText.startsWith("=:=")) {
+                    Doc.Layout(this._textBoxDoc)[this._fieldKey] = ComputedField.MakeFunction(nodeText.substring(3));
+                } else {
+                    this._dashDoc![this._fieldKey] = newText;
+                }
+            });
+        }
+    }
+
+    // display a collection of all the enumerable values for this field
+    onPointerDownEnumerables = async (e: any) => {
+        e.stopPropagation();
+        const collview = await Doc.addFieldEnumerations(this._textBoxDoc, this._fieldKey, [{ title: this._fieldKey }]);
+        collview instanceof Doc && this.props.tbox.props.addDocTab(collview, "onRight");
+    }
+
+
+    // clicking on the label creates a pivot view collection of all documents
+    // in the same collection.  The pivot field is the fieldKey of this label
+    onPointerDownLabelSpan = (e: any) => {
+        e.stopPropagation();
+        let container = this.props.tbox.props.ContainingCollectionView;
+        while (container?.props.Document.isTemplateForField || container?.props.Document.isTemplateDoc) {
+            container = container.props.ContainingCollectionView;
+        }
+        if (container) {
+            const alias = Doc.MakeAlias(container.props.Document);
+            alias.viewType = CollectionViewType.Time;
+            let list = Cast(alias.schemaColumns, listSpec(SchemaHeaderField));
+            if (!list) {
+                alias.schemaColumns = list = new List<SchemaHeaderField>();
+            }
+            list.map(c => c.heading).indexOf(this._fieldKey) === -1 && list.push(new SchemaHeaderField(this._fieldKey, "#f1efeb"));
+            list.map(c => c.heading).indexOf("text") === -1 && list.push(new SchemaHeaderField("text", "#f1efeb"));
+            alias._pivotField = this._fieldKey;
+            this.props.tbox.props.addDocTab(alias, "onRight");
+        }
+    }
+
+    render() {
+        return <div className="dashFieldView" style={{
+            width: this.props.width,
+            height: this.props.height,
+        }}>
+            <span className="dashFieldView-labelSpan" title="click to see related tags" onPointerDown={this.onPointerDownLabelSpan}>
+                {this._fieldKey}
+            </span>
+
+            <div className="dashFieldView-fieldSpan">
+                {this.fieldValueContent}
+            </div>
+
+            {!this._showEnumerables ? (null) : <div className="dashFieldView-enumerables" onPointerDown={this.onPointerDownEnumerables} />}
+
+        </div >;
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/FootnoteView.tsx b/src/client/views/nodes/formattedText/FootnoteView.tsx
new file mode 100644
index 000000000..ee21fb765
--- /dev/null
+++ b/src/client/views/nodes/formattedText/FootnoteView.tsx
@@ -0,0 +1,162 @@
+import { EditorView } from "prosemirror-view";
+import { EditorState } from "prosemirror-state";
+import { keymap } from "prosemirror-keymap";
+import { baseKeymap, toggleMark } from "prosemirror-commands";
+import { schema } from "./schema_rts";
+import { redo, undo } from "prosemirror-history";
+import { StepMap } from "prosemirror-transform";
+
+import React = require("react");
+
+interface IFootnoteView {
+    innerView: any;
+    outerView: any;
+    node: any;
+    dom: any;
+    getPos: any;
+}
+
+export class FootnoteView extends React.Component<IFootnoteView>  {
+    _innerView: any;
+    _node: any;
+
+    constructor(props: IFootnoteView) {
+        super(props);
+        const node = this.props.node;
+        const outerView = this.props.outerView;
+        const _innerView = this.props.innerView;
+        const getPos = this.props.getPos;
+    }
+
+    selectNode() {
+        const attrs = { ...this.props.node.attrs };
+        attrs.visibility = true;
+        this.dom.classList.add("ProseMirror-selectednode");
+        if (!this.props.innerView) this.open();
+    }
+
+    deselectNode() {
+        const attrs = { ...this.props.node.attrs };
+        attrs.visibility = false;
+        this.dom.classList.remove("ProseMirror-selectednode");
+        if (this.props.innerView) this.close();
+    }
+    open() {
+        // Append a tooltip to the outer node
+        const tooltip = this.dom.appendChild(document.createElement("div"));
+        tooltip.className = "footnote-tooltip";
+        // And put a sub-ProseMirror into that
+        this.props.innerView.defineProperty(new EditorView(tooltip, {
+            // You can use any node as an editor document
+            state: EditorState.create({
+                doc: this.props.node,
+                plugins: [keymap(baseKeymap),
+                keymap({
+                    "Mod-z": () => undo(this.props.outerView.state, this.props.outerView.dispatch),
+                    "Mod-y": () => redo(this.props.outerView.state, this.props.outerView.dispatch),
+                    "Mod-b": toggleMark(schema.marks.strong)
+                }),
+                    // new Plugin({
+                    //     view(newView) {
+                    //         // TODO -- make this work with RichTextMenu
+                    //         // return FormattedTextBox.getToolTip(newView);
+                    //     }
+                    // })
+                ],
+
+            }),
+            // This is the magic part
+            dispatchTransaction: this.dispatchInner.bind(this),
+            handleDOMEvents: {
+                pointerdown: ((view: any, e: PointerEvent) => {
+                    // Kludge to prevent issues due to the fact that the whole
+                    // footnote is node-selected (and thus DOM-selected) when
+                    // the parent editor is focused.
+                    e.stopPropagation();
+                    document.addEventListener("pointerup", this.ignore, true);
+                    if (this.props.outerView.hasFocus()) this.props.innerView.focus();
+                }) as any
+            }
+        }));
+        setTimeout(() => this.props.innerView && this.props.innerView.docView.setSelection(0, 0, this.props.innerView.root, true), 0);
+    }
+
+    ignore = (e: PointerEvent) => {
+        e.stopPropagation();
+        document.removeEventListener("pointerup", this.ignore, true);
+    }
+
+    dispatchInner(tr: any) {
+        const { state, transactions } = this.props.innerView.state.applyTransaction(tr);
+        this.props.innerView.updateState(state);
+
+        if (!tr.getMeta("fromOutside")) {
+            const outerTr = this.props.outerView.state.tr, offsetMap = StepMap.offset(this.props.getPos() + 1);
+            for (const transaction of transactions) {
+                const steps = transaction.steps;
+                for (const step of steps) {
+                    outerTr.step(step.map(offsetMap));
+                }
+            }
+            if (outerTr.docChanged) this.props.outerView.dispatch(outerTr);
+        }
+    }
+    update(node: any) {
+        if (!node.sameMarkup(this.props.node)) return false;
+        this._node = node; //not sure
+        if (this.props.innerView) {
+            const state = this.props.innerView.state;
+            const start = node.content.findDiffStart(state.doc.content);
+            if (start !== null) {
+                let { a: endA, b: endB } = node.content.findDiffEnd(state.doc.content);
+                const overlap = start - Math.min(endA, endB);
+                if (overlap > 0) { endA += overlap; endB += overlap; }
+                this.props.innerView.dispatch(
+                    state.tr
+                        .replace(start, endB, node.slice(start, endA))
+                        .setMeta("fromOutside", true));
+            }
+        }
+        return true;
+    }
+    onPointerUp = (e: any) => {
+        this.toggle(e);
+    }
+
+    toggle = (e: any) => {
+        e.preventDefault();
+        if (this.props.innerView) this.close();
+        else {
+            this.open();
+        }
+    }
+
+    close() {
+        this.props.innerView && this.props.innerView.destroy();
+        this._innerView = null;
+        this.dom.textContent = "";
+    }
+
+    destroy() {
+        if (this.props.innerView) this.close();
+    }
+
+    stopEvent(event: any) {
+        return this.props.innerView && this.props.innerView.dom.contains(event.target);
+    }
+
+    ignoreMutation() { return true; }
+
+
+    render() {
+        return (
+            <div
+                className="footnote"
+                onPointerUp={this.onPointerUp}>
+                <div className="footnote-tooltip" >
+
+                </div >
+            </div>
+        );
+    }
+}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
new file mode 100644
index 000000000..477a2ca08
--- /dev/null
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -0,0 +1,265 @@
+@import "../../globalCssVariables";
+
+.ProseMirror {
+    width: 100%;
+    height: 100%;
+    min-height: 100%;
+}
+
+.ProseMirror:focus {
+    outline: none !important;
+}
+
+.formattedTextBox-cont {
+    touch-action: none;
+    cursor: text;
+    background: inherit;
+    padding: 0;
+    border-width: 0px;
+    border-radius: inherit;
+    border-color: $intermediate-color;
+    box-sizing: border-box;
+    background-color: inherit;
+    border-style: solid;
+    overflow-y: auto;
+    overflow-x: hidden;
+    color: initial;
+    max-height: 100%;
+    display: flex;
+    flex-direction: row;
+    transition: opacity 1s;
+
+    .formattedTextBox-dictation {
+        height: 12px;
+        width: 10px;
+        top: 0px;
+        left: 0px;
+        position: absolute;
+    }
+}
+.formattedTextBox-outer {
+    position: relative;
+    overflow: auto;
+    display: inline-block;
+    width: 100%;
+    height: 100%;
+}
+
+.formattedTextBox-sidebar-handle {
+    position: absolute;
+    top: calc(50% - 17.5px);
+    width: 10px;
+    height: 35px;
+    background: lightgray;
+    border-radius: 20px;
+    cursor:grabbing;
+}
+
+.formattedTextBox-cont>.formattedTextBox-sidebar-handle {
+    right: 0;
+    left: unset;
+}
+
+.formattedTextBox-sidebar,
+.formattedTextBox-sidebar-inking {
+    border-left: dashed 1px black;
+    height: 100%;
+    display: inline-block;
+    position: absolute;
+    right: 0;
+
+    .collectionfreeformview-container {
+        position: relative;
+    }
+    
+    >.formattedTextBox-sidebar-handle {
+        right: unset;
+        left: -5;
+    }
+}
+
+.formattedTextBox-sidebar-inking {
+    pointer-events: all;
+}
+
+.formattedTextBox-inner-rounded {
+    height: 70%;
+    width: 85%;
+    position: absolute;
+    overflow: auto;
+    top: 15%;
+    left: 10%;
+}
+
+.formattedTextBox-inner-rounded,
+.formattedTextBox-inner {
+    height: 100%;
+    white-space: pre-wrap; 
+}
+
+// .menuicon {
+//     display: inline-block;
+//     border-right: 1px solid rgba(0, 0, 0, 0.2);
+//     color: #888;
+//     line-height: 1;
+//     padding: 0 7px;
+//     margin: 1px;
+//     cursor: pointer;
+//     text-align: center;
+//     min-width: 1.4em;
+// }
+
+.strong,
+.heading {
+    font-weight: bold;
+}
+
+.em {
+    font-style: italic;
+}
+
+.userMarkOpen {
+    background: rgba(255, 255, 0, 0.267);
+    display: inline;
+}
+
+.userMark {
+    background: rgba(255, 255, 0, 0.267);
+    font-size: 2px;
+    display: inline-grid;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    width: 10px;
+    min-height: 10px;
+    text-align: center;
+    align-content: center;
+}
+
+footnote {
+    display: inline-block;
+    position: relative;
+    cursor: pointer;
+
+    div {
+        padding: 0 !important;
+    }
+}
+
+footnote::after {
+    content: counter(prosemirror-footnote);
+    vertical-align: super;
+    font-size: 75%;
+    counter-increment: prosemirror-footnote;
+}
+
+.ProseMirror {
+    counter-reset: prosemirror-footnote;
+}
+
+.footnote-tooltip {
+    cursor: auto;
+    font-size: 75%;
+    position: absolute;
+    left: -30px;
+    top: calc(100% + 10px);
+    background: silver;
+    padding: 3px;
+    border-radius: 2px;
+    max-width: 100px;
+    min-width: 50px;
+    width: max-content;
+}
+
+.prosemirror-attribution {
+    font-size: 8px;
+}
+
+.footnote-tooltip::before {
+    border: 5px solid silver;
+    border-top-width: 0px;
+    border-left-color: transparent;
+    border-right-color: transparent;
+    position: absolute;
+    top: -5px;
+    left: 27px;
+    content: " ";
+    height: 0;
+    width: 0;
+}
+
+
+.formattedTextBox-inlineComment {
+    position: relative;
+    width: 40px;
+    height: 20px;
+    &::before {
+        content: "→";
+    }
+    &:hover {
+        background: orange;
+    }
+}
+
+.formattedTextBox-summarizer {
+    opacity: 0.5;
+    position: relative;
+    width: 40px;
+    height: 20px;
+    &::after {
+        content: "←";
+    }
+}
+
+.formattedTextBox-summarizer-collapsed {
+    opacity: 0.5;
+    position: relative;
+    width: 40px;
+    height: 20px;
+    &::after {
+        content: "...";
+    }
+}
+
+.ProseMirror {
+    touch-action: none;
+    span {
+        font-family: inherit;
+    }
+
+    ol, ul {
+        counter-reset: deci1 0 multi1 0;
+        padding-left: 1em;
+        font-family: inherit;
+    }
+    ol {
+        margin-left: 1em;
+        font-family: inherit;
+    }
+
+    .decimal1-ol { counter-reset: deci1; p {display: inline; font-family: inherit} margin-left: 0; }
+    .decimal2-ol { counter-reset: deci2; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 1em;}
+    .decimal3-ol { counter-reset: deci3; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 2em;}
+    .decimal4-ol { counter-reset: deci4; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 3em;}
+    .decimal5-ol { counter-reset: deci5; p {display: inline; font-family: inherit} font-size: smaller; }
+    .decimal6-ol { counter-reset: deci6; p {display: inline; font-family: inherit} font-size: smaller; }
+    .decimal7-ol { counter-reset: deci7; p {display: inline; font-family: inherit} font-size: smaller; }
+
+    .multi1-ol { counter-reset: multi1; p {display: inline; font-family: inherit} margin-left: 0;     padding-left: 1.2em }
+    .multi2-ol { counter-reset: multi2; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 1.4em;}
+    .multi3-ol { counter-reset: multi3; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 2em;}
+    .multi4-ol { counter-reset: multi4; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 3.4em;}
+
+    .decimal1:before { transition: 0.5s;counter-increment: deci1; display: inline-block; margin-left: -1em;   width:  1em;   content: counter(deci1) ". ";  }
+    .decimal2:before { transition: 0.5s;counter-increment: deci2; display: inline-block; margin-left: -2.1em; width: 2.1em;  content: counter(deci1) "."counter(deci2) ". ";  }
+    .decimal3:before { transition: 0.5s;counter-increment: deci3; display: inline-block; margin-left: -2.85em;width: 2.85em; content: counter(deci1) "."counter(deci2) "."counter(deci3) ". "; }
+    .decimal4:before { transition: 0.5s;counter-increment: deci4; display: inline-block; margin-left: -3.85em;width: 3.85em; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) ". "; }
+    .decimal5:before { transition: 0.5s;counter-increment: deci5; display: inline-block; margin-left: -2em;   width:  5em;   content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) ". "; }
+    .decimal6:before { transition: 0.5s;counter-increment: deci6; display: inline-block; margin-left: -2em;   width:  6em;   content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) "."counter(deci6) ". "; }
+    .decimal7:before { transition: 0.5s;counter-increment: deci7; display: inline-block; margin-left: -2em;   width:  7em;   content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) "."counter(deci6) "."counter(deci7) ". "; }
+    
+    .multi1:before { transition: 0.5s;counter-increment: multi1; display: inline-block; margin-left: -1em;    width: 1.2em;  content: counter(multi1, upper-alpha) ". ";  }
+    .multi2:before { transition: 0.5s;counter-increment: multi2; display: inline-block; margin-left: -2em;    width:   2em;  content: counter(multi1, upper-alpha) "."counter(multi2, decimal) ". "; }
+    .multi3:before { transition: 0.5s;counter-increment: multi3; display: inline-block; margin-left: -2.85em; width:2.85em;  content: counter(multi1, upper-alpha) "."counter(multi2, decimal) "."counter(multi3, lower-alpha) ". "; }
+    .multi4:before { transition: 0.5s;counter-increment: multi4; display: inline-block; margin-left:   -4.2em;  width: 4.2em;    content: counter(multi1, upper-alpha) "."counter(multi2, decimal) "."counter(multi3, lower-alpha) "."counter(multi4, lower-roman) ". "; }
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
new file mode 100644
index 000000000..248b4f467
--- /dev/null
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -0,0 +1,1303 @@
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { faEdit, faSmile, faTextHeight, faUpload } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { isEqual } from "lodash";
+import { action, computed, IReactionDisposer, Lambda, observable, reaction, runInAction } from "mobx";
+import { observer } from "mobx-react";
+import { baseKeymap } from "prosemirror-commands";
+import { history } from "prosemirror-history";
+import { inputRules } from 'prosemirror-inputrules';
+import { keymap } from "prosemirror-keymap";
+import { Fragment, Mark, Node, Slice } from "prosemirror-model";
+import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from "prosemirror-state";
+import { ReplaceStep } from 'prosemirror-transform';
+import { EditorView } from "prosemirror-view";
+import { DateField } from '../../../../new_fields/DateField';
+import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym } from "../../../../new_fields/Doc";
+import { documentSchema } from '../../../../new_fields/documentSchemas';
+import { Id } from '../../../../new_fields/FieldSymbols';
+import { InkTool } from '../../../../new_fields/InkField';
+import { PrefetchProxy } from '../../../../new_fields/Proxy';
+import { RichTextField } from "../../../../new_fields/RichTextField";
+import { RichTextUtils } from '../../../../new_fields/RichTextUtils';
+import { createSchema, makeInterface } from "../../../../new_fields/Schema";
+import { Cast, DateCast, NumCast, StrCast } from "../../../../new_fields/Types";
+import { TraceMobx } from '../../../../new_fields/util';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnOne, returnZero, Utils } from '../../../../Utils';
+import { GoogleApiClientUtils, Pulls, Pushes } from '../../../apis/google_docs/GoogleApiClientUtils';
+import { DocServer } from "../../../DocServer";
+import { Docs, DocUtils } from '../../../documents/Documents';
+import { DocumentType } from '../../../documents/DocumentTypes';
+import { DictationManager } from '../../../util/DictationManager';
+import { DragManager } from "../../../util/DragManager";
+import { makeTemplate } from '../../../util/DropConverter';
+import buildKeymap from "./ProsemirrorExampleTransfer";
+import RichTextMenu from './RichTextMenu';
+import { RichTextRules } from "./RichTextRules";
+import { DashDocCommentView, DashDocView, FootnoteView, ImageResizeView, OrderedListView, SummaryView } from "./RichTextSchema";
+// import { DashDocCommentView, DashDocView, DashFieldView, FootnoteView, SummaryView } from "./RichTextSchema";
+// import { OrderedListView } from "./RichTextSchema";
+// import { ImageResizeView } from "./ImageResizeView";
+// import { DashDocCommentView } from "./DashDocCommentView";
+// import { FootnoteView } from "./FootnoteView";
+// import { SummaryView } from "./SummaryView";
+// import { DashDocView } from "./DashDocView";
+import { DashFieldView } from "./DashFieldView";
+
+import { schema } from "./schema_rts";
+import { SelectionManager } from "../../../util/SelectionManager";
+import { undoBatch, UndoManager } from "../../../util/UndoManager";
+import { CollectionFreeFormView } from '../../collections/collectionFreeForm/CollectionFreeFormView';
+import { ContextMenu } from '../../ContextMenu';
+import { ContextMenuProps } from '../../ContextMenuItem';
+import { ViewBoxAnnotatableComponent } from "../../DocComponent";
+import { DocumentButtonBar } from '../../DocumentButtonBar';
+import { InkingControl } from "../../InkingControl";
+import { AudioBox } from '../AudioBox';
+import { FieldView, FieldViewProps } from "../FieldView";
+import "./FormattedTextBox.scss";
+import { FormattedTextBoxComment, formattedTextBoxCommentPlugin } from './FormattedTextBoxComment';
+import React = require("react");
+
+library.add(faEdit);
+library.add(faSmile, faTextHeight, faUpload);
+
+export interface FormattedTextBoxProps {
+    hideOnLeave?: boolean;
+    makeLink?: () => Opt<Doc>;
+    xMargin?: number;
+    yMargin?: number;
+}
+
+const richTextSchema = createSchema({
+    documentText: "string"
+});
+
+export const GoogleRef = "googleDocId";
+
+type RichTextDocument = makeInterface<[typeof richTextSchema, typeof documentSchema]>;
+const RichTextDocument = makeInterface(richTextSchema, documentSchema);
+
+type PullHandler = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => void;
+
+@observer
+export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProps & FormattedTextBoxProps), RichTextDocument>(RichTextDocument) {
+    public static LayoutString(fieldStr: string) { return FieldView.LayoutString(FormattedTextBox, fieldStr); }
+    public static blankState = () => EditorState.create(FormattedTextBox.Instance.config);
+    public static Instance: FormattedTextBox;
+    public ProseRef?: HTMLDivElement;
+    private _ref: React.RefObject<HTMLDivElement> = React.createRef();
+    private _scrollRef: React.RefObject<HTMLDivElement> = React.createRef();
+    private _editorView: Opt<EditorView>;
+    private _applyingChange: boolean = false;
+    private _searchIndex = 0;
+    private _sidebarMovement = 0;
+    private _lastX = 0;
+    private _lastY = 0;
+    private _undoTyping?: UndoManager.Batch;
+    private _disposers: { [name: string]: IReactionDisposer } = {};
+    private dropDisposer?: DragManager.DragDropDisposer;
+
+    @computed get _recording() { return this.dataDoc.audioState === "recording"; }
+    set _recording(value) { this.dataDoc.audioState = value ? "recording" : undefined; }
+
+    @observable private _entered = false;
+
+    public static FocusedBox: FormattedTextBox | undefined;
+    public static SelectOnLoad = "";
+    public static SelectOnLoadChar = "";
+    public static IsFragment(html: string) {
+        return html.indexOf("data-pm-slice") !== -1;
+    }
+    public static GetHref(html: string): string {
+        const parser = new DOMParser();
+        const parsedHtml = parser.parseFromString(html, 'text/html');
+        if (parsedHtml.body.childNodes.length === 1 && parsedHtml.body.childNodes[0].childNodes.length === 1 &&
+            (parsedHtml.body.childNodes[0].childNodes[0] as any).href) {
+            return (parsedHtml.body.childNodes[0].childNodes[0] as any).href;
+        }
+        return "";
+    }
+    public static GetDocFromUrl(url: string) {
+        if (url.startsWith(document.location.origin)) {
+            const split = new URL(url).pathname.split("doc/");
+            const docid = split[split.length - 1];
+            return docid;
+        }
+        return "";
+    }
+
+    @undoBatch
+    public setFontColor(color: string) {
+        const view = this._editorView!;
+        if (view.state.selection.from === view.state.selection.to) return false;
+        if (view.state.selection.to - view.state.selection.from > view.state.doc.nodeSize - 3) {
+            this.layoutDoc.color = color;
+        }
+        const colorMark = view.state.schema.mark(view.state.schema.marks.pFontColor, { color: color });
+        view.dispatch(view.state.tr.addMark(view.state.selection.from, view.state.selection.to, colorMark));
+        return true;
+    }
+
+    constructor(props: any) {
+        super(props);
+        FormattedTextBox.Instance = this;
+        this.updateHighlights();
+    }
+
+    public get CurrentDiv(): HTMLDivElement { return this._ref.current!; }
+
+    linkOnDeselect: Map<string, string> = new Map();
+
+    doLinkOnDeselect() {
+        Array.from(this.linkOnDeselect.entries()).map(entry => {
+            const key = entry[0];
+            const value = entry[1];
+            const id = Utils.GenerateDeterministicGuid(this.dataDoc[Id] + key);
+            DocServer.GetRefField(value).then(doc => {
+                DocServer.GetRefField(id).then(linkDoc => {
+                    this.dataDoc[key] = doc || Docs.Create.FreeformDocument([], { title: value, _width: 500, _height: 500 }, value);
+                    DocUtils.Publish(this.dataDoc[key] as Doc, value, this.props.addDocument, this.props.removeDocument);
+                    if (linkDoc) { (linkDoc as Doc).anchor2 = this.dataDoc[key] as Doc; }
+                    else DocUtils.MakeLink({ doc: this.props.Document }, { doc: this.dataDoc[key] as Doc }, "link to named target", id);
+                });
+            });
+        });
+        this.linkOnDeselect.clear();
+    }
+
+    dispatchTransaction = (tx: Transaction) => {
+        if (this._editorView) {
+            const metadata = tx.selection.$from.marks().find((m: Mark) => m.type === schema.marks.metadata);
+            if (metadata) {
+                const range = tx.selection.$from.blockRange(tx.selection.$to);
+                let text = range ? tx.doc.textBetween(range.start, range.end) : "";
+                let textEndSelection = tx.selection.to;
+                for (; textEndSelection < range!.end && text[textEndSelection - range!.start] !== " "; textEndSelection++) { }
+                text = text.substr(0, textEndSelection - range!.start);
+                text = text.split(" ")[text.split(" ").length - 1];
+                const split = text.split("::");
+                if (split.length > 1 && split[1]) {
+                    const key = split[0];
+                    const value = split[split.length - 1];
+                    this.linkOnDeselect.set(key, value);
+
+                    const id = Utils.GenerateDeterministicGuid(this.dataDoc[Id] + key);
+                    const link = this._editorView.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + id), location: "onRight", title: value });
+                    const mval = this._editorView.state.schema.marks.metadataVal.create();
+                    const offset = (tx.selection.to === range!.end - 1 ? -1 : 0);
+                    tx = tx.addMark(textEndSelection - value.length + offset, textEndSelection, link).addMark(textEndSelection - value.length + offset, textEndSelection, mval);
+                    this.dataDoc[key] = value;
+                }
+            }
+            const state = this._editorView.state.apply(tx);
+            this._editorView.updateState(state);
+            (tx.storedMarks && !this._editorView.state.storedMarks) && (this._editorView.state.storedMarks = tx.storedMarks);
+
+            const tsel = this._editorView.state.selection.$from;
+            tsel.marks().filter(m => m.type === this._editorView!.state.schema.marks.user_mark).map(m => AudioBox.SetScrubTime(Math.max(0, m.attrs.modified * 1000)));
+            const curText = state.doc.textBetween(0, state.doc.content.size, " \n");
+            const curTemp = Cast(this.props.Document[this.props.fieldKey + "-textTemplate"], RichTextField);
+            if (!this._applyingChange) {
+                this._applyingChange = true;
+                this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()));
+                if (!curTemp || curText) { // if no template, or there's text, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended)
+                    this.dataDoc[this.props.fieldKey] = new RichTextField(JSON.stringify(state.toJSON()), curText);
+                    this.dataDoc[this.props.fieldKey + "-noTemplate"] = (curTemp?.Text || "") !== curText; // mark the data field as being split from the template if it has been edited
+                } else { // if we've deleted all the text in a note driven by a template, then restore the template data
+                    this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse(curTemp.Data)));
+                    this.dataDoc[this.props.fieldKey + "-noTemplate"] = undefined; // mark the data field as not being split from any template it might have
+                }
+                this._applyingChange = false;
+            }
+            this.updateTitle();
+            this.tryUpdateHeight();
+        }
+    }
+
+    updateTitle = () => {
+        if ((this.props.Document.isTemplateForField === "text" || !this.props.Document.isTemplateForField) && // only update the title if the data document's data field is changing
+            StrCast(this.dataDoc.title).startsWith("-") && this._editorView && !this.rootDoc.customTitle) {
+            const str = this._editorView.state.doc.textContent;
+            const titlestr = str.substr(0, Math.min(40, str.length));
+            this.dataDoc.title = "-" + titlestr + (str.length > 40 ? "..." : "");
+        }
+    }
+
+    // needs a better API for taking in a set of words with target documents instead of just one target
+    public hyperlinkTerms = (terms: string[], target: Doc) => {
+        if (this._editorView && (this._editorView as any).docView && terms.some(t => t)) {
+            const res = terms.filter(t => t).map(term => this.findInNode(this._editorView!, this._editorView!.state.doc, term));
+            const tr = this._editorView.state.tr;
+            const flattened: TextSelection[] = [];
+            res.map(r => r.map(h => flattened.push(h)));
+            const lastSel = Math.min(flattened.length - 1, this._searchIndex);
+            this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex;
+            const alink = DocUtils.MakeLink({ doc: this.props.Document }, { doc: target }, "automatic")!;
+            const link = this._editorView.state.schema.marks.link.create({
+                href: Utils.prepend("/doc/" + alink[Id]),
+                title: "a link", location: location, linkId: alink[Id], targetId: target[Id]
+            });
+            this._editorView.dispatch(tr.addMark(flattened[lastSel].from, flattened[lastSel].to, link));
+        }
+    }
+    public highlightSearchTerms = (terms: string[]) => {
+        if (this._editorView && (this._editorView as any).docView && terms.some(t => t)) {
+            const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight);
+            const activeMark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight, { selected: true });
+            const res = terms.filter(t => t).map(term => this.findInNode(this._editorView!, this._editorView!.state.doc, term));
+            let tr = this._editorView.state.tr;
+            const flattened: TextSelection[] = [];
+            res.map(r => r.map(h => flattened.push(h)));
+            const lastSel = Math.min(flattened.length - 1, this._searchIndex);
+            flattened.forEach((h: TextSelection, ind: number) => tr = tr.addMark(h.from, h.to, ind === lastSel ? activeMark : mark));
+            this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex;
+            this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(flattened[lastSel].from), tr.doc.resolve(flattened[lastSel].to))).scrollIntoView());
+        }
+    }
+
+    public unhighlightSearchTerms = () => {
+        if (this._editorView && (this._editorView as any).docView) {
+            const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight);
+            const activeMark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight, { selected: true });
+            const end = this._editorView.state.doc.nodeSize - 2;
+            this._editorView.dispatch(this._editorView.state.tr.removeMark(0, end, mark).removeMark(0, end, activeMark));
+        }
+    }
+    adoptAnnotation = (start: number, end: number, mark: Mark) => {
+        const view = this._editorView!;
+        const nmark = view.state.schema.marks.user_mark.create({ ...mark.attrs, userid: Doc.CurrentUserEmail });
+        view.dispatch(view.state.tr.removeMark(start, end, nmark).addMark(start, end, nmark));
+    }
+    protected createDropTarget = (ele: HTMLDivElement) => {
+        this.ProseRef = ele;
+        this.dropDisposer?.();
+        ele && (this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.props.Document));
+    }
+
+    @undoBatch
+    @action
+    drop = async (e: Event, de: DragManager.DropEvent) => {
+        if (de.complete.docDragData) {
+            const draggedDoc = de.complete.docDragData.draggedDocuments.length && de.complete.docDragData.draggedDocuments[0];
+            // replace text contents whend dragging with Alt
+            if (draggedDoc && draggedDoc.type === DocumentType.RTF && !Doc.AreProtosEqual(draggedDoc, this.props.Document) && de.altKey) {
+                if (draggedDoc.data instanceof RichTextField) {
+                    Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new RichTextField(draggedDoc.data.Data, draggedDoc.data.Text);
+                    e.stopPropagation();
+                }
+                // embed document when dragging with a userDropAction or an embedDoc flag set
+            } else if (de.complete.docDragData.userDropAction || de.complete.docDragData.embedDoc) {
+                const target = de.complete.docDragData.droppedDocuments[0];
+                // const link = DocUtils.MakeLink({ doc: this.dataDoc, ctx: this.props.ContainingCollectionDoc }, { doc: target }, "Embedded Doc:" + target.title);
+                // if (link) {
+                target._fitToBox = true;
+                const node = schema.nodes.dashDoc.create({
+                    width: target[WidthSym](), height: target[HeightSym](),
+                    title: "dashDoc", docid: target[Id],
+                    float: "right"
+                });
+                const view = this._editorView!;
+                view.dispatch(view.state.tr.insert(view.posAtCoords({ left: de.x, top: de.y })!.pos, node));
+                this.tryUpdateHeight();
+                e.stopPropagation();
+                // }
+            } // otherwise, fall through to outer collection to handle drop
+        } else if (de.complete.linkDragData) {
+            de.complete.linkDragData.linkDropCallback = this.linkDrop;
+        }
+    }
+    linkDrop = (data: DragManager.LinkDragData) => {
+        const linkDoc = data.linkDocument!;
+        const anchor1Title = linkDoc.anchor1 instanceof Doc ? StrCast(linkDoc.anchor1.title) : "-untitled-";
+        const anchor1Id = linkDoc.anchor1 instanceof Doc ? linkDoc.anchor1[Id] : "";
+        this.makeLinkToSelection(linkDoc[Id], anchor1Title, "onRight", anchor1Id);
+    }
+
+    getNodeEndpoints(context: Node, node: Node): { from: number, to: number } | null {
+        let offset = 0;
+
+        if (context === node) return { from: offset, to: offset + node.nodeSize };
+
+        if (node.isBlock) {
+            // tslint:disable-next-line: prefer-for-of
+            for (let i = 0; i < (context.content as any).content.length; i++) {
+                const result = this.getNodeEndpoints((context.content as any).content[i], node);
+                if (result) {
+                    return {
+                        from: result.from + offset + (context.type.name === "doc" ? 0 : 1),
+                        to: result.to + offset + (context.type.name === "doc" ? 0 : 1)
+                    };
+                }
+                offset += (context.content as any).content[i].nodeSize;
+            }
+            return null;
+        } else {
+            return null;
+        }
+    }
+
+
+    //Recursively finds matches within a given node
+    findInNode(pm: EditorView, node: Node, find: string) {
+        let ret: TextSelection[] = [];
+
+        if (node.isTextblock) {
+            let index = 0, foundAt;
+            const ep = this.getNodeEndpoints(pm.state.doc, node);
+            while (ep && (foundAt = node.textContent.slice(index).search(RegExp(find, "i"))) > -1) {
+                const sel = new TextSelection(pm.state.doc.resolve(ep.from + index + foundAt + 1), pm.state.doc.resolve(ep.from + index + foundAt + find.length + 1));
+                ret.push(sel);
+                index = index + foundAt + find.length;
+            }
+        } else {
+            node.content.forEach((child, i) => ret = ret.concat(this.findInNode(pm, child, find)));
+        }
+        return ret;
+    }
+    static _highlights: string[] = ["Text from Others", "Todo Items", "Important Items", "Disagree Items", "Ignore Items"];
+
+    updateHighlights = () => {
+        clearStyleSheetRules(FormattedTextBox._userStyleSheet);
+        if (FormattedTextBox._highlights.indexOf("Text from Others") !== -1) {
+            addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-remote", { background: "yellow" });
+        }
+        if (FormattedTextBox._highlights.indexOf("My Text") !== -1) {
+            addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { background: "moccasin" });
+        }
+        if (FormattedTextBox._highlights.indexOf("Todo Items") !== -1) {
+            addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "todo", { outline: "black solid 1px" });
+        }
+        if (FormattedTextBox._highlights.indexOf("Important Items") !== -1) {
+            addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "important", { "font-size": "larger" });
+        }
+        if (FormattedTextBox._highlights.indexOf("Disagree Items") !== -1) {
+            addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "disagree", { "text-decoration": "line-through" });
+        }
+        if (FormattedTextBox._highlights.indexOf("Ignore Items") !== -1) {
+            addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "ignore", { "font-size": "1" });
+        }
+        if (FormattedTextBox._highlights.indexOf("By Recent Minute") !== -1) {
+            addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" });
+            const min = Math.round(Date.now() / 1000 / 60);
+            numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-min-" + (min - i), { opacity: ((10 - i - 1) / 10).toString() }));
+            setTimeout(() => this.updateHighlights());
+        }
+        if (FormattedTextBox._highlights.indexOf("By Recent Hour") !== -1) {
+            addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" });
+            const hr = Math.round(Date.now() / 1000 / 60 / 60);
+            numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-hr-" + (hr - i), { opacity: ((10 - i - 1) / 10).toString() }));
+        }
+    }
+
+    sidebarDown = (e: React.PointerEvent) => {
+        this._lastX = e.clientX;
+        this._lastY = e.clientY;
+        this._sidebarMovement = 0;
+        document.addEventListener("pointermove", this.sidebarMove);
+        document.addEventListener("pointerup", this.sidebarUp);
+        e.stopPropagation();
+        e.preventDefault(); // prevents text from being selected during drag
+    }
+    sidebarMove = (e: PointerEvent) => {
+        const bounds = this.CurrentDiv.getBoundingClientRect();
+        this._sidebarMovement += Math.sqrt((e.clientX - this._lastX) * (e.clientX - this._lastX) + (e.clientY - this._lastY) * (e.clientY - this._lastY));
+        this.props.Document.sidebarWidthPercent = "" + 100 * (1 - (e.clientX - bounds.left) / bounds.width) + "%";
+    }
+    sidebarUp = (e: PointerEvent) => {
+        document.removeEventListener("pointermove", this.sidebarMove);
+        document.removeEventListener("pointerup", this.sidebarUp);
+    }
+
+    toggleSidebar = () => this._sidebarMovement < 5 && (this.props.Document.sidebarWidthPercent = StrCast(this.props.Document.sidebarWidthPercent, "0%") === "0%" ? "25%" : "0%");
+
+    public static get DefaultLayout(): Doc | string | undefined {
+        return Cast(Doc.UserDoc().defaultTextLayout, Doc, null) || StrCast(Doc.UserDoc().defaultTextLayout, null);
+    }
+    specificContextMenu = (e: React.MouseEvent): void => {
+        const cm = ContextMenu.Instance;
+
+        const funcs: ContextMenuProps[] = [];
+        this.props.Document.isTemplateDoc && funcs.push({ description: "Make Default Layout", event: async () => Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.props.Document), icon: "eye" });
+        funcs.push({ description: "Reset Default Layout", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" });
+        !this.props.Document.rootDocument && funcs.push({
+            description: "Make Template", event: () => {
+                this.props.Document.isTemplateDoc = makeTemplate(this.props.Document);
+                Doc.AddDocToList(Cast(Doc.UserDoc()["template-notes"], Doc, null), "data", this.props.Document);
+            }, icon: "eye"
+        });
+        funcs.push({ description: "Toggle Single Line", event: () => this.props.Document._singleLine = !this.props.Document._singleLine, icon: "expand-arrows-alt" });
+        funcs.push({ description: "Toggle Sidebar", event: () => this.props.Document._showSidebar = !this.props.Document._showSidebar, icon: "expand-arrows-alt" });
+        funcs.push({ description: "Toggle Dictation Icon", event: () => this.props.Document._showAudio = !this.props.Document._showAudio, icon: "expand-arrows-alt" });
+        funcs.push({ description: "Toggle Menubar", event: () => this.toggleMenubar(), icon: "expand-arrows-alt" });
+
+        const highlighting: ContextMenuProps[] = [];
+        ["My Text", "Text from Others", "Todo Items", "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"].forEach(option =>
+            highlighting.push({
+                description: (FormattedTextBox._highlights.indexOf(option) === -1 ? "Highlight " : "Unhighlight ") + option, event: () => {
+                    e.stopPropagation();
+                    if (FormattedTextBox._highlights.indexOf(option) === -1) {
+                        FormattedTextBox._highlights.push(option);
+                    } else {
+                        FormattedTextBox._highlights.splice(FormattedTextBox._highlights.indexOf(option), 1);
+                    }
+                    this.updateHighlights();
+                }, icon: "expand-arrows-alt"
+            }));
+        funcs.push({ description: "highlighting...", subitems: highlighting, icon: "hand-point-right" });
+
+        ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
+
+        const change = cm.findByDescription("Change Perspective...");
+        const changeItems: ContextMenuProps[] = change && "subitems" in change ? change.subitems : [];
+
+        const noteTypesDoc = Cast(Doc.UserDoc()["template-notes"], Doc, null);
+        DocListCast(noteTypesDoc?.data).forEach(note => {
+            changeItems.push({
+                description: StrCast(note.title), event: undoBatch(() => {
+                    Doc.setNativeView(this.props.Document);
+                    Doc.makeCustomViewClicked(this.rootDoc, Docs.Create.TreeDocument, StrCast(note.title), note);
+                }), icon: "eye"
+            });
+        });
+        changeItems.push({ description: "FreeForm", event: undoBatch(() => Doc.makeCustomViewClicked(this.rootDoc, Docs.Create.FreeformDocument, "freeform"), "change view"), icon: "eye" });
+        !change && cm.addItem({ description: "Change Perspective...", subitems: changeItems, icon: "external-link-alt" });
+
+        const open = cm.findByDescription("Add a Perspective...");
+        const openItems: ContextMenuProps[] = open && "subitems" in open ? open.subitems : [];
+
+        openItems.push({
+            description: "FreeForm", event: undoBatch(() => {
+                const alias = Doc.MakeAlias(this.rootDoc);
+                Doc.makeCustomViewClicked(alias, Docs.Create.FreeformDocument, "freeform");
+                this.props.addDocTab(alias, "onRight");
+            }), icon: "eye"
+        });
+        !open && cm.addItem({ description: "Add a Perspective...", subitems: openItems, icon: "external-link-alt" });
+
+    }
+
+    recordDictation = () => {
+        DictationManager.Controls.listen({
+            interimHandler: this.setCurrentBulletContent,
+            continuous: { indefinite: false },
+        }).then(results => {
+            if (results && [DictationManager.Controls.Infringed].includes(results)) {
+                DictationManager.Controls.stop();
+            }
+            //this._editorView!.focus();
+        });
+    }
+    stopDictation = (abort: boolean) => { DictationManager.Controls.stop(!abort); };
+
+    @action
+    toggleMenubar = () => {
+        this.props.Document._chromeStatus = this.props.Document._chromeStatus === "disabled" ? "enabled" : "disabled";
+    }
+
+    recordBullet = async () => {
+        const completedCue = "end session";
+        const results = await DictationManager.Controls.listen({
+            interimHandler: this.setCurrentBulletContent,
+            continuous: { indefinite: false },
+            terminators: [completedCue, "bullet", "next"]
+        });
+        if (results && [DictationManager.Controls.Infringed, completedCue].includes(results)) {
+            DictationManager.Controls.stop();
+            return;
+        }
+        this.nextBullet(this._editorView!.state.selection.to);
+        setTimeout(this.recordBullet, 2000);
+    }
+
+    setCurrentBulletContent = (value: string) => {
+        if (this._editorView) {
+            const state = this._editorView.state;
+            const now = Date.now();
+            let mark = schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(now / 1000) });
+            if (!this._break && state.selection.to !== state.selection.from) {
+                for (let i = state.selection.from; i <= state.selection.to; i++) {
+                    const pos = state.doc.resolve(i);
+                    const um = Array.from(pos.marks()).find(m => m.type === schema.marks.user_mark);
+                    if (um) {
+                        mark = um;
+                        break;
+                    }
+                }
+            }
+            const recordingStart = DateCast(this.props.Document.recordingStart).date.getTime();
+            this._break = false;
+            value = "" + (mark.attrs.modified * 1000 - recordingStart) / 1000 + value;
+            const from = state.selection.from;
+            const inserted = state.tr.insertText(value).addMark(from, from + value.length + 1, mark);
+            this._editorView.dispatch(inserted.setSelection(TextSelection.create(inserted.doc, from, from + value.length + 1)));
+        }
+    }
+
+    nextBullet = (pos: number) => {
+        if (this._editorView) {
+            const frag = Fragment.fromArray(this.newListItems(2));
+            if (this._editorView.state.doc.resolve(pos).depth >= 2) {
+                const slice = new Slice(frag, 2, 2);
+                let state = this._editorView.state;
+                this._editorView.dispatch(state.tr.step(new ReplaceStep(pos, pos, slice)));
+                pos += 4;
+                state = this._editorView.state;
+                this._editorView.dispatch(state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos, pos)));
+            }
+        }
+    }
+
+    private newListItems = (count: number) => {
+        return numberRange(count).map(x => schema.nodes.list_item.create(undefined, schema.nodes.paragraph.create()));
+    }
+
+    _keymap: any = undefined;
+    _rules: RichTextRules | undefined;
+    @computed get config() {
+        this._keymap = buildKeymap(schema, this.props);
+        this._rules = new RichTextRules(this.props.Document, this);
+        return {
+            schema,
+            plugins: [
+                inputRules(this._rules.inpRules),
+                this.richTextMenuPlugin(),
+                history(),
+                keymap(this._keymap),
+                keymap(baseKeymap),
+                new Plugin({
+                    props: {
+                        attributes: { class: "ProseMirror-example-setup-style" }
+                    }
+                }),
+                formattedTextBoxCommentPlugin
+            ]
+        };
+    }
+
+    makeLinkToSelection(linkDocId: string, title: string, location: string, targetDocId: string) {
+        if (this._editorView) {
+            const link = this._editorView.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + linkDocId), title: title, location: location, linkId: linkDocId, targetId: targetDocId });
+            this._editorView.dispatch(this._editorView.state.tr.removeMark(this._editorView.state.selection.from, this._editorView.state.selection.to, this._editorView.state.schema.marks.link).
+                addMark(this._editorView.state.selection.from, this._editorView.state.selection.to, link));
+        }
+    }
+    componentDidMount() {
+        this._disposers.buttonBar = reaction(
+            () => DocumentButtonBar.Instance,
+            instance => {
+                if (instance) {
+                    this.pullFromGoogleDoc(this.checkState);
+                    this.dataDoc[GoogleRef] && this.dataDoc.unchanged && runInAction(() => instance.isAnimatingFetch = true);
+                }
+            }
+        );
+        this._disposers.linkMaker = reaction(
+            () => this.props.makeLink?.(),
+            (linkDoc: Opt<Doc>) => {
+                if (linkDoc) {
+                    const anchor2Title = linkDoc.anchor2 instanceof Doc ? StrCast(linkDoc.anchor2.title) : "-untitled-";
+                    const anchor2Id = linkDoc.anchor2 instanceof Doc ? linkDoc.anchor2[Id] : "";
+                    this.makeLinkToSelection(linkDoc[Id], anchor2Title, "onRight", anchor2Id);
+                }
+            },
+            { fireImmediately: true }
+        );
+        this._disposers.editorState = reaction(
+            () => {
+                if (this.dataDoc[this.props.fieldKey + "-noTemplate"] || !this.props.Document[this.props.fieldKey + "-textTemplate"]) {
+                    return Cast(this.dataDoc[this.props.fieldKey], RichTextField, null)?.Data;
+                }
+                return Cast(this.props.Document[this.props.fieldKey + "-textTemplate"], RichTextField, null)?.Data;
+            },
+            incomingValue => {
+                if (incomingValue !== undefined && this._editorView && !this._applyingChange) {
+                    const updatedState = JSON.parse(incomingValue);
+                    this._editorView.updateState(EditorState.fromJSON(this.config, updatedState));
+                    this.tryUpdateHeight();
+                }
+            }
+        );
+        this._disposers.pullDoc = reaction(
+            () => this.props.Document[Pulls],
+            () => {
+                if (!DocumentButtonBar.hasPulledHack) {
+                    DocumentButtonBar.hasPulledHack = true;
+                    const unchanged = this.dataDoc.unchanged;
+                    this.pullFromGoogleDoc(unchanged ? this.checkState : this.updateState);
+                }
+            }
+        );
+        this._disposers.pushDoc = reaction(
+            () => this.props.Document[Pushes],
+            () => {
+                if (!DocumentButtonBar.hasPushedHack) {
+                    DocumentButtonBar.hasPushedHack = true;
+                    this.pushToGoogleDoc();
+                }
+            }
+        );
+        this._disposers.height = reaction(
+            () => [this.layoutDoc[WidthSym](), this.layoutDoc._autoHeight],
+            () => this.tryUpdateHeight()
+        );
+
+        this.setupEditor(this.config, this.props.fieldKey);
+
+        this._disposers.search = reaction(() => this.rootDoc.searchMatch,
+            search => search ? this.highlightSearchTerms([Doc.SearchQuery()]) : this.unhighlightSearchTerms(),
+            { fireImmediately: true });
+
+        this._disposers.record = reaction(() => this._recording,
+            () => {
+                if (this._recording) {
+                    setTimeout(action(() => {
+                        this.stopDictation(true);
+                        setTimeout(() => this.recordDictation(), 500);
+                    }), 500);
+                } else setTimeout(() => this.stopDictation(true), 0);
+            }
+        );
+        this._disposers.scrollToRegion = reaction(
+            () => StrCast(this.layoutDoc.scrollToLinkID),
+            async (scrollToLinkID) => {
+                const findLinkFrag = (frag: Fragment, editor: EditorView) => {
+                    const nodes: Node[] = [];
+                    frag.forEach((node, index) => {
+                        const examinedNode = findLinkNode(node, editor);
+                        if (examinedNode && examinedNode.textContent) {
+                            nodes.push(examinedNode);
+                            start += index;
+                        }
+                    });
+                    return { frag: Fragment.fromArray(nodes), start: start };
+                };
+                const findLinkNode = (node: Node, editor: EditorView) => {
+                    if (!node.isText) {
+                        const content = findLinkFrag(node.content, editor);
+                        return node.copy(content.frag);
+                    }
+                    const marks = [...node.marks];
+                    const linkIndex = marks.findIndex(mark => mark.type === editor.state.schema.marks.link);
+                    return linkIndex !== -1 && scrollToLinkID === marks[linkIndex].attrs.href.replace(/.*\/doc\//, "") ? node : undefined;
+                };
+
+                let start = -1;
+                if (this._editorView && scrollToLinkID) {
+                    const editor = this._editorView;
+                    const ret = findLinkFrag(editor.state.doc.content, editor);
+
+                    if (ret.frag.size > 2 && ret.start >= 0) {
+                        let selection = TextSelection.near(editor.state.doc.resolve(ret.start)); // default to near the start
+                        if (ret.frag.firstChild) {
+                            selection = TextSelection.between(editor.state.doc.resolve(ret.start), editor.state.doc.resolve(ret.start + ret.frag.firstChild.nodeSize)); // bcz: looks better to not have the target selected
+                        }
+                        editor.dispatch(editor.state.tr.setSelection(new TextSelection(selection.$from, selection.$from)).scrollIntoView());
+                        const mark = editor.state.schema.mark(this._editorView.state.schema.marks.search_highlight);
+                        setTimeout(() => editor.dispatch(editor.state.tr.addMark(selection.from, selection.to, mark)), 0);
+                        setTimeout(() => this.unhighlightSearchTerms(), 2000);
+                    }
+                    Doc.SetInPlace(this.layoutDoc, "scrollToLinkID", undefined, false);
+                }
+
+            },
+            { fireImmediately: true }
+        );
+        this._disposers.scroll = reaction(() => NumCast(this.props.Document.scrollPos),
+            pos => this._scrollRef.current && this._scrollRef.current.scrollTo({ top: pos }), { fireImmediately: true }
+        );
+
+        setTimeout(() => this.tryUpdateHeight(NumCast(this.layoutDoc.limitHeight, 0)));
+    }
+
+    pushToGoogleDoc = async () => {
+        this.pullFromGoogleDoc(async (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => {
+            const modes = GoogleApiClientUtils.Docs.WriteMode;
+            let mode = modes.Replace;
+            let reference: Opt<GoogleApiClientUtils.Docs.Reference> = Cast(this.dataDoc[GoogleRef], "string");
+            if (!reference) {
+                mode = modes.Insert;
+                reference = { title: StrCast(this.dataDoc.title) };
+            }
+            const redo = async () => {
+                if (this._editorView && reference) {
+                    const content = await RichTextUtils.GoogleDocs.Export(this._editorView.state);
+                    const response = await GoogleApiClientUtils.Docs.write({ reference, content, mode });
+                    response && (this.dataDoc[GoogleRef] = response.documentId);
+                    const pushSuccess = response !== undefined && !("errors" in response);
+                    dataDoc.unchanged = pushSuccess;
+                    DocumentButtonBar.Instance.startPushOutcome(pushSuccess);
+                }
+            };
+            const undo = () => {
+                if (!exportState) {
+                    return;
+                }
+                const content: GoogleApiClientUtils.Docs.Content = {
+                    text: exportState.text,
+                    requests: []
+                };
+                if (reference && content) {
+                    GoogleApiClientUtils.Docs.write({ reference, content, mode });
+                }
+            };
+            UndoManager.AddEvent({ undo, redo });
+            redo();
+        });
+    }
+
+    pullFromGoogleDoc = async (handler: PullHandler) => {
+        const dataDoc = this.dataDoc;
+        const documentId = StrCast(dataDoc[GoogleRef]);
+        let exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>;
+        if (documentId) {
+            exportState = await RichTextUtils.GoogleDocs.Import(documentId, dataDoc);
+        }
+        UndoManager.RunInBatch(() => handler(exportState, dataDoc), Pulls);
+    }
+
+    updateState = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => {
+        let pullSuccess = false;
+        if (exportState !== undefined) {
+            pullSuccess = true;
+            dataDoc.data = new RichTextField(JSON.stringify(exportState.state.toJSON()));
+            setTimeout(() => {
+                if (this._editorView) {
+                    const state = this._editorView.state;
+                    const end = state.doc.content.size - 1;
+                    this._editorView.dispatch(state.tr.setSelection(TextSelection.create(state.doc, end, end)));
+                }
+            }, 0);
+            dataDoc.title = exportState.title;
+            this.rootDoc.customTitle = true;
+            dataDoc.unchanged = true;
+        } else {
+            delete dataDoc[GoogleRef];
+        }
+        DocumentButtonBar.Instance.startPullOutcome(pullSuccess);
+    }
+
+    checkState = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => {
+        if (exportState && this._editorView) {
+            const equalContent = isEqual(this._editorView.state.doc, exportState.state.doc);
+            const equalTitles = dataDoc.title === exportState.title;
+            const unchanged = equalContent && equalTitles;
+            dataDoc.unchanged = unchanged;
+            DocumentButtonBar.Instance.setPullState(unchanged);
+        }
+    }
+
+    clipboardTextSerializer = (slice: Slice): string => {
+        let text = "", separated = true;
+        const from = 0, to = slice.content.size;
+        slice.content.nodesBetween(from, to, (node, pos) => {
+            if (node.isText) {
+                text += node.text!.slice(Math.max(from, pos) - pos, to - pos);
+                separated = false;
+            } else if (!separated && node.isBlock) {
+                text += "\n";
+                separated = true;
+            } else if (node.type.name === "hard_break") {
+                text += "\n";
+            }
+        }, 0);
+        return text;
+    }
+
+    sliceSingleNode(slice: Slice) {
+        return slice.openStart === 0 && slice.openEnd === 0 && slice.content.childCount === 1 ? slice.content.firstChild : null;
+    }
+
+    handlePaste = (view: EditorView, event: Event, slice: Slice): boolean => {
+        const cbe = event as ClipboardEvent;
+        const pdfDocId = cbe.clipboardData && cbe.clipboardData.getData("dash/pdfOrigin");
+        const pdfRegionId = cbe.clipboardData && cbe.clipboardData.getData("dash/pdfRegion");
+        if (pdfDocId && pdfRegionId) {
+            DocServer.GetRefField(pdfDocId).then(pdfDoc => {
+                DocServer.GetRefField(pdfRegionId).then(pdfRegion => {
+                    if ((pdfDoc instanceof Doc) && (pdfRegion instanceof Doc)) {
+                        setTimeout(async () => {
+                            const targetField = Doc.LayoutFieldKey(pdfDoc);
+                            const targetAnnotations = await DocListCastAsync(pdfDoc[DataSym][targetField + "-annotations"]);// bcz: better to have the PDF's view handle updating its own annotations
+                            targetAnnotations?.push(pdfRegion);
+                        });
+
+                        const link = DocUtils.MakeLink({ doc: this.props.Document }, { doc: pdfRegion }, "PDF pasted");
+                        if (link) {
+                            cbe.clipboardData!.setData("dash/linkDoc", link[Id]);
+                            const linkId = link[Id];
+                            const frag = addMarkToFrag(slice.content, (node: Node) => addLinkMark(node, StrCast(pdfDoc.title), linkId));
+                            slice = new Slice(frag, slice.openStart, slice.openEnd);
+                            const tr = view.state.tr.replaceSelection(slice);
+                            view.dispatch(tr.scrollIntoView().setMeta("paste", true).setMeta("uiEvent", "paste"));
+                        }
+                    }
+                });
+            });
+            return true;
+        }
+        return false;
+
+
+        function addMarkToFrag(frag: Fragment, marker: (node: Node) => Node) {
+            const nodes: Node[] = [];
+            frag.forEach(node => nodes.push(marker(node)));
+            return Fragment.fromArray(nodes);
+        }
+        function addLinkMark(node: Node, title: string, linkId: string) {
+            if (!node.isText) {
+                const content = addMarkToFrag(node.content, (node: Node) => addLinkMark(node, title, linkId));
+                return node.copy(content);
+            }
+            const marks = [...node.marks];
+            const linkIndex = marks.findIndex(mark => mark.type.name === "link");
+            const link = view.state.schema.mark(view.state.schema.marks.link, { href: `http://localhost:1050/doc/${linkId}`, location: "onRight", title: title, docref: true });
+            marks.splice(linkIndex === -1 ? 0 : linkIndex, 1, link);
+            return node.mark(marks);
+        }
+    }
+
+    private setupEditor(config: any, fieldKey: string) {
+        const curText = Cast(this.dataDoc[this.props.fieldKey], RichTextField, null);
+        const useTemplate = !curText?.Text && this.props.Document[this.props.fieldKey + "-textTemplate"];
+        const rtfField = Cast((useTemplate && this.props.Document[this.props.fieldKey + "-textTemplate"]) || this.dataDoc[fieldKey], RichTextField);
+        if (this.ProseRef) {
+            const self = this;
+            this._editorView?.destroy();
+            this._editorView = new EditorView(this.ProseRef, {
+                state: rtfField?.Data ? EditorState.fromJSON(config, JSON.parse(rtfField.Data)) : EditorState.create(config),
+                handleScrollToSelection: (editorView) => {
+                    const docPos = editorView.coordsAtPos(editorView.state.selection.from);
+                    const viewRect = self._ref.current!.getBoundingClientRect();
+                    if (docPos.top < viewRect.top || docPos.top > viewRect.bottom) {
+                        docPos && (self._scrollRef.current!.scrollTop += (docPos.top - viewRect.top) * self.props.ScreenToLocalTransform().Scale);
+                    }
+                    return true;
+                },
+                dispatchTransaction: this.dispatchTransaction,
+                nodeViews: {
+                    dashComment(node, view, getPos) { return new DashDocCommentView(node, view, getPos); },
+                    dashField(node, view, getPos) { return new DashFieldView(node, view, getPos, self); },
+                    dashDoc(node, view, getPos) { return new DashDocView(node, view, getPos, self); },
+                    // dashDoc(node, view, getPos) { return new DashDocView({ node, view, getPos, self }); },
+
+                    // image(node, view, getPos) {
+                    //     //const addDocTab = this.props.addDocTab;
+                    //     return new ImageResizeView({ node, view, getPos, addDocTab: this.props.addDocTab });
+                    // },
+                    // // WAS : 
+                    // //image(node, view, getPos) { return new ImageResizeView(node, view, getPos, this.props.addDocTab); },
+
+                    summary(node, view, getPos) { return new SummaryView(node, view, getPos); },
+                    ordered_list(node, view, getPos) { return new OrderedListView(); },
+                    footnote(node, view, getPos) { return new FootnoteView(node, view, getPos); }
+                },
+                clipboardTextSerializer: this.clipboardTextSerializer,
+                handlePaste: this.handlePaste,
+            });
+            const startupText = !rtfField && this._editorView && Field.toString(this.dataDoc[fieldKey] as Field);
+            if (startupText) {
+                const { state: { tr }, dispatch } = this._editorView;
+                dispatch(tr.insertText(startupText));
+            }
+        }
+
+        const selectOnLoad = this.rootDoc[Id] === FormattedTextBox.SelectOnLoad;
+        if (selectOnLoad && !this.props.dontRegisterView) {
+            FormattedTextBox.SelectOnLoad = "";
+            this.props.select(false);
+            FormattedTextBox.SelectOnLoadChar && this._editorView!.dispatch(this._editorView!.state.tr.insertText(FormattedTextBox.SelectOnLoadChar));
+            FormattedTextBox.SelectOnLoadChar = "";
+
+        }
+        (selectOnLoad /* || !rtfField?.Text*/) && this._editorView!.focus();
+        // add user mark for any first character that was typed since the user mark that gets set in KeyPress won't have been called yet.
+        this._editorView!.state.storedMarks = [...(this._editorView!.state.storedMarks ? this._editorView!.state.storedMarks : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })];
+    }
+    getFont(font: string) {
+        switch (font) {
+            case "Arial": return schema.marks.arial.create();
+            case "Times New Roman": return schema.marks.timesNewRoman.create();
+            case "Georgia": return schema.marks.georgia.create();
+            case "Comic Sans MS": return schema.marks.comicSans.create();
+            case "Tahoma": return schema.marks.tahoma.create();
+            case "Impact": return schema.marks.impact.create();
+            case "ACrimson Textrial": return schema.marks.crimson.create();
+        }
+        return schema.marks.arial.create();
+    }
+
+    componentWillUnmount() {
+        Object.values(this._disposers).forEach(disposer => disposer?.());
+        this._editorView?.destroy();
+    }
+
+    static _downEvent: any;
+    _downX = 0;
+    _downY = 0;
+    _break = false;
+    onPointerDown = (e: React.PointerEvent): void => {
+        if (this._recording && !e.ctrlKey && e.button === 0) {
+            this.stopDictation(true);
+            this._break = true;
+            const state = this._editorView!.state;
+            const to = state.selection.to;
+            const updated = TextSelection.create(state.doc, to, to);
+            this._editorView!.dispatch(this._editorView!.state.tr.setSelection(updated).insertText("\n", to));
+            e.preventDefault();
+            e.stopPropagation();
+            if (this._recording) setTimeout(() => this.recordDictation(), 500);
+        }
+        this._downX = e.clientX;
+        this._downY = e.clientY;
+        this.doLinkOnDeselect();
+        FormattedTextBox._downEvent = true;
+        FormattedTextBoxComment.textBox = this;
+        if (this.props.onClick && e.button === 0 && !this.props.isSelected(false)) {
+            e.preventDefault();
+        }
+        if (e.button === 0 && this.active(true) && !e.altKey && !e.ctrlKey && !e.metaKey) {
+            if (e.clientX < this.ProseRef!.getBoundingClientRect().right) { // don't stop propagation if clicking in the sidebar
+                e.stopPropagation();
+            }
+        }
+        if (e.button === 2 || (e.button === 0 && e.ctrlKey)) {
+            e.preventDefault();
+        }
+    }
+
+    onPointerUp = (e: React.PointerEvent): void => {
+        if (!FormattedTextBox._downEvent) return;
+        FormattedTextBox._downEvent = false;
+        if (!(e.nativeEvent as any).formattedHandled) {
+            FormattedTextBoxComment.textBox = this;
+            FormattedTextBoxComment.update(this._editorView!);
+        }
+        (e.nativeEvent as any).formattedHandled = true;
+
+        if (e.buttons === 1 && this.props.isSelected(true) && !e.altKey) {
+            e.stopPropagation();
+        }
+        this._downX = this._downY = Number.NaN;
+    }
+
+    @action
+    onFocused = (e: React.FocusEvent): void => {
+        FormattedTextBox.FocusedBox = this;
+        this.tryUpdateHeight();
+
+        // see if we need to preserve the insertion point
+        const prosediv = this.ProseRef?.children?.[0] as any;
+        const keeplocation = prosediv?.keeplocation;
+        prosediv && (prosediv.keeplocation = undefined);
+        const pos = this._editorView?.state.selection.$from.pos || 1;
+        keeplocation && setTimeout(() => this._editorView?.dispatch(this._editorView?.state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos))));
+        const coords = !Number.isNaN(this._downX) ? { left: this._downX, top: this._downY, bottom: this._downY, right: this._downX } : this._editorView?.coordsAtPos(pos);
+
+        // jump rich text menu to this textbox
+        const bounds = this._ref.current?.getBoundingClientRect();
+        if (bounds && this.props.Document._chromeStatus !== "disabled") {
+            const x = Math.min(Math.max(bounds.left, 0), window.innerWidth - RichTextMenu.Instance.width);
+            let y = Math.min(Math.max(0, bounds.top - RichTextMenu.Instance.height - 50), window.innerHeight - RichTextMenu.Instance.height);
+            if (coords && coords.left > x && coords.left < x + RichTextMenu.Instance.width && coords.top > y && coords.top < y + RichTextMenu.Instance.height + 50) {
+                y = Math.min(bounds.bottom, window.innerHeight - RichTextMenu.Instance.height);
+            }
+            RichTextMenu.Instance.jumpTo(x, y);
+        }
+    }
+    onPointerWheel = (e: React.WheelEvent): void => {
+        // if a text note is not selected and scrollable, this prevents us from being able to scroll and zoom out at the same time
+        if (this.props.isSelected(true) || e.currentTarget.scrollHeight > e.currentTarget.clientHeight) {
+            e.stopPropagation();
+        }
+    }
+
+    static _bulletStyleSheet: any = addStyleSheet();
+    static _userStyleSheet: any = addStyleSheet();
+
+    onClick = (e: React.MouseEvent): void => {
+        if ((this._editorView!.root as any).getSelection().isCollapsed) { // this is a hack to allow the cursor to be placed at the end of a document when the document ends in an inline dash comment.  Apparently Chrome on Windows has a bug/feature which breaks this when clicking after the end of the text.
+            const pcords = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY });
+            const node = pcords && this._editorView!.state.doc.nodeAt(pcords.pos); // get what prosemirror thinks the clicked node is (if it's null, then we didn't click on any text)
+            if (pcords && node?.type === this._editorView!.state.schema.nodes.dashComment) {
+                this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, pcords.pos + 2)));
+                e.preventDefault();
+            }
+            if (!node && this.ProseRef) {
+                const lastNode = this.ProseRef.children[this.ProseRef.children.length - 1].children[this.ProseRef.children[this.ProseRef.children.length - 1].children.length - 1]; // get the last prosemirror div
+                if (e.clientY > lastNode?.getBoundingClientRect().bottom) { // if we clicked below the last prosemirror div, then set the selection to be the end of the document
+                    this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, this._editorView!.state.doc.content.size)));
+                }
+            }
+        }
+        if ((e.nativeEvent as any).formattedHandled) { e.stopPropagation(); return; }
+        (e.nativeEvent as any).formattedHandled = true;
+        // if (e.button === 0 && ((!this.props.isSelected(true) && !e.ctrlKey) || (this.props.isSelected(true) && e.ctrlKey)) && !e.metaKey && e.target) {
+        //     let href = (e.target as any).href;
+        //     let location: string;
+        //     if ((e.target as any).attributes.location) {
+        //         location = (e.target as any).attributes.location.value;
+        //     }
+        //     let pcords = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY });
+        //     let node = pcords && this._editorView!.state.doc.nodeAt(pcords.pos);
+        //     if (node) {
+        //         let link = node.marks.find(m => m.type === this._editorView!.state.schema.marks.link);
+        //         if (link && !(link.attrs.docref && link.attrs.title)) {  // bcz: getting hacky.  this indicates that we clicked on a PDF excerpt quotation.  In this case, we don't want to follow the link (we follow only the actual hyperlink for the quotation which is handled above).
+        //             href = link && link.attrs.href;
+        //             location = link && link.attrs.location;
+        //         }
+        //     }
+        //     if (href) {
+        //         if (href.indexOf(Utils.prepend("/doc/")) === 0) {
+        //             let linkClicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
+        //             if (linkClicked) {
+        //                 DocServer.GetRefField(linkClicked).then(async linkDoc => {
+        //                     (linkDoc instanceof Doc) &&
+        //                         DocumentManager.Instance.FollowLink(linkDoc, this.props.Document, document => this.props.addDocTab(document, location ? location : "inTab"), false);
+        //                 });
+        //             }
+        //         } else {
+        //             let webDoc = Docs.Create.WebDocument(href, { x: NumCast(this.layoutDoc.x, 0) + NumCast(this.layoutDoc.width, 0), y: NumCast(this.layoutDoc.y) });
+        //             this.props.addDocument && this.props.addDocument(webDoc);
+        //         }
+        //         e.stopPropagation();
+        //         e.preventDefault();
+        //     }
+        // }
+
+        if (Math.abs(e.clientX - this._downX) < 4 && Math.abs(e.clientX - this._downX) < 4) {
+            this.props.select(e.ctrlKey);
+            this.hitBulletTargets(e.clientX, e.clientY, e.shiftKey, false);
+        }
+    }
+
+    // this hackiness handles clicking on the list item bullets to do expand/collapse.  the bullets are ::before pseudo elements so there's no real way to hit test against them.
+    hitBulletTargets(x: number, y: number, select: boolean, highlightOnly: boolean) {
+        clearStyleSheetRules(FormattedTextBox._bulletStyleSheet);
+        const pos = this._editorView!.posAtCoords({ left: x, top: y });
+        if (pos && this.props.isSelected(true)) {
+            // let beforeEle = document.querySelector("." + hit.className) as Element; // const before = hit ? window.getComputedStyle(hit, ':before') : undefined;
+            //const node = this._editorView!.state.doc.nodeAt(pos.pos);
+            const $pos = this._editorView!.state.doc.resolve(pos.pos);
+            let list_node = $pos.node().type === schema.nodes.list_item ? $pos.node() : undefined;
+            if ($pos.node().type === schema.nodes.ordered_list) {
+                for (let off = 1; off < 100; off++) {
+                    const pos = this._editorView!.posAtCoords({ left: x + off, top: y });
+                    const node = pos && this._editorView!.state.doc.nodeAt(pos.pos);
+                    if (node?.type === schema.nodes.list_item) {
+                        list_node = node;
+                        break;
+                    }
+                }
+            }
+            if (list_node && pos.inside >= 0 && this._editorView!.state.doc.nodeAt(pos.inside)!.attrs.bulletStyle === list_node.attrs.bulletStyle) {
+                if (select) {
+                    const $olist_pos = this._editorView!.state.doc.resolve($pos.pos - $pos.parentOffset - 1);
+                    if (!highlightOnly) {
+                        this._editorView!.dispatch(this._editorView!.state.tr.setSelection(new NodeSelection($olist_pos)));
+                    }
+                    addStyleSheetRule(FormattedTextBox._bulletStyleSheet, list_node.attrs.mapStyle + list_node.attrs.bulletStyle + ":hover:before", { background: "lightgray" });
+                } else if (Math.abs(pos.pos - pos.inside) < 2) {
+                    if (!highlightOnly) {
+                        const offset = this._editorView!.state.doc.nodeAt(pos.inside)?.type === schema.nodes.ordered_list ? 1 : 0;
+                        this._editorView!.dispatch(this._editorView!.state.tr.setNodeMarkup(pos.inside + offset, list_node.type, { ...list_node.attrs, visibility: !list_node.attrs.visibility }));
+                        this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, pos.inside + offset)));
+                    }
+                    addStyleSheetRule(FormattedTextBox._bulletStyleSheet, list_node.attrs.mapStyle + list_node.attrs.bulletStyle + ":hover:before", { background: "lightgray" });
+                }
+            }
+        }
+    }
+    onMouseUp = (e: React.MouseEvent): void => {
+        e.stopPropagation();
+
+        const view = this._editorView as any;
+        // this interposes on prosemirror's upHandler to prevent prosemirror's up from invoked multiple times when there 
+        // are nested prosemirrors.  We only want the lowest level prosemirror to be invoked.
+        if (view.mouseDown) {
+            const originalUpHandler = view.mouseDown.up;
+            view.root.removeEventListener("mouseup", originalUpHandler);
+            view.mouseDown.up = (e: MouseEvent) => {
+                !(e as any).formattedHandled && originalUpHandler(e);
+                // e.stopPropagation();
+                (e as any).formattedHandled = true;
+            };
+            view.root.addEventListener("mouseup", view.mouseDown.up);
+        }
+    }
+
+    richTextMenuPlugin() {
+        return new Plugin({
+            view(newView) {
+                RichTextMenu.Instance && RichTextMenu.Instance.changeView(newView);
+                return RichTextMenu.Instance;
+            }
+        });
+    }
+
+    public static HadSelection: boolean = false;
+    onBlur = (e: any) => {
+        FormattedTextBox.HadSelection = window.getSelection()?.toString() !== "";
+        //DictationManager.Controls.stop(false);
+        if (this._undoTyping) {
+            this._undoTyping.end();
+            this._undoTyping = undefined;
+        }
+        this.doLinkOnDeselect();
+
+        // move the richtextmenu offscreen
+        if (!RichTextMenu.Instance.Pinned && !RichTextMenu.Instance.overMenu) RichTextMenu.Instance.jumpTo(-300, -300);
+    }
+
+    _lastTimedMark: Mark | undefined = undefined;
+    onKeyPress = (e: React.KeyboardEvent) => {
+        if (e.altKey) {
+            e.preventDefault();
+            return;
+        }
+        const state = this._editorView!.state;
+        if (!state.selection.empty && e.key === "%") {
+            this._rules!.EnteringStyle = true;
+            e.preventDefault();
+            e.stopPropagation();
+            return;
+        }
+
+        if (state.selection.empty || !this._rules!.EnteringStyle) {
+            this._rules!.EnteringStyle = false;
+        }
+        if (e.key === "Escape") {
+            this._editorView!.dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from)));
+            (document.activeElement as any).blur?.();
+            SelectionManager.DeselectAll();
+        }
+        e.stopPropagation();
+        if (e.key === "Tab" || e.key === "Enter") {
+            e.preventDefault();
+        }
+        const mark = e.key !== " " && this._lastTimedMark ? this._lastTimedMark : schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) });
+        this._lastTimedMark = mark;
+        this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(mark));
+
+        if (!this._undoTyping) {
+            this._undoTyping = UndoManager.StartBatch("undoTyping");
+        }
+    }
+
+    onscrolled = (ev: React.UIEvent) => {
+        this.props.Document.scrollPos = this._scrollRef.current!.scrollTop;
+    }
+    @action
+    tryUpdateHeight(limitHeight?: number) {
+        let scrollHeight = this._ref.current?.scrollHeight;
+        if (this.layoutDoc._autoHeight && scrollHeight &&
+            getComputedStyle(this._ref.current!.parentElement!).top === "0px") {  // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
+            if (limitHeight && scrollHeight > limitHeight) {
+                scrollHeight = limitHeight;
+                this.layoutDoc.limitHeight = undefined;
+                this.layoutDoc._autoHeight = false;
+            }
+            const nh = this.layoutDoc.isTemplateForField ? 0 : NumCast(this.dataDoc._nativeHeight, 0);
+            const dh = NumCast(this.layoutDoc._height, 0);
+            const newHeight = Math.max(10, (nh ? dh / nh * scrollHeight : scrollHeight) + (this.props.ChromeHeight ? this.props.ChromeHeight() : 0));
+            if (Math.abs(newHeight - dh) > 1) { // bcz: Argh!  without this, we get into a React crash if the same document is opened in a freeform view and in the treeview.  no idea why, but after dragging the freeform document, selecting it, and selecting text, it will compute to 1 pixel higher than the treeview which causes a cycle
+                this.layoutDoc._height = newHeight;
+                this.dataDoc._nativeHeight = nh ? scrollHeight : undefined;
+            }
+        }
+    }
+
+    @computed get sidebarWidthPercent() { return StrCast(this.props.Document.sidebarWidthPercent, "0%"); }
+    sidebarWidth = () => Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100 * this.props.PanelWidth();
+    sidebarScreenToLocal = () => this.props.ScreenToLocalTransform().translate(-(this.props.PanelWidth() - this.sidebarWidth()), 0);
+    @computed get sidebarColor() { return StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], "transparent")); }
+    render() {
+        TraceMobx();
+        const rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : "";
+        const interactive = InkingControl.Instance.selectedTool || this.layoutDoc.isBackground;
+        if (this.props.isSelected()) {
+            this._editorView && RichTextMenu.Instance.updateFromDash(this._editorView, undefined, this.props);
+        } else if (FormattedTextBoxComment.textBox === this) {
+            FormattedTextBoxComment.Hide();
+        }
+        return (
+
+            <div className={`formattedTextBox-cont`} ref={this._ref}
+                style={{
+                    height: this.layoutDoc._autoHeight && this.props.renderDepth ? "max-content" : `calc(100% - ${this.props.ChromeHeight?.() || 0}px`,
+                    background: StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], this.props.hideOnLeave ? "rgba(0,0,0 ,0.4)" : ""),
+                    opacity: this.props.hideOnLeave ? (this._entered ? 1 : 0.1) : 1,
+                    color: StrCast(this.layoutDoc[this.props.fieldKey + "-color"], this.props.hideOnLeave ? "white" : "inherit"),
+                    pointerEvents: interactive ? "none" : undefined,
+                    fontSize: Cast(this.layoutDoc._fontSize, "number", null),
+                    fontFamily: StrCast(this.layoutDoc._fontFamily, "inherit"),
+                }}
+                onContextMenu={this.specificContextMenu}
+                onKeyDown={this.onKeyPress}
+                onFocus={this.onFocused}
+                onClick={this.onClick}
+                onPointerMove={e => this.hitBulletTargets(e.clientX, e.clientY, e.shiftKey, true)}
+                onBlur={this.onBlur}
+                onPointerUp={this.onPointerUp}
+                onPointerDown={this.onPointerDown}
+                onMouseUp={this.onMouseUp}
+                onWheel={this.onPointerWheel}
+                onPointerEnter={action(() => this._entered = true)}
+                onPointerLeave={action((e: React.PointerEvent<HTMLDivElement>) => {
+                    this._entered = false;
+                    const target = document.elementFromPoint(e.nativeEvent.x, e.nativeEvent.y);
+                    for (let child: any = target; child; child = child?.parentElement) {
+                        if (child === this._ref.current!) {
+                            this._entered = true;
+                        }
+                    }
+                })}
+            >
+                <div className={`formattedTextBox-outer`} style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, }} onScroll={this.onscrolled} ref={this._scrollRef}>
+                    <div className={`formattedTextBox-inner${rounded}`} ref={this.createDropTarget}
+                        style={{
+                            padding: `${NumCast(this.layoutDoc._yMargin, this.props.yMargin || 0)}px  ${NumCast(this.layoutDoc._xMargin, this.props.xMargin || 0)}px`,
+                            pointerEvents: ((this.layoutDoc.isLinkButton || this.props.onClick) && !this.props.isSelected()) ? "none" : undefined
+                        }} />
+                </div>
+                {!this.props.Document._showSidebar ? (null) : this.sidebarWidthPercent === "0%" ?
+                    <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown} onClick={e => this.toggleSidebar()} /> :
+                    <div className={"formattedTextBox-sidebar" + (InkingControl.Instance.selectedTool !== InkTool.None ? "-inking" : "")}
+                        style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
+                        <CollectionFreeFormView {...this.props}
+                            PanelHeight={this.props.PanelHeight}
+                            PanelWidth={this.sidebarWidth}
+                            NativeHeight={returnZero}
+                            NativeWidth={returnZero}
+                            annotationsKey={this.annotationKey}
+                            isAnnotationOverlay={false}
+                            focus={this.props.focus}
+                            isSelected={this.props.isSelected}
+                            select={emptyFunction}
+                            active={this.annotationsActive}
+                            ContentScaling={returnOne}
+                            whenActiveChanged={this.whenActiveChanged}
+                            removeDocument={this.removeDocument}
+                            moveDocument={this.moveDocument}
+                            addDocument={this.addDocument}
+                            CollectionView={undefined}
+                            ScreenToLocalTransform={this.sidebarScreenToLocal}
+                            renderDepth={this.props.renderDepth + 1}
+                            ContainingCollectionDoc={this.props.ContainingCollectionDoc}>
+                        </CollectionFreeFormView>
+                        <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown} onClick={e => this.toggleSidebar()} />
+                    </div>}
+                {!this.props.Document._showAudio ? (null) :
+                    <div className="formattedTextBox-dictation"
+                        onPointerDown={e => {
+                            runInAction(() => this._recording = !this._recording);
+                            setTimeout(() => this._editorView!.focus(), 500);
+                            e.stopPropagation();
+                        }} >
+                        <FontAwesomeIcon className="formattedTExtBox-audioFont"
+                            style={{ color: this._recording ? "red" : "blue", opacity: this._recording ? 1 : 0.5, display: this.props.isSelected() ? "" : "none" }} icon={"microphone"} size="sm" />
+                    </div>}
+            </div>
+        );
+    }
+}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss b/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss
new file mode 100644
index 000000000..2dd63ec21
--- /dev/null
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss
@@ -0,0 +1,33 @@
+.FormattedTextBox-tooltip {
+    position: absolute;
+    pointer-events: none;
+    z-index: 20;
+    background: white;
+    border: 1px solid silver;
+    border-radius: 2px;
+    margin-bottom: 7px;
+    -webkit-transform: translateX(-50%);
+    transform: translateX(-50%);
+  }
+  .FormattedTextBox-tooltip: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;
+  }
+  .FormattedTextBox-tooltip: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: white;
+  }
\ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
new file mode 100644
index 000000000..f9e4c5210
--- /dev/null
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -0,0 +1,236 @@
+import { Mark, ResolvedPos } from "prosemirror-model";
+import { EditorState, Plugin } from "prosemirror-state";
+import { EditorView } from "prosemirror-view";
+import * as ReactDOM from 'react-dom';
+import { Doc, DocCastAsync } from "../../../../new_fields/Doc";
+import { Cast, FieldValue, NumCast } from "../../../../new_fields/Types";
+import { emptyFunction, returnEmptyString, returnFalse, Utils, emptyPath } from "../../../../Utils";
+import { DocServer } from "../../../DocServer";
+import { DocumentManager } from "../../../util/DocumentManager";
+import { schema } from "./schema_rts";
+import { Transform } from "../../../util/Transform";
+import { ContentFittingDocumentView } from "../ContentFittingDocumentView";
+import { FormattedTextBox } from "./FormattedTextBox";
+import './FormattedTextBoxComment.scss';
+import React = require("react");
+import { Docs } from "../../../documents/Documents";
+import wiki from "wikijs";
+import { DocumentType } from "../../../documents/DocumentTypes";
+
+export let formattedTextBoxCommentPlugin = new Plugin({
+    view(editorView) { return new FormattedTextBoxComment(editorView); }
+});
+export function findOtherUserMark(marks: Mark[]): Mark | undefined {
+    return marks.find(m => m.attrs.userid && m.attrs.userid !== Doc.CurrentUserEmail);
+}
+export function findUserMark(marks: Mark[]): Mark | undefined {
+    return marks.find(m => m.attrs.userid);
+}
+export function findLinkMark(marks: Mark[]): Mark | undefined {
+    return marks.find(m => m.type === schema.marks.link);
+}
+export function findStartOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: Mark[]) => Mark | undefined) {
+    let before = 0;
+    let nbef = rpos.nodeBefore;
+    while (nbef && finder(nbef.marks)) {
+        before += nbef.nodeSize;
+        rpos = view.state.doc.resolve(rpos.pos - nbef.nodeSize);
+        rpos && (nbef = rpos.nodeBefore);
+    }
+    return before;
+}
+export function findEndOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: Mark[]) => Mark | undefined) {
+    let after = 0;
+    let naft = rpos.nodeAfter;
+    while (naft && finder(naft.marks)) {
+        after += naft.nodeSize;
+        rpos = view.state.doc.resolve(rpos.pos + naft.nodeSize);
+        rpos && (naft = rpos.nodeAfter);
+    }
+    return after;
+}
+
+
+export class FormattedTextBoxComment {
+    static tooltip: HTMLElement;
+    static tooltipText: HTMLElement;
+    static tooltipInput: HTMLInputElement;
+    static start: number;
+    static end: number;
+    static mark: Mark;
+    static textBox: FormattedTextBox | undefined;
+    static linkDoc: Doc | undefined;
+    constructor(view: any) {
+        if (!FormattedTextBoxComment.tooltip) {
+            const root = document.getElementById("root");
+            FormattedTextBoxComment.tooltipInput = document.createElement("input");
+            FormattedTextBoxComment.tooltipInput.type = "checkbox";
+            FormattedTextBoxComment.tooltip = document.createElement("div");
+            FormattedTextBoxComment.tooltipText = document.createElement("div");
+            FormattedTextBoxComment.tooltipText.style.width = "100%";
+            FormattedTextBoxComment.tooltipText.style.height = "100%";
+            FormattedTextBoxComment.tooltipText.style.textOverflow = "ellipsis";
+            FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipText);
+            FormattedTextBoxComment.tooltip.className = "FormattedTextBox-tooltip";
+            FormattedTextBoxComment.tooltip.style.pointerEvents = "all";
+            FormattedTextBoxComment.tooltip.style.maxWidth = "350px";
+            FormattedTextBoxComment.tooltip.style.maxHeight = "250px";
+            FormattedTextBoxComment.tooltip.style.width = "100%";
+            FormattedTextBoxComment.tooltip.style.height = "100%";
+            FormattedTextBoxComment.tooltip.style.overflow = "hidden";
+            FormattedTextBoxComment.tooltip.style.display = "none";
+            FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipInput);
+            FormattedTextBoxComment.tooltip.onpointerdown = (e: PointerEvent) => {
+                const keep = e.target && (e.target as any).type === "checkbox" ? true : false;
+                const textBox = FormattedTextBoxComment.textBox;
+                if (FormattedTextBoxComment.linkDoc && !keep && textBox) {
+                    if (FormattedTextBoxComment.linkDoc.type !== DocumentType.LINK) {
+                        textBox.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "inTab" : "onRight");
+                    } else {
+                        DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document,
+                            (doc: Doc, followLinkLocation: string) => textBox.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation));
+                    }
+                } else if (textBox && (FormattedTextBoxComment.tooltipText as any).href) {
+                    textBox.props.addDocTab(Docs.Create.WebDocument((FormattedTextBoxComment.tooltipText as any).href, { title: (FormattedTextBoxComment.tooltipText as any).href, _width: 200, _height: 400 }), "onRight");
+                }
+                keep && textBox && FormattedTextBoxComment.start !== undefined && textBox.adoptAnnotation(
+                    FormattedTextBoxComment.start, FormattedTextBoxComment.end, FormattedTextBoxComment.mark);
+                e.stopPropagation();
+                e.preventDefault();
+            };
+            root && root.appendChild(FormattedTextBoxComment.tooltip);
+        }
+    }
+
+    public static Hide() {
+        FormattedTextBoxComment.textBox = undefined;
+        FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = "none");
+        ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText);
+    }
+    public static SetState(textBox: any, start: number, end: number, mark: Mark) {
+        FormattedTextBoxComment.textBox = textBox;
+        FormattedTextBoxComment.start = start;
+        FormattedTextBoxComment.end = end;
+        FormattedTextBoxComment.mark = mark;
+        FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = "");
+    }
+
+    static update(view: EditorView, lastState?: EditorState) {
+        const state = view.state;
+        // Don't do anything if the document/selection didn't change
+        if (lastState && lastState.doc.eq(state.doc) &&
+            lastState.selection.eq(state.selection)) {
+            return;
+        }
+        FormattedTextBoxComment.linkDoc = undefined;
+
+        const textBox = FormattedTextBoxComment.textBox;
+        if (!textBox || !textBox.props) {
+            return;
+        }
+        let set = "none";
+        let nbef = 0;
+        FormattedTextBoxComment.tooltipInput.style.display = "none";
+        FormattedTextBoxComment.tooltip.style.width = "";
+        FormattedTextBoxComment.tooltip.style.height = "";
+        (FormattedTextBoxComment.tooltipText as any).href = "";
+        FormattedTextBoxComment.tooltipText.style.whiteSpace = "";
+        FormattedTextBoxComment.tooltipText.style.overflow = "";
+        // this section checks to see if the insertion point is over text entered by a different user.  If so, it sets ths comment text to indicate the user and the modification date
+        if (state.selection.$from) {
+            nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark);
+            const naft = findEndOfMark(state.selection.$from, view, findOtherUserMark);
+            const noselection = view.state.selection.$from === view.state.selection.$to;
+            let child: any = null;
+            state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any, pos: number, parent: any) => !child && node.marks.length && (child = node));
+            const mark = child && findOtherUserMark(child.marks);
+            if (mark && child && (nbef || naft) && (!mark.attrs.opened || noselection)) {
+                FormattedTextBoxComment.SetState(FormattedTextBoxComment.textBox, state.selection.$from.pos - nbef, state.selection.$from.pos + naft, mark);
+            }
+            if (mark && child && ((nbef && naft) || !noselection)) {
+                FormattedTextBoxComment.tooltipText.textContent = mark.attrs.userid + " date=" + (new Date(mark.attrs.modified * 5000)).toDateString();
+                set = "";
+                FormattedTextBoxComment.tooltipInput.style.display = "";
+            }
+        }
+        // this checks if the selection is a hyperlink.  If so, it displays the target doc's text for internal links, and the url of the target for external links. 
+        if (set === "none" && state.selection.$from) {
+            nbef = findStartOfMark(state.selection.$from, view, findLinkMark);
+            const naft = findEndOfMark(state.selection.$from, view, findLinkMark);
+            let child: any = null;
+            state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any, pos: number, parent: any) => !child && node.marks.length && (child = node));
+            const mark = child && findLinkMark(child.marks);
+            if (mark && child && nbef && naft && mark.attrs.showPreview) {
+                FormattedTextBoxComment.tooltipText.textContent = "external => " + mark.attrs.href;
+                (FormattedTextBoxComment.tooltipText as any).href = mark.attrs.href;
+                if (mark.attrs.href.startsWith("https://en.wikipedia.org/wiki/")) {
+                    wiki().page(mark.attrs.href.replace("https://en.wikipedia.org/wiki/", "")).then(page => page.summary().then(summary => FormattedTextBoxComment.tooltipText.textContent = summary.substring(0, 500)));
+                } else {
+                    FormattedTextBoxComment.tooltipText.style.whiteSpace = "pre";
+                    FormattedTextBoxComment.tooltipText.style.overflow = "hidden";
+                }
+                if (mark.attrs.href.indexOf(Utils.prepend("/doc/")) === 0) {
+                    FormattedTextBoxComment.tooltipText.textContent = "target not found...";
+                    (FormattedTextBoxComment.tooltipText as any).href = "";
+                    const docTarget = mark.attrs.href.replace(Utils.prepend("/doc/"), "").split("?")[0];
+                    try {
+                        ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText);
+                    } catch (e) { }
+                    docTarget && DocServer.GetRefField(docTarget).then(async linkDoc => {
+                        if (linkDoc instanceof Doc) {
+                            (FormattedTextBoxComment.tooltipText as any).href = mark.attrs.href;
+                            FormattedTextBoxComment.linkDoc = linkDoc;
+                            const anchor = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(linkDoc.anchor1, Doc)), textBox.dataDoc) ? Cast(linkDoc.anchor2, Doc) : (Cast(linkDoc.anchor1, Doc)) || linkDoc);
+                            const target = anchor?.annotationOn ? await DocCastAsync(anchor.annotationOn) : anchor;
+                            if (anchor !== target && anchor && target) {
+                                target.scrollY = NumCast(anchor?.y);
+                            }
+                            if (target) {
+                                ReactDOM.render(<ContentFittingDocumentView
+                                    Document={target}
+                                    LibraryPath={emptyPath}
+                                    fitToBox={true}
+                                    moveDocument={returnFalse}
+                                    rootSelected={returnFalse}
+                                    getTransform={Transform.Identity}
+                                    active={returnFalse}
+                                    addDocument={returnFalse}
+                                    removeDocument={returnFalse}
+                                    addDocTab={returnFalse}
+                                    pinToPres={returnFalse}
+                                    dontRegisterView={true}
+                                    renderDepth={1}
+                                    PanelWidth={() => Math.min(350, NumCast(target._width, 350))}
+                                    PanelHeight={() => Math.min(250, NumCast(target._height, 250))}
+                                    focus={emptyFunction}
+                                    whenActiveChanged={returnFalse}
+                                />, FormattedTextBoxComment.tooltipText);
+                                FormattedTextBoxComment.tooltip.style.width = NumCast(target.width) ? `${NumCast(target.width)}` : "100%";
+                                FormattedTextBoxComment.tooltip.style.height = NumCast(target.height) ? `${NumCast(target.height)}` : "100%";
+                            }
+                            // let ext = (target && target.type !== DocumentType.PDFANNO && Doc.fieldExtensionDoc(target, "data")) || target; // try guessing that the target doc's data is in the 'data' field.  probably need an 'overviewLayout' and then just display the target Document ....
+                            // let text = ext && StrCast(ext.text);
+                            // ext && (FormattedTextBoxComment.tooltipText.textContent = (target && target.type === DocumentType.PDFANNO ? "Quoted from " : "") + "=> " + (text || StrCast(ext.title)));
+                        }
+                    });
+                }
+                set = "";
+            }
+        }
+        if (set !== "none") {
+            // These are in screen coordinates
+            // let start = view.coordsAtPos(state.selection.from), end = view.coordsAtPos(state.selection.to);
+            const start = view.coordsAtPos(state.selection.from - nbef), end = view.coordsAtPos(state.selection.from - nbef);
+            // The box in which the tooltip is positioned, to use as base
+            const box = (document.getElementsByClassName("mainView-container") as any)[0].getBoundingClientRect();
+            // Find a center-ish x position from the selection endpoints (when
+            // crossing lines, end may be more to the left)
+            const left = Math.max((start.left + end.left) / 2, start.left + 3);
+            FormattedTextBoxComment.tooltip.style.left = (left - box.left) + "px";
+            FormattedTextBoxComment.tooltip.style.bottom = (box.bottom - start.top) + "px";
+        }
+        FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = set);
+    }
+
+    destroy() { }
+}
diff --git a/src/client/views/nodes/formattedText/ImageResizeView.tsx b/src/client/views/nodes/formattedText/ImageResizeView.tsx
new file mode 100644
index 000000000..8f98da0fd
--- /dev/null
+++ b/src/client/views/nodes/formattedText/ImageResizeView.tsx
@@ -0,0 +1,138 @@
+import { NodeSelection } from "prosemirror-state";
+import { Doc } from "../../../../new_fields/Doc";
+import { DocServer } from "../../../DocServer";
+import { DocumentManager } from "../../../util/DocumentManager";
+import React = require("react");
+
+import { schema } from "./schema_rts";
+
+interface IImageResizeView {
+    node: any;
+    view: any;
+    getPos: any;
+    addDocTab: any;
+}
+
+export class ImageResizeView extends React.Component<IImageResizeView> {
+    constructor(props: IImageResizeView) {
+        super(props);
+    }
+
+    onClickImg = (e: any) => {
+        e.stopPropagation();
+        e.preventDefault();
+        if (this.props.view.state.selection.node && this.props.view.state.selection.node.type !== this.props.view.state.schema.nodes.image) {
+            this.props.view.dispatch(this.props.view.state.tr.setSelection(new NodeSelection(this.props.view.state.doc.resolve(this.props.view.state.selection.from - 2))));
+        }
+    }
+
+    onPointerDownImg = (e: any) => {
+        if (e.ctrlKey) {
+            e.preventDefault();
+            e.stopPropagation();
+            DocServer.GetRefField(this.props.node.attrs.docid).then(async linkDoc =>
+                (linkDoc instanceof Doc) &&
+                DocumentManager.Instance.FollowLink(linkDoc, this.props.view.state.schema.Document,
+                    document => this.props.addDocTab(document, this.props.node.attrs.location ? this.props.node.attrs.location : "inTab"), false));
+        }
+    }
+
+    onPointerDownHandle = (e: any) => {
+        e.preventDefault();
+        e.stopPropagation();
+        const elementImage = document.getElementById("imageId") as HTMLElement;
+        const wid = Number(getComputedStyle(elementImage).width.replace(/px/, ""));
+        const hgt = Number(getComputedStyle(elementImage).height.replace(/px/, ""));
+        const startX = e.pageX;
+        const startWidth = parseFloat(this.props.node.attrs.width);
+
+        const onpointermove = (e: any) => {
+            const elementOuter = document.getElementById("outerId") as HTMLElement;
+
+            const currentX = e.pageX;
+            const diffInPx = currentX - startX;
+            elementOuter.style.width = `${startWidth + diffInPx}`;
+            elementOuter.style.height = `${(startWidth + diffInPx) * hgt / wid}`;
+        };
+
+        const onpointerup = () => {
+            document.removeEventListener("pointermove", onpointermove);
+            document.removeEventListener("pointerup", onpointerup);
+            const pos = this.props.view.state.selection.from;
+            const elementOuter = document.getElementById("outerId") as HTMLElement;
+            this.props.view.dispatch(this.props.view.state.tr.setNodeMarkup(this.props.getPos(), null, { ...this.props.node.attrs, width: elementOuter.style.width, height: elementOuter.style.height }));
+            this.props.view.dispatch(this.props.view.state.tr.setSelection(new NodeSelection(this.props.view.state.doc.resolve(pos))));
+        };
+
+        document.addEventListener("pointermove", onpointermove);
+        document.addEventListener("pointerup", onpointerup);
+    }
+
+    selectNode() {
+        const elementImage = document.getElementById("imageId") as HTMLElement;
+        const elementHandle = document.getElementById("handleId") as HTMLElement;
+
+        elementImage.classList.add("ProseMirror-selectednode");
+        elementHandle.style.display = "";
+    }
+
+    deselectNode() {
+        const elementImage = document.getElementById("imageId") as HTMLElement;
+        const elementHandle = document.getElementById("handleId") as HTMLElement;
+
+        elementImage.classList.remove("ProseMirror-selectednode");
+        elementHandle.style.display = "none";
+    }
+
+
+    render() {
+
+        const outerStyle = {
+            width: this.props.node.attrs.width,
+            height: this.props.node.attrs.height,
+            display: "inline-block",
+            overflow: "hidden",
+            float: this.props.node.attrs.float
+        };
+
+        const imageStyle = {
+            width: "100%",
+        };
+
+        const handleStyle = {
+            position: "absolute",
+            width: "20px",
+            heiht: "20px",
+            backgroundColor: "blue",
+            borderRadius: "15px",
+            display: "none",
+            bottom: "-10px",
+            right: "-10px"
+
+        };
+
+
+
+        return (
+            <div id="outer"
+                style={outerStyle}
+            >
+                <img
+                    id="imageId"
+                    style={imageStyle}
+                    src={this.props.node.src}
+                    onClick={this.onClickImg}
+                    onPointerDown={this.onPointerDownImg}
+
+                >
+                </img>
+                <span
+                    id="handleId"
+                    onPointerDown={this.onPointerDownHandle}
+                >
+
+                </span>
+            </div >
+        );
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/ParagraphNodeSpec.ts b/src/client/views/nodes/formattedText/ParagraphNodeSpec.ts
new file mode 100644
index 000000000..d80e64634
--- /dev/null
+++ b/src/client/views/nodes/formattedText/ParagraphNodeSpec.ts
@@ -0,0 +1,143 @@
+import clamp from '../../../util/clamp';
+import convertToCSSPTValue from '../../../util/convertToCSSPTValue';
+import toCSSLineSpacing from '../../../util/toCSSLineSpacing';
+import { Node, DOMOutputSpec } from 'prosemirror-model';
+
+//import type { NodeSpec } from './Types';
+type NodeSpec = {
+    attrs?: { [key: string]: any },
+    content?: string,
+    draggable?: boolean,
+    group?: string,
+    inline?: boolean,
+    name?: string,
+    parseDOM?: Array<any>,
+    toDOM?: (node: any) => DOMOutputSpec,
+};
+
+// This assumes that every 36pt maps to one indent level.
+export const INDENT_MARGIN_PT_SIZE = 36;
+export const MIN_INDENT_LEVEL = 0;
+export const MAX_INDENT_LEVEL = 7;
+export const ATTRIBUTE_INDENT = 'data-indent';
+
+export const EMPTY_CSS_VALUE = new Set(['', '0%', '0pt', '0px']);
+
+const ALIGN_PATTERN = /(left|right|center|justify)/;
+
+// https://github.com/ProseMirror/prosemirror-schema-basic/blob/master/src/schema-basic.js
+// :: NodeSpec A plain paragraph textblock. Represented in the DOM
+// as a `<p>` element.
+const ParagraphNodeSpec: NodeSpec = {
+    attrs: {
+        align: { default: null },
+        color: { default: null },
+        id: { default: null },
+        indent: { default: null },
+        inset: { default: null },
+        lineSpacing: { default: null },
+        // TODO: Add UI to let user edit / clear padding.
+        paddingBottom: { default: null },
+        // TODO: Add UI to let user edit / clear padding.
+        paddingTop: { default: null },
+    },
+    content: 'inline*',
+    group: 'block',
+    parseDOM: [{ tag: 'p', getAttrs }],
+    toDOM,
+};
+
+function getAttrs(dom: HTMLElement): Object {
+    const {
+        lineHeight,
+        textAlign,
+        marginLeft,
+        paddingTop,
+        paddingBottom,
+    } = dom.style;
+
+    let align = dom.getAttribute('align') || textAlign || '';
+    align = ALIGN_PATTERN.test(align) ? align : "";
+
+    let indent = parseInt(dom.getAttribute(ATTRIBUTE_INDENT) || "", 10);
+
+    if (!indent && marginLeft) {
+        indent = convertMarginLeftToIndentValue(marginLeft);
+    }
+
+    indent = indent || MIN_INDENT_LEVEL;
+
+    const lineSpacing = lineHeight ? toCSSLineSpacing(lineHeight) : null;
+
+    const id = dom.getAttribute('id') || '';
+    return { align, indent, lineSpacing, paddingTop, paddingBottom, id };
+}
+
+function toDOM(node: Node): DOMOutputSpec {
+    const {
+        align,
+        indent,
+        inset,
+        lineSpacing,
+        paddingTop,
+        paddingBottom,
+        id,
+    } = node.attrs;
+    const attrs: { [key: string]: any } | null = {};
+
+    let style = '';
+    if (align && align !== 'left') {
+        style += `text-align: ${align};`;
+    }
+
+    if (lineSpacing) {
+        const cssLineSpacing = toCSSLineSpacing(lineSpacing);
+        style +=
+            `line-height: ${cssLineSpacing};` +
+            // This creates the local css variable `--czi-content-line-height`
+            // that its children may apply.
+            `--czi-content-line-height: ${cssLineSpacing}`;
+    }
+
+    if (paddingTop && !EMPTY_CSS_VALUE.has(paddingTop)) {
+        style += `padding-top: ${paddingTop};`;
+    }
+
+    if (paddingBottom && !EMPTY_CSS_VALUE.has(paddingBottom)) {
+        style += `padding-bottom: ${paddingBottom};`;
+    }
+
+    if (indent) {
+        style += `text-indent: ${indent}; padding-left: ${indent < 0 ? -indent : undefined};`;
+    }
+
+    if (inset) {
+        style += `margin-left: ${inset}; margin-right: ${inset};`;
+    }
+
+    style && (attrs.style = style);
+
+    if (indent) {
+        attrs[ATTRIBUTE_INDENT] = String(indent);
+    }
+
+    if (id) {
+        attrs.id = id;
+    }
+
+    return ['p', attrs, 0];
+}
+
+export const toParagraphDOM = toDOM;
+export const getParagraphNodeAttrs = getAttrs;
+
+export function convertMarginLeftToIndentValue(marginLeft: string): number {
+    const ptValue = convertToCSSPTValue(marginLeft);
+    return clamp(
+        MIN_INDENT_LEVEL,
+        Math.floor(ptValue / INDENT_MARGIN_PT_SIZE),
+        MAX_INDENT_LEVEL
+    );
+}
+
+export default ParagraphNodeSpec;
\ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
new file mode 100644
index 000000000..a0b02880e
--- /dev/null
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -0,0 +1,241 @@
+import { chainCommands, exitCode, joinDown, joinUp, lift, selectParentNode, setBlockType, splitBlockKeepMarks, toggleMark, wrapIn } from "prosemirror-commands";
+import { redo, undo } from "prosemirror-history";
+import { undoInputRule } from "prosemirror-inputrules";
+import { Schema } from "prosemirror-model";
+import { liftListItem, sinkListItem } from "./prosemirrorPatches.js";
+import { splitListItem, wrapInList, } from "prosemirror-schema-list";
+import { EditorState, Transaction, TextSelection } from "prosemirror-state";
+import { SelectionManager } from "../../../util/SelectionManager";
+import { Docs } from "../../../documents/Documents";
+import { NumCast, BoolCast, Cast, StrCast } from "../../../../new_fields/Types";
+import { Doc } from "../../../../new_fields/Doc";
+import { FormattedTextBox } from "./FormattedTextBox";
+import { Id } from "../../../../new_fields/FieldSymbols";
+
+const mac = typeof navigator !== "undefined" ? /Mac/.test(navigator.platform) : false;
+
+export type KeyMap = { [key: string]: any };
+
+export let updateBullets = (tx2: Transaction, schema: Schema, mapStyle?: string) => {
+    let fontSize: number | undefined = undefined;
+    tx2.doc.descendants((node: any, offset: any, index: any) => {
+        if (node.type === schema.nodes.ordered_list || node.type === schema.nodes.list_item) {
+            const path = (tx2.doc.resolve(offset) as any).path;
+            let depth = Array.from(path).reduce((p: number, c: any) => p + (c.hasOwnProperty("type") && c.type === schema.nodes.ordered_list ? 1 : 0), 0);
+            if (node.type === schema.nodes.ordered_list) depth++;
+            fontSize = depth === 1 && node.attrs.setFontSize ? Number(node.attrs.setFontSize) : fontSize;
+            const fsize = fontSize && node.type === schema.nodes.ordered_list ? Math.max(6, fontSize - (depth - 1) * 4) : undefined;
+            tx2.setNodeMarkup(offset, node.type, { ...node.attrs, mapStyle: mapStyle ? mapStyle : node.attrs.mapStyle, bulletStyle: depth, inheritedFontSize: fsize }, node.marks);
+        }
+    });
+    return tx2;
+};
+export default function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKeys?: KeyMap): KeyMap {
+    const keys: { [key: string]: any } = {};
+
+    function bind(key: string, cmd: any) {
+        if (mapKeys) {
+            const mapped = mapKeys[key];
+            if (mapped === false) return;
+            if (mapped) key = mapped;
+        }
+        keys[key] = cmd;
+    }
+
+    bind("Mod-z", undo);
+    bind("Shift-Mod-z", redo);
+    bind("Backspace", undoInputRule);
+
+    !mac && bind("Mod-y", redo);
+
+    bind("Alt-ArrowUp", joinUp);
+    bind("Alt-ArrowDown", joinDown);
+    bind("Mod-BracketLeft", lift);
+    bind("Escape", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
+        dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from)));
+        (document.activeElement as any).blur?.();
+        SelectionManager.DeselectAll();
+    });
+
+    bind("Mod-b", toggleMark(schema.marks.strong));
+    bind("Mod-B", toggleMark(schema.marks.strong));
+
+    bind("Mod-e", toggleMark(schema.marks.em));
+    bind("Mod-E", toggleMark(schema.marks.em));
+
+    bind("Mod-u", toggleMark(schema.marks.underline));
+    bind("Mod-U", toggleMark(schema.marks.underline));
+
+    bind("Mod-`", toggleMark(schema.marks.code));
+
+    bind("Ctrl-.", wrapInList(schema.nodes.bullet_list));
+
+    bind("Ctrl-n", wrapInList(schema.nodes.ordered_list));
+
+    bind("Ctrl->", wrapIn(schema.nodes.blockquote));
+
+    // bind("^", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
+    //     let newNode = schema.nodes.footnote.create({});
+    //     if (dispatch && state.selection.from === state.selection.to) {
+    //         let tr = state.tr;
+    //         tr.replaceSelectionWith(newNode); // replace insertion with a footnote.
+    //         dispatch(tr.setSelection(new NodeSelection( // select the footnote node to open its display
+    //             tr.doc.resolve(  // get the location of the footnote node by subtracting the nodesize of the footnote from the current insertion point anchor (which will be immediately after the footnote node)
+    //                 tr.selection.anchor - tr.selection.$anchor.nodeBefore!.nodeSize))));
+    //         return true;
+    //     }
+    //     return false;
+    // });
+
+
+    const cmd = chainCommands(exitCode, (state, dispatch) => {
+        if (dispatch) {
+            dispatch(state.tr.replaceSelectionWith(schema.nodes.hard_break.create()).scrollIntoView());
+            return true;
+        }
+        return false;
+    });
+    bind("Mod-Enter", cmd);
+    bind("Shift-Enter", cmd);
+    mac && bind("Ctrl-Enter", cmd);
+
+
+    bind("Shift-Ctrl-0", setBlockType(schema.nodes.paragraph));
+
+    bind("Shift-Ctrl-\\", setBlockType(schema.nodes.code_block));
+
+    for (let i = 1; i <= 6; i++) {
+        bind("Shift-Ctrl-" + i, setBlockType(schema.nodes.heading, { level: i }));
+    }
+
+    const hr = schema.nodes.horizontal_rule;
+    bind("Mod-_", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
+        dispatch(state.tr.replaceSelectionWith(hr.create()).scrollIntoView());
+        return true;
+    });
+
+    bind("Tab", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
+        const ref = state.selection;
+        const range = ref.$from.blockRange(ref.$to);
+        const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
+        if (!sinkListItem(schema.nodes.list_item)(state, (tx2: Transaction) => {
+            const tx3 = updateBullets(tx2, schema);
+            marks && tx3.ensureMarks([...marks]);
+            marks && tx3.setStoredMarks([...marks]);
+            dispatch(tx3);
+        })) { // couldn't sink into an existing list, so wrap in a new one
+            const newstate = state.applyTransaction(state.tr.setSelection(TextSelection.create(state.doc, range!.start, range!.end)));
+            if (!wrapInList(schema.nodes.ordered_list)(newstate.state, (tx2: Transaction) => {
+                const tx3 = updateBullets(tx2, schema);
+                // when promoting to a list, assume list will format things so don't copy the stored marks.
+                marks && tx3.ensureMarks([...marks]);
+                marks && tx3.setStoredMarks([...marks]);
+                dispatch(tx3);
+            })) {
+                console.log("bullet promote fail");
+            }
+        }
+    });
+
+    bind("Shift-Tab", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
+        const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
+
+        if (!liftListItem(schema.nodes.list_item)(state.tr, (tx2: Transaction) => {
+            const tx3 = updateBullets(tx2, schema);
+            marks && tx3.ensureMarks([...marks]);
+            marks && tx3.setStoredMarks([...marks]);
+            dispatch(tx3);
+        })) {
+            console.log("bullet demote fail");
+        }
+    });
+    bind("Ctrl-Enter", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
+        const layoutDoc = props.Document;
+        const originalDoc = layoutDoc.rootDocument || layoutDoc;
+        if (originalDoc instanceof Doc) {
+            const layoutKey = StrCast(originalDoc.layoutKey);
+            const newDoc = Docs.Create.TextDocument("", {
+                layout: Cast(originalDoc.layout, Doc, null) || FormattedTextBox.DefaultLayout,
+                layoutKey,
+                _singleLine: BoolCast(originalDoc._singleLine),
+                x: NumCast(originalDoc.x), y: NumCast(originalDoc.y) + NumCast(originalDoc._height) + 10, _width: NumCast(layoutDoc._width), _height: NumCast(layoutDoc._height)
+            });
+            if (layoutKey !== "layout" && originalDoc[layoutKey] instanceof Doc) {
+                newDoc[layoutKey] = originalDoc[layoutKey];
+            }
+            FormattedTextBox.SelectOnLoad = newDoc[Id];
+            props.addDocument(newDoc);
+        }
+    });
+
+    const splitMetadata = (marks: any, tx: Transaction) => {
+        marks && tx.ensureMarks(marks.filter((val: any) => val.type !== schema.marks.metadata && val.type !== schema.marks.metadataKey && val.type !== schema.marks.metadataVal));
+        marks && tx.setStoredMarks(marks.filter((val: any) => val.type !== schema.marks.metadata && val.type !== schema.marks.metadataKey && val.type !== schema.marks.metadataVal));
+        return tx;
+    };
+    const addTextOnRight = (force: boolean) => {
+        const layoutDoc = props.Document;
+        const originalDoc = layoutDoc.rootDocument || layoutDoc;
+        if (force || props.Document._singleLine) {
+            const layoutKey = StrCast(originalDoc.layoutKey);
+            const newDoc = Docs.Create.TextDocument("", {
+                layout: Cast(originalDoc.layout, Doc, null) || FormattedTextBox.DefaultLayout,
+                layoutKey,
+                _singleLine: BoolCast(originalDoc._singleLine),
+                x: NumCast(originalDoc.x) + NumCast(originalDoc._width) + 10, y: NumCast(originalDoc.y), _width: NumCast(layoutDoc._width), _height: NumCast(layoutDoc._height)
+            });
+            if (layoutKey !== "layout" && originalDoc[layoutKey] instanceof Doc) {
+                newDoc[layoutKey] = originalDoc[layoutKey];
+            }
+            FormattedTextBox.SelectOnLoad = newDoc[Id];
+            props.addDocument(newDoc);
+            return true;
+        }
+        return false;
+    };
+    bind("Alt-Enter", (state: EditorState<S>, dispatch: (tx: Transaction<Schema<any, any>>) => void) => {
+        return addTextOnRight(true);
+    });
+    bind("Enter", (state: EditorState<S>, dispatch: (tx: Transaction<Schema<any, any>>) => void) => {
+        if (addTextOnRight(false)) return true;
+        const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
+        if (!splitListItem(schema.nodes.list_item)(state, dispatch)) {
+            if (!splitBlockKeepMarks(state, (tx3: Transaction) => {
+                splitMetadata(marks, tx3);
+                if (!liftListItem(schema.nodes.list_item)(tx3, dispatch as ((tx: Transaction<Schema<any, any>>) => void))) {
+                    dispatch(tx3);
+                }
+            })) {
+                return false;
+            }
+        }
+        return true;
+    });
+    bind("Space", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
+        const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
+        dispatch(splitMetadata(marks, state.tr));
+        return false;
+    });
+    bind(":", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
+        const range = state.selection.$from.blockRange(state.selection.$to, (node: any) => {
+            return !node.marks || !node.marks.find((m: any) => m.type === schema.marks.metadata);
+        });
+        const path = (state.doc.resolve(state.selection.from - 1) as any).path;
+        const spaceSeparator = path[path.length - 3].childCount > 1 ? 0 : -1;
+        const anchor = range!.end - path[path.length - 3].lastChild.nodeSize + spaceSeparator;
+        if (anchor >= 0) {
+            const textsel = TextSelection.create(state.doc, anchor, range!.end);
+            const text = range ? state.doc.textBetween(textsel.from, textsel.to) : "";
+            let whitespace = text.length - 1;
+            for (; whitespace >= 0 && text[whitespace] !== " "; whitespace--) { }
+            if (text.endsWith(":")) {
+                dispatch(state.tr.addMark(textsel.from + whitespace + 1, textsel.to, schema.marks.metadata.create() as any).
+                    addMark(textsel.from + whitespace + 1, textsel.to - 2, schema.marks.metadataKey.create() as any));
+            }
+        }
+        return false;
+    });
+
+
+    return keys;
+}
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.scss b/src/client/views/nodes/formattedText/RichTextMenu.scss
new file mode 100644
index 000000000..36da769c3
--- /dev/null
+++ b/src/client/views/nodes/formattedText/RichTextMenu.scss
@@ -0,0 +1,121 @@
+@import "../../globalCssVariables";
+
+.button-dropdown-wrapper {
+    position: relative;
+
+    .dropdown-button {
+        width: 15px;
+        padding-left: 5px;
+        padding-right: 5px;
+    }
+
+    .dropdown-button-combined {
+        width: 50px;
+        display: flex;
+        justify-content: space-between;
+
+        svg:nth-child(2) {
+            margin-top: 2px;
+        }
+    }
+
+    .dropdown {
+        position: absolute;
+        top: 35px;
+        left: 0;
+        background-color: #323232;
+        color: $light-color-secondary;
+        border: 1px solid #4d4d4d;
+        border-radius: 0 6px 6px 6px;
+        box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25);
+        min-width: 150px;
+        padding: 5px;
+        font-size: 12px;
+        z-index: 10001;
+
+        button {
+            background-color: #323232;
+            border: 1px solid black;
+            border-radius: 1px;
+            padding: 6px;
+            margin: 5px 0;
+            font-size: 10px;
+
+            &:hover {
+                background-color: black;
+            }
+
+            &:last-child {
+                margin-bottom: 0;
+            }
+        }
+    }
+
+    input {
+        color: black;
+    }
+}
+
+.link-menu {
+    .divider {
+        background-color: white;
+        height: 1px;
+        width: 100%;
+    }
+}
+
+.color-preview-button {
+    .color-preview {
+        width: 100%;
+        height: 3px;
+        margin-top: 3px;
+    }
+}
+
+.color-wrapper {
+    display: flex;
+    flex-wrap: wrap;
+    justify-content: space-between;
+
+    button.color-button {
+        width: 20px;
+        height: 20px;
+        border-radius: 15px !important;
+        margin: 3px;
+        border: 2px solid transparent !important;
+        padding: 3px;
+
+        &.active {
+            border: 2px solid white !important;
+        }
+    }
+}
+
+select {
+    background-color: #323232;
+    color: white;
+    border: 1px solid black;
+    // border-top: none;
+    // border-bottom: none;
+    font-size: 12px;
+    height: 100%;
+    margin-right: 3px;
+
+    &:focus,
+    &:hover {
+        background-color: black;
+    }
+
+    &::-ms-expand {
+        color: white;
+    }
+}
+
+.row-2 {
+    display: flex;
+    justify-content: space-between;
+
+    >div {
+        display: flex;
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
new file mode 100644
index 000000000..cc04e0d6d
--- /dev/null
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -0,0 +1,875 @@
+import React = require("react");
+import AntimodeMenu from "../../AntimodeMenu";
+import { observable, action, } from "mobx";
+import { observer } from "mobx-react";
+import { Mark, MarkType, Node as ProsNode, NodeType, ResolvedPos, Schema } from "prosemirror-model";
+import { schema } from "./schema_rts";
+import { EditorView } from "prosemirror-view";
+import { EditorState, NodeSelection, TextSelection } from "prosemirror-state";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
+import { faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSubscript, faSuperscript, faIndent, faEyeDropper, faCaretDown, faPalette, faHighlighter, faLink, faPaintRoller, faSleigh } from "@fortawesome/free-solid-svg-icons";
+import { updateBullets } from "./ProsemirrorExampleTransfer";
+import { FieldViewProps } from "../FieldView";
+import { Cast, StrCast } from "../../../../new_fields/Types";
+import { FormattedTextBoxProps } from "./FormattedTextBox";
+import { unimplementedFunction, Utils } from "../../../../Utils";
+import { wrapInList } from "prosemirror-schema-list";
+import { PastelSchemaPalette, DarkPastelSchemaPalette } from '../../../../new_fields/SchemaHeaderField';
+import "./RichTextMenu.scss";
+import { DocServer } from "../../../DocServer";
+import { Doc } from "../../../../new_fields/Doc";
+import { SelectionManager } from "../../../util/SelectionManager";
+import { LinkManager } from "../../../util/LinkManager";
+const { toggleMark, setBlockType } = require("prosemirror-commands");
+
+library.add(faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper, faCaretDown, faPalette, faHighlighter, faLink, faPaintRoller);
+
+@observer
+export default class RichTextMenu extends AntimodeMenu {
+    static Instance: RichTextMenu;
+    public overMenu: boolean = false; // kind of hacky way to prevent selects not being selectable
+
+    private view?: EditorView;
+    public editorProps: FieldViewProps & FormattedTextBoxProps | undefined;
+
+    public _brushMap: Map<string, Set<Mark>> = new Map();
+    private fontSizeOptions: { mark: Mark | null, title: string, label: string, command: any, hidden?: boolean, style?: {} }[];
+    private fontFamilyOptions: { mark: Mark | null, title: string, label: string, command: any, hidden?: boolean, style?: {} }[];
+    private listTypeOptions: { node: NodeType | any | null, title: string, label: string, command: any, style?: {} }[];
+    private fontColors: (string | undefined)[];
+    private highlightColors: (string | undefined)[];
+
+    @observable private collapsed: boolean = false;
+    @observable private boldActive: boolean = false;
+    @observable private italicsActive: boolean = false;
+    @observable private underlineActive: boolean = false;
+    @observable private strikethroughActive: boolean = false;
+    @observable private subscriptActive: boolean = false;
+    @observable private superscriptActive: boolean = false;
+
+    @observable private activeFontSize: string = "";
+    @observable private activeFontFamily: string = "";
+    @observable private activeListType: string = "";
+
+    @observable private brushIsEmpty: boolean = true;
+    @observable private brushMarks: Set<Mark> = new Set();
+    @observable private showBrushDropdown: boolean = false;
+
+    @observable private activeFontColor: string = "black";
+    @observable private showColorDropdown: boolean = false;
+
+    @observable private activeHighlightColor: string = "transparent";
+    @observable private showHighlightDropdown: boolean = false;
+
+    @observable private currentLink: string | undefined = "";
+    @observable private showLinkDropdown: boolean = false;
+
+    constructor(props: Readonly<{}>) {
+        super(props);
+        RichTextMenu.Instance = this;
+        this._canFade = false;
+
+        this.fontSizeOptions = [
+            { mark: schema.marks.pFontSize.create({ fontSize: 7 }), title: "Set font size", label: "7pt", command: this.changeFontSize },
+            { mark: schema.marks.pFontSize.create({ fontSize: 8 }), title: "Set font size", label: "8pt", command: this.changeFontSize },
+            { mark: schema.marks.pFontSize.create({ fontSize: 9 }), title: "Set font size", label: "9pt", command: this.changeFontSize },
+            { mark: schema.marks.pFontSize.create({ fontSize: 10 }), title: "Set font size", label: "10pt", command: this.changeFontSize },
+            { mark: schema.marks.pFontSize.create({ fontSize: 12 }), title: "Set font size", label: "12pt", command: this.changeFontSize },
+            { mark: schema.marks.pFontSize.create({ fontSize: 14 }), title: "Set font size", label: "14pt", command: this.changeFontSize },
+            { mark: schema.marks.pFontSize.create({ fontSize: 16 }), title: "Set font size", label: "16pt", command: this.changeFontSize },
+            { mark: schema.marks.pFontSize.create({ fontSize: 18 }), title: "Set font size", label: "18pt", command: this.changeFontSize },
+            { mark: schema.marks.pFontSize.create({ fontSize: 20 }), title: "Set font size", label: "20pt", command: this.changeFontSize },
+            { mark: schema.marks.pFontSize.create({ fontSize: 24 }), title: "Set font size", label: "24pt", command: this.changeFontSize },
+            { mark: schema.marks.pFontSize.create({ fontSize: 32 }), title: "Set font size", label: "32pt", command: this.changeFontSize },
+            { mark: schema.marks.pFontSize.create({ fontSize: 48 }), title: "Set font size", label: "48pt", command: this.changeFontSize },
+            { mark: schema.marks.pFontSize.create({ fontSize: 72 }), title: "Set font size", label: "72pt", command: this.changeFontSize },
+            { mark: null, title: "", label: "various", command: unimplementedFunction, hidden: true },
+            { mark: null, title: "", label: "13pt", command: unimplementedFunction, hidden: true }, // this is here because the default size is 13, but there is no actual 13pt option
+        ];
+
+        this.fontFamilyOptions = [
+            { mark: schema.marks.pFontFamily.create({ family: "Times New Roman" }), title: "Set font family", label: "Times New Roman", command: this.changeFontFamily, style: { fontFamily: "Times New Roman" } },
+            { mark: schema.marks.pFontFamily.create({ family: "Arial" }), title: "Set font family", label: "Arial", command: this.changeFontFamily, style: { fontFamily: "Arial" } },
+            { mark: schema.marks.pFontFamily.create({ family: "Georgia" }), title: "Set font family", label: "Georgia", command: this.changeFontFamily, style: { fontFamily: "Georgia" } },
+            { mark: schema.marks.pFontFamily.create({ family: "Comic Sans MS" }), title: "Set font family", label: "Comic Sans MS", command: this.changeFontFamily, style: { fontFamily: "Comic Sans MS" } },
+            { mark: schema.marks.pFontFamily.create({ family: "Tahoma" }), title: "Set font family", label: "Tahoma", command: this.changeFontFamily, style: { fontFamily: "Tahoma" } },
+            { mark: schema.marks.pFontFamily.create({ family: "Impact" }), title: "Set font family", label: "Impact", command: this.changeFontFamily, style: { fontFamily: "Impact" } },
+            { mark: schema.marks.pFontFamily.create({ family: "Crimson Text" }), title: "Set font family", label: "Crimson Text", command: this.changeFontFamily, style: { fontFamily: "Crimson Text" } },
+            { mark: null, title: "", label: "various", command: unimplementedFunction, hidden: true },
+            // { mark: null, title: "", label: "default", command: unimplementedFunction, hidden: true },
+        ];
+
+        this.listTypeOptions = [
+            { node: schema.nodes.ordered_list.create({ mapStyle: "bullet" }), title: "Set list type", label: ":", command: this.changeListType },
+            { node: schema.nodes.ordered_list.create({ mapStyle: "decimal" }), title: "Set list type", label: "1.1", command: this.changeListType },
+            { node: schema.nodes.ordered_list.create({ mapStyle: "multi" }), title: "Set list type", label: "1.A", command: this.changeListType },
+            { node: undefined, title: "Set list type", label: "Remove", command: this.changeListType },
+        ];
+
+        this.fontColors = [
+            DarkPastelSchemaPalette.get("pink2"),
+            DarkPastelSchemaPalette.get("purple4"),
+            DarkPastelSchemaPalette.get("bluegreen1"),
+            DarkPastelSchemaPalette.get("yellow4"),
+            DarkPastelSchemaPalette.get("red2"),
+            DarkPastelSchemaPalette.get("bluegreen7"),
+            DarkPastelSchemaPalette.get("bluegreen5"),
+            DarkPastelSchemaPalette.get("orange1"),
+            "#757472",
+            "#000"
+        ];
+
+        this.highlightColors = [
+            PastelSchemaPalette.get("pink2"),
+            PastelSchemaPalette.get("purple4"),
+            PastelSchemaPalette.get("bluegreen1"),
+            PastelSchemaPalette.get("yellow4"),
+            PastelSchemaPalette.get("red2"),
+            PastelSchemaPalette.get("bluegreen7"),
+            PastelSchemaPalette.get("bluegreen5"),
+            PastelSchemaPalette.get("orange1"),
+            "white",
+            "transparent"
+        ];
+    }
+
+    @action
+    changeView(view: EditorView) {
+        this.view = view;
+    }
+
+    update(view: EditorView, lastState: EditorState | undefined) {
+        this.updateFromDash(view, lastState, this.editorProps);
+    }
+
+
+    public MakeLinkToSelection = (linkDocId: string, title: string, location: string, targetDocId: string): string => {
+        if (this.view) {
+            const link = this.view.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + linkDocId), title: title, location: location, linkId: linkDocId, targetId: targetDocId });
+            this.view.dispatch(this.view.state.tr.removeMark(this.view.state.selection.from, this.view.state.selection.to, this.view.state.schema.marks.link).
+                addMark(this.view.state.selection.from, this.view.state.selection.to, link));
+            return this.view.state.selection.$from.nodeAfter?.text || "";
+        }
+        return "";
+    }
+
+    @action
+    public async updateFromDash(view: EditorView, lastState: EditorState | undefined, props: any) {
+        if (!view) {
+            console.log("no editor?  why?");
+            return;
+        }
+        this.view = view;
+        const state = view.state;
+        props && (this.editorProps = props);
+
+        // Don't do anything if the document/selection didn't change
+        if (lastState && lastState.doc.eq(state.doc) && lastState.selection.eq(state.selection)) return;
+
+        // update active marks
+        const activeMarks = this.getActiveMarksOnSelection();
+        this.setActiveMarkButtons(activeMarks);
+
+        // update active font family and size
+        const active = this.getActiveFontStylesOnSelection();
+        const activeFamilies = active && active.get("families");
+        const activeSizes = active && active.get("sizes");
+
+        this.activeFontFamily = !activeFamilies || activeFamilies.length === 0 ? "Arial" : activeFamilies.length === 1 ? String(activeFamilies[0]) : "various";
+        this.activeFontSize = !activeSizes || activeSizes.length === 0 ? "13pt" : activeSizes.length === 1 ? String(activeSizes[0]) + "pt" : "various";
+
+        // update link in current selection
+        const targetTitle = await this.getTextLinkTargetTitle();
+        this.setCurrentLink(targetTitle);
+    }
+
+    setMark = (mark: Mark, state: EditorState<any>, dispatch: any) => {
+        if (mark) {
+            const node = (state.selection as NodeSelection).node;
+            if (node?.type === schema.nodes.ordered_list) {
+                let attrs = node.attrs;
+                if (mark.type === schema.marks.pFontFamily) attrs = { ...attrs, setFontFamily: mark.attrs.family };
+                if (mark.type === schema.marks.pFontSize) attrs = { ...attrs, setFontSize: mark.attrs.fontSize };
+                if (mark.type === schema.marks.pFontColor) attrs = { ...attrs, setFontColor: mark.attrs.color };
+                const tr = updateBullets(state.tr.setNodeMarkup(state.selection.from, node.type, attrs), state.schema);
+                dispatch(tr.setSelection(new NodeSelection(tr.doc.resolve(state.selection.from))));
+            } else {
+                toggleMark(mark.type, mark.attrs)(state, (tx: any) => {
+                    const { from, $from, to, empty } = tx.selection;
+                    if (!tx.doc.rangeHasMark(from, to, mark.type)) {
+                        toggleMark(mark.type, mark.attrs)({ tr: tx, doc: tx.doc, selection: tx.selection, storedMarks: tx.storedMarks }, dispatch);
+                    } else dispatch(tx);
+                });
+            }
+        }
+    }
+
+    // finds font sizes and families in selection
+    getActiveFontStylesOnSelection() {
+        if (!this.view) return;
+
+        const activeFamilies: string[] = [];
+        const activeSizes: string[] = [];
+        const state = this.view.state;
+        const pos = this.view.state.selection.$from;
+        const ref_node = this.reference_node(pos);
+        if (ref_node && ref_node !== this.view.state.doc && ref_node.isText) {
+            ref_node.marks.forEach(m => {
+                m.type === state.schema.marks.pFontFamily && activeFamilies.push(m.attrs.family);
+                m.type === state.schema.marks.pFontSize && activeSizes.push(String(m.attrs.fontSize) + "pt");
+            });
+        }
+
+        const styles = new Map<String, String[]>();
+        styles.set("families", activeFamilies);
+        styles.set("sizes", activeSizes);
+        return styles;
+    }
+
+    getMarksInSelection(state: EditorState<any>) {
+        const found = new Set<Mark>();
+        const { from, to } = state.selection as TextSelection;
+        state.doc.nodesBetween(from, to, (node) => node.marks.forEach(m => found.add(m)));
+        return found;
+    }
+
+    //finds all active marks on selection in given group
+    getActiveMarksOnSelection() {
+        if (!this.view) return;
+
+        const markGroup = [schema.marks.strong, schema.marks.em, schema.marks.underline, schema.marks.strikethrough, schema.marks.superscript, schema.marks.subscript];
+        if (this.view.state.storedMarks) return this.view.state.storedMarks.map(mark => mark.type);
+        //current selection
+        const { empty, ranges, $to } = this.view.state.selection as TextSelection;
+        const state = this.view.state;
+        let activeMarks: MarkType[] = [];
+        if (!empty) {
+            activeMarks = markGroup.filter(mark => {
+                const has = false;
+                for (let i = 0; !has && i < ranges.length; i++) {
+                    return state.doc.rangeHasMark(ranges[i].$from.pos, ranges[i].$to.pos, mark);
+                }
+                return false;
+            });
+        }
+        else {
+            const pos = this.view.state.selection.$from;
+            const ref_node: ProsNode | null = this.reference_node(pos);
+            if (ref_node !== null && ref_node !== this.view.state.doc) {
+                if (ref_node.isText) {
+                }
+                else {
+                    return [];
+                }
+                activeMarks = markGroup.filter(mark_type => {
+                    if (mark_type === state.schema.marks.pFontSize) {
+                        return ref_node.marks.some(m => m.type.name === state.schema.marks.pFontSize.name);
+                    }
+                    const mark = state.schema.mark(mark_type);
+                    return ref_node.marks.includes(mark);
+                });
+            }
+        }
+        return activeMarks;
+    }
+
+    destroy() {
+        this.fadeOut(true);
+    }
+
+    @action
+    setActiveMarkButtons(activeMarks: MarkType[] | undefined) {
+        if (!activeMarks) return;
+
+        this.boldActive = false;
+        this.italicsActive = false;
+        this.underlineActive = false;
+        this.strikethroughActive = false;
+        this.subscriptActive = false;
+        this.superscriptActive = false;
+
+        activeMarks.forEach(mark => {
+            switch (mark.name) {
+                case "strong": this.boldActive = true; break;
+                case "em": this.italicsActive = true; break;
+                case "underline": this.underlineActive = true; break;
+                case "strikethrough": this.strikethroughActive = true; break;
+                case "subscript": this.subscriptActive = true; break;
+                case "superscript": this.superscriptActive = true; break;
+            }
+        });
+    }
+
+    createButton(faIcon: string, title: string, isActive: boolean = false, command?: any, onclick?: any) {
+        const self = this;
+        function onClick(e: React.PointerEvent) {
+            e.preventDefault();
+            e.stopPropagation();
+            self.view && self.view.focus();
+            self.view && command && command(self.view.state, self.view.dispatch, self.view);
+            self.view && onclick && onclick(self.view.state, self.view.dispatch, self.view);
+            self.setActiveMarkButtons(self.getActiveMarksOnSelection());
+        }
+
+        return (
+            <button className={"antimodeMenu-button" + (isActive ? " active" : "")} key={title} title={title} onPointerDown={onClick}>
+                <FontAwesomeIcon icon={faIcon as IconProp} size="lg" />
+            </button>
+        );
+    }
+
+    createMarksDropdown(activeOption: string, options: { mark: Mark | null, title: string, label: string, command: (mark: Mark, view: EditorView) => void, hidden?: boolean, style?: {} }[], key: string): JSX.Element {
+        const items = options.map(({ title, label, hidden, style }) => {
+            if (hidden) {
+                return label === activeOption ?
+                    <option value={label} title={title} key={label} style={style ? style : {}} selected hidden>{label}</option> :
+                    <option value={label} title={title} key={label} style={style ? style : {}} hidden>{label}</option>;
+            }
+            return label === activeOption ?
+                <option value={label} title={title} key={label} style={style ? style : {}} selected>{label}</option> :
+                <option value={label} title={title} key={label} style={style ? style : {}}>{label}</option>;
+        });
+
+        const self = this;
+        function onChange(e: React.ChangeEvent<HTMLSelectElement>) {
+            e.stopPropagation();
+            e.preventDefault();
+            options.forEach(({ label, mark, command }) => {
+                if (e.target.value === label) {
+                    self.view && mark && command(mark, self.view);
+                }
+            });
+        }
+        return <select onChange={onChange} key={key}>{items}</select>;
+    }
+
+    createNodesDropdown(activeOption: string, options: { node: NodeType | any | null, title: string, label: string, command: (node: NodeType | any) => void, hidden?: boolean, style?: {} }[], key: string): JSX.Element {
+        const items = options.map(({ title, label, hidden, style }) => {
+            if (hidden) {
+                return label === activeOption ?
+                    <option value={label} title={title} key={label} style={style ? style : {}} selected hidden>{label}</option> :
+                    <option value={label} title={title} key={label} style={style ? style : {}} hidden>{label}</option>;
+            }
+            return label === activeOption ?
+                <option value={label} title={title} key={label} style={style ? style : {}} selected>{label}</option> :
+                <option value={label} title={title} key={label} style={style ? style : {}}>{label}</option>;
+        });
+
+        const self = this;
+        function onChange(val: string) {
+            options.forEach(({ label, node, command }) => {
+                if (val === label) {
+                    self.view && node && command(node);
+                }
+            });
+        }
+        return <select onChange={e => onChange(e.target.value)} key={key}>{items}</select>;
+    }
+
+    changeFontSize = (mark: Mark, view: EditorView) => {
+        this.setMark(view.state.schema.marks.pFontSize.create({ fontSize: mark.attrs.fontSize }), view.state, view.dispatch);
+    }
+
+    changeFontFamily = (mark: Mark, view: EditorView) => {
+        this.setMark(view.state.schema.marks.pFontFamily.create({ family: mark.attrs.family }), view.state, view.dispatch);
+    }
+
+    // TODO: remove doesn't work
+    //remove all node type and apply the passed-in one to the selected text
+    changeListType = (nodeType: NodeType | undefined) => {
+        if (!this.view) return;
+
+        if (nodeType === schema.nodes.bullet_list) {
+            wrapInList(nodeType)(this.view.state, this.view.dispatch);
+        } else {
+            const marks = this.view.state.storedMarks || (this.view.state.selection.$to.parentOffset && this.view.state.selection.$from.marks());
+            if (!wrapInList(schema.nodes.ordered_list)(this.view.state, (tx2: any) => {
+                const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle);
+                marks && tx3.ensureMarks([...marks]);
+                marks && tx3.setStoredMarks([...marks]);
+
+                this.view!.dispatch(tx2);
+            })) {
+                const tx2 = this.view.state.tr;
+                const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle);
+                marks && tx3.ensureMarks([...marks]);
+                marks && tx3.setStoredMarks([...marks]);
+
+                this.view.dispatch(tx3);
+            }
+        }
+    }
+
+    insertSummarizer(state: EditorState<any>, dispatch: any) {
+        if (state.selection.empty) return false;
+        const mark = state.schema.marks.summarize.create();
+        const tr = state.tr;
+        tr.addMark(state.selection.from, state.selection.to, mark);
+        const content = tr.selection.content();
+        const newNode = state.schema.nodes.summary.create({ visibility: false, text: content, textslice: content.toJSON() });
+        dispatch && dispatch(tr.replaceSelectionWith(newNode).removeMark(tr.selection.from - 1, tr.selection.from, mark));
+        return true;
+    }
+
+    @action toggleBrushDropdown() { this.showBrushDropdown = !this.showBrushDropdown; }
+
+    // todo: add brushes to brushMap to save with a style name
+    onBrushNameKeyPress = (e: React.KeyboardEvent) => {
+        if (e.key === "Enter") {
+            RichTextMenu.Instance.brushMarks && RichTextMenu.Instance._brushMap.set(this._brushNameRef.current!.value, RichTextMenu.Instance.brushMarks);
+            this._brushNameRef.current!.style.background = "lightGray";
+        }
+    }
+    _brushNameRef = React.createRef<HTMLInputElement>();
+
+    createBrushButton() {
+        const self = this;
+        function onBrushClick(e: React.PointerEvent) {
+            e.preventDefault();
+            e.stopPropagation();
+            self.view && self.view.focus();
+            self.view && self.fillBrush(self.view.state, self.view.dispatch);
+        }
+
+        let label = "Stored marks: ";
+        if (this.brushMarks && this.brushMarks.size > 0) {
+            this.brushMarks.forEach((mark: Mark) => {
+                const markType = mark.type;
+                label += markType.name;
+                label += ", ";
+            });
+            label = label.substring(0, label.length - 2);
+        } else {
+            label = "No marks are currently stored";
+        }
+
+        const button =
+            <button className="antimodeMenu-button" title="" onPointerDown={onBrushClick} style={this.brushMarks?.size > 0 ? { backgroundColor: "121212" } : {}}>
+                <FontAwesomeIcon icon="paint-roller" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.1s", transform: `rotate(${this.brushMarks?.size > 0 ? 45 : 0}deg)` }} />
+            </button>;
+
+        const dropdownContent =
+            <div className="dropdown">
+                <p>{label}</p>
+                <button onPointerDown={this.clearBrush}>Clear brush</button>
+                <input placeholder="-brush name-" ref={this._brushNameRef} onKeyPress={this.onBrushNameKeyPress}></input>
+            </div>;
+
+        return (
+            <ButtonDropdown view={this.view} key={"brush dropdown"} button={button} dropdownContent={dropdownContent} />
+        );
+    }
+
+    @action
+    clearBrush() {
+        RichTextMenu.Instance.brushIsEmpty = true;
+        RichTextMenu.Instance.brushMarks = new Set();
+    }
+
+    @action
+    fillBrush(state: EditorState<any>, dispatch: any) {
+        if (!this.view) return;
+
+        if (this.brushIsEmpty) {
+            const selected_marks = this.getMarksInSelection(this.view.state);
+            if (selected_marks.size >= 0) {
+                this.brushMarks = selected_marks;
+                this.brushIsEmpty = !this.brushIsEmpty;
+            }
+        }
+        else {
+            const { from, to, $from } = this.view.state.selection;
+            if (!this.view.state.selection.empty && $from && $from.nodeAfter) {
+                if (this.brushMarks && to - from > 0) {
+                    this.view.dispatch(this.view.state.tr.removeMark(from, to));
+                    Array.from(this.brushMarks).filter(m => m.type !== schema.marks.user_mark).forEach((mark: Mark) => {
+                        this.setMark(mark, this.view!.state, this.view!.dispatch);
+                    });
+                }
+            }
+            else {
+                this.brushIsEmpty = !this.brushIsEmpty;
+            }
+        }
+    }
+
+    @action toggleColorDropdown() { this.showColorDropdown = !this.showColorDropdown; }
+    @action setActiveColor(color: string) { this.activeFontColor = color; }
+
+    createColorButton() {
+        const self = this;
+        function onColorClick(e: React.PointerEvent) {
+            e.preventDefault();
+            e.stopPropagation();
+            self.view && self.view.focus();
+            self.view && self.insertColor(self.activeFontColor, self.view.state, self.view.dispatch);
+        }
+        function changeColor(e: React.PointerEvent, color: string) {
+            e.preventDefault();
+            e.stopPropagation();
+            self.view && self.view.focus();
+            self.setActiveColor(color);
+            self.view && self.insertColor(self.activeFontColor, self.view.state, self.view.dispatch);
+        }
+
+        const button =
+            <button className="antimodeMenu-button color-preview-button" title="" onPointerDown={onColorClick}>
+                <FontAwesomeIcon icon="palette" size="lg" />
+                <div className="color-preview" style={{ backgroundColor: this.activeFontColor }}></div>
+            </button>;
+
+        const dropdownContent =
+            <div className="dropdown" >
+                <p>Change font color:</p>
+                <div className="color-wrapper">
+                    {this.fontColors.map(color => {
+                        if (color) {
+                            return this.activeFontColor === color ?
+                                <button className="color-button active" key={"active" + color} style={{ backgroundColor: color }} onPointerDown={e => changeColor(e, color)}></button> :
+                                <button className="color-button" key={"other" + color} style={{ backgroundColor: color }} onPointerDown={e => changeColor(e, color)}></button>;
+                        }
+                    })}
+                </div>
+            </div>;
+
+        return (
+            <ButtonDropdown view={this.view} key={"color dropdown"} button={button} dropdownContent={dropdownContent} />
+        );
+    }
+
+    public insertColor(color: String, state: EditorState<any>, dispatch: any) {
+        const colorMark = state.schema.mark(state.schema.marks.pFontColor, { color: color });
+        if (state.selection.empty) {
+            dispatch(state.tr.addStoredMark(colorMark));
+            return false;
+        }
+        this.setMark(colorMark, state, dispatch);
+    }
+
+    @action toggleHighlightDropdown() { this.showHighlightDropdown = !this.showHighlightDropdown; }
+    @action setActiveHighlight(color: string) { this.activeHighlightColor = color; }
+
+    createHighlighterButton() {
+        const self = this;
+        function onHighlightClick(e: React.PointerEvent) {
+            e.preventDefault();
+            e.stopPropagation();
+            self.view && self.view.focus();
+            self.view && self.insertHighlight(self.activeHighlightColor, self.view.state, self.view.dispatch);
+        }
+        function changeHighlight(e: React.PointerEvent, color: string) {
+            e.preventDefault();
+            e.stopPropagation();
+            self.view && self.view.focus();
+            self.setActiveHighlight(color);
+            self.view && self.insertHighlight(self.activeHighlightColor, self.view.state, self.view.dispatch);
+        }
+
+        const button =
+            <button className="antimodeMenu-button color-preview-button" title="" key="highilghter-button" onPointerDown={onHighlightClick}>
+                <FontAwesomeIcon icon="highlighter" size="lg" />
+                <div className="color-preview" style={{ backgroundColor: this.activeHighlightColor }}></div>
+            </button>;
+
+        const dropdownContent =
+            <div className="dropdown">
+                <p>Change highlight color:</p>
+                <div className="color-wrapper">
+                    {this.highlightColors.map(color => {
+                        if (color) {
+                            return this.activeHighlightColor === color ?
+                                <button className="color-button active" key={`active ${color}`} style={{ backgroundColor: color }} onPointerDown={e => changeHighlight(e, color)}>{color === "transparent" ? "X" : ""}</button> :
+                                <button className="color-button" key={`inactive ${color}`} style={{ backgroundColor: color }} onPointerDown={e => changeHighlight(e, color)}>{color === "transparent" ? "X" : ""}</button>;
+                        }
+                    })}
+                </div>
+            </div>;
+
+        return (
+            <ButtonDropdown view={this.view} key={"highlighter"} button={button} dropdownContent={dropdownContent} />
+        );
+    }
+
+    insertHighlight(color: String, state: EditorState<any>, dispatch: any) {
+        if (state.selection.empty) return false;
+        toggleMark(state.schema.marks.marker, { highlight: color })(state, dispatch);
+    }
+
+    @action toggleLinkDropdown() { this.showLinkDropdown = !this.showLinkDropdown; }
+    @action setCurrentLink(link: string) { this.currentLink = link; }
+
+    createLinkButton() {
+        const self = this;
+
+        function onLinkChange(e: React.ChangeEvent<HTMLInputElement>) {
+            self.setCurrentLink(e.target.value);
+        }
+
+        const link = this.currentLink ? this.currentLink : "";
+
+        const button = <FontAwesomeIcon icon="link" size="lg" />;
+
+        const dropdownContent =
+            <div className="dropdown link-menu">
+                <p>Linked to:</p>
+                <input value={link} placeholder="Enter URL" onChange={onLinkChange} />
+                <button className="make-button" onPointerDown={e => this.makeLinkToURL(link, "onRight")}>Apply hyperlink</button>
+                <div className="divider"></div>
+                <button className="remove-button" onPointerDown={e => this.deleteLink()}>Remove link</button>
+            </div>;
+
+        return (
+            <ButtonDropdown view={this.view} key={"link button"} button={button} dropdownContent={dropdownContent} openDropdownOnButton={true} />
+        );
+    }
+
+    async getTextLinkTargetTitle() {
+        if (!this.view) return;
+
+        const node = this.view.state.selection.$from.nodeAfter;
+        const link = node && node.marks.find(m => m.type.name === "link");
+        if (link) {
+            const href = link.attrs.href;
+            if (href) {
+                if (href.indexOf(Utils.prepend("/doc/")) === 0) {
+                    const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
+                    if (linkclicked) {
+                        const linkDoc = await DocServer.GetRefField(linkclicked);
+                        if (linkDoc instanceof Doc) {
+                            const anchor1 = await Cast(linkDoc.anchor1, Doc);
+                            const anchor2 = await Cast(linkDoc.anchor2, Doc);
+                            const currentDoc = SelectionManager.SelectedDocuments().length && SelectionManager.SelectedDocuments()[0].props.Document;
+                            if (currentDoc && anchor1 && anchor2) {
+                                if (Doc.AreProtosEqual(currentDoc, anchor1)) {
+                                    return StrCast(anchor2.title);
+                                }
+                                if (Doc.AreProtosEqual(currentDoc, anchor2)) {
+                                    return StrCast(anchor1.title);
+                                }
+                            }
+                        }
+                    }
+                } else {
+                    return href;
+                }
+            } else {
+                return link.attrs.title;
+            }
+        }
+    }
+
+    // TODO: should check for valid URL
+    makeLinkToURL = (target: String, lcoation: string) => {
+        if (!this.view) return;
+
+        let node = this.view.state.selection.$from.nodeAfter;
+        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;
+        link = node && node.marks.find(m => m.type.name === "link");
+    }
+
+    deleteLink = () => {
+        if (!this.view) return;
+
+        const node = this.view.state.selection.$from.nodeAfter;
+        const link = node && node.marks.find(m => m.type === this.view!.state.schema.marks.link);
+        const href = link!.attrs.href;
+        if (href) {
+            if (href.indexOf(Utils.prepend("/doc/")) === 0) {
+                const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
+                if (linkclicked) {
+                    DocServer.GetRefField(linkclicked).then(async linkDoc => {
+                        if (linkDoc instanceof Doc) {
+                            LinkManager.Instance.deleteLink(linkDoc);
+                            this.view!.dispatch(this.view!.state.tr.removeMark(this.view!.state.selection.from, this.view!.state.selection.to, this.view!.state.schema.marks.link));
+                        }
+                    });
+                }
+            } else {
+                if (node) {
+                    const { tr, schema, selection } = this.view.state;
+                    const extension = this.linkExtend(selection.$anchor, href);
+                    this.view.dispatch(tr.removeMark(extension.from, extension.to, schema.marks.link));
+                }
+            }
+        }
+    }
+
+    linkExtend($start: ResolvedPos, href: string) {
+        const mark = this.view!.state.schema.marks.link;
+
+        let startIndex = $start.index();
+        let endIndex = $start.indexAfter();
+
+        while (startIndex > 0 && $start.parent.child(startIndex - 1).marks.filter(m => m.type === mark && m.attrs.href === href).length) startIndex--;
+        while (endIndex < $start.parent.childCount && $start.parent.child(endIndex).marks.filter(m => m.type === mark && m.attrs.href === href).length) endIndex++;
+
+        let startPos = $start.start();
+        let endPos = startPos;
+        for (let i = 0; i < endIndex; i++) {
+            const size = $start.parent.child(i).nodeSize;
+            if (i < startIndex) startPos += size;
+            endPos += size;
+        }
+        return { from: startPos, to: endPos };
+    }
+
+    reference_node(pos: ResolvedPos<any>): ProsNode | null {
+        if (!this.view) return null;
+
+        let ref_node: ProsNode = this.view.state.doc;
+        if (pos.nodeBefore !== null && pos.nodeBefore !== undefined) {
+            ref_node = pos.nodeBefore;
+        }
+        else if (pos.nodeAfter !== null && pos.nodeAfter !== undefined) {
+            ref_node = pos.nodeAfter;
+        }
+        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) => {
+                    if (node.isLeaf && !skip) {
+                        ref_node = node;
+                        skip = true;
+                    }
+
+                });
+            }
+        }
+        if (!ref_node.isLeaf && ref_node.childCount > 0) {
+            ref_node = ref_node.child(0);
+        }
+        return ref_node;
+    }
+
+    @action onPointerEnter(e: React.PointerEvent) { RichTextMenu.Instance.overMenu = true; }
+    @action onPointerLeave(e: React.PointerEvent) { RichTextMenu.Instance.overMenu = false; }
+
+    @action
+    toggleMenuPin = (e: React.MouseEvent) => {
+        this.Pinned = !this.Pinned;
+        if (!this.Pinned) {
+            this.fadeOut(true);
+        }
+    }
+
+    @action
+    protected toggleCollapse = (e: React.MouseEvent) => {
+        this.collapsed = !this.collapsed;
+        setTimeout(() => {
+            const x = Math.min(this._left, window.innerWidth - RichTextMenu.Instance.width);
+            RichTextMenu.Instance.jumpTo(x, this._top);
+        }, 0);
+    }
+
+    render() {
+
+        const row1 = <div className="antimodeMenu-row" key="row1" style={{ display: this.collapsed ? "none" : undefined }}>{[
+            this.createButton("bold", "Bold", this.boldActive, toggleMark(schema.marks.strong)),
+            this.createButton("italic", "Italic", this.italicsActive, toggleMark(schema.marks.em)),
+            this.createButton("underline", "Underline", this.underlineActive, toggleMark(schema.marks.underline)),
+            this.createButton("strikethrough", "Strikethrough", this.strikethroughActive, toggleMark(schema.marks.strikethrough)),
+            this.createButton("superscript", "Superscript", this.superscriptActive, toggleMark(schema.marks.superscript)),
+            this.createButton("subscript", "Subscript", this.subscriptActive, toggleMark(schema.marks.subscript)),
+            this.createColorButton(),
+            this.createHighlighterButton(),
+            this.createLinkButton(),
+            this.createBrushButton(),
+            this.createButton("indent", "Summarize", undefined, this.insertSummarizer),
+        ]}</div>;
+
+        const row2 = <div className="antimodeMenu-row row-2" key="antimodemenu row2">
+            <div key="row" style={{ display: this.collapsed ? "none" : undefined }}>
+                {[this.createMarksDropdown(this.activeFontSize, this.fontSizeOptions, "font size"),
+                this.createMarksDropdown(this.activeFontFamily, this.fontFamilyOptions, "font family"),
+                this.createNodesDropdown(this.activeListType, this.listTypeOptions, "nodes")]}
+            </div>
+            <div key="button">
+                <div key="collapser">
+                    <button className="antimodeMenu-button" key="collapse menu" title="Collapse menu" onClick={this.toggleCollapse} style={{ backgroundColor: this.collapsed ? "#121212" : "", width: 25 }}>
+                        <FontAwesomeIcon icon="chevron-left" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.3s", transform: `rotate(${this.collapsed ? 180 : 0}deg)` }} />
+                    </button>
+                </div>
+                <button className="antimodeMenu-button" key="pin menu" title="Pin menu" onClick={this.toggleMenuPin} style={{ backgroundColor: this.Pinned ? "#121212" : "", display: this.collapsed ? "none" : undefined }}>
+                    <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.1s", transform: `rotate(${this.Pinned ? 45 : 0}deg)` }} />
+                </button>
+                {this.getDragger()}
+            </div>
+        </div>;
+
+        return (
+            <div className="richTextMenu" onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}>
+                {this.getElementWithRows([row1, row2], 2, false)}
+            </div>
+        );
+    }
+}
+
+interface ButtonDropdownProps {
+    view?: EditorView;
+    button: JSX.Element;
+    dropdownContent: JSX.Element;
+    openDropdownOnButton?: boolean;
+}
+
+@observer
+class ButtonDropdown extends React.Component<ButtonDropdownProps> {
+
+    @observable private showDropdown: boolean = false;
+    private ref: HTMLDivElement | null = null;
+
+    componentDidMount() {
+        document.addEventListener("pointerdown", this.onBlur);
+    }
+
+    componentWillUnmount() {
+        document.removeEventListener("pointerdown", this.onBlur);
+    }
+
+    @action
+    setShowDropdown(show: boolean) {
+        this.showDropdown = show;
+    }
+    @action
+    toggleDropdown() {
+        this.showDropdown = !this.showDropdown;
+    }
+
+    onDropdownClick = (e: React.PointerEvent) => {
+        e.preventDefault();
+        e.stopPropagation();
+        this.props.view && this.props.view.focus();
+        this.toggleDropdown();
+    }
+
+    onBlur = (e: PointerEvent) => {
+        setTimeout(() => {
+            if (this.ref !== null && !this.ref.contains(e.target as Node)) {
+                this.setShowDropdown(false);
+            }
+        }, 0);
+    }
+
+    render() {
+        return (
+            <div className="button-dropdown-wrapper" ref={node => this.ref = node}>
+                {this.props.openDropdownOnButton ?
+                    <button className="antimodeMenu-button dropdown-button-combined" onPointerDown={this.onDropdownClick}>
+                        {this.props.button}
+                        <FontAwesomeIcon icon="caret-down" size="sm" />
+                    </button> :
+                    <>
+                        {this.props.button}
+                        <button className="dropdown-button antimodeMenu-button" key="antimodebutton" onPointerDown={this.onDropdownClick}>
+                            <FontAwesomeIcon icon="caret-down" size="sm" />
+                        </button>
+                    </>}
+
+                {this.showDropdown ? this.props.dropdownContent : (null)}
+            </div>
+        );
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
new file mode 100644
index 000000000..335094e23
--- /dev/null
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -0,0 +1,319 @@
+import { ellipsis, emDash, InputRule, smartQuotes, textblockTypeInputRule } from "prosemirror-inputrules";
+import { NodeSelection, TextSelection } from "prosemirror-state";
+import { DataSym, Doc } from "../../../../new_fields/Doc";
+import { Id } from "../../../../new_fields/FieldSymbols";
+import { ComputedField } from "../../../../new_fields/ScriptField";
+import { Cast, NumCast } from "../../../../new_fields/Types";
+import { returnFalse, Utils } from "../../../../Utils";
+import { DocServer } from "../../../DocServer";
+import { Docs, DocUtils } from "../../../documents/Documents";
+import { FormattedTextBox } from "./FormattedTextBox";
+import { wrappingInputRule } from "./prosemirrorPatches";
+import RichTextMenu from "./RichTextMenu";
+import { schema } from "./schema_rts";
+
+export class RichTextRules {
+    public Document: Doc;
+    public TextBox: FormattedTextBox;
+    public EnteringStyle: boolean = false;
+    constructor(doc: Doc, textBox: FormattedTextBox) {
+        this.Document = doc;
+        this.TextBox = textBox;
+    }
+    public inpRules = {
+        rules: [
+            ...smartQuotes,
+            ellipsis,
+            emDash,
+
+            // > blockquote
+            wrappingInputRule(/^\s*>\s$/, schema.nodes.blockquote),
+
+            // 1. ordered list
+            wrappingInputRule(
+                /^1\.\s$/,
+                schema.nodes.ordered_list,
+                () => {
+                    return ({ mapStyle: "decimal", bulletStyle: 1 });
+                },
+                (match: any, node: any) => {
+                    return node.childCount + node.attrs.order === +match[1];
+                },
+                (type: any) => ({ type: type, attrs: { mapStyle: "decimal", bulletStyle: 1 } })
+            ),
+            // a. alphabbetical list
+            wrappingInputRule(
+                /^a\.\s$/,
+                schema.nodes.ordered_list,
+                // match => {
+                () => {
+                    return ({ mapStyle: "alpha", bulletStyle: 1 });
+                    // return ({ order: +match[1] })
+                },
+                (match: any, node: any) => {
+                    return node.childCount + node.attrs.order === +match[1];
+                },
+                (type: any) => ({ type: type, attrs: { mapStyle: "alpha", bulletStyle: 1 } })
+            ),
+
+            // * bullet list
+            wrappingInputRule(/^\s*([-+*])\s$/, schema.nodes.bullet_list),
+
+            // ``` code block
+            textblockTypeInputRule(/^```$/, schema.nodes.code_block),
+
+            // create an inline view of a tag stored under the '#' field
+            new InputRule(
+                new RegExp(/#([a-zA-Z_\-]+[a-zA-Z_\-0-9]*)\s$/),
+                (state, match, start, end) => {
+                    const tag = match[1];
+                    if (!tag) return state.tr;
+                    this.Document[DataSym]["#"] = tag;
+                    const fieldView = state.schema.nodes.dashField.create({ fieldKey: "#" });
+                    return state.tr.deleteRange(start, end).insert(start, fieldView);
+                }),
+
+            // # heading
+            textblockTypeInputRule(
+                new RegExp(/^(#{1,6})\s$/),
+                schema.nodes.heading,
+                match => {
+                    return ({ level: match[1].length });
+                }
+            ),
+
+            // set the font size using #<font-size> 
+            new InputRule(
+                new RegExp(/%([0-9]+)\s$/),
+                (state, match, start, end) => {
+                    const size = Number(match[1]);
+                    return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontSize.create({ fontSize: size }));
+                }),
+
+            // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document [[ <fieldKey> : <Doc>]]   // [[:Doc]] => hyperlink   [[fieldKey]] => show field   [[fieldKey:Doc]] => show field of doc
+            new InputRule(
+                new RegExp(/\[\[([a-zA-Z_@\? \-0-9]*)(=[a-zA-Z_@\? \-0-9]*)?(:[a-zA-Z_@\? \-0-9]+)?\]\]$/),
+                (state, match, start, end) => {
+                    const fieldKey = match[1];
+                    const docid = match[3]?.substring(1);
+                    const value = match[2]?.substring(1);
+                    if (!fieldKey) {
+                        if (docid) {
+                            DocServer.GetRefField(docid).then(docx => {
+                                const target = ((docx instanceof Doc) && docx) || Docs.Create.FreeformDocument([], { title: docid, _width: 500, _height: 500, _LODdisable: true, }, docid);
+                                DocUtils.Publish(target, docid, returnFalse, returnFalse);
+                                DocUtils.MakeLink({ doc: this.Document }, { doc: target }, "portal to");
+                            });
+                            const link = state.schema.marks.link.create({ href: Utils.prepend("/doc/" + docid), location: "onRight", title: docid, targetId: docid });
+                            return state.tr.deleteRange(end - 1, end).deleteRange(start, start + 2).addMark(start, end - 3, link);
+                        }
+                        return state.tr;
+                    }
+                    if (value !== "" && value !== undefined) {
+                        const num = value.match(/^[0-9.]/);
+                        this.Document[DataSym][fieldKey] = value === "true" ? true : value === "false" ? false : (num ? Number(value) : value);
+                    }
+                    const fieldView = state.schema.nodes.dashField.create({ fieldKey, docid });
+                    return state.tr.deleteRange(start, end).insert(start, fieldView);
+                }),
+            // create an inline view of a document {{ <layoutKey> : <Doc> }}  // {{:Doc}} => show default view of document   {{<layout>}} => show layout for this doc   {{<layout> : Doc}} => show layout for another doc
+            new InputRule(
+                new RegExp(/\{\{([a-zA-Z_ \-0-9]*)(\([a-zA-Z0-9…._\-]*\))?(:[a-zA-Z_ \-0-9]+)?\}\}$/),
+                (state, match, start, end) => {
+                    const fieldKey = match[1] || "";
+                    const fieldParam = match[2]?.replace("…", "...") || "";
+                    const docid = match[3]?.substring(1);
+                    if (!fieldKey && !docid) return state.tr;
+                    docid && DocServer.GetRefField(docid).then(docx => {
+                        if (!(docx instanceof Doc && docx)) {
+                            const docx = Docs.Create.FreeformDocument([], { title: docid, _width: 500, _height: 500, _LODdisable: true }, docid);
+                            DocUtils.Publish(docx, docid, returnFalse, returnFalse);
+                        }
+                    });
+                    const node = (state.doc.resolve(start) as any).nodeAfter;
+                    const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 75, title: "dashDoc", docid, fieldKey: fieldKey + fieldParam, float: "unset", alias: Utils.GenerateGuid() });
+                    const sm = state.storedMarks || undefined;
+                    return node ? state.tr.replaceRangeWith(start, end, dashDoc).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
+                }),
+            new InputRule(
+                new RegExp(/>>$/),
+                (state, match, start, end) => {
+                    const textDoc = this.Document[DataSym];
+                    const numInlines = NumCast(textDoc.inlineTextCount);
+                    textDoc.inlineTextCount = numInlines + 1;
+                    const inlineFieldKey = "inline" + numInlines; // which field on the text document this annotation will write to
+                    const inlineLayoutKey = "layout_" + inlineFieldKey; // the field holding the layout string that will render the inline annotation
+                    const textDocInline = Docs.Create.TextDocument("", { layoutKey: inlineLayoutKey, _width: 75, _height: 35, annotationOn: textDoc, _autoHeight: true, _fontSize: 9, title: "inline comment" });
+                    textDocInline.title = inlineFieldKey; // give the annotation its own title
+                    textDocInline.customTitle = true; // And make sure that it's 'custom' so that editing text doesn't change the title of the containing doc
+                    textDocInline.isTemplateForField = inlineFieldKey; // this is needed in case the containing text doc is converted to a template at some point
+                    textDocInline.proto = textDoc;  // make the annotation inherit from the outer text doc so that it can resolve any nested field references, e.g., [[field]]
+                    textDocInline._textContext = ComputedField.MakeFunction(`copyField(self.${inlineFieldKey})`);
+                    textDoc[inlineLayoutKey] = FormattedTextBox.LayoutString(inlineFieldKey); // create a layout string for the layout key that will render the annotation text
+                    textDoc[inlineFieldKey] = ""; // set a default value for the annotation
+                    const node = (state.doc.resolve(start) as any).nodeAfter;
+                    const newNode = schema.nodes.dashComment.create({ docid: textDocInline[Id] });
+                    const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 35, title: "dashDoc", docid: textDocInline[Id], float: "right" });
+                    const sm = state.storedMarks || undefined;
+                    const replaced = node ? state.tr.insert(start, newNode).replaceRangeWith(start + 1, end + 1, dashDoc).insertText(" ", start + 2).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
+                        state.tr;
+                    return replaced;
+                }),
+            // stop using active style
+            new InputRule(
+                new RegExp(/%%$/),
+                (state, match, start, end) => {
+                    const tr = state.tr.deleteRange(start, end);
+                    const marks = state.tr.selection.$anchor.nodeBefore?.marks;
+                    return marks ? Array.from(marks).filter(m => m !== state.schema.marks.user_mark).reduce((tr, m) => tr.removeStoredMark(m), tr) : tr;
+                }),
+
+            // set the Todo user-tag on the current selection (assumes % was used to initiate an EnteringStyle mode)
+            new InputRule(
+                new RegExp(/[ti!x]$/),
+                (state, match, start, end) => {
+                    if (state.selection.to === state.selection.from || !this.EnteringStyle) return null;
+                    const tag = match[0] === "t" ? "todo" : match[0] === "i" ? "ignore" : match[0] === "x" ? "disagree" : match[0] === "!" ? "important" : "??";
+                    const node = (state.doc.resolve(start) as any).nodeAfter;
+                    if (node?.marks.findIndex((m: any) => m.type === schema.marks.user_tag) !== -1) return state.tr.removeMark(start, end, schema.marks.user_tag);
+                    return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: tag, modified: Math.round(Date.now() / 1000 / 60) })) : state.tr;
+                }),
+
+            // set the First-line indent node type for the selection's paragraph (assumes % was used to initiate an EnteringStyle mode)
+            new InputRule(
+                new RegExp(/(%d|d)$/),
+                (state, match, start, end) => {
+                    if (!match[0].startsWith("%") && !this.EnteringStyle) return null;
+                    const pos = (state.doc.resolve(start) as any);
+                    for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) {
+                        const node = pos.node(depth);
+                        if (node.type === schema.nodes.paragraph) {
+                            const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, node.type, { ...node.attrs, indent: node.attrs.indent === 25 ? undefined : 25 });
+                            const result = replaced.setSelection(new TextSelection(replaced.doc.resolve(start)));
+                            return match[0].startsWith("%") ? result.deleteRange(start, end) : result;
+                        }
+                    }
+                    return null;
+                }),
+
+            // set the Hanging indent node type for the current selection's paragraph (assumes % was used to initiate an EnteringStyle mode)
+            new InputRule(
+                new RegExp(/(%h|h)$/),
+                (state, match, start, end) => {
+                    if (!match[0].startsWith("%") && !this.EnteringStyle) return null;
+                    const pos = (state.doc.resolve(start) as any);
+                    for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) {
+                        const node = pos.node(depth);
+                        if (node.type === schema.nodes.paragraph) {
+                            const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, node.type, { ...node.attrs, indent: node.attrs.indent === -25 ? undefined : -25 });
+                            const result = replaced.setSelection(new TextSelection(replaced.doc.resolve(start)));
+                            return match[0].startsWith("%") ? result.deleteRange(start, end) : result;
+                        }
+                    }
+                    return null;
+                }),
+            // set the Quoted indent node type for the current selection's paragraph (assumes % was used to initiate an EnteringStyle mode)
+            new InputRule(
+                new RegExp(/(%q|q)$/),
+                (state, match, start, end) => {
+                    if (!match[0].startsWith("%") && !this.EnteringStyle) return null;
+                    const pos = (state.doc.resolve(start) as any);
+                    if (state.selection instanceof NodeSelection && state.selection.node.type === schema.nodes.ordered_list) {
+                        const node = state.selection.node;
+                        return state.tr.setNodeMarkup(pos.pos, node.type, { ...node.attrs, indent: node.attrs.indent === 30 ? undefined : 30 });
+                    }
+                    for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) {
+                        const node = pos.node(depth);
+                        if (node.type === schema.nodes.paragraph) {
+                            const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, node.type, { ...node.attrs, inset: node.attrs.inset === 30 ? undefined : 30 });
+                            const result = replaced.setSelection(new TextSelection(replaced.doc.resolve(start)));
+                            return match[0].startsWith("%") ? result.deleteRange(start, end) : result;
+                        }
+                    }
+                    return null;
+                }),
+
+
+            // center justify text
+            new InputRule(
+                new RegExp(/%\^$/),
+                (state, match, start, end) => {
+                    const node = (state.doc.resolve(start) as any).nodeAfter;
+                    const sm = state.storedMarks || undefined;
+                    const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "center" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
+                        state.tr;
+                    return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
+                }),
+            // left justify text
+            new InputRule(
+                new RegExp(/%\[$/),
+                (state, match, start, end) => {
+                    const node = (state.doc.resolve(start) as any).nodeAfter;
+                    const sm = state.storedMarks || undefined;
+                    const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "left" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
+                        state.tr;
+                    return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
+                }),
+            // right justify text
+            new InputRule(
+                new RegExp(/%\]$/),
+                (state, match, start, end) => {
+                    const node = (state.doc.resolve(start) as any).nodeAfter;
+                    const sm = state.storedMarks || undefined;
+                    const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "right" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
+                        state.tr;
+                    return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
+                }),
+            new InputRule(
+                new RegExp(/%\(/),
+                (state, match, start, end) => {
+                    const node = (state.doc.resolve(start) as any).nodeAfter;
+                    const sm = state.storedMarks || [];
+                    const mark = state.schema.marks.summarizeInclusive.create();
+                    sm.push(mark);
+                    const selected = state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).addMark(start, end, mark);
+                    const content = selected.selection.content();
+                    const replaced = node ? selected.replaceRangeWith(start, end,
+                        schema.nodes.summary.create({ visibility: true, text: content, textslice: content.toJSON() })) :
+                        state.tr;
+                    return replaced.setSelection(new TextSelection(replaced.doc.resolve(end + 1))).setStoredMarks([...node.marks, ...sm]);
+                }),
+            new InputRule(
+                new RegExp(/%\)/),
+                (state, match, start, end) => {
+                    return state.tr.deleteRange(start, end).removeStoredMark(state.schema.marks.summarizeInclusive.create());
+                }),
+            new InputRule(
+                new RegExp(/%f$/),
+                (state, match, start, end) => {
+                    const newNode = schema.nodes.footnote.create({});
+                    const tr = state.tr;
+                    tr.deleteRange(start, end).replaceSelectionWith(newNode); // replace insertion with a footnote.
+                    return tr.setSelection(new NodeSelection( // select the footnote node to open its display
+                        tr.doc.resolve(  // get the location of the footnote node by subtracting the nodesize of the footnote from the current insertion point anchor (which will be immediately after the footnote node)
+                            tr.selection.anchor - tr.selection.$anchor.nodeBefore!.nodeSize)));
+                }),
+
+            // activate a style by name using prefix '%'
+            new InputRule(
+                new RegExp(/%[a-z]+$/),
+                (state, match, start, end) => {
+                    const color = match[0].substring(1, match[0].length);
+                    const marks = RichTextMenu.Instance._brushMap.get(color);
+                    if (marks) {
+                        const tr = state.tr.deleteRange(start, end);
+                        return marks ? Array.from(marks).reduce((tr, m) => tr.addStoredMark(m), tr) : tr;
+                    }
+                    const isValidColor = (strColor: string) => {
+                        const s = new Option().style;
+                        s.color = strColor;
+                        return s.color === strColor.toLowerCase(); // 'false' if color wasn't assigned
+                    };
+                    if (isValidColor(color)) {
+                        return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontColor.create({ color: color }));
+                    }
+                    return null;
+                }),
+        ]
+    };
+}
diff --git a/src/client/views/nodes/formattedText/RichTextSchema.tsx b/src/client/views/nodes/formattedText/RichTextSchema.tsx
new file mode 100644
index 000000000..33caf5751
--- /dev/null
+++ b/src/client/views/nodes/formattedText/RichTextSchema.tsx
@@ -0,0 +1,718 @@
+import { IReactionDisposer, observable, reaction, runInAction } from "mobx";
+import { baseKeymap, toggleMark } from "prosemirror-commands";
+import { redo, undo } from "prosemirror-history";
+import { keymap } from "prosemirror-keymap";
+import { DOMOutputSpecArray, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model";
+import { bulletList, listItem, orderedList } from 'prosemirror-schema-list';
+import { EditorState, NodeSelection, Plugin, TextSelection } from "prosemirror-state";
+import { StepMap } from "prosemirror-transform";
+import { EditorView } from "prosemirror-view";
+import * as ReactDOM from 'react-dom';
+import { Doc, DocListCast, Field, HeightSym, WidthSym } from "../../../../new_fields/Doc";
+import { Id } from "../../../../new_fields/FieldSymbols";
+import { List } from "../../../../new_fields/List";
+import { ObjectField } from "../../../../new_fields/ObjectField";
+import { listSpec } from "../../../../new_fields/Schema";
+import { SchemaHeaderField } from "../../../../new_fields/SchemaHeaderField";
+import { ComputedField } from "../../../../new_fields/ScriptField";
+import { BoolCast, Cast, NumCast, StrCast, FieldValue } from "../../../../new_fields/Types";
+import { emptyFunction, returnEmptyString, returnFalse, returnOne, Utils, returnZero } from "../../../../Utils";
+import { DocServer } from "../../../DocServer";
+import { Docs } from "../../../documents/Documents";
+import { CollectionViewType } from "../../collections/CollectionView";
+import { DocumentView } from "../DocumentView";
+import { FormattedTextBox } from "./FormattedTextBox";
+import { DocumentManager } from "../../../util/DocumentManager";
+import { Transform } from "../../../util/Transform";
+import React = require("react");
+
+import { schema } from "./schema_rts";
+
+export class OrderedListView {
+    update(node: any) {
+        return false; // if attr's of an ordered_list (e.g., bulletStyle) change, return false forces the dom node to be recreated which is necessary for the bullet labels to update
+    }
+}
+
+export class ImageResizeView {
+    _handle: HTMLElement;
+    _img: HTMLElement;
+    _outer: HTMLElement;
+    constructor(node: any, view: any, getPos: any, addDocTab: any) {
+        //moved
+        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.height = node.attrs.height;
+        this._outer.style.display = "inline-block";
+        this._outer.style.overflow = "hidden";
+        (this._outer.style as any).float = node.attrs.float;
+        //moved
+        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";
+        const self = this;
+        //moved
+        this._img.onclick = function (e: any) {
+            e.stopPropagation();
+            e.preventDefault();
+            if (view.state.selection.node && view.state.selection.node.type !== view.state.schema.nodes.image) {
+                view.dispatch(view.state.tr.setSelection(new NodeSelection(view.state.doc.resolve(view.state.selection.from - 2))));
+            }
+        };
+        //moved
+        this._img.onpointerdown = function (e: any) {
+            if (e.ctrlKey) {
+                e.preventDefault();
+                e.stopPropagation();
+                DocServer.GetRefField(node.attrs.docid).then(async linkDoc =>
+                    (linkDoc instanceof Doc) &&
+                    DocumentManager.Instance.FollowLink(linkDoc, view.state.schema.Document,
+                        document => addDocTab(document, node.attrs.location ? node.attrs.location : "inTab"), false));
+            }
+        };
+        //moved
+        this._handle.onpointerdown = function (e: any) {
+            e.preventDefault();
+            e.stopPropagation();
+            const wid = Number(getComputedStyle(self._img).width.replace(/px/, ""));
+            const hgt = Number(getComputedStyle(self._img).height.replace(/px/, ""));
+            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}`;
+                self._outer.style.height = `${(startWidth + diffInPx) * hgt / wid}`;
+            };
+
+            const onpointerup = () => {
+                document.removeEventListener("pointermove", onpointermove);
+                document.removeEventListener("pointerup", onpointerup);
+                const pos = view.state.selection.from;
+                view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, { ...node.attrs, width: self._outer.style.width, height: self._outer.style.height }));
+                view.dispatch(view.state.tr.setSelection(new NodeSelection(view.state.doc.resolve(pos))));
+            };
+
+            document.addEventListener("pointermove", onpointermove);
+            document.addEventListener("pointerup", onpointerup);
+        };
+        //Moved
+        this._outer.appendChild(this._img);
+        this._outer.appendChild(this._handle);
+        (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";
+    }
+}
+
+export class DashDocCommentView {
+    _collapsed: HTMLElement;
+    _view: any;
+    constructor(node: any, view: any, getPos: any) {
+        //moved
+        this._collapsed = document.createElement("span");
+        this._collapsed.className = "formattedTextBox-inlineComment";
+        this._collapsed.id = "DashDocCommentView-" + node.attrs.docid;
+        this._view = view;
+        //moved
+        const targetNode = () => {  // search forward in the prosemirror doc for the attached dashDocNode that is the target of the comment anchor
+            for (let i = getPos() + 1; i < view.state.doc.content.size; i++) {
+                const m = view.state.doc.nodeAt(i);
+                if (m && m.type === view.state.schema.nodes.dashDoc && m.attrs.docid === node.attrs.docid) {
+                    return { node: m, pos: i, hidden: m.attrs.hidden } as { node: any, pos: number, hidden: boolean };
+                }
+            }
+            const dashDoc = view.state.schema.nodes.dashDoc.create({ width: 75, height: 35, title: "dashDoc", docid: node.attrs.docid, float: "right" });
+            view.dispatch(view.state.tr.insert(getPos() + 1, dashDoc));
+            setTimeout(() => { try { view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.tr.doc, getPos() + 2))); } catch (e) { } }, 0);
+            return undefined;
+        };
+        //moved
+        this._collapsed.onpointerdown = (e: any) => {
+            e.stopPropagation();
+        };
+        //moved
+        this._collapsed.onpointerup = (e: any) => {
+            const target = targetNode();
+            if (target) {
+                const expand = target.hidden;
+                const tr = view.state.tr.setNodeMarkup(target.pos, undefined, { ...target.node.attrs, hidden: target.node.attrs.hidden ? false : true });
+                view.dispatch(tr.setSelection(TextSelection.create(tr.doc, getPos() + (expand ? 2 : 1)))); // update the attrs
+                setTimeout(() => {
+                    expand && DocServer.GetRefField(node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc));
+                    try { view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.tr.doc, getPos() + (expand ? 2 : 1)))); } catch (e) { }
+                }, 0);
+            }
+            e.stopPropagation();
+        };
+        //moved
+        this._collapsed.onpointerenter = (e: any) => {
+            DocServer.GetRefField(node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc, false));
+            e.preventDefault();
+            e.stopPropagation();
+        };
+        //moved
+        this._collapsed.onpointerleave = (e: any) => {
+            DocServer.GetRefField(node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowUnhighlight());
+            e.preventDefault();
+            e.stopPropagation();
+        };
+
+        (this as any).dom = this._collapsed;
+    }
+    //moved
+    selectNode() { }
+}
+
+export class DashDocView {
+    _dashSpan: HTMLDivElement;
+    _outer: HTMLElement;
+    _dashDoc: Doc | undefined;
+    _reactionDisposer: IReactionDisposer | undefined;
+    _renderDisposer: IReactionDisposer | undefined;
+    _textBox: FormattedTextBox;
+
+    getDocTransform = () => {
+        const { scale, translateX, translateY } = Utils.GetScreenTransform(this._outer);
+        return new Transform(-translateX, -translateY, 1).scale(1 / this.contentScaling() / scale);
+    }
+    contentScaling = () => NumCast(this._dashDoc!._nativeWidth) > 0 ? this._dashDoc![WidthSym]() / NumCast(this._dashDoc!._nativeWidth) : 1;
+
+    outerFocus = (target: Doc) => this._textBox.props.focus(this._textBox.props.Document);  // ideally, this would scroll to show the focus target
+
+    constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
+        this._textBox = tbox;
+        this._dashSpan = document.createElement("div");
+        this._outer = document.createElement("span");
+        this._outer.style.position = "relative";
+        this._outer.style.textIndent = "0";
+        this._outer.style.border = "1px solid " + StrCast(tbox.layoutDoc.color, (Cast(Doc.UserDoc().activeWorkspace, Doc, null).darkScheme ? "dimGray" : "lightGray"));
+        this._outer.style.width = node.attrs.width;
+        this._outer.style.height = node.attrs.height;
+        this._outer.style.display = node.attrs.hidden ? "none" : "inline-block";
+        // this._outer.style.overflow = "hidden";  // bcz: not sure if this is needed.  if it's used, then the doc doesn't highlight when you hover over a docComment
+        (this._outer.style as any).float = node.attrs.float;
+
+        this._dashSpan.style.width = node.attrs.width;
+        this._dashSpan.style.height = node.attrs.height;
+        this._dashSpan.style.position = "absolute";
+        this._dashSpan.style.display = "inline-block";
+        this._dashSpan.onpointerleave = () => {
+            const ele = document.getElementById("DashDocCommentView-" + node.attrs.docid);
+            if (ele) {
+                (ele as HTMLDivElement).style.backgroundColor = "";
+            }
+        };
+        this._dashSpan.onpointerenter = () => {
+            const ele = document.getElementById("DashDocCommentView-" + node.attrs.docid);
+            if (ele) {
+                (ele as HTMLDivElement).style.backgroundColor = "orange";
+            }
+        };
+        const removeDoc = () => {
+            const pos = getPos();
+            const ns = new NodeSelection(view.state.doc.resolve(pos));
+            view.dispatch(view.state.tr.setSelection(ns).deleteSelection());
+            return true;
+        };
+        const alias = node.attrs.alias;
+
+        const docid = node.attrs.docid || tbox.props.Document[Id];// tbox.props.DataDoc?.[Id] || tbox.dataDoc?.[Id];
+        DocServer.GetRefField(docid + alias).then(async dashDoc => {
+            if (!(dashDoc instanceof Doc)) {
+                alias && DocServer.GetRefField(docid).then(async dashDocBase => {
+                    if (dashDocBase instanceof Doc) {
+                        const aliasedDoc = Doc.MakeAlias(dashDocBase, docid + alias);
+                        aliasedDoc.layoutKey = "layout";
+                        node.attrs.fieldKey && Doc.makeCustomViewClicked(aliasedDoc, Docs.Create.StackingDocument, node.attrs.fieldKey, undefined);
+                        self.doRender(aliasedDoc, removeDoc, node, view, getPos);
+                    }
+                });
+            } else {
+                self.doRender(dashDoc, removeDoc, node, view, getPos);
+            }
+        });
+        const self = this;
+        this._dashSpan.onkeydown = function (e: any) {
+            e.stopPropagation();
+            if (e.key === "Tab" || e.key === "Enter") {
+                e.preventDefault();
+            }
+        };
+        this._dashSpan.onkeypress = function (e: any) { e.stopPropagation(); };
+        this._dashSpan.onwheel = function (e: any) { e.preventDefault(); };
+        this._dashSpan.onkeyup = function (e: any) { e.stopPropagation(); };
+        this._outer.appendChild(this._dashSpan);
+        (this as any).dom = this._outer;
+    }
+
+    doRender(dashDoc: Doc, removeDoc: any, node: any, view: any, getPos: any) {
+        this._dashDoc = dashDoc;
+        const self = this;
+        const dashLayoutDoc = Doc.Layout(dashDoc);
+        const finalLayout = node.attrs.docid ? dashDoc : Doc.expandTemplateLayout(dashLayoutDoc, dashDoc, node.attrs.fieldKey);
+
+        if (!finalLayout) setTimeout(() => self.doRender(dashDoc, removeDoc, node, view, getPos), 0);
+        else {
+            this._reactionDisposer?.();
+            this._reactionDisposer = reaction(() => ({ dim: [finalLayout[WidthSym](), finalLayout[HeightSym]()], color: finalLayout.color }), ({ dim, color }) => {
+                this._dashSpan.style.width = this._outer.style.width = Math.max(20, dim[0]) + "px";
+                this._dashSpan.style.height = this._outer.style.height = Math.max(20, dim[1]) + "px";
+                this._outer.style.border = "1px solid " + StrCast(finalLayout.color, (Cast(Doc.UserDoc().activeWorkspace, Doc, null).darkScheme ? "dimGray" : "lightGray"));
+            }, { fireImmediately: true });
+            const doReactRender = (finalLayout: Doc, resolvedDataDoc: Doc) => {
+                ReactDOM.unmountComponentAtNode(this._dashSpan);
+
+                ReactDOM.render(<DocumentView
+                    Document={finalLayout}
+                    DataDoc={resolvedDataDoc}
+                    LibraryPath={this._textBox.props.LibraryPath}
+                    fitToBox={BoolCast(dashDoc._fitToBox)}
+                    addDocument={returnFalse}
+                    rootSelected={this._textBox.props.isSelected}
+                    removeDocument={removeDoc}
+                    ScreenToLocalTransform={this.getDocTransform}
+                    addDocTab={this._textBox.props.addDocTab}
+                    pinToPres={returnFalse}
+                    renderDepth={self._textBox.props.renderDepth + 1}
+                    NativeHeight={returnZero}
+                    NativeWidth={returnZero}
+                    PanelWidth={finalLayout[WidthSym]}
+                    PanelHeight={finalLayout[HeightSym]}
+                    focus={this.outerFocus}
+                    backgroundColor={returnEmptyString}
+                    parentActive={returnFalse}
+                    whenActiveChanged={returnFalse}
+                    bringToFront={emptyFunction}
+                    dontRegisterView={false}
+                    ContainingCollectionView={this._textBox.props.ContainingCollectionView}
+                    ContainingCollectionDoc={this._textBox.props.ContainingCollectionDoc}
+                    ContentScaling={this.contentScaling}
+                />, this._dashSpan);
+                if (node.attrs.width !== dashDoc._width + "px" || node.attrs.height !== dashDoc._height + "px") {
+                    try { // bcz: an exception will be thrown if two aliases are open at the same time when a doc view comment is made
+                        view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, { ...node.attrs, width: dashDoc._width + "px", height: dashDoc._height + "px" }));
+                    } catch (e) {
+                        console.log(e);
+                    }
+                }
+            };
+            this._renderDisposer?.();
+            this._renderDisposer = reaction(() => {
+                // if (!Doc.AreProtosEqual(finalLayout, dashDoc)) {
+                //     finalLayout.rootDocument = dashDoc.aliasOf; // bcz: check on this ... why is it here?
+                // }
+                const layoutKey = StrCast(finalLayout.layoutKey);
+                const finalKey = layoutKey && StrCast(finalLayout[layoutKey]).split("'")?.[1];
+                if (finalLayout !== dashDoc && finalKey) {
+                    const finalLayoutField = finalLayout[finalKey];
+                    if (finalLayoutField instanceof ObjectField) {
+                        finalLayout[finalKey + "-textTemplate"] = ComputedField.MakeFunction(`copyField(this.${finalKey})`, { this: Doc.name });
+                    }
+                }
+                return { finalLayout, resolvedDataDoc: Cast(finalLayout.resolvedDataDoc, Doc, null) };
+            },
+                (res) => doReactRender(res.finalLayout, res.resolvedDataDoc),
+                { fireImmediately: true });
+        }
+    }
+    destroy() {
+        ReactDOM.unmountComponentAtNode(this._dashSpan);
+        this._reactionDisposer?.();
+    }
+}
+
+export class DashFieldView {
+    _fieldWrapper: HTMLDivElement; // container for label and value
+    _labelSpan: HTMLSpanElement; // field label
+    _fieldSpan: HTMLSpanElement;  // field value
+    _fieldCheck: HTMLInputElement;
+    _enumerables: HTMLDivElement;  // field value
+    _reactionDisposer: IReactionDisposer | undefined;
+    _textBoxDoc: Doc;
+    @observable _dashDoc: Doc | undefined;
+    _fieldKey: string;
+    _options: Doc[] = [];
+
+    constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
+        this._fieldKey = node.attrs.fieldKey;
+        this._textBoxDoc = tbox.props.Document;
+        this._fieldWrapper = document.createElement("p");
+        this._fieldWrapper.style.width = node.attrs.width;
+        this._fieldWrapper.style.height = node.attrs.height;
+        this._fieldWrapper.style.fontWeight = "bold";
+        this._fieldWrapper.style.position = "relative";
+        this._fieldWrapper.style.display = "inline-block";
+
+        const self = this;
+
+        this._enumerables = document.createElement("div");
+        this._enumerables.style.width = "10px";
+        this._enumerables.style.height = "10px";
+        this._enumerables.style.position = "relative";
+        this._enumerables.style.display = "none";
+
+        //Moved
+        this._enumerables.onpointerdown = async (e) => {
+            e.stopPropagation();
+            const collview = await Doc.addFieldEnumerations(self._textBoxDoc, self._fieldKey, [{ title: self._fieldSpan.innerText }]);
+            collview instanceof Doc && tbox.props.addDocTab(collview, "onRight");
+        };
+        //Moved
+        const updateText = (forceMatch: boolean) => {
+            self._enumerables.style.display = "none";
+            const newText = self._fieldSpan.innerText.startsWith(":=") || self._fieldSpan.innerText.startsWith("=:=") ? ":=-computed-" : self._fieldSpan.innerText;
+
+            // look for a document whose id === the fieldKey being displayed.  If there's a match, then that document
+            // holds the different enumerated values for the field in the titles of its collected documents.
+            // if there's a partial match from the start of the input text, complete the text --- TODO: make this an auto suggest box and select from a drop down.
+            DocServer.GetRefField(self._fieldKey).then(options => {
+                let modText = "";
+                (options instanceof Doc) && DocListCast(options.data).forEach(opt => (forceMatch ? StrCast(opt.title).startsWith(newText) : StrCast(opt.title) === newText) && (modText = StrCast(opt.title)));
+                if (modText) {
+                    self._fieldSpan.innerHTML = self._dashDoc![self._fieldKey] = modText;
+                    Doc.addFieldEnumerations(self._textBoxDoc, self._fieldKey, []);
+                } // if the text starts with a ':=' then treat it as an expression by making a computed field from its value storing it in the key
+                else if (self._fieldSpan.innerText.startsWith(":=")) {
+                    self._dashDoc![self._fieldKey] = ComputedField.MakeFunction(self._fieldSpan.innerText.substring(2));
+                } else if (self._fieldSpan.innerText.startsWith("=:=")) {
+                    Doc.Layout(tbox.props.Document)[self._fieldKey] = ComputedField.MakeFunction(self._fieldSpan.innerText.substring(3));
+                } else {
+                    self._dashDoc![self._fieldKey] = newText;
+                }
+            });
+        };
+
+        //Moved
+        this._fieldCheck = document.createElement("input");
+        this._fieldCheck.id = Utils.GenerateGuid();
+        this._fieldCheck.type = "checkbox";
+        this._fieldCheck.style.position = "relative";
+        this._fieldCheck.style.display = "none";
+        this._fieldCheck.style.minWidth = "12px";
+        this._fieldCheck.style.backgroundColor = "rgba(155, 155, 155, 0.24)";
+        this._fieldCheck.onchange = function (e: any) {
+            self._dashDoc![self._fieldKey] = e.target.checked;
+        };
+
+        this._fieldSpan = document.createElement("span");
+        this._fieldSpan.id = Utils.GenerateGuid();
+        this._fieldSpan.contentEditable = "true";
+        this._fieldSpan.style.position = "relative";
+        this._fieldSpan.style.display = "none";
+        this._fieldSpan.style.minWidth = "12px";
+        this._fieldSpan.style.fontSize = "large";
+        this._fieldSpan.onkeypress = function (e: any) { e.stopPropagation(); };
+        this._fieldSpan.onkeyup = function (e: any) { e.stopPropagation(); };
+        this._fieldSpan.onmousedown = function (e: any) { e.stopPropagation(); self._enumerables.style.display = "inline-block"; };
+        this._fieldSpan.onblur = function (e: any) { updateText(false); };
+
+        // MOVED 
+        const setDashDoc = (doc: Doc) => {
+            self._dashDoc = doc;
+            if (self._options?.length && !self._dashDoc[self._fieldKey]) {
+                self._dashDoc[self._fieldKey] = StrCast(self._options[0].title);
+            }
+            this._labelSpan.innerHTML = `${self._fieldKey}: `;
+            const fieldVal = Cast(this._dashDoc?.[self._fieldKey], "boolean", null);
+            this._fieldCheck.style.display = (fieldVal === true || fieldVal === false) ? "inline-block" : "none";
+            this._fieldSpan.style.display = !(fieldVal === true || fieldVal === false) ? StrCast(this._dashDoc?.[self._fieldKey]) ? "" : "inline-block" : "none";
+        };
+
+        //Moved
+        this._fieldSpan.onkeydown = function (e: any) {
+            e.stopPropagation();
+            if ((e.key === "a" && e.ctrlKey) || (e.key === "a" && e.metaKey)) {
+                if (window.getSelection) {
+                    const range = document.createRange();
+                    range.selectNodeContents(self._fieldSpan);
+                    window.getSelection()!.removeAllRanges();
+                    window.getSelection()!.addRange(range);
+                }
+                e.preventDefault();
+            }
+            if (e.key === "Enter") {
+                e.preventDefault();
+                e.ctrlKey && Doc.addFieldEnumerations(self._textBoxDoc, self._fieldKey, [{ title: self._fieldSpan.innerText }]);
+                updateText(true);
+            }
+        };
+
+        this._labelSpan = document.createElement("span");
+        this._labelSpan.style.position = "relative";
+        this._labelSpan.style.fontSize = "small";
+        this._labelSpan.title = "click to see related tags";
+        this._labelSpan.style.fontSize = "x-small";
+        this._labelSpan.onpointerdown = function (e: any) {
+            e.stopPropagation();
+            let container = tbox.props.ContainingCollectionView;
+            while (container?.props.Document.isTemplateForField || container?.props.Document.isTemplateDoc) {
+                container = container.props.ContainingCollectionView;
+            }
+            if (container) {
+                const alias = Doc.MakeAlias(container.props.Document);
+                alias.viewType = CollectionViewType.Time;
+                let list = Cast(alias.schemaColumns, listSpec(SchemaHeaderField));
+                if (!list) {
+                    alias.schemaColumns = list = new List<SchemaHeaderField>();
+                }
+                list.map(c => c.heading).indexOf(self._fieldKey) === -1 && list.push(new SchemaHeaderField(self._fieldKey, "#f1efeb"));
+                list.map(c => c.heading).indexOf("text") === -1 && list.push(new SchemaHeaderField("text", "#f1efeb"));
+                alias._pivotField = self._fieldKey;
+                tbox.props.addDocTab(alias, "onRight");
+            }
+        };
+        this._labelSpan.innerHTML = `${self._fieldKey}: `;
+        //MOVED
+        if (node.attrs.docid) {
+            DocServer.GetRefField(node.attrs.docid).
+                then(async dashDoc => dashDoc instanceof Doc && runInAction(() => setDashDoc(dashDoc)));
+        } else {
+            setDashDoc(tbox.props.DataDoc || tbox.dataDoc);
+        }
+
+        //Moved
+        this._reactionDisposer?.();
+        this._reactionDisposer = reaction(() => { // this reaction will update the displayed text whenever the document's fieldKey's value changes
+            const dashVal = this._dashDoc?.[self._fieldKey];
+            return StrCast(dashVal).startsWith(":=") || dashVal === "" ? Doc.Layout(tbox.props.Document)[self._fieldKey] : dashVal;
+        }, fval => {
+            const boolVal = Cast(fval, "boolean", null);
+            if (boolVal === true || boolVal === false) {
+                this._fieldCheck.checked = boolVal;
+            } else {
+                this._fieldSpan.innerHTML = Field.toString(fval as Field) || "";
+            }
+            this._fieldCheck.style.display = (boolVal === true || boolVal === false) ? "inline-block" : "none";
+            this._fieldSpan.style.display = !(fval === true || fval === false) ? (StrCast(fval) ? "" : "inline-block") : "none";
+        }, { fireImmediately: true });
+
+        //MOVED IN ORDER
+        this._fieldWrapper.appendChild(this._labelSpan);
+        this._fieldWrapper.appendChild(this._fieldCheck);
+        this._fieldWrapper.appendChild(this._fieldSpan);
+        this._fieldWrapper.appendChild(this._enumerables);
+        (this as any).dom = this._fieldWrapper;
+        //updateText(false);
+    }
+    //MOVED
+    destroy() {
+        this._reactionDisposer?.();
+    }
+    //moved
+    selectNode() { }
+}
+
+export class FootnoteView {
+    innerView: any;
+    outerView: any;
+    node: any;
+    dom: any;
+    getPos: any;
+
+    constructor(node: any, view: any, getPos: any) {
+        // We'll need these later
+        this.node = node;
+        this.outerView = view;
+        this.getPos = getPos;
+
+        // The node's representation in the editor (empty, for now)
+        this.dom = document.createElement("footnote");
+        this.dom.addEventListener("pointerup", this.toggle, true);
+        // These are used when the footnote is selected
+        this.innerView = null;
+    }
+    selectNode() {
+        const attrs = { ...this.node.attrs };
+        attrs.visibility = true;
+        this.dom.classList.add("ProseMirror-selectednode");
+        if (!this.innerView) this.open();
+    }
+
+    deselectNode() {
+        const attrs = { ...this.node.attrs };
+        attrs.visibility = false;
+        this.dom.classList.remove("ProseMirror-selectednode");
+        if (this.innerView) this.close();
+    }
+    open() {
+        // Append a tooltip to the outer node
+        const tooltip = this.dom.appendChild(document.createElement("div"));
+        tooltip.className = "footnote-tooltip";
+        // And put a sub-ProseMirror into that
+        this.innerView = new EditorView(tooltip, {
+            // You can use any node as an editor document
+            state: EditorState.create({
+                doc: this.node,
+                plugins: [keymap(baseKeymap),
+                keymap({
+                    "Mod-z": () => undo(this.outerView.state, this.outerView.dispatch),
+                    "Mod-y": () => redo(this.outerView.state, this.outerView.dispatch),
+                    "Mod-b": toggleMark(schema.marks.strong)
+                }),
+                    // new Plugin({
+                    //     view(newView) {
+                    //         // TODO -- make this work with RichTextMenu
+                    //         // return FormattedTextBox.getToolTip(newView);
+                    //     }
+                    // })
+                ],
+
+            }),
+            // This is the magic part
+            dispatchTransaction: this.dispatchInner.bind(this),
+            handleDOMEvents: {
+                pointerdown: ((view: any, e: PointerEvent) => {
+                    // Kludge to prevent issues due to the fact that the whole
+                    // footnote is node-selected (and thus DOM-selected) when
+                    // the parent editor is focused.
+                    e.stopPropagation();
+                    document.addEventListener("pointerup", this.ignore, true);
+                    if (this.outerView.hasFocus()) this.innerView.focus();
+                }) as any
+            }
+
+        });
+        setTimeout(() => this.innerView && this.innerView.docView.setSelection(0, 0, this.innerView.root, true), 0);
+    }
+
+    ignore = (e: PointerEvent) => {
+        e.stopPropagation();
+        document.removeEventListener("pointerup", this.ignore, true);
+    }
+
+    toggle = () => {
+        if (this.innerView) this.close();
+        else {
+            this.open();
+        }
+    }
+    close() {
+        this.innerView && this.innerView.destroy();
+        this.innerView = null;
+        this.dom.textContent = "";
+    }
+
+    dispatchInner(tr: any) {
+        const { state, transactions } = this.innerView.state.applyTransaction(tr);
+        this.innerView.updateState(state);
+
+        if (!tr.getMeta("fromOutside")) {
+            const outerTr = this.outerView.state.tr, offsetMap = StepMap.offset(this.getPos() + 1);
+            for (const transaction of transactions) {
+                const steps = transaction.steps;
+                for (const step of steps) {
+                    outerTr.step(step.map(offsetMap));
+                }
+            }
+            if (outerTr.docChanged) this.outerView.dispatch(outerTr);
+        }
+    }
+    update(node: any) {
+        if (!node.sameMarkup(this.node)) return false;
+        this.node = node;
+        if (this.innerView) {
+            const state = this.innerView.state;
+            const start = node.content.findDiffStart(state.doc.content);
+            if (start !== null) {
+                let { a: endA, b: endB } = node.content.findDiffEnd(state.doc.content);
+                const overlap = start - Math.min(endA, endB);
+                if (overlap > 0) { endA += overlap; endB += overlap; }
+                this.innerView.dispatch(
+                    state.tr
+                        .replace(start, endB, node.slice(start, endA))
+                        .setMeta("fromOutside", true));
+            }
+        }
+        return true;
+    }
+
+    destroy() {
+        if (this.innerView) this.close();
+    }
+
+    stopEvent(event: any) {
+        return this.innerView && this.innerView.dom.contains(event.target);
+    }
+
+    ignoreMutation() { return true; }
+}
+
+export class SummaryView {
+    _collapsed: HTMLElement;
+    _view: any;
+    constructor(node: any, view: any, getPos: any) {
+        this._collapsed = document.createElement("span");
+        this._collapsed.className = this.className(node.attrs.visibility);
+        this._view = view;
+        const js = node.toJSON;
+        node.toJSON = function () {
+            return js.apply(this, arguments);
+        };
+
+        this._collapsed.onpointerdown = (e: any) => {
+            const visible = !node.attrs.visibility;
+            const attrs = { ...node.attrs, visibility: visible };
+            let textSelection = TextSelection.create(view.state.doc, getPos() + 1);
+            if (!visible) { // update summarized text and save in attrs
+                textSelection = this.updateSummarizedText(getPos() + 1);
+                attrs.text = textSelection.content();
+                attrs.textslice = attrs.text.toJSON();
+            }
+            view.dispatch(view.state.tr.
+                setSelection(textSelection). // select the current summarized text (or where it will be if its collapsed)
+                replaceSelection(!visible ? new Slice(Fragment.fromArray([]), 0, 0) : node.attrs.text). // collapse/expand it
+                setNodeMarkup(getPos(), undefined, attrs)); // update the attrs
+            e.preventDefault();
+            e.stopPropagation();
+            this._collapsed.className = this.className(visible);
+        };
+        (this as any).dom = this._collapsed;
+    }
+    selectNode() { }
+
+    deselectNode() { }
+
+    className = (visible: boolean) => "formattedTextBox-summarizer" + (visible ? "" : "-collapsed");
+
+    updateSummarizedText(start?: any) {
+        const mtype = this._view.state.schema.marks.summarize;
+        const mtypeInc = this._view.state.schema.marks.summarizeInclusive;
+        let endPos = start;
+
+        const visited = new Set();
+        for (let i: number = start + 1; i < this._view.state.doc.nodeSize - 1; 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.find((m: any) => m.type === mtype || m.type === mtypeInc)) {
+                        visited.add(node);
+                        endPos = i + node.nodeSize - 1;
+                    }
+                    else skip = true;
+                }
+            });
+        }
+        return TextSelection.create(this._view.state.doc, start, endPos);
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/SummaryView.tsx b/src/client/views/nodes/formattedText/SummaryView.tsx
new file mode 100644
index 000000000..89908d8ee
--- /dev/null
+++ b/src/client/views/nodes/formattedText/SummaryView.tsx
@@ -0,0 +1,81 @@
+import { TextSelection } from "prosemirror-state";
+import { Fragment, Node, Slice } from "prosemirror-model";
+
+import React = require("react");
+
+interface ISummaryView {
+    node: any;
+    view: any;
+    getPos: any;
+    self: any;
+}
+export class SummaryView extends React.Component<ISummaryView> {
+
+    onPointerDown = (e: any) => {
+        const visible = !this.props.node.attrs.visibility;
+        const attrs = { ...this.props.node.attrs, visibility: visible };
+        let textSelection = TextSelection.create(this.props.view.state.doc, this.props.getPos() + 1);
+        if (!visible) { // update summarized text and save in attrs
+            textSelection = this.updateSummarizedText(this.props.getPos() + 1);
+            attrs.text = textSelection.content();
+            attrs.textslice = attrs.text.toJSON();
+        }
+        this.props.view.dispatch(this.props.view.state.tr.
+            setSelection(textSelection). // select the current summarized text (or where it will be if its collapsed)
+            replaceSelection(!visible ? new Slice(Fragment.fromArray([]), 0, 0) : this.props.node.attrs.text). // collapse/expand it
+            setNodeMarkup(this.props.getPos(), undefined, attrs)); // update the attrs
+        e.preventDefault();
+        e.stopPropagation();
+        const _collapsed = document.getElementById('collapse') as HTMLElement;
+        _collapsed.className = this.className(visible);
+    }
+
+    updateSummarizedText(start?: any) {
+        const mtype = this.props.view.state.schema.marks.summarize;
+        const mtypeInc = this.props.view.state.schema.marks.summarizeInclusive;
+        let endPos = start;
+
+        const visited = new Set();
+        for (let i: number = start + 1; i < this.props.view.state.doc.nodeSize - 1; i++) {
+            let skip = false;
+            this.props.view.state.doc.nodesBetween(start, i, (node: Node, pos: number, parent: Node, index: number) => {
+                if (this.props.node.isLeaf && !visited.has(node) && !skip) {
+                    if (this.props.node.marks.find((m: any) => m.type === mtype || m.type === mtypeInc)) {
+                        visited.add(node);
+                        endPos = i + this.props.node.nodeSize - 1;
+                    }
+                    else skip = true;
+                }
+            });
+        }
+        return TextSelection.create(this.props.view.state.doc, start, endPos);
+    }
+
+    className = (visible: boolean) => "formattedTextBox-summarizer" + (visible ? "" : "-collapsed");
+
+    selectNode() { }
+
+    deselectNode() { }
+
+    render() {
+        const _view = this.props.node.view;
+        const js = this.props.node.toJSon;
+
+        this.props.node.toJSON = function () {
+            return js.apply(this, arguments);
+        };
+
+        const spanCollapsedClassName = this.className(this.props.node.attrs.visibility);
+
+        return (
+            <span
+                className={spanCollapsedClassName}
+                id='collapse'
+                onPointerDown={this.onPointerDown}
+            >
+
+            </span>
+        );
+
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/TooltipTextMenu.scss b/src/client/views/nodes/formattedText/TooltipTextMenu.scss
new file mode 100644
index 000000000..e2149e9c1
--- /dev/null
+++ b/src/client/views/nodes/formattedText/TooltipTextMenu.scss
@@ -0,0 +1,372 @@
+@import "../views/globalCssVariables";
+.ProseMirror-menu-dropdown-wrap {
+    display: inline-block;
+    position: relative;
+}
+  
+.ProseMirror-menu-dropdown {
+    vertical-align: 1px;
+    cursor: pointer;
+    position: relative;
+    padding: 0 15px 0 4px;
+    background: white;
+    border-radius: 2px;
+    text-align: left;
+    font-size: 12px;
+    white-space: nowrap;
+    margin-right: 4px;
+
+    &:after {
+        content: "";
+        border-left: 4px solid transparent;
+        border-right: 4px solid transparent;
+        border-top: 4px solid currentColor;
+        opacity: .6;
+        position: absolute;
+        right: 4px;
+        top: calc(50% - 2px);
+    }
+}
+
+.ProseMirror-menu-submenu-wrap {
+    position: relative;
+    margin-right: -4px;
+}
+
+.ProseMirror-menu-dropdown-menu, 
+.ProseMirror-menu-submenu {
+    font-size: 12px;
+    background: white;
+    border: 1px solid rgb(223, 223, 223);
+    min-width: 40px;
+    z-index: 50000;
+    position: absolute;
+    box-sizing: content-box;
+
+    .ProseMirror-menu-dropdown-item {
+        cursor: pointer;
+        z-index: 100000;
+        text-align: left;
+        padding: 3px;
+
+        &:hover {
+            background-color: $light-color-secondary;
+        }
+    }
+}
+
+
+.ProseMirror-menu-submenu-label:after {
+    content: "";
+    border-top: 4px solid transparent;
+    border-bottom: 4px solid transparent;
+    border-left: 4px solid currentColor;
+    opacity: .6;
+    position: absolute;
+    right: 4px;
+    top: calc(50% - 4px);
+}
+  
+  .ProseMirror-icon {
+    display: inline-block;
+    // line-height: .8;
+    // vertical-align: -2px; /* Compensate for padding */
+    // padding: 2px 8px;
+    cursor: pointer;
+
+    &.ProseMirror-menu-disabled {
+        cursor: default;
+    }
+
+    svg {
+        fill:white;
+        height: 1em;
+    }
+
+    span {
+        vertical-align: text-top;
+      }  
+  }
+
+.wrapper {
+    position: absolute;
+    pointer-events: all;
+    display: flex;
+    align-items: center;
+    transform: translateY(-85px);
+
+    height: 35px;
+    background: #323232;
+    border-radius: 6px;
+    box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25);
+
+}
+
+.tooltipMenu, .basic-tools {
+    z-index: 20000;
+    pointer-events: all;
+    padding: 3px;
+    padding-bottom: 5px;
+    display: flex;
+    align-items: center;
+
+    .ProseMirror-example-setup-style hr {
+      padding: 2px 10px;
+      border: none;
+      margin: 1em 0;
+    }
+    
+    .ProseMirror-example-setup-style hr:after {
+      content: "";
+      display: block;
+      height: 1px;
+      background-color: silver;
+      line-height: 2px;
+    }
+}
+
+.menuicon {
+    width: 25px;
+    height: 25px;
+    cursor: pointer;
+    text-align: center;
+    line-height: 25px;
+    margin: 0 2px;
+    border-radius: 3px;
+
+    &:hover {
+        background-color: black;
+
+        #link-drag {
+            background-color: black;
+        }
+    }
+
+    &> * {
+        margin-top: 50%;
+        margin-left: 50%;
+        transform: translate(-50%, -50%);
+    }
+
+    svg {
+        fill: white;
+        width: 18px;
+        height: 18px;
+    }
+}
+
+.menuicon-active {
+    width: 25px;
+    height: 25px;
+    cursor: pointer;
+    text-align: center;
+    line-height: 25px;
+    margin: 0 2px;
+    border-radius: 3px;
+
+    &:hover {
+        background-color: black;
+    }
+
+    &> * {
+        margin-top: 50%;
+        margin-left: 50%;
+        transform: translate(-50%, -50%);
+    }
+
+    svg {
+        fill: greenyellow;
+        width: 18px;
+        height: 18px;
+    }
+}
+
+#colorPicker {
+    position: relative;
+
+    svg {
+        width: 18px;
+        height: 18px;
+        // margin-top: 11px;
+    }
+
+    .buttonColor {
+        position: absolute;
+        top: 24px;
+        left: 1px;
+        width: 24px;
+        height: 4px;
+        margin-top: 0;
+    }
+}
+
+#link-drag {
+    background-color: #323232;
+}
+
+.underline svg {
+    margin-top: 13px;
+}
+
+  .font-size-indicator {
+      font-size: 12px;
+      padding-right: 0px;
+  }
+  .summarize{
+      color: white;
+      height: 20px;
+      text-align: center;
+  }
+  
+
+.brush{
+    display: inline-block;
+    width: 1em;
+    height: 1em;
+    stroke-width: 0;
+    stroke: currentColor;
+    fill: currentColor;
+    margin-right: 15px;
+}
+
+.brush-active{
+    display: inline-block;
+    width: 1em;
+    height: 1em;
+    stroke-width: 3;
+    fill: greenyellow;
+    margin-right: 15px;
+}
+
+.dragger-wrapper {
+    color: #eee;
+    height: 22px;
+    padding: 0 5px;
+    box-sizing: content-box;
+    cursor: grab;
+
+    .dragger {
+        width: 18px;
+        height: 100%;
+        display: flex;
+        justify-content: space-evenly;
+    }
+
+    .dragger-line {
+        width: 2px;
+        height: 100%;
+        background-color: black;
+    }
+}
+
+.button-dropdown-wrapper {
+    display: flex;
+    align-content: center;
+
+    &:hover {
+        background-color: black;
+    }
+}
+
+.buttonSettings-dropdown {
+
+    &.ProseMirror-menu-dropdown {
+        width: 10px;
+        height: 25px;
+        margin: 0;
+        padding: 0 2px;
+        background-color: #323232;
+        text-align: center;
+
+        &:after {
+            border-top: 4px solid white;
+            right: 2px;
+        }
+
+        &:hover {
+            background-color: black;
+        }
+    }
+
+    &.ProseMirror-menu-dropdown-menu {
+        min-width: 150px;
+        left: -27px;
+        top: 31px;
+        background-color: #323232;
+        border: 1px solid #4d4d4d;
+        color: $light-color-secondary;
+        // border: none;
+        // border: 1px solid $light-color-secondary;
+        border-radius: 0 6px 6px 6px;
+        padding: 3px;
+        box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25);
+
+        .ProseMirror-menu-dropdown-item{
+            cursor: default;
+
+            &:last-child {
+                border-bottom: none;
+            }
+
+            &:hover {
+                background-color: #323232;
+            }
+
+            .button-setting, .button-setting-disabled {
+                padding: 2px;
+                border-radius: 2px;
+            }
+
+            .button-setting:hover {
+                cursor: pointer;
+                background-color: black;
+            }
+
+            .separated-button {
+                border-top: 1px solid $light-color-secondary;
+                padding-top: 6px;
+            }
+
+            input {
+               color: black;
+               border: none;
+               border-radius: 1px;
+               padding: 3px;
+            }
+
+            button {
+               padding: 6px;
+               background-color: #323232;
+               border: 1px solid black;
+               border-radius: 1px;
+
+                &:hover {
+                   background-color: black;
+                }
+            }
+        }
+
+
+    }
+}
+
+.colorPicker-wrapper {
+    display: flex;
+    flex-wrap: wrap;
+    justify-content: space-between;
+    margin-top: 3px;
+    margin-left: -3px;
+    width: calc(100% + 6px);
+}
+
+button.colorPicker {
+    width: 20px;
+    height: 20px;
+    border-radius: 15px !important;
+    margin: 3px;
+    border: none !important;
+
+    &.active {
+        border: 2px solid white !important;
+    }
+}
diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts
new file mode 100644
index 000000000..46bf481fb
--- /dev/null
+++ b/src/client/views/nodes/formattedText/marks_rts.ts
@@ -0,0 +1,296 @@
+import React = require("react");
+import { DOMOutputSpecArray, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model";
+import { Doc } from "../../../../new_fields/Doc";
+
+
+const emDOM: DOMOutputSpecArray = ["em", 0];
+const strongDOM: DOMOutputSpecArray = ["strong", 0];
+const codeDOM: DOMOutputSpecArray = ["code", 0];
+
+// :: Object [Specs](#model.MarkSpec) for the marks in the schema.
+export const marks: { [index: string]: MarkSpec } = {
+    // :: MarkSpec A link. Has `href` and `title` attributes. `title`
+    // defaults to the empty string. Rendered and parsed as an `<a>`
+    // element.
+    link: {
+        attrs: {
+            href: {},
+            targetId: { default: "" },
+            linkId: { default: "" },
+            showPreview: { default: true },
+            location: { default: null },
+            title: { default: null },
+            docref: { default: false } // flags whether the linked text comes from a document within Dash.  If so, an attribution label is appended after the text
+        },
+        inclusive: false,
+        parseDOM: [{
+            tag: "a[href]", getAttrs(dom: any) {
+                return { href: dom.getAttribute("href"), location: dom.getAttribute("location"), title: dom.getAttribute("title"), targetId: dom.getAttribute("id") };
+            }
+        }],
+        toDOM(node: any) {
+            return node.attrs.docref && node.attrs.title ?
+                ["div", ["span", `"`], ["span", 0], ["span", `"`], ["br"], ["a", { ...node.attrs, class: "prosemirror-attribution", title: `${node.attrs.title}` }, node.attrs.title], ["br"]] :
+                ["a", { ...node.attrs, id: node.attrs.linkId + node.attrs.targetId, title: `${node.attrs.title}` }, 0];
+        }
+    },
+
+
+    // :: MarkSpec Coloring on text. Has `color` attribute that defined the color of the marked text.
+    pFontColor: {
+        attrs: {
+            color: { default: "#000" }
+        },
+        inclusive: true,
+        parseDOM: [{
+            tag: "span", getAttrs(dom: any) {
+                return { color: dom.getAttribute("color") };
+            }
+        }],
+        toDOM(node: any) {
+            return node.attrs.color ? ['span', { style: 'color:' + node.attrs.color }] : ['span', 0];
+        }
+    },
+
+    marker: {
+        attrs: {
+            highlight: { default: "transparent" }
+        },
+        inclusive: true,
+        parseDOM: [{
+            tag: "span", getAttrs(dom: any) {
+                return { highlight: dom.getAttribute("backgroundColor") };
+            }
+        }],
+        toDOM(node: any) {
+            return node.attrs.highlight ? ['span', { style: 'background-color:' + node.attrs.highlight }] : ['span', { style: 'background-color: transparent' }];
+        }
+    },
+
+    // :: MarkSpec An emphasis mark. Rendered as an `<em>` element.
+    // Has parse rules that also match `<i>` and `font-style: italic`.
+    em: {
+        parseDOM: [{ tag: "i" }, { tag: "em" }, { style: "font-style: italic" }],
+        toDOM() { return emDOM; }
+    },
+
+    // :: MarkSpec A strong mark. Rendered as `<strong>`, parse rules
+    // also match `<b>` and `font-weight: bold`.
+    strong: {
+        parseDOM: [{ tag: "strong" },
+        { tag: "b" },
+        { style: "font-weight" }],
+        toDOM() { return strongDOM; }
+    },
+
+    strikethrough: {
+        parseDOM: [
+            { tag: 'strike' },
+            { style: 'text-decoration=line-through' },
+            { style: 'text-decoration-line=line-through' }
+        ],
+        toDOM: () => ['span', {
+            style: 'text-decoration-line:line-through'
+        }]
+    },
+
+    subscript: {
+        excludes: 'superscript',
+        parseDOM: [
+            { tag: 'sub' },
+            { style: 'vertical-align=sub' }
+        ],
+        toDOM: () => ['sub']
+    },
+
+    superscript: {
+        excludes: 'subscript',
+        parseDOM: [
+            { tag: 'sup' },
+            { style: 'vertical-align=super' }
+        ],
+        toDOM: () => ['sup']
+    },
+
+    mbulletType: {
+        attrs: {
+            bulletType: { default: "decimal" }
+        },
+        toDOM(node: any) {
+            return ['span', {
+                style: `background: ${node.attrs.bulletType === "decimal" ? "yellow" : node.attrs.bulletType === "upper-alpha" ? "blue" : "green"}`
+            }];
+        }
+    },
+
+    metadata: {
+        toDOM() {
+            return ['span', { style: 'font-size:75%; background:rgba(100, 100, 100, 0.2); ' }];
+        }
+    },
+    metadataKey: {
+        toDOM() {
+            return ['span', { style: 'font-style:italic; ' }];
+        }
+    },
+    metadataVal: {
+        toDOM() {
+            return ['span'];
+        }
+    },
+
+    summarizeInclusive: {
+        parseDOM: [
+            {
+                tag: "span",
+                getAttrs: (p: any) => {
+                    if (typeof (p) !== "string") {
+                        const style = getComputedStyle(p);
+                        if (style.textDecoration === "underline") return null;
+                        if (p.parentElement.outerHTML.indexOf("text-decoration: underline") !== -1 &&
+                            p.parentElement.outerHTML.indexOf("text-decoration-style: solid") !== -1) {
+                            return null;
+                        }
+                    }
+                    return false;
+                }
+            },
+        ],
+        inclusive: true,
+        toDOM() {
+            return ['span', {
+                style: 'text-decoration: underline; text-decoration-style: solid; text-decoration-color: rgba(204, 206, 210, 0.92)'
+            }];
+        }
+    },
+
+    summarize: {
+        inclusive: false,
+        parseDOM: [
+            {
+                tag: "span",
+                getAttrs: (p: any) => {
+                    if (typeof (p) !== "string") {
+                        const style = getComputedStyle(p);
+                        if (style.textDecoration === "underline") return null;
+                        if (p.parentElement.outerHTML.indexOf("text-decoration: underline") !== -1 &&
+                            p.parentElement.outerHTML.indexOf("text-decoration-style: dotted") !== -1) {
+                            return null;
+                        }
+                    }
+                    return false;
+                }
+            },
+        ],
+        toDOM() {
+            return ['span', {
+                style: 'text-decoration: underline; text-decoration-style: dotted; text-decoration-color: rgba(204, 206, 210, 0.92)'
+            }];
+        }
+    },
+
+    underline: {
+        parseDOM: [
+            {
+                tag: "span",
+                getAttrs: (p: any) => {
+                    if (typeof (p) !== "string") {
+                        const style = getComputedStyle(p);
+                        if (style.textDecoration === "underline" || p.parentElement.outerHTML.indexOf("text-decoration-style:line") !== -1) {
+                            return null;
+                        }
+                    }
+                    return false;
+                }
+            }
+            // { style: "text-decoration=underline" }
+        ],
+        toDOM: () => ['span', {
+            style: 'text-decoration:underline;text-decoration-style:line'
+        }]
+    },
+
+    search_highlight: {
+        attrs: {
+            selected: { default: false }
+        },
+        parseDOM: [{ style: 'background: yellow' }],
+        toDOM(node: any) {
+            return ['span', {
+                style: `background: ${node.attrs.selected ? "orange" : "yellow"}`
+            }];
+        }
+    },
+
+    // the id of the user who entered the text
+    user_mark: {
+        attrs: {
+            userid: { default: "" },
+            modified: { default: "when?" }, // 1 second intervals since 1970
+        },
+        group: "inline",
+        toDOM(node: any) {
+            const uid = node.attrs.userid.replace(".", "").replace("@", "");
+            const min = Math.round(node.attrs.modified / 12);
+            const hr = Math.round(min / 60);
+            const day = Math.round(hr / 60 / 24);
+            const remote = node.attrs.userid !== Doc.CurrentUserEmail ? " userMark-remote" : "";
+            return ['span', { class: "userMark-" + uid + remote + " userMark-min-" + min + " userMark-hr-" + hr + " userMark-day-" + day }, 0];
+        }
+    },
+    // the id of the user who entered the text
+    user_tag: {
+        attrs: {
+            userid: { default: "" },
+            modified: { default: "when?" }, // 1 second intervals since 1970
+            tag: { default: "" }
+        },
+        group: "inline",
+        inclusive: false,
+        toDOM(node: any) {
+            const uid = node.attrs.userid.replace(".", "").replace("@", "");
+            return ['span', { class: "userTag-" + uid + " userTag-" + node.attrs.tag }, 0];
+        }
+    },
+
+
+    // :: MarkSpec Code font mark. Represented as a `<code>` element.
+    code: {
+        parseDOM: [{ tag: "code" }],
+        toDOM() { return codeDOM; }
+    },
+
+    /* FONTS */
+    pFontFamily: {
+        attrs: {
+            family: { default: "Crimson Text" },
+        },
+        parseDOM: [{
+            tag: "span", getAttrs(dom: any) {
+                const cstyle = getComputedStyle(dom);
+                if (cstyle.font) {
+                    if (cstyle.font.indexOf("Times New Roman") !== -1) return { family: "Times New Roman" };
+                    if (cstyle.font.indexOf("Arial") !== -1) return { family: "Arial" };
+                    if (cstyle.font.indexOf("Georgia") !== -1) return { family: "Georgia" };
+                    if (cstyle.font.indexOf("Comic Sans") !== -1) return { family: "Comic Sans MS" };
+                    if (cstyle.font.indexOf("Tahoma") !== -1) return { family: "Tahoma" };
+                    if (cstyle.font.indexOf("Crimson") !== -1) return { family: "Crimson Text" };
+                }
+            }
+        }],
+        toDOM: (node) => ['span', {
+            style: `font-family: "${node.attrs.family}";`
+        }]
+    },
+
+    /** FONT SIZES */
+    pFontSize: {
+        attrs: {
+            fontSize: { default: 10 }
+        },
+        parseDOM: [{ style: 'font-size: 10px;' }],
+        toDOM: (node) => ['span', {
+            style: `font-size: ${node.attrs.fontSize}px;`
+        }]
+    },
+};
diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts
new file mode 100644
index 000000000..e7bcf444a
--- /dev/null
+++ b/src/client/views/nodes/formattedText/nodes_rts.ts
@@ -0,0 +1,264 @@
+import React = require("react");
+import { DOMOutputSpecArray, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model";
+import { bulletList, listItem, orderedList } from 'prosemirror-schema-list';
+import ParagraphNodeSpec from "./ParagraphNodeSpec";
+
+const blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"],
+    preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0];
+
+// :: Object
+// [Specs](#model.NodeSpec) for the nodes defined in this schema.
+export const nodes: { [index: string]: NodeSpec } = {
+    // :: NodeSpec The top level document node.
+    doc: {
+        content: "block+"
+    },
+
+    footnote: {
+        group: "inline",
+        content: "inline*",
+        inline: true,
+        attrs: {
+            visibility: { default: false }
+        },
+        // This makes the view treat the node as a leaf, even though it
+        // technically has content
+        atom: true,
+        toDOM: () => ["footnote", 0],
+        parseDOM: [{ tag: "footnote" }]
+    },
+
+    paragraph: ParagraphNodeSpec,
+
+    // :: NodeSpec A blockquote (`<blockquote>`) wrapping one or more blocks.
+    blockquote: {
+        content: "block+",
+        group: "block",
+        defining: true,
+        parseDOM: [{ tag: "blockquote" }],
+        toDOM() { return blockquoteDOM; }
+    },
+
+    // :: NodeSpec A horizontal rule (`<hr>`).
+    horizontal_rule: {
+        group: "block",
+        parseDOM: [{ tag: "hr" }],
+        toDOM() { return hrDOM; }
+    },
+
+    // :: NodeSpec A heading textblock, with a `level` attribute that
+    // should hold the number 1 to 6. Parsed and serialized as `<h1>` to
+    // `<h6>` elements.
+    heading: {
+        attrs: { level: { default: 1 } },
+        content: "inline*",
+        group: "block",
+        defining: true,
+        parseDOM: [{ tag: "h1", attrs: { level: 1 } },
+        { tag: "h2", attrs: { level: 2 } },
+        { tag: "h3", attrs: { level: 3 } },
+        { tag: "h4", attrs: { level: 4 } },
+        { tag: "h5", attrs: { level: 5 } },
+        { tag: "h6", attrs: { level: 6 } }],
+        toDOM(node: any) { return ["h" + node.attrs.level, 0]; }
+    },
+
+    // :: NodeSpec A code listing. Disallows marks or non-text inline
+    // nodes by default. Represented as a `<pre>` element with a
+    // `<code>` element inside of it.
+    code_block: {
+        content: "text*",
+        marks: "",
+        group: "block",
+        code: true,
+        defining: true,
+        parseDOM: [{ tag: "pre", preserveWhitespace: "full" }],
+        toDOM() { return preDOM; }
+    },
+
+    // :: NodeSpec The text node.
+    text: {
+        group: "inline"
+    },
+
+    dashComment: {
+        attrs: {
+            docid: { default: "" },
+        },
+        inline: true,
+        group: "inline",
+        toDOM(node) {
+            const attrs = { style: `width: 40px` };
+            return ["span", { ...node.attrs, ...attrs }, "←"];
+        },
+    },
+
+    summary: {
+        inline: true,
+        attrs: {
+            visibility: { default: false },
+            text: { default: undefined },
+            textslice: { default: undefined },
+        },
+        group: "inline",
+        toDOM(node) {
+            const attrs = { style: `width: 40px` };
+            return ["span", { ...node.attrs, ...attrs }];
+        },
+    },
+
+    // :: NodeSpec An inline image (`<img>`) node. Supports `src`,
+    // `alt`, and `href` attributes. The latter two default to the empty
+    // string.
+    image: {
+        inline: true,
+        attrs: {
+            src: {},
+            agnostic: { default: null },
+            width: { default: 100 },
+            alt: { default: null },
+            title: { default: null },
+            float: { default: "left" },
+            location: { default: "onRight" },
+            docid: { default: "" }
+        },
+        group: "inline",
+        draggable: true,
+        parseDOM: [{
+            tag: "img[src]", getAttrs(dom: any) {
+                return {
+                    src: dom.getAttribute("src"),
+                    title: dom.getAttribute("title"),
+                    alt: dom.getAttribute("alt"),
+                    width: Math.min(100, Number(dom.getAttribute("width"))),
+                };
+            }
+        }],
+        // TODO if we don't define toDom, dragging the image crashes. Why?
+        toDOM(node) {
+            const attrs = { style: `width: ${node.attrs.width}` };
+            return ["img", { ...node.attrs, ...attrs }];
+        }
+    },
+
+    dashDoc: {
+        inline: true,
+        attrs: {
+            width: { default: 200 },
+            height: { default: 100 },
+            title: { default: null },
+            float: { default: "right" },
+            location: { default: "onRight" },
+            hidden: { default: false },
+            fieldKey: { default: "" },
+            docid: { default: "" },
+            alias: { default: "" }
+        },
+        group: "inline",
+        draggable: false,
+        toDOM(node) {
+            const attrs = { style: `width: ${node.attrs.width}, height: ${node.attrs.height}` };
+            return ["div", { ...node.attrs, ...attrs }];
+        }
+    },
+
+    dashField: {
+        inline: true,
+        attrs: {
+            fieldKey: { default: "" },
+            docid: { default: "" }
+        },
+        group: "inline",
+        draggable: false,
+        toDOM(node) {
+            const attrs = { style: `width: ${node.attrs.width}, height: ${node.attrs.height}` };
+            return ["div", { ...node.attrs, ...attrs }];
+        }
+    },
+
+    video: {
+        inline: true,
+        attrs: {
+            src: {},
+            width: { default: "100px" },
+            alt: { default: null },
+            title: { default: null }
+        },
+        group: "inline",
+        draggable: true,
+        parseDOM: [{
+            tag: "video[src]", getAttrs(dom: any) {
+                return {
+                    src: dom.getAttribute("src"),
+                    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 }];
+        }
+    },
+
+    // :: NodeSpec A hard line break, represented in the DOM as `<br>`.
+    hard_break: {
+        inline: true,
+        group: "inline",
+        selectable: false,
+        parseDOM: [{ tag: "br" }],
+        toDOM() { return brDOM; }
+    },
+
+    ordered_list: {
+        ...orderedList,
+        content: 'list_item+',
+        group: 'block',
+        attrs: {
+            bulletStyle: { default: 0 },
+            mapStyle: { default: "decimal" },
+            setFontSize: { default: undefined },
+            setFontFamily: { default: "inherit" },
+            setFontColor: { default: "inherit" },
+            inheritedFontSize: { default: undefined },
+            visibility: { default: true },
+            indent: { default: undefined }
+        },
+        toDOM(node: Node<any>) {
+            if (node.attrs.mapStyle === "bullet") return ['ul', 0];
+            const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : "";
+            const fsize = node.attrs.setFontSize ? node.attrs.setFontSize : node.attrs.inheritedFontSize;
+            const ffam = node.attrs.setFontFamily;
+            const color = node.attrs.setFontColor;
+            return node.attrs.visibility ?
+                ['ol', { class: `${map}-ol`, style: `list-style: none; font-size: ${fsize}; font-family: ${ffam}; color:${color}; margin-left: ${node.attrs.indent}` }, 0] :
+                ['ol', { class: `${map}-ol`, style: `list-style: none;` }];
+        }
+    },
+
+    bullet_list: {
+        ...bulletList,
+        content: 'list_item+',
+        group: 'block',
+        // parseDOM: [{ tag: "ul" }, { style: 'list-style-type=disc' }],
+        toDOM(node: Node<any>) {
+            return ['ul', 0];
+        }
+    },
+
+    list_item: {
+        attrs: {
+            bulletStyle: { default: 0 },
+            mapStyle: { default: "decimal" },
+            visibility: { default: true }
+        },
+        ...listItem,
+        content: 'paragraph block*',
+        toDOM(node: any) {
+            const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : "";
+            return node.attrs.visibility ? ["li", { class: `${map}` }, 0] : ["li", { class: `${map}` }, "..."];
+            //return ["li", { class: `${map}` }, 0];
+        }
+    },
+};
\ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/prosemirrorPatches.js b/src/client/views/nodes/formattedText/prosemirrorPatches.js
new file mode 100644
index 000000000..269423482
--- /dev/null
+++ b/src/client/views/nodes/formattedText/prosemirrorPatches.js
@@ -0,0 +1,139 @@
+'use strict';
+
+Object.defineProperty(exports, '__esModule', { value: true });
+
+var prosemirrorInputRules = require('prosemirror-inputrules');
+var prosemirrorTransform = require('prosemirror-transform');
+var prosemirrorModel = require('prosemirror-model');
+
+exports.liftListItem = liftListItem;
+exports.sinkListItem = sinkListItem;
+exports.wrappingInputRule = wrappingInputRule;
+// :: (NodeType) → (state: EditorState, dispatch: ?(tr: Transaction)) → bool
+// Create a command to lift the list item around the selection up into
+// a wrapping list.
+function liftListItem(itemType) {
+    return function (tx, dispatch) {
+        var ref = tx.selection;
+        var $from = ref.$from;
+        var $to = ref.$to;
+        var range = $from.blockRange($to, function (node) { return node.childCount && node.firstChild.type == itemType; });
+        if (!range) { return false }
+        if (!dispatch) { return true }
+        if ($from.node(range.depth - 1).type == itemType) // Inside a parent list
+        { return liftToOuterList(tx, dispatch, itemType, range) }
+        else // Outer list node
+        { return liftOutOfList(tx, dispatch, range) }
+    }
+}
+
+function liftToOuterList(tr, dispatch, itemType, range) {
+    var end = range.end, endOfList = range.$to.end(range.depth);
+    if (end < endOfList) {
+        // There are siblings after the lifted items, which must become
+        // children of the last item
+        tr.step(new prosemirrorTransform.ReplaceAroundStep(end - 1, endOfList, end, endOfList,
+            new prosemirrorModel.Slice(prosemirrorModel.Fragment.from(itemType.create(null, range.parent.copy())), 1, 0), 1, true));
+        range = new prosemirrorModel.NodeRange(tr.doc.resolve(range.$from.pos), tr.doc.resolve(endOfList), range.depth);
+    }
+    dispatch(tr.lift(range, prosemirrorTransform.liftTarget(range)).scrollIntoView());
+    return true
+}
+
+function liftOutOfList(tr, dispatch, range) {
+    var list = range.parent;
+    // Merge the list items into a single big item
+    for (var pos = range.end, i = range.endIndex - 1, e = range.startIndex; i > e; i--) {
+        pos -= list.child(i).nodeSize;
+        tr.delete(pos - 1, pos + 1);
+    }
+    var $start = tr.doc.resolve(range.start), item = $start.nodeAfter;
+    var atStart = range.startIndex == 0, atEnd = range.endIndex == list.childCount;
+    var parent = $start.node(-1), indexBefore = $start.index(-1);
+    if (!parent.canReplace(indexBefore + (atStart ? 0 : 1), indexBefore + 1,
+        item.content.append(atEnd ? prosemirrorModel.Fragment.empty : prosemirrorModel.Fragment.from(list)))) { return false }
+    var start = $start.pos, end = start + item.nodeSize;
+    // Strip off the surrounding list. At the sides where we're not at
+    // the end of the list, the existing list is closed. At sides where
+    // this is the end, it is overwritten to its end.
+    tr.step(new prosemirrorTransform.ReplaceAroundStep(start - (atStart ? 1 : 0), end + (atEnd ? 1 : 0), start + 1, end - 1,
+        new prosemirrorModel.Slice((atStart ? prosemirrorModel.Fragment.empty : prosemirrorModel.Fragment.from(list.copy(prosemirrorModel.Fragment.empty)))
+            .append(atEnd ? prosemirrorModel.Fragment.empty : prosemirrorModel.Fragment.from(list.copy(prosemirrorModel.Fragment.empty))),
+            atStart ? 0 : 1, atEnd ? 0 : 1), atStart ? 0 : 1));
+    dispatch(tr.scrollIntoView());
+    return true
+}
+
+// :: (NodeType) → (state: EditorState, dispatch: ?(tr: Transaction)) → bool
+// Create a command to sink the list item around the selection down
+// into an inner list.
+function sinkListItem(itemType) {
+    return function (state, dispatch) {
+        var ref = state.selection;
+        var $from = ref.$from;
+        var $to = ref.$to;
+        var range = $from.blockRange($to, function (node) { return node.childCount && node.firstChild.type == itemType; });
+        if (!range) { return false }
+        var startIndex = range.startIndex;
+        if (startIndex == 0) { return false }
+        var parent = range.parent, nodeBefore = parent.child(startIndex - 1);
+        if (nodeBefore.type != itemType) { return false; }
+
+        if (dispatch) {
+            var nestedBefore = nodeBefore.lastChild && nodeBefore.lastChild.type == parent.type;
+            var inner = prosemirrorModel.Fragment.from(nestedBefore ? itemType.create() : null);
+            let slice = new prosemirrorModel.Slice(prosemirrorModel.Fragment.from(itemType.create(null, prosemirrorModel.Fragment.from(parent.type.create({ ...parent.attrs, fontSize: parent.attrs.fontSize ? parent.attrs.fontSize - 4 : undefined }, inner)))),
+                nestedBefore ? 3 : 1, 0);
+            var before = range.start, after = range.end;
+            dispatch(state.tr.step(new prosemirrorTransform.ReplaceAroundStep(before - (nestedBefore ? 3 : 1), after,
+                before, after, slice, 1, true))
+                .scrollIntoView());
+        }
+        return true
+    }
+}
+
+function findWrappingOutside(range, type) {
+    var parent = range.parent;
+    var startIndex = range.startIndex;
+    var endIndex = range.endIndex;
+    var around = parent.contentMatchAt(startIndex).findWrapping(type);
+    if (!around) { return null }
+    var outer = around.length ? around[0] : type;
+    return parent.canReplaceWith(startIndex, endIndex, outer) ? around : null
+}
+
+function findWrappingInside(range, type) {
+    var parent = range.parent;
+    var startIndex = range.startIndex;
+    var endIndex = range.endIndex;
+    var inner = parent.child(startIndex);
+    var inside = type.contentMatch.findWrapping(inner.type);
+    if (!inside) { return null }
+    var lastType = inside.length ? inside[inside.length - 1] : type;
+    var innerMatch = lastType.contentMatch;
+    for (var i = startIndex; innerMatch && i < endIndex; i++) { innerMatch = innerMatch.matchType(parent.child(i).type); }
+    if (!innerMatch || !innerMatch.validEnd) { return null }
+    return inside
+}
+function findWrapping(range, nodeType, attrs, innerRange, customWithAttrs = null) {
+    if (innerRange === void 0) innerRange = range;
+    let withAttrs = (type) => ({ type: type, attrs: null });
+    var around = findWrappingOutside(range, nodeType);
+    var inner = around && findWrappingInside(innerRange, nodeType);
+    if (!inner) { return null }
+    return around.map(withAttrs).concat({ type: nodeType, attrs: attrs }).concat(inner.map(customWithAttrs ? customWithAttrs : withAttrs))
+}
+function wrappingInputRule(regexp, nodeType, getAttrs, joinPredicate, customWithAttrs = null) {
+    return new prosemirrorInputRules.InputRule(regexp, function (state, match, start, end) {
+        var attrs = getAttrs instanceof Function ? getAttrs(match) : getAttrs;
+        var tr = state.tr.delete(start, end);
+        var $start = tr.doc.resolve(start), range = $start.blockRange(), wrapping = range && findWrapping(range, nodeType, attrs, undefined, customWithAttrs);
+        if (!wrapping) { return null }
+        tr.wrap(range, wrapping);
+        var before = tr.doc.resolve(start - 1).nodeBefore;
+        if (before && before.type == nodeType && prosemirrorTransform.canJoin(tr.doc, start - 1) &&
+            (!joinPredicate || joinPredicate(match, before))) { tr.join(start - 1); }
+        return tr
+    })
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/schema_rts.ts b/src/client/views/nodes/formattedText/schema_rts.ts
new file mode 100644
index 000000000..83561073c
--- /dev/null
+++ b/src/client/views/nodes/formattedText/schema_rts.ts
@@ -0,0 +1,26 @@
+import { Schema, Slice } from "prosemirror-model";
+
+import { nodes } from "./nodes_rts";
+import { marks } from "./marks_rts";
+
+
+// :: Schema
+// This schema rougly corresponds to the document schema used by
+// [CommonMark](http://commonmark.org/), minus the list elements,
+// which are defined in the [`prosemirror-schema-list`](#schema-list)
+// module.
+//
+// To reuse elements from this schema, extend or read from its
+// `spec.nodes` and `spec.marks` [properties](#model.Schema.spec).
+
+export const schema = new Schema({ nodes, marks });
+
+const fromJson = schema.nodeFromJSON;
+
+schema.nodeFromJSON = (json: any) => {
+    const node = fromJson(json);
+    if (json.type === schema.nodes.summary.name) {
+        node.attrs.text = Slice.fromJSON(schema, node.attrs.textslice);
+    }
+    return node;
+};
\ No newline at end of file
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index 73ebbb303..69a80e1b4 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -7,7 +7,7 @@ import { observer } from 'mobx-react';
 import { DocServer } from '../client/DocServer';
 import { Docs } from '../client/documents/Documents';
 import { DocumentManager } from '../client/util/DocumentManager';
-import RichTextMenu from '../client/util/RichTextMenu';
+import RichTextMenu from '../client/views/nodes/formattedText/RichTextMenu';
 import { Scripting } from '../client/util/Scripting';
 import { Transform } from '../client/util/Transform';
 import { CollectionView } from '../client/views/collections/CollectionView';
diff --git a/src/new_fields/RichTextUtils.ts b/src/new_fields/RichTextUtils.ts
index c9960e783..c475d0d73 100644
--- a/src/new_fields/RichTextUtils.ts
+++ b/src/new_fields/RichTextUtils.ts
@@ -4,11 +4,11 @@ import { Fragment, Mark, Node } from "prosemirror-model";
 import { sinkListItem } from "prosemirror-schema-list";
 import { Utils } from "../Utils";
 import { Docs } from "../client/documents/Documents";
-import { schema } from "../client/util/schema_rts";
+import { schema } from "../client/views/nodes/formattedText/schema_rts";
 import { GooglePhotos } from "../client/apis/google_docs/GooglePhotosClientUtils";
 import { DocServer } from "../client/DocServer";
 import { Networking } from "../client/Network";
-import { FormattedTextBox } from "../client/views/nodes/FormattedTextBox";
+import { FormattedTextBox } from "../client/views/nodes/formattedText/FormattedTextBox";
 import { Doc, Opt } from "./Doc";
 import { Id } from "./FieldSymbols";
 import { RichTextField } from "./RichTextField";
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 1d41c3570..08dc21460 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -17,7 +17,7 @@ import { CollectionViewType } from "../../../client/views/collections/Collection
 import { makeTemplate } from "../../../client/util/DropConverter";
 import { RichTextField } from "../../../new_fields/RichTextField";
 import { PrefetchProxy } from "../../../new_fields/Proxy";
-import { FormattedTextBox } from "../../../client/views/nodes/FormattedTextBox";
+import { FormattedTextBox } from "../../../client/views/nodes/formattedText/FormattedTextBox";
 import { MainView } from "../../../client/views/MainView";
 import { DocumentType } from "../../../client/documents/DocumentTypes";
 import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
-- 
cgit v1.2.3-70-g09d2

