From 8bca5882b39c21560c29cb97c2770a7a78384018 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 9 Apr 2020 15:41:44 -0400
Subject: starting cleanup

---
 src/client/util/RichTextSchema.tsx | 2 --
 1 file changed, 2 deletions(-)

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

diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index de2707d36..3e6cbce56 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -959,8 +959,6 @@ export class DashFieldView {
             if (self._options?.length && !self._dashDoc[self._fieldKey]) {
                 self._dashDoc[self._fieldKey] = StrCast(self._options[0].title);
             }
-            // NOTE: if the field key starts with "@", then the actual field key is stored in the field 'fieldKey' (removing the @).
-            self._fieldKey = self._fieldKey.startsWith("@") ? StrCast(tbox.props.Document[StrCast(self._fieldKey).substring(1)]) : self._fieldKey;
             this._labelSpan.innerHTML = `${self._fieldKey}: `;
             const fieldVal = Cast(this._dashDoc?.[self._fieldKey], "boolean", null);
             this._fieldCheck.style.display = (fieldVal === true || fieldVal === false) ? "inline-block" : "none";
-- 
cgit v1.2.3-70-g09d2


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

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

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

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


From e091a465b860f0b205fbfa5ac6feedaf4f98e1dd Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 21 Apr 2020 01:15:17 -0400
Subject: added MakeClone to alias documents in a collection.  fixed  embedded
 custom templates.

---
 src/client/util/DragManager.ts          |  2 +-
 src/client/util/RichTextSchema.tsx      | 10 ++++----
 src/client/views/nodes/DocumentView.tsx |  2 +-
 src/client/views/search/SearchBox.scss  |  3 ++-
 src/new_fields/Doc.ts                   | 43 +++++++++++++++++++++++++++++++--
 5 files changed, 50 insertions(+), 10 deletions(-)

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

diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 3ea030171..42a78a4bf 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -205,7 +205,7 @@ export namespace DragManager {
             e.docDragData && (e.docDragData.droppedDocuments =
                 dragData.draggedDocuments.map(d => !dragData.isSelectionMove && !dragData.userDropAction && ScriptCast(d.onDragStart) ? addAudioTag(ScriptCast(d.onDragStart).script.run({ this: d }).result) :
                     dragData.userDropAction === "alias" || (!dragData.userDropAction && dragData.dropAction === "alias") ? Doc.MakeAlias(d) :
-                        dragData.userDropAction === "copy" || (!dragData.userDropAction && dragData.dropAction === "copy") ? Doc.MakeCopy(d, true) : d)
+                        dragData.userDropAction === "copy" || (!dragData.userDropAction && dragData.dropAction === "copy") ? Doc.MakeClone(d) : d)
             );
             e.docDragData?.droppedDocuments.forEach((drop: Doc, i: number) =>
                 (dragData?.removeDropProperties || []).concat(Cast(dragData.draggedDocuments[i].removeDropProperties, listSpec("string"), [])).map(prop => drop[prop] = undefined)
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 0599b3ebe..1522f5e21 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -27,11 +27,11 @@ import ParagraphNodeSpec from "./ParagraphNodeSpec";
 import { Transform } from "./Transform";
 import React = require("react");
 
-const 
-    blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], 
+const
+    blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0],
     hrDOM: DOMOutputSpecArray = ["hr"],
-    preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], 
-    brDOM: DOMOutputSpecArray = ["br"], 
+    preDOM: DOMOutputSpecArray = ["pre", ["code", 0]],
+    brDOM: DOMOutputSpecArray = ["br"],
     ulDOM: DOMOutputSpecArray = ["ul", 0];
 
 // :: Object
@@ -853,7 +853,7 @@ export class DashDocView {
             this._renderDisposer?.();
             this._renderDisposer = reaction(() => {
                 if (!Doc.AreProtosEqual(finalLayout, dashDoc)) {
-                    finalLayout.rootDocument = dashDoc.aliasOf;
+                    finalLayout.rootDocument = dashDoc.aliasOf; // bcz: check on this ... why is it here?
                 }
                 const layoutKey = StrCast(finalLayout.layoutKey);
                 const finalKey = layoutKey && StrCast(finalLayout[layoutKey]).split("'")?.[1];
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 8a05cfb0d..286044ab2 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -561,7 +561,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     static findTemplate(templateName: string, type: string, signature: string) {
         let docLayoutTemplate: Opt<Doc>;
         const iconViews = DocListCast(Cast(Doc.UserDoc()["template-icons"], Doc, null)?.data);
-        const templBtns = DocListCast(Cast(Doc.UserDoc().templateButtons, Doc, null)?.data);
+        const templBtns = DocListCast(Cast(Doc.UserDoc()["template-buttons"], Doc, null)?.data);
         const noteTypes = DocListCast(Cast(Doc.UserDoc().noteTypes, Doc, null)?.data);
         const clickFuncs = DocListCast(Cast(Doc.UserDoc().clickFuncs, Doc, null)?.data);
         const allTemplates = iconViews.concat(templBtns).concat(noteTypes).concat(clickFuncs).map(btnDoc => (btnDoc.dragFactory as Doc) || btnDoc).filter(doc => doc.isTemplateDoc);
diff --git a/src/client/views/search/SearchBox.scss b/src/client/views/search/SearchBox.scss
index f0223ca76..bb62113a1 100644
--- a/src/client/views/search/SearchBox.scss
+++ b/src/client/views/search/SearchBox.scss
@@ -9,7 +9,8 @@
     position: absolute;
     font-size: 10px;
     line-height: 1;
-    overflow: auto;
+    overflow-y: auto;
+    overflow-x: visible;
     background: lightgrey,
 }
 
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 8e8f0928c..5f790f886 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -565,8 +565,9 @@ export namespace Doc {
                 } else if (cfield instanceof ComputedField) {
                     copy[key] = ComputedField.MakeFunction(cfield.script.originalScript);
                 } else if (field instanceof ObjectField) {
-                    copy[key] = key.includes("layout[") && doc[key] instanceof Doc ? Doc.MakeCopy(doc[key] as Doc, false) :
-                        doc[key] instanceof Doc ? doc[key] : ObjectField.MakeCopy(field);
+                    copy[key] = doc[key] instanceof Doc ?
+                        key.includes("layout[") ? Doc.MakeCopy(doc[key] as Doc, false) : doc[key] : // reference documents except copy documents that are expanded teplate fields 
+                        ObjectField.MakeCopy(field);
                 } else if (field instanceof Promise) {
                     debugger; //This shouldn't happend...
                 } else {
@@ -578,6 +579,44 @@ export namespace Doc {
         return copy;
     }
 
+    export function MakeClone(doc: Doc, cloneProto: boolean = true): Doc {
+        const copy = new Doc(undefined, true);
+        const exclude = Cast(doc.excludeFields, listSpec("string"), []);
+        Object.keys(doc).forEach(key => {
+            if (exclude.includes(key)) return;
+            const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
+            const field = ProxyField.WithoutProxy(() => doc[key]);
+            if (key === "proto" && cloneProto) {
+                if (doc[key] instanceof Doc) {
+                    copy[key] = Doc.MakeClone(doc[key]!, false);
+                }
+            } else {
+                if (field instanceof RefField) {
+                    copy[key] = field;
+                } else if (cfield instanceof ComputedField) {
+                    copy[key] = ComputedField.MakeFunction(cfield.script.originalScript);
+                } else if (field instanceof ObjectField) {
+                    const list = Cast(doc[key], listSpec(Doc));
+                    if (list !== undefined && !(list instanceof Promise)) {
+                        copy[key] = new List<Doc>(list.filter(d => d instanceof Doc).map(d => Doc.MakeCopy(d as Doc, false)));
+                    } else {
+                        copy[key] = doc[key] instanceof Doc ?
+                            key.includes("layout[") ?
+                                Doc.MakeCopy(doc[key] as Doc, false) : doc[key] : // reference documents except copy documents that are expanded teplate fields 
+                            ObjectField.MakeCopy(field);
+                    }
+                } else if (field instanceof Promise) {
+                    debugger; //This shouldn't happend...
+                } else {
+                    copy[key] = field;
+                }
+            }
+        });
+        Doc.SetInPlace(copy, "title", "CLONE: " + doc.title, true);
+        copy.cloneOf = doc;
+        return copy;
+    }
+
     export function MakeDelegate(doc: Doc, id?: string, title?: string): Doc;
     export function MakeDelegate(doc: Opt<Doc>, id?: string, title?: string): Opt<Doc>;
     export function MakeDelegate(doc: Opt<Doc>, id?: string, title?: string): Opt<Doc> {
-- 
cgit v1.2.3-70-g09d2


From 3d629495644631e6b614b1b48f1d32d6c7aeefc6 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 21 Apr 2020 17:25:17 -0400
Subject: added parameterization capability to layout strings.  added Div's
 that can take Dash field values

---
 src/client/util/RichTextSchema.tsx              |  6 ++--
 src/client/views/DocumentDecorations.tsx        |  4 +--
 src/client/views/collections/CollectionView.tsx |  7 ++--
 src/client/views/nodes/DocumentContentsView.tsx | 45 +++++++++++++++++++++----
 4 files changed, 46 insertions(+), 16 deletions(-)

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

diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 1522f5e21..d23962d5c 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -852,9 +852,9 @@ export class DashDocView {
             };
             this._renderDisposer?.();
             this._renderDisposer = reaction(() => {
-                if (!Doc.AreProtosEqual(finalLayout, dashDoc)) {
-                    finalLayout.rootDocument = dashDoc.aliasOf; // bcz: check on this ... why is it here?
-                }
+                // if (!Doc.AreProtosEqual(finalLayout, dashDoc)) {
+                //     finalLayout.rootDocument = dashDoc.aliasOf; // bcz: check on this ... why is it here?
+                // }
                 const layoutKey = StrCast(finalLayout.layoutKey);
                 const finalKey = layoutKey && StrCast(finalLayout[layoutKey]).split("'")?.[1];
                 if (finalLayout !== dashDoc && finalKey) {
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index c8766a6b3..bd41788d4 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -3,7 +3,7 @@ import { faCaretUp, faFilePdf, faFilm, faImage, faObjectGroup, faStickyNote, faT
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { action, computed, observable, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, DataSym } from "../../new_fields/Doc";
+import { Doc, DataSym, Field } from "../../new_fields/Doc";
 import { PositionDocument } from '../../new_fields/documentSchemas';
 import { ScriptField } from '../../new_fields/ScriptField';
 import { Cast, StrCast, NumCast } from "../../new_fields/Types";
@@ -366,7 +366,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 return ScriptField.MakeFunction(this._titleControlString.substring(1), { doc: Doc.name })!.script.run({ self: selected.rootDoc, this: selected.layoutDoc }, console.log).result?.toString() || "";
             }
             if (this._titleControlString.startsWith("#")) {
-                return selected.props.Document[this._titleControlString.substring(1)]?.toString() || "-unset-";
+                return Field.toString(selected.props.Document[this._titleControlString.substring(1)] as Field) || "-unset-";
             }
             return this._accumulatedTitle;
         } else if (SelectionManager.SelectedDocuments().length > 1) {
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 178c3189b..4448fcf21 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -211,11 +211,8 @@ export class CollectionView extends Touchable<FieldViewProps> {
         subItems.push({ description: "Carousel", event: () => func(CollectionViewType.Carousel), icon: "columns" });
         subItems.push({ description: "Pivot/Time", event: () => func(CollectionViewType.Time), icon: "columns" });
         subItems.push({ description: "Map", event: () => func(CollectionViewType.Map), icon: "globe-americas" });
-        if (addExtras) switch (this.props.Document._viewType) {
-            case CollectionViewType.Freeform: {
-                subItems.push({ description: "Custom", icon: "fingerprint", event: AddCustomFreeFormLayout(this.props.Document, this.props.fieldKey) });
-                break;
-            }
+        if (addExtras && this.props.Document._viewType === CollectionViewType.Freeform) {
+            subItems.push({ description: "Custom", icon: "fingerprint", event: AddCustomFreeFormLayout(this.props.Document, this.props.fieldKey) });
         }
         addExtras && subItems.push({ description: "lightbox", event: action(() => this._isLightboxOpen = true), icon: "eye" });
         !existingVm && ContextMenu.Instance.addItem({ description: category, subitems: subItems, icon: "eye" });
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 7522af3a3..765416658 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -1,8 +1,8 @@
 import { computed } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, Opt } from "../../../new_fields/Doc";
-import { Cast, StrCast } from "../../../new_fields/Types";
-import { OmitKeys, Without } from "../../../Utils";
+import { Doc, Opt, Field } from "../../../new_fields/Doc";
+import { Cast, StrCast, NumCast } from "../../../new_fields/Types";
+import { OmitKeys, Without, emptyPath } from "../../../Utils";
 import DirectoryImportBox from "../../util/Import & Export/DirectoryImportBox";
 import { CollectionDockingView } from "../collections/CollectionDockingView";
 import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
@@ -53,6 +53,31 @@ class ObserverJsxParser1 extends JsxParser {
 
 const ObserverJsxParser: typeof JsxParser = ObserverJsxParser1 as any;
 
+
+interface DivProps {
+    Document: Doc;
+}
+
+@observer
+export class Div extends React.Component<DivProps> {
+    render() {
+        const style: { [key: string]: any } = {};
+        const divKeys = OmitKeys(this.props, ["children", "Document", "key", "__proto__"]).omit;
+        Object.keys(divKeys).map((prop: string) => {
+            let p = (this.props as any)[prop] as string;
+            if (p?.startsWith("@")) {  // bcz: extend this to support expression -- is this really a script?
+                const key = p.substring(1);
+                const n = Cast(this.props.Document[key], "number", null);
+                p = n ? n.toString() : StrCast(this.props.Document[key], p);
+            }
+            style[prop] = p;
+        });
+        return <div style={style}>
+            {this.props.children}
+        </div>;
+    }
+}
+
 @observer
 export class DocumentContentsView extends React.Component<DocumentViewProps & {
     isSelected: (outsideReaction: boolean) => boolean,
@@ -102,21 +127,29 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
 
     render() {
         TraceMobx();
-        return (this.props.renderDepth > 12 || !this.layout || !this.layoutDoc) ? (null) :
+        let layoutFrame = this.layout;
+        const replacer = (match: any, p1: string, offset: any, string: any) => {
+            const n = Cast(this.props.Document[p1], "number", null);
+            return n !== undefined ? n.toString() : StrCast(this.props.Document[p1], p1);
+        };
+        layoutFrame = layoutFrame.replace(/\{([a-zA-Z0-9_-]+)\}/g, replacer);
+        return (this.props.renderDepth > 12 || !layoutFrame || !this.layoutDoc) ? (null) :
             this.props.forceLayout === "FormattedTextBox" && this.props.forceFieldKey ?
                 <FormattedTextBox {...this.CreateBindings().props} fieldKey={this.props.forceFieldKey} />
                 :
                 <ObserverJsxParser
+                    key={42}
                     blacklistedAttrs={[]}
+                    renderInWrapper={false}
                     components={{
                         FormattedTextBox, ImageBox, DirectoryImportBox, FontIconBox, LabelBox, SliderBox, FieldView,
                         CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebBox, KeyValueBox,
                         PDFBox, VideoBox, AudioBox, PresBox, YoutubeBox, PresElementBox, QueryBox,
                         ColorBox, DashWebRTCVideo, LinkAnchorBox, InkingStroke, DocHolderBox, LinkBox, ScriptingBox,
-                        RecommendationsBox, ScreenshotBox
+                        RecommendationsBox, ScreenshotBox, Div
                     }}
                     bindings={this.CreateBindings()}
-                    jsx={this.layout}
+                    jsx={layoutFrame}
                     showWarnings={true}
 
                     onError={(test: any) => { console.log(test); }}
-- 
cgit v1.2.3-70-g09d2


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

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

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

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


From d24622da70be0100846afb780f3f8734f22f4196 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sun, 26 Apr 2020 11:30:47 -0400
Subject: tweaks to inline attribute fields in text boxes

---
 src/client/util/RichTextSchema.tsx | 21 ++++++++++-----------
 1 file changed, 10 insertions(+), 11 deletions(-)

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

diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index b88a7b017..8882cdc8e 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -15,7 +15,7 @@ import { ObjectField } from "../../new_fields/ObjectField";
 import { listSpec } from "../../new_fields/Schema";
 import { SchemaHeaderField } from "../../new_fields/SchemaHeaderField";
 import { ComputedField } from "../../new_fields/ScriptField";
-import { BoolCast, Cast, NumCast, StrCast } from "../../new_fields/Types";
+import { BoolCast, Cast, NumCast, StrCast, FieldValue } from "../../new_fields/Types";
 import { emptyFunction, returnEmptyString, returnFalse, returnOne, Utils, returnZero } from "../../Utils";
 import { DocServer } from "../DocServer";
 import { Docs } from "../documents/Documents";
@@ -879,7 +879,7 @@ export class DashDocView {
 export class DashFieldView {
     _fieldWrapper: HTMLDivElement; // container for label and value
     _labelSpan: HTMLSpanElement; // field label
-    _fieldSpan: HTMLDivElement;  // field value
+    _fieldSpan: HTMLSpanElement;  // field value
     _fieldCheck: HTMLInputElement;
     _enumerables: HTMLDivElement;  // field value
     _reactionDisposer: IReactionDisposer | undefined;
@@ -891,11 +891,12 @@ export class DashFieldView {
     constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
         this._fieldKey = node.attrs.fieldKey;
         this._textBoxDoc = tbox.props.Document;
-        this._fieldWrapper = document.createElement("div");
+        this._fieldWrapper = document.createElement("p");
         this._fieldWrapper.style.width = node.attrs.width;
         this._fieldWrapper.style.height = node.attrs.height;
+        this._fieldWrapper.style.fontWeight = "bold";
         this._fieldWrapper.style.position = "relative";
-        this._fieldWrapper.style.display = "inline-flex";
+        this._fieldWrapper.style.display = "inline-block";
 
         const self = this;
         this._enumerables = document.createElement("div");
@@ -903,7 +904,6 @@ export class DashFieldView {
         this._enumerables.style.height = "10px";
         this._enumerables.style.position = "relative";
         this._enumerables.style.display = "none";
-        this._enumerables.style.background = "dimGray";
 
         this._enumerables.onpointerdown = async (e) => {
             e.stopPropagation();
@@ -946,13 +946,13 @@ export class DashFieldView {
             self._dashDoc![self._fieldKey] = e.target.checked;
         };
 
-        this._fieldSpan = document.createElement("div");
+        this._fieldSpan = document.createElement("span");
         this._fieldSpan.id = Utils.GenerateGuid();
         this._fieldSpan.contentEditable = "true";
         this._fieldSpan.style.position = "relative";
         this._fieldSpan.style.display = "none";
         this._fieldSpan.style.minWidth = "12px";
-        this._fieldSpan.style.backgroundColor = "rgba(155, 155, 155, 0.24)";
+        this._fieldSpan.style.fontSize = "large";
         this._fieldSpan.onkeypress = function (e: any) { e.stopPropagation(); };
         this._fieldSpan.onkeyup = function (e: any) { e.stopPropagation(); };
         this._fieldSpan.onmousedown = function (e: any) { e.stopPropagation(); self._enumerables.style.display = "inline-block"; };
@@ -966,7 +966,7 @@ export class DashFieldView {
             this._labelSpan.innerHTML = `${self._fieldKey}: `;
             const fieldVal = Cast(this._dashDoc?.[self._fieldKey], "boolean", null);
             this._fieldCheck.style.display = (fieldVal === true || fieldVal === false) ? "inline-block" : "none";
-            this._fieldSpan.style.display = !(fieldVal === true || fieldVal === false) ? "inline-block" : "none";
+            this._fieldSpan.style.display = !(fieldVal === true || fieldVal === false) ? StrCast(this._dashDoc?.[self._fieldKey]) ? "" : "inline-block" : "none";
         };
         this._fieldSpan.onkeydown = function (e: any) {
             e.stopPropagation();
@@ -987,11 +987,10 @@ export class DashFieldView {
         };
 
         this._labelSpan = document.createElement("span");
-        this._labelSpan.style.backgroundColor = "rgba(155, 155, 155, 0.44)";
         this._labelSpan.style.position = "relative";
-        this._labelSpan.style.display = "inline-block";
         this._labelSpan.style.fontSize = "small";
         this._labelSpan.title = "click to see related tags";
+        this._labelSpan.style.fontSize = "x-small";
         this._labelSpan.onpointerdown = function (e: any) {
             e.stopPropagation();
             let container = tbox.props.ContainingCollectionView;
@@ -1029,7 +1028,7 @@ export class DashFieldView {
                 this._fieldSpan.innerHTML = Field.toString(fval as Field) || "";
             }
             this._fieldCheck.style.display = (boolVal === true || boolVal === false) ? "inline-block" : "none";
-            this._fieldSpan.style.display = !(boolVal === true || boolVal === false) ? "inline-block" : "none";
+            this._fieldSpan.style.display = !(fval === true || fval === false) ? (StrCast(fval) ? "" : "inline-block") : "none";
         }, { fireImmediately: true });
 
         this._fieldWrapper.appendChild(this._labelSpan);
-- 
cgit v1.2.3-70-g09d2


From 4f01a91938e7afc4affde3af469db55bdb7be3b7 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sun, 26 Apr 2020 15:39:06 -0400
Subject: shorterning of userMark to UM for readability.  added onClick and
 contenteditable to DASHdiv stuff

---
 src/client/util/RichTextSchema.tsx                 |  4 ++--
 src/client/views/nodes/FormattedTextBox.tsx        | 12 ++++++------
 src/client/views/nodes/FormattedTextBoxComment.tsx |  1 -
 3 files changed, 8 insertions(+), 9 deletions(-)

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

diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 8882cdc8e..d5dd05aa4 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -522,8 +522,8 @@ export const marks: { [index: string]: MarkSpec } = {
             const min = Math.round(node.attrs.modified / 12);
             const hr = Math.round(min / 60);
             const day = Math.round(hr / 60 / 24);
-            const remote = node.attrs.userid !== Doc.CurrentUserEmail ? " userMark-remote" : "";
-            return ['span', { class: "userMark-" + uid + remote + " userMark-min-" + min + " userMark-hr-" + hr + " userMark-day-" + day }, 0];
+            const remote = node.attrs.userid !== Doc.CurrentUserEmail ? " UM-remote" : "";
+            return ['span', { class: "UM-" + uid + remote + " UM-min-" + min + " UM-hr-" + hr + " UM-day-" + day }, 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 425af8ccf..567d4c440 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -341,10 +341,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     updateHighlights = () => {
         clearStyleSheetRules(FormattedTextBox._userStyleSheet);
         if (FormattedTextBox._highlights.indexOf("Text from Others") !== -1) {
-            addStyleSheetRule(FormattedTextBox._userStyleSheet, "userMark-remote", { background: "yellow" });
+            addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-remote", { background: "yellow" });
         }
         if (FormattedTextBox._highlights.indexOf("My Text") !== -1) {
-            addStyleSheetRule(FormattedTextBox._userStyleSheet, "userMark-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { background: "moccasin" });
+            addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { background: "moccasin" });
         }
         if (FormattedTextBox._highlights.indexOf("Todo Items") !== -1) {
             addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "todo", { outline: "black solid 1px" });
@@ -359,15 +359,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "ignore", { "font-size": "1" });
         }
         if (FormattedTextBox._highlights.indexOf("By Recent Minute") !== -1) {
-            addStyleSheetRule(FormattedTextBox._userStyleSheet, "userMark-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" });
+            addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" });
             const min = Math.round(Date.now() / 1000 / 60);
-            numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, "userMark-min-" + (min - i), { opacity: ((10 - i - 1) / 10).toString() }));
+            numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-min-" + (min - i), { opacity: ((10 - i - 1) / 10).toString() }));
             setTimeout(() => this.updateHighlights());
         }
         if (FormattedTextBox._highlights.indexOf("By Recent Hour") !== -1) {
-            addStyleSheetRule(FormattedTextBox._userStyleSheet, "userMark-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" });
+            addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" });
             const hr = Math.round(Date.now() / 1000 / 60 / 60);
-            numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, "userMark-hr-" + (hr - i), { opacity: ((10 - i - 1) / 10).toString() }));
+            numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-hr-" + (hr - i), { opacity: ((10 - i - 1) / 10).toString() }));
         }
     }
 
diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx
index a9f76ae8f..7d441a48b 100644
--- a/src/client/views/nodes/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/FormattedTextBoxComment.tsx
@@ -16,7 +16,6 @@ import React = require("react");
 import { Docs } from "../../documents/Documents";
 import wiki from "wikijs";
 import { DocumentType } from "../../documents/DocumentTypes";
-import { DocumentView } from "./DocumentView";
 
 export let formattedTextBoxCommentPlugin = new Plugin({
     view(editorView) { return new FormattedTextBoxComment(editorView); }
-- 
cgit v1.2.3-70-g09d2

