From f52f5a408c62b93ae79eb690ff86538a6fbf60ed Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Sun, 12 May 2019 22:56:13 -0400
Subject: Started adding flyout to go up a level

---
 .../views/collections/ParentDocumentSelector.tsx   | 39 ++++++++++++++++++++++
 1 file changed, 39 insertions(+)
 create mode 100644 src/client/views/collections/ParentDocumentSelector.tsx

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

diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx
new file mode 100644
index 000000000..1fdb9d4d9
--- /dev/null
+++ b/src/client/views/collections/ParentDocumentSelector.tsx
@@ -0,0 +1,39 @@
+import * as React from "react";
+import './ParentDocumentSelector.scss';
+import { Doc } from "../../../new_fields/Doc";
+import { observer } from "mobx-react";
+import { observable, action } from "mobx";
+
+@observer
+export class ParentDocSelector extends React.Component<{ Document: Doc }> {
+    @observable hover = false;
+
+    @action
+    onMouseLeave = () => {
+        this.hover = false;
+    }
+
+    @action
+    onMouseEnter = () => {
+        this.hover = true;
+    }
+
+    render() {
+        let flyout;
+        if (this.hover) {
+            flyout = (
+                <div className="PDS-flyout">
+                    <p>Hello world</p>
+                </div>
+            );
+        }
+        return (
+            <span style={{ position: "relative", display: "inline-block" }}
+                onMouseEnter={this.onMouseEnter}
+                onMouseLeave={this.onMouseLeave}>
+                <p>^</p>
+                {flyout}
+            </span>
+        );
+    }
+}
-- 
cgit v1.2.3-70-g09d2


From 2767d6b439dd9e90543f955c68e521e03c690af0 Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Mon, 13 May 2019 02:09:08 -0400
Subject: Added "find aliases" and "see collections that contain me"

---
 src/client/util/SearchUtil.ts                      | 25 +++++++++++++++++
 .../views/collections/ParentDocumentSelector.tsx   | 31 ++++++++++++++++++++--
 src/client/views/nodes/DocumentView.tsx            | 27 ++++++++++++-------
 src/new_fields/Doc.ts                              |  4 +--
 4 files changed, 73 insertions(+), 14 deletions(-)
 create mode 100644 src/client/util/SearchUtil.ts

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

diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts
new file mode 100644
index 000000000..4ccff0d1b
--- /dev/null
+++ b/src/client/util/SearchUtil.ts
@@ -0,0 +1,25 @@
+import * as rp from 'request-promise';
+import { DocServer } from '../DocServer';
+import { Doc } from '../../new_fields/Doc';
+import { Id } from '../../new_fields/RefField';
+
+export namespace SearchUtil {
+    export function Search(query: string, returnDocs: true): Promise<Doc[]>;
+    export function Search(query: string, returnDocs: false): Promise<string[]>;
+    export async function Search(query: string, returnDocs: boolean) {
+        const ids = JSON.parse(await rp.get(DocServer.prepend("/search"), {
+            qs: { query }
+        }));
+        if (!returnDocs) {
+            return ids;
+        }
+        const docMap = await DocServer.GetRefFields(ids);
+        return ids.map((id: string) => docMap[id]).filter((doc: any) => doc instanceof Doc);
+    }
+
+    export async function GetAliasesOfDocument(doc: Doc): Promise<Doc[]> {
+        const proto = await Doc.GetT(doc, "proto", Doc, true);
+        const protoId = (proto || doc)[Id];
+        return Search(`{!join from=id to=proto_i}id:${protoId}`, true);
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx
index 1fdb9d4d9..dd1516da7 100644
--- a/src/client/views/collections/ParentDocumentSelector.tsx
+++ b/src/client/views/collections/ParentDocumentSelector.tsx
@@ -2,7 +2,34 @@ import * as React from "react";
 import './ParentDocumentSelector.scss';
 import { Doc } from "../../../new_fields/Doc";
 import { observer } from "mobx-react";
-import { observable, action } from "mobx";
+import { observable, action, runInAction } from "mobx";
+import { Id } from "../../../new_fields/RefField";
+import { SearchUtil } from "../../util/SearchUtil";
+import { CollectionDockingView } from "./CollectionDockingView";
+
+@observer
+export class SelectorContextMenu extends React.Component<{ Document: Doc }> {
+    @observable private _docs: Doc[] = [];
+
+    constructor(props: { Document: Doc }) {
+        super(props);
+
+        this.fetchDocuments();
+    }
+
+    async fetchDocuments() {
+        const docs = await SearchUtil.Search(`data_l:${this.props.Document[Id]}`, true);
+        runInAction(() => this._docs = docs);
+    }
+
+    render() {
+        return (
+            <>
+                {this._docs.map(doc => <p><a onClick={() => CollectionDockingView.Instance.AddRightSplit(doc)}>{doc.title}</a></p>)}
+            </>
+        );
+    }
+}
 
 @observer
 export class ParentDocSelector extends React.Component<{ Document: Doc }> {
@@ -23,7 +50,7 @@ export class ParentDocSelector extends React.Component<{ Document: Doc }> {
         if (this.hover) {
             flyout = (
                 <div className="PDS-flyout">
-                    <p>Hello world</p>
+                    <SelectorContextMenu Document={this.props.Document} />
                 </div>
             );
         }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 90f67db7c..edc2158f0 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -26,6 +26,7 @@ import { CurrentUserUtils } from "../../../server/authentication/models/current_
 import { DocServer } from "../../DocServer";
 import { Id } from "../../../new_fields/RefField";
 import { PresentationView } from "../PresentationView";
+import { SearchUtil } from "../../util/SearchUtil";
 
 const linkSchema = createSchema({
     title: "string",
@@ -287,16 +288,22 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         }
         e.preventDefault();
 
-        ContextMenu.Instance.addItem({ description: "Full Screen", event: this.fullScreenClicked });
-        ContextMenu.Instance.addItem({ description: this.props.Document.isButton ? "Remove Button" : "Make Button", event: this.makeButton });
-        ContextMenu.Instance.addItem({ description: "Fields", event: this.fieldsClicked });
-        ContextMenu.Instance.addItem({ description: "Center", event: () => this.props.focus(this.props.Document) });
-        ContextMenu.Instance.addItem({ description: "Open Right", event: () => CollectionDockingView.Instance.AddRightSplit(this.props.Document) });
-        ContextMenu.Instance.addItem({ description: "Copy URL", event: () => Utils.CopyText(DocServer.prepend("/doc/" + this.props.Document[Id])) });
-        ContextMenu.Instance.addItem({ description: "Copy ID", event: () => Utils.CopyText(this.props.Document[Id]) });
-        //ContextMenu.Instance.addItem({ description: "Docking", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Docking) })
-        ContextMenu.Instance.addItem({ description: "Pin to Presentation", event: () => PresentationView.Instance.PinDoc(this.props.Document) });
-        ContextMenu.Instance.addItem({ description: "Delete", event: this.deleteClicked });
+        const cm = ContextMenu.Instance;
+        cm.addItem({ description: "Full Screen", event: this.fullScreenClicked });
+        cm.addItem({ description: this.props.Document.isButton ? "Remove Button" : "Make Button", event: this.makeButton });
+        cm.addItem({ description: "Fields", event: this.fieldsClicked });
+        cm.addItem({ description: "Center", event: () => this.props.focus(this.props.Document) });
+        cm.addItem({ description: "Open Right", event: () => CollectionDockingView.Instance.AddRightSplit(this.props.Document) });
+        cm.addItem({
+            description: "Find aliases", event: async () => {
+                const aliases = await SearchUtil.GetAliasesOfDocument(this.props.Document);
+                CollectionDockingView.Instance.AddRightSplit(Docs.SchemaDocument(aliases, {}));
+            }
+        });
+        cm.addItem({ description: "Copy URL", event: () => Utils.CopyText(DocServer.prepend("/doc/" + this.props.Document[Id])) });
+        cm.addItem({ description: "Copy ID", event: () => Utils.CopyText(this.props.Document[Id]) });
+        cm.addItem({ description: "Pin to Presentation", event: () => PresentationView.Instance.PinDoc(this.props.Document) });
+        cm.addItem({ description: "Delete", event: this.deleteClicked });
         if (!this.topMost) {
             // DocumentViews should stop propagation of this event
             e.stopPropagation();
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 46ccb3e90..c08049a39 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -130,8 +130,8 @@ export namespace Doc {
         const self = doc[Self];
         return getField(self, key, ignoreProto);
     }
-    export function GetT<T extends Field>(doc: Doc, key: string, ctor: ToConstructor<T>, ignoreProto: boolean = false): T | null | undefined {
-        return Cast(Get(doc, key, ignoreProto), ctor) as T | null | undefined;
+    export function GetT<T extends Field>(doc: Doc, key: string, ctor: ToConstructor<T>, ignoreProto: boolean = false): FieldResult<T> {
+        return Cast(Get(doc, key, ignoreProto), ctor) as FieldResult<T>;
     }
     export async function SetOnPrototype(doc: Doc, key: string, value: Field) {
         const proto = doc.proto;
-- 
cgit v1.2.3-70-g09d2


From d79d721baa25947dfbbd17eda173835d9ae93fa6 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Wed, 15 May 2019 15:05:57 -0400
Subject: several smaller fixes.

---
 src/client/views/DocumentDecorations.tsx           | 30 +++++++------
 .../views/collections/ParentDocumentSelector.tsx   |  2 +-
 .../collections/collectionFreeForm/MarqueeView.tsx | 17 ++-----
 .../views/nodes/CollectionFreeFormDocumentView.tsx | 52 ++++++++++++----------
 src/client/views/nodes/DocumentView.tsx            |  8 ++--
 src/client/views/nodes/FormattedTextBox.tsx        |  2 +-
 src/client/views/nodes/IconBox.scss                |  4 +-
 src/client/views/nodes/IconBox.tsx                 | 27 ++++++++---
 src/new_fields/List.ts                             | 10 +++++
 9 files changed, 87 insertions(+), 65 deletions(-)

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

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 627bedde2..8d85f7e2c 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -252,18 +252,20 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
             if (!this._removeIcon) {
                 if (selectedDocs.length === 1)
                     this.getIconDoc(selectedDocs[0]).then(icon => selectedDocs[0].props.toggleMinimized());
-                else {
-                    let docViews = SelectionManager.ViewsSortedVertically();
-                    let topDocView = docViews[0];
-                    let ind = topDocView.templates.indexOf(Templates.Bullet.Layout);
-                    if (ind !== -1) {
-                        topDocView.templates.splice(ind, 1);
-                        topDocView.props.Document.subBulletDocs = undefined;
-                    } else {
-                        topDocView.addTemplate(Templates.Bullet);
-                        topDocView.props.Document.subBulletDocs = new List<Doc>(docViews.filter(v => v !== topDocView).map(v => v.props.Document));
+                else
+                    if (Math.abs(e.pageX - this._downX) < Utils.DRAG_THRESHOLD &&
+                        Math.abs(e.pageY - this._downY) < Utils.DRAG_THRESHOLD) {
+                        let docViews = SelectionManager.ViewsSortedVertically();
+                        let topDocView = docViews[0];
+                        let ind = topDocView.templates.indexOf(Templates.Bullet.Layout);
+                        if (ind !== -1) {
+                            topDocView.templates.splice(ind, 1);
+                            topDocView.props.Document.subBulletDocs = undefined;
+                        } else {
+                            topDocView.addTemplate(Templates.Bullet);
+                            topDocView.props.Document.subBulletDocs = new List<Doc>(docViews.filter(v => v !== topDocView).map(v => v.props.Document));
+                        }
                     }
-                }
             }
             this._removeIcon = false;
         }
@@ -275,9 +277,9 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         let doc = selected[0].props.Document;
         let iconDoc = Docs.IconDocument(layoutString);
         iconDoc.isButton = true;
-        iconDoc.title = selected.length > 1 ? "ICONset" : "ICON" + StrCast(doc.title);
-        iconDoc.labelField = this._fieldKey;
-        iconDoc[this._fieldKey] = selected.length > 1 ? "collection" : undefined;
+        iconDoc.proto!.title = selected.length > 1 ? "ICONset" : "ICON" + StrCast(doc.title);
+        iconDoc.labelField = selected.length > 1 ? undefined : this._fieldKey;
+        iconDoc.proto![this._fieldKey] = selected.length > 1 ? "collection" : undefined;
         iconDoc.isMinimized = false;
         iconDoc.width = Number(MINIMIZED_ICON_SIZE);
         iconDoc.height = Number(MINIMIZED_ICON_SIZE);
diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx
index dd1516da7..52f7914f3 100644
--- a/src/client/views/collections/ParentDocumentSelector.tsx
+++ b/src/client/views/collections/ParentDocumentSelector.tsx
@@ -18,7 +18,7 @@ export class SelectorContextMenu extends React.Component<{ Document: Doc }> {
     }
 
     async fetchDocuments() {
-        const docs = await SearchUtil.Search(`data_l:${this.props.Document[Id]}`, true);
+        const docs = await SearchUtil.Search(`data_l:"${this.props.Document[Id]}"`, true);
         runInAction(() => this._docs = docs);
     }
 
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index be7cddca6..ae4852aa2 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -212,6 +212,8 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
         }
         if (e.key === "Backspace" || e.key === "Delete" || e.key === "d") {
             this._commandExecuted = true;
+            e.stopPropagation();
+            (e as any).propagationIsStopped = true;
             this.marqueeSelect().map(d => this.props.removeDocument(d));
             let ink = Cast(this.props.container.props.Document.ink, InkField);
             if (ink) {
@@ -291,20 +293,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                 this.props.selectDocuments([newCollection]);
             }
             this.cleanupInteractions(false);
-        } else
-            if (e.key === "s") {
-                // this._commandExecuted = true;
-                // e.stopPropagation();
-                // e.preventDefault();
-                // let bounds = this.Bounds;
-                // let selected = this.marqueeSelect();
-                // SelectionManager.DeselectAll();
-                // let summary = Docs.TextDocument({ x: bounds.left + bounds.width + 25, y: bounds.top, width: 300, height: 100, backgroundColor: "yellow", title: "-summary-" });
-                // this.props.addLiveTextDocument(summary);
-                // selected.forEach(select => Doc.MakeLink(summary.proto!, select.proto!));
-
-                // this.cleanupInteractions(false);
-            }
+        }
     }
     @action
     marqueeInkSelect(ink: Map<any, any>) {
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 925945b17..38ac5cb90 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -1,19 +1,19 @@
-import { computed, trace, action, reaction, IReactionDisposer } from "mobx";
+import { action, computed, IReactionDisposer, reaction } from "mobx";
 import { observer } from "mobx-react";
+import { Doc, DocListCast, DocListCastAsync } from "../../../new_fields/Doc";
+import { List } from "../../../new_fields/List";
+import { createSchema, listSpec, makeInterface } from "../../../new_fields/Schema";
+import { BoolCast, Cast, FieldValue, NumCast } from "../../../new_fields/Types";
+import { OmitKeys, Utils } from "../../../Utils";
+import { DocumentManager } from "../../util/DocumentManager";
+import { SelectionManager } from "../../util/SelectionManager";
 import { Transform } from "../../util/Transform";
+import { UndoManager } from "../../util/UndoManager";
+import { CollectionDockingView } from "../collections/CollectionDockingView";
+import { DocComponent } from "../DocComponent";
 import { DocumentView, DocumentViewProps, positionSchema } from "./DocumentView";
 import "./DocumentView.scss";
 import React = require("react");
-import { DocComponent } from "../DocComponent";
-import { createSchema, makeInterface, listSpec } from "../../../new_fields/Schema";
-import { FieldValue, Cast, NumCast, BoolCast, StrCast } from "../../../new_fields/Types";
-import { OmitKeys, Utils } from "../../../Utils";
-import { SelectionManager } from "../../util/SelectionManager";
-import { Doc, DocListCastAsync, DocListCast, } from "../../../new_fields/Doc";
-import { List } from "../../../new_fields/List";
-import { CollectionDockingView } from "../collections/CollectionDockingView";
-import { UndoManager } from "../../util/UndoManager";
-import { DocumentManager } from "../../util/DocumentManager";
 
 export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
 }
@@ -140,7 +140,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
             if (!CollectionFreeFormDocumentView._undoBatch) {
                 CollectionFreeFormDocumentView._undoBatch = UndoManager.StartBatch("iconAnimating");
             }
-            maximizedDocs.forEach(maximizedDoc => {
+            maximizedDocs.map(maximizedDoc => {
                 let iconAnimating = Cast(maximizedDoc.isIconAnimating, List);
                 if (!iconAnimating || (Date.now() - iconAnimating[6] > 1000)) {
                     if (isMinimized === undefined) {
@@ -182,12 +182,16 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                 let subBulletDocs = await DocListCastAsync(this.props.Document.subBulletDocs);
                 let maximizedDocs = await DocListCastAsync(this.props.Document.maximizedDocs);
                 let summarizedDocs = await DocListCastAsync(this.props.Document.summarizedDocs);
-                let linkedToDocs = await DocListCastAsync(this.props.Document.linkedToDocs);
-                let linkedFromDocs = await DocListCastAsync(this.props.Document.linkedFromDocs);
-                let expandedDocs = [...(subBulletDocs ? subBulletDocs : []),
-                ...(maximizedDocs ? maximizedDocs : []),
-                ...(summarizedDocs ? summarizedDocs : []),];
-                if (expandedDocs) {   // bcz: need a better way to associate behaviors with click events on widget-documents
+                let linkedToDocs = await DocListCastAsync(this.props.Document.linkedToDocs, []);
+                let linkedFromDocs = await DocListCastAsync(this.props.Document.linkedFromDocs, []);
+                let expandedDocs: Doc[] = [];
+                expandedDocs = subBulletDocs ? [...subBulletDocs, ...expandedDocs] : expandedDocs;
+                expandedDocs = maximizedDocs ? [...maximizedDocs, ...expandedDocs] : expandedDocs;
+                expandedDocs = summarizedDocs ? [...summarizedDocs, ...expandedDocs] : expandedDocs;
+                // let expandedDocs = [...(subBulletDocs ? subBulletDocs : []),
+                // ...(maximizedDocs ? maximizedDocs : []),
+                // ...(summarizedDocs ? summarizedDocs : []),];
+                if (expandedDocs.length) {   // bcz: need a better way to associate behaviors with click events on widget-documents
                     if ((altKey && !this.props.Document.maximizeOnRight) || (!altKey && this.props.Document.maximizeOnRight)) {
                         let dataDocs = DocListCast(CollectionDockingView.Instance.props.Document.data);
                         if (dataDocs) {
@@ -204,11 +208,13 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                         this.toggleIcon(expandedDocs);
                     }
                 }
-                let linkedFwdDocs = [
-                    linkedToDocs ? linkedToDocs[0].linkedTo as Doc : linkedFromDocs ? linkedFromDocs[0].linkedFrom as Doc : expandedDocs[0],
-                    linkedFromDocs ? linkedFromDocs[0].linkedFrom as Doc : linkedToDocs ? linkedToDocs[0].linkedTo as Doc : expandedDocs[0]];
-                if (linkedFwdDocs) {
-                    DocumentManager.Instance.jumpToDocument(linkedFwdDocs[altKey ? 1 : 0]);
+                else if (linkedToDocs.length || linkedFromDocs.length) {
+                    let linkedFwdDocs = [
+                        linkedToDocs.length ? linkedToDocs[0].linkedTo as Doc : linkedFromDocs.length ? linkedFromDocs[0].linkedFrom as Doc : expandedDocs[0],
+                        linkedFromDocs.length ? linkedFromDocs[0].linkedFrom as Doc : linkedToDocs.length ? linkedToDocs[0].linkedTo as Doc : expandedDocs[0]];
+                    if (linkedFwdDocs) {
+                        DocumentManager.Instance.jumpToDocument(linkedFwdDocs[altKey ? 1 : 0]);
+                    }
                 }
             }
         }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index b88d18046..27896f02b 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -112,8 +112,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         this._reactionDisposer = reaction(() => [this.props.Document.maximizedDocs, this.props.Document.summaryDoc, this.props.Document.summaryDoc instanceof Doc ? this.props.Document.summaryDoc.title : ""],
             () => {
                 let maxDoc = DocListCast(this.props.Document.maximizedDocs);
-                if (maxDoc && StrCast(this.props.Document.layout).indexOf("IconBox") !== -1) {
-                    this.props.Document.title = (maxDoc && maxDoc.length === 1 ? maxDoc[0].title + ".icon" : "");
+                if (maxDoc.length === 1 && StrCast(maxDoc[0].title).startsWith("-") && StrCast(this.props.Document.layout).indexOf("IconBox") !== -1) {
+                    this.props.Document.proto!.title = maxDoc[0].title + ".icon";
                 }
                 let sumDoc = Cast(this.props.Document.summaryDoc, Doc);
                 if (sumDoc instanceof Doc) {
@@ -332,8 +332,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
     render() {
         var scaling = this.props.ContentScaling();
-        var nativeHeight = this.nativeHeight > 0 ? this.nativeHeight.toString() + "px" : "100%";
-        var nativeWidth = this.nativeWidth > 0 ? this.nativeWidth.toString() + "px" : "100%";
+        var nativeHeight = this.nativeHeight > 0 ? `${this.nativeHeight}px` : (StrCast(this.props.Document.layout).indexOf("IconBox") === -1 ? "100%" : "auto");
+        var nativeWidth = this.nativeWidth > 0 ? `${this.nativeWidth}px` : "100%";
 
         return (
             <div className={`documentView-node${this.props.isTopMost ? "-topmost" : ""}`}
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index cb09ad42c..8e04071c7 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -229,7 +229,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             if (e.target && (e.target as any).href) {
                 let href = (e.target as any).href;
                 if (href.indexOf(DocServer.prepend("/doc/")) === 0) {
-                    let docid = href.replace(DocServer.prepend("/doc/"), "").split("%")[0];
+                    let docid = href.replace(DocServer.prepend("/doc/"), "").split("?")[0];
                     DocServer.GetRefField(docid).then(action((f: Opt<Field>) => {
                         if (f instanceof Doc) {
                             if (DocumentManager.Instance.getDocumentView(f))
diff --git a/src/client/views/nodes/IconBox.scss b/src/client/views/nodes/IconBox.scss
index f6d9860a3..893dc2d36 100644
--- a/src/client/views/nodes/IconBox.scss
+++ b/src/client/views/nodes/IconBox.scss
@@ -4,13 +4,13 @@
     position: inherit;
     left:0;
     top:0;
-    height: 100%;
+    height: auto;
     width: max-content;
     // overflow: hidden;
     pointer-events: all;
     svg {
         width:  $MINIMIZED_ICON_SIZE !important;
-        height: 100%;
+        height: auto;
         background: white;
     }
     .iconBox-label {
diff --git a/src/client/views/nodes/IconBox.tsx b/src/client/views/nodes/IconBox.tsx
index 071930633..b42eb44a5 100644
--- a/src/client/views/nodes/IconBox.tsx
+++ b/src/client/views/nodes/IconBox.tsx
@@ -7,7 +7,7 @@ import { observer } from "mobx-react";
 import { FieldView, FieldViewProps } from './FieldView';
 import "./IconBox.scss";
 import { Cast, StrCast, BoolCast } from "../../../new_fields/Types";
-import { Doc } from "../../../new_fields/Doc";
+import { Doc, DocListCast } from "../../../new_fields/Doc";
 import { IconField } from "../../../new_fields/IconField";
 import { ContextMenu } from "../ContextMenu";
 import Measure from "react-measure";
@@ -41,25 +41,40 @@ export class IconBox extends React.Component<FieldViewProps> {
     setLabelField = (e: React.MouseEvent): void => {
         this.props.Document.hideLabel = !BoolCast(this.props.Document.hideLabel);
     }
+    setUseOwnTitleField = (e: React.MouseEvent): void => {
+        this.props.Document.useOwnTitle = !BoolCast(this.props.Document.useTargetTitle);
+    }
 
     specificContextMenu = (e: React.MouseEvent): void => {
         ContextMenu.Instance.addItem({
-            description: BoolCast(this.props.Document.hideLabel) ? "show label" : "hide label",
+            description: BoolCast(this.props.Document.hideLabel) ? "Show label with icon" : "Remove label from icon",
             event: this.setLabelField
         });
+        let maxDocs = DocListCast(this.props.Document.maximizedDocs);
+        if (maxDocs.length === 1 && !BoolCast(this.props.Document.hideLabel)) {
+            ContextMenu.Instance.addItem({
+                description: BoolCast(this.props.Document.useOwnTitle) ? "Use target title for label" : "Use own title label",
+                event: this.setUseOwnTitleField
+            });
+        }
     }
     @observable _panelWidth: number = 0;
     @observable _panelHeight: number = 0;
     render() {
         let labelField = StrCast(this.props.Document.labelField);
         let hideLabel = BoolCast(this.props.Document.hideLabel);
-        let maxDoc = Cast(this.props.Document.maximizedDocs, listSpec(Doc), []);
-        let firstDoc = maxDoc && maxDoc.length > 0 && maxDoc[0] instanceof Doc ? maxDoc[0] as Doc : undefined;
-        let label = !hideLabel && firstDoc && labelField ? firstDoc[labelField] : "";
+        let maxDocs = DocListCast(this.props.Document.maximizedDocs);
+        let firstDoc = maxDocs.length ? maxDocs[0] : undefined;
+        let label = hideLabel ? "" : (firstDoc && labelField && !BoolCast(this.props.Document.useOwnTitle, false) ? firstDoc[labelField] : this.props.Document.title);
         return (
             <div className="iconBox-container" onContextMenu={this.specificContextMenu}>
                 {this.minimizedIcon}
-                <Measure offset onResize={(r) => runInAction(() => { if (r.offset!.width || BoolCast(this.props.Document.hideLabel)) this.props.Document.nativeWidth = this.props.Document.width = (r.offset!.width + Number(MINIMIZED_ICON_SIZE)); })}>
+                <Measure offset onResize={(r) => runInAction(() => {
+                    if (r.offset!.width || BoolCast(this.props.Document.hideLabel)) {
+                        this.props.Document.nativeWidth = (r.offset!.width + Number(MINIMIZED_ICON_SIZE));
+                        if (this.props.Document.height === Number(MINIMIZED_ICON_SIZE)) this.props.Document.width = this.props.Document.nativeWidth;
+                    }
+                })}>
                     {({ measureRef }) =>
                         <span ref={measureRef} className="iconBox-label">{label}</span>
                     }
diff --git a/src/new_fields/List.ts b/src/new_fields/List.ts
index 88a65eba4..70e36f911 100644
--- a/src/new_fields/List.ts
+++ b/src/new_fields/List.ts
@@ -230,6 +230,16 @@ class ListImpl<T extends Field> extends ObjectField {
         const list = new Proxy<this>(this, {
             set: setter,
             get: listGetter,
+            ownKeys: target => Object.keys(target.__fields),
+            getOwnPropertyDescriptor: (target, prop) => {
+                if (prop in target.__fields) {
+                    return {
+                        configurable: true,//TODO Should configurable be true?
+                        enumerable: true,
+                    };
+                }
+                return Reflect.getOwnPropertyDescriptor(target, prop);
+            },
             deleteProperty: deleteProperty,
             defineProperty: () => { throw new Error("Currently properties can't be defined on documents using Object.defineProperty"); },
         });
-- 
cgit v1.2.3-70-g09d2

