From c619bad0281ea6f248c48b8d418f324f67f530dd Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Wed, 3 Jul 2019 12:00:59 -0400
Subject: added to fitToBox to treeviews.  fixed some fitToBox issues.  moved
 notifications button to library tree view.

---
 src/client/views/nodes/LinkEditor.tsx | 1 -
 1 file changed, 1 deletion(-)

(limited to 'src/client/views/nodes/LinkEditor.tsx')

diff --git a/src/client/views/nodes/LinkEditor.tsx b/src/client/views/nodes/LinkEditor.tsx
index 22da732cf..e6cc50620 100644
--- a/src/client/views/nodes/LinkEditor.tsx
+++ b/src/client/views/nodes/LinkEditor.tsx
@@ -271,7 +271,6 @@ export class LinkGroupEditor extends React.Component<LinkGroupEditorProps> {
                 </>
             );
         }
-        trace();
         return (
             <div className="linkEditor-group">
                 <div className="linkEditor-group-row">
-- 
cgit v1.2.3-70-g09d2


From 5642ee106ac37982a84192cc2e7a694da3b4fc3f Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Mon, 8 Jul 2019 13:40:09 -0400
Subject: Hopefully got rid of new Docs being created too frequently

---
 src/client/util/LinkManager.ts          | 38 ++++++++++++++++++---------------
 src/client/views/nodes/LinkEditor.tsx   |  5 +++--
 src/client/views/nodes/LinkMenuItem.tsx | 12 ++++++-----
 src/client/views/search/SearchItem.tsx  | 16 +++++++-------
 src/new_fields/Types.ts                 |  3 ++-
 5 files changed, 41 insertions(+), 33 deletions(-)

(limited to 'src/client/views/nodes/LinkEditor.tsx')

diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index f2f3e51dd..944bc532f 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -68,8 +68,8 @@ export class LinkManager {
     // finds all links that contain the given anchor
     public getAllRelatedLinks(anchor: Doc): Doc[] {//List<Doc> {
         let related = LinkManager.Instance.getAllLinks().filter(link => {
-            let protomatch1 = Doc.AreProtosEqual(anchor, Cast(link.anchor1, Doc, new Doc));
-            let protomatch2 = Doc.AreProtosEqual(anchor, Cast(link.anchor2, Doc, new Doc));
+            let protomatch1 = Doc.AreProtosEqual(anchor, Cast(link.anchor1, Doc, null));
+            let protomatch2 = Doc.AreProtosEqual(anchor, Cast(link.anchor2, Doc, null));
             return protomatch1 || protomatch2;
         });
         return related;
@@ -100,9 +100,11 @@ export class LinkManager {
                 if (index > -1) groupTypes.splice(index, 1);
                 LinkManager.Instance.LinkManagerDoc.allGroupTypes = new List<string>(groupTypes);
                 LinkManager.Instance.LinkManagerDoc[groupType] = undefined;
-                LinkManager.Instance.getAllLinks().forEach(linkDoc => {
-                    LinkManager.Instance.removeGroupFromAnchor(linkDoc, Cast(linkDoc.anchor1, Doc, new Doc), groupType);
-                    LinkManager.Instance.removeGroupFromAnchor(linkDoc, Cast(linkDoc.anchor2, Doc, new Doc), groupType);
+                LinkManager.Instance.getAllLinks().forEach(async linkDoc => {
+                    const anchor1 = await Cast(linkDoc.anchor1, Doc);
+                    const anchor2 = await Cast(linkDoc.anchor2, Doc);
+                    anchor1 && LinkManager.Instance.removeGroupFromAnchor(linkDoc, anchor1, groupType);
+                    anchor2 && LinkManager.Instance.removeGroupFromAnchor(linkDoc, anchor2, groupType);
                 });
             }
             return true;
@@ -122,8 +124,8 @@ export class LinkManager {
     }
 
     // gets the groups associates with an anchor in a link
-    public getAnchorGroups(linkDoc: Doc, anchor: Doc): Array<Doc> {
-        if (Doc.AreProtosEqual(anchor, Cast(linkDoc.anchor1, Doc, new Doc))) {
+    public getAnchorGroups(linkDoc: Doc, anchor?: Doc): Array<Doc> {
+        if (Doc.AreProtosEqual(anchor, Cast(linkDoc.anchor1, Doc, null))) {
             return DocListCast(linkDoc.anchor1Groups);
         } else {
             return DocListCast(linkDoc.anchor2Groups);
@@ -132,7 +134,7 @@ export class LinkManager {
 
     // sets the groups of the given anchor in the given link
     public setAnchorGroups(linkDoc: Doc, anchor: Doc, groups: Doc[]) {
-        if (Doc.AreProtosEqual(anchor, Cast(linkDoc.anchor1, Doc, new Doc))) {
+        if (Doc.AreProtosEqual(anchor, Cast(linkDoc.anchor1, Doc, null))) {
             linkDoc.anchor1Groups = new List<Doc>(groups);
         } else {
             linkDoc.anchor2Groups = new List<Doc>(groups);
@@ -209,10 +211,10 @@ export class LinkManager {
         let md: Doc[] = [];
         let allLinks = LinkManager.Instance.getAllLinks();
         allLinks.forEach(linkDoc => {
-            let anchor1Groups = LinkManager.Instance.getAnchorGroups(linkDoc, Cast(linkDoc.anchor1, Doc, new Doc));
-            let anchor2Groups = LinkManager.Instance.getAnchorGroups(linkDoc, Cast(linkDoc.anchor2, Doc, new Doc));
-            anchor1Groups.forEach(groupDoc => { if (StrCast(groupDoc.type).toUpperCase() === groupType.toUpperCase()) md.push(Cast(groupDoc.metadata, Doc, new Doc)); });
-            anchor2Groups.forEach(groupDoc => { if (StrCast(groupDoc.type).toUpperCase() === groupType.toUpperCase()) md.push(Cast(groupDoc.metadata, Doc, new Doc)); });
+            let anchor1Groups = LinkManager.Instance.getAnchorGroups(linkDoc, Cast(linkDoc.anchor1, Doc, null));
+            let anchor2Groups = LinkManager.Instance.getAnchorGroups(linkDoc, Cast(linkDoc.anchor2, Doc, null));
+            anchor1Groups.forEach(groupDoc => { if (StrCast(groupDoc.type).toUpperCase() === groupType.toUpperCase()) { const meta = Cast(groupDoc.metadata, Doc, null); meta && md.push(meta); } });
+            anchor2Groups.forEach(groupDoc => { if (StrCast(groupDoc.type).toUpperCase() === groupType.toUpperCase()) { const meta = Cast(groupDoc.metadata, Doc, null); meta && md.push(meta); } });
         });
         return md;
     }
@@ -221,18 +223,20 @@ export class LinkManager {
     public doesLinkExist(anchor1: Doc, anchor2: Doc): boolean {
         let allLinks = LinkManager.Instance.getAllLinks();
         let index = allLinks.findIndex(linkDoc => {
-            return (Doc.AreProtosEqual(Cast(linkDoc.anchor1, Doc, new Doc), anchor1) && Doc.AreProtosEqual(Cast(linkDoc.anchor2, Doc, new Doc), anchor2)) ||
-                (Doc.AreProtosEqual(Cast(linkDoc.anchor1, Doc, new Doc), anchor2) && Doc.AreProtosEqual(Cast(linkDoc.anchor2, Doc, new Doc), anchor1));
+            return (Doc.AreProtosEqual(Cast(linkDoc.anchor1, Doc, null), anchor1) && Doc.AreProtosEqual(Cast(linkDoc.anchor2, Doc, null), anchor2)) ||
+                (Doc.AreProtosEqual(Cast(linkDoc.anchor1, Doc, null), anchor2) && Doc.AreProtosEqual(Cast(linkDoc.anchor2, Doc, null), anchor1));
         });
         return index !== -1;
     }
 
     // finds the opposite anchor of a given anchor in a link
+    //TODO This should probably return undefined if there isn't an opposite anchor
+    //TODO This should also await the return value of the anchor so we don't filter out promises
     public getOppositeAnchor(linkDoc: Doc, anchor: Doc): Doc {
-        if (Doc.AreProtosEqual(anchor, Cast(linkDoc.anchor1, Doc, new Doc))) {
-            return Cast(linkDoc.anchor2, Doc, new Doc);
+        if (Doc.AreProtosEqual(anchor, Cast(linkDoc.anchor1, Doc, null))) {
+            return Cast(linkDoc.anchor2, Doc, null)!;
         } else {
-            return Cast(linkDoc.anchor1, Doc, new Doc);
+            return Cast(linkDoc.anchor1, Doc, null)!;
         }
     }
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/LinkEditor.tsx b/src/client/views/nodes/LinkEditor.tsx
index e6cc50620..a97ec8831 100644
--- a/src/client/views/nodes/LinkEditor.tsx
+++ b/src/client/views/nodes/LinkEditor.tsx
@@ -177,9 +177,10 @@ export class LinkGroupEditor extends React.Component<LinkGroupEditorProps> {
         LinkManager.Instance.deleteGroupType(groupType);
     }
 
-    copyGroup = (groupType: string): void => {
+    copyGroup = async (groupType: string): Promise<void> => {
         let sourceGroupDoc = this.props.groupDoc;
-        let sourceMdDoc = Cast(sourceGroupDoc.metadata, Doc, new Doc);
+        const sourceMdDoc = await Cast(sourceGroupDoc.metadata, Doc);
+        if (!sourceMdDoc) return;
 
         let destDoc = LinkManager.Instance.getOppositeAnchor(this.props.linkDoc, this.props.sourceDoc);
         // let destGroupList = LinkManager.Instance.getAnchorGroups(this.props.linkDoc, destDoc);
diff --git a/src/client/views/nodes/LinkMenuItem.tsx b/src/client/views/nodes/LinkMenuItem.tsx
index 4dee6741f..9728671c0 100644
--- a/src/client/views/nodes/LinkMenuItem.tsx
+++ b/src/client/views/nodes/LinkMenuItem.tsx
@@ -56,11 +56,13 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
 
         let mdRows: Array<JSX.Element> = [];
         if (groupDoc) {
-            let mdDoc = Cast(groupDoc.metadata, Doc, new Doc);
-            let keys = LinkManager.Instance.getMetadataKeysInGroup(this.props.groupType);//groupMetadataKeys.get(this.props.groupType);
-            mdRows = keys.map(key => {
-                return (<div key={key} className="link-metadata-row"><b>{key}</b>: {StrCast(mdDoc[key])}</div>);
-            });
+            let mdDoc = Cast(groupDoc.metadata, Doc, null);
+            if (mdDoc) {
+                let keys = LinkManager.Instance.getMetadataKeysInGroup(this.props.groupType);//groupMetadataKeys.get(this.props.groupType);
+                mdRows = keys.map(key => {
+                    return (<div key={key} className="link-metadata-row"><b>{key}</b>: {StrCast(mdDoc[key])}</div>);
+                });
+            }
         }
 
         return (<div className="link-metadata">{mdRows}</div>);
diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx
index 6cedc7cfb..99701471d 100644
--- a/src/client/views/search/SearchItem.tsx
+++ b/src/client/views/search/SearchItem.tsx
@@ -187,10 +187,10 @@ export class SearchItem extends React.Component<SearchItemProps> {
         if (this.props.doc.type === DocTypes.LINK) {
             if (this.props.doc.anchor1 && this.props.doc.anchor2) {
 
-                let doc1 = Cast(this.props.doc.anchor1, Doc, new Doc());
-                let doc2 = Cast(this.props.doc.anchor2, Doc, new Doc());
-                doc1.libraryBrush = true;
-                doc2.libraryBrush = true;
+                let doc1 = Cast(this.props.doc.anchor1, Doc, null);
+                let doc2 = Cast(this.props.doc.anchor2, Doc, null);
+                doc1 && (doc1.libraryBrush = true);
+                doc2 && (doc2.libraryBrush = true);
             }
         } else {
             let docViews: DocumentView[] = DocumentManager.Instance.getAllDocumentViews(this.props.doc);
@@ -204,10 +204,10 @@ export class SearchItem extends React.Component<SearchItemProps> {
         if (this.props.doc.type === DocTypes.LINK) {
             if (this.props.doc.anchor1 && this.props.doc.anchor2) {
 
-                let doc1 = Cast(this.props.doc.anchor1, Doc, new Doc());
-                let doc2 = Cast(this.props.doc.anchor2, Doc, new Doc());
-                doc1.libraryBrush = false;
-                doc2.libraryBrush = false;
+                let doc1 = Cast(this.props.doc.anchor1, Doc, null);
+                let doc2 = Cast(this.props.doc.anchor2, Doc, null);
+                doc1 && (doc1.libraryBrush = false);
+                doc2 && (doc2.libraryBrush = false);
             }
         } else {
             let docViews: DocumentView[] = DocumentManager.Instance.getAllDocumentViews(this.props.doc);
diff --git a/src/new_fields/Types.ts b/src/new_fields/Types.ts
index 8dd893aa4..39d384d64 100644
--- a/src/new_fields/Types.ts
+++ b/src/new_fields/Types.ts
@@ -45,9 +45,10 @@ export interface Interface {
     [key: string]: InterfaceValue;
     // [key: string]: ToConstructor<Field> | ListSpec<Field[]>;
 }
+export type WithoutRefField<T extends Field> = T extends RefField ? never : T;
 
 export function Cast<T extends ToConstructor<Field> | ListSpec<Field>>(field: FieldResult, ctor: T): FieldResult<ToType<T>>;
-export function Cast<T extends ToConstructor<Field> | ListSpec<Field>>(field: FieldResult, ctor: T, defaultVal: WithoutList<ToType<T>> | null): WithoutList<ToType<T>>;
+export function Cast<T extends ToConstructor<Field> | ListSpec<Field>>(field: FieldResult, ctor: T, defaultVal: WithoutList<WithoutRefField<ToType<T>>> | null): WithoutList<ToType<T>>;
 export function Cast<T extends ToConstructor<Field> | ListSpec<Field>>(field: FieldResult, ctor: T, defaultVal?: ToType<T> | null): FieldResult<ToType<T>> | undefined {
     if (field instanceof Promise) {
         return defaultVal === undefined ? field.then(f => Cast(f, ctor) as any) as any : defaultVal === null ? undefined : defaultVal;
-- 
cgit v1.2.3-70-g09d2


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

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

(limited to 'src/client/views/nodes/LinkEditor.tsx')

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


From f9cda26e1139f51c6e995a12279fbbd57a55318f Mon Sep 17 00:00:00 2001
From: Fawn <fangrui_tong@brown.edu>
Date: Sun, 14 Jul 2019 23:09:52 -0400
Subject: linking menu updates on input and enter

---
 src/client/views/nodes/LinkEditor.scss | 29 ++++++++++-
 src/client/views/nodes/LinkEditor.tsx  | 95 +++++++++++++++++++++++++++-------
 2 files changed, 104 insertions(+), 20 deletions(-)

(limited to 'src/client/views/nodes/LinkEditor.tsx')

diff --git a/src/client/views/nodes/LinkEditor.scss b/src/client/views/nodes/LinkEditor.scss
index 3c49c2212..e1e6b70b4 100644
--- a/src/client/views/nodes/LinkEditor.scss
+++ b/src/client/views/nodes/LinkEditor.scss
@@ -47,7 +47,7 @@
     border-radius: 3px;
 
     .linkEditor-group-row {
-        // display: flex;
+        display: flex;
         margin-bottom: 3px;
 
         .linkEditor-group-row-label {
@@ -108,6 +108,32 @@
         &:hover {
             background-color: lightgray;
         }
+
+        &.onDown {
+            background-color: gray;
+        }
+    }
+}
+
+.linkEditor-typeButton {
+    // background-color: $dark-color;
+    // color: white;
+    // background-color: $light-color;
+    // background-color: white;
+    background-color: transparent;
+    color: $dark-color;
+    width: 100%;
+    height: 20px;
+    padding: 0 3px;
+    padding-bottom: 2px;
+    text-align: left;
+    text-transform: none;
+    letter-spacing: normal;
+    font-size: 12px;
+    font-weight: bold;
+
+    &:hover {
+        background-color: $light-color;
     }
 }
 
@@ -115,6 +141,7 @@
     height: 20px;
     display: flex;
     justify-content: flex-end;
+    margin-top: 5px;
 
     .linkEditor-button {
         margin-left: 6px;
diff --git a/src/client/views/nodes/LinkEditor.tsx b/src/client/views/nodes/LinkEditor.tsx
index 7200e5aa0..02373926e 100644
--- a/src/client/views/nodes/LinkEditor.tsx
+++ b/src/client/views/nodes/LinkEditor.tsx
@@ -22,11 +22,14 @@ interface GroupTypesDropdownProps {
 // this dropdown could be generalized
 @observer
 class GroupTypesDropdown extends React.Component<GroupTypesDropdownProps> {
-    @observable private _searchTerm: string = "";
+    @observable private _searchTerm: string = this.props.groupType;
     @observable private _groupType: string = this.props.groupType;
+    @observable private _isEditing: boolean = false;
+    // @observable private _ref: React.RefObject<HTMLInputElement> = React.createRef();
 
-    @action setSearchTerm = (value: string): void => { this._searchTerm = value; };
-    @action setGroupType = (value: string): void => { this._groupType = value; };
+    // @action setSearchTerm = (value: string): void => { this._searchTerm = value; };
+    // @action setGroupType = (value: string): void => { this._groupType = value; };
+    // @action setIsEditing = (isEditing: boolean): void => { this._isEditing = isEditing; console.log(this._isEditing); };
 
     @action
     createGroup = (groupType: string): void => {
@@ -34,9 +37,56 @@ class GroupTypesDropdown extends React.Component<GroupTypesDropdownProps> {
         LinkManager.Instance.addGroupType(groupType);
     }
 
+    @action
     onChange = (val: string): void => {
-        this.setSearchTerm(val);
-        this.setGroupType(val);
+        this._searchTerm = val;
+        this._groupType = val;
+        this._isEditing = true;
+    }
+
+    @action
+    onKeyDown = (e: React.KeyboardEvent): void => {
+        if (e.key === "Enter") {
+            let allGroupTypes = Array.from(LinkManager.Instance.getAllGroupTypes());
+            let groupOptions = allGroupTypes.filter(groupType => groupType.toUpperCase().indexOf(this._searchTerm.toUpperCase()) > -1);
+            let exactFound = groupOptions.findIndex(groupType => groupType.toUpperCase() === this._searchTerm.toUpperCase());
+
+            if (exactFound > -1) {
+                let groupType = groupOptions[exactFound];
+                this.props.setGroupType(groupType);
+                this._groupType = groupType;
+            } else {
+                this.createGroup(this._searchTerm);
+                this._groupType = this._searchTerm;
+            }
+
+            this._searchTerm = this._groupType;
+            this._isEditing = false;
+            console.log("on key down", this._isEditing);
+        }
+    }
+
+    @action
+    onOptionClick = (value: string, createNew: boolean): void => {
+        if (createNew) {
+            this.createGroup(this._searchTerm);
+            this._groupType = this._searchTerm;
+
+        } else {
+            this.props.setGroupType(value);
+            this._groupType = value;
+
+        }
+        this._searchTerm = this._groupType;
+        this._isEditing = false;
+        console.log("option clicked", this._isEditing);
+    }
+
+    @action
+    onButtonPointerDown = (): void => {
+        this._isEditing = true;
+        console.log("button down", this._isEditing);
+        //make focus on input
     }
 
     renderOptions = (): JSX.Element[] | JSX.Element => {
@@ -47,29 +97,36 @@ class GroupTypesDropdown extends React.Component<GroupTypesDropdownProps> {
         let exactFound = groupOptions.findIndex(groupType => groupType.toUpperCase() === this._searchTerm.toUpperCase()) > -1;
 
         let options = groupOptions.map(groupType => {
-            return <div key={groupType} className="linkEditor-option"
-                onClick={() => { this.props.setGroupType(groupType); this.setGroupType(groupType); this.setSearchTerm(""); }}>{groupType}</div>;
+            let ref = React.createRef<HTMLDivElement>();
+            return <div key={groupType} ref={ref} className="linkEditor-option"
+                onClick={() => this.onOptionClick(groupType, false)}>{groupType}</div>;
         });
 
         // if search term does not already exist as a group type, give option to create new group type
         if (!exactFound && this._searchTerm !== "") {
-            options.push(<div key={""} className="linkEditor-option"
-                onClick={() => { this.createGroup(this._searchTerm); this.setGroupType(this._searchTerm); this.setSearchTerm(""); }}>Define new "{this._searchTerm}" relationship</div>);
+            let ref = React.createRef<HTMLDivElement>();
+            options.push(<div key={""} ref={ref} className="linkEditor-option"
+                onClick={() => this.onOptionClick(this._searchTerm, true)}>Define new "{this._searchTerm}" relationship</div>);
         }
 
         return options;
     }
 
     render() {
-        return (
-            <div className="linkEditor-dropdown">
-                <input type="text" value={this._groupType} placeholder="Search for or create a new group"
-                    onChange={e => this.onChange(e.target.value)}></input>
-                <div className="linkEditor-options-wrapper">
-                    {this.renderOptions()}
-                </div>
-            </div >
-        );
+        console.log("render", this._isEditing);
+        if (this._isEditing || this._groupType === "") {
+            return (
+                <div className="linkEditor-dropdown">
+                    <input type="text" value={this._groupType} placeholder="Search for or create a new group"
+                        onChange={e => this.onChange(e.target.value)} onKeyDown={this.onKeyDown} autoFocus></input>
+                    <div className="linkEditor-options-wrapper">
+                        {this.renderOptions()}
+                    </div>
+                </div >
+            );
+        } else {
+            return <button className="linkEditor-typeButton" onClick={() => this.onButtonPointerDown()}>{this._groupType}</button>;
+        }
     }
 }
 
@@ -276,7 +333,7 @@ export class LinkGroupEditor extends React.Component<LinkGroupEditorProps> {
         }
         return (
             <div className="linkEditor-group">
-                <div className="linkEditor-group-row">
+                <div className="linkEditor-group-row ">
                     <p className="linkEditor-group-row-label">type:</p>
                     <GroupTypesDropdown groupType={groupType} setGroupType={this.setGroupType} />
                 </div>
-- 
cgit v1.2.3-70-g09d2


From 4905b2d7ecc6a946ab5173ca624b99d8f0155e9f Mon Sep 17 00:00:00 2001
From: Fawn <fangrui_tong@brown.edu>
Date: Sun, 14 Jul 2019 23:37:47 -0400
Subject: code clean on linking

---
 src/client/views/nodes/LinkEditor.scss | 15 ---------------
 src/client/views/nodes/LinkEditor.tsx  | 10 ----------
 2 files changed, 25 deletions(-)

(limited to 'src/client/views/nodes/LinkEditor.tsx')

diff --git a/src/client/views/nodes/LinkEditor.scss b/src/client/views/nodes/LinkEditor.scss
index e1e6b70b4..fc5f2410c 100644
--- a/src/client/views/nodes/LinkEditor.scss
+++ b/src/client/views/nodes/LinkEditor.scss
@@ -116,10 +116,6 @@
 }
 
 .linkEditor-typeButton {
-    // background-color: $dark-color;
-    // color: white;
-    // background-color: $light-color;
-    // background-color: white;
     background-color: transparent;
     color: $dark-color;
     width: 100%;
@@ -146,15 +142,4 @@
     .linkEditor-button {
         margin-left: 6px;
     }
-
-    // .linkEditor-groupOpts {
-    //     width: calc(20% - 3px);
-    //     height: 20px;
-    //     padding: 0;
-    //     font-size: 10px;
-
-    //     &:disabled {
-    //         background-color: gray;
-    //     }
-    // }
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/LinkEditor.tsx b/src/client/views/nodes/LinkEditor.tsx
index 02373926e..afde85b69 100644
--- a/src/client/views/nodes/LinkEditor.tsx
+++ b/src/client/views/nodes/LinkEditor.tsx
@@ -25,11 +25,6 @@ class GroupTypesDropdown extends React.Component<GroupTypesDropdownProps> {
     @observable private _searchTerm: string = this.props.groupType;
     @observable private _groupType: string = this.props.groupType;
     @observable private _isEditing: boolean = false;
-    // @observable private _ref: React.RefObject<HTMLInputElement> = React.createRef();
-
-    // @action setSearchTerm = (value: string): void => { this._searchTerm = value; };
-    // @action setGroupType = (value: string): void => { this._groupType = value; };
-    // @action setIsEditing = (isEditing: boolean): void => { this._isEditing = isEditing; console.log(this._isEditing); };
 
     @action
     createGroup = (groupType: string): void => {
@@ -62,7 +57,6 @@ class GroupTypesDropdown extends React.Component<GroupTypesDropdownProps> {
 
             this._searchTerm = this._groupType;
             this._isEditing = false;
-            console.log("on key down", this._isEditing);
         }
     }
 
@@ -79,14 +73,11 @@ class GroupTypesDropdown extends React.Component<GroupTypesDropdownProps> {
         }
         this._searchTerm = this._groupType;
         this._isEditing = false;
-        console.log("option clicked", this._isEditing);
     }
 
     @action
     onButtonPointerDown = (): void => {
         this._isEditing = true;
-        console.log("button down", this._isEditing);
-        //make focus on input
     }
 
     renderOptions = (): JSX.Element[] | JSX.Element => {
@@ -113,7 +104,6 @@ class GroupTypesDropdown extends React.Component<GroupTypesDropdownProps> {
     }
 
     render() {
-        console.log("render", this._isEditing);
         if (this._isEditing || this._groupType === "") {
             return (
                 <div className="linkEditor-dropdown">
-- 
cgit v1.2.3-70-g09d2

