From c7746aea51bd6891ac10f152bd8f09e5d8ddcfd9 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 7 Oct 2019 08:16:34 -0400
Subject: initial tests

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

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

diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 1109fd292..f5749a01b 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -770,8 +770,15 @@ export class DashDocView {
         let self = this;
         this._dashSpan.onclick = function (e: any) {
             FormattedTextBox.firstTarget && FormattedTextBox.firstTarget();
+        };
+        this._dashSpan.onpointermove = function (e: any) {
+            (e as any).formattedHandled = true;
+        };
+        this._dashSpan.onpointerup = function (e: any) {
             e.stopPropagation();
         };
+        this._dashSpan.onpointerdown = function (e: any) {
+        };
         this._dashSpan.onkeydown = function (e: any) { e.stopPropagation(); };
         this._dashSpan.onkeypress = function (e: any) { e.stopPropagation(); };
         this._dashSpan.onkeyup = function (e: any) { e.stopPropagation(); };
-- 
cgit v1.2.3-70-g09d2


From 8c9d58c506d986d5dbdd087b429a449c8283ac12 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Mon, 7 Oct 2019 12:59:41 -0400
Subject: many changes to support nested documents within a  text box

---
 src/client/util/RichTextSchema.tsx                 | 86 ++++++++++++----------
 src/client/util/TooltipTextMenu.tsx                |  9 ++-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  2 +-
 src/client/views/nodes/DocumentView.tsx            |  3 +-
 src/client/views/nodes/FormattedTextBox.tsx        | 39 +++++-----
 5 files changed, 73 insertions(+), 66 deletions(-)

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

diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index f5749a01b..29ec5b23c 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -1,24 +1,23 @@
+import { action, observable, runInAction, reaction, IReactionDisposer } 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, TextSelection, NodeSelection } from "prosemirror-state";
+import { EditorState, NodeSelection, TextSelection } from "prosemirror-state";
 import { StepMap } from "prosemirror-transform";
 import { EditorView } from "prosemirror-view";
-import { Doc } from "../../new_fields/Doc";
-import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
+import * as ReactDOM from 'react-dom';
+import { Doc, WidthSym, HeightSym } from "../../new_fields/Doc";
+import { emptyFunction, returnEmptyString, returnFalse, returnOne, Utils } from "../../Utils";
 import { DocServer } from "../DocServer";
+import { DocumentView } from "../views/nodes/DocumentView";
 import { DocumentManager } from "./DocumentManager";
 import ParagraphNodeSpec from "./ParagraphNodeSpec";
-import React = require("react");
-import { action, Lambda, observable, reaction, computed, runInAction, trace } from "mobx";
-import { observer } from "mobx-react";
-import * as ReactDOM from 'react-dom';
-import { DocumentView } from "../views/nodes/DocumentView";
-import { returnFalse, emptyFunction, returnEmptyString, returnOne } from "../../Utils";
 import { Transform } from "./Transform";
-import { NumCast } from "../../new_fields/Types";
+import React = require("react");
+import { BoolCast } from "../../new_fields/Types";
+import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
 
 const pDOM: DOMOutputSpecArray = ["p", 0], blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"],
     preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0];
@@ -716,7 +715,16 @@ export class DashDocView {
     _handle: HTMLElement;
     _dashSpan: HTMLDivElement;
     _outer: HTMLElement;
-    constructor(node: any, view: any, getPos: any, addDocTab: any) {
+    _dashDoc: Doc | undefined;
+    _reactionDisposer: IReactionDisposer | undefined;
+    _textBox: FormattedTextBox;
+
+    getDocTransform = () => {
+        let { scale, translateX, translateY } = Utils.GetScreenTransform(this._outer);
+        return new Transform(-translateX, -translateY, 1).scale(1 / scale);
+    }
+    constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
+        this._textBox = tbox;
         this._handle = document.createElement("span");
         this._dashSpan = document.createElement("div");
         this._outer = document.createElement("span");
@@ -741,19 +749,28 @@ export class DashDocView {
         this._handle.style.right = "-10px";
         DocServer.GetRefField(node.attrs.docid).then(async dashDoc => {
             if (dashDoc instanceof Doc) {
-                let scale = () => 100 / NumCast(dashDoc.nativeWidth, 100);
+                self._dashDoc = dashDoc;
+                if (node.attrs.width !== dashDoc.width + "px" || node.attrs.height !== dashDoc.height + "px") {
+                    view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, { ...node.attrs, width: dashDoc.width + "px", height: dashDoc.height + "px" }));
+                }
+                this._reactionDisposer && this._reactionDisposer();
+                this._reactionDisposer = reaction(() => {
+                    return dashDoc[HeightSym]();
+                }, () => {
+                    this._dashSpan.style.height = this._outer.style.height = dashDoc[HeightSym]() + "px";
+                });
                 ReactDOM.render(<DocumentView
-                    fitToBox={true}
+                    fitToBox={BoolCast(dashDoc.fitToBox)}
                     Document={dashDoc}
                     addDocument={returnFalse}
                     removeDocument={returnFalse}
                     ruleProvider={undefined}
-                    ScreenToLocalTransform={Transform.Identity}
-                    addDocTab={returnFalse}
+                    ScreenToLocalTransform={this.getDocTransform}
+                    addDocTab={self._textBox.props.addDocTab}
                     pinToPres={returnFalse}
                     renderDepth={1}
-                    PanelWidth={() => 100}
-                    PanelHeight={() => 100}
+                    PanelWidth={self._dashDoc![WidthSym]}
+                    PanelHeight={self._dashDoc![HeightSym]}
                     focus={emptyFunction}
                     backgroundColor={returnEmptyString}
                     parentActive={returnFalse}
@@ -763,24 +780,16 @@ export class DashDocView {
                     getScale={returnOne}
                     ContainingCollectionView={undefined}
                     ContainingCollectionDoc={undefined}
-                    ContentScaling={scale}
-                ></DocumentView>, this._dashSpan);
+                    ContentScaling={returnOne}
+                />, this._dashSpan);
             }
         });
         let self = this;
-        this._dashSpan.onclick = function (e: any) {
-            FormattedTextBox.firstTarget && FormattedTextBox.firstTarget();
-        };
-        this._dashSpan.onpointermove = function (e: any) {
-            (e as any).formattedHandled = true;
-        };
-        this._dashSpan.onpointerup = function (e: any) {
-            e.stopPropagation();
-        };
-        this._dashSpan.onpointerdown = function (e: any) {
-        };
+        this._outer.onpointerenter = function (e: any) { self.selectNode(); };
+        this._outer.onpointerleave = function (e: any) { self.deselectNode(); };
         this._dashSpan.onkeydown = function (e: any) { e.stopPropagation(); };
         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._handle.onpointerdown = function (e: any) {
             e.preventDefault();
@@ -789,19 +798,20 @@ export class DashDocView {
             const startY = e.pageY;
             const startWidth = parseFloat(node.attrs.width);
             const startHeight = parseFloat(node.attrs.height);
-            const onpointermove = (e: any) => {
-                const diffInPx = e.pageX - startX;
-                const diffInPy = e.pageY - startY;
-                self._outer.style.width = `${startWidth + diffInPx}`;
-                self._outer.style.height = `${startHeight + diffInPy}`;
-            };
+            const onpointermove = action((e: any) => {
+                let { scale } = Utils.GetScreenTransform(self._handle as HTMLElement);
+                const diffInPx = (e.pageX - startX) / scale;
+                const diffInPy = (e.pageY - startY) / scale;
+                self._dashDoc!.width = startWidth + diffInPx;
+                self._dashDoc!.height = startHeight + diffInPy;
+                self._outer.style.width = `${self._dashDoc!.width}`;
+                self._outer.style.height = `${self._dashDoc!.height}`;
+            });
 
             const onpointerup = () => {
                 document.removeEventListener("pointermove", onpointermove);
                 document.removeEventListener("pointerup", onpointerup);
-                let 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);
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 9116ef995..55c6e6609 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -186,7 +186,7 @@ export class TooltipTextMenu {
 
         this.updateListItemDropdown(":", this.listTypeBtnDom);
 
-        this.update(view, undefined, undefined);
+        this.updateInternal(view, undefined, undefined);
         TooltipTextMenu.Toolbar = this.wrapper;
     }
     public static Toolbar: HTMLDivElement | undefined;
@@ -439,7 +439,7 @@ export class TooltipTextMenu {
         let tr = state.tr;
         tr.addMark(state.selection.from, state.selection.to, mark);
         let content = tr.selection.content();
-        let newNode = schema.nodes.star.create({ visibility: false, text: content, textslice: content.toJSON() });
+        let newNode = state.schema.nodes.star.create({ visibility: false, text: content, textslice: content.toJSON() });
         dispatch && dispatch(tr.replaceSelectionWith(newNode).removeMark(tr.selection.from - 1, tr.selection.from, mark));
         return true;
     }
@@ -587,7 +587,7 @@ export class TooltipTextMenu {
             class: "summarize",
             execEvent: "",
             run: (state, dispatch) => {
-                TooltipTextMenu.insertStar(state, dispatch);
+                TooltipTextMenu.insertStar(this.view.state, this.view.dispatch);
             }
 
         });
@@ -849,8 +849,9 @@ export class TooltipTextMenu {
         }
     }
 
+    update(view: EditorView, lastState: EditorState | undefined) { this.updateInternal(view, lastState, this.editorProps) }
     //updates the tooltip menu when the selection changes
-    update(view: EditorView, lastState: EditorState | undefined, props: any) {
+    private updateInternal(view: EditorView, lastState: EditorState | undefined, props: any) {
         this.view = view;
         let state = view.state;
         props && (this.editorProps = props);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index cf76945e0..38488f033 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -267,7 +267,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
     @action
     onPointerDown = (e: React.PointerEvent): void => {
-        if (e.nativeEvent.cancelBubble || (e.nativeEvent as any).formattedHandled) return;
+        if (e.nativeEvent.cancelBubble) return;
         this._hitCluster = this.props.Document.useClusters ? this.pickCluster(this.getTransform().transformPoint(e.clientX, e.clientY)) !== -1 : false;
         if (e.button === 0 && !e.shiftKey && !e.altKey && (!this.isAnnotationOverlay || this.zoomScaling() !== 1) && this.props.active()) {
             document.removeEventListener("pointermove", this.onPointerMove);
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 5a9202d40..a670d280d 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -202,7 +202,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             }
             preventDefault && e.preventDefault();
         }
-        if ((e.nativeEvent as any).formattedHandled) { e.stopPropagation(); }
     }
 
     buttonClick = async (altKey: boolean, ctrlKey: boolean) => {
@@ -234,7 +233,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     }
 
     onPointerDown = (e: React.PointerEvent): void => {
-        if (e.nativeEvent.cancelBubble) return;
+        if (e.nativeEvent.cancelBubble && e.button === 0) return;
         this._downX = e.clientX;
         this._downY = e.clientY;
         this._hitTemplateDrag = false;
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 9396e3ea1..c030d4bce 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -268,9 +268,11 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 let model: NodeType = [".mov", ".mp4"].includes(url) ? schema.nodes.video : schema.nodes.image;
                 node = model.create({ src: url, docid: link[Id] });
             } else {
+                let alias = Doc.MakeAlias(target);
+                alias.fitToBox = true;
                 node = schema.nodes.dashDoc.create({
                     width: target[WidthSym](), height: target[HeightSym](),
-                    title: "dashDoc", docid: target[Id],
+                    title: "dashDoc", docid: alias[Id],
                     float: "none"
                 });
             }
@@ -700,7 +702,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 },
                 dispatchTransaction: this.dispatchTransaction,
                 nodeViews: {
-                    dashDoc(node, view, getPos) { return new DashDocView(node, view, getPos, self.props.addDocTab); },
+                    dashDoc(node, view, getPos) { return new DashDocView(node, view, getPos, self); },
                     image(node, view, getPos) { return new ImageResizeView(node, view, getPos, self.props.addDocTab); },
                     star(node, view, getPos) { return new SummarizedView(node, view, getPos); },
                     ordered_list(node, view, getPos) { return new OrderedListView(); },
@@ -749,10 +751,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         this._searchReactionDisposer && this._searchReactionDisposer();
         this._editorView && this._editorView.destroy();
     }
-    public static firstTarget: () => void = emptyFunction;
     onPointerDown = (e: React.PointerEvent): void => {
-        if ((e.nativeEvent as any).formattedHandled) { e.stopPropagation(); return; }
-        (e.nativeEvent as any).formattedHandled = true;
         let pos = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY });
         pos && (this._nodeClicked = this._editorView!.state.doc.nodeAt(pos.pos));
         if (this.props.onClick && e.button === 0) {
@@ -764,16 +763,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         if (e.button === 2 || (e.button === 0 && e.ctrlKey)) {
             e.preventDefault();
         }
-        FormattedTextBox.firstTarget = () => {  // this is here to support nested text boxes.  when that happens, the click event will propagate through prosemirror to the outer editor.  In RichTextSchema, the outer editor calls this function to revert the focus/selection
-            if (pos && pos.pos > 0) {
-                let node = this._editorView!.state.doc.nodeAt(pos.pos);
-                if (!node || (node.type !== this._editorView!.state.schema.nodes.dashDoc && node.type !== this._editorView!.state.schema.nodes.image &&
-                    pos.pos !== this._editorView!.state.selection.from)) {
-                    this._editorView!.dispatch(this._editorView!.state.tr.setSelection(new TextSelection(this._editorView!.state.doc.resolve(pos!.pos))));
-                    this._editorView!.focus();
-                }
-            }
-        };
     }
 
     onPointerUp = (e: React.PointerEvent): void => {
@@ -832,7 +821,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 e.stopPropagation();
                 e.preventDefault();
             }
-
         }
         // 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.
         if (this.props.isSelected() && e.nativeEvent.offsetX < 40) {
@@ -858,14 +846,23 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         }
         this._editorView!.focus();
     }
-    onMouseDown = (e: React.MouseEvent): void => {
-        if (!this.props.isSelected()) { // preventing default allows the onClick to be generated instead of being swallowed by the text box itself
-            e.preventDefault(); // bcz: this would normally be in OnPointerDown - however, if done there, no mouse move events will be generated which makes transititioning to GoldenLayout's drag interactions impossible
+    onMouseUp = (e: React.MouseEvent): void => {
+        e.stopPropagation();
+
+        // 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 ((this._editorView as any).mouseDown) {
+            let originalUpHandler = (this._editorView as any).mouseDown.up;
+            (this._editorView as any).root.removeEventListener("mouseup", originalUpHandler);
+            (this._editorView as any).mouseDown.up = (e: MouseEvent) => {
+                !(e as any).formattedHandled && originalUpHandler(e);
+                e.stopPropagation();
+                (e as any).formattedHandled = true;
+            };
+            (this._editorView as any).root.addEventListener("mouseup", (this._editorView as any).mouseDown.up);
         }
     }
 
     tooltipTextMenuPlugin() {
-        let myprops = this.props;
         let self = FormattedTextBox;
         return new Plugin({
             view(_editorView) {
@@ -944,7 +941,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 onBlur={this.onBlur}
                 onPointerUp={this.onPointerUp}
                 onPointerDown={this.onPointerDown}
-                onMouseDown={this.onMouseDown}
+                onMouseUp={this.onMouseUp}
                 onWheel={this.onPointerWheel}
                 onPointerEnter={action(() => this._entered = true)}
                 onPointerLeave={action(() => this._entered = false)}
-- 
cgit v1.2.3-70-g09d2


From 463cf0c4a6225fe01492510238973eabc1577fd5 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Mon, 7 Oct 2019 14:48:54 -0400
Subject: added deleting ndested nodes from text boxes

---
 src/client/util/RichTextSchema.tsx          | 72 ++++++-----------------------
 src/client/views/nodes/FormattedTextBox.tsx | 21 +++------
 2 files changed, 21 insertions(+), 72 deletions(-)

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

diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 29ec5b23c..432e0c2fb 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -16,7 +16,7 @@ import { DocumentManager } from "./DocumentManager";
 import ParagraphNodeSpec from "./ParagraphNodeSpec";
 import { Transform } from "./Transform";
 import React = require("react");
-import { BoolCast } from "../../new_fields/Types";
+import { BoolCast, NumCast } from "../../new_fields/Types";
 import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
 
 const pDOM: DOMOutputSpecArray = ["p", 0], blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"],
@@ -712,7 +712,6 @@ export class ImageResizeView {
 }
 
 export class DashDocView {
-    _handle: HTMLElement;
     _dashSpan: HTMLDivElement;
     _outer: HTMLElement;
     _dashDoc: Doc | undefined;
@@ -721,11 +720,11 @@ export class DashDocView {
 
     getDocTransform = () => {
         let { scale, translateX, translateY } = Utils.GetScreenTransform(this._outer);
-        return new Transform(-translateX, -translateY, 1).scale(1 / scale);
+        return new Transform(-translateX, -translateY, 1).scale(1 / this.contentScaling() / scale);
     }
+    contentScaling = () => NumCast(this._dashDoc!.nativeWidth) > 0 && !this._dashDoc!.ignoreAspect ? this._dashDoc![WidthSym]() / NumCast(this._dashDoc!.nativeWidth) : 1;
     constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
         this._textBox = tbox;
-        this._handle = document.createElement("span");
         this._dashSpan = document.createElement("div");
         this._outer = document.createElement("span");
         this._outer.style.position = "relative";
@@ -739,14 +738,11 @@ export class DashDocView {
         this._dashSpan.style.height = node.attrs.height;
         this._dashSpan.style.position = "absolute";
         this._dashSpan.style.display = "inline-block";
-        this._handle.style.position = "absolute";
-        this._handle.style.width = "20px";
-        this._handle.style.height = "20px";
-        this._handle.style.backgroundColor = "blue";
-        this._handle.style.borderRadius = "15px";
-        this._handle.style.display = "none";
-        this._handle.style.bottom = "-10px";
-        this._handle.style.right = "-10px";
+        let removeDoc = () => {
+            let ns = new NodeSelection(view.state.doc.resolve(getPos()));
+            view.dispatch(view.state.tr.setSelection(ns).deleteSelection());
+            return true;
+        }
         DocServer.GetRefField(node.attrs.docid).then(async dashDoc => {
             if (dashDoc instanceof Doc) {
                 self._dashDoc = dashDoc;
@@ -754,16 +750,15 @@ export class DashDocView {
                     view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, { ...node.attrs, width: dashDoc.width + "px", height: dashDoc.height + "px" }));
                 }
                 this._reactionDisposer && this._reactionDisposer();
-                this._reactionDisposer = reaction(() => {
-                    return dashDoc[HeightSym]();
-                }, () => {
+                this._reactionDisposer = reaction(() => dashDoc[HeightSym]() + dashDoc[WidthSym](), () => {
                     this._dashSpan.style.height = this._outer.style.height = dashDoc[HeightSym]() + "px";
+                    this._dashSpan.style.width = this._outer.style.width = dashDoc[WidthSym]() + "px";
                 });
                 ReactDOM.render(<DocumentView
                     fitToBox={BoolCast(dashDoc.fitToBox)}
                     Document={dashDoc}
                     addDocument={returnFalse}
-                    removeDocument={returnFalse}
+                    removeDocument={removeDoc}
                     ruleProvider={undefined}
                     ScreenToLocalTransform={this.getDocTransform}
                     addDocTab={self._textBox.props.addDocTab}
@@ -780,59 +775,20 @@ export class DashDocView {
                     getScale={returnOne}
                     ContainingCollectionView={undefined}
                     ContainingCollectionDoc={undefined}
-                    ContentScaling={returnOne}
+                    ContentScaling={this.contentScaling}
                 />, this._dashSpan);
             }
         });
         let self = this;
-        this._outer.onpointerenter = function (e: any) { self.selectNode(); };
-        this._outer.onpointerleave = function (e: any) { self.deselectNode(); };
         this._dashSpan.onkeydown = function (e: any) { e.stopPropagation(); };
         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._handle.onpointerdown = function (e: any) {
-            e.preventDefault();
-            e.stopPropagation();
-            const startX = e.pageX;
-            const startY = e.pageY;
-            const startWidth = parseFloat(node.attrs.width);
-            const startHeight = parseFloat(node.attrs.height);
-            const onpointermove = action((e: any) => {
-                let { scale } = Utils.GetScreenTransform(self._handle as HTMLElement);
-                const diffInPx = (e.pageX - startX) / scale;
-                const diffInPy = (e.pageY - startY) / scale;
-                self._dashDoc!.width = startWidth + diffInPx;
-                self._dashDoc!.height = startHeight + diffInPy;
-                self._outer.style.width = `${self._dashDoc!.width}`;
-                self._outer.style.height = `${self._dashDoc!.height}`;
-            });
-
-            const onpointerup = () => {
-                document.removeEventListener("pointermove", onpointermove);
-                document.removeEventListener("pointerup", onpointerup);
-                view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, { ...node.attrs, width: self._outer.style.width, height: self._outer.style.height }));
-            };
-
-            document.addEventListener("pointermove", onpointermove);
-            document.addEventListener("pointerup", onpointerup);
-        };
-
         this._outer.appendChild(this._dashSpan);
-        this._outer.appendChild(this._handle);
         (this as any).dom = this._outer;
     }
-
-    selectNode() {
-        this._dashSpan.classList.add("ProseMirror-selectednode");
-
-        this._handle.style.display = "";
-    }
-
-    deselectNode() {
-        this._dashSpan.classList.remove("ProseMirror-selectednode");
-
-        this._handle.style.display = "none";
+    destroy() {
+        this._reactionDisposer && this._reactionDisposer();
     }
 }
 
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index c030d4bce..966f183b0 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -262,20 +262,13 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             let target = de.data.embeddableSourceDoc;
             // We're dealing with an internal document drop
             const link = DocUtils.MakeLink({ doc: this.dataDoc, ctx: this.props.ContainingCollectionDoc }, { doc: target }, "ImgRef:" + target.title);
-            let node: Node<any>;
-            if (de.data.urlField && link) {
-                let url: string = de.data.urlField.url.href;
-                let model: NodeType = [".mov", ".mp4"].includes(url) ? schema.nodes.video : schema.nodes.image;
-                node = model.create({ src: url, docid: link[Id] });
-            } else {
-                let alias = Doc.MakeAlias(target);
-                alias.fitToBox = true;
-                node = schema.nodes.dashDoc.create({
-                    width: target[WidthSym](), height: target[HeightSym](),
-                    title: "dashDoc", docid: alias[Id],
-                    float: "none"
-                });
-            }
+            let alias = Doc.MakeAlias(target);
+            alias.fitToBox = true;
+            let node = schema.nodes.dashDoc.create({
+                width: target[WidthSym](), height: target[HeightSym](),
+                title: "dashDoc", docid: alias[Id],
+                float: "none"
+            });
             let pos = this._editorView!.posAtCoords({ left: de.x, top: de.y });
             link && this._editorView!.dispatch(this._editorView!.state.tr.insert(pos!.pos, node));
             this.tryUpdateHeight();
-- 
cgit v1.2.3-70-g09d2


From 2485a9f0f32968040a79f757118dfb6fad8930fd Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Mon, 7 Oct 2019 17:13:48 -0400
Subject: fixes for toolbar and text footnote/comments.

---
 src/client/util/RichTextSchema.tsx                 |  2 +-
 src/client/util/TooltipTextMenu.tsx                |  8 +++++---
 src/client/views/DocumentDecorations.tsx           |  9 ++++++++-
 src/client/views/nodes/FormattedTextBox.tsx        |  9 +++++----
 src/client/views/nodes/FormattedTextBoxComment.tsx | 10 ++++++----
 5 files changed, 25 insertions(+), 13 deletions(-)

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

diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 432e0c2fb..06b97fa68 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -168,7 +168,7 @@ export const nodes: { [index: string]: NodeSpec } = {
             width: { default: 200 },
             height: { default: 100 },
             title: { default: null },
-            float: { default: "left" },
+            float: { default: "right" },
             location: { default: "onRight" },
             docid: { default: "" }
         },
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 55c6e6609..2732b708d 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -19,6 +19,7 @@ import { schema } from "./RichTextSchema";
 import "./TooltipTextMenu.scss";
 import { Cast, NumCast } from '../../new_fields/Types';
 import { updateBullets } from './ProsemirrorExampleTransfer';
+import { DocumentDecorations } from '../views/DocumentDecorations';
 const { toggleMark, setBlockType } = require("prosemirror-commands");
 const { openPrompt, TextField } = require("./ProsemirrorCopy/prompt.js");
 
@@ -186,7 +187,7 @@ export class TooltipTextMenu {
 
         this.updateListItemDropdown(":", this.listTypeBtnDom);
 
-        this.updateInternal(view, undefined, undefined);
+        this.updateFromDash(view, undefined, undefined);
         TooltipTextMenu.Toolbar = this.wrapper;
     }
     public static Toolbar: HTMLDivElement | undefined;
@@ -849,11 +850,12 @@ export class TooltipTextMenu {
         }
     }
 
-    update(view: EditorView, lastState: EditorState | undefined) { this.updateInternal(view, lastState, this.editorProps) }
+    update(view: EditorView, lastState: EditorState | undefined) { this.updateFromDash(view, lastState, this.editorProps) }
     //updates the tooltip menu when the selection changes
-    private updateInternal(view: EditorView, lastState: EditorState | undefined, props: any) {
+    public updateFromDash(view: EditorView, lastState: EditorState | undefined, props: any) {
         this.view = view;
         let state = view.state;
+        DocumentDecorations.Instance.setTextBar(DocumentDecorations.Instance.TextBar);
         props && (this.editorProps = props);
         // Don't do anything if the document/selection didn't change
         if (lastState && lastState.doc.eq(state.doc) &&
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index b7ec27957..4f9bdbe9c 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -546,6 +546,13 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         return "-unset-";
     }
 
+    TextBar: HTMLDivElement | undefined;
+    setTextBar = (ele: HTMLDivElement) => {
+        if (ele) {
+            this.TextBar = ele;
+            TooltipTextMenu.Toolbar && Array.from(ele.childNodes).indexOf(TooltipTextMenu.Toolbar) === -1 && ele.appendChild(TooltipTextMenu.Toolbar);
+        }
+    }
     render() {
         var bounds = this.Bounds;
         let seldoc = SelectionManager.SelectedDocuments().length ? SelectionManager.SelectedDocuments()[0] : undefined;
@@ -578,7 +585,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 zIndex: SelectionManager.SelectedDocuments().length > 1 ? 900 : 0,
             }} onPointerDown={this.onBackgroundDown} onContextMenu={(e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); }} >
             </div>
-            <div className="documentDecorations-container" ref={(r) => r && TooltipTextMenu.Toolbar && Array.from(r.childNodes).indexOf(TooltipTextMenu.Toolbar) === -1 && r.appendChild(TooltipTextMenu.Toolbar)} style={{
+            <div className="documentDecorations-container" ref={this.setTextBar} style={{
                 width: (bounds.r - bounds.x + this._resizeBorderWidth) + "px",
                 height: (bounds.b - bounds.y + this._resizeBorderWidth + this._linkBoxHeight + this._titleHeight + 3) + "px",
                 left: bounds.x - this._resizeBorderWidth / 2,
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 966f183b0..20edbdb23 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -267,7 +267,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             let node = schema.nodes.dashDoc.create({
                 width: target[WidthSym](), height: target[HeightSym](),
                 title: "dashDoc", docid: alias[Id],
-                float: "none"
+                float: "right"
             });
             let pos = this._editorView!.posAtCoords({ left: de.x, top: de.y });
             link && this._editorView!.dispatch(this._editorView!.state.tr.insert(pos!.pos, node));
@@ -759,7 +759,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     }
 
     onPointerUp = (e: React.PointerEvent): void => {
-        FormattedTextBoxComment.textBox = this;
+        if (!(e.nativeEvent as any).formattedHandled) { FormattedTextBoxComment.textBox = this; }
+        (e.nativeEvent as any).formattedHandled = true;
+
         if (e.buttons === 1 && this.props.isSelected() && !e.altKey) {
             e.stopPropagation();
         }
@@ -837,7 +839,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 }
             }
         }
-        this._editorView!.focus();
     }
     onMouseUp = (e: React.MouseEvent): void => {
         e.stopPropagation();
@@ -914,7 +915,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             ? "none" : "all";
         Doc.UpdateDocumentExtensionForField(this.dataDoc, this.props.fieldKey);
         if (this.props.isSelected()) {
-            FormattedTextBox._toolTipTextMenu!.update(this._editorView!, undefined, this.props);
+            FormattedTextBox._toolTipTextMenu!.updateFromDash(this._editorView!, undefined, this.props);
         }
         return (
             <div className={`formattedTextBox-cont-${style}`} ref={this._ref}
diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx
index 2d2fff06d..a75bfd373 100644
--- a/src/client/views/nodes/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/FormattedTextBoxComment.tsx
@@ -7,6 +7,7 @@ import { schema } from "../../util/RichTextSchema";
 import { DocServer } from "../../DocServer";
 import { Utils } from "../../../Utils";
 import { StrCast } from "../../../new_fields/Types";
+import { FormattedTextBox } from "./FormattedTextBox";
 
 export let formattedTextBoxCommentPlugin = new Plugin({
     view(editorView) { return new FormattedTextBoxComment(editorView); }
@@ -49,7 +50,7 @@ export class FormattedTextBoxComment {
     static end: number;
     static mark: Mark;
     static opened: boolean;
-    static textBox: any;
+    static textBox: FormattedTextBox | undefined;
     constructor(view: any) {
         if (!FormattedTextBoxComment.tooltip) {
             const root = document.getElementById("root");
@@ -62,9 +63,9 @@ export class FormattedTextBoxComment {
             FormattedTextBoxComment.tooltip.style.pointerEvents = "all";
             FormattedTextBoxComment.tooltip.appendChild(input);
             FormattedTextBoxComment.tooltip.onpointerdown = (e: PointerEvent) => {
-                let keep = e.target && (e.target as any).type === "checkbox";
+                let keep = e.target && (e.target as any).type === "checkbox" ? true : false;
                 FormattedTextBoxComment.opened = keep || !FormattedTextBoxComment.opened;
-                FormattedTextBoxComment.textBox && FormattedTextBoxComment.textBox.setAnnotation(
+                FormattedTextBoxComment.textBox && FormattedTextBoxComment.start !== undefined && FormattedTextBoxComment.textBox.setAnnotation(
                     FormattedTextBoxComment.start, FormattedTextBoxComment.end, FormattedTextBoxComment.mark,
                     FormattedTextBoxComment.opened, keep);
             };
@@ -92,8 +93,9 @@ export class FormattedTextBoxComment {
         if (lastState && lastState.doc.eq(state.doc) &&
             lastState.selection.eq(state.selection)) return;
 
+        if (!FormattedTextBoxComment.textBox || !FormattedTextBoxComment.textBox.props.isSelected()) return;
         let set = "none";
-        if (state.selection.$from) {
+        if (FormattedTextBoxComment.textBox && state.selection.$from) {
             let nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark);
             let naft = findEndOfMark(state.selection.$from, view, findOtherUserMark);
             const spos = state.selection.$from.pos - nbef;
-- 
cgit v1.2.3-70-g09d2


From 86139163d9424a0eb95eacd5cdfd608048c0a4bd Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 7 Oct 2019 20:02:57 -0400
Subject: from last

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

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

diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 432e0c2fb..8d0f59ddc 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -742,7 +742,7 @@ export class DashDocView {
             let ns = new NodeSelection(view.state.doc.resolve(getPos()));
             view.dispatch(view.state.tr.setSelection(ns).deleteSelection());
             return true;
-        }
+        };
         DocServer.GetRefField(node.attrs.docid).then(async dashDoc => {
             if (dashDoc instanceof Doc) {
                 self._dashDoc = dashDoc;
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 0b1613ed6..765c2fe92 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -849,7 +849,7 @@ export class TooltipTextMenu {
         }
     }
 
-    update(view: EditorView, lastState: EditorState | undefined) { this.updateInternal(view, lastState, this.editorProps) }
+    update(view: EditorView, lastState: EditorState | undefined) { this.updateInternal(view, lastState, this.editorProps); }
     //updates the tooltip menu when the selection changes
     public updateInternal(view: EditorView, lastState: EditorState | undefined, props: any) {
         this.view = view;
-- 
cgit v1.2.3-70-g09d2


From d970ad82bc7b0b1c8dae27b5f55ad78bddb0f7cd Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 7 Oct 2019 21:48:01 -0400
Subject: added commeting with (( and ##

---
 src/client/documents/Documents.ts           |  1 +
 src/client/util/RichTextRules.ts            | 46 ++++++++++++++++++++++++++---
 src/client/util/RichTextSchema.tsx          |  3 +-
 src/client/views/nodes/FormattedTextBox.tsx |  2 +-
 4 files changed, 46 insertions(+), 6 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 98f56af01..34e146f0e 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -73,6 +73,7 @@ export interface DocumentOptions {
     dropAction?: dropActionType;
     backgroundLayout?: string;
     chromeStatus?: string;
+    fontSize?: number;
     curPage?: number;
     currentTimecode?: number;
     documentText?: string;
diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts
index 2d5ec1743..2ff758fbc 100644
--- a/src/client/util/RichTextRules.ts
+++ b/src/client/util/RichTextRules.ts
@@ -1,10 +1,12 @@
 import { textblockTypeInputRule, smartQuotes, emDash, ellipsis, InputRule } from "prosemirror-inputrules";
 import { schema } from "./RichTextSchema";
 import { wrappingInputRule } from "./prosemirrorPatches";
-import { NodeSelection } from "prosemirror-state";
+import { NodeSelection, TextSelection } from "prosemirror-state";
 import { NumCast, Cast } from "../../new_fields/Types";
 import { Doc } from "../../new_fields/Doc";
 import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
+import { Docs } from "../documents/Documents";
+import { Id } from "../../new_fields/FieldSymbols";
 
 export const inpRules = {
     rules: [
@@ -80,8 +82,9 @@ export const inpRules = {
                     ruleProvider["ruleAlign_" + heading] = "center";
                     return node ? state.tr.deleteRange(start, end).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
                 }
-                return node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "center" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
+                let 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)));
             }),
         new InputRule(
             new RegExp(/^\[\[\s$/),
@@ -94,8 +97,9 @@ export const inpRules = {
                     ruleProvider["ruleAlign_" + heading] = "left";
                     return node ? state.tr.deleteRange(start, end).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
                 }
-                return node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "left" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
+                let 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)));
             }),
         new InputRule(
             new RegExp(/^\]\]\s$/),
@@ -108,8 +112,42 @@ export const inpRules = {
                     ruleProvider["ruleAlign_" + heading] = "right";
                     return node ? state.tr.deleteRange(start, end).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
                 }
-                return node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "right" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
+                let 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(/##\s$/),
+            (state, match, start, end) => {
+                let node = (state.doc.resolve(start) as any).nodeAfter;
+                let sm = state.storedMarks || undefined;
+                let target = Docs.Create.TextDocument({ width: 75, height: 35, autoHeight: true, fontSize: 9, title: "inline comment" });
+                let replaced = node ? state.tr.insertText("←", start).replaceRangeWith(start + 1, end + 1, schema.nodes.dashDoc.create({
+                    width: 75, height: 35,
+                    title: "dashDoc", docid: target[Id],
+                    float: "right"
+                })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
+                    state.tr;
+                return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 1)));
+            }),
+        new InputRule(
+            new RegExp(/\(\(/),
+            (state, match, start, end) => {
+                let node = (state.doc.resolve(start) as any).nodeAfter;
+                let sm = state.storedMarks || undefined;
+                let mark = state.schema.marks.highlight.create();
+                let selected = state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).addMark(start, end, mark);
+                let content = selected.selection.content();
+                let replaced = node ? selected.replaceRangeWith(start, start,
+                    schema.nodes.star.create({ visibility: true, text: content, textslice: content.toJSON() })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
+                    state.tr;
+                return replaced.setSelection(new TextSelection(replaced.doc.resolve(end + 1)));
+            }),
+        new InputRule(
+            new RegExp(/\)\)/),
+            (state, match, start, end) => {
+                let mark = state.schema.marks.highlight.create();
+                return state.tr.removeStoredMark(mark);
             }),
         new InputRule(
             new RegExp(/\^f\s$/),
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 8d0f59ddc..047706368 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -739,7 +739,8 @@ export class DashDocView {
         this._dashSpan.style.position = "absolute";
         this._dashSpan.style.display = "inline-block";
         let removeDoc = () => {
-            let ns = new NodeSelection(view.state.doc.resolve(getPos()));
+            let pos = getPos();
+            let ns = new NodeSelection(view.state.doc.resolve(pos));
             view.dispatch(view.state.tr.setSelection(ns).deleteSelection());
             return true;
         };
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index cab408a31..cacef1ac3 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -457,7 +457,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         },
             action((rules: any) => {
                 this._fontFamily = rules ? rules.font : "Arial";
-                this._fontSize = rules ? rules.size : 13;
+                this._fontSize = rules ? rules.size : NumCast(this.props.Document.fontSize, 13);
                 rules && setTimeout(() => {
                     const view = this._editorView!;
                     if (this._proseRef) {
-- 
cgit v1.2.3-70-g09d2


From 00ada8bd463f15f69ae604b79445c69167f8425a Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 8 Oct 2019 01:11:18 -0400
Subject: a bunch of fixes to text outline mode.

---
 src/client/util/ProsemirrorExampleTransfer.ts |  4 ++--
 src/client/util/RichTextSchema.tsx            |  6 ++++--
 src/client/util/TooltipTextMenu.tsx           | 29 +++++++++------------------
 src/client/views/nodes/FormattedTextBox.scss  | 22 +++++++++++---------
 src/client/views/nodes/FormattedTextBox.tsx   | 25 +++++++++++++++++++----
 5 files changed, 49 insertions(+), 37 deletions(-)

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

diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts
index dd0f72af0..003ff6272 100644
--- a/src/client/util/ProsemirrorExampleTransfer.ts
+++ b/src/client/util/ProsemirrorExampleTransfer.ts
@@ -11,7 +11,7 @@ const mac = typeof navigator !== "undefined" ? /Mac/.test(navigator.platform) :
 
 export type KeyMap = { [key: string]: any };
 
-export let updateBullets = (tx2: Transaction, schema: Schema) => {
+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) {
@@ -20,7 +20,7 @@ export let updateBullets = (tx2: Transaction, schema: Schema) => {
             if (node.type === schema.nodes.ordered_list) depth++;
             fontSize = depth === 1 && node.attrs.setFontSize ? Number(node.attrs.setFontSize) : fontSize;
             let fsize = fontSize && node.type === schema.nodes.ordered_list ? Math.max(6, fontSize - (depth - 1) * 4) : undefined;
-            tx2.setNodeMarkup(offset, node.type, { ...node.attrs, mapStyle: node.attrs.mapStyle, bulletStyle: depth, inheritedFontSize: fsize }, node.marks);
+            tx2.setNodeMarkup(offset, node.type, { ...node.attrs, mapStyle: mapStyle ? mapStyle : node.attrs.mapStyle, bulletStyle: depth, inheritedFontSize: fsize }, node.marks);
         }
     });
     return tx2;
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 047706368..46c2740f4 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -234,6 +234,7 @@ export const nodes: { [index: string]: NodeSpec } = {
             bulletStyle: { default: 0 },
             mapStyle: { default: "decimal" },
             setFontSize: { default: undefined },
+            setFontFamily: { default: undefined },
             inheritedFontSize: { default: undefined },
             visibility: { default: true }
         },
@@ -243,8 +244,9 @@ export const nodes: { [index: string]: NodeSpec } = {
             const multiMap = bs === 1 ? "decimal1" : bs === 2 ? "upper-alpha" : bs === 3 ? "lower-roman" : bs === 4 ? "lower-alpha" : "";
             let map = node.attrs.mapStyle === "decimal" ? decMap : multiMap;
             let fsize = node.attrs.setFontSize ? node.attrs.setFontSize : node.attrs.inheritedFontSize;
-            return node.attrs.visibility ? ['ol', { class: `${map}-ol`, style: `list-style: none;font-size: ${fsize}` }, 0] :
-                ['ol', { class: `${map}-ol`, style: `list-style: none; font-size: ${fsize}` }];
+            let ffam = node.attrs.setFontFamily;
+            return node.attrs.visibility ? ['ol', { class: `${map}-ol`, style: `list-style: none; font-size: ${fsize}; font-family: ${ffam}` }, 0] :
+                ['ol', { class: `${map}-ol`, style: `list-style: none; font-size: ${fsize}; font-family: ${ffam}` }];
         }
     },
 
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 765c2fe92..75943e537 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -521,23 +521,14 @@ export class TooltipTextMenu {
             }
             //actually apply font
             if ((view.state.selection as any).node && (view.state.selection as any).node.type === view.state.schema.nodes.ordered_list) {
-                view.dispatch(updateBullets(view.state.tr.setNodeMarkup(view.state.selection.from, (view.state.selection as any).node.type,
-                    { ...(view.state.selection as NodeSelection).node.attrs, setFontSize: Number(markType.name.replace(/p/, "")) }), view.state.schema));
+                let status = updateBullets(view.state.tr.setNodeMarkup(view.state.selection.from, (view.state.selection as any).node.type,
+                    { ...(view.state.selection as NodeSelection).node.attrs, setFontFamily: markType.name, setFontSize: Number(markType.name.replace(/p/, "")) }), view.state.schema);
+                view.dispatch(status.setSelection(new NodeSelection(status.doc.resolve(view.state.selection.from))));
             }
             else toggleMark(markType)(view.state, view.dispatch, view);
         }
     }
 
-    updateBullets = (tx2: Transaction, style: string) => {
-        tx2.doc.descendants((node: any, offset: any, index: any) => {
-            if (node.type === schema.nodes.ordered_list || node.type === schema.nodes.list_item) {
-                let 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++;
-                tx2.setNodeMarkup(offset, node.type, { mapStyle: style, bulletStyle: depth }, node.marks);
-            }
-        });
-    }
     //remove all node typeand apply the passed-in one to the selected text
     changeToNodeType = (nodeType: NodeType | undefined, view: EditorView) => {
         //remove oldif (nodeType) { //add new
@@ -546,18 +537,18 @@ export class TooltipTextMenu {
         } else {
             var marks = view.state.storedMarks || (view.state.selection.$to.parentOffset && view.state.selection.$from.marks());
             if (!wrapInList(schema.nodes.ordered_list)(view.state, (tx2: any) => {
-                this.updateBullets(tx2, (nodeType as any).attrs.mapStyle);
-                marks && tx2.ensureMarks([...marks]);
-                marks && tx2.setStoredMarks([...marks]);
+                let tx3 = updateBullets(tx2, schema, (nodeType as any).attrs.mapStyle);
+                marks && tx3.ensureMarks([...marks]);
+                marks && tx3.setStoredMarks([...marks]);
 
                 view.dispatch(tx2);
             })) {
                 let tx2 = view.state.tr;
-                this.updateBullets(tx2, (nodeType as any).attrs.mapStyle);
-                marks && tx2.ensureMarks([...marks]);
-                marks && tx2.setStoredMarks([...marks]);
+                let tx3 = updateBullets(tx2, schema, (nodeType as any).attrs.mapStyle);
+                marks && tx3.ensureMarks([...marks]);
+                marks && tx3.setStoredMarks([...marks]);
 
-                view.dispatch(tx2);
+                view.dispatch(tx3);
             }
         }
     }
diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss
index 541c29faa..29e8b14a8 100644
--- a/src/client/views/nodes/FormattedTextBox.scss
+++ b/src/client/views/nodes/FormattedTextBox.scss
@@ -154,17 +154,18 @@ footnote::after {
     content: "...";
 }
 
-ol { counter-reset: deci1 0;}
-.decimal1-ol {counter-reset: deci1; p { display: inline }; font-size: 24 }
-.decimal2-ol {counter-reset: deci2; p { display: inline }; font-size: 18 }
-.decimal3-ol {counter-reset: deci3; p { display: inline }; font-size: 14 }
-.decimal4-ol {counter-reset: deci4; p { display: inline }; font-size: 10 }
-.decimal5-ol {counter-reset: deci5; p { display: inline }; font-size: 10 }
-.decimal6-ol {counter-reset: deci6; p { display: inline }; font-size: 10 }
-.decimal7-ol {counter-reset: deci7; p { display: inline }; font-size: 10 }
-.upper-alpha-ol {counter-reset: ualph; p { display: inline }; font-size: 18 }
+.ProseMirror {
+ol { counter-reset: deci1 0; padding-left: 0px; }
+.decimal1-ol {counter-reset: deci1; p { display: inline }; font-size: 24 ; ul, ol {  padding-left:30px; } }
+.decimal2-ol {counter-reset: deci2; p { display: inline }; font-size: 18 ; ul, ol {  padding-left:30px; } }
+.decimal3-ol {counter-reset: deci3; p { display: inline }; font-size: 14 ; ul, ol {  padding-left:30px; } }
+.decimal4-ol {counter-reset: deci4; p { display: inline }; font-size: 10 ; ul, ol {  padding-left:30px; } }
+.decimal5-ol {counter-reset: deci5; p { display: inline }; font-size: 10 ; ul, ol {  padding-left:30px; } }
+.decimal6-ol {counter-reset: deci6; p { display: inline }; font-size: 10 ; ul, ol {  padding-left:30px; } }
+.decimal7-ol {counter-reset: deci7; p { display: inline }; font-size: 10 ; ul, ol {  padding-left:30px; } } 
+.upper-alpha-ol {counter-reset: ualph; p { display: inline }; font-size: 18; }
 .lower-roman-ol {counter-reset: lroman;  p { display: inline }; font-size: 14; }
-.lower-alpha-ol {counter-reset: lalpha;  p { display: inline }; font-size: 10;}
+.lower-alpha-ol {counter-reset: lalpha;  p { display: inline }; font-size: 10; }
 .decimal1:before { content: counter(deci1) ") "; counter-increment: deci1; display:inline-block; min-width: 30;}
 .decimal2:before { content: counter(deci1) "." counter(deci2) ") "; counter-increment: deci2; display:inline-block; min-width: 35}
 .decimal3:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) ") "; counter-increment: deci3; display:inline-block; min-width: 35}
@@ -175,3 +176,4 @@ ol { counter-reset: deci1 0;}
 .upper-alpha:before { content: counter(deci1) "." counter(ualph, upper-alpha) ") "; counter-increment: ualph; display:inline-block; min-width: 35 }
 .lower-roman:before { content: counter(deci1) "." counter(ualph, upper-alpha) "." counter(lroman, lower-roman) ") "; counter-increment: lroman;display:inline-block; min-width: 50 }
 .lower-alpha:before { content: counter(deci1) "." counter(ualph, upper-alpha) "." counter(lroman, lower-roman) "." counter(lalpha, lower-alpha) ") "; counter-increment: lalpha; display:inline-block; min-width: 35}
+}
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index cacef1ac3..041675bef 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -779,6 +779,19 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         }
     }
 
+    static _sheet: any = undefined;
+    static addRule = ((style) => {
+        style.type = "text/css"
+        var sheets = document.head.appendChild(style);
+        FormattedTextBox._sheet = (sheets as any).sheet;
+        return function (selector: any, css: any) {
+            var propText = typeof css === "string" ? css : Object.keys(css).map(function (p) {
+                return p + ":" + (p === "content" ? "'" + css[p] + "'" : css[p]);
+            }).join(";");
+            return FormattedTextBox._sheet.insertRule("." + selector + "{" + propText + "}", FormattedTextBox._sheet.cssRules.length);
+        };
+    })(document.createElement("style"));
+
     onClick = (e: React.MouseEvent): void => {
         if ((e.nativeEvent as any).formattedHandled) { e.stopPropagation(); return; }
         (e.nativeEvent as any).formattedHandled = true;
@@ -814,6 +827,10 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 e.preventDefault();
             }
         }
+
+        if (FormattedTextBox._sheet.rules.length) {
+            FormattedTextBox._sheet.removeRule(0);
+        }
         // 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.
         if (this.props.isSelected() && e.nativeEvent.offsetX < 40) {
             let pos = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY });
@@ -821,14 +838,14 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 let node = this._editorView!.state.doc.nodeAt(pos.pos);
                 let node2 = node && node.type === schema.nodes.paragraph ? this._editorView!.state.doc.nodeAt(pos.pos - 1) : undefined;
                 if (node === this._nodeClicked && node2 && (node2.type === schema.nodes.ordered_list || node2.type === schema.nodes.list_item)) {
-                    let hit = this._editorView!.domAtPos(pos.pos).node as any;
-                    let beforeEle = document.querySelector("." + hit.className) as Element;
-                    let before = beforeEle ? window.getComputedStyle(beforeEle, ':before') : undefined;
+                    let hit = this._editorView!.domAtPos(pos.pos).node as any;   // let beforeEle = document.querySelector("." + hit.className) as Element;
+                    let before = hit ? window.getComputedStyle(hit, ':before') : undefined;
                     let beforeWidth = before ? Number(before.getPropertyValue('width').replace("px", "")) : undefined;
                     if (beforeWidth && e.nativeEvent.offsetX < beforeWidth) {
                         let ol = this._editorView!.state.doc.nodeAt(pos.pos - 2) ? this._editorView!.state.doc.nodeAt(pos.pos - 2) : undefined;
-                        if (ol && ol.type === schema.nodes.ordered_list && !e.shiftKey) {
+                        if (ol && ol.type === schema.nodes.ordered_list && e.shiftKey) {
                             this._editorView!.dispatch(this._editorView!.state.tr.setSelection(new NodeSelection(this._editorView!.state.doc.resolve(pos.pos - 2))));
+                            FormattedTextBox.addRule(hit.className + ":before", { background: "gray" });
                         } else {
                             this._editorView!.dispatch(this._editorView!.state.tr.setNodeMarkup(pos.pos - 1, node2.type, { ...node2.attrs, visibility: !node2.attrs.visibility }));
                         }
-- 
cgit v1.2.3-70-g09d2


From de0b723551eb033f7ad1999dd23309b4f306f344 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Tue, 8 Oct 2019 10:08:36 -0400
Subject: moved style sheet stuff into Utils.  fixed menu/footnote interaction

---
 src/Utils.ts                                | 13 ++++++++++
 src/client/util/RichTextSchema.tsx          | 11 +++++++--
 src/client/views/nodes/FormattedTextBox.tsx | 37 ++++++++++++-----------------
 3 files changed, 37 insertions(+), 24 deletions(-)

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

diff --git a/src/Utils.ts b/src/Utils.ts
index 4b892aa70..f6ab434a8 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -337,4 +337,17 @@ export default function smoothScroll(duration: number, element: HTMLElement, to:
         }
     };
     animateScroll();
+}
+export function addStyleSheet(styleType: string = "text/css") {
+    let style = document.createElement("style");
+    style.type = styleType;
+    var sheets = document.head.appendChild(style);
+    return (sheets as any).sheet;
+}
+export function addStyleSheetRule(sheet: any, selector: any, css: any) {
+    var propText = typeof css === "string" ? css : Object.keys(css).map(p => p + ":" + (p === "content" ? "'" + css[p] + "'" : css[p])).join(";");
+    return sheet.insertRule("." + selector + "{" + propText + "}", sheet.cssRules.length);
+}
+export function removeStyleSheetRule(sheet: any, rule: number) {
+    sheet.removeRule(rule);
 }
\ No newline at end of file
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 836713c01..bc1ad5070 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -4,7 +4,7 @@ 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 } from "prosemirror-state";
+import { EditorState, NodeSelection, TextSelection, Plugin } from "prosemirror-state";
 import { StepMap } from "prosemirror-transform";
 import { EditorView } from "prosemirror-view";
 import * as ReactDOM from 'react-dom';
@@ -847,7 +847,14 @@ export class FootnoteView {
                     "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) {
+                        return FormattedTextBox.getToolTip(newView);
+                    }
+                })
+                ],
+
             }),
             // This is the magic part
             dispatchTransaction: this.dispatchInner.bind(this),
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 41bd29422..96b434708 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -18,7 +18,7 @@ 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 { numberRange, timenow, Utils, emptyFunction } from '../../../Utils';
+import { numberRange, timenow, Utils, addStyleSheet, addStyleSheetRule, removeStyleSheetRule } from '../../../Utils';
 import { GoogleApiClientUtils, Pulls, Pushes } from '../../apis/google_docs/GoogleApiClientUtils';
 import { DocServer } from "../../DocServer";
 import { Docs, DocUtils } from '../../documents/Documents';
@@ -781,18 +781,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         }
     }
 
-    static _sheet: any = undefined;
-    static addRule = ((style) => {
-        style.type = "text/css"
-        var sheets = document.head.appendChild(style);
-        FormattedTextBox._sheet = (sheets as any).sheet;
-        return function (selector: any, css: any) {
-            var propText = typeof css === "string" ? css : Object.keys(css).map(function (p) {
-                return p + ":" + (p === "content" ? "'" + css[p] + "'" : css[p]);
-            }).join(";");
-            return FormattedTextBox._sheet.insertRule("." + selector + "{" + propText + "}", FormattedTextBox._sheet.cssRules.length);
-        };
-    })(document.createElement("style"));
+    static _sheet: any = addStyleSheet();
 
     onClick = (e: React.MouseEvent): void => {
         if ((e.nativeEvent as any).formattedHandled) { e.stopPropagation(); return; }
@@ -830,12 +819,16 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             }
         }
 
+        this.hitBulletTargets(e.clientX, e.clientY, e.nativeEvent.offsetX, e.shiftKey);
+    }
+
+    // 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, offsetX: number, select: boolean = false) {
         if (FormattedTextBox._sheet.rules.length) {
-            FormattedTextBox._sheet.removeRule(0);
+            removeStyleSheetRule(FormattedTextBox._sheet, 0);
         }
-        // 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.
-        if (this.props.isSelected() && e.nativeEvent.offsetX < 40) {
-            let pos = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY });
+        if (this.props.isSelected() && offsetX < 40) {
+            let pos = this._editorView!.posAtCoords({ left: x, top: y });
             if (pos && pos.pos > 0) {
                 let node = this._editorView!.state.doc.nodeAt(pos.pos);
                 let node2 = node && node.type === schema.nodes.paragraph ? this._editorView!.state.doc.nodeAt(pos.pos - 1) : undefined;
@@ -843,11 +836,11 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                     let hit = this._editorView!.domAtPos(pos.pos).node as any;   // let beforeEle = document.querySelector("." + hit.className) as Element;
                     let before = hit ? window.getComputedStyle(hit, ':before') : undefined;
                     let beforeWidth = before ? Number(before.getPropertyValue('width').replace("px", "")) : undefined;
-                    if (beforeWidth && e.nativeEvent.offsetX < beforeWidth) {
+                    if (beforeWidth && offsetX < beforeWidth) {
                         let ol = this._editorView!.state.doc.nodeAt(pos.pos - 2) ? this._editorView!.state.doc.nodeAt(pos.pos - 2) : undefined;
-                        if (ol && ol.type === schema.nodes.ordered_list && e.shiftKey) {
+                        if (ol && ol.type === schema.nodes.ordered_list && select) {
                             this._editorView!.dispatch(this._editorView!.state.tr.setSelection(new NodeSelection(this._editorView!.state.doc.resolve(pos.pos - 2))));
-                            FormattedTextBox.addRule(hit.className + ":before", { background: "gray" });
+                            addStyleSheetRule(FormattedTextBox._sheet, hit.className + ":before", { background: "gray" });
                         } else {
                             this._editorView!.dispatch(this._editorView!.state.tr.setNodeMarkup(pos.pos - 1, node2.type, { ...node2.attrs, visibility: !node2.attrs.visibility }));
                         }
@@ -875,8 +868,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     tooltipTextMenuPlugin() {
         let self = FormattedTextBox;
         return new Plugin({
-            view(_editorView) {
-                return self._toolTipTextMenu = FormattedTextBox.getToolTip(_editorView);
+            view(newView) {
+                return self._toolTipTextMenu = FormattedTextBox.getToolTip(newView);
             }
         });
     }
-- 
cgit v1.2.3-70-g09d2


From 84211163c9f085a521b3ca3af0dc9103640e3622 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Tue, 8 Oct 2019 14:07:32 -0400
Subject: cleaned up tagging of text spans with data and timestamps

---
 src/Utils.ts                                | 13 ++++++-
 src/client/util/DocumentManager.ts          |  2 +-
 src/client/util/RichTextRules.ts            | 21 ++++++++++
 src/client/util/RichTextSchema.tsx          | 34 ++++++++++++-----
 src/client/views/nodes/FormattedTextBox.tsx | 59 +++++++++++++++++++++++++----
 5 files changed, 109 insertions(+), 20 deletions(-)

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

diff --git a/src/Utils.ts b/src/Utils.ts
index f6ab434a8..489de3b50 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -349,5 +349,16 @@ export function addStyleSheetRule(sheet: any, selector: any, css: any) {
     return sheet.insertRule("." + selector + "{" + propText + "}", sheet.cssRules.length);
 }
 export function removeStyleSheetRule(sheet: any, rule: number) {
-    sheet.removeRule(rule);
+    if (sheet.rules.length) {
+        sheet.removeRule(rule);
+        return true;
+    }
+    return false;
+}
+export function clearStyleSheetRules(sheet: any) {
+    if (sheet.rules.length) {
+        numberRange(sheet.rules.length).map(n => sheet.removeRule(0));
+        return true;
+    }
+    return false;
 }
\ No newline at end of file
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 83a69465f..c45d3a75f 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -168,7 +168,7 @@ export class DocumentManager {
                             retryDocView.props.focus(targetDoc, willZoom); // focus on the target if it now exists in the context
                         } else {
                             if (closeContextIfNotFound && targetDocContextView.props.removeDocument) targetDocContextView.props.removeDocument(targetDocContextView.props.Document);
-                            (dockFunc || CollectionDockingView.AddRightSplit)(Doc.BrushDoc(Doc.MakeAlias(targetDoc)), undefined); // otherwise create a new view of the target
+                            targetDoc.layout && (dockFunc || CollectionDockingView.AddRightSplit)(Doc.BrushDoc(Doc.MakeAlias(targetDoc)), undefined); // otherwise create a new view of the target
                         }
                         highlight();
                     }, 0);
diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts
index 2ff758fbc..346d5ab99 100644
--- a/src/client/util/RichTextRules.ts
+++ b/src/client/util/RichTextRules.ts
@@ -71,6 +71,27 @@ export const inpRules = {
                 }
                 return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontSize.create({ fontSize: Number(match[1]) }));
             }),
+        new InputRule(
+            new RegExp(/\t/),
+            (state, match, start, end) => {
+                if (state.selection.to === state.selection.from) return null;
+                let node = (state.doc.resolve(start) as any).nodeAfter;
+                return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: "todo", modified: Math.round(Date.now() / 1000 / 60) })) : state.tr;
+            }),
+        new InputRule(
+            new RegExp(/\!/),
+            (state, match, start, end) => {
+                if (state.selection.to === state.selection.from) return null;
+                let node = (state.doc.resolve(start) as any).nodeAfter;
+                return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: "important", modified: Math.round(Date.now() / 1000 / 60) })) : state.tr;
+            }),
+        new InputRule(
+            new RegExp(/\x/),
+            (state, match, start, end) => {
+                if (state.selection.to === state.selection.from) return null;
+                let node = (state.doc.resolve(start) as any).nodeAfter;
+                return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: "disagree", modified: Math.round(Date.now() / 1000 / 60) })) : state.tr;
+            }),
         new InputRule(
             new RegExp(/^\^\^\s$/),
             (state, match, start, end) => {
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index bc1ad5070..80bcf25ad 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -439,20 +439,34 @@ export const marks: { [index: string]: MarkSpec } = {
     user_mark: {
         attrs: {
             userid: { default: "" },
-            hide_users: { default: [] },
             opened: { default: true },
-            modified: { default: "when?" }
+            modified: { default: "when?" }, // 5 second intervals since 1970
         },
         group: "inline",
         toDOM(node: any) {
-            let hideUsers = node.attrs.hide_users;
-            let hidden = hideUsers.indexOf(node.attrs.userid) !== -1 || (hideUsers.length === 0 && node.attrs.userid !== Doc.CurrentUserEmail);
-            return hidden ?
-                (node.attrs.opened ?
-                    ['span', { class: "userMarkOpen" }, 0] :
-                    ['span', { class: "userMark" }, ['span', 0]]
-                ) :
-                ['span', 0];
+            let uid = node.attrs.userid.replace(".", "").replace("@", "");
+            let min = Math.round(node.attrs.modified / 12);
+            let hr = Math.round(min / 60);
+            let day = Math.round(hr / 60 / 24);
+            return node.attrs.opened ?
+                ['span', { class: "userMark-" + uid + " userMark-min-" + min + " userMark-hr-" + hr + " userMark-day-" + day }, 0] :
+                ['span', { class: "userMark-" + uid + " userMark-min-" + min + " userMark-hr-" + hr + " userMark-day-" + day }, ['span', 0]];
+        }
+    },
+    // the id of the user who entered the text
+    user_tag: {
+        attrs: {
+            userid: { default: "" },
+            opened: { default: true },
+            modified: { default: "when?" }, // 5 second intervals since 1970
+            tag: { default: "" }
+        },
+        group: "inline",
+        toDOM(node: any) {
+            let uid = node.attrs.userid.replace(".", "").replace("@", "");
+            return node.attrs.opened ?
+                ['span', { class: "userTag-" + uid + " userTag-" + node.attrs.tag }, 0] :
+                ['span', { class: "userTag-" + uid + " userTag-" + node.attrs.tag }, ['span', 0]];
         }
     },
 
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 96b434708..d878f2b74 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -18,7 +18,7 @@ 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 { numberRange, timenow, Utils, addStyleSheet, addStyleSheetRule, removeStyleSheetRule } from '../../../Utils';
+import { numberRange, Utils, addStyleSheet, addStyleSheetRule, clearStyleSheetRules } from '../../../Utils';
 import { GoogleApiClientUtils, Pulls, Pushes } from '../../apis/google_docs/GoogleApiClientUtils';
 import { DocServer } from "../../DocServer";
 import { Docs, DocUtils } from '../../documents/Documents';
@@ -43,6 +43,7 @@ import { FormattedTextBoxComment, formattedTextBoxCommentPlugin } from './Format
 import React = require("react");
 import { ContextMenuProps } from '../ContextMenuItem';
 import { ContextMenu } from '../ContextMenu';
+import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
 
 library.add(faEdit);
 library.add(faSmile, faTextHeight, faUpload);
@@ -298,9 +299,51 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         }
     }
 
+
+    static _highlights: string[] = [];
+
+    updateHighlights = () => {
+        clearStyleSheetRules(FormattedTextBox._userStyleSheet);
+        if (FormattedTextBox._highlights.indexOf("My Text") !== -1) {
+            addStyleSheetRule(FormattedTextBox._userStyleSheet, "userMark-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { background: "yellow" });
+        }
+        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("By Recent Minute") !== -1) {
+            addStyleSheetRule(FormattedTextBox._userStyleSheet, "userMark-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" });
+            let min = Math.round(Date.now() / 1000 / 60);
+            numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, "userMark-min-" + (min - i), { opacity: ((10 - i - 1) / 10).toString() }));
+            setTimeout(() => this.updateHighlights());
+        }
+        if (FormattedTextBox._highlights.indexOf("By Recent Hour") !== -1) {
+            addStyleSheetRule(FormattedTextBox._userStyleSheet, "userMark-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" });
+            let hr = Math.round(Date.now() / 1000 / 60 / 60);
+            numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, "userMark-hr-" + (hr - i), { opacity: ((10 - i - 1) / 10).toString() }));
+        }
+    }
+
     specificContextMenu = (e: React.MouseEvent): void => {
         let funcs: ContextMenuProps[] = [];
         funcs.push({ description: "Dictate", event: () => { e.stopPropagation(); this.recordBullet(); }, icon: "expand-arrows-alt" });
+        ["My Text", "Todo Items", "Important Items", "Disagree Items", "By Recent Minute", "By Recent Hour"].forEach(option =>
+            funcs.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"
+            }));
 
         ContextMenu.Instance.addItem({ description: "Text Funcs...", subitems: funcs, icon: "asterisk" });
     }
@@ -718,7 +761,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         }
         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: timenow() })];
+        this._editorView!.state.storedMarks = [...(this._editorView!.state.storedMarks ? this._editorView!.state.storedMarks : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.round(Date.now() / 1000 / 5) })];
     }
     getFont(font: string) {
         switch (font) {
@@ -781,7 +824,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         }
     }
 
-    static _sheet: any = addStyleSheet();
+    static _bulletStyleSheet: any = addStyleSheet();
+    static _userStyleSheet: any = addStyleSheet();
 
     onClick = (e: React.MouseEvent): void => {
         if ((e.nativeEvent as any).formattedHandled) { e.stopPropagation(); return; }
@@ -824,9 +868,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
 
     // 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, offsetX: number, select: boolean = false) {
-        if (FormattedTextBox._sheet.rules.length) {
-            removeStyleSheetRule(FormattedTextBox._sheet, 0);
-        }
+        clearStyleSheetRules(FormattedTextBox._bulletStyleSheet);
         if (this.props.isSelected() && offsetX < 40) {
             let pos = this._editorView!.posAtCoords({ left: x, top: y });
             if (pos && pos.pos > 0) {
@@ -840,7 +882,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                         let ol = this._editorView!.state.doc.nodeAt(pos.pos - 2) ? this._editorView!.state.doc.nodeAt(pos.pos - 2) : undefined;
                         if (ol && ol.type === schema.nodes.ordered_list && select) {
                             this._editorView!.dispatch(this._editorView!.state.tr.setSelection(new NodeSelection(this._editorView!.state.doc.resolve(pos.pos - 2))));
-                            addStyleSheetRule(FormattedTextBox._sheet, hit.className + ":before", { background: "gray" });
+                            addStyleSheetRule(FormattedTextBox._bulletStyleSheet, hit.className + ":before", { background: "gray" });
                         } else {
                             this._editorView!.dispatch(this._editorView!.state.tr.setNodeMarkup(pos.pos - 1, node2.type, { ...node2.attrs, visibility: !node2.attrs.visibility }));
                         }
@@ -898,7 +940,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         if (e.key === "Tab" || e.key === "Enter") {
             e.preventDefault();
         }
-        this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() })));
+        let mark = schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.round(Date.now() / 1000 / 5) });
+        this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(mark));
 
         if (!this._undoTyping) {
             this._undoTyping = UndoManager.StartBatch("undoTyping");
-- 
cgit v1.2.3-70-g09d2


From bfdbc495c0327b87ee0f896778968e65e2f18af6 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 9 Oct 2019 20:39:40 -0400
Subject: fixed rich text search highlights

---
 src/client/util/RichTextSchema.tsx          |  7 ++-
 src/client/views/nodes/FormattedTextBox.tsx | 79 ++++++++++++++++++++---------
 2 files changed, 59 insertions(+), 27 deletions(-)

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

diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 80bcf25ad..a5502577b 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -427,10 +427,13 @@ export const marks: { [index: string]: MarkSpec } = {
     },
 
     search_highlight: {
+        attrs: {
+            selected: { default: false }
+        },
         parseDOM: [{ style: 'background: yellow' }],
-        toDOM() {
+        toDOM(node: any) {
             return ['span', {
-                style: 'background: yellow'
+                style: `background: ${node.attrs.selected ? "orange" : "yellow"}`
             }];
         }
     },
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 67d1bc42c..77543d2e8 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -7,7 +7,7 @@ import { baseKeymap } from "prosemirror-commands";
 import { history } from "prosemirror-history";
 import { inputRules } from 'prosemirror-inputrules';
 import { keymap } from "prosemirror-keymap";
-import { Fragment, Mark, Node, Node as ProsNode, NodeType, Slice } from "prosemirror-model";
+import { Fragment, Mark, Node, Node as ProsNode, Slice } from "prosemirror-model";
 import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from "prosemirror-state";
 import { ReplaceStep } from 'prosemirror-transform';
 import { EditorView } from "prosemirror-view";
@@ -43,7 +43,7 @@ import { FormattedTextBoxComment, formattedTextBoxCommentPlugin } from './Format
 import React = require("react");
 import { ContextMenuProps } from '../ContextMenuItem';
 import { ContextMenu } from '../ContextMenu';
-import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
+import { TextShadowProperty } from 'csstype';
 
 library.add(faEdit);
 library.add(faSmile, faTextHeight, faUpload);
@@ -80,6 +80,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     private _editorView: Opt<EditorView>;
     private _applyingChange: boolean = false;
     private _nodeClicked: any;
+    private _searchIndex = 0;
     private _undoTyping?: UndoManager.Batch;
     private _searchReactionDisposer?: Lambda;
     private _scrollToRegionReactionDisposer: Opt<IReactionDisposer>;
@@ -212,37 +213,27 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         }
     }
 
-    public highlightSearchTerms = (terms: String[]) => {
+    public highlightSearchTerms = (terms: string[]) => {
         if (this._editorView && (this._editorView as any).docView) {
-            const doc = this._editorView.state.doc;
             const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight);
-            doc.nodesBetween(0, doc.content.size, (node: ProsNode, pos: number, parent: ProsNode, index: number) => {
-                if (node.isLeaf && node.isText && node.text) {
-                    let nodeText: String = node.text;
-                    let tokens = nodeText.split(" ");
-                    let start = pos;
-                    tokens.forEach((word) => {
-                        if (terms.includes(word) && this._editorView) {
-                            this._editorView.dispatch(this._editorView.state.tr.addMark(start, start + word.length, mark).removeStoredMark(mark));
-                        }
-                        start += word.length + 1;
-                    });
-                }
-            });
+            const activeMark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight, { selected: true });
+            let res = terms.map(term => this.findInNode(this._editorView!, this._editorView!.state.doc, term));
+            let tr = this._editorView!.state.tr;
+            let flattened: TextSelection[] = [];
+            res.map(r => r.map(h => flattened.push(h)));
+            let 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 doc = this._editorView.state.doc;
             const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight);
-            doc.nodesBetween(0, doc.content.size, (node: ProsNode, pos: number, parent: ProsNode, index: number) => {
-                if (node.isLeaf && node.isText && node.text) {
-                    if (node.marks.includes(mark) && this._editorView) {
-                        this._editorView.dispatch(this._editorView.state.tr.removeMark(pos, pos + node.nodeSize, mark));
-                    }
-                }
-            });
+            const activeMark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight, { selected: true });
+            let end = this._editorView!.state.doc.textContent.length - 1;
+            this._editorView!.dispatch(this._editorView!.state.tr.removeMark(0, end, mark).removeMark(0, end, activeMark));
         }
     }
     setAnnotation = (start: number, end: number, mark: Mark, opened: boolean, keep: boolean = false) => {
@@ -299,7 +290,45 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         }
     }
 
+    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) {
+            for (let i = 0; i < (context.content as any).content.length; i++) {
+                let 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, ep = this.getNodeEndpoints(pm.state.doc, node);
+            while (ep && (foundAt = node.textContent.slice(index).search(RegExp(find, "i"))) > -1) {
+                let 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[] = [];
 
     updateHighlights = () => {
-- 
cgit v1.2.3-70-g09d2


From 21aab2fbf28e9cf6d08366b57d368d120d6813bb Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 10 Oct 2019 16:42:12 -0400
Subject: got rid of all CollectionView wrappers around XxxBox's  added
 DocAnnotatableComponent

---
 src/client/documents/Documents.ts                  |   4 +-
 src/client/util/RichTextSchema.tsx                 |   4 +-
 src/client/views/DocComponent.tsx                  |  50 ++++++-
 .../collections/CollectionMasonryViewFieldRow.tsx  |   2 +-
 .../CollectionStackingViewFieldColumn.tsx          |   2 +-
 src/client/views/collections/CollectionSubView.tsx |   1 +
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  20 +--
 .../collections/collectionFreeForm/MarqueeView.tsx |   2 +-
 src/client/views/nodes/FormattedTextBox.tsx        |   8 +-
 src/client/views/nodes/ImageBox.scss               |   8 +-
 src/client/views/nodes/ImageBox.tsx                |  44 +++++--
 src/client/views/nodes/PDFBox.tsx                  |  24 ++--
 src/client/views/nodes/VideoBox.scss               |   8 +-
 src/client/views/nodes/VideoBox.tsx                | 143 ++++++---------------
 src/client/views/nodes/WebBox.tsx                  |  69 +++++++---
 src/client/views/pdf/Annotation.tsx                |   8 +-
 src/client/views/pdf/PDFViewer.tsx                 |  65 +++-------
 src/new_fields/Doc.ts                              |   6 +-
 18 files changed, 234 insertions(+), 234 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 22aa74634..0114f82d8 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -120,11 +120,11 @@ export namespace Docs {
                 options: { height: 300, backgroundColor: "black" }
             }],
             [DocumentType.IMG, {
-                layout: { view: ImageBox, collectionView: [CollectionView, data, anno] as CollectionViewType },
+                layout: { view: ImageBox, ext: anno },
                 options: {}
             }],
             [DocumentType.WEB, {
-                layout: { view: WebBox, collectionView: [CollectionView, data, anno] as CollectionViewType },
+                layout: { view: WebBox, ext: anno },
                 options: { height: 300 }
             }],
             [DocumentType.COL, {
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index a5502577b..063686d58 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -784,8 +784,8 @@ export class DashDocView {
                     addDocTab={self._textBox.props.addDocTab}
                     pinToPres={returnFalse}
                     renderDepth={1}
-                    PanelWidth={self._dashDoc![WidthSym]}
-                    PanelHeight={self._dashDoc![HeightSym]}
+                    PanelWidth={self._dashDoc[WidthSym]}
+                    PanelHeight={self._dashDoc[HeightSym]}
                     focus={emptyFunction}
                     backgroundColor={returnEmptyString}
                     parentActive={returnFalse}
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index d6562492f..93e852bef 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -1,6 +1,8 @@
 import * as React from 'react';
 import { Doc } from '../../new_fields/Doc';
-import { computed } from 'mobx';
+import { computed, action } from 'mobx';
+import { Cast } from '../../new_fields/Types';
+import { listSpec } from '../../new_fields/Schema';
 
 export function DocComponent<P extends { Document: Doc }, T>(schemaCtor: (doc: Doc) => T) {
     class Component extends React.Component<P> {
@@ -11,4 +13,50 @@ export function DocComponent<P extends { Document: Doc }, T>(schemaCtor: (doc: D
         }
     }
     return Component;
+}
+
+interface DocAnnotatableProps {
+    Document: Doc;
+    DataDoc?: Doc;
+    fieldKey: string;
+    fieldExt: string;
+    whenActiveChanged: (isActive: boolean) => void;
+    isSelected: () => boolean, renderDepth: number
+}
+export function DocAnnotatableComponent<P extends DocAnnotatableProps, T>(schemaCtor: (doc: Doc) => T) {
+    class Component extends React.Component<P> {
+        //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then
+        @computed
+        get Document(): T {
+            return schemaCtor(this.props.Document);
+        }
+        _isChildActive = false;
+        @action.bound
+        removeDocument(doc: Doc): boolean {
+            Doc.GetProto(doc).annotationOn = undefined;
+            let value = Cast(this.extensionDoc[this.props.fieldExt], listSpec(Doc), []);
+            let index = value ? Doc.IndexOf(doc, value.map(d => d as Doc), true) : -1;
+            return index !== -1 && value.splice(index, 1) ? true : false;
+        }
+
+        @computed get dataDoc() { return (this.props.DataDoc && this.props.Document.isTemplate ? this.props.DataDoc : Doc.GetProto(this.props.Document)) as Doc; }
+
+        @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); }
+
+        // if the moved document is already in this overlay collection nothing needs to be done.
+        // otherwise, if the document can be removed from where it was, it will then be added to this document's overlay collection. 
+        @action.bound
+        moveDocument(doc: Doc, targetCollection: Doc, addDocument: (doc: Doc) => boolean): boolean {
+            return Doc.AreProtosEqual(this.props.Document, targetCollection) ? true : this.removeDocument(doc) ? addDocument(doc) : false;
+        }
+
+        @action.bound
+        addDocument(doc: Doc): boolean {
+            Doc.GetProto(doc).annotationOn = this.props.Document;
+            return Doc.AddDocToList(this.extensionDoc, this.props.fieldExt, doc);
+        }
+        whenActiveChanged = (isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive);
+        active = () => this.props.isSelected() || this._isChildActive || this.props.renderDepth === 0;
+    }
+    return Component;
 }
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
index 5c1960d53..6251d7114 100644
--- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
+++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
@@ -86,7 +86,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
             this.props.parent.drop(e, de);
             e.stopPropagation();
         }
-    })
+    });
 
     getValue = (value: string): any => {
         let parsed = parseInt(value);
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index e8627780d..815b28586 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -72,7 +72,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
             this.props.parent.drop(e, de);
             e.stopPropagation();
         }
-    })
+    });
     getValue = (value: string): any => {
         let parsed = parseInt(value);
         if (!isNaN(parsed)) {
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 689adc375..06d048383 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -38,6 +38,7 @@ export interface CollectionViewProps extends FieldViewProps {
 export interface SubCollectionViewProps extends CollectionViewProps {
     CollectionView: Opt<CollectionView>;
     ruleProvider: Doc | undefined;
+    children?: never | (() => JSX.Element[]) | React.ReactNode;
 }
 
 export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index d2644480c..adbad5da5 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, faPaintBrush, faTable, faUpload, faFileUpload } from "@fortawesome/free-solid-svg-icons";
+import { faBraille, faChalkboard, faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faFileUpload, faPaintBrush, faTable, faUpload } from "@fortawesome/free-solid-svg-icons";
 import { action, computed, observable } from "mobx";
 import { observer } from "mobx-react";
 import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../../new_fields/Doc";
@@ -286,7 +286,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
     @action
     onPointerMove = (e: PointerEvent): void => {
-        if (!e.cancelBubble && !this.isAnnotationOverlay) {
+        if (!e.cancelBubble) {
             if (this._hitCluster && this.tryDragCluster(e)) {
                 e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document'  however it does mark the event as cancelBubble=true which we test for in the move event handlers
                 e.preventDefault();
@@ -339,7 +339,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
     @action
     onPointerWheel = (e: React.WheelEvent): void => {
-        if (this.props.Document.lockedPosition || this.props.Document.inOverlay || this.isAnnotationOverlay) return;
+        if (this.props.Document.lockedPosition || this.props.Document.inOverlay) return;
         if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) { // things that can scroll vertically should do that instead of zooming
             e.stopPropagation();
         }
@@ -477,7 +477,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             PanelWidth: layoutDoc[WidthSym],
             PanelHeight: layoutDoc[HeightSym],
             ContentScaling: returnOne,
-            ContainingCollectionView: this.props.CollectionView,
+            ContainingCollectionView: this.props.ContainingCollectionView,
             focus: this.focusDocument,
             backgroundColor: returnEmptyString,
             parentActive: this.props.active,
@@ -681,10 +681,14 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     }
 
 
-    private childViews = () => [
-        <CollectionFreeFormBackgroundView key="backgroundView" {...this.props} {...this.getDocumentViewProps(this.props.Document)} />,
-        ...this.views
-    ]
+    private childViews = () => {
+        let children = typeof this.props.children === "function" ? (this.props.children as any)() as JSX.Element[] : [];
+        return [
+            <CollectionFreeFormBackgroundView key="backgroundView" {...this.props} {...this.getDocumentViewProps(this.props.Document)} />,
+            ...children,
+            ...this.views,
+        ];
+    }
     render() {
         // update the actual dimensions of the collection so that they can inquired (e.g., by a minimap)
         this.props.Document.fitX = this.contentBounds && this.contentBounds.x;
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index bb787106c..ecdd02b0f 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -217,7 +217,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
             this._downY = y;
             PreviewCursor.Show(x, y, this.onKeyPress, this.props.addLiveTextDocument, this.props.getTransform, this.props.addDocument);
         }
-    })
+    });
 
     @action
     onClick = (e: React.MouseEvent): void => {
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 9ca77cc8b..cbe4945af 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -218,13 +218,13 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             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 });
             let res = terms.map(term => this.findInNode(this._editorView!, this._editorView!.state.doc, term));
-            let tr = this._editorView!.state.tr;
+            let tr = this._editorView.state.tr;
             let flattened: TextSelection[] = [];
             res.map(r => r.map(h => flattened.push(h)));
             let 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());
+            this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(flattened[lastSel].from), tr.doc.resolve(flattened[lastSel].to))).scrollIntoView());
         }
     }
 
@@ -232,8 +232,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         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 });
-            let end = this._editorView!.state.doc.nodeSize - 2;
-            this._editorView!.dispatch(this._editorView!.state.tr.removeMark(0, end, mark).removeMark(0, end, activeMark));
+            let end = this._editorView.state.doc.nodeSize - 2;
+            this._editorView.dispatch(this._editorView.state.tr.removeMark(0, end, mark).removeMark(0, end, activeMark));
         }
     }
     setAnnotation = (start: number, end: number, mark: Mark, opened: boolean, keep: boolean = false) => {
diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss
index 71d718b39..97d858f58 100644
--- a/src/client/views/nodes/ImageBox.scss
+++ b/src/client/views/nodes/ImageBox.scss
@@ -1,9 +1,9 @@
-.imageBox-cont {
+.imageBox-cont, .imageBox-cont-interactive {
     padding: 0vw;
-    position: relative;
+    position: absolute;
     text-align: center;
     width: 100%;
-    height: auto;
+    height: 100%;
     max-width: 100%;
     max-height: 100%;
     pointer-events: none;
@@ -11,8 +11,6 @@
 
 .imageBox-cont-interactive {
     pointer-events: all;
-    width: 100%;
-    height: auto;
 }
 
 .imageBox-dot {
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index a198a0764..9f39eccea 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -13,20 +13,21 @@ import { ComputedField } from '../../../new_fields/ScriptField';
 import { BoolCast, Cast, FieldValue, NumCast, StrCast } from '../../../new_fields/Types';
 import { AudioField, ImageField } from '../../../new_fields/URLField';
 import { RouteStore } from '../../../server/RouteStore';
-import { Utils } from '../../../Utils';
+import { Utils, returnOne, emptyFunction } from '../../../Utils';
 import { CognitiveServices, Confidence, Service, Tag } from '../../cognitive_services/CognitiveServices';
 import { Docs } from '../../documents/Documents';
 import { DragManager } from '../../util/DragManager';
 import { undoBatch } from '../../util/UndoManager';
 import { ContextMenu } from "../../views/ContextMenu";
 import { ContextMenuProps } from '../ContextMenuItem';
-import { DocComponent } from '../DocComponent';
+import { DocAnnotatableComponent } from '../DocComponent';
 import { InkingControl } from '../InkingControl';
 import { documentSchema } from './DocumentView';
 import FaceRectangles from './FaceRectangles';
 import { FieldView, FieldViewProps } from './FieldView';
 import "./ImageBox.scss";
 import React = require("react");
+import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
 var requestImageSize = require('../../util/request-image-size');
 var path = require('path');
 const { Howl } = require('howler');
@@ -54,9 +55,10 @@ type ImageDocument = makeInterface<[typeof pageSchema, typeof documentSchema]>;
 const ImageDocument = makeInterface(pageSchema, documentSchema);
 
 @observer
-export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageDocument) {
+export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocument>(ImageDocument) {
 
-    public static LayoutString(fieldKey?: string) { return FieldView.LayoutString(ImageBox, fieldKey); }
+    public static LayoutString(fieldExt?: string) { return FieldView.LayoutString(ImageBox, "data", fieldExt); }
+    @observable static _showControls: boolean;
     private _imgRef: React.RefObject<HTMLImageElement> = React.createRef();
     private _downX: number = 0;
     private _downY: number = 0;
@@ -65,10 +67,6 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
     private dropDisposer?: DragManager.DragDropDisposer;
     @observable private hoverActive = false;
 
-    @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); }
-
-    @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplate ? this.props.DataDoc : Doc.GetProto(this.props.Document); }
-
     protected createDropTarget = (ele: HTMLDivElement) => {
         if (this.dropDisposer) {
             this.dropDisposer();
@@ -381,7 +379,8 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
         return (null);
     }
 
-    render() {
+    @computed
+    get content() {
         // let transform = this.props.ScreenToLocalTransform().inverse();
         let pw = typeof this.props.PanelWidth === "function" ? this.props.PanelWidth() : typeof this.props.PanelWidth === "number" ? (this.props.PanelWidth as any) as number : 50;
         // var [sptX, sptY] = transform.transformPoint(0, 0);
@@ -393,7 +392,6 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
         let paths: string[] = [Utils.CorsProxy("http://www.cs.brown.edu/~bcz/noImage.png")];
         // this._curSuffix = "";
         // if (w > 20) {
-        Doc.UpdateDocumentExtensionForField(this.dataDoc, this.props.fieldKey);
         let alts = DocListCast(this.extensionDoc.Alternates);
         let altpaths: string[] = alts.filter(doc => doc.data instanceof ImageField).map(doc => this.choosePath((doc.data as ImageField).url));
         let field = this.dataDoc[this.props.fieldKey];
@@ -448,4 +446,30 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
                 <FaceRectangles document={this.extensionDoc} color={"#0000FF"} backgroundColor={"#0000FF"} />
             </div>);
     }
+
+    render() {
+        Doc.UpdateDocumentExtensionForField(this.dataDoc, this.props.fieldKey);
+        return (<div className={"imageBox-container"} onContextMenu={this.specificContextMenu}>
+            <CollectionFreeFormView {...this.props}
+                PanelHeight={this.props.PanelHeight}
+                PanelWidth={this.props.PanelWidth}
+                focus={this.props.focus}
+                isSelected={this.props.isSelected}
+                select={emptyFunction}
+                active={this.active}
+                ContentScaling={returnOne}
+                whenActiveChanged={this.whenActiveChanged}
+                removeDocument={this.removeDocument}
+                moveDocument={this.moveDocument}
+                addDocument={this.addDocument}
+                CollectionView={undefined}
+                ScreenToLocalTransform={this.props.ScreenToLocalTransform}
+                ruleProvider={undefined}
+                renderDepth={this.props.renderDepth + 1}
+                ContainingCollectionDoc={this.props.ContainingCollectionDoc}
+                chromeCollapsed={true}>
+                {() => [this.content]}
+            </CollectionFreeFormView>
+        </div >);
+    }
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 57803be1f..9af6d7cad 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -1,17 +1,21 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, IReactionDisposer, observable, reaction, runInAction, untracked, trace } from 'mobx';
+import { action, observable, runInAction } from 'mobx';
 import { observer } from "mobx-react";
 import * as Pdfjs from "pdfjs-dist";
 import "pdfjs-dist/web/pdf_viewer.css";
 import 'react-image-lightbox/style.css';
-import { Doc, Opt, WidthSym } from "../../../new_fields/Doc";
+import { Opt, WidthSym } from "../../../new_fields/Doc";
 import { makeInterface } from "../../../new_fields/Schema";
 import { ScriptField } from '../../../new_fields/ScriptField';
-import { Cast, NumCast } from "../../../new_fields/Types";
+import { Cast } from "../../../new_fields/Types";
 import { PdfField } from "../../../new_fields/URLField";
+import { Utils } from '../../../Utils';
 import { KeyCodes } from '../../northstar/utils/KeyCodes';
+import { undoBatch } from '../../util/UndoManager';
 import { panZoomSchema } from '../collections/collectionFreeForm/CollectionFreeFormView';
-import { DocComponent } from "../DocComponent";
+import { ContextMenu } from '../ContextMenu';
+import { ContextMenuProps } from '../ContextMenuItem';
+import { DocAnnotatableComponent } from "../DocComponent";
 import { InkingControl } from "../InkingControl";
 import { PDFViewer } from "../pdf/PDFViewer";
 import { documentSchema } from "./DocumentView";
@@ -19,22 +23,17 @@ import { FieldView, FieldViewProps } from './FieldView';
 import { pageSchema } from "./ImageBox";
 import "./PDFBox.scss";
 import React = require("react");
-import { undoBatch } from '../../util/UndoManager';
-import { ContextMenuProps } from '../ContextMenuItem';
-import { ContextMenu } from '../ContextMenu';
-import { Utils } from '../../../Utils';
 
 type PdfDocument = makeInterface<[typeof documentSchema, typeof panZoomSchema, typeof pageSchema]>;
 const PdfDocument = makeInterface(documentSchema, panZoomSchema, pageSchema);
 
 @observer
-export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocument) {
+export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument>(PdfDocument) {
     public static LayoutString(fieldExt?: string) { return FieldView.LayoutString(PDFBox, "data", fieldExt); }
     private _keyValue: string = "";
     private _valueValue: string = "";
     private _scriptValue: string = "";
     private _searchString: string = "";
-    private _isChildActive = false;
     private _everActive = false; // has this box ever had its contents activated -- if so, stop drawing the overlay title
     private _pdfViewer: PDFViewer | undefined;
     private _keyRef: React.RefObject<HTMLInputElement> = React.createRef();
@@ -46,9 +45,6 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
     @observable private _pdf: Opt<Pdfjs.PDFDocumentProxy>;
     @observable private _pageControls = false;
 
-    @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); }
-    @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplate ? this.props.DataDoc : Doc.GetProto(this.props.Document); }
-
     componentDidMount() {
         const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField);
         if (pdfUrl instanceof PdfField) {
@@ -206,7 +202,7 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
                     pinToPres={this.props.pinToPres} addDocument={this.props.addDocument}
                     ScreenToLocalTransform={this.props.ScreenToLocalTransform} select={this.props.select}
                     isSelected={this.props.isSelected} whenActiveChanged={this.whenActiveChanged}
-                    fieldKey={this.props.fieldKey} fieldExtensionDoc={this.extensionDoc} startupLive={this._initialScale < 2.5 ? true : false} />
+                    fieldKey={this.props.fieldKey} extensionDoc={this.extensionDoc} startupLive={this._initialScale < 2.5 ? true : false} />
                 {this.settingsPanel()}
             </div>);
     }
diff --git a/src/client/views/nodes/VideoBox.scss b/src/client/views/nodes/VideoBox.scss
index b3cd439aa..48623eaaf 100644
--- a/src/client/views/nodes/VideoBox.scss
+++ b/src/client/views/nodes/VideoBox.scss
@@ -1,8 +1,6 @@
-// .videoBox-container {
-//     .collectionfreeformview-container {
-//         mix-blend-mode: multiply;
-//     }
-// }
+.videoBox-container {
+    pointer-events: all;
+}
 
 .videoBox-content-YouTube, .videoBox-content-YouTube-fullScreen,
 .videoBox-content, .videoBox-content-interactive, .videoBox-cont-fullScreen {
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index feb067d8f..aa9b28118 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -11,7 +11,7 @@ import { Utils, emptyFunction, returnOne } from "../../../Utils";
 import { Docs, DocUtils } from "../../documents/Documents";
 import { ContextMenu } from "../ContextMenu";
 import { ContextMenuProps } from "../ContextMenuItem";
-import { DocComponent } from "../DocComponent";
+import { DocAnnotatableComponent } from "../DocComponent";
 import { DocumentDecorations } from "../DocumentDecorations";
 import { InkingControl } from "../InkingControl";
 import { documentSchema } from "./DocumentView";
@@ -35,7 +35,7 @@ const VideoDocument = makeInterface(documentSchema, positionSchema, timeSchema);
 library.add(faVideo);
 
 @observer
-export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoDocument) {
+export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocument>(VideoDocument) {
     static _youtubeIframeCounter: number = 0;
     private _reactionDisposer?: IReactionDisposer;
     private _youtubeReactionDisposer?: IReactionDisposer;
@@ -43,16 +43,12 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
     private _videoRef: HTMLVideoElement | null = null;
     private _youtubeIframeId: number = -1;
     private _youtubeContentCreated = false;
-    private _downX: number = 0;
-    private _downY: number = 0;
     private _isResetClick = 0;
-    private _isChildActive = false;
-    private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean) => void);
     @observable _forceCreateYouTubeIFrame = false;
-    @observable static _showControls: boolean;
     @observable _playTimer?: NodeJS.Timeout = undefined;
     @observable _fullScreen = false;
-    @observable public Playing: boolean = false;
+    @observable _playing = false;
+    @observable static _showControls: boolean;
     public static LayoutString(fieldExt?: string) { return FieldView.LayoutString(VideoBox, "data", fieldExt); }
 
     public get player(): HTMLVideoElement | null {
@@ -72,7 +68,7 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
     }
 
     @action public Play = (update: boolean = true) => {
-        this.Playing = true;
+        this._playing = true;
         update && this.player && this.player.play();
         update && this._youtubePlayer && this._youtubePlayer.playVideo();
         this._youtubePlayer && !this._playTimer && (this._playTimer = setInterval(this.updateTimecode, 5));
@@ -85,7 +81,7 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
     }
 
     @action public Pause = (update: boolean = true) => {
-        this.Playing = false;
+        this._playing = false;
         update && this.player && this.player.pause();
         update && this._youtubePlayer && this._youtubePlayer.pauseVideo && this._youtubePlayer.pauseVideo();
         this._youtubePlayer && this._playTimer && clearInterval(this._playTimer);
@@ -181,7 +177,7 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
             vref.onfullscreenchange = action((e) => this._fullScreen = vref.webkitDisplayingFullscreen);
             this._reactionDisposer && this._reactionDisposer();
             this._reactionDisposer = reaction(() => this.Document.currentTimecode || 0,
-                time => !this.Playing && (vref.currentTime = time), { fireImmediately: true });
+                time => !this._playing && (vref.currentTime = time), { fireImmediately: true });
         }
     }
 
@@ -218,7 +214,7 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
         let interactive = InkingControl.Instance.selectedTool || !this.props.isSelected() ? "" : "-interactive";
         let style = "videoBox-content" + (this._fullScreen ? "-fullScreen" : "") + interactive;
         return !field ? <div>Loading</div> :
-            <video className={`${style}`} ref={this.setVideoRef} onCanPlay={this.videoLoad} controls={VideoBox._showControls}
+            <video className={`${style}`} key="video" ref={this.setVideoRef} onCanPlay={this.videoLoad} controls={VideoBox._showControls}
                 onPlay={() => this.Play()} onSeeked={this.updateTimecode} onPause={() => this.Pause()} onClick={e => e.preventDefault()}>
                 <source src={field.url.href} type="video/mp4" />
                 Not supported.
@@ -246,13 +242,13 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
                 this.Pause();
                 return;
             }
-            if (event.data === YT.PlayerState.PLAYING && !this.Playing) this.Play(false);
-            if (event.data === YT.PlayerState.PAUSED && this.Playing) this.Pause(false);
+            if (event.data === YT.PlayerState.PLAYING && !this._playing) this.Play(false);
+            if (event.data === YT.PlayerState.PAUSED && this._playing) this.Pause(false);
         });
         let onYoutubePlayerReady = (event: any) => {
             this._reactionDisposer && this._reactionDisposer();
             this._youtubeReactionDisposer && this._youtubeReactionDisposer();
-            this._reactionDisposer = reaction(() => this.Document.currentTimecode, () => !this.Playing && this.Seek(this.Document.currentTimecode || 0));
+            this._reactionDisposer = reaction(() => this.Document.currentTimecode, () => !this._playing && this.Seek(this.Document.currentTimecode || 0));
             this._youtubeReactionDisposer = reaction(() => [this.props.isSelected(), DocumentDecorations.Instance.Interacting, InkingControl.Instance.selectedTool], () => {
                 let interactive = InkingControl.Instance.selectedTool === InkTool.None && this.props.isSelected() && !DocumentDecorations.Instance.Interacting;
                 iframe.style.pointerEvents = interactive ? "all" : "none";
@@ -278,22 +274,16 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
         </div>,
         VideoBox._showControls ? (null) : [
             <div className="videoBox-play" key="play" onPointerDown={this.onPlayDown} style={{ transform: `scale(${scaling})` }}>
-                <FontAwesomeIcon icon={this.Playing ? "pause" : "play"} size="lg" />
+                <FontAwesomeIcon icon={this._playing ? "pause" : "play"} size="lg" />
             </div>,
-            <div className="collecvideoBoxtionVideoView-full" key="full" onPointerDown={this.onFullDown} style={{ transform: `scale(${scaling})` }}>
+            <div className="videoBox-full" key="full" onPointerDown={this.onFullDown} style={{ transform: `scale(${scaling})` }}>
                 F
             </div>
         ]]);
     }
 
     @action
-    onPlayDown = () => {
-        if (this.Playing) {
-            this.Pause();
-        } else {
-            this.Play();
-        }
-    }
+    onPlayDown = () => this._playing ? this.Pause() : this.Play()
 
     @action
     onFullDown = (e: React.PointerEvent) => {
@@ -342,97 +332,40 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
         let start = untracked(() => Math.round(this.Document.currentTimecode || 0));
         return <iframe key={this._youtubeIframeId} id={`${this.youtubeVideoId + this._youtubeIframeId}-player`}
             onLoad={this.youtubeIframeLoaded} className={`${style}`} width={(this.Document.nativeWidth || 640)} height={(this.Document.nativeHeight || 390)}
-            src={`https://www.youtube.com/embed/${this.youtubeVideoId}?enablejsapi=1&rel=0&showinfo=1&autoplay=1&mute=1&start=${start}&modestbranding=1&controls=${VideoBox._showControls ? 1 : 0}`}
-        ></iframe>;
-    }
-
-
-    setPreviewCursor = (func?: (x: number, y: number, drag: boolean) => void) => {
-        this._setPreviewCursor = func;
+            src={`https://www.youtube.com/embed/${this.youtubeVideoId}?enablejsapi=1&rel=0&showinfo=1&autoplay=1&mute=1&start=${start}&modestbranding=1&controls=${VideoBox._showControls ? 1 : 0}`} />;
     }
 
     @action.bound
-    removeDocument(doc: Doc): boolean {
-        Doc.GetProto(doc).annotationOn = undefined;
-        //TODO This won't create the field if it doesn't already exist
-        let targetDataDoc = this.fieldExtensionDoc;
-        let targetField = this.props.fieldExt;
-        let value = Cast(targetDataDoc[targetField], 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);
-        index !== -1 && value.splice(index, 1);
-        return true;
-    }
-    // this is called with the document that was dragged and the collection to move it into.
-    // if the target collection is the same as this collection, then the move will be allowed.
-    // otherwise, the document being moved must be able to be removed from its container before
-    // moving it into the target.  
-    @action.bound
-    moveDocument(doc: Doc, targetCollection: Doc, addDocument: (doc: Doc) => boolean): boolean {
-        if (Doc.AreProtosEqual(this.props.Document, targetCollection)) {
-            return true;
-        }
-        return this.removeDocument(doc) ? addDocument(doc) : false;
-    }
-
-    @action.bound
-    addDocument(doc: Doc): boolean {
+    addDocumentWithTimestamp(doc: Doc): boolean {
         Doc.GetProto(doc).annotationOn = this.props.Document;
         var curTime = NumCast(this.props.Document.currentTimecode, -1);
         curTime !== -1 && (doc.displayTimecode = curTime);
-        Doc.AddDocToList(this.fieldExtensionDoc, this.props.fieldExt, doc);
-        return true;
-    }
-    whenActiveChanged = (isActive: boolean) => {
-        this._isChildActive = isActive;
-        this.props.whenActiveChanged(isActive);
-    }
-    active = () => {
-        return this.props.isSelected() || this._isChildActive || this.props.renderDepth === 0;
-    }
-    onClick = (e: React.MouseEvent) => {
-        this._setPreviewCursor &&
-            e.button === 0 &&
-            Math.abs(e.clientX - this._downX) < 3 &&
-            Math.abs(e.clientY - this._downY) < 3 &&
-            this._setPreviewCursor(e.clientX, e.clientY, false);
-    }
-
-    onPointerDown = (e: React.PointerEvent): void => {
-        this._downX = e.clientX;
-        this._downY = e.clientY;
-        if ((e.button !== 0 || e.altKey) && this.active()) {
-            this._setPreviewCursor && this._setPreviewCursor(e.clientX, e.clientY, true);
-        }
-    }
-    @computed get annotationLayer() {
-        return <CollectionFreeFormView {...this.props}
-            setPreviewCursor={this.setPreviewCursor}
-            PanelHeight={this.props.PanelHeight}
-            PanelWidth={this.props.PanelWidth}
-            focus={this.props.focus}
-            isSelected={this.props.isSelected}
-            select={emptyFunction}
-            active={this.active}
-            ContentScaling={returnOne}
-            whenActiveChanged={this.whenActiveChanged}
-            removeDocument={this.removeDocument}
-            moveDocument={this.moveDocument}
-            addDocument={this.addDocument}
-            CollectionView={undefined}
-            ScreenToLocalTransform={this.props.ScreenToLocalTransform}
-            ruleProvider={undefined}
-            renderDepth={this.props.renderDepth + 1}
-            ContainingCollectionDoc={this.props.ContainingCollectionDoc}
-            chromeCollapsed={true}>
-        </CollectionFreeFormView>
+        return Doc.AddDocToList(this.fieldExtensionDoc, this.props.fieldExt, doc);
     }
 
     render() {
         Doc.UpdateDocumentExtensionForField(this.dataDoc, this.props.fieldKey);
-        return (<div className={"videoBox-container"} onClick={this.onClick} onPointerDown={this.onPointerDown} onContextMenu={this.specificContextMenu}>
-            {this.youtubeVideoId ? this.youtubeContent : this.content}
-            {this.annotationLayer}
+        return (<div className={"videoBox-container"} onContextMenu={this.specificContextMenu}>
+            <CollectionFreeFormView {...this.props}
+                PanelHeight={this.props.PanelHeight}
+                PanelWidth={this.props.PanelWidth}
+                focus={this.props.focus}
+                isSelected={this.props.isSelected}
+                select={emptyFunction}
+                active={this.active}
+                ContentScaling={returnOne}
+                whenActiveChanged={this.whenActiveChanged}
+                removeDocument={this.removeDocument}
+                moveDocument={this.moveDocument}
+                addDocument={this.addDocumentWithTimestamp}
+                CollectionView={undefined}
+                ScreenToLocalTransform={this.props.ScreenToLocalTransform}
+                ruleProvider={undefined}
+                renderDepth={this.props.renderDepth + 1}
+                ContainingCollectionDoc={this.props.ContainingCollectionDoc}
+                chromeCollapsed={true}>
+                {() => [this.youtubeVideoId ? this.youtubeContent : this.content]}
+            </CollectionFreeFormView>
             {this.uIButtons}
         </div >);
     }
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index bb4c5adc9..7c7f9fb83 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -1,34 +1,36 @@
+import { library } from "@fortawesome/fontawesome-svg-core";
+import { faStickyNote } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { action, computed, observable } from "mobx";
 import { observer } from "mobx-react";
-import { FieldResult, Doc, Field } from "../../../new_fields/Doc";
+import { Doc, FieldResult } from "../../../new_fields/Doc";
 import { HtmlField } from "../../../new_fields/HtmlField";
+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 { emptyFunction, returnOne, Utils } from "../../../Utils";
+import { Docs } from "../../documents/Documents";
+import { SelectionManager } from "../../util/SelectionManager";
+import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
 import { DocumentDecorations } from "../DocumentDecorations";
 import { InkingControl } from "../InkingControl";
 import { FieldView, FieldViewProps } from './FieldView';
+import { KeyValueBox } from "./KeyValueBox";
 import "./WebBox.scss";
 import React = require("react");
-import { InkTool } from "../../../new_fields/InkField";
-import { Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types";
-import { Utils } from "../../../Utils";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { faStickyNote } from '@fortawesome/free-solid-svg-icons';
-import { observable, action, computed } from "mobx";
-import { listSpec } from "../../../new_fields/Schema";
-import { RefField } from "../../../new_fields/RefField";
-import { ObjectField } from "../../../new_fields/ObjectField";
-import { updateSourceFile } from "typescript";
-import { KeyValueBox } from "./KeyValueBox";
-import { setReactionScheduler } from "mobx/lib/internal";
-import { library } from "@fortawesome/fontawesome-svg-core";
-import { SelectionManager } from "../../util/SelectionManager";
-import { Docs } from "../../documents/Documents";
+import { documentSchema } from "./DocumentView";
+import { DocAnnotatableComponent } from "../DocComponent";
 
 library.add(faStickyNote);
 
+type WebDocument = makeInterface<[typeof documentSchema]>;
+const WebDocument = makeInterface(documentSchema);
+
 @observer
-export class WebBox extends React.Component<FieldViewProps> {
+export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument>(WebDocument) {
 
-    public static LayoutString() { return FieldView.LayoutString(WebBox); }
+    public static LayoutString(fieldExt?: string) { return FieldView.LayoutString(WebBox, "data", fieldExt); }
     @observable private collapsed: boolean = true;
     @observable private url: string = "";
 
@@ -162,8 +164,10 @@ export class WebBox extends React.Component<FieldViewProps> {
             e.stopPropagation();
         }
     }
-    render() {
-        let field = this.props.Document[this.props.fieldKey];
+
+    @computed
+    get content() {
+        let field = this.dataDoc[this.props.fieldKey];
         let view;
         if (field instanceof HtmlField) {
             view = <span id="webBox-htmlSpan" dangerouslySetInnerHTML={{ __html: field.html }} />;
@@ -189,4 +193,29 @@ export class WebBox extends React.Component<FieldViewProps> {
                 {!frozen ? (null) : <div className="webBox-overlay" onWheel={this.onPreWheel} onPointerDown={this.onPrePointer} onPointerMove={this.onPrePointer} onPointerUp={this.onPrePointer} />}
             </>);
     }
+    render() {
+        Doc.UpdateDocumentExtensionForField(this.dataDoc, this.props.fieldKey);
+        return (<div className={"imageBox-container"} >
+            <CollectionFreeFormView {...this.props}
+                PanelHeight={this.props.PanelHeight}
+                PanelWidth={this.props.PanelWidth}
+                focus={this.props.focus}
+                isSelected={this.props.isSelected}
+                select={emptyFunction}
+                active={this.active}
+                ContentScaling={returnOne}
+                whenActiveChanged={this.whenActiveChanged}
+                removeDocument={this.removeDocument}
+                moveDocument={this.moveDocument}
+                addDocument={this.addDocument}
+                CollectionView={undefined}
+                ScreenToLocalTransform={this.props.ScreenToLocalTransform}
+                ruleProvider={undefined}
+                renderDepth={this.props.renderDepth + 1}
+                ContainingCollectionDoc={this.props.ContainingCollectionDoc}
+                chromeCollapsed={true}>
+                {() => [this.content]}
+            </CollectionFreeFormView>
+        </div >);
+    }
 }
\ No newline at end of file
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index 5a07b88d9..ad6240c70 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -11,7 +11,7 @@ import "./Annotation.scss";
 
 interface IAnnotationProps {
     anno: Doc;
-    fieldExtensionDoc: Doc;
+    extensionDoc: Doc;
     addDocTab: (document: Doc, dataDoc: Opt<Doc>, where: string) => boolean;
     pinToPres: (document: Doc) => void;
     focus: (doc: Doc) => void;
@@ -29,7 +29,7 @@ interface IRegionAnnotationProps {
     y: number;
     width: number;
     height: number;
-    fieldExtensionDoc: Doc;
+    extensionDoc: Doc;
     addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => boolean;
     pinToPres: (document: Doc) => void;
     document: Doc;
@@ -66,12 +66,12 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
     }
 
     deleteAnnotation = () => {
-        let annotation = DocListCast(this.props.fieldExtensionDoc.annotations);
+        let annotation = DocListCast(this.props.extensionDoc.annotations);
         let group = FieldValue(Cast(this.props.document.group, Doc));
         if (group) {
             if (annotation.indexOf(group) !== -1) {
                 let newAnnotations = annotation.filter(a => a !== FieldValue(Cast(this.props.document.group, Doc)));
-                this.props.fieldExtensionDoc.annotations = new List<Doc>(newAnnotations);
+                this.props.extensionDoc.annotations = new List<Doc>(newAnnotations);
             }
 
             DocListCast(group.annotations).forEach(anno => anno.delete = true);
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 366861144..d0759d207 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -6,7 +6,7 @@ import { Dictionary } from "typescript-collections";
 import { Doc, DocListCast, FieldResult, WidthSym, Opt, HeightSym } from "../../../new_fields/Doc";
 import { Id } from "../../../new_fields/FieldSymbols";
 import { List } from "../../../new_fields/List";
-import { listSpec } from "../../../new_fields/Schema";
+import { makeInterface } from "../../../new_fields/Schema";
 import { ScriptField } from "../../../new_fields/ScriptField";
 import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
 import { smoothScroll, Utils, emptyFunction, returnOne, intersectRect, addStyleSheet, addStyleSheetRule, clearStyleSheetRules } from "../../../Utils";
@@ -23,19 +23,24 @@ import Annotation from "./Annotation";
 import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
 import { SelectionManager } from "../../util/SelectionManager";
 import { undoBatch } from "../../util/UndoManager";
+import { DocAnnotatableComponent } from "../DocComponent";
+import { documentSchema } from "../nodes/DocumentView";
 const PDFJSViewer = require("pdfjs-dist/web/pdf_viewer");
 const pdfjsLib = require("pdfjs-dist");
 
 pdfjsLib.GlobalWorkerOptions.workerSrc = `/assets/pdf.worker.js`;
+type PdfDocument = makeInterface<[typeof documentSchema]>;
+const PdfDocument = makeInterface(documentSchema);
 
 interface IViewerProps {
     pdf: Pdfjs.PDFDocumentProxy;
     url: string;
-    Document: Doc;
-    DataDoc?: Doc;
-    fieldExtensionDoc: Doc;
     fieldKey: string;
     fieldExt: string;
+    Document: Doc;
+    DataDoc?: Doc;
+    ContainingCollectionView: Opt<CollectionView>;
+    extensionDoc: Doc;
     PanelWidth: () => number;
     PanelHeight: () => number;
     ContentScaling: () => number;
@@ -52,7 +57,6 @@ interface IViewerProps {
     addDocument?: (doc: Doc) => boolean;
     setPdfViewer: (view: PDFViewer) => void;
     ScreenToLocalTransform: () => Transform;
-    ContainingCollectionView: Opt<CollectionView>;
     whenActiveChanged: (isActive: boolean) => void;
 }
 
@@ -60,7 +64,7 @@ interface IViewerProps {
  * Handles rendering and virtualization of the pdf
  */
 @observer
-export class PDFViewer extends React.Component<IViewerProps> {
+export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument>(PdfDocument) {
     static _annotationStyle: any = addStyleSheet();
     @observable private _pageSizes: { width: number, height: number }[] = [];
     @observable private _annotations: Doc[] = [];
@@ -79,7 +83,6 @@ export class PDFViewer extends React.Component<IViewerProps> {
 
     private _pdfViewer: any;
     private _retries = 0; // number of times tried to create the PDF viewer 
-    private _isChildActive = false;
     private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean) => void);
     private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
     private _reactionDisposer?: IReactionDisposer;
@@ -97,7 +100,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
     private _coverPath: any;
 
     @computed get allAnnotations() {
-        return DocListCast(this.props.fieldExtensionDoc.annotations).filter(
+        return DocListCast(this.props.extensionDoc.annotations).filter(
             anno => this._script.run({ this: anno }, console.log, true).result);
     }
 
@@ -186,7 +189,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
         await this.initialLoad();
 
         this._annotationReactionDisposer = reaction(
-            () => this.props.fieldExtensionDoc && DocListCast(this.props.fieldExtensionDoc.annotations),
+            () => this.props.extensionDoc && DocListCast(this.props.extensionDoc.annotations),
             annotations => annotations && annotations.length && this.renderAnnotations(annotations, true),
             { fireImmediately: true });
 
@@ -530,7 +533,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
     highlight = (color: string) => {
         // creates annotation documents for current highlights
         let annotationDoc = this.makeAnnotationDocument(color);
-        annotationDoc && Doc.AddDocToList(this.props.fieldExtensionDoc, this.props.fieldExt, annotationDoc);
+        annotationDoc && Doc.AddDocToList(this.props.extensionDoc, this.props.fieldExt, annotationDoc);
         return annotationDoc;
     }
 
@@ -570,40 +573,9 @@ export class PDFViewer extends React.Component<IViewerProps> {
         DragManager.StartDocumentDrag([], new DragManager.DocumentDragData([view]), 0, 0);
     }
 
-    // this is called with the document that was dragged and the collection to move it into.
-    // if the target collection is the same as this collection, then the move will be allowed.
-    // otherwise, the document being moved must be able to be removed from its container before
-    // moving it into the target.  
-    @action.bound
-    moveDocument(doc: Doc, targetCollection: Doc, addDocument: (doc: Doc) => boolean): boolean {
-        if (Doc.AreProtosEqual(this.props.Document, targetCollection)) {
-            return true;
-        }
-        return this.removeDocument(doc) ? addDocument(doc) : false;
-    }
-    @action.bound
-    addDocument(doc: Doc): boolean {
-        Doc.GetProto(doc).annotationOn = this.props.Document;
-        Doc.AddDocToList(this.props.fieldExtensionDoc, this.props.fieldExt, doc);
-        return true;
-    }
-    @action.bound
-    removeDocument(doc: Doc): boolean {
-        Doc.GetProto(doc).annotationOn = undefined;
-        let targetDataDoc = this.props.fieldExtensionDoc;
-        let targetField = this.props.fieldExt;
-        let value = Cast(targetDataDoc[targetField], 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);
-        index !== -1 && value.splice(index, 1);
-        return true;
-    }
     scrollXf = () => {
         return this._mainCont.current ? this.props.ScreenToLocalTransform().translate(0, this._scrollTop) : this.props.ScreenToLocalTransform();
     }
-    setPreviewCursor = (func?: (x: number, y: number, drag: boolean) => void) => {
-        this._setPreviewCursor = func;
-    }
     onClick = (e: React.MouseEvent) => {
         this._setPreviewCursor &&
             e.button === 0 &&
@@ -611,13 +583,9 @@ export class PDFViewer extends React.Component<IViewerProps> {
             Math.abs(e.clientY - this._downY) < 3 &&
             this._setPreviewCursor(e.clientX, e.clientY, false);
     }
-    whenActiveChanged = (isActive: boolean) => {
-        this._isChildActive = isActive;
-        this.props.whenActiveChanged(isActive);
-    }
-    active = () => {
-        return this.props.isSelected() || this._isChildActive || this.props.renderDepth === 0;
-    }
+
+    setPreviewCursor = (func?: (x: number, y: number, drag: boolean) => void) => this._setPreviewCursor = func;
+
 
     getCoverImage = () => {
         if (!this.props.Document[HeightSym]() || !this.props.Document.nativeHeight) {
@@ -632,7 +600,6 @@ export class PDFViewer extends React.Component<IViewerProps> {
             style={{ position: "absolute", display: "inline-block", top: 0, left: 0, width: `${nativeWidth}px`, height: `${nativeHeight}px` }} />;
     }
 
-
     @action
     onZoomWheel = (e: React.WheelEvent) => {
         e.stopPropagation();
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 418863bcc..eb752f8c6 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -332,8 +332,10 @@ export namespace Doc {
         return Array.from(results);
     }
 
-    export function IndexOf(toFind: Doc, list: Doc[]) {
-        return list.findIndex(doc => doc === toFind || Doc.AreProtosEqual(doc, toFind));
+    export function IndexOf(toFind: Doc, list: Doc[], allowProtos: boolean = true) {
+        let index = list.reduce((p, v, i) => (v instanceof Doc && v === toFind) ? i : p, -1);
+        index = allowProtos && index !== -1 ? index : list.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, toFind)) ? i : p, -1);
+        return index; // list.findIndex(doc => doc === toFind || Doc.AreProtosEqual(doc, toFind));
     }
     export function RemoveDocFromList(listDoc: Doc, key: string, doc: Doc) {
         if (listDoc[key] === undefined) {
-- 
cgit v1.2.3-70-g09d2


From e6be0feda60963d28838d9fe7c79bf40cde6bc62 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Fri, 11 Oct 2019 10:15:29 -0400
Subject: added highlighting other's text.  fixed some exceptions with text
 highlighitng.

---
 src/client/util/RichTextSchema.tsx                 |  6 ++++--
 src/client/views/nodes/FormattedTextBox.tsx        | 10 ++++++----
 src/client/views/nodes/FormattedTextBoxComment.tsx |  2 +-
 3 files changed, 11 insertions(+), 7 deletions(-)

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

diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 063686d58..cc3548e1a 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -18,6 +18,7 @@ import { Transform } from "./Transform";
 import React = require("react");
 import { BoolCast, NumCast } from "../../new_fields/Types";
 import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
+import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils";
 
 const pDOM: DOMOutputSpecArray = ["p", 0], blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"],
     preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0];
@@ -451,9 +452,10 @@ export const marks: { [index: string]: MarkSpec } = {
             let min = Math.round(node.attrs.modified / 12);
             let hr = Math.round(min / 60);
             let day = Math.round(hr / 60 / 24);
+            let remote = node.attrs.userid !== Doc.CurrentUserEmail ? " userMark-remote" : "";
             return node.attrs.opened ?
-                ['span', { class: "userMark-" + uid + " userMark-min-" + min + " userMark-hr-" + hr + " userMark-day-" + day }, 0] :
-                ['span', { class: "userMark-" + uid + " userMark-min-" + min + " userMark-hr-" + hr + " userMark-day-" + day }, ['span', 0]];
+                ['span', { class: "userMark-" + uid + remote + " userMark-min-" + min + " userMark-hr-" + hr + " userMark-day-" + day }, 0] :
+                ['span', { class: "userMark-" + uid + remote + " userMark-min-" + min + " userMark-hr-" + hr + " userMark-day-" + day }, ['span', 0]];
         }
     },
     // the id of the user who entered the text
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index cbe4945af..b05d0046c 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -238,9 +238,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     }
     setAnnotation = (start: number, end: number, mark: Mark, opened: boolean, keep: boolean = false) => {
         let view = this._editorView!;
-        let mid = view.state.doc.resolve(Math.round((start + end) / 2));
         let nmark = view.state.schema.marks.user_mark.create({ ...mark.attrs, userid: keep ? Doc.CurrentUserEmail : mark.attrs.userid, opened: opened });
-        view.dispatch(view.state.tr.removeMark(start, end, nmark).addMark(start, end, nmark).setSelection(new TextSelection(mid)));
+        view.dispatch(view.state.tr.removeMark(start, end, nmark).addMark(start, end, nmark));
     }
     protected createDropTarget = (ele: HTMLDivElement) => {
         this._proseRef = ele;
@@ -333,8 +332,11 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
 
     updateHighlights = () => {
         clearStyleSheetRules(FormattedTextBox._userStyleSheet);
+        if (FormattedTextBox._highlights.indexOf("Text from Others") !== -1) {
+            addStyleSheetRule(FormattedTextBox._userStyleSheet, "userMark-remote", { background: "yellow" });
+        }
         if (FormattedTextBox._highlights.indexOf("My Text") !== -1) {
-            addStyleSheetRule(FormattedTextBox._userStyleSheet, "userMark-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { background: "yellow" });
+            addStyleSheetRule(FormattedTextBox._userStyleSheet, "userMark-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { background: "moccasin" });
         }
         if (FormattedTextBox._highlights.indexOf("Todo Items") !== -1) {
             addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "todo", { outline: "black solid 1px" });
@@ -364,7 +366,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     specificContextMenu = (e: React.MouseEvent): void => {
         let funcs: ContextMenuProps[] = [];
         funcs.push({ description: "Dictate", event: () => { e.stopPropagation(); this.recordBullet(); }, icon: "expand-arrows-alt" });
-        ["My Text", "Todo Items", "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"].forEach(option =>
+        ["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: () => {
                     e.stopPropagation();
diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx
index a75bfd373..bde278be3 100644
--- a/src/client/views/nodes/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/FormattedTextBoxComment.tsx
@@ -93,7 +93,7 @@ export class FormattedTextBoxComment {
         if (lastState && lastState.doc.eq(state.doc) &&
             lastState.selection.eq(state.selection)) return;
 
-        if (!FormattedTextBoxComment.textBox || !FormattedTextBoxComment.textBox.props.isSelected()) return;
+        if (!FormattedTextBoxComment.textBox || !FormattedTextBoxComment.textBox.props || !FormattedTextBoxComment.textBox.props.isSelected()) return;
         let set = "none";
         if (FormattedTextBoxComment.textBox && state.selection.$from) {
             let nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark);
-- 
cgit v1.2.3-70-g09d2

