From 866561aa20aff0f570d31edf64eae36cd2d35279 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 14 Jul 2020 17:27:19 -0400
Subject: fixed onClickHandler stuff to use functions properly.  fixed several
 linking to annotations/pushpins problems.

---
 src/client/views/nodes/formattedText/RichTextMenu.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

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

diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 8da1f99b5..c8798d757 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -431,7 +431,7 @@ export default class RichTextMenu extends AntimodeMenu {
         const nextIsOL = this.view.state.selection.$from.nodeAfter?.type === schema.nodes.ordered_list;
         let inList: any = undefined;
         let fromList = -1;
-        let path: any = Array.from((this.view.state.selection.$from as any).path);
+        const path: any = Array.from((this.view.state.selection.$from as any).path);
         for (let i = 0; i < path.length; i++) {
             if (path[i]?.type === schema.nodes.ordered_list) {
                 inList = path[i];
-- 
cgit v1.2.3-70-g09d2


From 420f4cc781747af4769445e71e02cd884dbe1131 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 15 Jul 2020 12:43:08 -0400
Subject: fixed runtime memoize warnings.

---
 src/client/views/collections/CollectionView.tsx       |  2 +-
 src/client/views/nodes/formattedText/RichTextMenu.tsx | 18 +++++++++---------
 2 files changed, 10 insertions(+), 10 deletions(-)

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

diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 00bd43b8f..c1a6b5b0d 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -545,7 +545,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
             ChildLayoutTemplate: this.childLayoutTemplate,
             ChildLayoutString: this.childLayoutString,
         };
-        setTimeout(action(() => this.props.isSelected() && (CollectionMenu.Instance.SelectedCollection = this)), 0);
+        setTimeout(action(() => this.props.isSelected(true) && (CollectionMenu.Instance.SelectedCollection = this)), 0);
         const boxShadow = Doc.UserDoc().renderStyle === "comic" || this.props.Document.isBackground || this.collectionViewType === CollectionViewType.Linear ? undefined :
             `${Cast(Doc.UserDoc().activeWorkspace, Doc, null)?.darkScheme ? "rgb(30, 32, 31) " : "#9c9396 "} ${StrCast(this.props.Document.boxShadow, "0.2vw 0.2vw 0.8vw")}`;
         return (<div className={"collectionView"} onContextMenu={this.onContextMenu}
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index c8798d757..d0e6f3b37 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -218,7 +218,7 @@ export default class RichTextMenu extends AntimodeMenu {
 
     // finds font sizes and families in selection
     getActiveAlignment() {
-        if (this.view && this.TextView.props.isSelected()) {
+        if (this.view && this.TextView.props.isSelected(true)) {
             const path = (this.view.state.selection.$from as any).path;
             for (let i = path.length - 3; i < path.length && i >= 0; i -= 3) {
                 if (path[i]?.type === this.view.state.schema.nodes.paragraph) {
@@ -231,7 +231,7 @@ export default class RichTextMenu extends AntimodeMenu {
 
     // finds font sizes and families in selection
     getActiveListStyle() {
-        if (this.view && this.TextView.props.isSelected()) {
+        if (this.view && this.TextView.props.isSelected(true)) {
             const path = (this.view.state.selection.$from as any).path;
             for (let i = 0; i < path.length; i += 3) {
                 if (path[i].type === this.view.state.schema.nodes.ordered_list) {
@@ -251,7 +251,7 @@ export default class RichTextMenu extends AntimodeMenu {
 
         const activeFamilies: string[] = [];
         const activeSizes: string[] = [];
-        if (this.TextView.props.isSelected()) {
+        if (this.TextView.props.isSelected(true)) {
             const state = this.view.state;
             const pos = this.view.state.selection.$from;
             const ref_node = this.reference_node(pos);
@@ -279,7 +279,7 @@ export default class RichTextMenu extends AntimodeMenu {
     //finds all active marks on selection in given group
     getActiveMarksOnSelection() {
         let activeMarks: MarkType[] = [];
-        if (!this.view || !this.TextView.props.isSelected()) return activeMarks;
+        if (!this.view || !this.TextView.props.isSelected(true)) return activeMarks;
 
         const markGroup = [schema.marks.strong, schema.marks.em, schema.marks.underline, schema.marks.strikethrough, schema.marks.superscript, schema.marks.subscript];
         if (this.view.state.storedMarks) return this.view.state.storedMarks.map(mark => mark.type);
@@ -378,7 +378,7 @@ export default class RichTextMenu extends AntimodeMenu {
             self.TextView.endUndoTypingBatch();
             options.forEach(({ label, mark, command }) => {
                 if (e.target.value === label && mark) {
-                    if (!self.TextView.props.isSelected()) {
+                    if (!self.TextView.props.isSelected(true)) {
                         switch (mark.type) {
                             case schema.marks.pFontFamily: setter(Doc.UserDoc().fontFamily = mark.attrs.family); break;
                             case schema.marks.pFontSize: setter(Doc.UserDoc().fontSize = mark.attrs.fontSize.toString() + "pt"); break;
@@ -405,7 +405,7 @@ export default class RichTextMenu extends AntimodeMenu {
             self.TextView.endUndoTypingBatch();
             options.forEach(({ label, node, command }) => {
                 if (val === label && node) {
-                    if (self.TextView.props.isSelected()) {
+                    if (self.TextView.props.isSelected(true)) {
                         UndoManager.RunInBatch(() => self.view && node && command(node), "nodes dropdown");
                         setter(val);
                     }
@@ -469,13 +469,13 @@ export default class RichTextMenu extends AntimodeMenu {
         return true;
     }
     alignCenter = (state: EditorState<any>, dispatch: any) => {
-        return this.TextView.props.isSelected() && this.alignParagraphs(state, "center", dispatch);
+        return this.TextView.props.isSelected(true) && this.alignParagraphs(state, "center", dispatch);
     }
     alignLeft = (state: EditorState<any>, dispatch: any) => {
-        return this.TextView.props.isSelected() && this.alignParagraphs(state, "left", dispatch);
+        return this.TextView.props.isSelected(true) && this.alignParagraphs(state, "left", dispatch);
     }
     alignRight = (state: EditorState<any>, dispatch: any) => {
-        return this.TextView.props.isSelected() && this.alignParagraphs(state, "right", dispatch);
+        return this.TextView.props.isSelected(true) && this.alignParagraphs(state, "right", dispatch);
     }
 
     alignParagraphs(state: EditorState<any>, align: "left" | "right" | "center", dispatch: any) {
-- 
cgit v1.2.3-70-g09d2


From 3fdee81100954e66f54c73661cb11967993ff467 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 15 Jul 2020 15:24:53 -0400
Subject: fixed tooltips to share a csss style.  added tooltips for richtext.

---
 src/client/documents/Documents.ts                  |  2 +-
 src/client/views/DocumentButtonBar.tsx             | 10 ++---
 src/client/views/DocumentDecorations.tsx           | 15 +++----
 src/client/views/MainView.scss                     |  4 ++
 .../views/collections/CollectionLinearView.tsx     | 20 +++++----
 src/client/views/collections/CollectionMenu.tsx    | 13 +++---
 src/client/views/collections/CollectionView.tsx    |  3 +-
 .../CollectionFreeFormLinkView.tsx                 | 12 +++---
 src/client/views/linking/LinkEditor.tsx            | 14 +++----
 src/client/views/linking/LinkMenuItem.tsx          | 19 +++------
 src/client/views/nodes/DocumentLinksButton.tsx     |  5 +--
 src/client/views/nodes/DocumentView.tsx            | 25 +++--------
 .../formattedText/FormattedTextBoxComment.tsx      |  4 +-
 .../views/nodes/formattedText/RichTextMenu.tsx     | 49 +++++++++++++---------
 src/client/views/pdf/PDFViewer.tsx                 |  2 +-
 15 files changed, 94 insertions(+), 103 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 1fd533b62..b57f4c6c7 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -915,7 +915,7 @@ export namespace DocUtils {
 
         const linkDoc = Docs.Create.LinkDocument(source, target, { linkRelationship, layoutKey: "layout_linkView", description }, id);
         linkDoc.linkDisplay = true;
-        linkDoc.hideAnhors = true;
+        linkDoc.hidden = true;
         linkDoc.layout_linkView = Cast(Cast(Doc.UserDoc()["template-button-link"], Doc, null).dragFactory, Doc, null);
         Doc.GetProto(linkDoc).title = ComputedField.MakeFunction('self.anchor1?.title +" (" + (self.linkRelationship||"to") +") "  + self.anchor2?.title');
 
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 05538a28e..45f4c7393 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -118,7 +118,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
         const targetDoc = this.view0?.props.Document;
         const published = targetDoc && Doc.GetProto(targetDoc)[GoogleRef] !== undefined;
         const animation = this.isAnimatingPulse ? "shadow-pulse 1s linear infinite" : "none";
-        return !targetDoc ? (null) : <Tooltip title={<React.Fragment><div style={{ fontSize: "11px", padding: "2px" }}>{`${published ? "Push" : "Publish"} to Google Docs`}</div></React.Fragment>}>
+        return !targetDoc ? (null) : <Tooltip title={<><div className="dash-tooltip">{`${published ? "Push" : "Publish"} to Google Docs`}</div></>}>
             <div
                 className="documentButtonBar-linker"
                 style={{ animation }}
@@ -148,7 +148,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
         })();
 
         return !targetDoc || !dataDoc || !dataDoc[GoogleRef] ? (null) : <Tooltip
-            title={<React.Fragment><div style={{ fontSize: "11px", padding: "2px" }}>{title}</div></React.Fragment>}>
+            title={<><div className="dash-tooltip">{title}</div></>}>
             <div className="documentButtonBar-linker"
                 style={{ backgroundColor: this.pullColor }}
                 onPointerEnter={action(e => {
@@ -197,7 +197,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
     get pinButton() {
         const targetDoc = this.view0?.props.Document;
         const isPinned = targetDoc && Doc.isDocPinned(targetDoc);
-        return !targetDoc ? (null) : <Tooltip title={<React.Fragment><div style={{ fontSize: "11px", padding: "2px" }}>{Doc.isDocPinned(targetDoc) ? "Unpin from presentation" : "Pin to presentation"}</div></React.Fragment>}>
+        return !targetDoc ? (null) : <Tooltip title={<><div className="dash-tooltip">{Doc.isDocPinned(targetDoc) ? "Unpin from presentation" : "Pin to presentation"}</div></>}>
             <div className="documentButtonBar-linker"
                 style={{ backgroundColor: isPinned ? "black" : "white", color: isPinned ? "white" : "black" }}
                 onClick={e => DockedFrameRenderer.PinDoc(targetDoc, isPinned)}>
@@ -209,7 +209,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
     @computed
     get metadataButton() {
         const view0 = this.view0;
-        return !view0 ? (null) : <Tooltip title={<React.Fragment><div style={{ fontSize: "11px", padding: "2px" }}>Show metadata panel</div></React.Fragment>}>
+        return !view0 ? (null) : <Tooltip title={<><div className="dash-tooltip">Show metadata panel</div></>}>
             <div className="documentButtonBar-linkFlyout">
                 <Flyout anchorPoint={anchorPoints.LEFT_TOP}
                     content={<MetadataEntryMenu docs={this.props.views().filter(dv => dv).map(dv => dv!.props.Document)} suggestWithFunction />  /* tfs: @bcz This might need to be the data document? */}>
@@ -258,7 +258,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
         Array.from(Object.values(Templates.TemplateList)).map(template =>
             templates.set(template, views.reduce((checked, doc) => checked || doc?.props.Document["_show" + template.Name] ? true : false, false as boolean)));
         return !view0 ? (null) :
-            <Tooltip title={<React.Fragment><div style={{ fontSize: "11px", padding: "2px" }}>Tap: Customize layout.  Drag: Create alias</div></React.Fragment>}>
+            <Tooltip title={<><div className="dash-tooltip">Tap: Customize layout.  Drag: Create alias</div></>}>
                 <div className="documentButtonBar-linkFlyout" ref={this._dragRef}>
                     <Flyout anchorPoint={anchorPoints.LEFT_TOP} onOpen={action(() => this._aliasDown = true)} onClose={action(() => this._aliasDown = false)}
                         content={!this._aliasDown ? (null) : <TemplateMenu docViews={views.filter(v => v).map(v => v as DocumentView)} templates={templates} />}>
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 2fd017f7b..376b1d46b 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -81,6 +81,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         return SelectionManager.SelectedDocuments().reduce((bounds, documentView) => {
             if (documentView.props.renderDepth === 0 ||
                 documentView.props.treeViewDoc ||
+                !documentView.ContentDiv ||
                 Doc.AreProtosEqual(documentView.props.Document, Doc.UserDoc())) {
                 return bounds;
             }
@@ -88,7 +89,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
             var [sptX, sptY] = transform.transformPoint(0, 0);
             let [bptX, bptY] = transform.transformPoint(documentView.props.PanelWidth(), documentView.props.PanelHeight());
             if (documentView.props.Document.type === DocumentType.LINK) {
-                const docuBox = documentView.ContentDiv!.getElementsByClassName("linkAnchorBox-cont");
+                const docuBox = documentView.ContentDiv.getElementsByClassName("linkAnchorBox-cont");
                 if (docuBox.length) {
                     const rect = docuBox[0].getBoundingClientRect();
                     sptX = rect.left;
@@ -544,11 +545,11 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         }
         const minimal = bounds.r - bounds.x < 100 ? true : false;
         const maximizeIcon = minimal ? (
-            <Tooltip title={<React.Fragment><div style={{ fontSize: "11px", padding: "2px" }}>Show context menu</div></React.Fragment>} placement="top">
+            <Tooltip title={<><div className="dash-tooltip">Show context menu</div></>} placement="top">
                 <div className="documentDecorations-contextMenu" onPointerDown={this.onSettingsDown}>
                     <FontAwesomeIcon size="lg" icon="cog" />
                 </div></Tooltip>) : (
-                <Tooltip title={<React.Fragment><div style={{ fontSize: "11px", padding: "2px" }}>Iconify</div></React.Fragment>} placement="top">
+                <Tooltip title={<><div className="dash-tooltip">Iconify</div></>} placement="top">
                     <div className="documentDecorations-minimizeButton" onClick={this.onCloseClick}>
                         {/* Currently, this is set to be enabled if there is no ink selected. It might be interesting to think about minimizing ink if it's useful? -syip2*/}
                         <FontAwesomeIcon className="documentdecorations-times" icon={faTimes} size="lg" />
@@ -572,7 +573,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 </div>}
             </> :
             <>
-                {minimal ? (null) : <Tooltip title={<React.Fragment><div style={{ fontSize: "11px", padding: "2px" }}>Show context menu</div></React.Fragment>} placement="top"><div className="documentDecorations-contextMenu" key="menu" onPointerDown={this.onSettingsDown}>
+                {minimal ? (null) : <Tooltip title={<><div className="dash-tooltip">Show context menu</div></>} placement="top"><div className="documentDecorations-contextMenu" key="menu" onPointerDown={this.onSettingsDown}>
                     <FontAwesomeIcon size="lg" icon="cog" />
                 </div></Tooltip>}
                 <div className="documentDecorations-title" key="title" onPointerDown={this.onTitleDown} >
@@ -611,11 +612,11 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                     {maximizeIcon}
                     {titleArea}
                     {SelectionManager.SelectedDocuments().length !== 1 || seldoc.Document.type === DocumentType.INK ? (null) :
-                        <Tooltip title={<React.Fragment><div style={{ fontSize: "11px", padding: "2px" }}>{`${seldoc.finalLayoutKey.includes("icon") ? "De" : ""}Iconify Document`}</div></React.Fragment>} placement="top">
+                        <Tooltip title={<><div className="dash-tooltip">{`${seldoc.finalLayoutKey.includes("icon") ? "De" : ""}Iconify Document`}</div></>} placement="top">
                             <div className="documentDecorations-iconifyButton" onPointerDown={this.onIconifyDown}>
                                 {"_"}
                             </div></Tooltip>}
-                    <Tooltip title={<React.Fragment><div style={{ fontSize: "11px", padding: "2px" }}>Open Document In Tab</div></React.Fragment>} placement="top"><div className="documentDecorations-closeButton" onPointerDown={this.onMaximizeDown}>
+                    <Tooltip title={<><div className="dash-tooltip">Open Document In Tab</div></>} placement="top"><div className="documentDecorations-closeButton" onPointerDown={this.onMaximizeDown}>
                         {SelectionManager.SelectedDocuments().length === 1 ? DocumentDecorations.DocumentIcon(StrCast(seldoc.props.Document.layout, "...")) : "..."}
                     </div></Tooltip>
                     <div id="documentDecorations-rotation" className="documentDecorations-rotation"
@@ -638,7 +639,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                     <div id="documentDecorations-bottomRightResizer" className="documentDecorations-resizer"
                         onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
                     {seldoc.props.renderDepth <= 1 || !seldoc.props.ContainingCollectionView ? (null) :
-                        <Tooltip title={<React.Fragment><div style={{ fontSize: "11px", padding: "2px" }}>tap to select containing document</div></React.Fragment>} placement="top">
+                        <Tooltip title={<><div className="dash-tooltip">tap to select containing document</div></>} placement="top">
                             <div id="documentDecorations-levelSelector" className="documentDecorations-selector"
                                 onPointerDown={this.onSelectorUp} onContextMenu={e => e.preventDefault()}>
                                 <FontAwesomeIcon className="documentdecorations-times" icon={faArrowAltCircleUp} size="lg" />
diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss
index 5b142ffda..e1ddbc533 100644
--- a/src/client/views/MainView.scss
+++ b/src/client/views/MainView.scss
@@ -1,6 +1,10 @@
 @import "globalCssVariables";
 @import "nodeModuleOverrides";
 
+.dash-tooltip {
+    font-size: 11px;
+    padding: 2px;
+}
 
 .mainView-tabButtons {
     position: relative;
diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx
index 319cca70f..407524353 100644
--- a/src/client/views/collections/CollectionLinearView.tsx
+++ b/src/client/views/collections/CollectionLinearView.tsx
@@ -124,7 +124,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
 
         return <div className="collectionLinearView-outer">
             <div className="collectionLinearView" ref={this.createDashEventsTarget} >
-                <Tooltip title={<React.Fragment><div style={{ fontSize: "11px", padding: "2px" }}>{BoolCast(this.props.Document.linearViewIsExpanded) ? "Close menu" : "Open menu"}</div></React.Fragment>} placement="top">
+                <Tooltip title={<><div className="dash-tooltip">{BoolCast(this.props.Document.linearViewIsExpanded) ? "Close menu" : "Open menu"}</div></>} placement="top">
                     {menuOpener}
                 </Tooltip>
                 <input id={`${guid}`} type="checkbox" checked={BoolCast(this.props.Document.linearViewIsExpanded)} ref={this.addMenuToggle}
@@ -176,18 +176,20 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
                 }}
                     onPointerDown={e => e.stopPropagation()} >
                     <span className="bottomPopup-text" >
-                        Creating link from: {DocumentLinksButton.StartLink.title} </span>
+                        Creating link from: {DocumentLinksButton.StartLink.title}
+                    </span>
 
-                    <Tooltip title={<React.Fragment><div style={{ fontSize: "11px", padding: "2px" }}>{LinkDescriptionPopup.showDescriptions ? "Turn off description pop-up" :
-                        "Turn on description pop-up"} </div></React.Fragment>} placement="top">
-                        <span className="bottomPopup-descriptions" onClick={this.changeDescriptionSetting}
-                        > Labels: {LinkDescriptionPopup.showDescriptions ? LinkDescriptionPopup.showDescriptions : "ON"}
+                    <Tooltip title={<><div className="dash-tooltip">{LinkDescriptionPopup.showDescriptions ? "Turn off description pop-up" :
+                        "Turn on description pop-up"} </div></>} placement="top">
+                        <span className="bottomPopup-descriptions" onClick={this.changeDescriptionSetting}>
+                            Labels: {LinkDescriptionPopup.showDescriptions ? LinkDescriptionPopup.showDescriptions : "ON"}
                         </span>
                     </Tooltip>
 
-                    <Tooltip title={<React.Fragment><div style={{ fontSize: "11px", padding: "2px" }}>Exit link clicking mode </div></React.Fragment>} placement="top">
-                        <span className="bottomPopup-exit" onClick={this.exitLongLinks}
-                        >Clear</span>
+                    <Tooltip title={<><div className="dash-tooltip">Exit link clicking mode </div></>} placement="top">
+                        <span className="bottomPopup-exit" onClick={this.exitLongLinks}>
+                            Clear
+                        </span>
                     </Tooltip>
 
                     {/* <FontAwesomeIcon icon="times-circle" size="lg" style={{ color: "red" }}
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 827c3e299..0696b93db 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -38,12 +38,13 @@ export default class CollectionMenu extends AntimodeMenu {
     }
 
     render() {
-        return this.getElement([
-            !this.SelectedCollection ? <></> : <CollectionViewBaseChrome CollectionView={this.SelectedCollection} type={StrCast(this.SelectedCollection.props.Document._viewType) as CollectionViewType} />,
-            <button className="antimodeMenu-button" key="pin menu" title="Pin menu" onClick={this.toggleMenuPin} style={{ backgroundColor: "#121721" }}>
-                <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.1s", transform: `rotate(${this.Pinned ? 45 : 0}deg)` }} />
-            </button>
-        ]);
+        const button = <button className="antimodeMenu-button" key="pin menu" title="Pin menu" onClick={this.toggleMenuPin} style={{ backgroundColor: "#121721" }}>
+            <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.1s", transform: `rotate(${this.Pinned ? 45 : 0}deg)` }} />
+        </button>;
+
+        return this.getElement(!this.SelectedCollection ? [button] :
+            [<CollectionViewBaseChrome key="chrome" CollectionView={this.SelectedCollection} type={StrCast(this.SelectedCollection.props.Document._viewType) as CollectionViewType} />,
+            button]);
     }
 }
 
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index c1a6b5b0d..c1da23470 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -142,6 +142,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
                     const context = Cast(doc.context, Doc, null);
                     if (context && (context.type === DocumentType.VID || context.type === DocumentType.WEB || context.type === DocumentType.PDF || context.type === DocumentType.IMG)) {
                         const pushpin = Docs.Create.FontIconDocument({
+                            title: "pushpin",
                             icon: "map-pin", x: Cast(doc.x, "number", null), y: Cast(doc.y, "number", null), _backgroundColor: "#0000003d", color: "#ACCEF7",
                             _width: 15, _height: 15, _xPadding: 0, isLinkButton: true, displayTimecode: Cast(doc.displayTimecode, "number", null)
                         });
@@ -150,8 +151,6 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
                         Doc.SetInPlace(doc, "annotationOn", undefined, true);
                         Doc.AddDocToList(context, Doc.LayoutFieldKey(context) + "-annotations", pushpin);
                         const pushpinLink = DocUtils.MakeLink({ doc: pushpin }, { doc: doc }, "pushpin", "");
-                        const first = DocListCast(pushpin.links).find(d => d instanceof Doc);
-                        first && (first.hidden = true);
                         doc.displayTimecode = undefined;
                     }
                     doc.context = this.props.Document;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index dea936113..0933d525a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -29,13 +29,13 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
     componentDidMount() {
         this._anchorDisposer = reaction(() => [this.props.A.props.ScreenToLocalTransform(), this.props.B.props.ScreenToLocalTransform(), this.props.A.isSelected() || Doc.IsBrushed(this.props.A.props.Document), this.props.A.isSelected() || Doc.IsBrushed(this.props.A.props.Document)],
             action(() => {
-                if (SnappingManager.GetIsDragging()) return;
+                if (SnappingManager.GetIsDragging() || !this.props.A.ContentDiv || !this.props.B.ContentDiv) return;
                 setTimeout(action(() => this._opacity = 1), 0); // since the render code depends on querying the Dom through getBoudndingClientRect, we need to delay triggering render()
                 setTimeout(action(() => (!this.props.LinkDocs.length || !this.props.LinkDocs[0].linkDisplay) && (this._opacity = 0.05)), 750); // this will unhighlight the link line.
-                const acont = this.props.A.props.Document.type === DocumentType.LINK ? this.props.A.ContentDiv!.getElementsByClassName("linkAnchorBox-cont") : [];
-                const bcont = this.props.B.props.Document.type === DocumentType.LINK ? this.props.B.ContentDiv!.getElementsByClassName("linkAnchorBox-cont") : [];
-                const adiv = (acont.length ? acont[0] : this.props.A.ContentDiv!);
-                const bdiv = (bcont.length ? bcont[0] : this.props.B.ContentDiv!);
+                const acont = this.props.A.props.Document.type === DocumentType.LINK ? this.props.A.ContentDiv.getElementsByClassName("linkAnchorBox-cont") : [];
+                const bcont = this.props.B.props.Document.type === DocumentType.LINK ? this.props.B.ContentDiv.getElementsByClassName("linkAnchorBox-cont") : [];
+                const adiv = (acont.length ? acont[0] : this.props.A.ContentDiv);
+                const bdiv = (bcont.length ? bcont[0] : this.props.B.ContentDiv);
                 const a = adiv.getBoundingClientRect();
                 const b = bdiv.getBoundingClientRect();
                 const abounds = adiv.parentElement!.getBoundingClientRect();
@@ -103,7 +103,7 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
 
 
     render() {
-        if (SnappingManager.GetIsDragging() || !this.props.LinkDocs.length) return null;
+        if (SnappingManager.GetIsDragging() || !this.props.A.ContentDiv || !this.props.B.ContentDiv || !this.props.LinkDocs.length) return (null);
         this.props.A.props.ScreenToLocalTransform().transform(this.props.B.props.ScreenToLocalTransform());
         const acont = this.props.A.ContentDiv!.getElementsByClassName("linkAnchorBox-cont");
         const bcont = this.props.B.ContentDiv!.getElementsByClassName("linkAnchorBox-cont");
diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx
index 1cc981d42..ed2bea722 100644
--- a/src/client/views/linking/LinkEditor.tsx
+++ b/src/client/views/linking/LinkEditor.tsx
@@ -288,7 +288,6 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
 
     @observable description = StrCast(LinkManager.currentLink?.description);
     @observable openDropdown: boolean = false;
-    @observable followBehavior = this.props.linkDoc.follow ? this.props.linkDoc.follow : "Default";
     @observable showInfo: boolean = false;
     @computed get infoIcon() { if (this.showInfo) { return "chevron-up"; } return "chevron-down"; }
     @observable private buttonColor: string = "black";
@@ -364,8 +363,7 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
     @action
     changeFollowBehavior = (follow: string) => {
         this.openDropdown = false;
-        this.followBehavior = follow;
-        this.props.linkDoc.follow = follow;
+        Doc.GetProto(this.props.linkDoc).followLinkLocation = follow;
     }
 
     @computed
@@ -376,7 +374,7 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
             <div className="linkEditor-followingDropdown-dropdown">
                 <div className="linkEditor-followingDropdown-header"
                     onPointerDown={this.changeDropdown}>
-                    {this.followBehavior}
+                    {StrCast(this.props.linkDoc.followLinkLocation, "Default")}
                     <FontAwesomeIcon className="linkEditor-followingDropdown-icon"
                         icon={this.openDropdown ? "chevron-up" : "chevron-down"}
                         size={"lg"} />
@@ -388,11 +386,11 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
                         Default
                         </div>
                     <div className="linkEditor-followingDropdown-option"
-                        onPointerDown={() => this.changeFollowBehavior("Always open in right tab")}>
+                        onPointerDown={() => this.changeFollowBehavior("onRight")}>
                         Always open in right tab
                         </div>
                     <div className="linkEditor-followingDropdown-option"
-                        onPointerDown={() => this.changeFollowBehavior("Always open in new tab")}>
+                        onPointerDown={() => this.changeFollowBehavior("inTab")}>
                         Always open in new tab
                         </div>
                 </div>
@@ -416,7 +414,7 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
         return !destination ? (null) : (
             <div className="linkEditor">
                 <div className="linkEditor-info">
-                    <Tooltip title={<React.Fragment><div style={{ fontSize: "11px", padding: "2px" }}>Return to link menu</div></React.Fragment>} placement="top">
+                    <Tooltip title={<><div className="dash-tooltip">Return to link menu</div></>} placement="top">
                         <button className="linkEditor-button-back"
                             style={{ display: this.props.hideback ? "none" : "" }}
                             onClick={this.props.showLinks}>
@@ -426,7 +424,7 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
                         destination.proto?.title ?? destination.title ?? "untitled"}</b></p>
                     {/* <button className="linkEditor-button" onPointerDown={() => this.deleteLink()} title="Delete link">
                         <FontAwesomeIcon icon="trash" size="sm" /></button> */}
-                    <Tooltip title={<React.Fragment><div style={{ fontSize: "11px", padding: "2px" }}>Show more link information</div></React.Fragment>} placement="top">
+                    <Tooltip title={<><div className="dash-tooltip">Show more link information</div></>} placement="top">
                         <div className="linkEditor-downArrow"><FontAwesomeIcon className="button" icon={this.infoIcon} size="lg" onPointerDown={this.changeInfo} /></div>
                     </Tooltip>
                 </div>
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index 6782f625b..0e847632b 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -16,7 +16,6 @@ import { DocumentView } from '../nodes/DocumentView';
 import { DocumentLinksButton } from '../nodes/DocumentLinksButton';
 import { LinkDocPreview } from '../nodes/LinkDocPreview';
 import { Tooltip } from '@material-ui/core';
-import { RichTextField } from '../../../fields/RichTextField';
 import { DocumentType } from '../../documents/DocumentTypes';
 library.add(faEye, faEdit, faTimes, faArrowRight, faChevronDown, faChevronUp, faPencilAlt, faEyeSlash);
 
@@ -157,14 +156,8 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
         DocumentLinksButton.EditLink = undefined;
         LinkDocPreview.LinkInfo = undefined;
 
-        if (this.props.linkDoc.follow) {
-            if (this.props.linkDoc.follow === "Default") {
-                DocumentManager.Instance.FollowLink(this.props.linkDoc, this.props.sourceDoc, doc => this.props.addDocTab(doc, "onRight"), false);
-            } else if (this.props.linkDoc.follow === "Always open in right tab") {
-                this.props.addDocTab(this.props.destinationDoc, "onRight");
-            } else if (this.props.linkDoc.follow === "Always open in new tab") {
-                this.props.addDocTab(this.props.destinationDoc, "inTab");
-            }
+        if (this.props.linkDoc.followLinkLocation && this.props.linkDoc.followLinkLocation !== "Default") {
+            this.props.addDocTab(this.props.destinationDoc, StrCast(this.props.linkDoc.followLinkLocation));
         } else {
             DocumentManager.Instance.FollowLink(this.props.linkDoc, this.props.sourceDoc, doc => this.props.addDocTab(doc, "onRight"), false);
         }
@@ -221,8 +214,6 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
                 StrCast(this.props.linkDoc.storedText).substr(0, 18)
                 : this.props.linkDoc.storedText : undefined : undefined;
 
-        const showTitle = this.props.linkDoc.hidden ? "Show link" : "Hide link";
-
         return (
             <div className="linkMenu-item">
                 <div className={canExpand ? "linkMenu-item-content expand-three" : "linkMenu-item-content expand-two"}>
@@ -255,16 +246,16 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
                             {canExpand ? <div title="Show more" className="button" onPointerDown={e => this.toggleShowMore(e)}>
                                 <FontAwesomeIcon className="fa-icon" icon={this._showMore ? "chevron-up" : "chevron-down"} size="sm" /></div> : <></>}
 
-                            <Tooltip title={<React.Fragment><div style={{ fontSize: "11px", padding: "2px" }}>{showTitle}</div></React.Fragment>}>
+                            <Tooltip title={<><div className="dash-tooltip">{this.props.linkDoc.hidden ? "Show link" : "Hide link"}</div></>}>
                                 <div className="button" ref={this._editRef} onPointerDown={this.showLink}>
                                     <FontAwesomeIcon className="fa-icon" icon={eyeIcon} size="sm" /></div>
                             </Tooltip>
 
-                            <Tooltip title={<React.Fragment><div style={{ fontSize: "11px", padding: "2px" }}>Edit Link</div></React.Fragment>}>
+                            <Tooltip title={<><div className="dash-tooltip">Edit Link</div></>}>
                                 <div className="button" ref={this._editRef} onPointerDown={this.onEdit}>
                                     <FontAwesomeIcon className="fa-icon" icon="edit" size="sm" /></div>
                             </Tooltip>
-                            <Tooltip title={<React.Fragment><div style={{ fontSize: "11px", padding: "2px" }}>Delete Link</div></React.Fragment>}>
+                            <Tooltip title={<><div className="dash-tooltip">Delete Link</div></>}>
                                 <div className="button" onPointerDown={this.deleteLink}>
                                     <FontAwesomeIcon className="fa-icon" icon="trash" size="sm" /></div>
                             </Tooltip>
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index 01c068966..bbef48e44 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -134,7 +134,6 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
                 if (DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View) {
                     const linkDoc = DocUtils.MakeLink({ doc: DocumentLinksButton.StartLink.props.Document }, { doc: this.props.View.props.Document }, "long drag");
                     LinkManager.currentLink = linkDoc;
-                    linkDoc ? linkDoc.hidden = true : null;
 
                     runInAction(() => {
                         if (linkDoc) {
@@ -213,10 +212,10 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
 
         return (!links.length) && !this.props.AlwaysOn ? (null) :
             this.props.InMenu ?
-                <Tooltip title={<React.Fragment><div style={{ fontSize: "11px", padding: "2px" }}>{title}</div></React.Fragment>}>
+                <Tooltip title={<><div className="dash-tooltip">{title}</div></>}>
                     {linkButton}
                 </Tooltip> : !!!DocumentLinksButton.EditLink ?
-                    <Tooltip title={<React.Fragment><div style={{ fontSize: "11px", padding: "2px" }}>{title}</div></React.Fragment>}>
+                    <Tooltip title={<><div className="dash-tooltip">{title}</div></>}>
                         {linkButton}
                     </Tooltip> :
                     linkButton;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index a6771443a..c0a8b3a59 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -589,16 +589,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
     @undoBatch
     toggleLinkButtonBehavior = (): void => {
+        this.Document.ignoreClick = false;
         if (this.Document.isLinkButton || this.onClickHandler || this.Document.ignoreClick) {
             this.Document.isLinkButton = false;
-            const first = DocListCast(this.Document.links).find(d => d instanceof Doc);
-            first && (first.hidden = false);
-            this.Document.ignoreClick = false;
             this.Document.onClick = this.layoutDoc.onClick = undefined;
         } else {
             this.Document.isLinkButton = true;
-            const first = DocListCast(this.Document.links).find(d => d instanceof Doc);
-            first && (first.hidden = true);
             this.Document.followLinkZoom = false;
             this.Document.followLinkLocation = undefined;
         }
@@ -606,14 +602,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
     @undoBatch
     toggleFollowInPlace = (): void => {
+        this.Document.ignoreClick = false;
+        this.Document.isLinkButton = !this.Document.isLinkButton;
         if (this.Document.isLinkButton) {
-            this.Document.isLinkButton = false;
-            const first = DocListCast(this.Document.links).find(d => d instanceof Doc);
-            first && (first.hidden = false);
-        } else {
-            this.Document.isLinkButton = true;
-            const first = DocListCast(this.Document.links).find(d => d instanceof Doc);
-            first && (first.hidden = true);
             this.Document.followLinkZoom = true;
             this.Document.followLinkLocation = "inPlace";
         }
@@ -621,15 +612,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
     @undoBatch
     toggleFollowOnRight = (): void => {
+        this.Document.ignoreClick = false;
+        this.Document.isLinkButton = !this.Document.isLinkButton;
         if (this.Document.isLinkButton) {
-            this.Document.isLinkButton = false;
-            const first = DocListCast(this.Document.links).find(d => d instanceof Doc);
-            first && (first.hidden = false);
-        } else {
-            this.Document.isLinkButton = true;
             this.Document.followLinkZoom = false;
-            const first = DocListCast(this.Document.links).find(d => d instanceof Doc);
-            first && (first.hidden = true);
             this.Document.followLinkLocation = "onRight";
         }
     }
@@ -643,7 +629,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         }
         const makeLink = action((linkDoc: Doc) => {
             LinkManager.currentLink = linkDoc;
-            linkDoc.hidden = true;
 
             LinkCreatedBox.popupX = de.x;
             LinkCreatedBox.popupY = de.y - 33;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
index c98b5eac1..3687d5513 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -270,14 +270,14 @@ export class FormattedTextBoxComment {
                                         </div>
                                         <div className="wrapper" style={{ float: "right" }}>
 
-                                            <Tooltip title={<React.Fragment><div style={{ fontSize: "11px", padding: "2px" }}>Delete Link</div></React.Fragment>} placement="top">
+                                            <Tooltip title={<><div className="dash-tooltip">Delete Link</div></>} placement="top">
                                                 <div className="FormattedTextBoxComment-button"
                                                     ref={(r) => this._deleteRef = r}>
                                                     <FontAwesomeIcon className="FormattedTextBoxComment-fa-icon" icon="trash" color="white"
                                                         size="sm" /></div>
                                             </Tooltip>
 
-                                            <Tooltip title={<React.Fragment><div style={{ fontSize: "11px", padding: "2px" }}>Follow Link</div></React.Fragment>} placement="top">
+                                            <Tooltip title={<><div className="dash-tooltip">Follow Link</div></>} placement="top">
                                                 <div className="FormattedTextBoxComment-button"
                                                     ref={(r) => this._followRef = r}>
                                                     <FontAwesomeIcon className="FormattedTextBoxComment-fa-icon" icon="arrow-right" color="white"
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index d0e6f3b37..63f6fdc54 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -24,6 +24,7 @@ import "./RichTextMenu.scss";
 import { schema } from "./schema_rts";
 import { TraceMobx } from "../../../../fields/util";
 import { UndoManager } from "../../../util/UndoManager";
+import { Tooltip } from "@material-ui/core";
 const { toggleMark } = require("prosemirror-commands");
 
 library.add(faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faOutdent, faIndent, faHandPointLeft, faHandPointRight, faEyeDropper, faCaretDown, faPalette, faHighlighter, faLink, faPaintRoller);
@@ -357,9 +358,11 @@ export default class RichTextMenu extends AntimodeMenu {
         }
 
         return (
-            <button className={"antimodeMenu-button" + (isActive ? " active" : "")} key={title} title={title} onPointerDown={onClick}>
-                <FontAwesomeIcon icon={faIcon as IconProp} size="lg" />
-            </button>
+            <Tooltip title={<div className="dash-tooltip">{title}</div>} key={title} placement="bottom">
+                <button className={"antimodeMenu-button" + (isActive ? " active" : "")} onPointerDown={onClick}>
+                    <FontAwesomeIcon icon={faIcon as IconProp} size="lg" />
+                </button>
+            </Tooltip>
         );
     }
 
@@ -388,7 +391,9 @@ export default class RichTextMenu extends AntimodeMenu {
                 }
             });
         }
-        return <select onChange={onChange} value={activeOption} key={key}>{items}</select>;
+        return <Tooltip key={key} title={<div className="dash-tooltip">{key}</div>} placement="bottom">
+            <select onChange={onChange} value={activeOption}>{items}</select>
+        </Tooltip>;
     }
 
     createNodesDropdown(activeMap: string, options: { node: NodeType | any | null, title: string, label: string, command: (node: NodeType | any) => void, hidden?: boolean, style?: {} }[], key: string, setter: (val: string) => {}): JSX.Element {
@@ -412,7 +417,10 @@ export default class RichTextMenu extends AntimodeMenu {
                 }
             });
         }
-        return <select value={activeOption} onChange={e => onChange(e.target.value)} key={key}>{items}</select>;
+
+        return <Tooltip key={key} title={<div className="dash-tooltip">{key}</div>} placement="bottom">
+            <select value={activeOption} onChange={e => onChange(e.target.value)}>{items}</select>
+        </Tooltip>;
     }
 
     changeFontSize = (mark: Mark, view: EditorView) => {
@@ -595,10 +603,11 @@ export default class RichTextMenu extends AntimodeMenu {
             label = "No marks are currently stored";
         }
 
-        const button =
-            <button className="antimodeMenu-button" title="" onPointerDown={onBrushClick} style={this.brushMarks?.size > 0 ? { backgroundColor: "121212" } : {}}>
+        const button = <Tooltip title={<div className="dash-tooltip">style brush</div>} placement="bottom">
+            <button className="antimodeMenu-button" onPointerDown={onBrushClick} style={this.brushMarks?.size > 0 ? { backgroundColor: "121212" } : {}}>
                 <FontAwesomeIcon icon="paint-roller" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.1s", transform: `rotate(${this.brushMarks?.size > 0 ? 45 : 0}deg)` }} />
-            </button>;
+            </button>
+        </Tooltip>;
 
         const dropdownContent =
             <div className="dropdown">
@@ -667,11 +676,12 @@ export default class RichTextMenu extends AntimodeMenu {
             self.TextView.EditorView!.focus();
         }
 
-        const button =
-            <button className="antimodeMenu-button color-preview-button" title="" onPointerDown={onColorClick}>
+        const button = <Tooltip title={<div className="dash-tooltip">set font color</div>} placement="bottom">
+            <button className="antimodeMenu-button color-preview-button" onPointerDown={onColorClick}>
                 <FontAwesomeIcon icon="palette" size="lg" />
                 <div className="color-preview" style={{ backgroundColor: this.activeFontColor }}></div>
-            </button>;
+            </button>
+        </Tooltip>;
 
         const dropdownContent =
             <div className="dropdown" >
@@ -720,11 +730,12 @@ export default class RichTextMenu extends AntimodeMenu {
             UndoManager.RunInBatch(() => self.view && self.insertHighlight(self.activeHighlightColor, self.view.state, self.view.dispatch), "rt highlighter");
         }
 
-        const button =
-            <button className="antimodeMenu-button color-preview-button" title="" key="highilghter-button" onPointerDown={onHighlightClick}>
+        const button = <Tooltip title={<div className="dash-tooltip">set highlight color</div>} placement="bottom">
+            <button className="antimodeMenu-button color-preview-button" key="highilghter-button" onPointerDown={onHighlightClick}>
                 <FontAwesomeIcon icon="highlighter" size="lg" />
                 <div className="color-preview" style={{ backgroundColor: this.activeHighlightColor }}></div>
-            </button>;
+            </button>
+        </Tooltip>;
 
         const dropdownContent =
             <div className="dropdown">
@@ -763,7 +774,9 @@ export default class RichTextMenu extends AntimodeMenu {
 
         const link = this.currentLink ? this.currentLink : "";
 
-        const button = <FontAwesomeIcon icon="link" size="lg" />;
+        const button = <Tooltip title={<div className="dash-tooltip">set hyperlink</div>} placement="bottom">
+            <div><FontAwesomeIcon icon="link" size="lg" /> </div>
+        </Tooltip>;
 
         const dropdownContent =
             <div className="dropdown link-menu">
@@ -774,9 +787,7 @@ export default class RichTextMenu extends AntimodeMenu {
                 <button className="remove-button" onPointerDown={e => this.deleteLink()}>Remove link</button>
             </div>;
 
-        return (
-            <ButtonDropdown view={this.view} key={"link button"} button={button} dropdownContent={dropdownContent} openDropdownOnButton={true} />
-        );
+        return <ButtonDropdown view={this.view} key={"link button"} button={button} dropdownContent={dropdownContent} openDropdownOnButton={true} />;
     }
 
     async getTextLinkTargetTitle() {
@@ -935,7 +946,7 @@ export default class RichTextMenu extends AntimodeMenu {
                 {[this.createMarksDropdown(this.activeFontSize, this.fontSizeOptions, "font size", action((val: string) => this.activeFontSize = val)),
                 this.createMarksDropdown(this.activeFontFamily, this.fontFamilyOptions, "font family", action((val: string) => this.activeFontFamily = val)),
                 <div className="richTextMenu-divider" key="divider 4" />,
-                this.createNodesDropdown(this.activeListType, this.listTypeOptions, "nodes", action((val: string) => this.activeListType = val)),
+                this.createNodesDropdown(this.activeListType, this.listTypeOptions, "list type", action((val: string) => this.activeListType = val)),
                 this.createButton("sort-amount-down", "Summarize", undefined, this.insertSummarizer),
                 this.createButton("quote-left", "Blockquote", undefined, this.insertBlockquote),
                 this.createButton("minus", "Horizontal Rule", undefined, this.insertHorizontalRule),
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 4e5fdbfbb..56212a773 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -637,7 +637,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
 
     @action
     onZoomWheel = (e: React.WheelEvent) => {
-        if (this.active()) {
+        if (this.active(true)) {
             e.stopPropagation();
             if (e.ctrlKey) {
                 const curScale = Number(this._pdfViewer.currentScaleValue);
-- 
cgit v1.2.3-70-g09d2


From c63b780580b5cd9c079da361ff65019641782226 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 17 Jul 2020 03:26:29 -0400
Subject: fixed issues with embedded text boxes in text boxes when displayed
 multiple times (alias or minimap).  fixed richtextmenu to always "point to"
 the selected text box.

---
 .../views/collections/CollectionDockingView.tsx      |  2 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx   | 20 ++++++++++++--------
 .../views/nodes/formattedText/RichTextMenu.tsx       |  9 +++++----
 .../views/nodes/formattedText/RichTextSchema.tsx     | 10 +++++++++-
 4 files changed, 27 insertions(+), 14 deletions(-)

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

diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index be1ef56d0..8938541ac 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -916,7 +916,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
                 docFilters={returnEmptyFilter}
                 ContainingCollectionView={undefined}
                 ContainingCollectionDoc={undefined} />
-            {document._viewType === CollectionViewType.Freeform ? this.renderMiniMap() : (null)}
+            {document._viewType === CollectionViewType.Freeform && !this._document?.hideMinimap ? this.renderMiniMap() : (null)}
         </>;
     }
 
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index f87b28c7f..30e1fa9cd 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -232,6 +232,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             const curProto = Cast(Cast(this.dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null);              // the default text inherited from a prototype
             const curLayout = this.rootDoc !== this.layoutDoc ? Cast(this.layoutDoc[this.fieldKey], RichTextField, null) : undefined; // the default text stored in a layout template
             const json = JSON.stringify(state.toJSON());
+            let unchanged = true;
             if (!this.dataDoc[AclSym]) {
                 if (!this._applyingChange && json.replace(/"selection":.*/, "") !== curProto?.Data.replace(/"selection":.*/, "")) {
                     this._applyingChange = true;
@@ -243,21 +244,25 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                             this.dataDoc[this.props.fieldKey] = new RichTextField(json, curText);
                             this.dataDoc[this.props.fieldKey + "-noTemplate"] = (curTemp?.Text || "") !== curText; // mark the data field as being split from the template if it has been edited
                             ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, self: this.rootDoc, text: curText });
+                            unchanged = false;
                         }
                     } else { // if we've deleted all the text in a note driven by a template, then restore the template data
                         this.dataDoc[this.props.fieldKey] = undefined;
                         this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse((curProto || curTemp).Data)));
                         this.dataDoc[this.props.fieldKey + "-noTemplate"] = undefined; // mark the data field as not being split from any template it might have
+                        unchanged = false;
                     }
                     this._applyingChange = false;
+                    if (!unchanged) {
+                        this.updateTitle();
+                        this.tryUpdateHeight();
+                    }
                 }
             } else {
                 const json = JSON.parse(Cast(this.dataDoc[this.fieldKey], RichTextField)?.Data!);
                 json.selection = state.toJSON().selection;
                 this._editorView.updateState(EditorState.fromJSON(this.config, json));
             }
-            this.updateTitle();
-            this.tryUpdateHeight();
         }
     }
 
@@ -1036,7 +1041,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         this._editorView?.destroy();
     }
 
-    static _downEvent: any;
+    _downEvent: any;
     _downX = 0;
     _downY = 0;
     _break = false;
@@ -1056,7 +1061,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         this._downX = e.clientX;
         this._downY = e.clientY;
         this.doLinkOnDeselect();
-        FormattedTextBox._downEvent = true;
+        this._downEvent = true;
         FormattedTextBoxComment.textBox = this;
         if (this.props.onClick && e.button === 0 && !this.props.isSelected(false)) {
             e.preventDefault();
@@ -1072,8 +1077,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     }
 
     onPointerUp = (e: React.PointerEvent): void => {
-        if (!FormattedTextBox._downEvent) return;
-        FormattedTextBox._downEvent = false;
+        if (!this._downEvent) return;
+        this._downEvent = false;
         if (!(e.nativeEvent as any).formattedHandled) {
             const editor = this._editorView!;
             FormattedTextBoxComment.textBox = this;
@@ -1092,7 +1097,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     onDoubleClick = (e: React.MouseEvent): void => {
 
         this.doLinkOnDeselect();
-        FormattedTextBox._downEvent = true;
         FormattedTextBoxComment.textBox = this;
         if (this.props.onClick && e.button === 0 && !this.props.isSelected(false)) {
             e.preventDefault();
@@ -1244,7 +1248,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     richTextMenuPlugin() {
         return new Plugin({
             view(newView) {
-                RichTextMenu.Instance && RichTextMenu.Instance.changeView(newView);
+                RichTextMenu.Instance?.changeView(newView);
                 return RichTextMenu.Instance;
             }
         });
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 63f6fdc54..9890ef2c1 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -156,7 +156,9 @@ export default class RichTextMenu extends AntimodeMenu {
 
     @action
     changeView(view: EditorView) {
-        this.view = view;
+        if ((view as any)?.TextView?.props.isSelected(true)) {
+            this.view = view;
+        }
     }
 
     update(view: EditorView, lastState: EditorState | undefined) {
@@ -165,8 +167,7 @@ export default class RichTextMenu extends AntimodeMenu {
 
     @action
     public async updateFromDash(view: EditorView, lastState: EditorState | undefined, props: any) {
-        if (!view) {
-            console.log("no editor?  why?");
+        if (!view || !(view as any).TextView?.props.isSelected(true)) {
             return;
         }
         this.view = view;
@@ -318,7 +319,7 @@ export default class RichTextMenu extends AntimodeMenu {
     }
 
     destroy() {
-        this.fadeOut(true);
+        !this.TextView.props.isSelected(false) && this.fadeOut(true);
     }
 
     @action
diff --git a/src/client/views/nodes/formattedText/RichTextSchema.tsx b/src/client/views/nodes/formattedText/RichTextSchema.tsx
index a989abd6a..7a50ec3af 100644
--- a/src/client/views/nodes/formattedText/RichTextSchema.tsx
+++ b/src/client/views/nodes/formattedText/RichTextSchema.tsx
@@ -54,6 +54,8 @@ export class DashDocView {
         this._dashSpan.style.height = node.attrs.height;
         this._dashSpan.style.position = "absolute";
         this._dashSpan.style.display = "inline-block";
+        this._dashSpan.style.left = "0";
+        this._dashSpan.style.top = "0";
         this._dashSpan.style.whiteSpace = "normal";
 
         this._dashSpan.onpointerleave = () => {
@@ -160,7 +162,13 @@ export class DashDocView {
 
                 if (node.attrs.width !== dashDoc._width + "px" || node.attrs.height !== dashDoc._height + "px") {
                     try { // bcz: an exception will be thrown if two aliases are open at the same time when a doc view comment is made
-                        view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, { ...node.attrs, width: dashDoc._width + "px", height: dashDoc._height + "px" }));
+                        if (getPos() !== undefined) {
+                            const node = view.state.tr.doc.nodeAt(getPos());
+                            if (node.attrs.width !== dashDoc._width + "px" ||
+                                node.attrs.height !== dashDoc._height + "px") {
+                                view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, { ...node.attrs, width: dashDoc._width + "px", height: dashDoc._height + "px" }));
+                            }
+                        }
                     } catch (e) {
                         console.log(e);
                     }
-- 
cgit v1.2.3-70-g09d2


From 934fd41b249533ced87b5869c116f628f4a013b9 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 17 Jul 2020 11:19:03 -0400
Subject: changed dropping menu items from collectionMenu to create
 fonticonbuttons if all parameters are filled in. . and buttons are placed in
 overlay layer.   added labels to all fonticons usin title + added a tooltip
 field.  turned off autoscroll of freeform view when dragign buttons (which
 will be in the overlay)

---
 src/client/documents/Documents.ts                  |  2 +-
 src/client/util/CurrentUserUtils.ts                | 44 +++++++++++-----------
 src/client/util/DragManager.ts                     |  8 +++-
 src/client/views/collections/CollectionMenu.tsx    | 22 +++++------
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  4 +-
 src/client/views/nodes/FontIconBox.tsx             | 21 ++++++-----
 .../views/nodes/formattedText/RichTextMenu.tsx     |  4 +-
 src/fields/Doc.ts                                  |  9 +++--
 8 files changed, 62 insertions(+), 52 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 5b58aa2e3..8e7d125b0 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -91,7 +91,7 @@ export interface DocumentOptions {
     layoutKey?: string;
     type?: string;
     title?: string;
-    label?: string; // short form of title for use as an icon label
+    toolTip?: string; // tooltip to display on hover
     style?: string;
     page?: number;
     description?: string; // added for links
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index e0cb90b46..892d0ca1d 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -373,7 +373,7 @@ export class CurrentUserUtils {
     }
 
     static creatorBtnDescriptors(doc: Doc): {
-        title: string, label: string, icon: string, drag?: string, ignoreClick?: boolean,
+        title: string, toolTip: string, icon: string, drag?: string, ignoreClick?: boolean,
         click?: string, ischecked?: string, activeInkPen?: Doc, backgroundColor?: string, dragFactory?: Doc
     }[] {
         if (doc.emptyPresentation === undefined) {
@@ -402,27 +402,27 @@ export class CurrentUserUtils {
             this.setupActiveMobileMenu(doc);
         }
         return [
-            { title: "Drag a collection", label: "Col", icon: "folder", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyCollection as Doc },
-            { title: "Drag a web page", label: "Web", icon: "globe-asia", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyWebpage as Doc },
-            { title: "Drag a cat image", label: "Image", icon: "cat", ignoreClick: true, drag: 'Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { _width: 250, _nativeWidth:250, title: "an image of a cat" })' },
-            { title: "Drag a comparison box", label: "Comp", icon: "columns", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyComparison as Doc },
-            { title: "Drag a screengrabber", label: "Grab", icon: "photo-video", ignoreClick: true, drag: 'Docs.Create.ScreenshotDocument("", { _width: 400, _height: 200, title: "screen snapshot" })' },
-            //  { title: "Drag a webcam", label: "Cam", icon: "video", ignoreClick: true, drag: 'Docs.Create.WebCamDocument("", { _width: 400, _height: 400, title: "a test cam" })' },
-            { title: "Drag a audio recorder", label: "Audio", icon: "microphone", ignoreClick: true, drag: `Docs.Create.AudioDocument("${nullAudio}", { _width: 200, title: "ready to record audio" })` },
-            { title: "Drag a button", label: "Button", icon: "bolt", ignoreClick: true, drag: 'Docs.Create.ButtonDocument({ _width: 150, _height: 50, _xPadding:10, _yPadding: 10, title: "Button" })' },
-            { title: "Drag a presentation view", label: "Prezi", icon: "tv", click: 'openOnRight(Doc.UserDoc().activePresentation = getCopy(this.dragFactory, true))', drag: `Doc.UserDoc().activePresentation = getCopy(this.dragFactory,true)`, dragFactory: doc.emptyPresentation as Doc },
-            { title: "Drag a search box", label: "Query", icon: "search", ignoreClick: true, drag: 'Docs.Create.QueryDocument({ _width: 200, title: "an image of a cat" })' },
-            { title: "Drag a scripting box", label: "Script", icon: "terminal", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyScript as Doc },
-            // { title: "Drag an import folder", label: "Load", icon: "cloud-upload-alt", ignoreClick: true, drag: 'Docs.Create.DirectoryImportDocument({ title: "Directory Import", _width: 400, _height: 400 })' },
-            { title: "Drag a mobile view", label: "Phone", icon: "mobile", click: 'openOnRight(Doc.UserDoc().activeMobileMenu)', drag: 'this.dragFactory', dragFactory: doc.activeMobileMenu as Doc },
-            // { title: "Drag an instance of the device collection", label: "Buxton", icon: "globe-asia", ignoreClick: true, drag: 'Docs.Create.Buxton()' },
+            { toolTip: "Drag a collection", title: "Col", icon: "folder", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyCollection as Doc },
+            { toolTip: "Drag a web page", title: "Web", icon: "globe-asia", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyWebpage as Doc },
+            { toolTip: "Drag a cat image", title: "Image", icon: "cat", ignoreClick: true, drag: 'Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { _width: 250, _nativeWidth:250, title: "an image of a cat" })' },
+            { toolTip: "Drag a comparison box", title: "Comp", icon: "columns", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyComparison as Doc },
+            { toolTip: "Drag a screengrabber", title: "Grab", icon: "photo-video", ignoreClick: true, drag: 'Docs.Create.ScreenshotDocument("", { _width: 400, _height: 200, title: "screen snapshot" })' },
+            //  { title: "Drag a webcam", title: "Cam", icon: "video", ignoreClick: true, drag: 'Docs.Create.WebCamDocument("", { _width: 400, _height: 400, title: "a test cam" })' },
+            { toolTip: "Drag a audio recorder", title: "Audio", icon: "microphone", ignoreClick: true, drag: `Docs.Create.AudioDocument("${nullAudio}", { _width: 200, title: "ready to record audio" })` },
+            { toolTip: "Drag a button", title: "Button", icon: "bolt", ignoreClick: true, drag: 'Docs.Create.ButtonDocument({ _width: 150, _height: 50, _xPadding:10, _yPadding: 10, title: "Button" })' },
+            { toolTip: "Drag a presentation view", title: "Prezi", icon: "tv", click: 'openOnRight(Doc.UserDoc().activePresentation = getCopy(this.dragFactory, true))', drag: `Doc.UserDoc().activePresentation = getCopy(this.dragFactory,true)`, dragFactory: doc.emptyPresentation as Doc },
+            { toolTip: "Drag a search box", title: "Query", icon: "search", ignoreClick: true, drag: 'Docs.Create.QueryDocument({ _width: 200, title: "an image of a cat" })' },
+            { toolTip: "Drag a scripting box", title: "Script", icon: "terminal", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyScript as Doc },
+            // { title: "Drag an import folder", title: "Load", icon: "cloud-upload-alt", ignoreClick: true, drag: 'Docs.Create.DirectoryImportDocument({ title: "Directory Import", _width: 400, _height: 400 })' },
+            { toolTip: "Drag a mobile view", title: "Phone", icon: "mobile", click: 'openOnRight(Doc.UserDoc().activeMobileMenu)', drag: 'this.dragFactory', dragFactory: doc.activeMobileMenu as Doc },
+            // { title: "Drag an instance of the device collection", title: "Buxton", icon: "globe-asia", ignoreClick: true, drag: 'Docs.Create.Buxton()' },
             // { title: "use pen", icon: "pen-nib", click: 'activatePen(this.activeInkPen = sameDocs(this.activeInkPen, this) ? undefined : this)', backgroundColor: "blue", ischecked: `sameDocs(this.activeInkPen,  this)`, activeInkPen: doc },
             // { title: "use highlighter", icon: "highlighter", click: 'activateBrush(this.activeInkPen = sameDocs(this.activeInkPen, this) ? undefined : this,20,this.backgroundColor)', backgroundColor: "yellow", ischecked: `sameDocs(this.activeInkPen, this)`, activeInkPen: doc },
             // { title: "use stamp", icon: "stamp", click: 'activateStamp(this.activeInkPen = sameDocs(this.activeInkPen, this) ? undefined : this)', backgroundColor: "orange", ischecked: `sameDocs(this.activeInkPen, this)`, activeInkPen: doc },
             // { title: "use eraser", icon: "eraser", click: 'activateEraser(this.activeInkPen = sameDocs(this.activeInkPen, this) ? undefined : this);', ischecked: `sameDocs(this.activeInkPen, this)`, backgroundColor: "pink", activeInkPen: doc },
             // { title: "use drag", icon: "mouse-pointer", click: 'deactivateInk();this.activeInkPen = this;', ischecked: `sameDocs(this.activeInkPen, this)`, backgroundColor: "white", activeInkPen: doc },
-            { title: "Drag a document previewer", label: "Prev", icon: "expand", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory,true)', dragFactory: doc.emptyDocHolder as Doc },
-            { title: "Toggle a Calculator REPL", label: "repl", icon: "calculator", click: 'addOverlayWindow("ScriptingRepl", { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" })' },
+            { toolTip: "Drag a document previewer", title: "Prev", icon: "expand", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory,true)', dragFactory: doc.emptyDocHolder as Doc },
+            { toolTip: "Toggle a Calculator REPL", title: "repl", icon: "calculator", click: 'addOverlayWindow("ScriptingRepl", { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" })' },
         ];
 
     }
@@ -439,11 +439,11 @@ export class CurrentUserUtils {
             }
         }
         const buttons = CurrentUserUtils.creatorBtnDescriptors(doc).filter(d => !alreadyCreatedButtons?.includes(d.title));
-        const creatorBtns = buttons.map(({ title, label, icon, ignoreClick, drag, click, ischecked, activeInkPen, backgroundColor, dragFactory }) => Docs.Create.FontIconDocument({
+        const creatorBtns = buttons.map(({ title, toolTip, icon, ignoreClick, drag, click, ischecked, activeInkPen, backgroundColor, dragFactory }) => Docs.Create.FontIconDocument({
             _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100,
             icon,
             title,
-            label,
+            toolTip,
             ignoreClick,
             dropAction: "copy",
             onDragStart: drag ? ScriptField.MakeFunction(drag) : undefined,
@@ -735,14 +735,14 @@ export class CurrentUserUtils {
         if (doc["dockedBtn-pen"] === undefined) {
             doc["dockedBtn-pen"] = CurrentUserUtils.ficon({
                 onClick: ScriptField.MakeScript("activatePen(this.activeInkPen = sameDocs(this.activeInkPen, this) ? undefined : this)"),
-                author: "systemTemplates", title: "ink mode", icon: "pen-nib", ischecked: ComputedField.MakeFunction(`sameDocs(this.activeInkPen,  this)`), activeInkPen: doc
+                author: "systemTemplates", toolTip: "open drawing tools", title: "draw", icon: "pen-nib", ischecked: ComputedField.MakeFunction(`sameDocs(this.activeInkPen,  this)`), activeInkPen: doc
             });
         }
         if (doc["dockedBtn-undo"] === undefined) {
-            doc["dockedBtn-undo"] = CurrentUserUtils.ficon({ onClick: ScriptField.MakeScript("undo()"), title: "undo button", icon: "undo-alt" });
+            doc["dockedBtn-undo"] = CurrentUserUtils.ficon({ onClick: ScriptField.MakeScript("undo()"), toolTip: "click to undo", title: "undo", icon: "undo-alt" });
         }
         if (doc["dockedBtn-redo"] === undefined) {
-            doc["dockedBtn-redo"] = CurrentUserUtils.ficon({ onClick: ScriptField.MakeScript("redo()"), title: "redo button", icon: "redo-alt" });
+            doc["dockedBtn-redo"] = CurrentUserUtils.ficon({ onClick: ScriptField.MakeScript("redo()"), toolTip: "click to redo", title: "redo", icon: "redo-alt" });
         }
         if (doc.dockedBtns === undefined) {
             doc.dockedBtns = CurrentUserUtils.blist({ title: "docked buttons", ignoreClick: true }, [doc["dockedBtn-undo"] as Doc, doc["dockedBtn-redo"] as Doc, doc["dockedBtn-pen"] as Doc]);
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 5f34509a1..6a3108157 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -83,6 +83,7 @@ export namespace DragManager {
         hideSource?: boolean;  // hide source document during drag
         offsetX?: number;      // offset of top left of source drag visual from cursor
         offsetY?: number;
+        noAutoscroll?: boolean;
     }
 
     // event called when the drag operation results in a drop action
@@ -225,13 +226,16 @@ export namespace DragManager {
     // drag a button template and drop a new button 
     export function StartButtonDrag(eles: HTMLElement[], script: string, title: string, vars: { [name: string]: Field }, params: string[], initialize: (button: Doc) => void, downX: number, downY: number, options?: DragOptions) {
         const finishDrag = (e: DragCompleteEvent) => {
-            const bd = Docs.Create.ButtonDocument({ _width: 150, _height: 50, title, onClick: ScriptField.MakeScript(script) });
+            const bd = params.length > Object.keys(vars).length ?
+                Docs.Create.ButtonDocument({ toolTip: title, z: 1, _width: 150, _height: 50, title, onClick: ScriptField.MakeScript(script) }) :
+                Docs.Create.FontIconDocument({ toolTip: title, z: 1, _nativeWidth: 30, _nativeHeight: 30, _width: 30, _height: 30, title, onClick: ScriptField.MakeScript(script) });
             params.map(p => Object.keys(vars).indexOf(p) !== -1 && (Doc.GetProto(bd)[p] = new PrefetchProxy(vars[p] as Doc))); // copy all "captured" arguments into document parameterfields
             initialize?.(bd);
             Doc.GetProto(bd)["onClick-paramFieldKeys"] = new List<string>(params);
             e.docDragData && (e.docDragData.droppedDocuments = [bd]);
             return e;
         };
+        options && (options.noAutoscroll = true);
         StartDrag(eles, new DragManager.DocumentDragData([]), downX, downY, options, finishDrag);
     }
 
@@ -431,7 +435,7 @@ export namespace DragManager {
 
             const complete = new DragCompleteEvent(false, dragData);
 
-            if (target) {
+            if (target && !options?.noAutoscroll) {
                 target.dispatchEvent(
                     new CustomEvent<React.DragEvent>("dashDragging", {
                         bubbles: true,
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index cdd653823..84e5d56cb 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -62,37 +62,37 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp
 
     get target() { return this.props.CollectionView.props.Document; }
     _templateCommand = {
-        params: ["target", "source"], title: "=> item view",
-        script: "this.target.childLayout = getDocTemplate(this.source?.[0])",
-        immediate: undoBatch((source: Doc[]) => source.length && (this.target.childLayout = Doc.getDocTemplate(source?.[0]))),
+        params: ["target", "source"], title: "item view",
+        script: "this.target.childLayoutTemplate = getDocTemplate(this.source?.[0])",
+        immediate: undoBatch((source: Doc[]) => source.length && (this.target.childLayoutTemplate = Doc.getDocTemplate(source?.[0]))),
         initialize: emptyFunction,
     };
     _narrativeCommand = {
-        params: ["target", "source"], title: "=> child click view",
+        params: ["target", "source"], title: "child click view",
         script: "this.target.childClickedOpenTemplateView = getDocTemplate(this.source?.[0])",
         immediate: undoBatch((source: Doc[]) => source.length && (this.target.childClickedOpenTemplateView = Doc.getDocTemplate(source?.[0]))),
         initialize: emptyFunction,
     };
     _contentCommand = {
-        params: ["target", "source"], title: "=> clear content",
+        params: ["target", "source"], title: "clear content",
         script: "getProto(this.target).data = copyField(this.source);",
         immediate: undoBatch((source: Doc[]) => Doc.GetProto(this.target).data = new List<Doc>(source)), // Doc.aliasDocs(source),
         initialize: emptyFunction,
     };
     _viewCommand = {
-        params: ["target"], title: "=> reset view",
-        script: "this.target._panX = this.restoredPanX; this.target._panY = this.restoredPanY; this.target.scale = this.restoredScale;",
-        immediate: undoBatch((source: Doc[]) => { this.target._panX = 0; this.target._panY = 0; this.target.scale = 1; }),
-        initialize: (button: Doc) => { button.restoredPanX = this.target._panX; button.restoredPanY = this.target._panY; button.restoredScale = this.target.scale; },
+        params: ["target"], title: "bookmark view",
+        script: "this.target._panX = this['target-panX']; this.target._panY = this['target-panY']; this.target._viewScale = this['target-viewScale'];",
+        immediate: undoBatch((source: Doc[]) => { this.target._panX = 0; this.target._panY = 0; this.target._viewScale = 1; }),
+        initialize: (button: Doc) => { button['target-panX'] = this.target._panX; button['target-panY'] = this.target._panY; button['target-viewScale'] = this.target._viewScale; },
     };
     _clusterCommand = {
-        params: ["target"], title: "=> fit content",
+        params: ["target"], title: "fit content",
         script: "this.target._fitToBox = !this.target._fitToBox;",
         immediate: undoBatch((source: Doc[]) => this.target._fitToBox = !this.target._fitToBox),
         initialize: emptyFunction
     };
     _fitContentCommand = {
-        params: ["target"], title: "=> toggle clusters",
+        params: ["target"], title: "toggle clusters",
         script: "this.target.useClusters = !this.target.useClusters;",
         immediate: undoBatch((source: Doc[]) => this.target.useClusters = !this.target.useClusters),
         initialize: emptyFunction
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 922cb17fe..e119910bd 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -953,8 +953,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             DataDoc: childData,
             Document: childLayout,
             LibraryPath: this.libraryPath,
-            LayoutTemplate: this.props.ChildLayoutTemplate,
-            LayoutTemplateString: this.props.ChildLayoutString,
+            LayoutTemplate: childLayout.z ? undefined : this.props.ChildLayoutTemplate,
+            LayoutTemplateString: childLayout.z ? undefined : this.props.ChildLayoutString,
             FreezeDimensions: this.props.freezeChildDimensions,
             layoutKey: undefined,
             setupDragLines: this.setupDragLines,
diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx
index 86e9a4527..a4d16fe0e 100644
--- a/src/client/views/nodes/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox.tsx
@@ -11,6 +11,7 @@ import { runInAction, observable, reaction, IReactionDisposer } from 'mobx';
 import { Doc } from '../../../fields/Doc';
 import { ContextMenu } from '../ContextMenu';
 import { ScriptField } from '../../../fields/ScriptField';
+import { Tooltip } from '@material-ui/core';
 const FontIconSchema = createSchema({
     icon: "string"
 });
@@ -60,14 +61,16 @@ export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>(
     render() {
         const referenceDoc = (this.layoutDoc.dragFactory instanceof Doc ? this.layoutDoc.dragFactory : this.layoutDoc);
         const refLayout = Doc.Layout(referenceDoc);
-        return <button className="fontIconBox-outerDiv" title={StrCast(this.layoutDoc.title)} ref={this._ref} onContextMenu={this.specificContextMenu}
-            style={{
-                padding: Cast(this.layoutDoc._xPadding, "number", null),
-                background: StrCast(refLayout._backgroundColor, StrCast(refLayout.backgroundColor)),
-                boxShadow: this.layoutDoc.ischecked ? `4px 4px 12px black` : undefined
-            }}>
-            <FontAwesomeIcon className="fontIconBox-icon" icon={this.dataDoc.icon as any} color={StrCast(this.layoutDoc.color, this._foregroundColor)} size="sm" />
-            {!this.rootDoc.label ? (null) : <div className="fontIconBox-label"> {StrCast(this.rootDoc.label).substring(0, 6)} </div>}
-        </button>;
+        return <Tooltip title={<div className="dash-tooltip">{StrCast(this.layoutDoc.toolTip)}</div>}>
+            <button className="fontIconBox-outerDiv" ref={this._ref} onContextMenu={this.specificContextMenu}
+                style={{
+                    padding: Cast(this.layoutDoc._xPadding, "number", null),
+                    background: StrCast(refLayout._backgroundColor, StrCast(refLayout.backgroundColor)),
+                    boxShadow: this.layoutDoc.ischecked ? `4px 4px 12px black` : undefined
+                }}>
+                <FontAwesomeIcon className="fontIconBox-icon" icon={this.dataDoc.icon as any} color={StrCast(this.layoutDoc.color, this._foregroundColor)} size="sm" />
+                {!this.rootDoc.title ? (null) : <div className="fontIconBox-label"> {StrCast(this.rootDoc.title).substring(0, 6)} </div>}
+            </button>
+        </Tooltip>;
     }
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 9890ef2c1..3c7c58126 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -319,7 +319,7 @@ export default class RichTextMenu extends AntimodeMenu {
     }
 
     destroy() {
-        !this.TextView.props.isSelected(false) && this.fadeOut(true);
+        !this.TextView?.props.isSelected(false) && this.fadeOut(true);
     }
 
     @action
@@ -657,7 +657,7 @@ export default class RichTextMenu extends AntimodeMenu {
 
     @action toggleColorDropdown() { this.showColorDropdown = !this.showColorDropdown; }
     @action setActiveColor(color: string) { this.activeFontColor = color; }
-    get TextView() { return (this.view as any).TextView as FormattedTextBox; }
+    get TextView() { return (this.view as any)?.TextView as FormattedTextBox; }
 
     createColorButton() {
         const self = this;
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 7aa1d528d..3ad9f4e41 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -899,9 +899,12 @@ export namespace Doc {
     }
 
     export function getDocTemplate(doc?: Doc) {
-        return doc?.isTemplateDoc ? doc :
-            Cast(doc?.dragFactory, Doc, null)?.isTemplateDoc ? doc?.dragFactory :
-                Cast(doc?.layout, Doc, null)?.isTemplateDoc ? doc?.layout : undefined;
+        return !doc ? undefined :
+            doc.isTemplateDoc ? doc :
+                Cast(doc.dragFactory, Doc, null)?.isTemplateDoc ? doc.dragFactory :
+                    Cast(Doc.Layout(doc), Doc, null)?.isTemplateDoc ?
+                        (Cast(Doc.Layout(doc), Doc, null).resolvedDataDoc ? Doc.Layout(doc).proto : Doc.Layout(doc)) :
+                        undefined;
     }
 
     export function matchFieldValue(doc: Doc, key: string, value: any): boolean {
-- 
cgit v1.2.3-70-g09d2


From 9aad53f026f463d15769dcfed47d6ce4df61e143 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 18 Jul 2020 17:47:32 -0400
Subject: from last

---
 src/client/views/nodes/formattedText/RichTextMenu.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

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

diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 3c7c58126..2e0b0e659 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -319,7 +319,7 @@ export default class RichTextMenu extends AntimodeMenu {
     }
 
     destroy() {
-        !this.TextView?.props.isSelected(false) && this.fadeOut(true);
+        !this.TextView?.props.isSelected(true) && this.fadeOut(true);
     }
 
     @action
-- 
cgit v1.2.3-70-g09d2


From 828a28e112db3357f52d3923987070129fd64b21 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 22 Jul 2020 11:33:28 -0400
Subject: restored download document.  fixed clone() to clone to be async and
 handles lists properly.

---
 src/client/util/CurrentUserUtils.ts                |  6 +++++
 src/client/util/LinkManager.ts                     | 12 ++-------
 src/client/util/Scripting.ts                       | 10 +++-----
 src/client/views/GlobalKeyHandler.ts               |  4 +--
 src/client/views/PreviewCursor.tsx                 |  4 +--
 .../collections/collectionFreeForm/MarqueeView.tsx |  8 +++---
 src/client/views/linking/LinkEditor.tsx            |  4 +--
 src/client/views/linking/LinkMenuItem.tsx          |  3 ++-
 src/client/views/nodes/DocumentView.tsx            | 14 ++++------
 src/client/views/nodes/LinkDocPreview.tsx          |  9 +------
 .../formattedText/FormattedTextBoxComment.tsx      |  2 ++
 .../views/nodes/formattedText/RichTextMenu.tsx     |  4 ++-
 src/fields/Doc.ts                                  | 30 +++++++++++-----------
 src/server/ApiManagers/DownloadManager.ts          | 12 +++------
 src/server/ApiManagers/UploadManager.ts            | 14 +++-------
 15 files changed, 56 insertions(+), 80 deletions(-)

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

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 03a75381a..23b8f09de 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -22,6 +22,7 @@ import { DocumentType } from "../documents/DocumentTypes";
 import { SchemaHeaderField } from "../../fields/SchemaHeaderField";
 import { DimUnit } from "../views/collections/collectionMulticolumn/CollectionMulticolumnView";
 import { LabelBox } from "../views/nodes/LabelBox";
+import { LinkManager } from "./LinkManager";
 
 export class CurrentUserUtils {
     private static curr_id: string;
@@ -888,3 +889,8 @@ export class CurrentUserUtils {
 
 Scripting.addGlobal(function createNewWorkspace() { return MainView.Instance.createNewWorkspace(); },
     "creates a new workspace when called");
+
+Scripting.addGlobal(function links(doc: any) { return new List(LinkManager.Instance.getAllRelatedLinks(doc)); },
+    "returns all the links to the document or its annotations", "(doc: any)");
+Scripting.addGlobal(function directLinks(doc: any) { return new List(LinkManager.Instance.getAllDirectLinks(doc)); },
+    "returns all the links directly to the document", "(doc: any)");
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index 974744344..223f0e7ef 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -2,8 +2,6 @@ import { Doc, DocListCast, Opt } from "../../fields/Doc";
 import { List } from "../../fields/List";
 import { listSpec } from "../../fields/Schema";
 import { Cast, StrCast } from "../../fields/Types";
-import { Scripting } from "./Scripting";
-import { undoBatch } from "./UndoManager";
 
 /* 
  * link doc: 
@@ -25,12 +23,12 @@ export class LinkManager {
 
     private static _instance: LinkManager;
 
-
     public static currentLink: Opt<Doc>;
 
     public static get Instance(): LinkManager {
         return this._instance || (this._instance = new this());
     }
+
     private constructor() {
     }
 
@@ -53,7 +51,6 @@ export class LinkManager {
         return false;
     }
 
-    @undoBatch
     public deleteLink(linkDoc: Doc): boolean {
         if (LinkManager.Instance.LinkManagerDoc && linkDoc instanceof Doc) {
             Doc.RemoveDocFromList(LinkManager.Instance.LinkManagerDoc, "data", linkDoc);
@@ -210,9 +207,4 @@ export class LinkManager {
         if (Doc.AreProtosEqual(anchor, a2)) return a1;
         if (Doc.AreProtosEqual(anchor, linkDoc)) return linkDoc;
     }
-}
-
-Scripting.addGlobal(function links(doc: any) { return new List(LinkManager.Instance.getAllRelatedLinks(doc)); },
-    "returns all the links to the document or its annotations", "(doc: any)");
-Scripting.addGlobal(function directLinks(doc: any) { return new List(LinkManager.Instance.getAllDirectLinks(doc)); },
-    "returns all the links directly to the document", "(doc: any)");
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
index e6cf50de3..f1e6155d2 100644
--- a/src/client/util/Scripting.ts
+++ b/src/client/util/Scripting.ts
@@ -10,8 +10,6 @@ export { ts };
 // @ts-ignore
 import * as typescriptlib from '!!raw-loader!./type_decls.d';
 import { Doc, Field } from '../../fields/Doc';
-import { Cast } from "../../fields/Types";
-import { listSpec } from "../../fields/Schema";
 
 export interface ScriptSucccess {
     success: true;
@@ -95,10 +93,10 @@ export namespace Scripting {
     export function removeGlobal(name: string) {
         if (getGlobals().includes(name)) {
             delete _scriptingGlobals[name];
-            if (_scriptingDescriptions[name]){
+            if (_scriptingDescriptions[name]) {
                 delete _scriptingDescriptions[name];
             }
-            if (_scriptingParams[name]){
+            if (_scriptingParams[name]) {
                 delete _scriptingParams[name];
             }
             return true;
@@ -123,11 +121,11 @@ export namespace Scripting {
         return _scriptingGlobals;
     }
 
-    export function getDescriptions(){
+    export function getDescriptions() {
         return _scriptingDescriptions;
     }
 
-    export function getParameters(){
+    export function getParameters() {
         return _scriptingParams;
     }
 }
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 4dfa7aec8..b63537b5f 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -309,13 +309,13 @@ export default class KeyManager {
                 const targetDataDoc = Doc.GetProto(first.props.Document);
                 const fieldKey = Doc.LayoutFieldKey(first.props.Document);
                 const docList = DocListCast(targetDataDoc[fieldKey]);
-                docids.map((did, i) => i && DocServer.GetRefField(did).then(doc => {
+                docids.map((did, i) => i && DocServer.GetRefField(did).then(async doc => {
                     count++;
                     if (doc instanceof Doc) {
                         list.push(doc);
                     }
                     if (count === docids.length) {
-                        const added = list.filter(d => !docList.includes(d)).map(d => clone ? Doc.MakeClone(d) : d);
+                        const added = await Promise.all(list.filter(d => !docList.includes(d)).map(async d => clone ? await Doc.MakeClone(d) : d));
                         if (added.length) {
                             added.map(doc => doc.context = targetDataDoc);
                             undoBatch(() => {
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index 6583589f3..2d51403d7 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -69,11 +69,11 @@ export class PreviewCursor extends React.Component<{}> {
                     const list: Doc[] = [];
 
                     let first: Doc | undefined;
-                    docids.map((did, i) => i && DocServer.GetRefField(did).then(doc => {
+                    docids.map((did, i) => i && DocServer.GetRefField(did).then(async doc => {
                         count++;
                         if (doc instanceof Doc) {
                             i === 1 && (first = doc);
-                            const alias = clone ? Doc.MakeClone(doc) : doc;
+                            const alias = clone ? await Doc.MakeClone(doc) : doc;
                             const deltaX = NumCast(doc.x) - NumCast(first!.x) - ptx;
                             const deltaY = NumCast(doc.y) - NumCast(first!.y) - pty;
                             alias.x = newPoint[0] + deltaX;
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 6db8c8992..84719b2c9 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -189,8 +189,8 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
     onPointerDown = (e: React.PointerEvent): void => {
         this._downX = this._lastX = e.clientX;
         this._downY = this._lastY = e.clientY;
-        if (!(e as any).marqueeHit) {
-            (e as any).marqueeHit = true;
+        if (!(e.nativeEvent as any).marqueeHit) {
+            (e.nativeEvent as any).marqueeHit = true;
             // allow marquee if right click OR alt+left click OR space bar + left click
             if (e.button === 2 || (e.button === 0 && (e.altKey || (MarqueeView.DragMarquee && this.props.active(true))))) {
                 // if (e.altKey || (MarqueeView.DragMarquee && this.props.active(true))) {
@@ -291,8 +291,8 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         if (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD &&
             Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
             if (Doc.GetSelectedTool() === InkTool.None) {
-                if (!(e as any).marqueeHit) {
-                    (e as any).marqueeHit = true;
+                if (!(e.nativeEvent as any).marqueeHit) {
+                    (e.nativeEvent as any).marqueeHit = true;
                     !(e.nativeEvent as any).formattedHandled && this.setPreviewCursor(e.clientX, e.clientY, false);
                 }
             }
diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx
index ed2bea722..04329182e 100644
--- a/src/client/views/linking/LinkEditor.tsx
+++ b/src/client/views/linking/LinkEditor.tsx
@@ -14,6 +14,7 @@ import { DocumentLinksButton } from "../nodes/DocumentLinksButton";
 import { EditableView } from "../EditableView";
 import { RefObject } from "react";
 import { Tooltip } from "@material-ui/core";
+import { undoBatch } from "../../util/UndoManager";
 
 library.add(faArrowLeft, faEllipsisV, faTable, faTrash, faCog, faExchangeAlt, faTimes, faPlus);
 
@@ -295,6 +296,7 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
 
     //@observable description = this.props.linkDoc.description ? StrCast(this.props.linkDoc.description) : "DESCRIPTION";
 
+    @undoBatch
     @action
     deleteLink = (): void => {
         LinkManager.Instance.deleteLink(this.props.linkDoc);
@@ -422,8 +424,6 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
                     </Tooltip>
                     <p className="linkEditor-linkedTo">Editing Link to: <b>{
                         destination.proto?.title ?? destination.title ?? "untitled"}</b></p>
-                    {/* <button className="linkEditor-button" onPointerDown={() => this.deleteLink()} title="Delete link">
-                        <FontAwesomeIcon icon="trash" size="sm" /></button> */}
                     <Tooltip title={<><div className="dash-tooltip">Show more link information</div></>} placement="top">
                         <div className="linkEditor-downArrow"><FontAwesomeIcon className="button" icon={this.infoIcon} size="lg" onPointerDown={this.changeInfo} /></div>
                     </Tooltip>
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index 0e847632b..d8ba39f09 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -17,6 +17,7 @@ import { DocumentLinksButton } from '../nodes/DocumentLinksButton';
 import { LinkDocPreview } from '../nodes/LinkDocPreview';
 import { Tooltip } from '@material-ui/core';
 import { DocumentType } from '../../documents/DocumentTypes';
+import { undoBatch } from '../../util/UndoManager';
 library.add(faEye, faEdit, faTimes, faArrowRight, faChevronDown, faChevronUp, faPencilAlt, faEyeSlash);
 
 
@@ -163,10 +164,10 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
         }
     }
 
+    @undoBatch
     @action
     deleteLink = (): void => {
         LinkManager.Instance.deleteLink(this.props.linkDoc);
-        //this.props.showLinks();
         LinkDocPreview.LinkInfo = undefined;
         DocumentLinksButton.EditLink = undefined;
     }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 0b5bd707b..12d9890c9 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -794,16 +794,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             }
             moreItems.push({
                 description: "Download document", icon: "download", event: async () => {
-                    const response = await rp.get(Utils.CorsProxy("http://localhost:8983/solr/dash/select"), {
-                        qs: { q: 'world', fq: 'NOT baseProto_b:true AND NOT deleted:true', start: '0', rows: '100', hl: true, 'hl.fl': '*' }
-                    });
-                    console.log(response ? JSON.parse(response) : undefined);
+                    const a = document.createElement("a");
+                    const url = Utils.prepend(`/downloadId/${this.props.Document[Id]}`);
+                    a.href = url;
+                    a.download = `DocExport-${this.props.Document[Id]}.zip`;
+                    a.click();
                 }
-                // const a = document.createElement("a");
-                // const url = Utils.prepend(`/downloadId/${this.props.Document[Id]}`);
-                // a.href = url;
-                // a.download = `DocExport-${this.props.Document[Id]}.zip`;
-                // a.click();
             });
             moreItems.push({ description: "Copy ID", event: () => Utils.CopyText(Utils.prepend("/doc/" + this.props.Document[Id])), icon: "fingerprint" });
         }
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index 1caa82380..ebb916307 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -15,6 +15,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { LinkManager } from '../../util/LinkManager';
 import { DocumentLinksButton } from './DocumentLinksButton';
 import { ContextMenu } from '../ContextMenu';
+import { undoBatch } from '../../util/UndoManager';
 
 interface Props {
     linkDoc?: Doc;
@@ -31,14 +32,6 @@ export class LinkDocPreview extends React.Component<Props> {
     @observable _toolTipText = "";
     _editRef = React.createRef<HTMLDivElement>();
 
-    @action
-    deleteLink = (): void => {
-        this.props.linkDoc ? LinkManager.Instance.deleteLink(this.props.linkDoc) : null;
-        //this.props.showLinks();
-        LinkDocPreview.LinkInfo = undefined;
-        DocumentLinksButton.EditLink = undefined;
-    }
-
     @action
     onContextMenu = (e: React.MouseEvent) => {
         DocumentLinksButton.EditLink = undefined;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
index 3687d5513..6f3984f39 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -22,6 +22,7 @@ import { LinkManager } from "../../../util/LinkManager";
 import { LinkDocPreview } from "../LinkDocPreview";
 import { DocumentLinksButton } from "../DocumentLinksButton";
 import { Tooltip } from "@material-ui/core";
+import { undoBatch } from "../../../util/UndoManager";
 
 export let formattedTextBoxCommentPlugin = new Plugin({
     view(editorView) { return new FormattedTextBoxComment(editorView); }
@@ -143,6 +144,7 @@ export class FormattedTextBoxComment {
         }
     }
 
+    @undoBatch
     @action
     deleteLink = () => {
         FormattedTextBoxComment.linkDoc ? LinkManager.Instance.deleteLink(FormattedTextBoxComment.linkDoc) : null;
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 2e0b0e659..47a4911b8 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -23,7 +23,7 @@ import { updateBullets } from "./ProsemirrorExampleTransfer";
 import "./RichTextMenu.scss";
 import { schema } from "./schema_rts";
 import { TraceMobx } from "../../../../fields/util";
-import { UndoManager } from "../../../util/UndoManager";
+import { UndoManager, undoBatch } from "../../../util/UndoManager";
 import { Tooltip } from "@material-ui/core";
 const { toggleMark } = require("prosemirror-commands");
 
@@ -831,6 +831,8 @@ export default class RichTextMenu extends AntimodeMenu {
         ((this.view as any)?.TextView as FormattedTextBox).makeLinkToSelection("", target, "onRight", "", target);
     }
 
+    @undoBatch
+    @action
     deleteLink = () => {
         if (this.view) {
             const link = this.view.state.selection.$from.nodeAfter?.marks.find(m => m.type === this.view!.state.schema.marks.linkAnchor);
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 2f3b7025e..16ade5912 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -483,25 +483,25 @@ export namespace Doc {
         return alias;
     }
 
-
-
-    export function makeClone(doc: Doc, cloneMap: Map<string, Doc>, rtfs: { copy: Doc, key: string, field: RichTextField }[]): Doc {
+    export async function makeClone(doc: Doc, cloneMap: Map<string, Doc>, rtfs: { copy: Doc, key: string, field: RichTextField }[], exclusions: string[]): Promise<Doc> {
         if (Doc.IsBaseProto(doc)) return doc;
         if (cloneMap.get(doc[Id])) return cloneMap.get(doc[Id])!;
         const copy = new Doc(undefined, true);
         cloneMap.set(doc[Id], copy);
         if (LinkManager.Instance.getAllLinks().includes(doc) && LinkManager.Instance.getAllLinks().indexOf(copy) === -1) LinkManager.Instance.addLink(copy);
-        const exclude = Cast(doc.excludeFields, listSpec("string"), []);
-        Object.keys(doc).forEach(key => {
-            if (exclude.includes(key)) return;
+        const filter = Cast(doc.cloneFieldFilter, listSpec("string"), exclusions);
+        Object.keys(doc).forEach(async key => {
+            if (filter.includes(key)) return;
             const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
             const field = ProxyField.WithoutProxy(() => doc[key]);
-            const copyObjectField = (field: 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.makeClone(d as Doc, cloneMap, rtfs)));
+            const copyObjectField = async (field: ObjectField) => {
+                const list = await Cast(doc[key], listSpec(Doc));
+                const docs = list && (await DocListCastAsync(list))?.filter(d => d instanceof Doc);
+                if (docs !== undefined && docs.length) {
+                    const clones = docs.map(async d => await Doc.makeClone(d as Doc, cloneMap, rtfs, exclusions));
+                    copy[key] = new List<Doc>(await Promise.all(clones));
                 } else if (doc[key] instanceof Doc) {
-                    copy[key] = key.includes("layout[") ? undefined : Doc.makeClone(doc[key] as Doc, cloneMap, rtfs); // reference documents except copy documents that are expanded teplate fields 
+                    copy[key] = key.includes("layout[") ? undefined : key.startsWith("layout") ? doc[key] as Doc : await Doc.makeClone(doc[key] as Doc, cloneMap, rtfs, exclusions); // reference documents except copy documents that are expanded teplate fields 
                 } else {
                     copy[key] = ObjectField.MakeCopy(field);
                     if (field instanceof RichTextField) {
@@ -513,7 +513,7 @@ export namespace Doc {
             };
             if (key === "proto") {
                 if (doc[key] instanceof Doc) {
-                    copy[key] = Doc.makeClone(doc[key]!, cloneMap, rtfs);
+                    copy[key] = await Doc.makeClone(doc[key]!, cloneMap, rtfs, exclusions);
                 }
             } else {
                 if (field instanceof RefField) {
@@ -535,10 +535,10 @@ export namespace Doc {
         cloneMap.set(doc[Id], copy);
         return copy;
     }
-    export function MakeClone(doc: Doc): Doc {
+    export async function MakeClone(doc: Doc): Promise<Doc> {
         const cloneMap = new Map<string, Doc>();
         const rtfMap: { copy: Doc, key: string, field: RichTextField }[] = [];
-        const copy = Doc.makeClone(doc, cloneMap, rtfMap);
+        const copy = await Doc.makeClone(doc, cloneMap, rtfMap, ["context", "annotationOn", "cloneOf"]);
         rtfMap.map(({ copy, key, field }) => {
             const replacer = (match: any, attr: string, id: string, offset: any, string: any) => {
                 const mapped = cloneMap.get(id);
@@ -657,7 +657,7 @@ export namespace Doc {
 
     export function MakeCopy(doc: Doc, copyProto: boolean = false, copyProtoId?: string): Doc {
         const copy = new Doc(copyProtoId, true);
-        const exclude = Cast(doc.excludeFields, listSpec("string"), []);
+        const exclude = Cast(doc.cloneFieldFilter, listSpec("string"), []);
         Object.keys(doc).forEach(key => {
             if (exclude.includes(key)) return;
             const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
diff --git a/src/server/ApiManagers/DownloadManager.ts b/src/server/ApiManagers/DownloadManager.ts
index c5f3ca717..0d4472fdc 100644
--- a/src/server/ApiManagers/DownloadManager.ts
+++ b/src/server/ApiManagers/DownloadManager.ts
@@ -80,20 +80,14 @@ async function getDocs(id: string) {
         }
         const ids: string[] = [];
         for (const key in doc.fields) {
-            if (!doc.fields.hasOwnProperty(key)) {
-                continue;
-            }
+            if (!doc.fields.hasOwnProperty(key)) { continue; }
             const field = doc.fields[key];
-            if (field === undefined || field === null) {
-                continue;
-            }
+            if (field === undefined || field === null) { continue; }
 
             if (field.__type === "proxy" || field.__type === "prefetch_proxy") {
                 ids.push(field.fieldId);
             } else if (field.__type === "script" || field.__type === "computed") {
-                if (field.captures) {
-                    ids.push(field.captures.fieldId);
-                }
+                field.captures && ids.push(field.captures.fieldId);
             } else if (field.__type === "list") {
                 ids.push(...fn(field));
             } else if (typeof field === "string") {
diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts
index 55ceab9fb..0b9e999ac 100644
--- a/src/server/ApiManagers/UploadManager.ts
+++ b/src/server/ApiManagers/UploadManager.ts
@@ -139,13 +139,9 @@ export default class UploadManager extends ApiManager {
                         doc.id = getId(doc.id);
                     }
                     for (const key in doc.fields) {
-                        if (!doc.fields.hasOwnProperty(key)) {
-                            continue;
-                        }
+                        if (!doc.fields.hasOwnProperty(key)) { continue; }
                         const field = doc.fields[key];
-                        if (field === undefined || field === null) {
-                            continue;
-                        }
+                        if (field === undefined || field === null) { continue; }
 
                         if (field.__type === "proxy" || field.__type === "prefetch_proxy") {
                             field.fieldId = getId(field.fieldId);
@@ -208,11 +204,7 @@ export default class UploadManager extends ApiManager {
                                 } catch (e) { console.log(e); }
                                 unlink(path_2, () => { });
                             }
-                            if (id) {
-                                res.send(JSON.stringify(getId(id)));
-                            } else {
-                                res.send(JSON.stringify("error"));
-                            }
+                            res.send(JSON.stringify(id ? getId(id) : "error"));
                         } catch (e) { console.log(e); }
                         resolve();
                     });
-- 
cgit v1.2.3-70-g09d2

