From 3dab85351e1fe862a54294cec70e7139abea0d7d Mon Sep 17 00:00:00 2001
From: Lionel Han <47760119+IGoByJoe@users.noreply.github.com>
Date: Mon, 15 Jun 2020 00:10:16 -0700
Subject: added ? to a lot of things

---
 src/client/views/nodes/formattedText/FormattedTextBox.tsx | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

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

diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 1fab54d7e..b0b648b66 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -188,7 +188,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                 }
             }
             const state = this._editorView.state.apply(tx);
-            this._editorView.updateState(state);
+            this?._editorView?.updateState(state);
             (tx.storedMarks && !this._editorView.state.storedMarks) && (this._editorView.state.storedMarks = tx.storedMarks);
 
             const tsel = this._editorView.state.selection.$from;
@@ -1213,7 +1213,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         const rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : "";
         const interactive = Doc.GetSelectedTool() === InkTool.None && !this.layoutDoc.isBackground;
         if (this.props.isSelected()) {
-            this._editorView && RichTextMenu.Instance.updateFromDash(this._editorView, undefined, this.props);
+            this._editorView && RichTextMenu.Instance?.updateFromDash(this._editorView, undefined, this.props);
         } else if (FormattedTextBoxComment.textBox === this) {
             FormattedTextBoxComment.Hide();
         }
-- 
cgit v1.2.3-70-g09d2


From 4b1dc65c9e0bb917c1f1f780f8a63fc0c91b4ad6 Mon Sep 17 00:00:00 2001
From: geireann <60007097+geireann@users.noreply.github.com>
Date: Tue, 16 Jun 2020 23:53:16 +0800
Subject: mobile menu bug fixes + doc button fix

---
 deploy/mobile/image.html                           |  1 +
 src/client/util/CurrentUserUtils.ts                | 73 ++++++++++++----------
 .../views/collections/CollectionStackingView.tsx   |  2 +-
 src/client/views/nodes/AudioBox.tsx                |  2 +-
 src/client/views/nodes/PresBox.scss                |  5 ++
 src/client/views/nodes/RadialMenu.tsx              |  6 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  3 +-
 .../views/presentationview/PresElementBox.scss     |  2 +-
 src/mobile/MobileInterface.tsx                     | 69 +++++++-------------
 src/mobile/MobileMenu.scss                         |  4 +-
 10 files changed, 77 insertions(+), 90 deletions(-)

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

diff --git a/deploy/mobile/image.html b/deploy/mobile/image.html
index d30ad6ac2..718550f02 100644
--- a/deploy/mobile/image.html
+++ b/deploy/mobile/image.html
@@ -3,6 +3,7 @@
 <head>
     <title>Dash Mobile</title>
     <link href="https://fonts.googleapis.com/css?family=Fjalla+One|Hind+Siliguri:300" rel="stylesheet">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0,user-scalable=0" />
 </head>
 
 <body>
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 323e398be..9ce162897 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -55,6 +55,15 @@ export class CurrentUserUtils {
             });
         }
 
+        if (doc["template-mobile-button"] === undefined) {
+            const queryTemplate = this.mobileButton({ title: "NEW MOBILE BUTTON", onClick: undefined, _backgroundColor: "lightgrey" }, [this.ficon({ ignoreClick: true, icon: "mobile", backgroundColor: "rgba(0,0,0,0)" }), this.mobileTextContainer({}, [this.mobileButtonText({}, "NEW MOBILE BUTTON"), this.mobileButtonInfo({}, "You can customize this button and make it your own.")])]);
+            doc["template-mobile-button"] = CurrentUserUtils.ficon({
+                onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'),
+                dragFactory: new PrefetchProxy(queryTemplate) as any as Doc,
+                removeDropProperties: new List<string>(["dropAction"]), title: "mobile button", icon: "mobile"
+            });
+        }
+
         if (doc["template-button-slides"] === undefined) {
             const slideTemplate = Docs.Create.MultirowDocument(
                 [
@@ -165,16 +174,6 @@ export class CurrentUserUtils {
             });
         }
 
-        // if (doc["mobile-button"] === undefined) {
-        //     const mobileTemplate = this.mobileButton({ title: "mobile button", _backgroundColor: "lightgrey" }, [this.ficon({ ignoreClick: true, icon: "mobile", backgroundColor: "rgba(0,0,0,0)" }), this.mobileTextContainer({}, [this.mobileButtonText({}, "text"), this.mobileButtonInfo({}, "This is a default mobile button for use in the mobile menu")])]);
-        //     mobileTemplate.isTemplateDoc = makeTemplate(mobileTemplate);
-        //     doc["mobile-button"] = CurrentUserUtils.ficon({
-        //         onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'),
-        //         dragFactory: new PrefetchProxy(mobileTemplate) as any as Doc,
-        //         removeDropProperties: new List<string>(["dropAction"]), title: "mobile button view", icon: "mobile"
-        //     });
-        // }
-
         if (doc["template-button-detail"] === undefined) {
             const { TextDocument, MasonryDocument, CarouselDocument } = Docs.Create;
 
@@ -229,6 +228,7 @@ export class CurrentUserUtils {
             doc["template-button-slides"] as Doc,
             doc["template-button-description"] as Doc,
             doc["template-button-query"] as Doc,
+            doc["template-mobile-button"] as Doc,
             doc["template-button-detail"] as Doc,
             doc["template-button-link"] as Doc,
             doc["template-button-switch"] as Doc];
@@ -378,9 +378,8 @@ export class CurrentUserUtils {
         if (doc.emptyWebpage === undefined) {
             doc.emptyWebpage = Docs.Create.WebDocument("", { title: "New Webpage", _nativeWidth: 850, _nativeHeight: 962, _width: 600, UseCors: true });
         }
-        if (doc.activeMobile === undefined) {
-            console.log("phone setup");
-            this.setupActiveMobile(doc);
+        if (doc.activeMobileMenu === undefined) {
+            this.setupActiveMobileMenu(doc);
         }
         return [
             { title: "Drag a comparison box", label: "Comp", icon: "columns", ignoreClick: true, drag: 'Docs.Create.ComparisonDocument()' },
@@ -395,7 +394,7 @@ export class CurrentUserUtils {
             { 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", ignoreClick: true, drag: 'Docs.Create.ScriptingDocument(undefined, { _width: 200, _height: 250 title: "untitled script" })' },
             { 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(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory,true)', dragFactory: doc.activeMobile as Doc },
+            { 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()' },
             // { 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 },
@@ -449,12 +448,12 @@ export class CurrentUserUtils {
         return doc.myItemCreators as Doc;
     }
 
-    static setupActiveMobile(doc: Doc) {
-        if (doc.activeMobile === undefined) {
+    static setupActiveMobileMenu(doc: Doc) {
+        if (doc.activeMobileMenu === undefined) {
             console.log("undefined");
-            doc.activeMobile = this.setupMobileMenu();
+            doc.activeMobileMenu = this.setupMobileMenu();
         }
-        return doc.activeMobile as Doc;
+        return doc.activeMobileMenu as Doc;
     }
 
     static setupMobileMenu() {
@@ -466,37 +465,45 @@ export class CurrentUserUtils {
 
     static setupMobileButtons(doc?: Doc, buttons?: string[]) {
         const docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, click?: string, ischecked?: string, activePen?: Doc, backgroundColor?: string, info: string, dragFactory?: Doc }[] = [
-            { title: "LIBRARY", icon: "bars", click: 'switchToLibrary()', backgroundColor: "#ffd6d6", info: "Navigate and access all of your documents within their respective collections" },
-            { title: "RECORD", icon: "microphone", click: 'openMobileAudio()', backgroundColor: "#ffbfbf", info: "Use your mobile to record audio and access it on Dash Web." },
-            { title: "UPLOAD", icon: "upload", click: 'uploadImageMobile()', backgroundColor: "#ff9e9e", info: "Upload an image from your mobile device so it can be accessed on Dash Web" },
+            { title: "LIBRARY", icon: "bars", click: 'switchToLibrary()', backgroundColor: "#ffd6d6", info: "Navigate and access all of your documents within their respective collections." },
+            { title: "RECORD", icon: "microphone", click: 'openMobileAudio()', backgroundColor: "#ffbfbf", info: "Use your phone to record and upload audio onto Dash Web." },
+            { title: "UPLOAD", icon: "upload", click: 'uploadImageMobile()', backgroundColor: "#ff9e9e", info: "Upload images or videos from your mobile device so they can be accessed on Dash Web." },
             { title: "PRESENTATION", icon: "desktop", click: 'openMobilePresentation()', backgroundColor: "#ff8080", info: "Use your phone as a remote for you presentation." },
-            { title: "SETTINGS", icon: "cog", click: 'openMobileSettings()', backgroundColor: "#ff5e5e", info: "Change your password, log out, or manage your account security" }
+            { title: "SETTINGS", icon: "cog", click: 'openMobileSettings()', backgroundColor: "#ff5e5e", info: "Change your password, log out, or manage your account security." }
         ];
-        return docProtoData.filter(d => !buttons || !buttons.includes(d.title)).map(data => this.mobileButton({ title: data.title, onClick: data.click ? ScriptField.MakeScript(data.click) : undefined, _backgroundColor: data.backgroundColor }, [this.ficon({ ignoreClick: true, icon: data.icon, backgroundColor: "rgba(0,0,0,0)" }), this.mobileTextContainer({}, [this.mobileButtonText({}, data.title), this.mobileButtonInfo({}, data.info)])]));
+        return docProtoData.filter(d => !buttons || !buttons.includes(d.title)).map(data =>
+            this.mobileButton({
+                title: data.title,
+                lockedPosition: true,
+                onClick: data.click ? ScriptField.MakeScript(data.click) : undefined,
+                _backgroundColor: data.backgroundColor
+            },
+                [this.ficon({ ignoreClick: true, icon: data.icon, backgroundColor: "rgba(0,0,0,0)" }), this.mobileTextContainer({}, [this.mobileButtonText({}, data.title), this.mobileButtonInfo({}, data.info)])])
+        );
     }
 
-    static mobileButton = (opts: DocumentOptions, docs: Doc[]) => new PrefetchProxy(Docs.Create.MulticolumnDocument(docs, {
+    static mobileButton = (opts: DocumentOptions, docs: Doc[]) => Docs.Create.MulticolumnDocument(docs, {
         ...opts,
         dropAction: undefined, removeDropProperties: new List<string>(["dropAction"]), _nativeWidth: 900, _nativeHeight: 250, _width: 900, _height: 250, _yMargin: 15,
-        borderRounding: "5px", boxShadow: "0 0", _chromeStatus: "disabled",
-    })) as any as Doc
+        borderRounding: "5px", boxShadow: "0 0", _chromeStatus: "disabled"
+    }) as any as Doc
 
-    static mobileTextContainer = (opts: DocumentOptions, docs: Doc[]) => new PrefetchProxy(Docs.Create.MultirowDocument(docs, {
+    static mobileTextContainer = (opts: DocumentOptions, docs: Doc[]) => Docs.Create.MultirowDocument(docs, {
         ...opts,
         dropAction: undefined, removeDropProperties: new List<string>(["dropAction"]), _nativeWidth: 450, _nativeHeight: 250, _width: 450, _height: 250, _yMargin: 25,
         backgroundColor: "rgba(0,0,0,0)", borderRounding: "0", boxShadow: "0 0", _chromeStatus: "disabled", ignoreClick: true
-    })) as any as Doc
+    }) as any as Doc
 
 
-    static mobileButtonText = (opts: DocumentOptions, buttonTitle: string) => new PrefetchProxy(Docs.Create.TextDocument(buttonTitle, {
+    static mobileButtonText = (opts: DocumentOptions, buttonTitle: string) => Docs.Create.TextDocument(buttonTitle, {
         ...opts,
         dropAction: undefined, title: buttonTitle, _fontSize: 37, _xMargin: 0, _yMargin: 0, ignoreClick: true, _chromeStatus: "disabled", backgroundColor: "rgba(0,0,0,0)"
-    })) as any as Doc
+    }) as any as Doc
 
-    static mobileButtonInfo = (opts: DocumentOptions, buttonInfo: string) => new PrefetchProxy(Docs.Create.TextDocument(buttonInfo, {
+    static mobileButtonInfo = (opts: DocumentOptions, buttonInfo: string) => Docs.Create.TextDocument(buttonInfo, {
         ...opts,
         dropAction: undefined, title: "info", _fontSize: 25, _xMargin: 0, _yMargin: 0, ignoreClick: true, _chromeStatus: "disabled", backgroundColor: "rgba(0,0,0,0)", _dimMagnitude: 2,
-    })) as any as Doc
+    }) as any as Doc
 
 
     static setupThumbButtons(doc: Doc) {
@@ -795,7 +802,7 @@ export class CurrentUserUtils {
         this.setupDefaultIconTemplates(doc);  // creates a set of icon templates triggered by the document deoration icon
         this.setupDocTemplates(doc); // sets up the template menu of templates
         this.setupRightSidebar(doc);  // sets up the right sidebar collection for mobile upload documents and sharing
-        this.setupActiveMobile(doc);
+        this.setupActiveMobileMenu(doc);
         this.setupOverlays(doc);  // documents in overlay layer
         this.setupDockedButtons(doc);  // the bottom bar of font icons
         this.setupDefaultPresentation(doc); // presentation that's initially triggered
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 0a1b03522..bd0b69da8 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -476,7 +476,7 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
                         transformOrigin: "top left",
                     }}
                     onScroll={action(e => {
-                        if (!this.props.isSelected()) e.currentTarget.scrollTop = this._scroll;
+                        if (!this.props.isSelected() && window.innerWidth > 1000) e.currentTarget.scrollTop = this._scroll;
                         else this._scroll = e.currentTarget.scrollTop;
                     })}
                     onDrop={this.onExternalDrop.bind(this)}
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 1a935d9b0..2e0be1c6a 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -228,7 +228,7 @@ export class AudioBox extends ViewBoxBaseComponent<FieldViewProps, AudioDocument
             {!this.path ?
                 <div className="audiobox-buttons">
                     <div className="audiobox-dictation" onClick={this.onFile}>
-                        <FontAwesomeIcon style={{ width: "30px", background: this.layoutDoc.playOnSelect ? "yellow" : "dimGray" }} icon="file-alt" size={this.props.PanelHeight() < 36 ? "1x" : "2x"} />
+                        <FontAwesomeIcon style={{ width: "30px", background: this.layoutDoc.playOnSelect ? "yellow" : "rgba(0,0,0,0)" }} icon="file-alt" size={this.props.PanelHeight() < 36 ? "1x" : "2x"} />
                     </div>
                     <button className={`audiobox-record${interactive}`} style={{ backgroundColor: this.audioState === "recording" ? "red" : "black" }}>
                         {this.audioState === "recording" ? "STOP" : "RECORD"}
diff --git a/src/client/views/nodes/PresBox.scss b/src/client/views/nodes/PresBox.scss
index 2bb249ee4..ccce6e627 100644
--- a/src/client/views/nodes/PresBox.scss
+++ b/src/client/views/nodes/PresBox.scss
@@ -126,4 +126,9 @@
         position: absolute;
         min-width: 50px;
     }
+
+    .input,
+    .select {
+        font-size: 100%;
+    }
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/RadialMenu.tsx b/src/client/views/nodes/RadialMenu.tsx
index ddfdb67b4..2bbb80b2a 100644
--- a/src/client/views/nodes/RadialMenu.tsx
+++ b/src/client/views/nodes/RadialMenu.tsx
@@ -1,9 +1,9 @@
 import React = require("react");
 import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
-import MobileInterface from "../../../mobile/MobileInterface";
 import "./RadialMenu.scss";
 import { RadialMenuItem, RadialMenuProps } from "./RadialMenuItem";
+import { MobileInterface } from "../../../mobile/MobileInterface";
 
 @observer
 export class RadialMenu extends React.Component {
@@ -38,7 +38,6 @@ export class RadialMenu extends React.Component {
         this._mouseY = e.clientY;
         this.used = false;
         document.addEventListener("pointermove", this.onPointerMove);
-
     }
 
     @observable
@@ -176,7 +175,6 @@ export class RadialMenu extends React.Component {
 
     @action
     openMenu = (x: number, y: number) => {
-
         this._pageX = x;
         this._pageY = y;
         this._shouldDisplay;
@@ -216,7 +214,7 @@ export class RadialMenu extends React.Component {
 
 
     render() {
-        if (!this._display || MobileInterface.Instance) {
+        if (!this._display) {
             return null;
         }
         const style = this._yRelativeToTop ? { left: this._pageX - 130, top: this._pageY - 130 } :
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 1db50a5cc..912bafc46 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -262,7 +262,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     }
 
     public unhighlightSearchTerms = () => {
-        if (this._editorView && (this._editorView as any).docView) {
+        if (window.innerWidth < 1000) null;
+        else if (this._editorView && (this._editorView as any).docView) {
             const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight);
             const activeMark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight, { selected: true });
             const end = this._editorView.state.doc.nodeSize - 2;
diff --git a/src/client/views/presentationview/PresElementBox.scss b/src/client/views/presentationview/PresElementBox.scss
index ec82b0c51..bf268eb8d 100644
--- a/src/client/views/presentationview/PresElementBox.scss
+++ b/src/client/views/presentationview/PresElementBox.scss
@@ -79,7 +79,7 @@
 }
 
 .presElementBox-name {
-    font-size: 12pxππ;
+    font-size: 12px;
     position: absolute;
     display: inline-block;
     width: calc(100% - 45px);
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index 25dc9fd55..7af11a9c4 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -35,6 +35,7 @@ import { nullAudio } from '../fields/URLField';
 import GestureOverlay from "../client/views/GestureOverlay";
 import { ScriptField } from "../fields/ScriptField";
 import InkOptionsMenu from "../client/views/collections/collectionFreeForm/InkOptionsMenu";
+import { RadialMenu } from "../client/views/nodes/RadialMenu";
 
 library.add(faTasks, faMobile, faThLarge, faWindowClose, faEdit, faTrashAlt, faPalette, faAngleRight, faBell, faTrash, faCamera, faExpand, faCaretDown, faCaretLeft, faCaretRight, faCaretSquareDown, faCaretSquareRight, faArrowsAltH, faPlus, faMinus,
     faTerminal, faToggleOn, fileSolid, faExternalLinkAlt, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faWindowMaximize, faAddressCard,
@@ -48,7 +49,7 @@ export class MobileInterface extends React.Component {
     @observable static Instance: MobileInterface;
     @computed private get userDoc() { return Doc.UserDoc(); }
     @computed private get mainContainer() { return this.userDoc ? FieldValue(Cast(this.userDoc.activeMobile, Doc)) : CurrentUserUtils.GuestMobile; }
-    @observable private mainDoc: any = CurrentUserUtils.setupActiveMobile(this.userDoc);
+    @observable private mainDoc: any = CurrentUserUtils.setupActiveMobileMenu(this.userDoc);
     @observable private renderView?: () => JSX.Element;
     @observable private sidebarActive: boolean = false;
     @observable private imageUploadActive: boolean = false;
@@ -94,31 +95,6 @@ export class MobileInterface extends React.Component {
         this.renderView = renderView;
     }
 
-    onSwitchUpload = async () => {
-        let width = 300;
-        let height = 300;
-        const res = await rp.get(Utils.prepend("/getUserDocumentId"));
-
-        // get width and height of the collection doc
-        if (this.mainContainer) {
-            const data = Cast(this.mainContainer.data, listSpec(Doc));
-            if (data) {
-                const collectionDoc = await data[1]; // this should be the collection doc since the positions should be locked
-                const docView = DocumentManager.Instance.getDocumentView(collectionDoc);
-                if (docView) {
-                    width = docView.nativeWidth ? docView.nativeWidth : 300;
-                    height = docView.nativeHeight ? docView.nativeHeight : 300;
-                }
-            }
-        }
-        DocServer.Mobile.dispatchOverlayTrigger({
-            enableOverlay: true,
-            width: width,
-            height: height,
-            text: "Documents uploaded from mobile will show here",
-        });
-    }
-
     // For toggling the hamburger menu
     @action
     toggleSidebar = () => this.sidebarActive = !this.sidebarActive
@@ -138,8 +114,8 @@ export class MobileInterface extends React.Component {
      * Back method for navigating
      */
     back = () => {
-        let header = document.getElementById("header") as HTMLElement;
-        let doc = Cast(this._parents.pop(), Doc) as Doc;
+        const header = document.getElementById("header") as HTMLElement;
+        const doc = Cast(this._parents.pop(), Doc) as Doc;
 
         if (doc === Cast(this._library, Doc) as Doc) {
             this._child = null;
@@ -198,7 +174,7 @@ export class MobileInterface extends React.Component {
         if (this.mainContainer) {
             const backgroundColor = () => "white";
             return (
-                <div style={{ position: "relative", top: '200px', height: `calc(100% - 200px)`, width: "100%", overflow: "hidden", left: "0%" }}>
+                <div style={{ position: "relative", top: '200px', height: `calc(100% - 350px)`, width: "100%", left: "0%" }}>
                     <DocumentView
                         Document={this.mainContainer}
                         DataDoc={undefined}
@@ -238,16 +214,15 @@ export class MobileInterface extends React.Component {
      * @param doc: doc for which the method is called
      */
     handleClick = async (doc: Doc) => {
-        let children = DocListCast(doc.data);
+        const children = DocListCast(doc.data);
         if (doc.type !== "collection") {
             this._parents.push(this._activeDoc);
             this._activeDoc = doc;
             this.switchCurrentView((userDoc: Doc) => doc);
             this._homeMenu = false;
             this.toggleSidebar();
-        } else if (doc.type === "collection" && children.length === 0) {
-            console.log("This collection has no children");
-        } else {
+        } else if (doc.type === "collection" && children.length === 0) console.log("This collection has no children");
+        else {
             this._parents.push(this._activeDoc);
             this._activeDoc = doc;
             this.switchCurrentView((userDoc: Doc) => doc);
@@ -260,7 +235,7 @@ export class MobileInterface extends React.Component {
      * Handles creation of array which is then rendered in renderPathbar()
      */
     createPathname = () => {
-        let docArray = [];
+        const docArray = [];
         this._parents.map((doc: Doc, index: any) => {
             docArray.push(doc);
         });
@@ -270,8 +245,8 @@ export class MobileInterface extends React.Component {
 
     // Renders the graphical pathbar
     renderPathbar = () => {
-        let docArray = this.createPathname();
-        let items = docArray.map((doc: Doc, index: any) => {
+        const docArray = this.createPathname();
+        const items = docArray.map((doc: Doc, index: any) => {
             if (index === 0) {
                 return (
                     <>
@@ -341,9 +316,8 @@ export class MobileInterface extends React.Component {
             this._child = null;
             this.switchCurrentView((userDoc: Doc) => doc);
             this._parents.length = index;
-        } else if (doc === this._homeDoc) {
-            this.returnHome();
-        } else {
+        } else if (doc === this._homeDoc) this.returnHome();
+        else {
             this._activeDoc = doc;
             this._child = doc;
             this.switchCurrentView((userDoc: Doc) => doc);
@@ -384,7 +358,7 @@ export class MobileInterface extends React.Component {
             workspaces = this._child;
         }
 
-        let buttons = DocListCast(workspaces.data).map((doc: Doc, index: any) => {
+        const buttons = DocListCast(workspaces.data).map((doc: Doc, index: any) => {
             if (doc.type !== "ink") {
                 return (
                     <div
@@ -473,7 +447,7 @@ export class MobileInterface extends React.Component {
                 console.log("here");
                 return <div className="colorSelector">
                     <InkOptionsMenu />
-                </div>
+                </div>;
 
             }
         }
@@ -677,17 +651,18 @@ export class MobileInterface extends React.Component {
                     {this.uploadImage()}
                 </div>
                 {this.switchMenuView()}
-                <div className="docButtonContainer">
-                    {this.pinToPresentation()}
-                    {this.downloadDocument()}
-                    {this.drawInk()}
-                    {this.uploadAudioButton()}
-                </div>
                 {this.inkMenu()}
                 <GestureOverlay>
+                    <div className="docButtonContainer">
+                        {this.pinToPresentation()}
+                        {this.downloadDocument()}
+                        {this.drawInk()}
+                        {this.uploadAudioButton()}
+                    </div>
                     {this.displayWorkspaces()}
                     {this.renderDefaultContent()}
                 </GestureOverlay>
+                <RadialMenu />
             </div>
         );
     }
diff --git a/src/mobile/MobileMenu.scss b/src/mobile/MobileMenu.scss
index 8d996f779..3126a7d0e 100644
--- a/src/mobile/MobileMenu.scss
+++ b/src/mobile/MobileMenu.scss
@@ -256,7 +256,7 @@ body {
 }
 
 .docButtonContainer {
-    top: 90%;
+    top: 80%;
     position: absolute;
     display: flex;
     transform: translate(-50%, 0);
@@ -306,7 +306,7 @@ body {
     width: 200px;
     left: 0px;
     top: 0px;
-    background-image: linear-gradient(to right, #1a1a1a, rgba(255, 0, 0, 0));
+    background-image: linear-gradient(to right, #1a1a1a, rgba(0, 0, 0, 0));
     text-align: center;
     user-select: none;
     z-index: 99;
-- 
cgit v1.2.3-70-g09d2


From 57d4c562b6f0ef39dcd68f2985a56c4a683fcf49 Mon Sep 17 00:00:00 2001
From: geireann <60007097+geireann@users.noreply.github.com>
Date: Thu, 18 Jun 2020 04:38:08 +0800
Subject: pdf changes + radial menu + create new workspaces

(bug with ink)
---
 src/client/views/GestureOverlay.tsx                |  8 ---
 .../views/collections/CollectionStackingView.tsx   |  2 +-
 .../collectionFreeForm/InkOptionsMenu.tsx          |  4 +-
 src/client/views/nodes/DocumentView.tsx            | 12 ++--
 src/client/views/nodes/PDFBox.scss                 |  2 +-
 src/client/views/nodes/PDFBox.tsx                  |  7 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  2 +-
 src/client/views/pdf/PDFViewer.tsx                 |  4 +-
 src/mobile/ImageUpload.tsx                         |  6 +-
 src/mobile/MobileInterface.tsx                     | 80 +++++++++++++++++++---
 src/server/ApiManagers/PDFManager.ts               |  3 +-
 11 files changed, 94 insertions(+), 36 deletions(-)

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

diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index e51e2d4e1..1c305ebeb 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -575,14 +575,6 @@ export default class GestureOverlay extends Touchable {
             const points = this._points.map(p => ({ X: p.X - B.left, Y: p.Y - B.top }));
             //push first points to so interactionUtil knows pointer is up
             this._points.push({ X: this._points[0].X, Y: this._points[0].Y });
-            if (MobileInterface.Instance && MobileInterface.Instance.drawingInk) {
-                DocServer.Mobile.dispatchGesturePoints({
-                    points: this._points,
-                    bounds: B,
-                    color: ActiveInkColor(),
-                    width: ActiveInkWidth()
-                });
-            }
 
             const initialPoint = this._points[0.];
             const xInGlass = initialPoint.X > (this._thumbX ?? Number.MAX_SAFE_INTEGER) && initialPoint.X < (this._thumbX ?? Number.MAX_SAFE_INTEGER) + (this.height);
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 797aabf18..9f1b5d63c 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -476,7 +476,7 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
                         transformOrigin: "top left",
                     }}
                     onScroll={action(e => {
-                        if (!this.props.isSelected() && this.props.renderDepth && window.innerWidth > 1000) e.currentTarget.scrollTop = this._scroll;
+                        if (!this.props.isSelected() && this.props.renderDepth && window.screen.width > 600) e.currentTarget.scrollTop = this._scroll;
                         else this._scroll = e.currentTarget.scrollTop;
                     })}
                     onDrop={this.onExternalDrop.bind(this)}
diff --git a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx
index 647847b68..676dc10f4 100644
--- a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx
+++ b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx
@@ -130,13 +130,13 @@ export default class InkOptionsMenu extends AntimodeMenu {
         ];
 
         const mobileButtons = [
-            this.shapeButtons,
+            ...this.shapeButtons,
             this.bezierButton,
             this.widthPicker,
             this.colorPicker,
         ];
 
-        return (window.innerWidth < 1000 ? this.getElement(mobileButtons) : this.getElement(buttons));
+        return (window.screen.width < 600 ? this.getElement(mobileButtons) : this.getElement(buttons));
     }
 }
 Scripting.addGlobal(function activatePen(penBtn: any) {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 75627607b..e027b6a0f 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -31,7 +31,7 @@ import { SelectionManager } from "../../util/SelectionManager";
 import SharingManager from '../../util/SharingManager';
 import { Transform } from "../../util/Transform";
 import { undoBatch, UndoManager } from "../../util/UndoManager";
-import { CollectionDockingView } from "../collections/CollectionDockingView";
+import { CollectionDockingView, DockedFrameRenderer } from "../collections/CollectionDockingView";
 import { CollectionView, CollectionViewType } from '../collections/CollectionView';
 import { ContextMenu } from "../ContextMenu";
 import { ContextMenuProps } from '../ContextMenuItem';
@@ -43,6 +43,7 @@ import "./DocumentView.scss";
 import { LinkAnchorBox } from './LinkAnchorBox';
 import { RadialMenu } from './RadialMenu';
 import React = require("react");
+import { MobileInterface } from '../../../mobile/MobileInterface';
 
 library.add(fa.faEdit, fa.faTrash, fa.faShare, fa.faDownload, fa.faExpandArrowsAlt, fa.faCompressArrowsAlt, fa.faLayerGroup, fa.faExternalLinkAlt, fa.faAlignCenter, fa.faCaretSquareRight,
     fa.faSquare, fa.faConciergeBell, fa.faWindowRestore, fa.faFolder, fa.faMapPin, fa.faLink, fa.faFingerprint, fa.faCrosshairs, fa.faDesktop, fa.faUnlock, fa.faLock, fa.faLaptopCode, fa.faMale,
@@ -181,10 +182,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         const pt = me.touchEvent.touches[me.touchEvent.touches.length - 1];
         RadialMenu.Instance.openMenu(pt.pageX - 15, pt.pageY - 15);
 
-        RadialMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "onRight"), icon: "map-pin", selected: -1 });
-        RadialMenu.Instance.addItem({ description: "Delete this document", event: () => { this.props.ContainingCollectionView?.removeDocument(this.props.Document), RadialMenu.Instance.closeMenu(); }, icon: "layer-group", selected: -1 });
-        RadialMenu.Instance.addItem({ description: "Open in a new tab", event: () => this.props.addDocTab(this.props.Document, "onRight"), icon: "trash", selected: -1 });
-        RadialMenu.Instance.addItem({ description: "Pin to Presentation", event: () => this.props.pinToPres(this.props.Document), icon: "folder", selected: -1 });
+        // RadialMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "onRight"), icon: "map-pin", selected: -1 });
+        RadialMenu.Instance.addItem({ description: "Delete", event: () => { this.props.ContainingCollectionView?.removeDocument(this.props.Document), RadialMenu.Instance.closeMenu(); }, icon: "external-link-square-alt", selected: -1 });
+        // RadialMenu.Instance.addItem({ description: "Open in a new tab", event: () => this.props.addDocTab(this.props.Document, "onRight"), icon: "trash", selected: -1 });
+        RadialMenu.Instance.addItem({ description: "Pin", event: () => DockedFrameRenderer.PinDoc(this.props.Document), icon: "map-pin", selected: -1 });
+        RadialMenu.Instance.addItem({ description: "Open", event: () => MobileInterface.Instance.handleClick(this.props.Document), icon: "trash", selected: -1 });
 
         SelectionManager.DeselectAll();
     }
diff --git a/src/client/views/nodes/PDFBox.scss b/src/client/views/nodes/PDFBox.scss
index 474587159..1a31d2916 100644
--- a/src/client/views/nodes/PDFBox.scss
+++ b/src/client/views/nodes/PDFBox.scss
@@ -224,7 +224,7 @@
     }
 }
 
-@media only screen and (max-width: 1000px) {
+@media only screen and (max-width: 600px) {
 
     .pdfBox .pdfBox-ui .pdfBox-settingsCont .pdfBox-settingsButton,
     .pdfBox-interactive .pdfBox-ui .pdfBox-settingsCont .pdfBox-settingsButton {
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 78b7faeee..4b4348d3c 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -215,13 +215,14 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum
 
     @computed get contentScaling() { return this.props.ContentScaling(); }
     @computed get renderTitleBox() {
-        console.log("fitWidth ?: " + !(this.props.Document._fitWidth) && (window.innerWidth > 1000));
+        console.log("fitWidth ?: " + !(this.props.Document._fitWidth) && (window.screen.width > 600));
         console.log("_nativeHeight: " + this.Document._nativeHeight);
         console.log("%: " + `${100 / this.contentScaling}%`);
         const classname = "pdfBox" + (this.active() ? "-interactive" : "");
         return <div className={classname} style={{
             width: !this.props.Document._fitWidth ? this.Document._nativeWidth || 0 : `${100 / this.contentScaling}%`,
-            height: !this.props.Document._fitWidth && (window.innerWidth > 1000) ? this.Document._nativeHeight || 0 : `${100 / this.contentScaling}%`, //Adjusted for mobile (!window.innerWidth < 1000)
+            //height adjusted for mobile (window.screen.width > 600)
+            height: !this.props.Document._fitWidth && (window.screen.width > 600) ? this.Document._nativeHeight || 0 : `${100 / this.contentScaling}%`,
             transform: `scale(${this.contentScaling})`
         }}  >
             <div className="pdfBox-title-outer">
@@ -233,7 +234,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum
     isChildActive = (outsideReaction?: boolean) => this._isChildActive;
     @computed get renderPdfView() {
         const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField);
-        return <div className={"pdfBox"} onContextMenu={this.specificContextMenu} style={{ height: this.props.Document._scrollTop && !this.Document._fitWidth && (window.innerWidth > 1000) ? NumCast(this.Document._height) * this.props.PanelWidth() / NumCast(this.Document._width) : undefined }}>
+        return <div className={"pdfBox"} onContextMenu={this.specificContextMenu} style={{ height: this.props.Document._scrollTop && !this.Document._fitWidth && (window.screen.width > 600) ? NumCast(this.Document._height) * this.props.PanelWidth() / NumCast(this.Document._width) : undefined }}>
             <PDFViewer {...this.props} pdf={this._pdf!} url={pdfUrl!.url.pathname} active={this.props.active} loaded={this.loaded}
                 setPdfViewer={this.setPdfViewer} ContainingCollectionView={this.props.ContainingCollectionView}
                 renderDepth={this.props.renderDepth} PanelHeight={this.props.PanelHeight} PanelWidth={this.props.PanelWidth}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index f3d0d135c..ae508cd67 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -261,7 +261,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     }
 
     public unhighlightSearchTerms = () => {
-        if (window.innerWidth < 1000) null;
+        if (window.screen.width < 600) null;
         else if (this._editorView && (this._editorView as any).docView) {
             const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight);
             const activeMark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight, { selected: true });
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 9cdbea2da..57b7bceca 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -709,8 +709,8 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
             onScroll={this.onScroll} onWheel={this.onZoomWheel} onPointerDown={this.onPointerDown} onClick={this.onClick}
             style={{
                 overflowX: this._zoomed !== 1 ? "scroll" : undefined,
-                width: !this.props.Document._fitWidth && (window.innerWidth > 1000) ? NumCast(this.props.Document._nativeWidth) : `${100 / this.contentScaling}%`,
-                height: !this.props.Document._fitWidth && (window.innerWidth > 1000) ? NumCast(this.props.Document._nativeHeight) : `${100 / this.contentScaling}%`,
+                width: !this.props.Document._fitWidth && (window.screen.width > 600) ? NumCast(this.props.Document._nativeWidth) : `${100 / this.contentScaling}%`,
+                height: !this.props.Document._fitWidth && (window.screen.width > 600) ? NumCast(this.props.Document._nativeHeight) : `${100 / this.contentScaling}%`,
                 transform: `scale(${this.props.ContentScaling()})`
             }}  >
             {this.pdfViewerDiv}
diff --git a/src/mobile/ImageUpload.tsx b/src/mobile/ImageUpload.tsx
index d8b1913ee..b66f0461d 100644
--- a/src/mobile/ImageUpload.tsx
+++ b/src/mobile/ImageUpload.tsx
@@ -65,8 +65,11 @@ export class Uploader extends React.Component {
                             }
                             const path = Utils.prepend(result.accessPaths.agnostic.client);
                             let doc = null;
+                            console.log("type: " + file.type);
                             if (file.type === "video/mp4") {
                                 doc = Docs.Create.VideoDocument(path, { _nativeWidth: 200, _width: 200, title: name });
+                            } else if (file.type === "application/pdf") {
+                                doc = Docs.Create.PdfDocument(path, { _width: 200, title: name });
                             } else {
                                 doc = Docs.Create.ImageDocument(path, { _nativeWidth: 200, _width: 200, title: name });
                             }
@@ -175,7 +178,7 @@ export class Uploader extends React.Component {
     private get uploadInterface() {
         return (
             <div className="imgupload_cont">
-                <input type="file" accept="video/*,image/*" className="inputFile" id="input_image_file" ref={inputRef} onChange={this.inputLabel} multiple></input>
+                <input type="file" accept="application/pdf, video/*,image/*" className="inputFile" id="input_image_file" ref={inputRef} onChange={this.inputLabel} multiple></input>
                 <label className="file" id="label" htmlFor="input_image_file">{this.nm}</label>
                 <div className="upload_label" onClick={this.onClick}>
                     <FontAwesomeIcon icon="upload" size="sm" />
@@ -217,4 +220,3 @@ export class Uploader extends React.Component {
 }
 
 
-// DocServer.init(window.location.protocol, window.location.hostname, 4321, "image upload");
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index 5acdc1dea..644535179 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -1,7 +1,7 @@
 import * as React from "react";
 import { library } from '@fortawesome/fontawesome-svg-core';
 import {
-    faTasks, faMobile, faThLarge, faWindowClose, faEdit, faTrashAlt, faPalette, faAngleRight, faBell, faTrash, faCamera, faExpand, faCaretDown, faCaretLeft, faCaretRight, faCaretSquareDown, faCaretSquareRight, faArrowsAltH, faPlus, faMinus,
+    faTasks, faExternalLinkSquareAlt, faMobile, faThLarge, faWindowClose, faEdit, faTrashAlt, faPalette, faAngleRight, faBell, faTrash, faCamera, faExpand, faCaretDown, faCaretLeft, faCaretRight, faCaretSquareDown, faCaretSquareRight, faArrowsAltH, faPlus, faMinus,
     faTerminal, faToggleOn, faFile as fileSolid, faExternalLinkAlt, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faWindowMaximize, faAddressCard,
     faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt,
     faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter,
@@ -17,7 +17,7 @@ import { FieldValue, Cast } from '../fields/Types';
 import { CurrentUserUtils } from '../client/util/CurrentUserUtils';
 import { emptyPath, emptyFunction, returnFalse, returnOne, returnTrue, returnZero, Utils } from '../Utils';
 import { DocServer } from '../client/DocServer';
-import { Docs } from '../client/documents/Documents';
+import { Docs, DocumentOptions } from '../client/documents/Documents';
 import { Scripting } from '../client/util/Scripting';
 import { DocumentView } from '../client/views/nodes/DocumentView';
 import { Transform } from '../client/util/Transform';
@@ -36,8 +36,11 @@ import GestureOverlay from "../client/views/GestureOverlay";
 import { ScriptField } from "../fields/ScriptField";
 import InkOptionsMenu from "../client/views/collections/collectionFreeForm/InkOptionsMenu";
 import { RadialMenu } from "../client/views/nodes/RadialMenu";
+import { UndoManager } from "../client/util/UndoManager";
+import { MainView } from "../client/views/MainView";
+import { List } from "../fields/List";
 
-library.add(faTasks, faMobile, faThLarge, faWindowClose, faEdit, faTrashAlt, faPalette, faAngleRight, faBell, faTrash, faCamera, faExpand, faCaretDown, faCaretLeft, faCaretRight, faCaretSquareDown, faCaretSquareRight, faArrowsAltH, faPlus, faMinus,
+library.add(faTasks, faExternalLinkSquareAlt, faMobile, faThLarge, faWindowClose, faEdit, faTrashAlt, faPalette, faAngleRight, faBell, faTrash, faCamera, faExpand, faCaretDown, faCaretLeft, faCaretRight, faCaretSquareDown, faCaretSquareRight, faArrowsAltH, faPlus, faMinus,
     faTerminal, faToggleOn, fileSolid, faExternalLinkAlt, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faWindowMaximize, faAddressCard,
     faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt,
     faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter,
@@ -149,7 +152,7 @@ export class MobileInterface extends React.Component {
      * Return 'Home", which implies returning to 'Home' buttons
      */
     returnHome = () => {
-        if (this._homeMenu === false || this.sidebarActive === true) {
+        if (this._homeMenu || this.sidebarActive) {
             this._homeMenu = true;
             this._parents = [];
             this._activeDoc = this._homeDoc;
@@ -220,7 +223,7 @@ export class MobileInterface extends React.Component {
      */
     handleClick = async (doc: Doc) => {
         const children = DocListCast(doc.data);
-        if (doc.type !== "collection") {
+        if (doc.type !== "collection" && this.sidebarActive) {
             this._parents.push(this._activeDoc);
             this._activeDoc = doc;
             this.switchCurrentView((userDoc: Doc) => doc);
@@ -333,7 +336,7 @@ export class MobileInterface extends React.Component {
 
     // Renders the contents of the menu and sidebar
     renderDefaultContent = () => {
-        if (this._homeMenu === true) {
+        if (this._homeMenu) {
             return (
                 <div>
                     <div className="navbar">
@@ -351,7 +354,7 @@ export class MobileInterface extends React.Component {
                         <div className="sidebarButtons">
                             <div
                                 className="item"
-                                onClick={() => ScriptField.MakeScript("createNewWorkspace()")}>Create New Workspace
+                                onClick={() => MainView.Instance?.createNewWorkspace()}>Create New Workspace
                             </div>
                         </div>
                     </div>
@@ -403,7 +406,10 @@ export class MobileInterface extends React.Component {
                                 {buttons}
                                 <div
                                     className="item"
-                                    onClick={() => ScriptField.MakeScript("createNewWorkspace()")}>Create New Workspace
+                                    style={{ opacity: 0.7 }}
+                                    onClick={() => this.createNewWorkspace()}>
+                                    <FontAwesomeIcon className="right" icon="plus" size="lg" />
+                                    <div className="item-type">Create New Workspace</div>
                                 </div>
                             </>
                         }
@@ -413,6 +419,32 @@ export class MobileInterface extends React.Component {
         );
     }
 
+    /**
+     * Handles the Create New Workspace button in the menu
+     */
+    @action
+    createNewWorkspace = async (id?: string) => {
+        const workspaces = Cast(this.userDoc.myWorkspaces, Doc) as Doc;
+        const workspaceCount = DocListCast(workspaces.data).length + 1;
+        const freeformOptions: DocumentOptions = {
+            x: 0,
+            y: 400,
+            title: "Collection " + workspaceCount,
+            _LODdisable: true
+        };
+        const freeformDoc = CurrentUserUtils.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions);
+        const workspaceDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600, path: [Doc.UserDoc().myCatalog as Doc] }], { title: `Workspace ${workspaceCount}` }, id, "row");
+
+        const toggleTheme = ScriptField.MakeScript(`self.darkScheme = !self.darkScheme`);
+        const toggleComic = ScriptField.MakeScript(`toggleComicMode()`);
+        const cloneWorkspace = ScriptField.MakeScript(`cloneWorkspace()`);
+        workspaceDoc.contextMenuScripts = new List<ScriptField>([toggleTheme!, toggleComic!, cloneWorkspace!]);
+        workspaceDoc.contextMenuLabels = new List<string>(["Toggle Theme Colors", "Toggle Comic Mode", "New Workspace Layout"]);
+
+        Doc.AddDocToList(workspaces, "data", workspaceDoc);
+        // bcz: strangely, we need a timeout to prevent exceptions/issues initializing GoldenLayout (the rendering engine for Main Container)
+    }
+
     stop = (e: React.MouseEvent) => {
         e.stopPropagation();
     }
@@ -431,7 +463,7 @@ export class MobileInterface extends React.Component {
         }
     }
 
-    // Button for switching between pan and ink mode
+    // Button for switching between pen and ink mode
     @action
     onSwitchInking = () => {
         const button = document.getElementById("inkButton") as HTMLElement;
@@ -465,6 +497,16 @@ export class MobileInterface extends React.Component {
         if (this._activeDoc._viewType === "docking") {
             return (
                 <>
+                    {/* <div className="docButton"
+                        id="undoButton"
+                        title="undo"
+                        onClick={(e: React.MouseEvent) => {
+                            UndoManager.Undo();
+                            e.stopPropagation();
+                        }}>
+                        <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="undo-alt"
+                        />
+                    </div> */}
                     <div className="docButton"
                         id="inkButton"
                         title={Doc.isDocPinned(this._activeDoc) ? "Pen on" : "Pen off"}
@@ -472,6 +514,16 @@ export class MobileInterface extends React.Component {
                         <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="pen-nib"
                         />
                     </div>
+                    {/* <div className="docButton"
+                        id="redoButton"
+                        title="redo"
+                        onClick={(e: React.MouseEvent) => {
+                            UndoManager.Redo();
+                            e.stopPropagation();
+                        }}>
+                        <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="redo-alt"
+                        />
+                    </div> */}
                 </>);
         }
     }
@@ -645,6 +697,12 @@ export class MobileInterface extends React.Component {
         );
     }
 
+    displayRadialMenu = () => {
+        if (this._activeDoc.type === "collection" && this._activeDoc !== this._homeDoc) {
+            return <RadialMenu />;
+        }
+    }
+
     onDragOver = (e: React.DragEvent) => {
         e.preventDefault();
         e.stopPropagation();
@@ -669,12 +727,14 @@ export class MobileInterface extends React.Component {
                     {this.displayWorkspaces()}
                     {this.renderDefaultContent()}
                 </GestureOverlay>
-                <RadialMenu />
+                {this.displayRadialMenu()}
             </div>
         );
     }
 }
 
+
+
 Scripting.addGlobal(function switchMobileView(doc: (userDoc: Doc) => Doc, renderView?: () => JSX.Element, onSwitch?: () => void) { return MobileInterface.Instance.switchCurrentView(doc, renderView, onSwitch); });
 Scripting.addGlobal(function openMobilePresentation() { return MobileInterface.Instance.setupDefaultPresentation(); });
 Scripting.addGlobal(function toggleMobileSidebar() { return MobileInterface.Instance.toggleSidebar(); });
diff --git a/src/server/ApiManagers/PDFManager.ts b/src/server/ApiManagers/PDFManager.ts
index d2a9e9cce..b0b5a484a 100644
--- a/src/server/ApiManagers/PDFManager.ts
+++ b/src/server/ApiManagers/PDFManager.ts
@@ -55,7 +55,8 @@ async function CreateThumbnail(coreFilename: string, pageNum: number, res: expre
     const documentProxy = await Pdfjs.getDocument(sourcePath).promise;
     const factory = new NodeCanvasFactory();
     const page = await documentProxy.getPage(pageNum);
-    const viewport = page.getViewport(1 as any);
+    const scale = 1;
+    const viewport = page.getViewport(scale as any);
     const { canvas, context } = factory.create(viewport.width, viewport.height);
     const renderContext = {
         canvasContext: context,
-- 
cgit v1.2.3-70-g09d2


From 48e841282b896a0961d595281211b276b7a9d345 Mon Sep 17 00:00:00 2001
From: geireann <60007097+geireann@users.noreply.github.com>
Date: Fri, 26 Jun 2020 22:06:33 +0800
Subject: audio dictation

---
 src/client/util/CurrentUserUtils.ts                |   2 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |   4 +
 src/mobile/AudioUpload.tsx                         |  10 +-
 src/mobile/MobileInterface.tsx                     | 116 +++++++--------------
 4 files changed, 48 insertions(+), 84 deletions(-)

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

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 823a99436..e402718bf 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -474,7 +474,7 @@ export class CurrentUserUtils {
 
     static setupMobileButtons(doc?: Doc, buttons?: string[]) {
         const docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, click?: string, ischecked?: string, activePen?: Doc, backgroundColor?: string, info: string, dragFactory?: Doc }[] = [
-            { title: "WORKSPACES", icon: "bars", click: 'openMobileWorkspaces()', backgroundColor: "lightgrey", info: "Access your Workspaces from your mobile, and navigate through all of your documents. " },
+            { title: "WORKSPACES", icon: "bars", click: 'switchToMobileLibrary()', backgroundColor: "lightgrey", info: "Access your Workspaces from your mobile, and navigate through all of your documents. " },
             { title: "UPLOAD", icon: "upload", click: 'uploadImageMobile()', backgroundColor: "lightgrey", info: "Upload files from your mobile device so they can be accessed on Dash Web." },
             { title: "MOBILE UPLOAD", icon: "mobile", click: 'switchToMobileUploads()', backgroundColor: "lightgrey", info: "Access the collection of your mobile uploads." },
             { title: "RECORD", icon: "microphone", click: 'openMobileAudio()', backgroundColor: "lightgrey", info: "Use your phone to record, dictate and then upload audio onto Dash Web." },
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 3eecc98a4..b5591ca02 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -505,6 +505,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     }
 
     recordDictation = () => {
+        console.log("recording dictation");
+        console.log(this._editorView);
         DictationManager.Controls.listen({
             interimHandler: this.setCurrentBulletContent,
             continuous: { indefinite: false },
@@ -523,6 +525,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     }
 
     recordBullet = async () => {
+        console.log("recording bullet");
         const completedCue = "end session";
         const results = await DictationManager.Controls.listen({
             interimHandler: this.setCurrentBulletContent,
@@ -539,6 +542,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
 
     setCurrentBulletContent = (value: string) => {
         if (this._editorView) {
+            console.log("this._editorView");
             const state = this._editorView.state;
             const now = Date.now();
             let mark = schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(now / 1000) });
diff --git a/src/mobile/AudioUpload.tsx b/src/mobile/AudioUpload.tsx
index f859b0ba3..83da41d90 100644
--- a/src/mobile/AudioUpload.tsx
+++ b/src/mobile/AudioUpload.tsx
@@ -19,6 +19,9 @@ import { nullAudio } from '../fields/URLField';
 import { Transform } from '../client/util/Transform';
 import { DocumentView } from '../client/views/nodes/DocumentView';
 import { MobileInterface } from './MobileInterface';
+import { DictationOverlay } from '../client/views/DictationOverlay';
+import RichTextMenu from '../client/views/nodes/formattedText/RichTextMenu';
+import { ContextMenu } from '../client/views/ContextMenu';
 
 export interface ImageUploadProps {
     Document: Doc;
@@ -57,6 +60,9 @@ export class AudioUpload extends React.Component {
     private get uploadInterface() {
         return (
             <>
+                <ContextMenu />
+                <DictationOverlay />
+                <div style={{ display: "none" }}><RichTextMenu key="rich" /></div>
                 <div className="closeUpload" onClick={() => this.closeUpload()}>
                     <FontAwesomeIcon icon="window-close" size={"lg"} />
                 </div>
@@ -66,10 +72,10 @@ export class AudioUpload extends React.Component {
                         Document={this._audioCol}
                         DataDoc={undefined}
                         LibraryPath={emptyPath}
-                        addDocument={returnFalse}
+                        addDocument={undefined}
                         addDocTab={returnFalse}
                         pinToPres={emptyFunction}
-                        rootSelected={returnFalse}
+                        rootSelected={returnTrue}
                         removeDocument={undefined}
                         docFilters={returnEmptyFilter}
                         onClick={undefined}
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index ed5076402..90672e19f 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -45,6 +45,8 @@ import { Cast, FieldValue } from '../fields/Types';
 import { CollectionView } from '../client/views/collections/CollectionView';
 import { InkingStroke } from '../client/views/InkingStroke';
 import RichTextMenu from "../client/views/nodes/formattedText/RichTextMenu";
+import { AudioBox } from "../client/views/nodes/AudioBox";
+import { FormattedTextBox } from "../client/views/nodes/formattedText/FormattedTextBox";
 
 library.add(faTasks, faReply, faQuoteLeft, faHandPointLeft, faFolderOpen, faAngleDoubleLeft, faExternalLinkSquareAlt, faMobile, faThLarge, faWindowClose, faEdit, faTrashAlt, faPalette, faAngleRight, faBell, faTrash, faCamera, faExpand, faCaretDown, faCaretLeft, faCaretRight, faCaretSquareDown, faCaretSquareRight, faArrowsAltH, faPlus, faMinus,
     faTerminal, faToggleOn, fileSolid, faExternalLinkAlt, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faWindowMaximize, faAddressCard,
@@ -80,13 +82,22 @@ export class MobileInterface extends React.Component {
 
     @action
     componentDidMount = () => {
-        Doc.UserDoc().activeMobile = this._homeDoc;
+        // Doc.UserDoc().activeMobile = this._homeDoc;
         this._homeDoc._viewType === "stacking" ? this.menuListView = true : this.menuListView = false;
         Doc.SetSelectedTool(InkTool.None);
-        this.switchCurrentView((userDoc: Doc) => this._homeDoc);
+        Doc.UserDoc().activeMobile = this._homeDoc;
+        AudioBox.Enabled = true;
 
         document.removeEventListener("dblclick", this.onReactDoubleClick);
         document.addEventListener("dblclick", this.onReactDoubleClick);
+        document.addEventListener("dash", (e: any) => {  // event used by chrome plugin to tell Dash which document to focus on 
+            const id = FormattedTextBox.GetDocFromUrl(e.detail);
+            DocServer.GetRefField(id).then(doc => {
+                if (doc instanceof Doc) {
+                    DocumentManager.Instance.jumpToDocument(doc, false, undefined);
+                }
+            });
+        });
     }
 
     @action
@@ -101,10 +112,14 @@ export class MobileInterface extends React.Component {
 
     // Switch the mobile view to the given doc
     @action
-    switchCurrentView = (doc: (userDoc: Doc) => Doc, renderView?: () => JSX.Element, onSwitch?: () => void) => {
+    switchCurrentView = (doc: Doc, renderView?: () => JSX.Element, onSwitch?: () => void) => {
         if (!this.userDoc) return;
-
-        Doc.UserDoc().activeMobile = doc(this.userDoc);
+        if (this._activeDoc === this._homeDoc) {
+            this._parents.push(this._activeDoc);
+            this._homeMenu = false;
+        }
+        this._activeDoc = doc;
+        Doc.UserDoc().activeMobile = doc;
         onSwitch && onSwitch();
 
         this.renderView = renderView;
@@ -118,21 +133,10 @@ export class MobileInterface extends React.Component {
      * Method called when 'Library' button is pressed on the home screen
      */
     switchToLibrary = async () => {
-        this._parents.push(this._activeDoc);
-        this.switchCurrentView((userDoc: Doc) => this._library);
-        this._activeDoc = this._library;
+        this.switchCurrentView(this._library);
         this._homeMenu = false;
         this.toggleSidebar();
         //setTimeout(this.toggleSidebar, 300);
-
-    }
-
-    openWorkspaces = () => {
-        this._parents.push(this._activeDoc);
-        this.switchCurrentView((userDoc: Doc) => this._library);
-        this._activeDoc = this._library;
-        this._homeMenu = false;
-        this.sidebarActive = true;
     }
 
     /**
@@ -144,24 +148,20 @@ export class MobileInterface extends React.Component {
 
         if (doc === Cast(this._library, Doc) as Doc) {
             this._child = null;
-            this.userDoc.activeMobile = this._library;
+            this.switchCurrentView(this._library);
         } else if (doc === Cast(this._homeDoc, Doc) as Doc) {
             this._homeMenu = true;
             this._parents = [];
-            this._activeDoc = this._homeDoc;
             this._child = null;
-            this.switchCurrentView((userDoc: Doc) => this._homeDoc);
+            this.switchCurrentView(this._homeDoc);
         } else {
             if (doc) {
                 this._child = doc;
-                this.switchCurrentView((userDoc: Doc) => doc);
+                this.switchCurrentView(doc);
                 this._homeMenu = false;
                 header.textContent = String(doc.title);
             }
         }
-        if (doc) {
-            this._activeDoc = doc;
-        }
         this._ink = false;
     }
 
@@ -172,9 +172,8 @@ export class MobileInterface extends React.Component {
         if (!this._homeMenu || this.sidebarActive) {
             this._homeMenu = true;
             this._parents = [];
-            this._activeDoc = this._homeDoc;
             this._child = null;
-            this.switchCurrentView((userDoc: Doc) => this._homeDoc);
+            this.switchCurrentView(this._homeDoc);
         }
         if (this.sidebarActive) {
             this.toggleSidebar();
@@ -186,8 +185,7 @@ export class MobileInterface extends React.Component {
      */
     returnMain = () => {
         this._parents = [this._homeDoc];
-        this._activeDoc = this._library;
-        this.switchCurrentView((userDoc: Doc) => this._library);
+        this.switchCurrentView(this._library);
         this._homeMenu = false;
         this._child = null;
     }
@@ -244,21 +242,17 @@ export class MobileInterface extends React.Component {
      * Navigates to the given doc and updates the sidebar.
      * @param doc: doc for which the method is called
      */
-    @undoBatch
     handleClick = async (doc: Doc) => {
         const children = DocListCast(doc.data);
         if (doc.type !== "collection" && this.sidebarActive) {
             this._parents.push(this._activeDoc);
-            this._activeDoc = doc;
-            this.switchCurrentView((userDoc: Doc) => doc);
+            this.switchCurrentView(doc);
             this._homeMenu = false;
             this.toggleSidebar();
         }
-        else if (doc.type === "collection" && children.length === 0) this.openFromSidebar(doc);
         else {
             this._parents.push(this._activeDoc);
-            this._activeDoc = doc;
-            this.switchCurrentView((userDoc: Doc) => doc);
+            this.switchCurrentView(doc);
             this._homeMenu = false;
             this._child = doc;
         }
@@ -266,8 +260,7 @@ export class MobileInterface extends React.Component {
 
     openFromSidebar = (doc: Doc) => {
         this._parents.push(this._activeDoc);
-        this._activeDoc = doc;
-        this.switchCurrentView((userDoc: Doc) => doc);
+        this.switchCurrentView(doc);
         this._homeMenu = false;
         this._child = doc; //?
         this.toggleSidebar();
@@ -354,16 +347,14 @@ export class MobileInterface extends React.Component {
     // Handles when user clicks on a document in the pathbar
     handlePathClick = (doc: Doc, index: number) => {
         if (doc === this._library) {
-            this._activeDoc = doc;
             this._child = null;
-            this.switchCurrentView((userDoc: Doc) => doc);
+            this.switchCurrentView(doc);
             this._parents.length = index;
         } else if (doc === this._homeDoc) {
             this.returnHome();
         } else {
-            this._activeDoc = doc;
             this._child = doc;
-            this.switchCurrentView((userDoc: Doc) => doc);
+            this.switchCurrentView(doc);
             this._parents.length = index;
         }
     }
@@ -610,31 +601,6 @@ export class MobileInterface extends React.Component {
         }
     }
 
-    // Mobile audio doc
-    recordAudio = async () => {
-        // upload to server with known URL
-        if (this._activeDoc.title !== "mobile audio") {
-            this._parents.push(this._activeDoc);
-        }
-        const audioDoc = Cast(Docs.Create.AudioDocument(nullAudio, { _width: 200, _height: 100, title: "mobile audio" }), Doc) as Doc;
-        if (audioDoc) {
-            this._activeDoc = audioDoc;
-            this.switchCurrentView((userDoc: Doc) => audioDoc);
-            this._homeMenu = false;
-        }
-    }
-
-    // // Pushing the audio doc onto Dash Web through the right side bar
-    // uploadAudio = () => {
-    //     const audioRightSidebar = Cast(Doc.UserDoc().rightSidebarCollection, Doc) as Doc;
-    //     const audioDoc = this._activeDoc;
-    //     const data = Cast(audioRightSidebar.data, listSpec(Doc));
-
-    //     if (data) {
-    //         data.push(audioDoc);
-    //     }
-    // }
-
     // Button for pinning images to presentation
     pinToPresentation = () => {
         // Only making button available if it is an image
@@ -719,15 +685,11 @@ export class MobileInterface extends React.Component {
 
     // For setting up the presentation document for the home menu
     setupDefaultPresentation = () => {
-        if (this._activeDoc.title !== "Presentation") {
-            this._parents.push(this._activeDoc);
-        }
 
         const presentation = Cast(Doc.UserDoc().activePresentation, Doc) as Doc;
 
         if (presentation) {
-            this._activeDoc = presentation;
-            this.switchCurrentView((userDoc: Doc) => presentation);
+            this.switchCurrentView(presentation);
             this._homeMenu = false;
         }
     }
@@ -801,13 +763,8 @@ export class MobileInterface extends React.Component {
     }
 
     switchToMobileUploads = () => {
-        if (this._activeDoc.title !== "Presentation") {
-            this._parents.push(this._activeDoc);
-        }
         const mobileUpload = Cast(Doc.UserDoc().rightSidebarCollection, Doc) as Doc;
-        console.log(mobileUpload.title);
-        this._activeDoc = mobileUpload;
-        this.switchCurrentView((userDoc: Doc) => mobileUpload);
+        this.switchCurrentView(mobileUpload);
         this._homeMenu = false;
     }
 
@@ -831,9 +788,7 @@ export class MobileInterface extends React.Component {
                         {this.undo()}
                         {this.drawInk()}
                         {this.redo()}
-                        {/* {this.upload()} */}
                         {this.uploadImageButton()}
-                        {/* {this.uploadAudioButton()} */}
                     </div>
                     {this.displayWorkspaces()}
                     {this.renderDefaultContent()}
@@ -846,14 +801,13 @@ export class MobileInterface extends React.Component {
 
 
 
-Scripting.addGlobal(function switchMobileView(doc: (userDoc: Doc) => Doc, renderView?: () => JSX.Element, onSwitch?: () => void) { return MobileInterface.Instance.switchCurrentView(doc, renderView, onSwitch); },
+Scripting.addGlobal(function switchMobileView(doc: Doc, renderView?: () => JSX.Element, onSwitch?: () => void) { return MobileInterface.Instance.switchCurrentView(doc, renderView, onSwitch); },
     "changes the active document displayed on the mobile, (doc: any)");
 Scripting.addGlobal(function openMobilePresentation() { return MobileInterface.Instance.setupDefaultPresentation(); },
     "opens the presentation on mobile");
 Scripting.addGlobal(function toggleMobileSidebar() { return MobileInterface.Instance.toggleSidebar(); });
 Scripting.addGlobal(function openMobileAudio() { return MobileInterface.Instance.toggleAudio(); });
 Scripting.addGlobal(function openMobileSettings() { return SettingsManager.Instance.open(); });
-Scripting.addGlobal(function openMobileWorkspaces() { return MobileInterface.Instance.openWorkspaces(); });
 Scripting.addGlobal(function uploadImageMobile() { return MobileInterface.Instance.toggleUpload(); });
 Scripting.addGlobal(function switchToMobileUploads() { return MobileInterface.Instance.switchToMobileUploads(); });
-Scripting.addGlobal(function switchToLibrary() { return MobileInterface.Instance.switchToLibrary(); });
\ No newline at end of file
+Scripting.addGlobal(function switchToMobileLibrary() { return MobileInterface.Instance.switchToLibrary(); });
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From 24ac6997048932c4f2717651e992f744e6a5276a Mon Sep 17 00:00:00 2001
From: anika-ahluwalia <anika.ahluwalia@gmail.com>
Date: Wed, 1 Jul 2020 17:42:38 -0500
Subject: started clicking mode popup

---
 src/client/views/MainView.tsx                      |  4 +--
 .../views/collections/CollectionLinearView.scss    | 42 +++++++++++++++-------
 .../views/collections/CollectionLinearView.tsx     | 25 ++++++++++++-
 src/client/views/linking/LinkMenu.tsx              |  6 ++--
 src/client/views/linking/LinkMenuItem.tsx          |  3 +-
 src/client/views/nodes/DocumentLinksButton.tsx     | 31 +++++++++++++---
 src/client/views/nodes/DocumentView.tsx            |  1 +
 .../views/nodes/formattedText/FormattedTextBox.tsx | 36 +++++++++++++++++++
 8 files changed, 124 insertions(+), 24 deletions(-)

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

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index f6db1af66..662232810 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -4,7 +4,7 @@ import {
     faTerminal, faToggleOn, faFile as fileSolid, faExternalLinkAlt, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faWindowMaximize, faAddressCard,
     faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt,
     faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter,
-    faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote,
+    faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faTimesCircle,
     faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faArrowsAlt, faQuoteLeft, faSortAmountDown, faAlignLeft, faAlignCenter, faAlignRight
 } from '@fortawesome/free-solid-svg-icons';
 import { ANTIMODEMENU_HEIGHT } from './globalCssVariables.scss';
@@ -138,7 +138,7 @@ export class MainView extends React.Component {
         }
 
         library.add(faTasks, faEdit, faTrashAlt, faPalette, faAngleRight, faBell, faTrash, faCamera, faExpand, faCaretDown, faCaretLeft, faCaretRight, faCaretSquareDown, faCaretSquareRight, faArrowsAltH, faPlus, faMinus,
-            faTerminal, faToggleOn, faExternalLinkAlt, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faWindowMaximize, faAddressCard, fileSolid,
+            faTerminal, faToggleOn, faExternalLinkAlt, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faTimesCircle, faWindowMaximize, faAddressCard, fileSolid,
             faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt,
             faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter,
             faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faTrashAlt, faAngleRight, faBell,
diff --git a/src/client/views/collections/CollectionLinearView.scss b/src/client/views/collections/CollectionLinearView.scss
index 123a27deb..90c96e08b 100644
--- a/src/client/views/collections/CollectionLinearView.scss
+++ b/src/client/views/collections/CollectionLinearView.scss
@@ -1,12 +1,27 @@
 @import "../globalCssVariables";
 @import "../_nodeModuleOverrides";
 
-.collectionLinearView-outer{
+.collectionLinearView-outer {
     overflow: hidden;
-    height:100%;
+    height: 100%;
+
     .collectionLinearView {
-        display:flex;
+        display: flex;
         height: 100%;
+
+        >span {
+            margin-top: "auto";
+            margin-bottom: "auto";
+            background: $dark-color;
+            color: $light-color;
+            display: inline-block;
+            border-radius: 18px;
+            margin-top: auto;
+            margin-bottom: auto;
+            margin-right: 3px;
+            cursor: pointer;
+        }
+
         >label {
             margin-top: "auto";
             margin-bottom: "auto";
@@ -17,15 +32,15 @@
             font-size: 12.5px;
             width: 18px;
             height: 18px;
-            margin-top:auto;
-            margin-bottom:auto;
+            margin-top: auto;
+            margin-bottom: auto;
             margin-right: 3px;
             cursor: pointer;
             transition: transform 0.2s;
         }
 
         label p {
-            padding-left:5px;
+            padding-left: 5px;
         }
 
         label:hover {
@@ -36,13 +51,14 @@
         >input {
             display: none;
         }
+
         >input:not(:checked)~.collectionLinearView-content {
             display: none;
         }
 
         >input:checked~label {
             transform: rotate(45deg);
-            transition: transform 0.5s;
+transition: transform 0.5s;
             cursor: pointer;
         }
 
@@ -52,12 +68,14 @@
             position: relative;
             margin-top: auto;
 
-            .collectionLinearView-docBtn, .collectionLinearView-docBtn-scalable {
-                position:relative;
-                margin:auto;
+            .collectionLinearView-docBtn,
+            .collectionLinearView-docBtn-scalable {
+                position: relative;
+                margin: auto;
                 margin-left: 3px;
                 transform-origin: center 80%;
             }
+
             .collectionLinearView-docBtn-scalable:hover {
                 transform: scale(1.15);
             }
@@ -68,10 +86,10 @@
                 border-radius: 18px;
                 font-size: 15px;
             }
-            
+
             .collectionLinearView-round-button:hover {
                 transform: scale(1.15);
             }
         }
     }
-}
+}
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx
index f38eeaf41..29deb906a 100644
--- a/src/client/views/collections/CollectionLinearView.tsx
+++ b/src/client/views/collections/CollectionLinearView.tsx
@@ -13,6 +13,8 @@ import { CollectionSubView } from './CollectionSubView';
 import { DocumentView } from '../nodes/DocumentView';
 import { documentSchema } from '../../../fields/documentSchemas';
 import { Id } from '../../../fields/FieldSymbols';
+import { DocumentLinksButton } from '../nodes/DocumentLinksButton';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 
 
 type LinearDocument = makeInterface<[typeof documentSchema,]>;
@@ -75,6 +77,11 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
         return new Transform(-translateX, -translateY, 1);
     }
 
+    @action
+    exitLongLinks = () => {
+        DocumentLinksButton.StartLink = undefined
+    }
+
     render() {
         const guid = Utils.GenerateGuid();
         const flexDir: any = StrCast(this.Document.flexDirection);
@@ -82,7 +89,12 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
         const color = StrCast(this.props.Document.color, "white");
         return <div className="collectionLinearView-outer">
             <div className="collectionLinearView" ref={this.createDashEventsTarget} >
-                <label htmlFor={`${guid}`} title="Close Menu" style={{ background: backgroundColor === color ? "black" : backgroundColor }}
+                <label htmlFor={`${guid}`} title="Close Menu" style={{
+                    background: backgroundColor === color ? "black" : backgroundColor,
+                    // width: "18px", height: "18px", fontSize: "12.5px",
+                    // transition: this.props.Document.linearViewIsExpanded ? "transform 0.2s" : "transform 0.5s",
+                    // transform: this.props.Document.linearViewIsExpanded ? "" : "rotate(45deg)"
+                }}
                     onPointerDown={e => e.stopPropagation()} >
                     <p>+</p>
                 </label>
@@ -130,6 +142,17 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
                         </div>;
                     })}
                 </div>
+                {DocumentLinksButton.StartLink ? <span style={{
+                    background: backgroundColor === color ? "black" : backgroundColor,
+                }}
+                    onPointerDown={e => e.stopPropagation()} >
+                    <span style={{ fontSize: "7px", display: "inline-block" }}>
+                        Creating Link From: {DocumentLinksButton.StartLink.title} </span>
+
+                    <FontAwesomeIcon icon="times-circle" size="lg" style={{ color: "red" }}
+                        onClick={this.exitLongLinks} />
+
+                </span> : null}
             </div>
         </div>;
     }
diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx
index 2a9bbed39..c36e7b67c 100644
--- a/src/client/views/linking/LinkMenu.tsx
+++ b/src/client/views/linking/LinkMenu.tsx
@@ -32,9 +32,9 @@ export class LinkMenu extends React.Component<Props> {
             DocumentLinksButton.EditLink = undefined;
         }
 
-        // if (this._linkMenuRef && !this._linkMenuRef.contains(e.target)) {
-        //     DocumentLinksButton.EditLink = undefined;
-        // }
+        if (this._linkMenuRef && !this._linkMenuRef.contains(e.target as any)) {
+            DocumentLinksButton.EditLink = undefined;
+        }
     }
     @action
     componentDidMount() {
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index d5d31d1ba..54dca261d 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -137,6 +137,7 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
 
     @action.bound
     async followDefault() {
+        console.log("FOLLOWWW");
         DocumentLinksButton.EditLink = undefined;
         LinkDocPreview.LinkInfo = undefined;
         DocumentManager.Instance.FollowLink(this.props.linkDoc, this.props.sourceDoc, doc => this.props.addDocTab(doc, "onRight"), false);
@@ -174,7 +175,7 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
                             {/* <div title="Edit link" className="button" ref={this._editRef} onPointerDown={this.onEdit}><FontAwesomeIcon className="fa-icon" icon="edit" size="sm" /></div> */}
                             <div title="Delete link" className="button" ref={this._editRef} onPointerDown={this.deleteLink}>
                                 <FontAwesomeIcon className="fa-icon" icon="trash" size="sm" /></div>
-                            <div title="Follow link" className="button" onClick={this.followDefault} onContextMenu={this.onContextMenu}>
+                            <div title="Follow link" className="button" onClick={e => this.followDefault()} onContextMenu={this.onContextMenu}>
                                 <FontAwesomeIcon className="fa-icon" icon="arrow-right" size="sm" />
                             </div>
                         </div>
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index c625c4cd6..d04cdd34c 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -24,6 +24,8 @@ interface DocumentLinksButtonProps {
 export class DocumentLinksButton extends React.Component<DocumentLinksButtonProps, {}> {
     private _linkButton = React.createRef<HTMLDivElement>();
 
+    @observable public static StartLink: DocumentView | undefined;
+
     @action
     onLinkButtonMoved = (e: PointerEvent) => {
         if (this._linkButton.current !== null) {
@@ -51,11 +53,9 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
         return false;
     }
 
-    @observable static StartLink: DocumentView | undefined;
     onLinkButtonDown = (e: React.PointerEvent): void => {
-
         setupMoveUpEvents(this, e, this.onLinkButtonMoved, emptyFunction, action((e, doubleTap) => {
-            if (doubleTap) {
+            if (doubleTap && this.props.InMenu) {
                 DocumentLinksButton.StartLink = this.props.View;
             } else if (!!!this.props.InMenu) {
                 DocumentLinksButton.EditLink = this.props.View;
@@ -63,6 +63,17 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
             }
         }));
     }
+
+    @action
+    onLinkClick = (e: React.MouseEvent): void => {
+        if (this.props.InMenu) {
+            DocumentLinksButton.StartLink = this.props.View;
+        } else if (!!!this.props.InMenu) {
+            DocumentLinksButton.EditLink = this.props.View;
+            DocumentLinksButton.EditLinkLoc = [e.clientX + 10, e.clientY];
+        }
+    }
+
     completeLink = (e: React.PointerEvent): void => {
         setupMoveUpEvents(this, e, returnFalse, emptyFunction, action((e, doubleTap) => {
             if (doubleTap) {
@@ -76,6 +87,15 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
         }));
     }
 
+    finishLinkClick = () => {
+        if (DocumentLinksButton.StartLink === this.props.View) {
+            DocumentLinksButton.StartLink = undefined;
+        } else {
+            DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View &&
+                DocUtils.MakeLink({ doc: DocumentLinksButton.StartLink.props.Document }, { doc: this.props.View.props.Document }, "long drag");
+        }
+    }
+
     @observable
     public static EditLink: DocumentView | undefined;
     public static EditLinkLoc: number[] = [0, 0];
@@ -86,7 +106,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
         return (!links.length || links[0].hidden) && !this.props.AlwaysOn ? (null) :
             <div title="Drag(create link) Tap(view links)" ref={this._linkButton} style={{ minWidth: 20, minHeight: 20, position: "absolute", left: this.props.Offset?.[0] }}>
                 <div className={"documentLinksButton"} style={{ backgroundColor: DocumentLinksButton.StartLink ? "transparent" : "" }}
-                    onPointerDown={this.onLinkButtonDown}
+                    onPointerDown={this.onLinkButtonDown} onClick={this.onLinkClick}
                 // onPointerLeave={action(() => LinkDocPreview.LinkInfo = undefined)}
                 // onPointerEnter={action(e => links.length && (LinkDocPreview.LinkInfo = {
                 //     addDocTab: this.props.View.props.addDocTab,
@@ -97,7 +117,8 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
                 >
                     {links.length && !!!this.props.InMenu ? links.length : <FontAwesomeIcon className="documentdecorations-icon" icon="link" size="sm" />}
                 </div>
-                {DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View ? <div className={"documentLinksButton-endLink"} onPointerDown={this.completeLink} /> : (null)}
+                {DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View ? <div className={"documentLinksButton-endLink"}
+                    onPointerDown={this.completeLink} onClick={this.finishLinkClick} /> : (null)}
                 {DocumentLinksButton.StartLink === this.props.View ? <div className={"documentLinksButton-startLink"} /> : (null)}
             </div>;
     }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 09eeaee36..6820b2047 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -116,6 +116,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
     private holdDisposer?: InteractionUtils.MultiTouchEventDisposer;
 
+    public get title() { return this.props.Document.title }
     public get displayName() { return "DocumentView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive
     public get ContentDiv() { return this._mainCont.current; }
     get active() { return SelectionManager.IsSelected(this, true) || this.props.parentActive(true); }
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 2a79e2f13..dc7d8c7ca 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -58,6 +58,7 @@ import { FieldView, FieldViewProps } from "../FieldView";
 import "./FormattedTextBox.scss";
 import { FormattedTextBoxComment, formattedTextBoxCommentPlugin } from './FormattedTextBoxComment';
 import React = require("react");
+import { DocumentManager } from '../../../util/DocumentManager';
 
 library.add(faEdit);
 library.add(faSmile, faTextHeight, faUpload);
@@ -1047,6 +1048,40 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         }
     }
 
+    @action
+    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();
+        }
+        if (e.button === 0 && this.props.isSelected(true) && !e.altKey && !e.ctrlKey && !e.metaKey) {
+            if (e.clientX < this.ProseRef!.getBoundingClientRect().right) { // stop propagation if not in sidebar
+                e.stopPropagation();  // if the text box is selected, then it consumes all down events
+            }
+        }
+        if (e.button === 2 || (e.button === 0 && e.ctrlKey)) {
+            e.preventDefault();
+        }
+        FormattedTextBoxComment.Hide();
+        if (FormattedTextBoxComment.linkDoc) {
+            if (FormattedTextBoxComment.linkDoc.type !== DocumentType.LINK) {
+                this.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "inTab" : "onRight");
+            } else {
+                DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, this.props.Document,
+                    (doc: Doc, followLinkLocation: string) => this.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation));
+            }
+        }
+
+        (e.nativeEvent as any).formattedHandled = true;
+
+        if (e.buttons === 1 && this.props.isSelected(true) && !e.altKey) {
+            e.stopPropagation();
+        }
+    }
+
     @action
     onFocused = (e: React.FocusEvent): void => {
         console.log("FOUCSS")
@@ -1317,6 +1352,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                             }
                         }
                     })}
+                    onDoubleClick={this.onDoubleClick}
                 >
                     <div className={`formattedTextBox-outer`} ref={this._scrollRef}
                         style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, pointerEvents: !this.props.isSelected() ? "none" : undefined }}
-- 
cgit v1.2.3-70-g09d2


From 713b200804fa84d654aeb0051e086641d55d95d9 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 2 Jul 2020 10:17:26 -0400
Subject: started some cleanup for Mobile  ..  lookslike workspaces view is
 broken now

---
 src/client/util/CurrentUserUtils.ts                |   3 +-
 src/client/views/AntimodeMenu.tsx                  |   2 +-
 src/client/views/nodes/DocumentView.tsx            |   7 +-
 src/client/views/nodes/PDFBox.tsx                  |   3 -
 src/client/views/nodes/RadialMenu.tsx              |   2 -
 .../views/nodes/formattedText/FormattedTextBox.tsx |   6 +-
 src/mobile/MobileInterface.tsx                     | 393 +++++++++------------
 7 files changed, 170 insertions(+), 246 deletions(-)

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

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index e86f72684..6906c0bb3 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -32,7 +32,6 @@ export class CurrentUserUtils {
     public static get MainDocId() { return this.mainDocId; }
     public static set MainDocId(id: string | undefined) { this.mainDocId = id; }
     @computed public static get UserDocument() { return Doc.UserDoc(); }
-    @computed public static get ActivePen() { return Doc.UserDoc().activePen instanceof Doc && (Doc.UserDoc().activePen as Doc).inkPen as Doc; }
 
     @observable public static GuestTarget: Doc | undefined;
     @observable public static GuestWorkspace: Doc | undefined;
@@ -365,7 +364,7 @@ export class CurrentUserUtils {
         } else {
             const templateIconsDoc = Cast(doc["template-icons"], Doc, null);
             const requiredTypes = [doc["template-icon-view"] as Doc, doc["template-icon-view-img"] as Doc,
-            doc["template-icon-view-col"] as Doc, doc["template-icon-view-rtf"] as Doc, doc["template-icon-view-pdf"] as Doc];
+            doc["template-icon-view-col"] as Doc, doc["template-icon-view-rtf"] as Doc];
             DocListCastAsync(templateIconsDoc.data).then(async curIcons => {
                 await Promise.all(curIcons!);
                 requiredTypes.map(ntype => Doc.AddDocToList(templateIconsDoc, "data", ntype));
diff --git a/src/client/views/AntimodeMenu.tsx b/src/client/views/AntimodeMenu.tsx
index 3e4d20fea..b8f77932d 100644
--- a/src/client/views/AntimodeMenu.tsx
+++ b/src/client/views/AntimodeMenu.tsx
@@ -131,7 +131,7 @@ export default abstract class AntimodeMenu extends React.Component {
     }
 
     protected getDragger = () => {
-        return <div className="antimodeMenu-dragger" onPointerDown={this.dragStart} style={{ width: "20px" }} />;
+        return <div className="antimodeMenu-dragger" key="dragger" onPointerDown={this.dragStart} style={{ width: "20px" }} />;
     }
 
     protected getElement(buttons: JSX.Element[]) {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 0ec676379..8d122baeb 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -29,7 +29,6 @@ import { SelectionManager } from "../../util/SelectionManager";
 import SharingManager from '../../util/SharingManager';
 import { Transform } from "../../util/Transform";
 import { undoBatch, UndoManager } from "../../util/UndoManager";
-import { CollectionDockingView, DockedFrameRenderer } from "../collections/CollectionDockingView";
 import { CollectionView, CollectionViewType } from '../collections/CollectionView';
 import { ContextMenu } from "../ContextMenu";
 import { ContextMenuProps } from '../ContextMenuItem';
@@ -185,7 +184,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         // RadialMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "onRight"), icon: "map-pin", selected: -1 });
         RadialMenu.Instance.addItem({ description: "Delete", event: () => { this.props.ContainingCollectionView?.removeDocument(this.props.Document), RadialMenu.Instance.closeMenu(); }, icon: "external-link-square-alt", selected: -1 });
         // RadialMenu.Instance.addItem({ description: "Open in a new tab", event: () => this.props.addDocTab(this.props.Document, "onRight"), icon: "trash", selected: -1 });
-        RadialMenu.Instance.addItem({ description: "Pin", event: () => DockedFrameRenderer.PinDoc(this.props.Document), icon: "map-pin", selected: -1 });
+        RadialMenu.Instance.addItem({ description: "Pin", event: () => this.props.pinToPres(this.props.Document), icon: "map-pin", selected: -1 });
         RadialMenu.Instance.addItem({ description: "Open", event: () => MobileInterface.Instance.handleClick(this.props.Document), icon: "trash", selected: -1 });
 
         SelectionManager.DeselectAll();
@@ -945,7 +944,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         recommendations.documentIconHeight = 150;
         recommendations.sourceDoc = this.props.Document;
         recommendations.sourceDocContext = this.props.ContainingCollectionView!.props.Document;
-        CollectionDockingView.AddRightSplit(recommendations, undefined);
+        this.props.addDocTab(recommendations, "onRight");
 
         // RecommendationsBox.Instance.displayRecommendations(e.pageX + 100, e.pageY);
     }
@@ -977,7 +976,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             body.href = urls[i];
             bodies.push(body);
         }
-        CollectionDockingView.AddRightSplit(Docs.Create.SchemaDocument(headers, bodies, { title: `Showing External Recommendations for "${StrCast(doc.title)}"` }), undefined);
+        this.props.addDocTab(Docs.Create.SchemaDocument(headers, bodies, { title: `Showing External Recommendations for "${StrCast(doc.title)}"` }), "onRight");
         this._showKPQuery = true;
         this._queries = kps.toString();
     }
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 54f44a0f9..1c5825a8f 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -226,9 +226,6 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum
 
     @computed get contentScaling() { return this.props.ContentScaling(); }
     @computed get renderTitleBox() {
-        console.log("fitWidth ?: " + !(this.props.Document._fitWidth) && (window.screen.width > 600));
-        console.log("_nativeHeight: " + this.Document._nativeHeight);
-        console.log("%: " + `${100 / this.contentScaling}%`);
         const classname = "pdfBox" + (this.active() ? "-interactive" : "");
         return <div className={classname} style={{
             width: !this.props.Document._fitWidth ? this.Document._nativeWidth || 0 : `${100 / this.contentScaling}%`,
diff --git a/src/client/views/nodes/RadialMenu.tsx b/src/client/views/nodes/RadialMenu.tsx
index 2bbb80b2a..a3ac09a11 100644
--- a/src/client/views/nodes/RadialMenu.tsx
+++ b/src/client/views/nodes/RadialMenu.tsx
@@ -3,7 +3,6 @@ import { action, computed, IReactionDisposer, observable, reaction, runInAction
 import { observer } from "mobx-react";
 import "./RadialMenu.scss";
 import { RadialMenuItem, RadialMenuProps } from "./RadialMenuItem";
-import { MobileInterface } from "../../../mobile/MobileInterface";
 
 @observer
 export class RadialMenu extends React.Component {
@@ -26,7 +25,6 @@ export class RadialMenu extends React.Component {
 
 
     catchTouch = (te: React.TouchEvent) => {
-        console.log("caught");
         te.stopPropagation();
         te.preventDefault();
     }
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index ae1db76de..270fcb01c 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -190,7 +190,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                 }
             }
             const state = this._editorView.state.apply(tx);
-            this?._editorView?.updateState(state);
+            this._editorView?.updateState(state);
             (tx.storedMarks && !this._editorView.state.storedMarks) && (this._editorView.state.storedMarks = tx.storedMarks);
 
             const tsel = this._editorView.state.selection.$from;
@@ -512,8 +512,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     }
 
     recordDictation = () => {
-        console.log("recording dictation");
-        console.log(this._editorView);
         DictationManager.Controls.listen({
             interimHandler: this.setCurrentBulletContent,
             continuous: { indefinite: false },
@@ -532,7 +530,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     }
 
     recordBullet = async () => {
-        console.log("recording bullet");
         const completedCue = "end session";
         const results = await DictationManager.Controls.listen({
             interimHandler: this.setCurrentBulletContent,
@@ -549,7 +546,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
 
     setCurrentBulletContent = (value: string) => {
         if (this._editorView) {
-            console.log("this._editorView");
             const state = this._editorView.state;
             const now = Date.now();
             let mark = schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(now / 1000) });
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index 1b934df93..34894250a 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -13,7 +13,7 @@ import { action, computed, observable, reaction, trace } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc, DocListCast } from '../fields/Doc';
 import { CurrentUserUtils } from '../client/util/CurrentUserUtils';
-import { emptyFunction, emptyPath, returnEmptyString, returnFalse, returnOne, returnTrue, returnZero, returnEmptyFilter } from '../Utils';
+import { emptyFunction, emptyPath, returnFalse, returnOne, returnTrue, returnZero, returnEmptyFilter } from '../Utils';
 import { Docs, DocumentOptions } from '../client/documents/Documents';
 import { Scripting } from '../client/util/Scripting';
 import { DocumentView } from '../client/views/nodes/DocumentView';
@@ -35,7 +35,6 @@ import { AudioUpload } from "./AudioUpload";
 import { Cast, FieldValue } from '../fields/Types';
 import RichTextMenu from "../client/views/nodes/formattedText/RichTextMenu";
 import { AudioBox } from "../client/views/nodes/AudioBox";
-import { Compute } from "google-auth-library";
 
 library.add(faTasks, faReply, faQuoteLeft, faHandPointLeft, faFolderOpen, faAngleDoubleLeft, faExternalLinkSquareAlt, faMobile, faThLarge, faWindowClose, faEdit, faTrashAlt, faPalette, faAngleRight, faBell, faTrash, faCamera, faExpand, faCaretDown, faCaretLeft, faCaretRight, faCaretSquareDown, faCaretSquareRight, faArrowsAltH, faPlus, faMinus,
     faTerminal, faToggleOn, fileSolid, faExternalLinkAlt, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faWindowMaximize, faAddressCard,
@@ -47,22 +46,22 @@ library.add(faTasks, faReply, faQuoteLeft, faHandPointLeft, faFolderOpen, faAngl
 @observer
 export class MobileInterface extends React.Component {
     @observable static Instance: MobileInterface;
-    @computed private get userDoc() { return Doc.UserDoc(); }
-    @computed private get mainContainer() { return this.userDoc ? FieldValue(Cast(this.userDoc.activeMobile, Doc)) : CurrentUserUtils.GuestMobile; }
-    @observable private mainDoc: any = CurrentUserUtils.setupActiveMobileMenu(this.userDoc);
-    @observable private renderView?: () => JSX.Element;
-    @observable private sidebarActive: boolean = false; //to toggle sidebar display
-    @observable private imageUploadActive: boolean = false; //to toggle image upload
-    @observable private audioUploadActive: boolean = false;
-    @observable private menuListView: boolean = false; //to switch between menu view (list / icon)
+    @observable private _mainDoc: any = CurrentUserUtils.setupActiveMobileMenu(Doc.UserDoc());
+    @observable private _sidebarActive: boolean = false; //to toggle sidebar display
+    @observable private _imageUploadActive: boolean = false; //to toggle image upload
+    @observable private _audioUploadActive: boolean = false;
+    @observable private _menuListView: boolean = false; //to switch between menu view (list / icon)
     @observable private _ink: boolean = false; //toggle whether ink is being dispalyed
+    @observable private renderView?: () => JSX.Element;
 
-    public _activeDoc: Doc = this.mainDoc; // doc updated as the active mobile page is updated (initially home menu)
-    public _homeDoc: Doc = this.mainDoc; // home menu as a document
+    private _activeDoc: Doc = this._mainDoc; // doc updated as the active mobile page is updated (initially home menu)
+    private _homeDoc: Doc = this._mainDoc; // home menu as a document
     private _homeMenu: boolean = true; // to determine whether currently at home menu
     private _child: Doc | null = null; // currently selected document
     private _parents: Array<Doc> = []; // array of parent docs (for pathbar)
-    private _library: Doc = CurrentUserUtils.setupLibrary(this.userDoc); // to access documents in Dash Web
+    private _library: Doc = CurrentUserUtils.setupLibrary(Doc.UserDoc()); // to access documents in Dash Web
+
+    @computed private get mainContainer() { return Doc.UserDoc() ? FieldValue(Cast(Doc.UserDoc().activeMobile, Doc)) : CurrentUserUtils.GuestMobile; }
 
     constructor(props: Readonly<{}>) {
         super(props);
@@ -72,7 +71,7 @@ export class MobileInterface extends React.Component {
     @action
     componentDidMount = () => {
         // if the home menu is in list view -> adjust the menu toggle appropriately
-        this._homeDoc._viewType === "stacking" ? this.menuListView = true : this.menuListView = false;
+        this._menuListView = this._homeDoc._viewType === "stacking" ? true : false;
         Doc.SetSelectedTool(InkTool.None); // ink should intially be set to none
         Doc.UserDoc().activeMobile = this._homeDoc; // active mobile set to home
         AudioBox.Enabled = true;
@@ -95,14 +94,14 @@ export class MobileInterface extends React.Component {
     // Switch the mobile view to the given doc
     @action
     switchCurrentView = (doc: Doc, renderView?: () => JSX.Element, onSwitch?: () => void) => {
-        if (!this.userDoc) return;
+        if (!Doc.UserDoc()) return;
         if (this._activeDoc === this._homeDoc) {
             this._parents.push(this._activeDoc);
             this._homeMenu = false;
         }
         this._activeDoc = doc;
         Doc.UserDoc().activeMobile = doc;
-        onSwitch && onSwitch();
+        onSwitch?.();
 
         this.renderView = renderView;
         // Ensures that switching to home is not registed
@@ -113,7 +112,7 @@ export class MobileInterface extends React.Component {
     // For toggling the hamburger menu
     @action
     toggleSidebar = () => {
-        this.sidebarActive = !this.sidebarActive;
+        this._sidebarActive = !this._sidebarActive;
 
         if (this._ink) {
             this.onSwitchInking();
@@ -126,7 +125,6 @@ export class MobileInterface extends React.Component {
         this.switchCurrentView(this._library);
         this._homeMenu = false;
         this.toggleSidebar();
-        //setTimeout(this.toggleSidebar, 300);
     }
 
     /**
@@ -146,13 +144,11 @@ export class MobileInterface extends React.Component {
             this._child = null;
             this.switchCurrentView(this._homeDoc);
             // Case 3: Parent document is any document
-        } else {
-            if (doc) {
-                this._child = doc;
-                this.switchCurrentView(doc);
-                this._homeMenu = false;
-                header.textContent = String(doc.title);
-            }
+        } else if (doc) {
+            this._child = doc;
+            this.switchCurrentView(doc);
+            this._homeMenu = false;
+            header.textContent = String(doc.title);
         }
         this._ink = false; // turns ink off
     }
@@ -161,13 +157,13 @@ export class MobileInterface extends React.Component {
      * Return 'Home", which implies returning to 'Home' menu buttons
      */
     returnHome = () => {
-        if (!this._homeMenu || this.sidebarActive) {
+        if (!this._homeMenu || this._sidebarActive) {
             this._homeMenu = true;
             this._parents = [];
             this._child = null;
             this.switchCurrentView(this._homeDoc);
         }
-        if (this.sidebarActive) {
+        if (this._sidebarActive) {
             this.toggleSidebar();
         }
     }
@@ -185,42 +181,38 @@ export class MobileInterface extends React.Component {
     /**
      * DocumentView for graphic display of all documents
      */
+    whitebackground = () => "white";
     @computed get displayWorkspaces() {
-        if (this.mainContainer) {
-            const backgroundColor = () => "white";
-            return (
-                <div style={{ position: "relative", top: '198px', height: `calc(100% - 350px)`, width: "100%", left: "0%" }}>
-                    <DocumentView
-                        Document={this.mainContainer}
-                        DataDoc={undefined}
-                        LibraryPath={emptyPath}
-                        addDocument={returnFalse}
-                        addDocTab={returnFalse}
-                        pinToPres={emptyFunction}
-                        rootSelected={returnFalse}
-                        removeDocument={undefined}
-                        onClick={undefined}
-                        ScreenToLocalTransform={Transform.Identity}
-                        ContentScaling={returnOne}
-                        PanelWidth={this.returnWidth}
-                        PanelHeight={this.returnHeight}
-                        NativeHeight={returnZero}
-                        NativeWidth={returnZero}
-                        renderDepth={0}
-                        focus={emptyFunction}
-                        backgroundColor={backgroundColor}
-                        parentActive={returnTrue}
-                        whenActiveChanged={emptyFunction}
-                        bringToFront={emptyFunction}
-                        docFilters={returnEmptyFilter}
-                        ContainingCollectionView={undefined}
-                        ContainingCollectionDoc={undefined}
-                    />
-                </div>
-            );
-        }
+        return !this.mainContainer ? (null) :
+            <div style={{ position: "relative", top: '198px', height: `calc(100% - 350px)`, width: "100%", left: "0%" }}>
+                <DocumentView
+                    Document={this.mainContainer}
+                    DataDoc={undefined}
+                    LibraryPath={emptyPath}
+                    addDocument={returnFalse}
+                    addDocTab={returnFalse}
+                    pinToPres={emptyFunction}
+                    rootSelected={returnFalse}
+                    removeDocument={undefined}
+                    onClick={undefined}
+                    ScreenToLocalTransform={Transform.Identity}
+                    ContentScaling={returnOne}
+                    PanelWidth={this.returnWidth}
+                    PanelHeight={this.returnHeight}
+                    NativeHeight={returnZero}
+                    NativeWidth={returnZero}
+                    renderDepth={0}
+                    focus={emptyFunction}
+                    backgroundColor={this.whitebackground}
+                    parentActive={returnTrue}
+                    whenActiveChanged={emptyFunction}
+                    bringToFront={emptyFunction}
+                    docFilters={returnEmptyFilter}
+                    ContainingCollectionView={undefined}
+                    ContainingCollectionDoc={undefined}
+                />
+            </div>;
     }
-
     /**
      * Note: window.innerWidth and window.screen.width compute different values.
      * window.screen.width is the display size, however window.innerWidth is the
@@ -235,8 +227,7 @@ export class MobileInterface extends React.Component {
      * @param doc: doc for which the method is called
      */
     handleClick = async (doc: Doc) => {
-        const children = DocListCast(doc.data);
-        if (doc.type !== "collection" && this.sidebarActive) {
+        if (doc.type !== "collection" && this._sidebarActive) {
             this._parents.push(this._activeDoc);
             this.switchCurrentView(doc);
             this._homeMenu = false;
@@ -267,12 +258,7 @@ export class MobileInterface extends React.Component {
      * Handles creation of array which is then rendered in renderPathbar()
      */
     createPathname = () => {
-        const docArray = [];
-        this._parents.map((doc: Doc, index: any) => {
-            docArray.push(doc);
-        });
-        docArray.push(this._activeDoc);
-        return docArray;
+        return [...this._parents, this._activeDoc];
     }
 
     // Renders the graphical pathbar
@@ -280,24 +266,21 @@ export class MobileInterface extends React.Component {
         const docArray = this.createPathname();
         const items = docArray.map((doc: Doc, index: any) => {
             if (index === 0) {
-                return (
-                    <>
-                        {this._homeMenu ?
-                            <div className="pathbarItem">
-                                <div className="pathbarText"
-                                    style={{ backgroundColor: "rgb(119, 37, 37)" }}
-                                    key={index}
-                                    onClick={() => this.handlePathClick(doc, index)}>{doc.title}
-                                </div>
-                            </div>
-                            :
-                            <div className="pathbarItem">
-                                <div className="pathbarText"
-                                    key={index}
-                                    onClick={() => this.handlePathClick(doc, index)}>{doc.title}
-                                </div>
-                            </div>}
-                    </>);
+                return this._homeMenu ?
+                    <div className="pathbarItem" key={index}>
+                        <div className="pathbarText"
+                            style={{ backgroundColor: "rgb(119, 37, 37)" }}
+                            key={index}
+                            onClick={() => this.handlePathClick(doc, index)}>{doc.title}
+                        </div>
+                    </div>
+                    :
+                    <div className="pathbarItem" key={index}>
+                        <div className="pathbarText"
+                            key={index}
+                            onClick={() => this.handlePathClick(doc, index)}>{doc.title}
+                        </div>
+                    </div>;
 
             } else if (doc === this._activeDoc) {
                 return (
@@ -357,14 +340,14 @@ export class MobileInterface extends React.Component {
     }
 
     // Renders the contents of the menu and sidebar
-    renderDefaultContent = () => {
+    @computed get renderDefaultContent() {
         if (this._homeMenu) {
             return (
                 <div>
                     <div className="navbar">
                         <FontAwesomeIcon className="home" icon="home" onClick={this.returnHome} />
                         <div className="header" id="header">{this._homeDoc.title}</div>
-                        <div className="cover" id="cover" onClick={(e) => this.stop(e)}></div>
+                        <div className="cover" id="cover" onClick={e => e.stopPropagation()}></div>
                         <div className="toggle-btn" id="menuButton" onClick={this.toggleSidebar}>
                             <span></span>
                             <span></span>
@@ -376,7 +359,7 @@ export class MobileInterface extends React.Component {
             );
         }
         // stores workspace documents as 'workspaces' variable
-        let workspaces = Cast(this.userDoc.myWorkspaces, Doc) as Doc;
+        let workspaces = Cast(Doc.UserDoc().myWorkspaces, Doc) as Doc;
         if (this._child) {
             workspaces = this._child;
         }
@@ -400,16 +383,16 @@ export class MobileInterface extends React.Component {
             <div>
                 <div className="navbar">
                     <FontAwesomeIcon className="home" icon="home" onClick={this.returnHome} />
-                    <div className="header" id="header">{this.sidebarActive ? "library" : this._activeDoc.title}</div>
-                    <div className={`toggle-btn ${this.sidebarActive ? "active" : ""}`} onClick={this.toggleSidebar}>
+                    <div className="header" id="header">{this._sidebarActive ? "library" : this._activeDoc.title}</div>
+                    <div className={`toggle-btn ${this._sidebarActive ? "active" : ""}`} onClick={this.toggleSidebar}>
                         <span></span>
                         <span></span>
                         <span></span>
                     </div>
-                    <div className={`background ${this.sidebarActive ? "active" : ""}`} onClick={this.toggleSidebar}></div>
+                    <div className={`background ${this._sidebarActive ? "active" : ""}`} onClick={this.toggleSidebar}></div>
                 </div>
                 {this.renderPathbar()}
-                <div className={`sidebar ${this.sidebarActive ? "active" : ""}`}>
+                <div className={`sidebar ${this._sidebarActive ? "active" : ""}`}>
                     <div className="sidebarButtons">
                         {this._child ?
                             <>
@@ -435,7 +418,7 @@ export class MobileInterface extends React.Component {
                         }
                     </div>
                 </div>
-                <div className={`blanket ${this.sidebarActive ? "active" : ""}`} onClick={this.toggleSidebar}>
+                <div className={`blanket ${this._sidebarActive ? "active" : ""}`} onClick={this.toggleSidebar}>
                 </div>
             </div>
         );
@@ -446,7 +429,7 @@ export class MobileInterface extends React.Component {
      */
     @action
     createNewWorkspace = async (id?: string) => {
-        const workspaces = Cast(this.userDoc.myWorkspaces, Doc) as Doc;
+        const workspaces = Cast(Doc.UserDoc().myWorkspaces, Doc) as Doc;
         const workspaceCount = DocListCast(workspaces.data).length + 1;
         const freeformOptions: DocumentOptions = {
             x: 0,
@@ -465,10 +448,6 @@ export class MobileInterface extends React.Component {
         Doc.AddDocToList(workspaces, "data", workspaceDoc);
     }
 
-    stop = (e: React.MouseEvent) => {
-        e.stopPropagation();
-    }
-
     // Button for switching between pen and ink mode
     @action
     onSwitchInking = () => {
@@ -486,152 +465,121 @@ export class MobileInterface extends React.Component {
     }
 
     // The static ink menu that appears at the top
-    inkMenu = () => {
-        if (this._activeDoc._viewType === "docking") {
-            if (this._ink) {
-                return <div className="colorSelector">
-                    <InkOptionsMenu />
-                </div>;
-
-            }
-        }
+    @computed get inkMenu() {
+        return this._activeDoc._viewType !== "docking" || !this._ink ? (null) :
+            <div className="colorSelector">
+                <InkOptionsMenu />
+            </div>;
     }
 
     // DocButton that uses UndoManager and handles the opacity change if CanUndo is true
     @computed get undo() {
-        if (this.mainContainer) {
-            if (this._activeDoc.type === "collection" && this._activeDoc !== this._homeDoc && this._activeDoc !== this.userDoc.rightSidebarCollection && this._activeDoc.title !== "WORKSPACES") {
-                return (<>
-                    <div className="docButton"
-                        style={{ backgroundColor: "black", color: "white", fontSize: "60", opacity: UndoManager.CanUndo() ? "1" : "0.4", }}
-                        id="undoButton"
-                        title="undo"
-                        onClick={(e: React.MouseEvent) => {
-                            UndoManager.Undo();
-                            e.stopPropagation();
-                        }}>
-                        <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="undo-alt" />
-                    </div>
-                </>);
-            }
-        }
+        if (this.mainContainer && this._activeDoc.type === "collection" && this._activeDoc !== this._homeDoc &&
+            this._activeDoc !== Doc.UserDoc().rightSidebarCollection && this._activeDoc.title !== "WORKSPACES") {
+            return (
+                <div className="docButton"
+                    style={{ backgroundColor: "black", color: "white", fontSize: "60", opacity: UndoManager.CanUndo() ? "1" : "0.4", }}
+                    id="undoButton"
+                    title="undo"
+                    onClick={(e: React.MouseEvent) => {
+                        UndoManager.Undo();
+                        e.stopPropagation();
+                    }}>
+                    <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="undo-alt" />
+                </div>);
+        } else return (null);
     }
 
     // DocButton that uses UndoManager and handles the opacity change if CanRedo is true
     @computed get redo() {
-        if (this.mainContainer) {
-            if (this._activeDoc.type === "collection" && this._activeDoc !== this._homeDoc && this._activeDoc !== this.userDoc.rightSidebarCollection && this._activeDoc.title !== "WORKSPACES") {
-                return (<>
-                    <div className="docButton"
-                        style={{ backgroundColor: "black", color: "white", fontSize: "60", opacity: UndoManager.CanRedo() ? "1" : "0.4", }}
-                        id="undoButton"
-                        title="redo"
-                        onClick={(e: React.MouseEvent) => {
-                            UndoManager.Redo();
-                            e.stopPropagation();
-                        }}>
-                        <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="redo-alt" />
-                    </div>
-                </>);
-            }
-        }
+        if (this.mainContainer && this._activeDoc.type === "collection" && this._activeDoc !== this._homeDoc &&
+            this._activeDoc !== Doc.UserDoc().rightSidebarCollection && this._activeDoc.title !== "WORKSPACES") {
+            return (
+                <div className="docButton"
+                    style={{ backgroundColor: "black", color: "white", fontSize: "60", opacity: UndoManager.CanRedo() ? "1" : "0.4", }}
+                    id="undoButton"
+                    title="redo"
+                    onClick={(e: React.MouseEvent) => {
+                        UndoManager.Redo();
+                        e.stopPropagation();
+                    }}>
+                    <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="redo-alt" />
+                </div>);
+        } else return (null);
     }
 
     // DocButton for switching into ink mode
     @computed get drawInk() {
-        if (this.mainContainer) {
-            if (this._activeDoc._viewType === "docking") {
-                return (
-                    <>
-                        <div className="docButton"
-                            id="inkButton"
-                            title={Doc.isDocPinned(this._activeDoc) ? "Pen on" : "Pen off"}
-                            onClick={this.onSwitchInking}>
-                            <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="pen-nib"
-                            />
-                        </div>
-                    </>);
-            }
-        }
+        return !this.mainContainer || this._activeDoc._viewType !== "docking" ? (null) :
+            <div className="docButton"
+                id="inkButton"
+                title={Doc.isDocPinned(this._activeDoc) ? "Pen on" : "Pen off"}
+                onClick={this.onSwitchInking}>
+                <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="pen-nib" />
+            </div>;
     }
 
     // DocButton: Button that appears on the bottom of the screen to initiate image upload
-    uploadImageButton = () => {
+    @computed get uploadImageButton() {
         if (this._activeDoc.type === "collection" && this._activeDoc !== this._homeDoc && this._activeDoc._viewType !== "docking" && this._activeDoc.title !== "WORKSPACES") {
             return <div className="docButton"
                 id="imageButton"
                 title={Doc.isDocPinned(this._activeDoc) ? "Pen on" : "Pen off"}
                 onClick={this.toggleUpload}>
-                <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="upload"
-                />
+                <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="upload" />
             </div>;
-        }
+        } else return (null);
     }
 
     // DocButton to download images on the mobile
-    downloadDocument = () => {
+    @computed get downloadDocument() {
         if (this._activeDoc.type === "image" || this._activeDoc.type === "pdf" || this._activeDoc.type === "video") {
-            const url = this._activeDoc["data-path"]?.toString();
             return <div className="docButton"
                 title={"Download Image"}
                 style={{ backgroundColor: "white", color: "black" }}
-                onClick={e => {
-                    window.open(url);
-                }}>
-                <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="download"
-                />
+                onClick={e => window.open(this._activeDoc["data-path"]?.toString())}> // daa-path holds the url
+                <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="download" />
             </div>;
-        }
+        } else return (null);
     }
 
     // DocButton for pinning images to presentation
-    pinToPresentation = () => {
+    @computed get pinToPresentation() {
         // Only making button available if it is an image
         if (!(this._activeDoc.type === "collection" || this._activeDoc.type === "presentation")) {
             const isPinned = this._activeDoc && Doc.isDocPinned(this._activeDoc);
             return <div className="docButton"
                 title={Doc.isDocPinned(this._activeDoc) ? "Unpin from presentation" : "Pin to presentation"}
                 style={{ backgroundColor: isPinned ? "black" : "white", color: isPinned ? "white" : "black" }}
-                onClick={e => {
-                    if (isPinned) {
-                        DockedFrameRenderer.UnpinDoc(this._activeDoc);
-                    }
-                    else {
-                        DockedFrameRenderer.PinDoc(this._activeDoc);
-                    }
-                }}>
-                <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="map-pin"
-                />
+                onClick={e => DockedFrameRenderer.PinDoc(this._activeDoc, isPinned)}>
+                <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="map-pin" />
             </div>;
-        }
+        } else return (null);
     }
 
     // Buttons for switching the menu between large and small icons
-    switchMenuView = () => {
-        if (this._activeDoc.title === this._homeDoc.title) {
-            return (
-                <div className="homeSwitch">
-                    <div className={`list ${!this.menuListView ? "active" : ""}`} onClick={this.changeToIconView}>
-                        <FontAwesomeIcon size="sm" icon="th-large" />
-                    </div>
-                    <div className={`list ${this.menuListView ? "active" : ""}`} onClick={this.changeToListView}>
-                        <FontAwesomeIcon size="sm" icon="bars" />
-                    </div>
+    @computed get switchMenuView() {
+        return this._activeDoc.title !== this._homeDoc.title ? (null) :
+            <div className="homeSwitch">
+                <div className={`list ${!this._menuListView ? "active" : ""}`} onClick={this.changeToIconView}>
+                    <FontAwesomeIcon size="sm" icon="th-large" />
                 </div>
-            );
-        }
+                <div className={`list ${this._menuListView ? "active" : ""}`} onClick={this.changeToListView}>
+                    <FontAwesomeIcon size="sm" icon="bars" />
+                </div>
+            </div>;
     }
 
     // Logic for switching the menu into the icons
     @action
     changeToIconView = () => {
         if (this._homeDoc._viewType = "stacking") {
-            this.menuListView = false;
+            this._menuListView = false;
             this._homeDoc._viewType = "masonry";
             this._homeDoc.columnWidth = 300;
             this._homeDoc._columnWidth = 300;
             const menuButtons = DocListCast(this._homeDoc.data);
-            menuButtons.map((doc: Doc, index: any) => {
+            menuButtons.map(doc => {
                 const buttonData = DocListCast(doc.data);
                 buttonData[1]._nativeWidth = 0.1;
                 buttonData[1]._width = 0.1;
@@ -647,9 +595,9 @@ export class MobileInterface extends React.Component {
     changeToListView = () => {
         if (this._homeDoc._viewType = "masonry") {
             this._homeDoc._viewType = "stacking";
-            this.menuListView = true;
+            this._menuListView = true;
             const menuButtons = DocListCast(this._homeDoc.data);
-            menuButtons.map((doc: Doc, index: any) => {
+            menuButtons.map(doc => {
                 const buttonData = DocListCast(doc.data);
                 buttonData[1]._nativeWidth = 450;
                 buttonData[1]._dimMagnitude = 2;
@@ -671,49 +619,37 @@ export class MobileInterface extends React.Component {
 
     // For toggling image upload pop up
     @action
-    toggleUpload = () => this.imageUploadActive = !this.imageUploadActive
+    toggleUpload = () => this._imageUploadActive = !this._imageUploadActive
 
     // For toggling audio record and dictate pop up
     @action
-    toggleAudio = () => this.audioUploadActive = !this.audioUploadActive
+    toggleAudio = () => this._audioUploadActive = !this._audioUploadActive
 
     // Button for toggling the upload pop up in a collection
     @action
     toggleUploadInCollection = () => {
         const button = document.getElementById("imageButton") as HTMLElement;
-        button.style.backgroundColor = this.imageUploadActive ? "white" : "black";
-        button.style.color = this.imageUploadActive ? "black" : "white";
+        button.style.backgroundColor = this._imageUploadActive ? "white" : "black";
+        button.style.color = this._imageUploadActive ? "black" : "white";
 
-        this.imageUploadActive = !this.imageUploadActive;
+        this._imageUploadActive = !this._imageUploadActive;
     }
 
     // For closing the image upload pop up
     @action
     closeUpload = () => {
-        this.imageUploadActive = false;
+        this._imageUploadActive = false;
     }
 
     // Returns the image upload pop up
-    uploadImage = () => {
-        let doc;
-        let toggle;
-        if (this._homeMenu === false) {
-            doc = this._activeDoc;
-            toggle = this.toggleUploadInCollection;
-        } else {
-            doc = Cast(Doc.UserDoc().rightSidebarCollection, Doc) as Doc;
-            toggle = this.toggleUpload;
-        }
-        return (
-            <Uploader Document={doc} />
-        );
+    @computed get uploadImage() {
+        const doc = !this._homeMenu ? this._activeDoc : Cast(Doc.UserDoc().rightSidebarCollection, Doc) as Doc;
+        return <Uploader Document={doc} />;
     }
 
     // Radial menu can only be used if it is a colleciton and it is not a homeDoc
-    displayRadialMenu = () => {
-        if (this._activeDoc.type === "collection" && this._activeDoc !== this._homeDoc) {
-            return <RadialMenu />;
-        }
+    @computed get displayRadialMenu() {
+        return this._activeDoc.type === "collection" && this._activeDoc !== this._homeDoc ? <RadialMenu /> : (null);
     }
 
     onDragOver = (e: React.DragEvent) => {
@@ -733,32 +669,31 @@ export class MobileInterface extends React.Component {
     }
 
     render() {
-        trace();
         return (
             <div className="mobileInterface-container" onDragOver={this.onDragOver}>
                 <SettingsManager />
-                <div className={`image-upload ${this.imageUploadActive ? "active" : ""}`}>
-                    {this.uploadImage()}
+                <div className={`image-upload ${this._imageUploadActive ? "active" : ""}`}>
+                    {this.uploadImage}
                 </div>
-                <div className={`audio-upload ${this.audioUploadActive ? "active" : ""}`}>
+                <div className={`audio-upload ${this._audioUploadActive ? "active" : ""}`}>
                     <AudioUpload />
                 </div>
-                {this.switchMenuView()}
-                {this.inkMenu()}
+                {this.switchMenuView}
+                {this.inkMenu}
                 <GestureOverlay>
                     <div style={{ display: "none" }}><RichTextMenu key="rich" /></div>
                     <div className="docButtonContainer">
-                        {this.pinToPresentation()}
-                        {this.downloadDocument()}
+                        {this.pinToPresentation}
+                        {this.downloadDocument}
                         {this.undo}
                         {this.redo}
                         {this.drawInk}
-                        {this.uploadImageButton()}
+                        {this.uploadImageButton}
                     </div>
                     {this.displayWorkspaces}
-                    {this.renderDefaultContent()}
+                    {this.renderDefaultContent}
                 </GestureOverlay>
-                {this.displayRadialMenu()}
+                {this.displayRadialMenu}
             </div>
         );
     }
-- 
cgit v1.2.3-70-g09d2


From 96001ff5b299326de8742a6540acd5017d0f751a Mon Sep 17 00:00:00 2001
From: anika-ahluwalia <anika.ahluwalia@gmail.com>
Date: Thu, 2 Jul 2020 10:40:17 -0500
Subject: started link created popup, tried hihglighting source

---
 package-lock.json                                  | 331 ++++++++++++++++++++-
 package.json                                       |   2 +
 src/client/documents/Documents.ts                  |   9 +-
 src/client/views/GlobalKeyHandler.ts               |   8 +
 src/client/views/MainView.tsx                      |  17 ++
 .../views/collections/CollectionLinearView.scss    |   2 +-
 .../views/collections/CollectionLinearView.tsx     |  17 +-
 src/client/views/nodes/DocumentLinksButton.tsx     |   9 +
 src/client/views/nodes/DocumentView.tsx            |   7 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |   4 +-
 src/fields/Doc.ts                                  |   2 +
 11 files changed, 398 insertions(+), 10 deletions(-)

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

diff --git a/package-lock.json b/package-lock.json
index d42e286e2..ad181758c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -372,6 +372,101 @@
             "resolved": "https://registry.npmjs.org/@log4js-node/log4js-api/-/log4js-api-1.0.2.tgz",
             "integrity": "sha512-6SJfx949YEWooh/CUPpJ+F491y4BYJmknz4hUN1+RHvKoUEynKbRmhnwbk/VLmh4OthLLDNCyWXfbh4DG1cTXA=="
         },
+        "@material-ui/core": {
+            "version": "4.11.0",
+            "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.11.0.tgz",
+            "integrity": "sha512-bYo9uIub8wGhZySHqLQ833zi4ZML+XCBE1XwJ8EuUVSpTWWG57Pm+YugQToJNFsEyiKFhPh8DPD0bgupz8n01g==",
+            "requires": {
+                "@babel/runtime": "^7.4.4",
+                "@material-ui/styles": "^4.10.0",
+                "@material-ui/system": "^4.9.14",
+                "@material-ui/types": "^5.1.0",
+                "@material-ui/utils": "^4.10.2",
+                "@types/react-transition-group": "^4.2.0",
+                "clsx": "^1.0.4",
+                "hoist-non-react-statics": "^3.3.2",
+                "popper.js": "1.16.1-lts",
+                "prop-types": "^15.7.2",
+                "react-is": "^16.8.0",
+                "react-transition-group": "^4.4.0"
+            },
+            "dependencies": {
+                "dom-helpers": {
+                    "version": "5.1.4",
+                    "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.1.4.tgz",
+                    "integrity": "sha512-TjMyeVUvNEnOnhzs6uAn9Ya47GmMo3qq7m+Lr/3ON0Rs5kHvb8I+SQYjLUSYn7qhEm0QjW0yrBkvz9yOrwwz1A==",
+                    "requires": {
+                        "@babel/runtime": "^7.8.7",
+                        "csstype": "^2.6.7"
+                    }
+                },
+                "popper.js": {
+                    "version": "1.16.1-lts",
+                    "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1-lts.tgz",
+                    "integrity": "sha512-Kjw8nKRl1m+VrSFCoVGPph93W/qrSO7ZkqPpTf7F4bk/sqcfWK019dWBUpE/fBOsOQY1dks/Bmcbfn1heM/IsA=="
+                },
+                "react-transition-group": {
+                    "version": "4.4.1",
+                    "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.1.tgz",
+                    "integrity": "sha512-Djqr7OQ2aPUiYurhPalTrVy9ddmFCCzwhqQmtN+J3+3DzLO209Fdr70QrN8Z3DsglWql6iY1lDWAfpFiBtuKGw==",
+                    "requires": {
+                        "@babel/runtime": "^7.5.5",
+                        "dom-helpers": "^5.0.1",
+                        "loose-envify": "^1.4.0",
+                        "prop-types": "^15.6.2"
+                    }
+                }
+            }
+        },
+        "@material-ui/styles": {
+            "version": "4.10.0",
+            "resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.10.0.tgz",
+            "integrity": "sha512-XPwiVTpd3rlnbfrgtEJ1eJJdFCXZkHxy8TrdieaTvwxNYj42VnnCyFzxYeNW9Lhj4V1oD8YtQ6S5Gie7bZDf7Q==",
+            "requires": {
+                "@babel/runtime": "^7.4.4",
+                "@emotion/hash": "^0.8.0",
+                "@material-ui/types": "^5.1.0",
+                "@material-ui/utils": "^4.9.6",
+                "clsx": "^1.0.4",
+                "csstype": "^2.5.2",
+                "hoist-non-react-statics": "^3.3.2",
+                "jss": "^10.0.3",
+                "jss-plugin-camel-case": "^10.0.3",
+                "jss-plugin-default-unit": "^10.0.3",
+                "jss-plugin-global": "^10.0.3",
+                "jss-plugin-nested": "^10.0.3",
+                "jss-plugin-props-sort": "^10.0.3",
+                "jss-plugin-rule-value-function": "^10.0.3",
+                "jss-plugin-vendor-prefixer": "^10.0.3",
+                "prop-types": "^15.7.2"
+            }
+        },
+        "@material-ui/system": {
+            "version": "4.9.14",
+            "resolved": "https://registry.npmjs.org/@material-ui/system/-/system-4.9.14.tgz",
+            "integrity": "sha512-oQbaqfSnNlEkXEziDcJDDIy8pbvwUmZXWNqlmIwDqr/ZdCK8FuV3f4nxikUh7hvClKV2gnQ9djh5CZFTHkZj3w==",
+            "requires": {
+                "@babel/runtime": "^7.4.4",
+                "@material-ui/utils": "^4.9.6",
+                "csstype": "^2.5.2",
+                "prop-types": "^15.7.2"
+            }
+        },
+        "@material-ui/types": {
+            "version": "5.1.0",
+            "resolved": "https://registry.npmjs.org/@material-ui/types/-/types-5.1.0.tgz",
+            "integrity": "sha512-7cqRjrY50b8QzRSYyhSpx4WRw2YuO0KKIGQEVk5J8uoz2BanawykgZGoWEqKm7pVIbzFDN0SpPcVV4IhOFkl8A=="
+        },
+        "@material-ui/utils": {
+            "version": "4.10.2",
+            "resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-4.10.2.tgz",
+            "integrity": "sha512-eg29v74P7W5r6a4tWWDAAfZldXIzfyO1am2fIsC39hdUUHm/33k6pGOKPbgDjg/U/4ifmgAePy/1OjkKN6rFRw==",
+            "requires": {
+                "@babel/runtime": "^7.4.4",
+                "prop-types": "^15.7.2",
+                "react-is": "^16.8.0"
+            }
+        },
         "@rkusa/linebreak": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/@rkusa/linebreak/-/linebreak-1.0.0.tgz",
@@ -1075,7 +1170,6 @@
             "version": "4.4.0",
             "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.0.tgz",
             "integrity": "sha512-/QfLHGpu+2fQOqQaXh8MG9q03bFENooTb/it4jr5kKaZlDQfWvjqWZg48AwzPVMBHlRuTRAY7hRHCEOXz5kV6w==",
-            "dev": true,
             "requires": {
                 "@types/react": "*"
             }
@@ -2478,6 +2572,11 @@
             "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.5.0.tgz",
             "integrity": "sha512-Z93QoXvodoVslA+PWNdk23Hze4RBYIkpb5h8I2HY2Tu2h7A0LpAgLcyrhrSUyo2/Oxm2l1fRZPs1e5hnxnliXA=="
         },
+        "bowser": {
+            "version": "1.9.4",
+            "resolved": "https://registry.npmjs.org/bowser/-/bowser-1.9.4.tgz",
+            "integrity": "sha512-9IdMmj2KjigRq6oWhmwv1W36pDuA4STQZ8q6YO9um+x07xgYNCD3Oou+WP/3L1HNz7iqythGet3/p4wvc8AAwQ=="
+        },
         "boxen": {
             "version": "1.3.0",
             "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz",
@@ -2878,6 +2977,11 @@
                 "type-detect": "^4.0.5"
             }
         },
+        "chain-function": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/chain-function/-/chain-function-1.0.1.tgz",
+            "integrity": "sha512-SxltgMwL9uCko5/ZCLiyG2B7R9fY4pDZUw7hJ4MhirdjBLosoDqkWABi3XMucddHdLiFJMb7PD2MZifZriuMTg=="
+        },
         "chalk": {
             "version": "2.4.2",
             "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
@@ -2888,6 +2992,11 @@
                 "supports-color": "^5.3.0"
             }
         },
+        "change-emitter": {
+            "version": "0.1.6",
+            "resolved": "https://registry.npmjs.org/change-emitter/-/change-emitter-0.1.6.tgz",
+            "integrity": "sha1-6LL+PX8at9aaMhma/5HqaTFAlRU="
+        },
         "character-parser": {
             "version": "2.2.0",
             "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz",
@@ -3614,6 +3723,11 @@
                 "shallow-clone": "^3.0.0"
             }
         },
+        "clsx": {
+            "version": "1.1.1",
+            "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz",
+            "integrity": "sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA=="
+        },
         "code-point-at": {
             "version": "1.1.0",
             "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
@@ -4263,6 +4377,15 @@
             "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz",
             "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4="
         },
+        "css-in-js-utils": {
+            "version": "2.0.1",
+            "resolved": "https://registry.npmjs.org/css-in-js-utils/-/css-in-js-utils-2.0.1.tgz",
+            "integrity": "sha512-PJF0SpJT+WdbVVt0AOYp9C8GnuruRlL/UFW7932nLWmFLQTaWEzTBQEx7/hn4BuV+WON75iAViSUJLiU3PKbpA==",
+            "requires": {
+                "hyphenate-style-name": "^1.0.2",
+                "isobject": "^3.0.1"
+            }
+        },
         "css-loader": {
             "version": "2.1.1",
             "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-2.1.1.tgz",
@@ -4317,6 +4440,15 @@
                 }
             }
         },
+        "css-vendor": {
+            "version": "2.0.8",
+            "resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-2.0.8.tgz",
+            "integrity": "sha512-x9Aq0XTInxrkuFeHKbYC7zWY8ai7qJ04Kxd9MnvbC1uO5DagxoHQjm4JvG+vCdXOoFtCjbL2XSZfxmoYa9uQVQ==",
+            "requires": {
+                "@babel/runtime": "^7.8.3",
+                "is-in-browser": "^1.0.2"
+            }
+        },
         "css-what": {
             "version": "2.1.3",
             "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz",
@@ -7032,6 +7164,11 @@
                 }
             }
         },
+        "hyphenate-style-name": {
+            "version": "1.0.3",
+            "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.3.tgz",
+            "integrity": "sha512-EcuixamT82oplpoJ2XU4pDtKGWQ7b00CD9f1ug9IaQ3p1bkHMiKCZ9ut9QDI6qsa6cpUuB+A/I+zLtdNK4n2DQ=="
+        },
         "i": {
             "version": "0.3.6",
             "resolved": "https://registry.npmjs.org/i/-/i-0.3.6.tgz",
@@ -7247,6 +7384,15 @@
             "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
             "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw=="
         },
+        "inline-style-prefixer": {
+            "version": "3.0.8",
+            "resolved": "https://registry.npmjs.org/inline-style-prefixer/-/inline-style-prefixer-3.0.8.tgz",
+            "integrity": "sha1-hVG45bTVcyROZqNLBPfTIHaitTQ=",
+            "requires": {
+                "bowser": "^1.7.3",
+                "css-in-js-utils": "^2.0.0"
+            }
+        },
         "inspect-function": {
             "version": "0.2.2",
             "resolved": "https://registry.npmjs.org/inspect-function/-/inspect-function-0.2.2.tgz",
@@ -7555,6 +7701,11 @@
                 "is-extglob": "^2.1.1"
             }
         },
+        "is-in-browser": {
+            "version": "1.1.3",
+            "resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz",
+            "integrity": "sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU="
+        },
         "is-installed-globally": {
             "version": "0.1.0",
             "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.1.0.tgz",
@@ -7922,6 +8073,84 @@
                 "verror": "1.10.0"
             }
         },
+        "jss": {
+            "version": "10.3.0",
+            "resolved": "https://registry.npmjs.org/jss/-/jss-10.3.0.tgz",
+            "integrity": "sha512-B5sTRW9B6uHaUVzSo9YiMEOEp3UX8lWevU0Fsv+xtRnsShmgCfIYX44bTH8bPJe6LQKqEXku3ulKuHLbxBS97Q==",
+            "requires": {
+                "@babel/runtime": "^7.3.1",
+                "csstype": "^2.6.5",
+                "is-in-browser": "^1.1.3",
+                "tiny-warning": "^1.0.2"
+            }
+        },
+        "jss-plugin-camel-case": {
+            "version": "10.3.0",
+            "resolved": "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.3.0.tgz",
+            "integrity": "sha512-tadWRi/SLWqLK3EUZEdDNJL71F3ST93Zrl9JYMjV0QDqKPAl0Liue81q7m/nFUpnSTXczbKDy4wq8rI8o7WFqA==",
+            "requires": {
+                "@babel/runtime": "^7.3.1",
+                "hyphenate-style-name": "^1.0.3",
+                "jss": "^10.3.0"
+            }
+        },
+        "jss-plugin-default-unit": {
+            "version": "10.3.0",
+            "resolved": "https://registry.npmjs.org/jss-plugin-default-unit/-/jss-plugin-default-unit-10.3.0.tgz",
+            "integrity": "sha512-tT5KkIXAsZOSS9WDSe8m8lEHIjoEOj4Pr0WrG0WZZsMXZ1mVLFCSsD2jdWarQWDaRNyMj/I4d7czRRObhOxSuw==",
+            "requires": {
+                "@babel/runtime": "^7.3.1",
+                "jss": "^10.3.0"
+            }
+        },
+        "jss-plugin-global": {
+            "version": "10.3.0",
+            "resolved": "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.3.0.tgz",
+            "integrity": "sha512-etYTG/y3qIR/vxZnKY+J3wXwObyBDNhBiB3l/EW9/pE3WHE//BZdK8LFvQcrCO48sZW1Z6paHo6klxUPP7WbzA==",
+            "requires": {
+                "@babel/runtime": "^7.3.1",
+                "jss": "^10.3.0"
+            }
+        },
+        "jss-plugin-nested": {
+            "version": "10.3.0",
+            "resolved": "https://registry.npmjs.org/jss-plugin-nested/-/jss-plugin-nested-10.3.0.tgz",
+            "integrity": "sha512-qWiEkoXNEkkZ+FZrWmUGpf+zBsnEOmKXhkjNX85/ZfWhH9dfGxUCKuJFuOWFM+rjQfxV4csfesq4hY0jk8Qt0w==",
+            "requires": {
+                "@babel/runtime": "^7.3.1",
+                "jss": "^10.3.0",
+                "tiny-warning": "^1.0.2"
+            }
+        },
+        "jss-plugin-props-sort": {
+            "version": "10.3.0",
+            "resolved": "https://registry.npmjs.org/jss-plugin-props-sort/-/jss-plugin-props-sort-10.3.0.tgz",
+            "integrity": "sha512-boetORqL/lfd7BWeFD3K+IyPqyIC+l3CRrdZr+NPq7Noqp+xyg/0MR7QisgzpxCEulk+j2CRcEUoZsvgPC4nTg==",
+            "requires": {
+                "@babel/runtime": "^7.3.1",
+                "jss": "^10.3.0"
+            }
+        },
+        "jss-plugin-rule-value-function": {
+            "version": "10.3.0",
+            "resolved": "https://registry.npmjs.org/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.3.0.tgz",
+            "integrity": "sha512-7WiMrKIHH3rwxTuJki9+7nY11r1UXqaUZRhHvqTD4/ZE+SVhvtD5Tx21ivNxotwUSleucA/8boX+NF21oXzr5Q==",
+            "requires": {
+                "@babel/runtime": "^7.3.1",
+                "jss": "^10.3.0",
+                "tiny-warning": "^1.0.2"
+            }
+        },
+        "jss-plugin-vendor-prefixer": {
+            "version": "10.3.0",
+            "resolved": "https://registry.npmjs.org/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.3.0.tgz",
+            "integrity": "sha512-sZQbrcZyP5V0ADjCLwUA1spVWoaZvM7XZ+2fSeieZFBj31cRsnV7X70FFDerMHeiHAXKWzYek+67nMDjhrZAVQ==",
+            "requires": {
+                "@babel/runtime": "^7.3.1",
+                "css-vendor": "^2.0.8",
+                "jss": "^10.3.0"
+            }
+        },
         "jstransformer": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-1.0.0.tgz",
@@ -7955,6 +8184,11 @@
             "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.1.tgz",
             "integrity": "sha512-l3hLhffs9zqoDe8zjmb/mAN4B8VT3L56EUvKNqLFVs9YlFA+zx7ke1DO8STAdDyYNkeSo1nKmjuvQeI12So8Xw=="
         },
+        "keycode": {
+            "version": "2.2.0",
+            "resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.0.tgz",
+            "integrity": "sha1-PQr1bce4uOXLqNCpfxByBO7CKwQ="
+        },
         "keygrip": {
             "version": "1.1.0",
             "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz",
@@ -8201,6 +8435,11 @@
             "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
             "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4="
         },
+        "lodash.merge": {
+            "version": "4.6.2",
+            "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+            "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="
+        },
         "lodash.padend": {
             "version": "4.6.1",
             "resolved": "https://registry.npmjs.org/lodash.padend/-/lodash.padend-4.6.1.tgz",
@@ -8212,6 +8451,11 @@
             "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=",
             "dev": true
         },
+        "lodash.throttle": {
+            "version": "4.1.1",
+            "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
+            "integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ="
+        },
         "lodash.union": {
             "version": "4.6.0",
             "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz",
@@ -8371,6 +8615,38 @@
             "resolved": "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz",
             "integrity": "sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg=="
         },
+        "material-ui": {
+            "version": "0.20.2",
+            "resolved": "https://registry.npmjs.org/material-ui/-/material-ui-0.20.2.tgz",
+            "integrity": "sha512-VeqgQkdvtK193w+FFvXDEwlVxI4rWk83eWbpYLeOIHDPWr3rbB9B075JRnJt/8IsI2X8q5Aia5W3+7m4KkleDg==",
+            "requires": {
+                "babel-runtime": "^6.23.0",
+                "inline-style-prefixer": "^3.0.8",
+                "keycode": "^2.1.8",
+                "lodash.merge": "^4.6.0",
+                "lodash.throttle": "^4.1.1",
+                "prop-types": "^15.5.7",
+                "react-event-listener": "^0.6.2",
+                "react-transition-group": "^1.2.1",
+                "recompose": "^0.26.0",
+                "simple-assign": "^0.1.0",
+                "warning": "^3.0.0"
+            },
+            "dependencies": {
+                "react-transition-group": {
+                    "version": "1.2.1",
+                    "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-1.2.1.tgz",
+                    "integrity": "sha512-CWaL3laCmgAFdxdKbhhps+c0HRGF4c+hdM4H23+FI1QBNUyx/AMeIJGWorehPNSaKnQNOAxL7PQmqMu78CDj3Q==",
+                    "requires": {
+                        "chain-function": "^1.0.0",
+                        "dom-helpers": "^3.2.0",
+                        "loose-envify": "^1.3.1",
+                        "prop-types": "^15.5.6",
+                        "warning": "^3.0.0"
+                    }
+                }
+            }
+        },
         "md5.js": {
             "version": "1.3.5",
             "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
@@ -13888,6 +14164,26 @@
                 "prop-types": "^15.6.0"
             }
         },
+        "react-event-listener": {
+            "version": "0.6.6",
+            "resolved": "https://registry.npmjs.org/react-event-listener/-/react-event-listener-0.6.6.tgz",
+            "integrity": "sha512-+hCNqfy7o9wvO6UgjqFmBzARJS7qrNoda0VqzvOuioEpoEXKutiKuv92dSz6kP7rYLmyHPyYNLesi5t/aH1gfw==",
+            "requires": {
+                "@babel/runtime": "^7.2.0",
+                "prop-types": "^15.6.0",
+                "warning": "^4.0.1"
+            },
+            "dependencies": {
+                "warning": {
+                    "version": "4.0.3",
+                    "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
+                    "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
+                    "requires": {
+                        "loose-envify": "^1.0.0"
+                    }
+                }
+            }
+        },
         "react-grid-layout": {
             "version": "0.18.3",
             "resolved": "https://registry.npmjs.org/react-grid-layout/-/react-grid-layout-0.18.3.tgz",
@@ -14224,6 +14520,24 @@
                 "resolve": "^1.1.6"
             }
         },
+        "recompose": {
+            "version": "0.26.0",
+            "resolved": "https://registry.npmjs.org/recompose/-/recompose-0.26.0.tgz",
+            "integrity": "sha512-KwOu6ztO0mN5vy3+zDcc45lgnaUoaQse/a5yLVqtzTK13czSWnFGmXbQVmnoMgDkI5POd1EwIKSbjU1V7xdZog==",
+            "requires": {
+                "change-emitter": "^0.1.2",
+                "fbjs": "^0.8.1",
+                "hoist-non-react-statics": "^2.3.1",
+                "symbol-observable": "^1.0.4"
+            },
+            "dependencies": {
+                "hoist-non-react-statics": {
+                    "version": "2.5.5",
+                    "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz",
+                    "integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw=="
+                }
+            }
+        },
         "redent": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz",
@@ -14988,6 +15302,11 @@
             "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz",
             "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA=="
         },
+        "simple-assign": {
+            "version": "0.1.0",
+            "resolved": "https://registry.npmjs.org/simple-assign/-/simple-assign-0.1.0.tgz",
+            "integrity": "sha1-F/0wZqXz13OPUDIbsPFMooHMS6o="
+        },
         "simple-concat": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.0.tgz",
@@ -15827,6 +16146,11 @@
                 "has-flag": "^3.0.0"
             }
         },
+        "symbol-observable": {
+            "version": "1.2.0",
+            "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz",
+            "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ=="
+        },
         "symbol-tree": {
             "version": "3.2.4",
             "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
@@ -16122,6 +16446,11 @@
             "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz",
             "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="
         },
+        "tiny-warning": {
+            "version": "1.0.3",
+            "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
+            "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="
+        },
         "tinycolor2": {
             "version": "1.4.1",
             "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.1.tgz",
diff --git a/package.json b/package.json
index 5e7598376..75dc817f5 100644
--- a/package.json
+++ b/package.json
@@ -121,6 +121,7 @@
         "@hig/flyout": "^1.2.1",
         "@hig/theme-context": "^2.1.3",
         "@hig/theme-data": "^2.16.1",
+        "@material-ui/core": "^4.11.0",
         "@types/google-maps": "^3.2.2",
         "@types/webscopeio__react-textarea-autocomplete": "^4.6.1",
         "@webscopeio/react-textarea-autocomplete": "^4.7.0",
@@ -172,6 +173,7 @@
         "jsonschema": "^1.2.5",
         "libxmljs": "^0.19.7",
         "lodash": "^4.17.15",
+        "material-ui": "^0.20.2",
         "mobile-detect": "^1.4.4",
         "mobx": "^5.15.3",
         "mobx-react": "^5.3.5",
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 85da621e0..2e0323ede 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1,4 +1,4 @@
-import { runInAction } from "mobx";
+import { runInAction, action } from "mobx";
 import { extname, basename } from "path";
 import { DateField } from "../../fields/DateField";
 import { Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym } from "../../fields/Doc";
@@ -50,6 +50,7 @@ import { DashWebRTCVideo } from "../views/webcam/DashWebRTCVideo";
 import { DocumentType } from "./DocumentTypes";
 import { Networking } from "../Network";
 import { Upload } from "../../server/SharedMediaTypes";
+import { MainView } from "../views/MainView";
 const path = require('path');
 
 export interface DocumentOptions {
@@ -866,6 +867,7 @@ export namespace DocUtils {
     export function MakeLinkToActiveAudio(doc: Doc) {
         DocUtils.ActiveRecordings.map(d => DocUtils.MakeLink({ doc: doc }, { doc: d }, "audio link", "audio timeline"));
     }
+
     export function MakeLink(source: { doc: Doc }, target: { doc: Doc }, linkRelationship: string = "", id?: string) {
         const sv = DocumentManager.Instance.getDocumentView(source.doc);
         if (sv && sv.props.ContainingCollectionDoc === target.doc) return;
@@ -877,6 +879,11 @@ export namespace DocUtils {
 
         Doc.GetProto(source.doc).links = ComputedField.MakeFunction("links(self)");
         Doc.GetProto(target.doc).links = ComputedField.MakeFunction("links(self)");
+
+        runInAction(() => { MainView.linkCreated = true; });
+        console.log("link created");
+        runInAction(() => { setTimeout("MainView.changeLinkCreated()", 2000); });
+
         return linkDoc;
     }
 
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index c85849adb..369d6f4ae 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -79,7 +79,15 @@ export default class KeyManager {
                 // MarqueeView.DragMarquee = !MarqueeView.DragMarquee; // bcz: this needs a better disclosure UI
                 break;
             case "escape":
+                // if (DocumentLinksButton.StartLink) {
+                //     if (DocumentLinksButton.StartLink.Document) {
+                //         action((e: React.PointerEvent<HTMLDivElement>) => {
+                //             Doc.UnBrushDoc(DocumentLinksButton.StartLink?.Document as Doc);
+                //         });
+                //     }
+                // }
                 DocumentLinksButton.StartLink = undefined;
+
                 const main = MainView.Instance;
                 Doc.SetSelectedTool(InkTool.None);
                 var doDeselect = true;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 662232810..4898e114a 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -59,6 +59,7 @@ import { DocumentManager } from '../util/DocumentManager';
 import { DocumentLinksButton } from './nodes/DocumentLinksButton';
 import { LinkMenu } from './linking/LinkMenu';
 import { LinkDocPreview } from './nodes/LinkDocPreview';
+import { Fade } from '@material-ui/core';
 
 @observer
 export class MainView extends React.Component {
@@ -82,6 +83,8 @@ export class MainView extends React.Component {
 
     public isPointerDown = false;
 
+    @observable public static linkCreated: boolean = false;
+
 
     componentDidMount() {
         DocServer.setPlaygroundFields(["dataTransition", "_viewTransition", "_panX", "_panY", "_viewScale", "_viewType"]); // can play with these fields on someone else's
@@ -362,6 +365,11 @@ export class MainView extends React.Component {
         }
     }
 
+    @action
+    public static changeLinkCreated = () => {
+        MainView.linkCreated = !MainView.linkCreated;
+    }
+
     @action
     onPointerMove = (e: PointerEvent) => {
         this.flyoutWidth = Math.max(e.clientX, 0);
@@ -606,6 +614,15 @@ export class MainView extends React.Component {
                 {this.mainContent}
             </GestureOverlay>
             <PreviewCursor />
+            {MainView.linkCreated ? <div className="mainView-linkCreated" style={{
+                border: "2px solid red", top: screenTop / 2,
+                left: screenLeft / 2, width: "50px", height: "20px", zIndex: 10000,
+                fontSize: "12px"
+            }}>
+                {/* <Fade in={MainView.linkCreated}> */}
+                link created!
+                {/* </Fade> */}
+            </div> : null}
             {DocumentLinksButton.EditLink ? <LinkMenu location={DocumentLinksButton.EditLinkLoc} docView={DocumentLinksButton.EditLink} addDocTab={DocumentLinksButton.EditLink.props.addDocTab} changeFlyout={emptyFunction} /> : (null)}
             {LinkDocPreview.LinkInfo ? <LinkDocPreview location={LinkDocPreview.LinkInfo.Location} backgroundColor={this.defaultBackgroundColors}
                 linkDoc={LinkDocPreview.LinkInfo.linkDoc} linkSrc={LinkDocPreview.LinkInfo.linkSrc} href={LinkDocPreview.LinkInfo.href}
diff --git a/src/client/views/collections/CollectionLinearView.scss b/src/client/views/collections/CollectionLinearView.scss
index 647ce1b01..134d62d4f 100644
--- a/src/client/views/collections/CollectionLinearView.scss
+++ b/src/client/views/collections/CollectionLinearView.scss
@@ -10,6 +10,7 @@
         height: 100%;
 
         >span {
+
             margin-top: 8px;
             margin-left: 4px;
             //margin-bottom: 2px;
@@ -18,7 +19,6 @@
             display: inline-block;
             border-radius: 18px;
             margin-right: 6px;
-            padding: "4px";
             cursor: pointer;
         }
 
diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx
index df8e2c58e..a180991d0 100644
--- a/src/client/views/collections/CollectionLinearView.tsx
+++ b/src/client/views/collections/CollectionLinearView.tsx
@@ -79,7 +79,14 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
 
     @action
     exitLongLinks = () => {
-        DocumentLinksButton.StartLink = undefined
+        if (DocumentLinksButton.StartLink) {
+            if (DocumentLinksButton.StartLink.Document) {
+                action((e: React.PointerEvent<HTMLDivElement>) => {
+                    Doc.UnBrushDoc(DocumentLinksButton.StartLink?.Document as Doc);
+                });
+            }
+        }
+        DocumentLinksButton.StartLink = undefined;
     }
 
     render() {
@@ -146,8 +153,12 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
                     background: backgroundColor === color ? "black" : backgroundColor,
                 }}
                     onPointerDown={e => e.stopPropagation()} >
-                    <span style={{ fontSize: "12.5px", display: "inline", whiteSpace: "nowrap" }}>
-                        Creating Link From: {DocumentLinksButton.StartLink.title} </span>
+                    <span style={{
+                        fontSize: "12.5px", display: "inline", whiteSpace: "nowrap",
+                        paddingLeft: "8px", paddingRight: "8px", paddingTop: "4px",
+                        paddingBottom: "10px"
+                    }}>
+                        Creating link from: {DocumentLinksButton.StartLink.title} </span>
 
                     <FontAwesomeIcon icon="times-circle" size="lg" style={{ color: "red" }}
                         onClick={this.exitLongLinks} />
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index d04cdd34c..27fbbc7fc 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -10,6 +10,7 @@ import React = require("react");
 import { DocUtils } from "../../documents/Documents";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { LinkDocPreview } from "./LinkDocPreview";
+import { MainView } from "../MainView";
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -56,6 +57,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
     onLinkButtonDown = (e: React.PointerEvent): void => {
         setupMoveUpEvents(this, e, this.onLinkButtonMoved, emptyFunction, action((e, doubleTap) => {
             if (doubleTap && this.props.InMenu) {
+                //action(() => Doc.BrushDoc(this.props.View.Document));
                 DocumentLinksButton.StartLink = this.props.View;
             } else if (!!!this.props.InMenu) {
                 DocumentLinksButton.EditLink = this.props.View;
@@ -68,6 +70,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
     onLinkClick = (e: React.MouseEvent): void => {
         if (this.props.InMenu) {
             DocumentLinksButton.StartLink = this.props.View;
+            //action(() => Doc.BrushDoc(this.props.View.Document));
         } else if (!!!this.props.InMenu) {
             DocumentLinksButton.EditLink = this.props.View;
             DocumentLinksButton.EditLinkLoc = [e.clientX + 10, e.clientY];
@@ -79,6 +82,9 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
             if (doubleTap) {
                 if (DocumentLinksButton.StartLink === this.props.View) {
                     DocumentLinksButton.StartLink = undefined;
+                    // action((e: React.PointerEvent<HTMLDivElement>) => {
+                    //     Doc.UnBrushDoc(this.props.View.Document);
+                    // });
                 } else {
                     DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View &&
                         DocUtils.MakeLink({ doc: DocumentLinksButton.StartLink.props.Document }, { doc: this.props.View.props.Document }, "long drag");
@@ -90,6 +96,9 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
     finishLinkClick = () => {
         if (DocumentLinksButton.StartLink === this.props.View) {
             DocumentLinksButton.StartLink = undefined;
+            // action((e: React.PointerEvent<HTMLDivElement>) => {
+            //     Doc.UnBrushDoc(this.props.View.Document);
+            // });
         } else {
             DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View &&
                 DocUtils.MakeLink({ doc: DocumentLinksButton.StartLink.props.Document }, { doc: this.props.View.props.Document }, "long drag");
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 6820b2047..0d7029576 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -116,7 +116,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
     private holdDisposer?: InteractionUtils.MultiTouchEventDisposer;
 
-    public get title() { return this.props.Document.title }
+    public get title() { return this.props.Document.title; }
     public get displayName() { return "DocumentView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive
     public get ContentDiv() { return this._mainCont.current; }
     get active() { return SelectionManager.IsSelected(this, true) || this.props.parentActive(true); }
@@ -1191,7 +1191,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             id={this.props.Document[Id]}
             ref={this._mainCont} onKeyDown={this.onKeyDown}
             onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick}
-            onPointerEnter={action(() => Doc.BrushDoc(this.props.Document))}
+            onPointerEnter={action(() => { Doc.BrushDoc(this.props.Document); })}
             onPointerLeave={action((e: React.PointerEvent<HTMLDivElement>) => {
                 let entered = false;
                 const target = document.elementFromPoint(e.nativeEvent.x, e.nativeEvent.y);
@@ -1200,7 +1200,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                         entered = true;
                     }
                 }
+                // if (this.props.Document !== DocumentLinksButton.StartLink?.Document) {
                 !entered && Doc.UnBrushDoc(this.props.Document);
+                //}
+
             })}
             style={{
                 transformOrigin: this._animateScalingTo ? "center center" : undefined,
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index dc7d8c7ca..276c79453 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1084,7 +1084,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
 
     @action
     onFocused = (e: React.FocusEvent): void => {
-        console.log("FOUCSS")
+        console.log("FOUCSS");
         FormattedTextBox.FocusedBox = this;
         this.tryUpdateHeight();
 
@@ -1216,7 +1216,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     }
     public static HadSelection: boolean = false;
     onBlur = (e: any) => {
-        console.log("BLURRR")
+        console.log("BLURRR");
         FormattedTextBox.HadSelection = window.getSelection()?.toString() !== "";
         //DictationManager.Controls.stop(false);
         this.endUndoTypingBatch();
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index e4d11dd4d..8a7b91e49 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -832,12 +832,14 @@ export namespace Doc {
         })(doc);
     }
     export function BrushDoc(doc: Doc) {
+        console.log("brushed");
         if (!doc || doc[AclSym] === AclPrivate || Doc.GetProto(doc)[AclSym] === AclPrivate) return doc;
         brushManager.BrushedDoc.set(doc, true);
         brushManager.BrushedDoc.set(Doc.GetProto(doc), true);
         return doc;
     }
     export function UnBrushDoc(doc: Doc) {
+        console.log("unbrushed");
         if (!doc || doc[AclSym] === AclPrivate || Doc.GetProto(doc)[AclSym] === AclPrivate) return doc;
         brushManager.BrushedDoc.delete(doc);
         brushManager.BrushedDoc.delete(Doc.GetProto(doc));
-- 
cgit v1.2.3-70-g09d2


From 1daf0a496175267d86f495bb24aa5140b6a9eeab Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 2 Jul 2020 14:05:33 -0400
Subject: small clanup in Mobile related code

---
 src/client/views/GestureOverlay.tsx                |  2 +-
 src/client/views/collections/CollectionView.tsx    | 21 ++++++++--------
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 11 ++++----
 src/client/views/nodes/DocumentView.tsx            | 29 +++++++++++-----------
 .../views/nodes/formattedText/FormattedTextBox.tsx |  2 +-
 5 files changed, 33 insertions(+), 32 deletions(-)

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

diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 839ab09c7..487467b2b 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -264,7 +264,7 @@ export default class GestureOverlay extends Touchable {
     handleHandDown = async (e: React.TouchEvent) => {
         this._holdTimer && clearTimeout(this._holdTimer);
 
-        // this chunk of code helps us keep track of which touch events ar e associated with a hand event
+        // this chunk of code helps us keep track of which touch events are associated with a hand event
         // so that if a hand is held down, but a second hand is interacting with dash, the second hand's events
         // won't interfere with the first hand's events.
         const fingers = new Array<React.Touch>();
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 35196b634..300309882 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -231,12 +231,12 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
 
 
     setupViewTypes(category: string, func: (viewType: CollectionViewType) => Doc, addExtras: boolean) {
-        const existingVm = ContextMenu.Instance?.findByDescription(category);
+        const existingVm = ContextMenu.Instance.findByDescription(category);
         const subItems = existingVm && "subitems" in existingVm ? existingVm.subitems : [];
 
         subItems.push({ description: "Freeform", event: () => func(CollectionViewType.Freeform), icon: "signature" });
         if (addExtras && CollectionView._safeMode) {
-            ContextMenu.Instance?.addItem({ description: "Test Freeform", event: () => func(CollectionViewType.Invalid), icon: "project-diagram" });
+            ContextMenu.Instance.addItem({ description: "Test Freeform", event: () => func(CollectionViewType.Invalid), icon: "project-diagram" });
         }
         subItems.push({ description: "Schema", event: () => func(CollectionViewType.Schema), icon: "th-list" });
         subItems.push({ description: "Tree", event: () => func(CollectionViewType.Tree), icon: "tree" });
@@ -255,11 +255,12 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
             subItems.push({ description: "Custom", icon: "fingerprint", event: AddCustomFreeFormLayout(this.props.Document, this.props.fieldKey) });
         }
         addExtras && subItems.push({ description: "lightbox", event: action(() => this._isLightboxOpen = true), icon: "eye" });
-        !existingVm && ContextMenu.Instance?.addItem({ description: category, subitems: subItems, icon: "eye" });
+        !existingVm && ContextMenu.Instance.addItem({ description: category, subitems: subItems, icon: "eye" });
     }
 
     onContextMenu = (e: React.MouseEvent): void => {
-        if (!e.isPropagationStopped() && this.props.Document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
+        const cm = ContextMenu.Instance;
+        if (cm && !e.isPropagationStopped() && this.props.Document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
             this.setupViewTypes("Add a Perspective...", vtype => {
                 const newRendition = Doc.MakeAlias(this.props.Document);
                 newRendition._viewType = vtype;
@@ -267,7 +268,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
                 return newRendition;
             }, false);
 
-            const existing = ContextMenu.Instance?.findByDescription("Options...");
+            const existing = cm.findByDescription("Options...");
             const layoutItems = existing && "subitems" in existing ? existing.subitems : [];
             layoutItems.push({ description: `${this.props.Document.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.props.Document.forceActive = !this.props.Document.forceActive, icon: "project-diagram" });
             if (this.props.Document.childLayout instanceof Doc) {
@@ -278,9 +279,9 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
             }
             layoutItems.push({ description: `${this.props.Document.isInPlaceContainer ? "Unset" : "Set"} inPlace Container`, event: () => this.props.Document.isInPlaceContainer = !this.props.Document.isInPlaceContainer, icon: "project-diagram" });
 
-            !existing && ContextMenu.Instance?.addItem({ description: "Options...", subitems: layoutItems, icon: "hand-point-right" });
+            !existing && cm.addItem({ description: "Options...", subitems: layoutItems, icon: "hand-point-right" });
 
-            const existingOnClick = ContextMenu.Instance?.findByDescription("OnClick...");
+            const existingOnClick = cm.findByDescription("OnClick...");
             const onClicks = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
             const funcs = [
                 { key: "onChildClick", name: "On Child Clicked" },
@@ -296,13 +297,13 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
                     icon: "edit",
                     event: () => this.props.Document[StrCast(childClick.targetScriptKey)] = ObjectField.MakeCopy(ScriptCast(childClick.data)),
                 }));
-            !existingOnClick && ContextMenu.Instance?.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
+            !existingOnClick && cm.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
 
             if (!Doc.UserDoc().noviceMode) {
-                const more = ContextMenu.Instance?.findByDescription("More...");
+                const more = cm.findByDescription("More...");
                 const moreItems = more && "subitems" in more ? more.subitems : [];
                 moreItems.push({ description: "Export Image Hierarchy", icon: "columns", event: () => ImageUtils.ExportHierarchyToFileSystem(this.props.Document) });
-                !more && ContextMenu.Instance?.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" });
+                !more && cm.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" });
             }
         }
     }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index ae7213d0b..21b0473c9 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -602,7 +602,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     pan = (e: PointerEvent | React.Touch | { clientX: number, clientY: number }): void => {
         // bcz: theres should be a better way of doing these than referencing these static instances directly
         MarqueeOptionsMenu.Instance?.fadeOut(true);// I think it makes sense for the marquee menu to go away when panned. -syip2
-        // PDFMenu.Instance.fadeOut(true);
 
         const [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
         this.setPan((this.Document._panX || 0) - dx, (this.Document._panY || 0) - dy, undefined, true);
@@ -1204,17 +1203,17 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     private thumbIdentifier?: number;
 
     onContextMenu = (e: React.MouseEvent) => {
-        if (this.props.annotationsKey) return;
+        if (this.props.annotationsKey || !ContextMenu.Instance) return;
 
-        const appearance = ContextMenu.Instance?.findByDescription("Appearance...");
+        const appearance = ContextMenu.Instance.findByDescription("Appearance...");
         const appearanceItems = appearance && "subitems" in appearance ? appearance.subitems : [];
         appearanceItems.push({ description: "reset view", event: () => { this.props.Document._panX = this.props.Document._panY = 0; this.props.Document[this.scaleFieldKey] = 1; }, icon: "compress-arrows-alt" });
         appearanceItems.push({ description: `${this.fitToContent ? "Unset" : "Set"} Fit To Container`, event: () => this.Document._fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" });
         appearanceItems.push({ description: `${this.Document.useClusters ? "Uncluster" : "Use Clusters"}`, event: () => this.updateClusters(!this.Document.useClusters), icon: "braille" });
         appearanceItems.push({ description: "Use Background Color as Default", event: () => Cast(Doc.UserDoc().emptyCollection, Doc, null)._backgroundColor = StrCast(this.layoutDoc._backgroundColor), icon: "palette" });
-        !appearance && ContextMenu.Instance?.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "eye" });
+        !appearance && ContextMenu.Instance.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "eye" });
 
-        const options = ContextMenu.Instance?.findByDescription("Options...");
+        const options = ContextMenu.Instance.findByDescription("Options...");
         const optionItems = options && "subitems" in options ? options.subitems : [];
         !this.props.isAnnotationOverlay &&
             optionItems.push({ description: (this.showTimeline ? "Close" : "Open") + " Animation Timeline", event: action(() => this.showTimeline = !this.showTimeline), icon: faEye });
@@ -1254,7 +1253,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                 }
             });
         }
-        !options && ContextMenu.Instance?.addItem({ description: "Options...", subitems: optionItems, icon: "eye" });
+        !options && ContextMenu.Instance.addItem({ description: "Options...", subitems: optionItems, icon: "eye" });
 
     }
     @observable showTimeline = false;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 8d122baeb..732089068 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -736,21 +736,22 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         }
 
         const cm = ContextMenu.Instance;
+        if (!cm) return;
 
         const customScripts = Cast(this.props.Document.contextMenuScripts, listSpec(ScriptField), []);
         Cast(this.props.Document.contextMenuLabels, listSpec("string"), []).forEach((label, i) =>
-            cm?.addItem({ description: label, event: () => customScripts[i]?.script.run({ this: this.layoutDoc, self: this.rootDoc }), icon: "sticky-note" }));
+            cm.addItem({ description: label, event: () => customScripts[i]?.script.run({ this: this.layoutDoc, self: this.rootDoc }), icon: "sticky-note" }));
         this.props.contextMenuItems?.().forEach(item =>
-            cm?.addItem({ description: item.label, event: () => item.script.script.run({ this: this.layoutDoc, self: this.rootDoc }), icon: "sticky-note" }));
+            cm.addItem({ description: item.label, event: () => item.script.script.run({ this: this.layoutDoc, self: this.rootDoc }), icon: "sticky-note" }));
 
         const options = cm.findByDescription("Options...");
         const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : [];
         const templateDoc = Cast(this.props.Document[StrCast(this.props.Document.layoutKey)], Doc, null);
         templateDoc && optionItems.push({ description: "Open Template   ", event: () => this.props.addDocTab(templateDoc, "onRight"), icon: "eye" });
         optionItems.push({ description: "Toggle Show Each Link Dot", event: () => this.layoutDoc.showLinks = !this.layoutDoc.showLinks, icon: "eye" });
-        !options && cm?.addItem({ description: "Options...", subitems: optionItems, icon: "compass" });
+        !options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "compass" });
 
-        const existingOnClick = cm?.findByDescription("OnClick...");
+        const existingOnClick = cm.findByDescription("OnClick...");
         const onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
         onClicks.push({ description: "Enter Portal", event: this.makeIntoPortal, icon: "window-restore" });
         onClicks.push({ description: "Toggle Detail", event: () => this.Document.onClick = ScriptField.MakeScript(`toggleDetail(self, "${this.Document.layoutKey}")`), icon: "window-restore" });
@@ -759,17 +760,17 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         onClicks.push({ description: this.Document.isLinkButton ? "Remove Follow Behavior" : "Follow Link on Right", event: this.toggleFollowOnRight, icon: "concierge-bell" });
         onClicks.push({ description: this.Document.isLinkButton || this.onClickHandler ? "Remove Click Behavior" : "Follow Link", event: this.toggleLinkButtonBehavior, icon: "concierge-bell" });
         onClicks.push({ description: "Edit onClick Script", event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.props.Document, undefined, "onClick"), "edit onClick"), icon: "edit" });
-        !existingOnClick && cm?.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
+        !existingOnClick && cm.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
 
         const funcs: ContextMenuProps[] = [];
         if (this.layoutDoc.onDragStart) {
             funcs.push({ description: "Drag an Alias", icon: "edit", event: () => this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getAlias(this.dragFactory)')) });
             funcs.push({ description: "Drag a Copy", icon: "edit", event: () => this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)')) });
             funcs.push({ description: "Drag Document", icon: "edit", event: () => this.layoutDoc.onDragStart = undefined });
-            cm?.addItem({ description: "OnDrag...", subitems: funcs, icon: "asterisk" });
+            cm.addItem({ description: "OnDrag...", subitems: funcs, icon: "asterisk" });
         }
 
-        const more = cm?.findByDescription("More...");
+        const more = cm.findByDescription("More...");
         const moreItems = more && "subitems" in more ? more.subitems : [];
         if (!Doc.UserDoc().noviceMode) {
             moreItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc), icon: "concierge-bell" });
@@ -798,16 +799,16 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         moreItems.push({ description: "Copy ID", event: () => Utils.CopyText(Utils.prepend("/doc/" + this.props.Document[Id])), icon: "fingerprint" });
         moreItems.push({ description: "Delete", event: this.deleteClicked, icon: "trash" });
         moreItems.push({ description: "Share", event: () => SharingManager.Instance.open(this), icon: "external-link-alt" });
-        !more && cm?.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" });
-        cm?.moveAfter(cm?.findByDescription("More...")!, cm?.findByDescription("OnClick...")!);
+        !more && cm.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" });
+        cm.moveAfter(cm.findByDescription("More...")!, cm.findByDescription("OnClick...")!);
 
-        const help = cm?.findByDescription("Help...");
+        const help = cm.findByDescription("Help...");
         const helpItems: ContextMenuProps[] = help && "subitems" in help ? help.subitems : [];
         helpItems.push({ description: "Text Shortcuts Ctrl+/", event: () => this.props.addDocTab(Docs.Create.PdfDocument("http://localhost:1050/assets/cheat-sheet.pdf", { _width: 300, _height: 300 }), "onRight"), icon: "keyboard" });
         helpItems.push({ description: "Show Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "onRight"), icon: "layer-group" });
-        cm?.addItem({ description: "Help...", subitems: helpItems, icon: "question" });
+        cm.addItem({ description: "Help...", subitems: helpItems, icon: "question" });
 
-        const existingAcls = cm?.findByDescription("Privacy...");
+        const existingAcls = cm.findByDescription("Privacy...");
         const aclItems: ContextMenuProps[] = existingAcls && "subitems" in existingAcls ? existingAcls.subitems : [];
         aclItems.push({ description: "Make Add Only", event: () => this.setAcl("addOnly"), icon: "concierge-bell" });
         aclItems.push({ description: "Make Read Only", event: () => this.setAcl("readOnly"), icon: "concierge-bell" });
@@ -815,7 +816,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         aclItems.push({ description: "Make Editable", event: () => this.setAcl("write"), icon: "concierge-bell" });
         aclItems.push({ description: "Test Private", event: () => this.testAcl("ownerOnly"), icon: "concierge-bell" });
         aclItems.push({ description: "Test Readonly", event: () => this.testAcl("readOnly"), icon: "concierge-bell" });
-        !existingAcls && cm?.addItem({ description: "Privacy...", subitems: aclItems, icon: "question" });
+        !existingAcls && cm.addItem({ description: "Privacy...", subitems: aclItems, icon: "question" });
 
         // const recommender_subitems: ContextMenuProps[] = [];
 
@@ -876,7 +877,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 // DocumentViews should stop propagation of this event
                 e.stopPropagation();
             }
-            ContextMenu.Instance?.displayMenu(e.pageX - 15, e.pageY - 15);
+            cm.displayMenu(e.pageX - 15, e.pageY - 15);
             !SelectionManager.IsSelected(this, true) && SelectionManager.SelectDoc(this, false);
         });
         const path = this.props.LibraryPath.reduce((p: string, d: Doc) => p + "/" + (Doc.AreProtosEqual(d, (Doc.UserDoc()["tabs-button-library"] as Doc).sourcePanel as Doc) ? "" : d.title), "");
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 270fcb01c..b127cd024 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -190,7 +190,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                 }
             }
             const state = this._editorView.state.apply(tx);
-            this._editorView?.updateState(state);
+            this._editorView.updateState(state);
             (tx.storedMarks && !this._editorView.state.storedMarks) && (this._editorView.state.storedMarks = tx.storedMarks);
 
             const tsel = this._editorView.state.selection.$from;
-- 
cgit v1.2.3-70-g09d2


From 5022535496730c0b02db5fb74a1e1e224bcaf745 Mon Sep 17 00:00:00 2001
From: anika-ahluwalia <anika.ahluwalia@gmail.com>
Date: Thu, 2 Jul 2020 14:46:24 -0500
Subject: cleaned text link preview

---
 .../views/nodes/formattedText/FormattedTextBox.tsx |   2 +-
 .../formattedText/FormattedTextBoxComment.scss     |  47 ++++++++--
 .../formattedText/FormattedTextBoxComment.tsx      | 100 ++++++++++++++-------
 3 files changed, 105 insertions(+), 44 deletions(-)

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

diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 1ffa16e43..d4222b4bc 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1430,7 +1430,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                                 setTimeout(() => this._editorView!.focus(), 500);
                                 e.stopPropagation();
                             }} >
-                            <FontAwesomeIcon className="formattedTExtBox-audioFont"
+                            <FontAwesomeIcon className="formattedTextBox-audioFont"
                                 style={{ color: this._recording ? "red" : "blue", opacity: this._recording ? 1 : 0.5, display: this.props.isSelected() ? "" : "none" }} icon={"microphone"} size="sm" />
                         </div>}
                 </div>
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss b/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss
index adef7f81f..9089e7039 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss
@@ -36,32 +36,61 @@
     border-top-color: white;
 }
 
-.FormattedTextBox-buttons {
+.FormattedTextBoxComment-buttons {
     display: none;
     position: absolute;
     top: 50%;
     right: 0;
     transform: translateY(-50%);
 
-    .FormattedTextBox-button {
+    .FormattedTextBoxComment-button {
         width: 20px;
         height: 20px;
         margin: 0;
         margin-right: 6px;
         border-radius: 50%;
         pointer-events: auto;
-        background-color: rgb(53, 146, 199);
-        color: rgb(150, 211, 248);
+        background-color: rgb(38, 40, 41);
+        color: rgb(178, 181, 184);
         font-size: 65%;
         transition: transform 0.2s;
         text-align: center;
         position: relative;
 
-        .FormattedTextBox-fa-icon {
-            position: absolute;
-            top: 50%;
-            left: 50%;
-            transform: translate(-50%, -50%);
+        // margin-top: "auto";
+        //     margin-bottom: "auto";
+        //     background: black;
+        //     color: white;
+        //     display: inline-block;
+        //     border-radius: 18px;
+        //     font-size: 12.5px;
+        //     width: 18px;
+        //     height: 18px;
+        //     margin-top: auto;
+        //     margin-bottom: auto;
+        //     margin-right: 3px;
+        //     cursor: pointer;
+        //     transition: transform 0.2s;
+
+        .FormattedTextBoxComment-fa-icon {
+            margin-top: "auto";
+            margin-bottom: "auto";
+            background: black;
+            color: white;
+            display: inline-block;
+            border-radius: 18px;
+            font-size: 12.5px;
+            width: 18px;
+            height: 18px;
+            margin-top: auto;
+            margin-bottom: auto;
+            margin-right: 3px;
+            cursor: pointer;
+            transition: transform 0.2s;
+            // position: absolute;
+            // top: 50%;
+            // left: 50%;
+            // transform: translate(-50%, -50%);
         }
 
         &:last-child {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
index 21033f315..56826e5c7 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -84,8 +84,8 @@ export class FormattedTextBoxComment {
             FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipText);
             FormattedTextBoxComment.tooltip.className = "FormattedTextBox-tooltip";
             FormattedTextBoxComment.tooltip.style.pointerEvents = "all";
-            FormattedTextBoxComment.tooltip.style.maxWidth = "350px";
-            FormattedTextBoxComment.tooltip.style.maxHeight = "250px";
+            FormattedTextBoxComment.tooltip.style.maxWidth = "200px";
+            FormattedTextBoxComment.tooltip.style.maxHeight = "206px";
             FormattedTextBoxComment.tooltip.style.width = "100%";
             FormattedTextBoxComment.tooltip.style.height = "100%";
             FormattedTextBoxComment.tooltip.style.overflow = "hidden";
@@ -241,50 +241,82 @@ export class FormattedTextBoxComment {
                             }
                             if (target?.author) {
                                 FormattedTextBoxComment.showCommentbox("", view, nbef);
-                                const docPreview = <div style={{ backgroundColor: "white", border: "7px solid white" }}>
+                                const docPreview = <div style={{ backgroundColor: "white", border: "8px solid white" }}>
                                     {target.title}
                                     <div className="wrapper" style={{ float: "right" }}>
                                         <div title="Delete link" className="FormattedTextBoxComment-button" style={{
                                             display: "inline",
-                                            paddingLeft: "5px",
-                                            paddingRight: "5px"
+                                            paddingLeft: "6px",
+                                            paddingRight: "6px",
+                                            paddingTop: "2.5px",
+                                            paddingBottom: "2.5px",
+                                            width: "20px",
+                                            height: "20px",
+                                            margin: 0,
+                                            marginRight: "6px",
+                                            borderRadius: "50%",
+                                            pointerEvents: "auto",
+                                            backgroundColor: "rgb(38, 40, 41)",
+                                            color: "rgb(178, 181, 184)",
+                                            transition: "transform 0.2s",
+                                            textAlign: "center",
+                                            position: "relative"
                                         }} ref={(r) => this._deleteRef = r}>
-                                            <FontAwesomeIcon className="FormattedTextBox-fa-icon" icon="trash" size="sm"
-                                            /></div>
+                                            <FontAwesomeIcon className="FormattedTextBox-fa-icon" icon="trash"
+                                                size="sm" /></div>
                                         <div title="Follow link" className="FormattedTextBoxComment-button" style={{
                                             display: "inline",
-                                            paddingRight: "5px"
+                                            paddingLeft: "6px",
+                                            paddingRight: "6px",
+                                            paddingTop: "2.5px",
+                                            paddingBottom: "2.5px",
+                                            width: "20px",
+                                            height: "20px",
+                                            margin: 0,
+                                            marginRight: "6px",
+                                            borderRadius: "50%",
+                                            pointerEvents: "auto",
+                                            backgroundColor: "rgb(38, 40, 41)",
+                                            color: "rgb(178, 181, 184)",
+                                            transition: "transform 0.2s",
+                                            textAlign: "center",
+                                            position: "relative"
                                         }} ref={(r) => this._followRef = r}>
                                             <FontAwesomeIcon className="FormattedTextBox-fa-icon" icon="arrow-right"
                                                 size="sm" />
                                         </div>
                                     </div>
-                                    <ContentFittingDocumentView
-                                        Document={target}
-                                        LibraryPath={emptyPath}
-                                        fitToBox={true}
-                                        moveDocument={returnFalse}
-                                        rootSelected={returnFalse}
-                                        ScreenToLocalTransform={Transform.Identity}
-                                        parentActive={returnFalse}
-                                        addDocument={returnFalse}
-                                        removeDocument={returnFalse}
-                                        addDocTab={returnFalse}
-                                        pinToPres={returnFalse}
-                                        dontRegisterView={true}
-                                        docFilters={returnEmptyFilter}
-                                        ContainingCollectionDoc={undefined}
-                                        ContainingCollectionView={undefined}
-                                        renderDepth={0}
-                                        PanelWidth={() => Math.min(350, NumCast(target._width, 350))}
-                                        PanelHeight={() => Math.min(250, NumCast(target._height, 250))}
-                                        focus={emptyFunction}
-                                        whenActiveChanged={returnFalse}
-                                        bringToFront={returnFalse}
-                                        ContentScaling={returnOne}
-                                        NativeWidth={returnZero}
-                                        NativeHeight={returnZero}
-                                    />
+                                    <div className="wrapper" style={{
+                                        maxWidth: "180px", maxHeight: "168px", overflow: "hidden",
+                                        overflowY: "hidden", paddingTop: "5px"
+                                    }}>
+                                        <ContentFittingDocumentView
+                                            Document={target}
+                                            LibraryPath={emptyPath}
+                                            fitToBox={true}
+                                            moveDocument={returnFalse}
+                                            rootSelected={returnFalse}
+                                            ScreenToLocalTransform={Transform.Identity}
+                                            parentActive={returnFalse}
+                                            addDocument={returnFalse}
+                                            removeDocument={returnFalse}
+                                            addDocTab={returnFalse}
+                                            pinToPres={returnFalse}
+                                            dontRegisterView={true}
+                                            docFilters={returnEmptyFilter}
+                                            ContainingCollectionDoc={undefined}
+                                            ContainingCollectionView={undefined}
+                                            renderDepth={0}
+                                            PanelWidth={() => Math.min(350, NumCast(target._width, 350))}
+                                            PanelHeight={() => Math.min(250, NumCast(target._height, 250))}
+                                            focus={emptyFunction}
+                                            whenActiveChanged={returnFalse}
+                                            bringToFront={returnFalse}
+                                            ContentScaling={returnOne}
+                                            NativeWidth={returnZero}
+                                            NativeHeight={returnZero}
+                                        />
+                                    </div>
                                 </div>;
                                 FormattedTextBoxComment.showCommentbox("", view, nbef);
 
-- 
cgit v1.2.3-70-g09d2


From f215c6c02d819e9eec4eb94a428c272652e54096 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 4 Jul 2020 01:37:38 -0400
Subject: fixed interactions with collapsing text bullets

---
 .../nodes/formattedText/FormattedTextBox.scss      | 15 ++++-----
 .../views/nodes/formattedText/FormattedTextBox.tsx | 37 ++++++++++++++--------
 2 files changed, 30 insertions(+), 22 deletions(-)

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

diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
index 0a094ba6a..6bb0007ce 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -303,7 +303,6 @@ footnote::after {
         font-family: inherit;
     }
     ol {
-        margin-left: 1em;
         font-family: inherit;
     }
     .bullet {  p {display: inline-block; font-family: inherit} margin-left: 0; }
@@ -311,17 +310,17 @@ footnote::after {
     .bullet2,.bullet3,.bullet4,.bullet5,.bullet6 { p {display: inline-block; font-family: inherit} font-size: smaller; }
 
     .decimal1-ol { counter-reset: deci1; p {display: inline-block; font-family: inherit} margin-left: 0; }
-    .decimal2-ol { counter-reset: deci2; p {display: inline-block; font-family: inherit} font-size: smaller; padding-left: 1em;}
-    .decimal3-ol { counter-reset: deci3; p {display: inline-block; font-family: inherit} font-size: smaller; padding-left: 2em;}
-    .decimal4-ol { counter-reset: deci4; p {display: inline-block; font-family: inherit} font-size: smaller; padding-left: 3em;}
+    .decimal2-ol { counter-reset: deci2; p {display: inline-block; font-family: inherit} font-size: smaller; padding-left: 2.1em;}
+    .decimal3-ol { counter-reset: deci3; p {display: inline-block; font-family: inherit} font-size: smaller; padding-left: 2.85em;}
+    .decimal4-ol { counter-reset: deci4; p {display: inline-block; font-family: inherit} font-size: smaller; padding-left: 3.85em;}
     .decimal5-ol { counter-reset: deci5; p {display: inline-block; font-family: inherit} font-size: smaller; }
     .decimal6-ol { counter-reset: deci6; p {display: inline-block; font-family: inherit} font-size: smaller; }
     .decimal7-ol { counter-reset: deci7; p {display: inline-block; font-family: inherit} font-size: smaller; }
 
     .multi1-ol { counter-reset: multi1; p {display: inline-block; font-family: inherit} margin-left: 0;     padding-left: 1.2em }
-    .multi2-ol { counter-reset: multi2; p {display: inline-block; font-family: inherit} font-size: smaller; padding-left: 1.4em;}
-    .multi3-ol { counter-reset: multi3; p {display: inline-block; font-family: inherit} font-size: smaller; padding-left: 2em;}
-    .multi4-ol { counter-reset: multi4; p {display: inline-block; font-family: inherit} font-size: smaller; padding-left: 3.4em;}
+    .multi2-ol { counter-reset: multi2; p {display: inline-block; font-family: inherit} font-size: smaller; padding-left: 2em;}
+    .multi3-ol { counter-reset: multi3; p {display: inline-block; font-family: inherit} font-size: smaller; padding-left: 2.85em;}
+    .multi4-ol { counter-reset: multi4; p {display: inline-block; font-family: inherit} font-size: smaller; padding-left: 3.85em;}
 
     .bullet:before, .bullet1:before, .bullet2:before, .bullet3:before, .bullet4:before, .bullet5:before { transition: 0.5s; display: inline-block; vertical-align: top; margin-left: -1em; width: 1em; content:" " }
 
@@ -333,7 +332,7 @@ footnote::after {
     .decimal6:before { transition: 0.5s;counter-increment: deci6; display: inline-block; vertical-align: top; margin-left: -2em;   width:  6em;   content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) "."counter(deci6) ". "; }
     .decimal7:before { transition: 0.5s;counter-increment: deci7; display: inline-block; vertical-align: top; margin-left: -2em;   width:  7em;   content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) "."counter(deci6) "."counter(deci7) ". "; }
 
-    .multi1:before { transition: 0.5s;counter-increment: multi1; display: inline-block; vertical-align: top; margin-left: -1em;    width: 1.2em;  content: counter(multi1, upper-alpha) ". ";  }
+    .multi1:before { transition: 0.5s;counter-increment: multi1; display: inline-block; vertical-align: top; margin-left: -1.3em;  width: 1.2em;  content: counter(multi1, upper-alpha) ". ";  }
     .multi2:before { transition: 0.5s;counter-increment: multi2; display: inline-block; vertical-align: top; margin-left: -2em;    width:   2em;  content: counter(multi1, upper-alpha) "."counter(multi2, decimal) ". "; }
     .multi3:before { transition: 0.5s;counter-increment: multi3; display: inline-block; vertical-align: top; margin-left: -2.85em; width:2.85em;  content: counter(multi1, upper-alpha) "."counter(multi2, decimal) "."counter(multi3, lower-alpha) ". "; }
     .multi4:before { transition: 0.5s;counter-increment: multi4; display: inline-block; vertical-align: top; margin-left: -4.2em;  width: 4.2em;    content: counter(multi1, upper-alpha) "."counter(multi2, decimal) "."counter(multi3, lower-alpha) "."counter(multi4, lower-roman) ". "; }
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 167ba782f..8e3087672 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1032,6 +1032,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     _downX = 0;
     _downY = 0;
     _break = false;
+    _collapsed = false;
     onPointerDown = (e: React.PointerEvent): void => {
         if (this._recording && !e.ctrlKey && e.button === 0) {
             this.stopDictation(true);
@@ -1147,9 +1148,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
 
     static _bulletStyleSheet: any = addStyleSheet();
     static _userStyleSheet: any = addStyleSheet();
-
+    _forceUncollapse = true; // if the cursor doesn't move between clicks, then the selection will disappear for some reason.  This flags the 2nd click as happening on a selection which allows bullet points to toggle
+    _forceDownNode: Node | undefined;
     onClick = (e: React.MouseEvent): void => {
-        if ((this._editorView!.root as any).getSelection().isCollapsed) { // this is a hack to allow the cursor to be placed at the end of a document when the document ends in an inline dash comment.  Apparently Chrome on Windows has a bug/feature which breaks this when clicking after the end of the text.
+        if (Math.abs(e.clientX - this._downX) > 4 || Math.abs(e.clientY - this._downY) > 4) {
+            this._forceDownNode = undefined;
+            return;
+        }
+        if (!this._forceUncollapse || (this._editorView!.root as any).getSelection().isCollapsed) { // this is a hack to allow the cursor to be placed at the end of a document when the document ends in an inline dash comment.  Apparently Chrome on Windows has a bug/feature which breaks this when clicking after the end of the text.
             const pcords = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY });
             const node = pcords && this._editorView!.state.doc.nodeAt(pcords.pos); // get what prosemirror thinks the clicked node is (if it's null, then we didn't click on any text)
             if (pcords && node?.type === this._editorView!.state.schema.nodes.dashComment) {
@@ -1161,6 +1167,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                 if (e.clientY > lastNode?.getBoundingClientRect().bottom) { // if we clicked below the last prosemirror div, then set the selection to be the end of the document
                     this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, this._editorView!.state.doc.content.size)));
                 }
+            } else if ([this._editorView!.state.schema.nodes.ordered_list, this._editorView!.state.schema.nodes.listItem].includes(node?.type) &&
+                node !== (this._editorView!.state.selection as NodeSelection)?.node && pcords) {
+                this._editorView!.dispatch(this._editorView!.state.tr.setSelection(NodeSelection.create(this._editorView!.state.doc, pcords.pos!)));
             }
         }
         if ((e.nativeEvent as any).formattedHandled) { e.stopPropagation(); return; }
@@ -1168,12 +1177,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
 
         if (this.props.isSelected(true)) { // if text box is selected, then it consumes all click events
             e.stopPropagation();
-            this.hitBulletTargets(e.clientX, e.clientY, e.shiftKey, false);
+            this.hitBulletTargets(e.clientX, e.clientY, !this._editorView?.state.selection.empty || this._forceUncollapse, false, this._forceDownNode, e.shiftKey);
         }
+        this._forceUncollapse = !(this._editorView!.root as any).getSelection().isCollapsed;
+        this._forceDownNode = (this._editorView!.state.selection as NodeSelection)?.node;
     }
 
     // this hackiness handles clicking on the list item bullets to do expand/collapse.  the bullets are ::before pseudo elements so there's no real way to hit test against them.
-    hitBulletTargets(x: number, y: number, collapse: boolean, highlightOnly: boolean) {
+    hitBulletTargets(x: number, y: number, collapse: boolean, highlightOnly: boolean, downNode: Node | undefined = undefined, selectOrderedList: boolean = false) {
+        this._forceUncollapse = false;
         clearStyleSheetRules(FormattedTextBox._bulletStyleSheet);
         const clickPos = this._editorView!.posAtCoords({ left: x, top: y });
         let olistPos = clickPos?.pos;
@@ -1189,20 +1201,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                     $olistPos = this._editorView?.state.doc.resolve(($olistPos as any).path[($olistPos as any).path.length - 4]);
                 }
             }
+            const listPos = this._editorView?.state.doc.resolve(clickPos.pos);
             const listNode = this._editorView?.state.doc.nodeAt(clickPos.pos);
-            if (olistNode && olistNode.type === this._editorView?.state.schema.nodes.ordered_list) {
-                if (!collapse) {
-                    if (!highlightOnly) {
-                        this._editorView.dispatch(this._editorView.state.tr.setSelection(new NodeSelection($olistPos!)));
-                    }
-                    addStyleSheetRule(FormattedTextBox._bulletStyleSheet, olistNode.attrs.mapStyle + olistNode.attrs.bulletStyle + ":hover:before", { background: "lightgray" });
-                } else if (listNode && listNode.type === this._editorView.state.schema.nodes.list_item) {
-                    if (!highlightOnly) {
+            if (olistNode && olistNode.type === this._editorView?.state.schema.nodes.ordered_list && listNode) {
+                if (!highlightOnly) {
+                    if (selectOrderedList || (!collapse && listNode.attrs.visibility)) {
+                        this._editorView.dispatch(this._editorView.state.tr.setSelection(new NodeSelection(selectOrderedList ? $olistPos! : listPos!)));
+                    } else if (!listNode.attrs.visibility || downNode === listNode) {
                         this._editorView.dispatch(this._editorView.state.tr.setNodeMarkup(clickPos.pos, listNode.type, { ...listNode.attrs, visibility: !listNode.attrs.visibility }));
                         this._editorView.dispatch(this._editorView.state.tr.setSelection(TextSelection.create(this._editorView.state.doc, clickPos.pos)));
                     }
-                    addStyleSheetRule(FormattedTextBox._bulletStyleSheet, olistNode.attrs.mapStyle + olistNode.attrs.bulletStyle + ":hover:before", { background: "lightgray" });
                 }
+                addStyleSheetRule(FormattedTextBox._bulletStyleSheet, olistNode.attrs.mapStyle + olistNode.attrs.bulletStyle + ":hover:before", { background: "lightgray" });
             }
         }
     }
@@ -1247,7 +1257,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     }
     public static HadSelection: boolean = false;
     onBlur = (e: any) => {
-        console.log("BLURRR");
         FormattedTextBox.HadSelection = window.getSelection()?.toString() !== "";
         //DictationManager.Controls.stop(false);
         this.endUndoTypingBatch();
-- 
cgit v1.2.3-70-g09d2


From 6b24899bcf2c099163c1ca872d65b6318c11a53b Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 4 Jul 2020 12:25:09 -0400
Subject: fixed highlighting active alignment and bullet type in richtextmenu

---
 .../views/nodes/formattedText/FormattedTextBox.tsx |  1 -
 .../formattedText/ProsemirrorExampleTransfer.ts    | 10 ++--
 .../views/nodes/formattedText/RichTextMenu.tsx     | 57 ++++++++++++++++------
 3 files changed, 49 insertions(+), 19 deletions(-)

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

diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 8e3087672..fc63dfbf5 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1116,7 +1116,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
 
     @action
     onFocused = (e: React.FocusEvent): void => {
-        console.log("FOUCSS");
         FormattedTextBox.FocusedBox = this;
         this.tryUpdateHeight();
 
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index 9d69f4be7..3f73ec436 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -16,13 +16,17 @@ const mac = typeof navigator !== "undefined" ? /Mac/.test(navigator.platform) :
 
 export type KeyMap = { [key: string]: any };
 
-export let updateBullets = (tx2: Transaction, schema: Schema, mapStyle?: string, from?: number, to?: number) => {
+export let updateBullets = (tx2: Transaction, schema: Schema, assignedMapStyle?: string, from?: number, to?: number) => {
+    let mapStyle = assignedMapStyle;
     tx2.doc.descendants((node: any, offset: any, index: any) => {
         if ((from === undefined || to === undefined || (from <= offset + node.nodeSize && to >= offset)) && (node.type === schema.nodes.ordered_list || node.type === schema.nodes.list_item)) {
             const path = (tx2.doc.resolve(offset) as any).path;
             let depth = Array.from(path).reduce((p: number, c: any) => p + (c.hasOwnProperty("type") && c.type === schema.nodes.ordered_list ? 1 : 0), 0);
-            if (node.type === schema.nodes.ordered_list) depth++;
-            tx2.setNodeMarkup(offset, node.type, { ...node.attrs, mapStyle: mapStyle || node.attrs.mapStyle, bulletStyle: depth, }, node.marks);
+            if (node.type === schema.nodes.ordered_list) {
+                if (depth === 0 && !assignedMapStyle) mapStyle = node.attrs.mapStyle;
+                depth++;
+            }
+            tx2.setNodeMarkup(offset, node.type, { ...node.attrs, mapStyle, bulletStyle: depth, }, node.marks);
         }
     });
     return tx2;
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 95d6c9fac..9075a6486 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -55,6 +55,7 @@ export default class RichTextMenu extends AntimodeMenu {
     @observable private activeFontSize: string = "";
     @observable private activeFontFamily: string = "";
     @observable private activeListType: string = "";
+    @observable private activeAlignment: string = "left";
 
     @observable private brushIsEmpty: boolean = true;
     @observable private brushMarks: Set<Mark> = new Set();
@@ -91,7 +92,7 @@ export default class RichTextMenu extends AntimodeMenu {
             { mark: schema.marks.pFontSize.create({ fontSize: 32 }), title: "Set font size", label: "32pt", command: this.changeFontSize },
             { mark: schema.marks.pFontSize.create({ fontSize: 48 }), title: "Set font size", label: "48pt", command: this.changeFontSize },
             { mark: schema.marks.pFontSize.create({ fontSize: 72 }), title: "Set font size", label: "72pt", command: this.changeFontSize },
-            { mark: null, title: "", label: "various", command: unimplementedFunction, hidden: true },
+            { mark: null, title: "", label: "...", command: unimplementedFunction, hidden: true },
             { mark: null, title: "", label: "13pt", command: unimplementedFunction, hidden: true }, // this is here because the default size is 13, but there is no actual 13pt option
         ];
 
@@ -110,7 +111,7 @@ export default class RichTextMenu extends AntimodeMenu {
         this.listTypeOptions = [
             { node: schema.nodes.ordered_list.create({ mapStyle: "bullet" }), title: "Set list type", label: ":", command: this.changeListType },
             { node: schema.nodes.ordered_list.create({ mapStyle: "decimal" }), title: "Set list type", label: "1.1", command: this.changeListType },
-            { node: schema.nodes.ordered_list.create({ mapStyle: "multi" }), title: "Set list type", label: "1.A", command: this.changeListType },
+            { node: schema.nodes.ordered_list.create({ mapStyle: "multi" }), title: "Set list type", label: "A.1", command: this.changeListType },
             //{ node: undefined, title: "Set list type", label: "Remove", command: this.changeListType },
         ];
 
@@ -178,11 +179,13 @@ export default class RichTextMenu extends AntimodeMenu {
 
         // update active font family and size
         const active = this.getActiveFontStylesOnSelection();
-        const activeFamilies = active?.get("families");
-        const activeSizes = active?.get("sizes");
+        const activeFamilies = active.activeFamilies;
+        const activeSizes = active.activeSizes;
 
-        this.activeFontFamily = !activeFamilies?.length ? "Arial" : activeFamilies.length === 1 ? String(activeFamilies[0]) : "various";
-        this.activeFontSize = !activeSizes?.length ? "13pt" : activeSizes.length === 1 ? String(activeSizes[0]) : "various";
+        this.activeListType = this.getActiveListStyle();
+        this.activeAlignment = this.getActiveAlignment();
+        this.activeFontFamily = !activeFamilies.length ? "Arial" : activeFamilies.length === 1 ? String(activeFamilies[0]) : "various";
+        this.activeFontSize = !activeSizes.length ? "13pt" : activeSizes.length === 1 ? String(activeSizes[0]) : "...";
 
         // update link in current selection
         const targetTitle = await this.getTextLinkTargetTitle();
@@ -212,9 +215,35 @@ export default class RichTextMenu extends AntimodeMenu {
         }
     }
 
+    // finds font sizes and families in selection
+    getActiveAlignment() {
+        if (this.view) {
+            const path = (this.view.state.selection.$from as any).path;
+            for (let i = path.length - 3; i < path.length; i -= 3) {
+                if (path[i]?.type === this.view.state.schema.nodes.paragraph) {
+                    return path[i].attrs.align || "left";
+                }
+            }
+        }
+        return "left";
+    }
+
+    // finds font sizes and families in selection
+    getActiveListStyle() {
+        if (this.view) {
+            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) {
+                    return path[i].attrs.mapStyle;
+                }
+            }
+        }
+        return "decimal";
+    }
+
     // finds font sizes and families in selection
     getActiveFontStylesOnSelection() {
-        if (!this.view) return;
+        if (!this.view) return { activeFamilies: [], activeSizes: [] };
 
         const activeFamilies: string[] = [];
         const activeSizes: string[] = [];
@@ -228,10 +257,7 @@ export default class RichTextMenu extends AntimodeMenu {
             });
         }
 
-        const styles = new Map<String, String[]>();
-        styles.set("families", activeFamilies);
-        styles.set("sizes", activeSizes);
-        return styles;
+        return { activeFamilies, activeSizes };
     }
 
     getMarksInSelection(state: EditorState<any>) {
@@ -354,7 +380,8 @@ export default class RichTextMenu extends AntimodeMenu {
         return <select onChange={onChange} key={key}>{items}</select>;
     }
 
-    createNodesDropdown(activeOption: string, options: { node: NodeType | any | null, title: string, label: string, command: (node: NodeType | any) => void, hidden?: boolean, style?: {} }[], key: string): JSX.Element {
+    createNodesDropdown(activeMap: string, options: { node: NodeType | any | null, title: string, label: string, command: (node: NodeType | any) => void, hidden?: boolean, style?: {} }[], key: string): JSX.Element {
+        const activeOption = activeMap === "bullet" ? ":" : activeMap === "decimal" ? "1.1" : "A.1";
         const items = options.map(({ title, label, hidden, style }) => {
             if (hidden) {
                 return label === activeOption ?
@@ -871,9 +898,9 @@ export default class RichTextMenu extends AntimodeMenu {
             this.createLinkButton(),
             this.createBrushButton(),
             <div className="richTextMenu-divider" />,
-            this.createButton("align-left", "Align Left", undefined, this.alignLeft),
-            this.createButton("align-center", "Align Center", undefined, this.alignCenter),
-            this.createButton("align-right", "Align Right", undefined, this.alignRight),
+            this.createButton("align-left", "Align Left", this.activeAlignment === "left", this.alignLeft),
+            this.createButton("align-center", "Align Center", this.activeAlignment === "center", this.alignCenter),
+            this.createButton("align-right", "Align Right", this.activeAlignment === "right", this.alignRight),
             this.createButton("indent", "Inset More", undefined, this.insetParagraph),
             this.createButton("outdent", "Inset Less", undefined, this.outsetParagraph),
             this.createButton("hand-point-left", "Hanging Indent", undefined, this.hangingIndentParagraph),
-- 
cgit v1.2.3-70-g09d2


From f4830de4f8c4794ec98e54be9ba8730e46155c35 Mon Sep 17 00:00:00 2001
From: usodhi <61431818+usodhi@users.noreply.github.com>
Date: Mon, 6 Jul 2020 18:18:17 +0530
Subject: trying first implementation of storing acls

---
 src/client/DocServer.ts                            |  7 ++-
 src/client/util/GroupManager.tsx                   | 60 +++----------------
 src/client/util/SharingManager.tsx                 | 42 +++++++-------
 src/client/views/DocComponent.tsx                  |  7 ++-
 src/client/views/collections/CollectionView.tsx    |  8 ++-
 src/client/views/nodes/DocumentContentsView.tsx    |  6 +-
 src/client/views/nodes/DocumentView.tsx            |  5 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  8 ++-
 src/fields/Doc.ts                                  | 67 ++++++++++++++--------
 src/fields/util.ts                                 | 60 +++++++++++++++++--
 10 files changed, 151 insertions(+), 119 deletions(-)

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

diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index eac53bb02..860a8fd92 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -39,9 +39,10 @@ export namespace DocServer {
     const docsWithUpdates: { [field: string]: Set<Doc> } = {};
 
     export var PlaygroundFields: string[];
-    export function setPlaygroundFields(livePlayougroundFields: string[]) {
-        DocServer.PlaygroundFields = livePlayougroundFields;
-        livePlayougroundFields.forEach(f => DocServer.setFieldWriteMode(f, DocServer.WriteMode.LivePlayground));
+    export function setPlaygroundFields(livePlaygroundFields: string[]) {
+        console.log("here");
+        DocServer.PlaygroundFields = livePlaygroundFields;
+        livePlaygroundFields.forEach(f => DocServer.setFieldWriteMode(f, DocServer.WriteMode.LivePlayground));
     }
 
     export function setFieldWriteMode(field: string, writeMode: WriteMode) {
diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx
index b14dcf55b..83b206f94 100644
--- a/src/client/util/GroupManager.tsx
+++ b/src/client/util/GroupManager.tsx
@@ -33,7 +33,7 @@ export default class GroupManager extends React.Component<{}> {
     @observable private selectedUsers: UserOptions[] | null = null; // list of users selected in the "Select users" dropdown.
     @observable currentGroup: Opt<Doc>; // the currently selected group.
     private inputRef: React.RefObject<HTMLInputElement> = React.createRef(); // the ref for the input box.
-    private currentUserGroups: Doc[] = [];
+    currentUserGroups: string[] = [];
 
     constructor(props: Readonly<{}>) {
         super(props);
@@ -51,7 +51,7 @@ export default class GroupManager extends React.Component<{}> {
         DocListCastAsync(this.GroupManagerDoc?.data).then(groups => {
             groups?.forEach(group => {
                 const members: string[] = JSON.parse(StrCast(group.members));
-                if (members.includes(Doc.CurrentUserEmail)) this.currentUserGroups.push(group);
+                if (members.includes(Doc.CurrentUserEmail)) this.currentUserGroups.push(StrCast(group.groupName));
             });
         })
             .finally(() => console.log(this.currentUserGroups));
@@ -82,11 +82,6 @@ export default class GroupManager extends React.Component<{}> {
         return this.users.map(user => ({ label: user, value: user }));
     }
 
-
-    get groupMemberships() {
-        return this.currentUserGroups;
-    }
-
     /**
      * Makes the GroupManager visible.
      */
@@ -151,6 +146,11 @@ export default class GroupManager extends React.Component<{}> {
         );
     }
 
+    getGroupMembers(group: string | Doc): string[] {
+        if (group instanceof Doc) return JSON.parse(StrCast(group.members)) as string[];
+        else return JSON.parse(StrCast(this.getGroup(group)!.members)) as string[];
+    }
+
     /**
      * @returns the members of the admin group.
      */
@@ -279,52 +279,6 @@ export default class GroupManager extends React.Component<{}> {
         this.inputRef.current.value = "";
     }
 
-    /**
-     * A getter that @returns the interface rendered to view an individual group.
-     */
-    // private get editingInterface() {
-    //     const members: string[] = this.currentGroup ? JSON.parse(StrCast(this.currentGroup.members)) : [];
-    //     const options: UserOptions[] = this.currentGroup ? this.options.filter(option => !(JSON.parse(StrCast(this.currentGroup!.members)) as string[]).includes(option.value)) : [];
-    //     return (!this.currentGroup ? null :
-    //         <div className="editing-interface">
-    //             <div className="editing-header">
-    //                 <b>{this.currentGroup.groupName}</b>
-    //                 <div className={"close-button"} onClick={action(() => this.currentGroup = undefined)}>
-    //                     <FontAwesomeIcon icon={fa.faWindowClose} size={"lg"} />
-    //                 </div>
-
-    //                 {this.hasEditAccess(this.currentGroup) ?
-    //                     <div className="group-buttons">
-    //                         <div className="add-member-dropdown">
-    //                             <Select
-    //                                 // isMulti={true}
-    //                                 isSearchable={true}
-    //                                 options={options}
-    //                                 onChange={selectedOption => this.addMemberToGroup(this.currentGroup!, (selectedOption as UserOptions).value)}
-    //                                 placeholder={"Add members"}
-    //                                 value={null}
-    //                                 closeMenuOnSelect={true}
-    //                             />
-    //                         </div>
-    //                         <button onClick={() => this.deleteGroup(this.currentGroup!)}>Delete group</button>
-    //                     </div> :
-    //                     null}
-    //             </div>
-    //             <div className="editing-contents">
-    //                 {members.map(member => (
-    //                     <div className="editing-row">
-    //                         <div className="user-email">
-    //                             {member}
-    //                         </div>
-    //                         {this.hasEditAccess(this.currentGroup!) ? <button onClick={() => this.removeMemberFromGroup(this.currentGroup!, member)}> Remove </button> : null}
-    //                     </div>
-    //                 ))}
-    //             </div>
-    //         </div>
-    //     );
-
-    // }
-
     /**
      * A getter that @returns the main interface for the GroupManager.
      */
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index e74824581..bec6b973b 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -43,18 +43,18 @@ export enum SharingPermissions {
 //     [SharingPermissions.Edit, "green"]
 // ]);
 
-const HierarchyMapping = new Map<string, string>([
-    [SharingPermissions.None, "0"],
-    [SharingPermissions.View, "1"],
-    [SharingPermissions.Add, "2"],
-    [SharingPermissions.Edit, "3"],
+// export const HierarchyMapping = new Map<string, number>([
+//     [SharingPermissions.None, 0],
+//     [SharingPermissions.View, 1],
+//     [SharingPermissions.Add, 2],
+//     [SharingPermissions.Edit, 3]
 
-    ["0", SharingPermissions.None],
-    ["1", SharingPermissions.View],
-    ["2", SharingPermissions.Add],
-    ["3", SharingPermissions.Edit]
+//     // ["0", SharingPermissions.None],
+//     // ["1", SharingPermissions.View],
+//     // ["2", SharingPermissions.Add],
+//     // ["3", SharingPermissions.Edit]
 
-]);
+// ]);
 
 interface GroupOptions {
     label: string;
@@ -161,14 +161,6 @@ export default class SharingManager extends React.Component<{}> {
         const ACL = `ACL-${StrCast(group.groupName)}`;
 
         target[ACL] = permission;
-        // const sharingDoc = this.sharingDoc!;
-        // if (permission === SharingPermissions.None) {
-        //     const metadata = sharingDoc[StrCast(group.groupName)];
-        //     if (metadata) sharingDoc[StrCast(group.groupName)] = undefined;
-        // }
-        // else {
-        //     sharingDoc[StrCast(group.groupName)] = permission;
-        // }
 
         group.docsShared ? Doc.IndexOf(target, DocListCast(group.docsShared)) === -1 && (group.docsShared as List<Doc>).push(target) : group.docsShared = new List<Doc>([target]);
 
@@ -206,7 +198,7 @@ export default class SharingManager extends React.Component<{}> {
                 const users: ValidatedUser[] = this.users.filter(user => members.includes(user.user.email));
 
                 users.forEach(user => Doc.RemoveDocFromList(user.notificationDoc, storage, doc));
-            })
+            });
         }
     }
 
@@ -224,8 +216,16 @@ export default class SharingManager extends React.Component<{}> {
         target[ACL] = permission;
 
 
-        if (permission !== SharingPermissions.None) Doc.IndexOf(target, DocListCast(notificationDoc[storage])) === -1 && Doc.AddDocToList(notificationDoc, storage, target);
-        else Doc.IndexOf(target, DocListCast(notificationDoc[storage])) !== -1 && Doc.RemoveDocFromList(notificationDoc, storage, target);
+        if (permission !== SharingPermissions.None) {
+            !this.sharedUsers.includes(recipient) && this.sharedUsers.push(recipient);
+
+            Doc.IndexOf(target, DocListCast(notificationDoc[storage])) === -1 && Doc.AddDocToList(notificationDoc, storage, target);
+        }
+        else {
+            const index = this.sharedUsers.findIndex(user => user === recipient);
+            index !== -1 && this.sharedUsers.splice(index, 1);
+            Doc.IndexOf(target, DocListCast(notificationDoc[storage])) !== -1 && Doc.RemoveDocFromList(notificationDoc, storage, target);
+        }
 
     }
 
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 9b9a28f0f..e8c34d931 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -7,6 +7,7 @@ import { InteractionUtils } from '../util/InteractionUtils';
 import { List } from '../../fields/List';
 import { DateField } from '../../fields/DateField';
 import { ScriptField } from '../../fields/ScriptField';
+import { getEffectiveAcl } from '../../fields/util';
 
 
 ///  DocComponent returns a generic React base class used by views that don't have 'fieldKey' props (e.g.,CollectionFreeFormDocumentView, DocumentView)
@@ -137,10 +138,12 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
             const targetDataDoc = this.props.Document[DataSym];
             const docList = DocListCast(targetDataDoc[this.annotationKey]);
             const added = docs.filter(d => !docList.includes(d));
+            console.log("here");
+            const effectiveAcl = getEffectiveAcl(this.dataDoc);
             if (added.length) {
-                if (this.dataDoc[AclSym] === AclReadonly) {
+                if (effectiveAcl === AclReadonly) {
                     return false;
-                } else if (this.dataDoc[AclSym] === AclAddonly) {
+                } else if (effectiveAcl === AclAddonly) {
                     added.map(doc => Doc.AddDocToList(targetDataDoc, this.annotationKey, doc));
                 } else {
                     added.map(doc => doc.context = this.props.Document);
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 26abd2529..0dfe9c52a 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -17,7 +17,7 @@ import { listSpec } from '../../../fields/Schema';
 import { ComputedField, ScriptField } from '../../../fields/ScriptField';
 import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { ImageField } from '../../../fields/URLField';
-import { TraceMobx } from '../../../fields/util';
+import { TraceMobx, getEffectiveAcl } from '../../../fields/util';
 import { emptyFunction, emptyPath, returnEmptyFilter, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from '../../../Utils';
 import { Docs, DocUtils } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
@@ -132,10 +132,12 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
         const targetDataDoc = this.props.Document[DataSym];
         const docList = DocListCast(targetDataDoc[this.props.fieldKey]);
         const added = docs.filter(d => !docList.includes(d));
+        console.log("here");
+        const effectiveAcl = getEffectiveAcl(this.dataDoc);
         if (added.length) {
-            if (this.dataDoc[AclSym] === AclReadonly) {
+            if (effectiveAcl === AclReadonly) {
                 return false;
-            } else if (this.dataDoc[AclSym] === AclAddonly) {
+            } else if (effectiveAcl === AclAddonly) {
                 added.map(doc => Doc.AddDocToList(targetDataDoc, this.props.fieldKey, doc));
             } else {
                 added.map(doc => {
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index f1438fd54..d480c76d0 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -36,7 +36,7 @@ import { WebBox } from "./WebBox";
 import { InkingStroke } from "../InkingStroke";
 import React = require("react");
 import { RecommendationsBox } from "../RecommendationsBox";
-import { TraceMobx } from "../../../fields/util";
+import { TraceMobx, getEffectiveAcl } from "../../../fields/util";
 import { ScriptField } from "../../../fields/ScriptField";
 import XRegExp = require("xregexp");
 
@@ -184,8 +184,8 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
 
         const bindings = this.CreateBindings(onClick, onInput);
         //  layoutFrame = splits.length > 1 ? splits[0] + splits[1].replace(/{([^{}]|(?R))*}/, replacer4) : ""; // might have been more elegant if javascript supported recursive patterns
-
-        return (this.props.renderDepth > 12 || !layoutFrame || !this.layoutDoc || this.layoutDoc[AclSym] === AclPrivate) ? (null) :
+        console.log("here");
+        return (this.props.renderDepth > 12 || !layoutFrame || !this.layoutDoc || getEffectiveAcl(this.layoutDoc) === AclPrivate) ? (null) :
             <ObserverJsxParser
                 key={42}
                 blacklistedAttrs={[]}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 297a9f2a5..e277ddc36 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -12,7 +12,7 @@ import { listSpec } from "../../../fields/Schema";
 import { SchemaHeaderField } from '../../../fields/SchemaHeaderField';
 import { ScriptField } from '../../../fields/ScriptField';
 import { BoolCast, Cast, NumCast, StrCast } from "../../../fields/Types";
-import { TraceMobx } from '../../../fields/util';
+import { TraceMobx, getEffectiveAcl } from '../../../fields/util';
 import { GestureUtils } from '../../../pen-gestures/GestureUtils';
 import { emptyFunction, OmitKeys, returnOne, returnTransparent, Utils, emptyPath } from "../../../Utils";
 import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
@@ -1188,7 +1188,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     }
 
     render() {
-        if (this.props.Document[AclSym] && this.props.Document[AclSym] === AclPrivate) return (null);
+        console.log("here");
+        if (getEffectiveAcl(this.props.Document) === AclPrivate) return (null);
         if (!(this.props.Document instanceof Doc)) return (null);
         const backgroundColor = Doc.UserDoc().renderStyle === "comic" ? undefined : this.props.forcedBackgroundColor?.(this.Document) || StrCast(this.layoutDoc._backgroundColor) || StrCast(this.layoutDoc.backgroundColor) || StrCast(this.Document.backgroundColor) || this.props.backgroundColor?.(this.Document);
         const opacity = Cast(this.layoutDoc._opacity, "number", Cast(this.layoutDoc.opacity, "number", Cast(this.Document.opacity, "number", null)));
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index fc63dfbf5..a0dbcd980 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -13,7 +13,7 @@ import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from "
 import { ReplaceStep } from 'prosemirror-transform';
 import { EditorView } from "prosemirror-view";
 import { DateField } from '../../../../fields/DateField';
-import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym, AclSym } from "../../../../fields/Doc";
+import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym, AclSym, AclEdit } from "../../../../fields/Doc";
 import { documentSchema } from '../../../../fields/documentSchemas';
 import applyDevTools = require("prosemirror-dev-tools");
 import { removeMarkWithAttrs } from "./prosemirrorPatches";
@@ -24,7 +24,7 @@ import { RichTextField } from "../../../../fields/RichTextField";
 import { RichTextUtils } from '../../../../fields/RichTextUtils';
 import { createSchema, makeInterface } from "../../../../fields/Schema";
 import { Cast, DateCast, NumCast, StrCast, ScriptCast } from "../../../../fields/Types";
-import { TraceMobx, OVERRIDE_ACL } from '../../../../fields/util';
+import { TraceMobx, OVERRIDE_ACL, getEffectiveAcl } from '../../../../fields/util';
 import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnOne, returnZero, Utils, setupMoveUpEvents } from '../../../../Utils';
 import { GoogleApiClientUtils, Pulls, Pushes } from '../../../apis/google_docs/GoogleApiClientUtils';
 import { DocServer } from "../../../DocServer";
@@ -226,7 +226,9 @@ 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());
-            if (!this.dataDoc[AclSym]) {
+            // if (!this.dataDoc[AclSym]) { // what?
+            console.log("here");
+            if (getEffectiveAcl(this.dataDoc) === AclEdit) {
                 if (!this._applyingChange && json.replace(/"selection":.*/, "") !== curProto?.Data.replace(/"selection":.*/, "")) {
                     this._applyingChange = true;
                     (curText !== Cast(this.dataDoc[this.fieldKey], RichTextField)?.Text) && (this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now())));
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index bbc9d388a..c965dc282 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -17,8 +17,9 @@ import { RichTextField } from "./RichTextField";
 import { listSpec } from "./Schema";
 import { ComputedField } from "./ScriptField";
 import { Cast, FieldValue, NumCast, StrCast, ToConstructor } from "./Types";
-import { deleteProperty, getField, getter, makeEditable, makeReadOnly, setter, updateFunction } from "./util";
+import { deleteProperty, getField, getter, makeEditable, makeReadOnly, setter, updateFunction, getEffectiveAcl } from "./util";
 import { LinkManager } from "../client/util/LinkManager";
+import { SharingPermissions } from "../client/util/SharingManager";
 
 export namespace Field {
     export function toKeyValueString(doc: Doc, key: string): string {
@@ -100,23 +101,39 @@ export const AclEdit = Symbol("AclEdit");
 export const UpdatingFromServer = Symbol("UpdatingFromServer");
 const CachedUpdates = Symbol("Cached updates");
 
+const AclMap = new Map<string, symbol>([
+    [SharingPermissions.None, AclPrivate],
+    [SharingPermissions.View, AclReadonly],
+    [SharingPermissions.Add, AclAddonly],
+    [SharingPermissions.Edit, AclEdit]
+]);
 
 export function fetchProto(doc: Doc) {
+    console.log("in fetchproto");
     if (doc.author !== Doc.CurrentUserEmail) { // storing acls for groups needs to be extended here - AclSym should store a datastructure that stores information about permissions
-        const acl = Doc.Get(doc, "ACL", true);
-        switch (acl) {
-            case "ownerOnly":
-                doc[AclSym] = AclPrivate;
-                return undefined;
-            case "readOnly":
-                doc[AclSym] = AclReadonly;
-                break;
-            case "addOnly":
-                doc[AclSym] = AclAddonly;
-                break;
-            case "edit":
-                doc[AclSym] = AclEdit;
-        }
+
+        const permissions: { [key: string]: symbol } = {};
+
+        Object.keys(doc).forEach(key => {
+            if (key.startsWith("ACL")) permissions[key] = AclMap.get(StrCast(doc[key]))!;
+        });
+
+        doc[AclSym] = permissions;
+
+        // const acl = Doc.Get(doc, "ACL", true);
+        // switch (acl) {
+        //     case "ownerOnly":
+        //         doc[AclSym] = AclPrivate;
+        //         return undefined;
+        //     case "readOnly":
+        //         doc[AclSym] = AclReadonly;
+        //         break;
+        //     case "addOnly":
+        //         doc[AclSym] = AclAddonly;
+        //         break;
+        //     // case "edit":
+        //     //     doc[AclSym] = AclEdit;
+        // }
     }
 
     if (doc.proto instanceof Promise) {
@@ -134,10 +151,10 @@ export class Doc extends RefField {
             set: setter,
             get: getter,
             // getPrototypeOf: (target) => Cast(target[SelfProxy].proto, Doc) || null, // TODO this might be able to replace the proto logic in getter
-            has: (target, key) => target[AclSym] !== AclPrivate && key in target.__fields,
+            has: (target, key) => getEffectiveAcl(target) !== AclPrivate && key in target.__fields,
             ownKeys: target => {
                 const obj = {} as any;
-                if (target[AclSym] !== AclPrivate) Object.assign(obj, target.___fields);
+                if (getEffectiveAcl(target) !== AclPrivate) Object.assign(obj, target.___fields);
                 runInAction(() => obj.__LAYOUT__ = target.__LAYOUT__);
                 return Object.keys(obj);
             },
@@ -191,11 +208,11 @@ export class Doc extends RefField {
 
     private [Self] = this;
     private [SelfProxy]: any;
-    public [AclSym]: any = undefined;
+    public [AclSym]: any;
     public [WidthSym] = () => NumCast(this[SelfProxy]._width);
     public [HeightSym] = () => NumCast(this[SelfProxy]._height);
     public [ToScriptString]() { return `DOC-"${this[Self][Id]}"-`; }
-    public [ToString]() { return `Doc(${this[AclSym] === AclPrivate ? "-inaccessible-" : this.title})`; }
+    public [ToString]() { return `Doc(${getEffectiveAcl(this) === AclPrivate ? "-inaccessible-" : this.title})`; }
     public get [LayoutSym]() { return this[SelfProxy].__LAYOUT__; }
     public get [DataSym]() {
         const self = this[SelfProxy];
@@ -823,7 +840,8 @@ export namespace Doc {
     }
     // don't bother memoizing (caching) the result if called from a non-reactive context. (plus this avoids a warning message)
     export function IsBrushedDegreeUnmemoized(doc: Doc) {
-        if (!doc || doc[AclSym] === AclPrivate || Doc.GetProto(doc)[AclSym] === AclPrivate) return 0;
+        console.log("here");
+        if (!doc || getEffectiveAcl(doc) === AclPrivate || getEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return 0;
         return brushManager.BrushedDoc.has(doc) ? 2 : brushManager.BrushedDoc.has(Doc.GetProto(doc)) ? 1 : 0;
     }
     export function IsBrushedDegree(doc: Doc) {
@@ -832,15 +850,15 @@ export namespace Doc {
         })(doc);
     }
     export function BrushDoc(doc: Doc) {
-
-        if (!doc || doc[AclSym] === AclPrivate || Doc.GetProto(doc)[AclSym] === AclPrivate) return doc;
+        console.log("here");
+        if (!doc || getEffectiveAcl(doc) === AclPrivate || getEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return doc;
         brushManager.BrushedDoc.set(doc, true);
         brushManager.BrushedDoc.set(Doc.GetProto(doc), true);
         return doc;
     }
     export function UnBrushDoc(doc: Doc) {
 
-        if (!doc || doc[AclSym] === AclPrivate || Doc.GetProto(doc)[AclSym] === AclPrivate) return doc;
+        if (!doc || getEffectiveAcl(doc) === AclPrivate || getEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return doc;
         brushManager.BrushedDoc.delete(doc);
         brushManager.BrushedDoc.delete(Doc.GetProto(doc));
         return doc;
@@ -870,7 +888,8 @@ export namespace Doc {
     }
     const highlightManager = new HighlightBrush();
     export function IsHighlighted(doc: Doc) {
-        if (!doc || doc[AclSym] === AclPrivate || Doc.GetProto(doc)[AclSym] === AclPrivate) return false;
+        console.log("here");
+        if (!doc || getEffectiveAcl(doc) === AclPrivate || getEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return false;
         return highlightManager.HighlightedDoc.get(doc) || highlightManager.HighlightedDoc.get(Doc.GetProto(doc));
     }
     export function HighlightDoc(doc: Doc, dataAndDisplayDocs = true) {
diff --git a/src/fields/util.ts b/src/fields/util.ts
index 7bb090a93..a3e7a36f8 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -1,5 +1,5 @@
 import { UndoManager } from "../client/util/UndoManager";
-import { Doc, Field, FieldResult, UpdatingFromServer, LayoutSym, AclSym, AclPrivate } from "./Doc";
+import { Doc, Field, FieldResult, UpdatingFromServer, LayoutSym, AclSym, AclPrivate, AclEdit, AclReadonly, AclAddonly } from "./Doc";
 import { SerializationHelper } from "../client/util/SerializationHelper";
 import { ProxyField, PrefetchProxy } from "./Proxy";
 import { RefField } from "./RefField";
@@ -9,6 +9,7 @@ import { Parent, OnUpdate, Update, Id, SelfProxy, Self } from "./FieldSymbols";
 import { DocServer } from "../client/DocServer";
 import { ComputedField } from "./ScriptField";
 import { ScriptCast } from "./Types";
+import GroupManager from "../client/util/GroupManager";
 
 
 function _readOnlySetter(): never {
@@ -107,11 +108,57 @@ export function OVERRIDE_ACL(val: boolean) {
     _overrideAcl = val;
 }
 
+const HierarchyMapping = new Map<symbol, number>([
+    [AclPrivate, 0],
+    [AclReadonly, 1],
+    [AclAddonly, 2],
+    [AclEdit, 3]
+]);
+
+export function getEffectiveAcl(target: any): symbol {
+
+    console.log("in getEffectiveAcl");
+
+    if (target[AclSym].ACL) return target[AclSym].ACL;
+
+    let effectiveAcl = AclEdit;
+
+    for (const [key, value] of Object.entries(target[AclSym])) {
+        if (key.startsWith("ACL-")) {
+            if (GroupManager.Instance.currentUserGroups.includes(key.substring(4)) || Doc.CurrentUserEmail === key.substring(4).replace("_", ".")) {
+                if (HierarchyMapping.get(value as symbol)! > HierarchyMapping.get(effectiveAcl)!) {
+                    effectiveAcl = value as symbol;
+                    if (effectiveAcl === AclEdit) break;
+                }
+            }
+        }
+    }
+
+    return effectiveAcl;
+}
+
+function testPermission(target: any, in_prop: string | symbol | number): boolean {
+
+    console.log("here");
+    // if (target[AclSym].ACL !== AclEdit && !_overrideAcl && !DocServer.PlaygroundFields.includes(in_prop.toString())) return false;
+    if (target[AclSym].ACL === AclEdit) return true;
+    for (const [key, value] of Object.entries(target[AclSym])) {
+        if (key.startsWith("ACL-")) {
+            if (GroupManager.Instance.currentUserGroups.includes(key.substring(4))) {
+                if (value === AclEdit) return true;
+            }
+        }
+    }
+    return _overrideAcl || DocServer.PlaygroundFields.includes(in_prop.toString());
+}
+
 const layoutProps = ["panX", "panY", "width", "height", "nativeWidth", "nativeHeight", "fitWidth", "fitToBox",
     "chromeStatus", "viewType", "gridGap", "xMargin", "yMargin", "autoHeight"];
 export function setter(target: any, in_prop: string | symbol | number, value: any, receiver: any): boolean {
+    console.log("in setter")
     let prop = in_prop;
-    if (target[AclSym] && !_overrideAcl && !DocServer.PlaygroundFields.includes(in_prop.toString())) return true; // generalise to a testpermission function
+    // if (target[AclSym] && !_overrideAcl && !DocServer.PlaygroundFields.includes(in_prop.toString())) return true; // generalise to a testpermission function
+    if (!testPermission(target, in_prop)) return true;
     if (typeof prop === "string" && prop !== "__id" && prop !== "__fields" && (prop.startsWith("_") || layoutProps.includes(prop))) {
         if (!prop.startsWith("_")) {
             console.log(prop + " is deprecated - switch to _" + prop);
@@ -129,9 +176,11 @@ export function setter(target: any, in_prop: string | symbol | number, value: an
 }
 
 export function getter(target: any, in_prop: string | symbol | number, receiver: any): any {
+    console.log("in getter")
     let prop = in_prop;
-    if (in_prop === AclSym) return _overrideAcl ? undefined : target[AclSym];
-    if (target[AclSym] === AclPrivate && !_overrideAcl) return undefined;
+    const effectiveAcl = getEffectiveAcl(target);
+    if (in_prop === AclSym) return _overrideAcl ? undefined : effectiveAcl;
+    if (effectiveAcl === AclPrivate && !_overrideAcl) return undefined;
     if (prop === LayoutSym) {
         return target.__LAYOUT__;
     }
@@ -168,7 +217,8 @@ function getFieldImpl(target: any, prop: string | number, receiver: any, ignoreP
     }
     if (field === undefined && !ignoreProto && prop !== "proto") {
         const proto = getFieldImpl(target, "proto", receiver, true);//TODO tfs: instead of receiver we could use target[SelfProxy]... I don't which semantics we want or if it really matters
-        if (proto instanceof Doc && proto[AclSym] !== AclPrivate) {
+        console.log("here");
+        if (proto instanceof Doc && getEffectiveAcl(proto) !== AclPrivate) {
             return getFieldImpl(proto[Self], prop, receiver, ignoreProto);
         }
         return undefined;
-- 
cgit v1.2.3-70-g09d2


From 12285b8c0aff514a2345874508c35b6de1026ef4 Mon Sep 17 00:00:00 2001
From: usodhi <61431818+usodhi@users.noreply.github.com>
Date: Thu, 9 Jul 2020 12:25:46 +0530
Subject: acls now work I think + some cleanup

---
 src/client/DocServer.ts                            |  1 -
 src/client/util/GroupManager.tsx                   | 13 +++-
 src/client/util/GroupMemberView.tsx                |  2 +-
 src/client/util/SharingManager.tsx                 |  2 -
 src/client/views/DocComponent.tsx                  |  1 -
 src/client/views/GlobalKeyHandler.ts               |  2 +
 src/client/views/collections/CollectionView.tsx    | 21 +++---
 src/client/views/nodes/DocumentContentsView.tsx    |  1 -
 src/client/views/nodes/DocumentView.tsx            | 22 +++---
 .../views/nodes/formattedText/FormattedTextBox.tsx |  1 -
 src/fields/Doc.ts                                  | 24 +------
 src/fields/util.ts                                 | 84 +++++++++++-----------
 12 files changed, 84 insertions(+), 90 deletions(-)

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

diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index 860a8fd92..bac324c77 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -40,7 +40,6 @@ export namespace DocServer {
 
     export var PlaygroundFields: string[];
     export function setPlaygroundFields(livePlaygroundFields: string[]) {
-        console.log("here");
         DocServer.PlaygroundFields = livePlaygroundFields;
         livePlaygroundFields.forEach(f => DocServer.setFieldWriteMode(f, DocServer.WriteMode.LivePlayground));
     }
diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx
index 83b206f94..23bdd248b 100644
--- a/src/client/util/GroupManager.tsx
+++ b/src/client/util/GroupManager.tsx
@@ -14,6 +14,7 @@ import Select from 'react-select';
 import "./GroupManager.scss";
 import { StrCast } from "../../fields/Types";
 import GroupMemberView from "./GroupMemberView";
+import { setGroups } from "../../fields/util";
 
 library.add(fa.faWindowClose);
 
@@ -54,7 +55,7 @@ export default class GroupManager extends React.Component<{}> {
                 if (members.includes(Doc.CurrentUserEmail)) this.currentUserGroups.push(StrCast(group.groupName));
             });
         })
-            .finally(() => console.log(this.currentUserGroups));
+            .finally(() => setGroups(this.currentUserGroups));
 
         // (this.GroupManagerDoc?.data as List<Doc>).forEach(group => {
         //     Promise.resolve(group).then(resolvedGroup => {
@@ -178,6 +179,10 @@ export default class GroupManager extends React.Component<{}> {
         groupDoc.groupName = groupName;
         groupDoc.owners = JSON.stringify([Doc.CurrentUserEmail]);
         groupDoc.members = JSON.stringify(memberEmails);
+        if (memberEmails.includes(Doc.CurrentUserEmail)) {
+            this.currentUserGroups.push(groupName);
+            setGroups(this.currentUserGroups);
+        }
         this.addGroup(groupDoc);
     }
 
@@ -204,6 +209,12 @@ export default class GroupManager extends React.Component<{}> {
                 // SharingManager.Instance.setInternalGroupSharing(group, "Not Shared");
                 Doc.RemoveDocFromList(this.GroupManagerDoc, "data", group);
                 SharingManager.Instance.removeGroup(group);
+                const members: string[] = JSON.parse(StrCast(group.members));
+                if (members.includes(Doc.CurrentUserEmail)) {
+                    const index = this.currentUserGroups.findIndex(groupName => groupName === group.groupName);
+                    index !== -1 && this.currentUserGroups.splice(index, 1);
+                    setGroups(this.currentUserGroups);
+                }
                 if (group === this.currentGroup) {
                     runInAction(() => this.currentGroup = undefined);
                 }
diff --git a/src/client/util/GroupMemberView.tsx b/src/client/util/GroupMemberView.tsx
index cc279b6b2..742caa676 100644
--- a/src/client/util/GroupMemberView.tsx
+++ b/src/client/util/GroupMemberView.tsx
@@ -51,7 +51,7 @@ export default class GroupMemberView extends React.Component<GroupMemberViewProp
                                     }}
                                 />
                             </div>
-                            <button onClick={() => console.log(GroupManager.Instance.deleteGroup(this.props.group))}>Delete group</button>
+                            <button onClick={() => GroupManager.Instance.deleteGroup(this.props.group)}>Delete group</button>
                         </div> :
                         null}
                 </div>
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index bec6b973b..d64302456 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -359,8 +359,6 @@ export default class SharingManager extends React.Component<{}> {
     share = () => {
         this.selectedUsers?.forEach(user => {
             if (user.value.includes(indType)) {
-                console.log(user);
-                console.log(this.users.find(u => u.user.email === user.label));
                 this.setInternalSharing(this.users.find(u => u.user.email === user.label)!, this.permissions);
             }
             else {
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index e8c34d931..781673e59 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -138,7 +138,6 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
             const targetDataDoc = this.props.Document[DataSym];
             const docList = DocListCast(targetDataDoc[this.annotationKey]);
             const added = docs.filter(d => !docList.includes(d));
-            console.log("here");
             const effectiveAcl = getEffectiveAcl(this.dataDoc);
             if (added.length) {
                 if (effectiveAcl === AclReadonly) {
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index a3a023164..45d53a5f5 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -22,6 +22,7 @@ import { DocumentView } from "./nodes/DocumentView";
 import { DocumentLinksButton } from "./nodes/DocumentLinksButton";
 import PDFMenu from "./pdf/PDFMenu";
 import { ContextMenu } from "./ContextMenu";
+import GroupManager from "../util/GroupManager";
 
 const modifiers = ["control", "meta", "shift", "alt"];
 type KeyHandler = (keycode: string, e: KeyboardEvent) => KeyControlInfo | Promise<KeyControlInfo>;
@@ -107,6 +108,7 @@ export default class KeyManager {
                 GoogleAuthenticationManager.Instance.cancel();
                 HypothesisAuthenticationManager.Instance.cancel();
                 SharingManager.Instance.close();
+                GroupManager.Instance.close();
                 break;
             case "delete":
             case "backspace":
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 31f0c1df3..6a6a475c8 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -8,7 +8,7 @@ import * as React from 'react';
 import Lightbox from 'react-image-lightbox-with-rotate';
 import 'react-image-lightbox-with-rotate/style.css'; // This only needs to be imported once in your app
 import { DateField } from '../../../fields/DateField';
-import { AclAddonly, AclReadonly, AclSym, DataSym, Doc, DocListCast, Field, Opt } from '../../../fields/Doc';
+import { AclAddonly, AclReadonly, AclSym, DataSym, Doc, DocListCast, Field, Opt, AclEdit } from '../../../fields/Doc';
 import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
 import { ObjectField } from '../../../fields/ObjectField';
@@ -132,8 +132,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
         const targetDataDoc = this.props.Document[DataSym];
         const docList = DocListCast(targetDataDoc[this.props.fieldKey]);
         const added = docs.filter(d => !docList.includes(d));
-        console.log("here");
-        const effectiveAcl = getEffectiveAcl(this.dataDoc);
+        const effectiveAcl = getEffectiveAcl(this.props.Document);
         if (added.length) {
             if (effectiveAcl === AclReadonly) {
                 return false;
@@ -167,13 +166,15 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
 
     @action.bound
     removeDocument = (doc: any): boolean => {
-        const docs = doc instanceof Doc ? [doc] : doc as Doc[];
-        const targetDataDoc = this.props.Document[DataSym];
-        const value = DocListCast(targetDataDoc[this.props.fieldKey]);
-        const result = value.filter(v => !docs.includes(v));
-        if (result.length !== value.length) {
-            targetDataDoc[this.props.fieldKey] = new List<Doc>(result);
-            return true;
+        if (getEffectiveAcl(this.props.Document) === AclEdit) {
+            const docs = doc instanceof Doc ? [doc] : doc as Doc[];
+            const targetDataDoc = this.props.Document[DataSym];
+            const value = DocListCast(targetDataDoc[this.props.fieldKey]);
+            const result = value.filter(v => !docs.includes(v));
+            if (result.length !== value.length) {
+                targetDataDoc[this.props.fieldKey] = new List<Doc>(result);
+                return true;
+            }
         }
         return false;
     }
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index d480c76d0..e34ceb994 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -184,7 +184,6 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
 
         const bindings = this.CreateBindings(onClick, onInput);
         //  layoutFrame = splits.length > 1 ? splits[0] + splits[1].replace(/{([^{}]|(?R))*}/, replacer4) : ""; // might have been more elegant if javascript supported recursive patterns
-        console.log("here");
         return (this.props.renderDepth > 12 || !layoutFrame || !this.layoutDoc || getEffectiveAcl(this.layoutDoc) === AclPrivate) ? (null) :
             <ObserverJsxParser
                 key={42}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 1d76633be..b9ae8b444 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -26,7 +26,7 @@ import { InteractionUtils } from '../../util/InteractionUtils';
 import { Scripting } from '../../util/Scripting';
 import { SearchUtil } from '../../util/SearchUtil';
 import { SelectionManager } from "../../util/SelectionManager";
-import SharingManager from '../../util/SharingManager';
+import SharingManager, { SharingPermissions } from '../../util/SharingManager';
 import { Transform } from "../../util/Transform";
 import { undoBatch, UndoManager } from "../../util/UndoManager";
 import { CollectionView, CollectionViewType } from '../collections/CollectionView';
@@ -725,7 +725,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
     @undoBatch
     @action
-    setAcl = (acl: "readOnly" | "addOnly" | "ownerOnly" | "write") => {
+    setAcl = (acl: SharingPermissions) => {
         this.dataDoc.ACL = this.props.Document.ACL = acl;
         DocListCast(this.dataDoc[Doc.LayoutFieldKey(this.dataDoc)]).map(d => {
             if (d.author === Doc.CurrentUserEmail) d.ACL = acl;
@@ -735,7 +735,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     }
     @undoBatch
     @action
-    testAcl = (acl: "readOnly" | "addOnly" | "ownerOnly" | "write") => {
+    testAcl = (acl: SharingPermissions) => {
         this.dataDoc.author = this.props.Document.author = "ADMIN";
         this.dataDoc.ACL = this.props.Document.ACL = acl;
         DocListCast(this.dataDoc[Doc.LayoutFieldKey(this.dataDoc)]).map(d => {
@@ -845,12 +845,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
         const existingAcls = cm.findByDescription("Privacy...");
         const aclItems: ContextMenuProps[] = existingAcls && "subitems" in existingAcls ? existingAcls.subitems : [];
-        aclItems.push({ description: "Make Add Only", event: () => this.setAcl("addOnly"), icon: "concierge-bell" });
-        aclItems.push({ description: "Make Read Only", event: () => this.setAcl("readOnly"), icon: "concierge-bell" });
-        aclItems.push({ description: "Make Private", event: () => this.setAcl("ownerOnly"), icon: "concierge-bell" });
-        aclItems.push({ description: "Make Editable", event: () => this.setAcl("write"), icon: "concierge-bell" });
-        aclItems.push({ description: "Test Private", event: () => this.testAcl("ownerOnly"), icon: "concierge-bell" });
-        aclItems.push({ description: "Test Readonly", event: () => this.testAcl("readOnly"), icon: "concierge-bell" });
+        aclItems.push({ description: "Make Add Only", event: () => this.setAcl(SharingPermissions.Add), icon: "concierge-bell" });
+        aclItems.push({ description: "Make Read Only", event: () => this.setAcl(SharingPermissions.View), icon: "concierge-bell" });
+        aclItems.push({ description: "Make Private", event: () => this.setAcl(SharingPermissions.None), icon: "concierge-bell" });
+        aclItems.push({ description: "Make Editable", event: () => this.setAcl(SharingPermissions.Edit), icon: "concierge-bell" });
+        aclItems.push({ description: "Test Private", event: () => this.testAcl(SharingPermissions.None), icon: "concierge-bell" });
+        aclItems.push({ description: "Test Readonly", event: () => this.testAcl(SharingPermissions.View), icon: "concierge-bell" });
         !existingAcls && cm.addItem({ description: "Privacy...", subitems: aclItems, icon: "question" });
 
         // const recommender_subitems: ContextMenuProps[] = [];
@@ -1206,9 +1206,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     }
 
     render() {
-        console.log("here");
-        if (getEffectiveAcl(this.props.Document) === AclPrivate) return (null);
         if (!(this.props.Document instanceof Doc)) return (null);
+        if (getEffectiveAcl(this.props.Document) === AclPrivate) return (null);
+        if (this.props.Document.hidden) return (null);
         const backgroundColor = Doc.UserDoc().renderStyle === "comic" ? undefined : this.props.forcedBackgroundColor?.(this.Document) || StrCast(this.layoutDoc._backgroundColor) || StrCast(this.layoutDoc.backgroundColor) || StrCast(this.Document.backgroundColor) || this.props.backgroundColor?.(this.Document);
         const opacity = Cast(this.layoutDoc._opacity, "number", Cast(this.layoutDoc.opacity, "number", Cast(this.Document.opacity, "number", null)));
         const finalOpacity = this.props.opacity ? this.props.opacity() : opacity;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index a0dbcd980..ccf83cbf9 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -227,7 +227,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             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());
             // if (!this.dataDoc[AclSym]) { // what?
-            console.log("here");
             if (getEffectiveAcl(this.dataDoc) === AclEdit) {
                 if (!this._applyingChange && json.replace(/"selection":.*/, "") !== curProto?.Data.replace(/"selection":.*/, "")) {
                     this._applyingChange = true;
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index c965dc282..27eabf451 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -109,7 +109,6 @@ const AclMap = new Map<string, symbol>([
 ]);
 
 export function fetchProto(doc: Doc) {
-    console.log("in fetchproto");
     if (doc.author !== Doc.CurrentUserEmail) { // storing acls for groups needs to be extended here - AclSym should store a datastructure that stores information about permissions
 
         const permissions: { [key: string]: symbol } = {};
@@ -118,22 +117,8 @@ export function fetchProto(doc: Doc) {
             if (key.startsWith("ACL")) permissions[key] = AclMap.get(StrCast(doc[key]))!;
         });
 
-        doc[AclSym] = permissions;
 
-        // const acl = Doc.Get(doc, "ACL", true);
-        // switch (acl) {
-        //     case "ownerOnly":
-        //         doc[AclSym] = AclPrivate;
-        //         return undefined;
-        //     case "readOnly":
-        //         doc[AclSym] = AclReadonly;
-        //         break;
-        //     case "addOnly":
-        //         doc[AclSym] = AclAddonly;
-        //         break;
-        //     // case "edit":
-        //     //     doc[AclSym] = AclEdit;
-        // }
+        if (Object.keys(permissions).length) doc[AclSym] = permissions;
     }
 
     if (doc.proto instanceof Promise) {
@@ -208,7 +193,7 @@ export class Doc extends RefField {
 
     private [Self] = this;
     private [SelfProxy]: any;
-    public [AclSym]: any;
+    public [AclSym]: { [key: string]: symbol };
     public [WidthSym] = () => NumCast(this[SelfProxy]._width);
     public [HeightSym] = () => NumCast(this[SelfProxy]._height);
     public [ToScriptString]() { return `DOC-"${this[Self][Id]}"-`; }
@@ -232,8 +217,8 @@ export class Doc extends RefField {
             return Cast(this[SelfProxy][renderFieldKey + "-layout[" + templateLayoutDoc[Id] + "]"], Doc, null) || templateLayoutDoc;
         }
         return undefined;
-    }
 
+    }
 
     private [CachedUpdates]: { [key: string]: () => void | Promise<any> } = {};
     public static CurrentUserEmail: string = "";
@@ -840,7 +825,6 @@ export namespace Doc {
     }
     // don't bother memoizing (caching) the result if called from a non-reactive context. (plus this avoids a warning message)
     export function IsBrushedDegreeUnmemoized(doc: Doc) {
-        console.log("here");
         if (!doc || getEffectiveAcl(doc) === AclPrivate || getEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return 0;
         return brushManager.BrushedDoc.has(doc) ? 2 : brushManager.BrushedDoc.has(Doc.GetProto(doc)) ? 1 : 0;
     }
@@ -850,7 +834,6 @@ export namespace Doc {
         })(doc);
     }
     export function BrushDoc(doc: Doc) {
-        console.log("here");
         if (!doc || getEffectiveAcl(doc) === AclPrivate || getEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return doc;
         brushManager.BrushedDoc.set(doc, true);
         brushManager.BrushedDoc.set(Doc.GetProto(doc), true);
@@ -888,7 +871,6 @@ export namespace Doc {
     }
     const highlightManager = new HighlightBrush();
     export function IsHighlighted(doc: Doc) {
-        console.log("here");
         if (!doc || getEffectiveAcl(doc) === AclPrivate || getEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return false;
         return highlightManager.HighlightedDoc.get(doc) || highlightManager.HighlightedDoc.get(Doc.GetProto(doc));
     }
diff --git a/src/fields/util.ts b/src/fields/util.ts
index a3e7a36f8..a1af1d3c5 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -1,5 +1,5 @@
 import { UndoManager } from "../client/util/UndoManager";
-import { Doc, Field, FieldResult, UpdatingFromServer, LayoutSym, AclSym, AclPrivate, AclEdit, AclReadonly, AclAddonly } from "./Doc";
+import { Doc, FieldResult, UpdatingFromServer, LayoutSym, AclPrivate, AclEdit, AclReadonly, AclAddonly, AclSym, fetchProto } from "./Doc";
 import { SerializationHelper } from "../client/util/SerializationHelper";
 import { ProxyField, PrefetchProxy } from "./Proxy";
 import { RefField } from "./RefField";
@@ -9,7 +9,6 @@ import { Parent, OnUpdate, Update, Id, SelfProxy, Self } from "./FieldSymbols";
 import { DocServer } from "../client/DocServer";
 import { ComputedField } from "./ScriptField";
 import { ScriptCast } from "./Types";
-import GroupManager from "../client/util/GroupManager";
 
 
 function _readOnlySetter(): never {
@@ -108,57 +107,65 @@ export function OVERRIDE_ACL(val: boolean) {
     _overrideAcl = val;
 }
 
-const HierarchyMapping = new Map<symbol, number>([
-    [AclPrivate, 0],
-    [AclReadonly, 1],
-    [AclAddonly, 2],
-    [AclEdit, 3]
-]);
+let currentUserGroups: string[] = [];
+let currentUserEmail: string;// = Doc.CurrentUserEmail;
 
-export function getEffectiveAcl(target: any): symbol {
+export function setGroups(groups: string[]) {
+    currentUserGroups = groups;
+    currentUserEmail = Doc.CurrentUserEmail;
+}
 
-    console.log("in getEffectiveAcl");
 
-    if (target[AclSym].ACL) return target[AclSym].ACL;
+export function getEffectiveAcl(target: any, in_prop?: string | symbol | number): symbol {
 
-    let effectiveAcl = AclEdit;
+    const HierarchyMapping = new Map<symbol, number>([
+        [AclPrivate, 0],
+        [AclReadonly, 1],
+        [AclAddonly, 2],
+        [AclEdit, 3]
+    ]);
 
-    for (const [key, value] of Object.entries(target[AclSym])) {
-        if (key.startsWith("ACL-")) {
-            if (GroupManager.Instance.currentUserGroups.includes(key.substring(4)) || Doc.CurrentUserEmail === key.substring(4).replace("_", ".")) {
-                if (HierarchyMapping.get(value as symbol)! > HierarchyMapping.get(effectiveAcl)!) {
-                    effectiveAcl = value as symbol;
-                    if (effectiveAcl === AclEdit) break;
-                }
-            }
-        }
+    if (!target[AclSym] && target instanceof Doc) {
+        fetchProto(target);
     }
 
-    return effectiveAcl;
-}
+    if (target[AclSym] && Object.keys(target[AclSym]).length) {
+
+        if (target.author === currentUserEmail) return AclEdit;
+
+        if (_overrideAcl || (in_prop && DocServer.PlaygroundFields?.includes(in_prop.toString()))) return AclEdit;
 
-function testPermission(target: any, in_prop: string | symbol | number): boolean {
+        if (target[AclSym].ACL) return target[AclSym].ACL;
 
-    console.log("here");
-    // if (target[AclSym].ACL !== AclEdit && !_overrideAcl && !DocServer.PlaygroundFields.includes(in_prop.toString())) return false;
-    if (target[AclSym].ACL === AclEdit) return true;
-    for (const [key, value] of Object.entries(target[AclSym])) {
-        if (key.startsWith("ACL-")) {
-            if (GroupManager.Instance.currentUserGroups.includes(key.substring(4))) {
-                if (value === AclEdit) return true;
+        let effectiveAcl = AclPrivate;
+        let aclPresent = false;
+
+        for (const [key, value] of Object.entries(target[AclSym])) {
+            if (key.startsWith("ACL-")) {
+                if (currentUserGroups.includes(key.substring(4)) || currentUserEmail === key.substring(4).replace("_", ".")) {
+                    if (HierarchyMapping.get(value as symbol)! >= HierarchyMapping.get(effectiveAcl)!) {
+                        aclPresent = true;
+                        effectiveAcl = value as symbol;
+                        if (effectiveAcl === AclEdit) break;
+                    }
+                }
             }
         }
+        return aclPresent ? effectiveAcl : AclEdit;
+    }
+    else {
+        return AclEdit;
     }
-    return _overrideAcl || DocServer.PlaygroundFields.includes(in_prop.toString());
 }
 
+
 const layoutProps = ["panX", "panY", "width", "height", "nativeWidth", "nativeHeight", "fitWidth", "fitToBox",
     "chromeStatus", "viewType", "gridGap", "xMargin", "yMargin", "autoHeight"];
 export function setter(target: any, in_prop: string | symbol | number, value: any, receiver: any): boolean {
-    console.log("in setter")
     let prop = in_prop;
-    // if (target[AclSym] && !_overrideAcl && !DocServer.PlaygroundFields.includes(in_prop.toString())) return true; // generalise to a testpermission function
-    if (!testPermission(target, in_prop)) return true;
+    if (getEffectiveAcl(target, in_prop) !== AclEdit) {
+        return true;
+    }
     if (typeof prop === "string" && prop !== "__id" && prop !== "__fields" && (prop.startsWith("_") || layoutProps.includes(prop))) {
         if (!prop.startsWith("_")) {
             console.log(prop + " is deprecated - switch to _" + prop);
@@ -176,11 +183,9 @@ export function setter(target: any, in_prop: string | symbol | number, value: an
 }
 
 export function getter(target: any, in_prop: string | symbol | number, receiver: any): any {
-    console.log("in getter")
     let prop = in_prop;
-    const effectiveAcl = getEffectiveAcl(target);
-    if (in_prop === AclSym) return _overrideAcl ? undefined : effectiveAcl;
-    if (effectiveAcl === AclPrivate && !_overrideAcl) return undefined;
+    if (in_prop === AclSym) return _overrideAcl ? undefined : target[AclSym];
+    if (getEffectiveAcl(target) === AclPrivate && !_overrideAcl) return undefined;
     if (prop === LayoutSym) {
         return target.__LAYOUT__;
     }
@@ -217,7 +222,6 @@ function getFieldImpl(target: any, prop: string | number, receiver: any, ignoreP
     }
     if (field === undefined && !ignoreProto && prop !== "proto") {
         const proto = getFieldImpl(target, "proto", receiver, true);//TODO tfs: instead of receiver we could use target[SelfProxy]... I don't which semantics we want or if it really matters
-        console.log("here");
         if (proto instanceof Doc && getEffectiveAcl(proto) !== AclPrivate) {
             return getFieldImpl(proto[Self], prop, receiver, ignoreProto);
         }
-- 
cgit v1.2.3-70-g09d2


From d70c9004215aea00514030be4137ccc2247b541a Mon Sep 17 00:00:00 2001
From: usodhi <61431818+usodhi@users.noreply.github.com>
Date: Thu, 9 Jul 2020 12:42:20 +0530
Subject: removed some unnecessary imports

---
 src/client/util/SharingManager.tsx                        | 2 +-
 src/client/views/DocComponent.tsx                         | 2 +-
 src/client/views/collections/CollectionView.tsx           | 2 +-
 src/client/views/nodes/DocumentContentsView.tsx           | 2 +-
 src/client/views/nodes/DocumentView.tsx                   | 2 +-
 src/client/views/nodes/formattedText/FormattedTextBox.tsx | 3 +--
 6 files changed, 6 insertions(+), 7 deletions(-)

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

diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index d64302456..fd3b2dd04 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -1,7 +1,7 @@
 import { observable, runInAction, action } from "mobx";
 import * as React from "react";
 import MainViewModal from "../views/MainViewModal";
-import { Doc, Opt, DocCastAsync, DocListCast } from "../../fields/Doc";
+import { Doc, Opt, DocListCast } from "../../fields/Doc";
 import { DocServer } from "../DocServer";
 import { Cast, StrCast } from "../../fields/Types";
 import * as RequestPromise from "request-promise";
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 781673e59..43ffe225f 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -1,4 +1,4 @@
-import { Doc, Opt, DataSym, DocListCast, AclSym, AclReadonly, AclAddonly } from '../../fields/Doc';
+import { Doc, Opt, DataSym, DocListCast, AclReadonly, AclAddonly } from '../../fields/Doc';
 import { Touchable } from './Touchable';
 import { computed, action, observable } from 'mobx';
 import { Cast, BoolCast, ScriptCast } from '../../fields/Types';
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 6a6a475c8..032012b2d 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -8,7 +8,7 @@ import * as React from 'react';
 import Lightbox from 'react-image-lightbox-with-rotate';
 import 'react-image-lightbox-with-rotate/style.css'; // This only needs to be imported once in your app
 import { DateField } from '../../../fields/DateField';
-import { AclAddonly, AclReadonly, AclSym, DataSym, Doc, DocListCast, Field, Opt, AclEdit } from '../../../fields/Doc';
+import { AclAddonly, AclReadonly, DataSym, Doc, DocListCast, Field, Opt, AclEdit } from '../../../fields/Doc';
 import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
 import { ObjectField } from '../../../fields/ObjectField';
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index e34ceb994..1e6966c05 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -1,6 +1,6 @@
 import { computed } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, Opt, Field, AclSym, AclPrivate } from "../../../fields/Doc";
+import { Doc, Opt, Field, AclPrivate } from "../../../fields/Doc";
 import { Cast, StrCast, NumCast } from "../../../fields/Types";
 import { OmitKeys, Without, emptyPath } from "../../../Utils";
 import { DirectoryImportBox } from "../../util/Import & Export/DirectoryImportBox";
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index b9ae8b444..9dc30c683 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -4,7 +4,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, observable, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import * as rp from "request-promise";
-import { Doc, DocListCast, HeightSym, Opt, WidthSym, DataSym, AclSym, AclReadonly, AclPrivate } from "../../../fields/Doc";
+import { Doc, DocListCast, HeightSym, Opt, WidthSym, DataSym, AclPrivate } from "../../../fields/Doc";
 import { Document } from '../../../fields/documentSchemas';
 import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index ccf83cbf9..aa89b4a10 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -13,7 +13,7 @@ import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from "
 import { ReplaceStep } from 'prosemirror-transform';
 import { EditorView } from "prosemirror-view";
 import { DateField } from '../../../../fields/DateField';
-import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym, AclSym, AclEdit } from "../../../../fields/Doc";
+import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym, AclEdit } from "../../../../fields/Doc";
 import { documentSchema } from '../../../../fields/documentSchemas';
 import applyDevTools = require("prosemirror-dev-tools");
 import { removeMarkWithAttrs } from "./prosemirrorPatches";
@@ -226,7 +226,6 @@ 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());
-            // if (!this.dataDoc[AclSym]) { // what?
             if (getEffectiveAcl(this.dataDoc) === AclEdit) {
                 if (!this._applyingChange && json.replace(/"selection":.*/, "") !== curProto?.Data.replace(/"selection":.*/, "")) {
                     this._applyingChange = true;
-- 
cgit v1.2.3-70-g09d2


From bc0d6410ac42af595cea1fb242e10e464da321ae Mon Sep 17 00:00:00 2001
From: usodhi <61431818+usodhi@users.noreply.github.com>
Date: Thu, 9 Jul 2020 20:41:28 +0530
Subject: change DocListCast to async in SharingManager + prevented textbox
 from showing up on click

---
 src/client/util/SharingManager.tsx                 | 71 +++++++++++-----------
 src/client/views/DocComponent.tsx                  |  4 +-
 src/client/views/collections/CollectionView.tsx    |  6 +-
 .../collections/collectionFreeForm/MarqueeView.tsx |  6 +-
 src/client/views/nodes/DocumentContentsView.tsx    |  4 +-
 src/client/views/nodes/DocumentView.tsx            |  4 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  4 +-
 src/fields/Doc.ts                                  | 16 ++---
 src/fields/util.ts                                 | 24 +++-----
 9 files changed, 70 insertions(+), 69 deletions(-)

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

diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index fd3b2dd04..af68edab6 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -1,7 +1,7 @@
 import { observable, runInAction, action } from "mobx";
 import * as React from "react";
 import MainViewModal from "../views/MainViewModal";
-import { Doc, Opt, DocListCast } from "../../fields/Doc";
+import { Doc, Opt, DocListCastAsync } from "../../fields/Doc";
 import { DocServer } from "../DocServer";
 import { Cast, StrCast } from "../../fields/Types";
 import * as RequestPromise from "request-promise";
@@ -43,19 +43,6 @@ export enum SharingPermissions {
 //     [SharingPermissions.Edit, "green"]
 // ]);
 
-// export const HierarchyMapping = new Map<string, number>([
-//     [SharingPermissions.None, 0],
-//     [SharingPermissions.View, 1],
-//     [SharingPermissions.Add, 2],
-//     [SharingPermissions.Edit, 3]
-
-//     // ["0", SharingPermissions.None],
-//     // ["1", SharingPermissions.View],
-//     // ["2", SharingPermissions.Add],
-//     // ["3", SharingPermissions.Edit]
-
-// ]);
-
 interface GroupOptions {
     label: string;
     options: UserOptions[];
@@ -88,8 +75,6 @@ export default class SharingManager extends React.Component<{}> {
     @observable private overlayOpacity = 0.4;
     @observable private selectedUsers: UserOptions[] | null = null;
     @observable private permissions: SharingPermissions = SharingPermissions.Edit;
-    @observable private sharedUsers: ValidatedUser[] = [];
-    @observable private sharedGroups: Doc[] = [];
 
     // private get linkVisible() {
     //     return this.sharingDoc ? this.sharingDoc[PublicKey] !== SharingPermissions.None : false;
@@ -162,13 +147,16 @@ export default class SharingManager extends React.Component<{}> {
 
         target[ACL] = permission;
 
-        group.docsShared ? Doc.IndexOf(target, DocListCast(group.docsShared)) === -1 && (group.docsShared as List<Doc>).push(target) : group.docsShared = new List<Doc>([target]);
+        group.docsShared ? DocListCastAsync(group.docsShared).then(resolved => Doc.IndexOf(target, resolved!) === -1 && (group.docsShared as List<Doc>).push(target)) : group.docsShared = new List<Doc>([target]);
+        // group.docsShared ? Doc.IndexOf(target, DocListCast(group.docsShared)) === -1 && (group.docsShared as List<Doc>).push(target) : group.docsShared = new List<Doc>([target]);
 
         users.forEach(({ notificationDoc }) => {
 
-            if (permission !== SharingPermissions.None) Doc.IndexOf(target, DocListCast(notificationDoc[storage])) === -1 && Doc.AddDocToList(notificationDoc, storage, target);
-            else Doc.IndexOf(target, DocListCast(notificationDoc[storage])) !== -1 && Doc.RemoveDocFromList(notificationDoc, storage, target);
-
+            DocListCastAsync(notificationDoc[storage]).then(res => console.log(res));
+            DocListCastAsync(notificationDoc[storage]).then(resolved => {
+                if (permission !== SharingPermissions.None) Doc.IndexOf(target, resolved!) === -1 && Doc.AddDocToList(notificationDoc, storage, target);
+                else Doc.IndexOf(target, resolved!) !== -1 && Doc.RemoveDocFromList(notificationDoc, storage, target);
+            });
         });
     }
 
@@ -176,7 +164,12 @@ export default class SharingManager extends React.Component<{}> {
         const user: ValidatedUser = this.users.find(user => user.user.email === email)!;
 
         if (group.docsShared) {
-            DocListCast(group.docsShared).forEach(doc => Doc.IndexOf(doc, DocListCast(user.notificationDoc[storage])) === -1 && Doc.AddDocToList(user.notificationDoc, storage, doc));
+            DocListCastAsync(group.docsShared).then(docsShared => {
+                docsShared?.forEach(doc => {
+                    DocListCastAsync(user.notificationDoc[storage]).then(resolved => Doc.IndexOf(doc, resolved!) === -1 && Doc.AddDocToList(user.notificationDoc, storage, doc));
+                });
+            });
+            // DocListCast(group.docsShared).forEach(doc => Doc.IndexOf(doc, DocListCast(user.notificationDoc[storage])) === -1 && Doc.AddDocToList(user.notificationDoc, storage, doc));
         }
     }
 
@@ -184,20 +177,28 @@ export default class SharingManager extends React.Component<{}> {
         const user: ValidatedUser = this.users.find(user => user.user.email === email)!;
 
         if (group.docsShared) {
-            DocListCast(group.docsShared).forEach(doc => Doc.IndexOf(doc, DocListCast(user.notificationDoc[storage])) !== -1 && Doc.RemoveDocFromList(user.notificationDoc, storage, doc));
+            DocListCastAsync(group.docsShared).then(docsShared => {
+                docsShared?.forEach(doc => {
+                    DocListCastAsync(user.notificationDoc[storage]).then(resolved => Doc.IndexOf(doc, resolved!) !== -1 && Doc.RemoveDocFromList(user.notificationDoc, storage, doc));
+                });
+            });
+            // DocListCast(group.docsShared).forEach(doc => Doc.IndexOf(doc, DocListCast(user.notificationDoc[storage])) === -1 && Doc.AddDocToList(user.notificationDoc, storage, doc));
         }
     }
 
     removeGroup = (group: Doc) => {
         if (group.docsShared) {
-            DocListCast(group.docsShared).forEach(doc => {
-                const ACL = `ACL-${StrCast(group.groupName)}`;
-                doc[ACL] = "Not Shared";
+            DocListCastAsync(group.docsShared).then(resolved => {
+                resolved?.forEach(doc => {
+                    const ACL = `ACL-${StrCast(group.groupName)}`;
+                    doc[ACL] = "Not Shared";
+
+                    const members: string[] = JSON.parse(StrCast(group.members));
+                    const users: ValidatedUser[] = this.users.filter(user => members.includes(user.user.email));
 
-                const members: string[] = JSON.parse(StrCast(group.members));
-                const users: ValidatedUser[] = this.users.filter(user => members.includes(user.user.email));
+                    users.forEach(user => Doc.RemoveDocFromList(user.notificationDoc, storage, doc));
+                });
 
-                users.forEach(user => Doc.RemoveDocFromList(user.notificationDoc, storage, doc));
             });
         }
     }
@@ -217,14 +218,16 @@ export default class SharingManager extends React.Component<{}> {
 
 
         if (permission !== SharingPermissions.None) {
-            !this.sharedUsers.includes(recipient) && this.sharedUsers.push(recipient);
-
-            Doc.IndexOf(target, DocListCast(notificationDoc[storage])) === -1 && Doc.AddDocToList(notificationDoc, storage, target);
+            console.log(target);
+            console.log(notificationDoc);
+            DocListCastAsync(notificationDoc[storage]).then(resolved => {
+                Doc.IndexOf(target, resolved!) === -1 && Doc.AddDocToList(notificationDoc, storage, target);
+            });
         }
         else {
-            const index = this.sharedUsers.findIndex(user => user === recipient);
-            index !== -1 && this.sharedUsers.splice(index, 1);
-            Doc.IndexOf(target, DocListCast(notificationDoc[storage])) !== -1 && Doc.RemoveDocFromList(notificationDoc, storage, target);
+            DocListCastAsync(notificationDoc[storage]).then(resolved => {
+                Doc.IndexOf(target, resolved!) === -1 && Doc.RemoveDocFromList(notificationDoc, storage, target);
+            });
         }
 
     }
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 43ffe225f..eb58d8a3e 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -7,7 +7,7 @@ import { InteractionUtils } from '../util/InteractionUtils';
 import { List } from '../../fields/List';
 import { DateField } from '../../fields/DateField';
 import { ScriptField } from '../../fields/ScriptField';
-import { getEffectiveAcl } from '../../fields/util';
+import { GetEffectiveAcl } from '../../fields/util';
 
 
 ///  DocComponent returns a generic React base class used by views that don't have 'fieldKey' props (e.g.,CollectionFreeFormDocumentView, DocumentView)
@@ -138,7 +138,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
             const targetDataDoc = this.props.Document[DataSym];
             const docList = DocListCast(targetDataDoc[this.annotationKey]);
             const added = docs.filter(d => !docList.includes(d));
-            const effectiveAcl = getEffectiveAcl(this.dataDoc);
+            const effectiveAcl = GetEffectiveAcl(this.dataDoc);
             if (added.length) {
                 if (effectiveAcl === AclReadonly) {
                     return false;
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 032012b2d..7448ae002 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -17,7 +17,7 @@ import { listSpec } from '../../../fields/Schema';
 import { ComputedField, ScriptField } from '../../../fields/ScriptField';
 import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { ImageField } from '../../../fields/URLField';
-import { TraceMobx, getEffectiveAcl } from '../../../fields/util';
+import { TraceMobx, GetEffectiveAcl } from '../../../fields/util';
 import { emptyFunction, emptyPath, returnEmptyFilter, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from '../../../Utils';
 import { Docs, DocUtils } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
@@ -132,7 +132,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
         const targetDataDoc = this.props.Document[DataSym];
         const docList = DocListCast(targetDataDoc[this.props.fieldKey]);
         const added = docs.filter(d => !docList.includes(d));
-        const effectiveAcl = getEffectiveAcl(this.props.Document);
+        const effectiveAcl = GetEffectiveAcl(this.props.Document);
         if (added.length) {
             if (effectiveAcl === AclReadonly) {
                 return false;
@@ -166,7 +166,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
 
     @action.bound
     removeDocument = (doc: any): boolean => {
-        if (getEffectiveAcl(this.props.Document) === AclEdit) {
+        if (GetEffectiveAcl(this.props.Document) === AclEdit) {
             const docs = doc instanceof Doc ? [doc] : doc as Doc[];
             const targetDataDoc = this.props.Document[DataSym];
             const value = DocListCast(targetDataDoc[this.props.fieldKey]);
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index b47236bea..971c501ca 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -1,6 +1,7 @@
 import { action, computed, observable } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, Opt, DocListCast, DataSym } from "../../../../fields/Doc";
+import { Doc, Opt, DocListCast, DataSym, AclEdit, AclAddonly } from "../../../../fields/Doc";
+import { GetEffectiveAcl } from "../../../../fields/util";
 import { InkData, InkField, InkTool } from "../../../../fields/InkField";
 import { List } from "../../../../fields/List";
 import { RichTextField } from "../../../../fields/RichTextField";
@@ -276,7 +277,8 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         } else {
             this._downX = x;
             this._downY = y;
-            PreviewCursor.Show(x, y, this.onKeyPress, this.props.addLiveTextDocument, this.props.getTransform, this.props.addDocument, this.props.nudge);
+            const effectiveAcl = GetEffectiveAcl(this.props.Document);
+            if (effectiveAcl === AclEdit || effectiveAcl === AclAddonly) PreviewCursor.Show(x, y, this.onKeyPress, this.props.addLiveTextDocument, this.props.getTransform, this.props.addDocument, this.props.nudge);
             this.clearSelection();
         }
     });
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 1e6966c05..f2f8ada68 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -36,7 +36,7 @@ import { WebBox } from "./WebBox";
 import { InkingStroke } from "../InkingStroke";
 import React = require("react");
 import { RecommendationsBox } from "../RecommendationsBox";
-import { TraceMobx, getEffectiveAcl } from "../../../fields/util";
+import { TraceMobx, GetEffectiveAcl } from "../../../fields/util";
 import { ScriptField } from "../../../fields/ScriptField";
 import XRegExp = require("xregexp");
 
@@ -184,7 +184,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
 
         const bindings = this.CreateBindings(onClick, onInput);
         //  layoutFrame = splits.length > 1 ? splits[0] + splits[1].replace(/{([^{}]|(?R))*}/, replacer4) : ""; // might have been more elegant if javascript supported recursive patterns
-        return (this.props.renderDepth > 12 || !layoutFrame || !this.layoutDoc || getEffectiveAcl(this.layoutDoc) === AclPrivate) ? (null) :
+        return (this.props.renderDepth > 12 || !layoutFrame || !this.layoutDoc || GetEffectiveAcl(this.layoutDoc) === AclPrivate) ? (null) :
             <ObserverJsxParser
                 key={42}
                 blacklistedAttrs={[]}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 9dc30c683..7c0bdc6c1 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -12,7 +12,7 @@ import { listSpec } from "../../../fields/Schema";
 import { SchemaHeaderField } from '../../../fields/SchemaHeaderField';
 import { ScriptField } from '../../../fields/ScriptField';
 import { BoolCast, Cast, NumCast, StrCast } from "../../../fields/Types";
-import { TraceMobx, getEffectiveAcl } from '../../../fields/util';
+import { TraceMobx, GetEffectiveAcl } from '../../../fields/util';
 import { GestureUtils } from '../../../pen-gestures/GestureUtils';
 import { emptyFunction, OmitKeys, returnOne, returnTransparent, Utils, emptyPath } from "../../../Utils";
 import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
@@ -1207,7 +1207,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
     render() {
         if (!(this.props.Document instanceof Doc)) return (null);
-        if (getEffectiveAcl(this.props.Document) === AclPrivate) return (null);
+        if (GetEffectiveAcl(this.props.Document) === AclPrivate) return (null);
         if (this.props.Document.hidden) return (null);
         const backgroundColor = Doc.UserDoc().renderStyle === "comic" ? undefined : this.props.forcedBackgroundColor?.(this.Document) || StrCast(this.layoutDoc._backgroundColor) || StrCast(this.layoutDoc.backgroundColor) || StrCast(this.Document.backgroundColor) || this.props.backgroundColor?.(this.Document);
         const opacity = Cast(this.layoutDoc._opacity, "number", Cast(this.layoutDoc.opacity, "number", Cast(this.Document.opacity, "number", null)));
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index aa89b4a10..0583c7f39 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -24,7 +24,7 @@ import { RichTextField } from "../../../../fields/RichTextField";
 import { RichTextUtils } from '../../../../fields/RichTextUtils';
 import { createSchema, makeInterface } from "../../../../fields/Schema";
 import { Cast, DateCast, NumCast, StrCast, ScriptCast } from "../../../../fields/Types";
-import { TraceMobx, OVERRIDE_ACL, getEffectiveAcl } from '../../../../fields/util';
+import { TraceMobx, OVERRIDE_ACL, GetEffectiveAcl } from '../../../../fields/util';
 import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnOne, returnZero, Utils, setupMoveUpEvents } from '../../../../Utils';
 import { GoogleApiClientUtils, Pulls, Pushes } from '../../../apis/google_docs/GoogleApiClientUtils';
 import { DocServer } from "../../../DocServer";
@@ -226,7 +226,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());
-            if (getEffectiveAcl(this.dataDoc) === AclEdit) {
+            if (GetEffectiveAcl(this.dataDoc) === AclEdit) {
                 if (!this._applyingChange && json.replace(/"selection":.*/, "") !== curProto?.Data.replace(/"selection":.*/, "")) {
                     this._applyingChange = true;
                     (curText !== Cast(this.dataDoc[this.fieldKey], RichTextField)?.Text) && (this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now())));
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 27eabf451..e8dca5fb6 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -17,7 +17,7 @@ import { RichTextField } from "./RichTextField";
 import { listSpec } from "./Schema";
 import { ComputedField } from "./ScriptField";
 import { Cast, FieldValue, NumCast, StrCast, ToConstructor } from "./Types";
-import { deleteProperty, getField, getter, makeEditable, makeReadOnly, setter, updateFunction, getEffectiveAcl } from "./util";
+import { deleteProperty, getField, getter, makeEditable, makeReadOnly, setter, updateFunction, GetEffectiveAcl } from "./util";
 import { LinkManager } from "../client/util/LinkManager";
 import { SharingPermissions } from "../client/util/SharingManager";
 
@@ -136,10 +136,10 @@ export class Doc extends RefField {
             set: setter,
             get: getter,
             // getPrototypeOf: (target) => Cast(target[SelfProxy].proto, Doc) || null, // TODO this might be able to replace the proto logic in getter
-            has: (target, key) => getEffectiveAcl(target) !== AclPrivate && key in target.__fields,
+            has: (target, key) => GetEffectiveAcl(target) !== AclPrivate && key in target.__fields,
             ownKeys: target => {
                 const obj = {} as any;
-                if (getEffectiveAcl(target) !== AclPrivate) Object.assign(obj, target.___fields);
+                if (GetEffectiveAcl(target) !== AclPrivate) Object.assign(obj, target.___fields);
                 runInAction(() => obj.__LAYOUT__ = target.__LAYOUT__);
                 return Object.keys(obj);
             },
@@ -197,7 +197,7 @@ export class Doc extends RefField {
     public [WidthSym] = () => NumCast(this[SelfProxy]._width);
     public [HeightSym] = () => NumCast(this[SelfProxy]._height);
     public [ToScriptString]() { return `DOC-"${this[Self][Id]}"-`; }
-    public [ToString]() { return `Doc(${getEffectiveAcl(this) === AclPrivate ? "-inaccessible-" : this.title})`; }
+    public [ToString]() { return `Doc(${GetEffectiveAcl(this) === AclPrivate ? "-inaccessible-" : this.title})`; }
     public get [LayoutSym]() { return this[SelfProxy].__LAYOUT__; }
     public get [DataSym]() {
         const self = this[SelfProxy];
@@ -825,7 +825,7 @@ export namespace Doc {
     }
     // don't bother memoizing (caching) the result if called from a non-reactive context. (plus this avoids a warning message)
     export function IsBrushedDegreeUnmemoized(doc: Doc) {
-        if (!doc || getEffectiveAcl(doc) === AclPrivate || getEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return 0;
+        if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return 0;
         return brushManager.BrushedDoc.has(doc) ? 2 : brushManager.BrushedDoc.has(Doc.GetProto(doc)) ? 1 : 0;
     }
     export function IsBrushedDegree(doc: Doc) {
@@ -834,14 +834,14 @@ export namespace Doc {
         })(doc);
     }
     export function BrushDoc(doc: Doc) {
-        if (!doc || getEffectiveAcl(doc) === AclPrivate || getEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return doc;
+        if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return doc;
         brushManager.BrushedDoc.set(doc, true);
         brushManager.BrushedDoc.set(Doc.GetProto(doc), true);
         return doc;
     }
     export function UnBrushDoc(doc: Doc) {
 
-        if (!doc || getEffectiveAcl(doc) === AclPrivate || getEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return doc;
+        if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return doc;
         brushManager.BrushedDoc.delete(doc);
         brushManager.BrushedDoc.delete(Doc.GetProto(doc));
         return doc;
@@ -871,7 +871,7 @@ export namespace Doc {
     }
     const highlightManager = new HighlightBrush();
     export function IsHighlighted(doc: Doc) {
-        if (!doc || getEffectiveAcl(doc) === AclPrivate || getEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return false;
+        if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return false;
         return highlightManager.HighlightedDoc.get(doc) || highlightManager.HighlightedDoc.get(Doc.GetProto(doc));
     }
     export function HighlightDoc(doc: Doc, dataAndDisplayDocs = true) {
diff --git a/src/fields/util.ts b/src/fields/util.ts
index a1af1d3c5..f82ea26e0 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -108,15 +108,13 @@ export function OVERRIDE_ACL(val: boolean) {
 }
 
 let currentUserGroups: string[] = [];
-let currentUserEmail: string;// = Doc.CurrentUserEmail;
 
 export function setGroups(groups: string[]) {
     currentUserGroups = groups;
-    currentUserEmail = Doc.CurrentUserEmail;
 }
 
 
-export function getEffectiveAcl(target: any, in_prop?: string | symbol | number): symbol {
+export function GetEffectiveAcl(target: any, in_prop?: string | symbol | number): symbol {
 
     const HierarchyMapping = new Map<symbol, number>([
         [AclPrivate, 0],
@@ -131,7 +129,7 @@ export function getEffectiveAcl(target: any, in_prop?: string | symbol | number)
 
     if (target[AclSym] && Object.keys(target[AclSym]).length) {
 
-        if (target.author === currentUserEmail) return AclEdit;
+        if (target.author === Doc.CurrentUserEmail || currentUserGroups.includes("admin")) return AclEdit;
 
         if (_overrideAcl || (in_prop && DocServer.PlaygroundFields?.includes(in_prop.toString()))) return AclEdit;
 
@@ -141,13 +139,11 @@ export function getEffectiveAcl(target: any, in_prop?: string | symbol | number)
         let aclPresent = false;
 
         for (const [key, value] of Object.entries(target[AclSym])) {
-            if (key.startsWith("ACL-")) {
-                if (currentUserGroups.includes(key.substring(4)) || currentUserEmail === key.substring(4).replace("_", ".")) {
-                    if (HierarchyMapping.get(value as symbol)! >= HierarchyMapping.get(effectiveAcl)!) {
-                        aclPresent = true;
-                        effectiveAcl = value as symbol;
-                        if (effectiveAcl === AclEdit) break;
-                    }
+            if (currentUserGroups.includes(key.substring(4)) || Doc.CurrentUserEmail === key.substring(4).replace("_", ".")) {
+                if (HierarchyMapping.get(value as symbol)! >= HierarchyMapping.get(effectiveAcl)!) {
+                    aclPresent = true;
+                    effectiveAcl = value as symbol;
+                    if (effectiveAcl === AclEdit) break;
                 }
             }
         }
@@ -163,7 +159,7 @@ const layoutProps = ["panX", "panY", "width", "height", "nativeWidth", "nativeHe
     "chromeStatus", "viewType", "gridGap", "xMargin", "yMargin", "autoHeight"];
 export function setter(target: any, in_prop: string | symbol | number, value: any, receiver: any): boolean {
     let prop = in_prop;
-    if (getEffectiveAcl(target, in_prop) !== AclEdit) {
+    if (GetEffectiveAcl(target, in_prop) !== AclEdit) {
         return true;
     }
     if (typeof prop === "string" && prop !== "__id" && prop !== "__fields" && (prop.startsWith("_") || layoutProps.includes(prop))) {
@@ -185,7 +181,7 @@ export function setter(target: any, in_prop: string | symbol | number, value: an
 export function getter(target: any, in_prop: string | symbol | number, receiver: any): any {
     let prop = in_prop;
     if (in_prop === AclSym) return _overrideAcl ? undefined : target[AclSym];
-    if (getEffectiveAcl(target) === AclPrivate && !_overrideAcl) return undefined;
+    if (GetEffectiveAcl(target) === AclPrivate && !_overrideAcl) return undefined;
     if (prop === LayoutSym) {
         return target.__LAYOUT__;
     }
@@ -222,7 +218,7 @@ function getFieldImpl(target: any, prop: string | number, receiver: any, ignoreP
     }
     if (field === undefined && !ignoreProto && prop !== "proto") {
         const proto = getFieldImpl(target, "proto", receiver, true);//TODO tfs: instead of receiver we could use target[SelfProxy]... I don't which semantics we want or if it really matters
-        if (proto instanceof Doc && getEffectiveAcl(proto) !== AclPrivate) {
+        if (proto instanceof Doc && GetEffectiveAcl(proto) !== AclPrivate) {
             return getFieldImpl(proto[Self], prop, receiver, ignoreProto);
         }
         return undefined;
-- 
cgit v1.2.3-70-g09d2


From 27b0f32460245539d77bcd435627a68364e0bc0d Mon Sep 17 00:00:00 2001
From: anika-ahluwalia <anika.ahluwalia@gmail.com>
Date: Thu, 9 Jul 2020 12:15:42 -0500
Subject: fixed linking undo issues

---
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 96 ++++++++++++----------
 src/client/views/nodes/DocumentLinksButton.tsx     |  6 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  2 +
 3 files changed, 57 insertions(+), 47 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index a3d3a210d..ffa4be4b9 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1174,47 +1174,55 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     @action
     handleDragging = (e: Event, de: DragEvent) => {
 
-        const nw = NumCast(this.Document._nativeWidth, this.props.NativeWidth());
-        const nh = NumCast(this.Document._nativeHeight, this.props.NativeHeight());
-        const hscale = nh ? this.props.PanelHeight() / nh : 1;
-        const wscale = nw ? this.props.PanelWidth() / nw : 1;
+        const top = this.panX();
+        const left = this.panY();
+
+        const size = this.getTransform().transformDirection(this.props.PanelWidth(), this.props.PanelHeight());
+
+        const scale = this.getLocalTransform().inverse().Scale;
 
         if (this._marqueeRef) {
+
             if (this._marqueeRef.current) {
 
+                // console.log("top: " + this._marqueeRef.current.clientTop);
                 // console.log("left: " + this._marqueeRef.current.clientLeft);
                 // console.log("width: " + this._marqueeRef.current.clientWidth);
-                // console.log("client x: " + de.clientX);
-
-                // console.log("top: " + this._marqueeRef.current.clientTop);
                 // console.log("height: " + this._marqueeRef.current.clientHeight);
-                // console.log("client y: " + de.clientY);
+
+                console.log("width: " + this._marqueeRef.current.getBoundingClientRect().width);
+                console.log("height: " + this._marqueeRef.current.getBoundingClientRect().width);
+                console.log("x: " + this._marqueeRef.current.getBoundingClientRect().x);
+                console.log("y: " + this._marqueeRef.current.getBoundingClientRect().y);
+
+                // console.log("mouse x: " + de.screenX);
+                // console.log("mouse y: " + de.screenY);
 
 
                 if (this._marqueeRef.current.clientWidth > 0) {
                     if (de.clientX - 315 - this._marqueeRef.current.clientLeft < 25) {
                         console.log("PAN left ");
-                        if (this.canPanX) {
-                            this.Document._panX = de.clientX - 20 - this._marqueeRef.current.clientLeft;
-                            setTimeout(action(() => {
-                                this.canPanX = true;
-                                this.panX();
-                            }), 2500);
-                            this.canPanX = false;
-                        }
+
+                        // if (this.canPanX) {
+                        //     this.Document._panX = left - 5;
+                        //     setTimeout(action(() => {
+                        //         this.canPanX = true;
+                        //         this.panX();
+                        //     }), 250);
+                        //     this.canPanX = false;
+                        // }
                     } else if (de.clientX - 315 - this._marqueeRef.current.clientLeft -
                         this._marqueeRef.current.clientWidth < 25) {
                         console.log("PAN right ");
-                        if (this.canPanX) {
-                            this.Document._panX = de.clientX - 315 - this._marqueeRef.current.clientLeft -
-                                this._marqueeRef.current.clientWidth;
-
-                            setTimeout(action(() => {
-                                this.panX();
-                                this.canPanX = true;
-                            }), 2500);
-                            this.canPanX = false;
-                        }
+
+                        // if (this.canPanX) {
+                        //     this.Document._panX = left + 5;
+                        //     setTimeout(action(() => {
+                        //         this.panX();
+                        //         this.canPanX = true;
+                        //     }), 250);
+                        //     this.canPanX = false;
+                        // }
 
                     }
                 }
@@ -1222,27 +1230,27 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                 if (this._marqueeRef.current.clientHeight > 0) {
                     if (de.clientY - 120 - this._marqueeRef.current.clientTop < 25) {
                         console.log("PAN top ");
-                        if (this.canPanY) {
-                            this.Document._panY = de.clientY - 20 - this._marqueeRef.current.clientTop;
-                            setTimeout(action(() => {
-                                this.canPanY = true;
-                                this.panY();
-                            }), 2500);
-                            this.canPanY = false;
-                        }
+
+                        // if (this.canPanY) {
+                        //     this.Document._panY = top - 5;
+                        //     setTimeout(action(() => {
+                        //         this.canPanY = true;
+                        //         this.panY();
+                        //     }), 250);
+                        //     this.canPanY = false;
+                        // }
                     } else if (de.clientY - 120 - this._marqueeRef.current.clientTop -
                         this._marqueeRef.current.clientHeight < 25) {
                         console.log("PAN bottom ");
-                        if (this.canPanY) {
-                            this.Document._panY = de.clientY - 120 - this._marqueeRef.current.clientTop -
-                                this._marqueeRef.current.clientHeight;
-
-                            setTimeout(action(() => {
-                                this.panY();
-                                this.canPanY = true;
-                            }), 2500);
-                            this.canPanY = false;
-                        }
+
+                        // if (this.canPanY) {
+                        //     this.Document._panY = top + 5;
+                        //     setTimeout(action(() => {
+                        //         this.panY();
+                        //         this.canPanY = true;
+                        //     }), 250);
+                        //     this.canPanY = false;
+                        // }
 
                     }
 
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index 44e72215d..ce96eddfe 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -3,7 +3,7 @@ import { observer } from "mobx-react";
 import { Doc, DocListCast } from "../../../fields/Doc";
 import { emptyFunction, setupMoveUpEvents, returnFalse } from "../../../Utils";
 import { DragManager } from "../../util/DragManager";
-import { UndoManager } from "../../util/UndoManager";
+import { UndoManager, undoBatch } from "../../util/UndoManager";
 import './DocumentLinksButton.scss';
 import { DocumentView } from "./DocumentView";
 import React = require("react");
@@ -29,7 +29,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
 
     @observable public static StartLink: DocumentView | undefined;
 
-    @action
+    @action @undoBatch
     onLinkButtonMoved = (e: PointerEvent) => {
         if (this._linkButton.current !== null) {
             const linkDrag = UndoManager.StartBatch("Drag Link");
@@ -56,7 +56,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
         return false;
     }
 
-
+    @undoBatch
     onLinkButtonDown = (e: React.PointerEvent): void => {
         setupMoveUpEvents(this, e, this.onLinkButtonMoved, emptyFunction, action((e, doubleTap) => {
             if (doubleTap && this.props.InMenu) {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index fc63dfbf5..30e0738bf 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -944,6 +944,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             frag.forEach(node => nodes.push(marker(node)));
             return Fragment.fromArray(nodes);
         }
+
+
         function addLinkMark(node: Node, title: string, linkId: string) {
             if (!node.isText) {
                 const content = addMarkToFrag(node.content, (node: Node) => addLinkMark(node, title, linkId));
-- 
cgit v1.2.3-70-g09d2


From bf8c338e662327b39cdef3f90c436447e48d2807 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 9 Jul 2020 13:54:07 -0400
Subject: fixed name::portal syntax to make link properly

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

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

diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index fc63dfbf5..962d0012f 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -183,7 +183,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                     this.dataDoc[key] = doc || Docs.Create.FreeformDocument([], { title: value, _width: 500, _height: 500 }, value);
                     DocUtils.Publish(this.dataDoc[key] as Doc, value, this.props.addDocument, this.props.removeDocument);
                     if (linkDoc) { (linkDoc as Doc).anchor2 = this.dataDoc[key] as Doc; }
-                    else DocUtils.MakeLink({ doc: this.rootDoc }, { doc: this.dataDoc[key] as Doc }, "link to named target", id);
+                    else DocUtils.MakeLink({ doc: this.rootDoc }, { doc: this.dataDoc[key] as Doc }, "portal link", "link to named target", id);
                 });
             });
         });
-- 
cgit v1.2.3-70-g09d2


From 1d7fb12118db997ed411fa967e832c2a5f741584 Mon Sep 17 00:00:00 2001
From: anika-ahluwalia <anika.ahluwalia@gmail.com>
Date: Thu, 9 Jul 2020 12:56:57 -0500
Subject: trying to add linked text in menu

---
 src/client/documents/Documents.ts                         | 6 +++++-
 src/client/views/linking/LinkMenuGroup.tsx                | 2 ++
 src/client/views/linking/LinkMenuItem.scss                | 8 ++++++++
 src/client/views/linking/LinkMenuItem.tsx                 | 3 ++-
 src/client/views/nodes/formattedText/FormattedTextBox.tsx | 6 +++++-
 5 files changed, 22 insertions(+), 3 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index fa85d58f0..565e7c25d 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -909,7 +909,7 @@ export namespace DocUtils {
         DocUtils.ActiveRecordings.map(d => DocUtils.MakeLink({ doc: doc }, { doc: d }, "audio link", "audio timeline"));
     }
 
-    export function MakeLink(source: { doc: Doc }, target: { doc: Doc }, linkRelationship: string = "", description: string = "", id?: string) {
+    export function MakeLink(source: { doc: Doc }, target: { doc: Doc }, linkRelationship: string = "", description: string = "", id?: string, linkedText?: string) {
         const sv = DocumentManager.Instance.getDocumentView(source.doc);
         if (sv && sv.props.ContainingCollectionDoc === target.doc) return;
         if (target.doc === Doc.UserDoc()) return undefined;
@@ -921,6 +921,10 @@ export namespace DocUtils {
         Doc.GetProto(source.doc).links = ComputedField.MakeFunction("links(self)");
         Doc.GetProto(target.doc).links = ComputedField.MakeFunction("links(self)");
 
+        if (linkedText) {
+            Doc.GetProto(linkDoc).linkedText = linkedText;
+        }
+
         return linkDoc;
     }
 
diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx
index ec17776e3..2f6b75437 100644
--- a/src/client/views/linking/LinkMenuGroup.tsx
+++ b/src/client/views/linking/LinkMenuGroup.tsx
@@ -82,11 +82,13 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
 
         return (
             <div className="linkMenu-group" ref={this._menuRef}>
+
                 {/* <div className="linkMenu-group-name">
                     <p ref={this._drag} onPointerDown={this.onLinkButtonDown}
                         className={this.props.groupType === "*" || this.props.groupType === "" ? "" : "expand-one"} > {this.props.groupType}:</p>
                     {this.props.groupType === "*" || this.props.groupType === "" ? <></> : this.viewGroupAsTable(this.props.groupType)}
                 </div> */}
+
                 <div className="linkMenu-group-wrapper">
                     {groupItems}
                 </div>
diff --git a/src/client/views/linking/LinkMenuItem.scss b/src/client/views/linking/LinkMenuItem.scss
index 9f1f82ce2..6a6ea7fa0 100644
--- a/src/client/views/linking/LinkMenuItem.scss
+++ b/src/client/views/linking/LinkMenuItem.scss
@@ -21,6 +21,14 @@
             padding: 4px 2px;
             //display: inline;
 
+            .linkMenu-source-title {
+                text-decoration: none;
+                color: rgb(43, 43, 43);
+                font-size: 7px;
+                padding-bottom: 0.75px;
+            }
+
+
             .linkMenu-destination-title {
                 text-decoration: none;
                 color: rgb(85, 120, 196);
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index 57993d240..b7cd50b7e 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -195,6 +195,8 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
                         onPointerDown={this.onLinkButtonDown}>
 
                         <div className="linkMenu-text">
+                            {this.props.linkDoc.linkedText ? <p className="linkMenu-source-title">
+                                Source: <b>{StrCast(this.props.linkDoc.linkedText)}</b></p> : null}
                             <p className="linkMenu-destination-title"
                                 onPointerDown={this.followDefault}>
                                 {StrCast(this.props.destinationDoc.title)}</p>
@@ -208,7 +210,6 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
                             <div title="Show link" className="button" ref={this._editRef} onPointerDown={emptyFunction}>
                                 <FontAwesomeIcon className="fa-icon" icon={eyeIcon} size="sm" /></div>
 
-
                             <div title="Edit link" className="button" ref={this._editRef} onPointerDown={this.onEdit}>
                                 <FontAwesomeIcon className="fa-icon" icon="edit" size="sm" /></div>
                             <div title="Delete link" className="button" onPointerDown={this.deleteLink}>
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 30e0738bf..7fd0d89a1 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -174,6 +174,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     linkOnDeselect: Map<string, string> = new Map();
 
     doLinkOnDeselect() {
+
+        console.log("link on deselect");
         Array.from(this.linkOnDeselect.entries()).map(entry => {
             const key = entry[0];
             const value = entry[1];
@@ -183,7 +185,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                     this.dataDoc[key] = doc || Docs.Create.FreeformDocument([], { title: value, _width: 500, _height: 500 }, value);
                     DocUtils.Publish(this.dataDoc[key] as Doc, value, this.props.addDocument, this.props.removeDocument);
                     if (linkDoc) { (linkDoc as Doc).anchor2 = this.dataDoc[key] as Doc; }
-                    else DocUtils.MakeLink({ doc: this.rootDoc }, { doc: this.dataDoc[key] as Doc }, "link to named target", id);
+                    else {
+                        const linkDoc = DocUtils.MakeLink({ doc: this.rootDoc }, { doc: this.dataDoc[key] as Doc }, "link to named target", id);
+                    }
                 });
             });
         });
-- 
cgit v1.2.3-70-g09d2


From 3d0c64cf9979f739177b0efd9970ad0e0a9fa3d0 Mon Sep 17 00:00:00 2001
From: anika-ahluwalia <anika.ahluwalia@gmail.com>
Date: Thu, 9 Jul 2020 17:00:12 -0500
Subject: small changes

---
 src/client/documents/Documents.ts                                 | 6 +-----
 .../collections/collectionFreeForm/CollectionFreeFormView.tsx     | 8 +++++---
 src/client/views/linking/LinkMenuItem.tsx                         | 2 +-
 src/client/views/nodes/formattedText/FormattedTextBox.tsx         | 2 ++
 4 files changed, 9 insertions(+), 9 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 565e7c25d..fa85d58f0 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -909,7 +909,7 @@ export namespace DocUtils {
         DocUtils.ActiveRecordings.map(d => DocUtils.MakeLink({ doc: doc }, { doc: d }, "audio link", "audio timeline"));
     }
 
-    export function MakeLink(source: { doc: Doc }, target: { doc: Doc }, linkRelationship: string = "", description: string = "", id?: string, linkedText?: string) {
+    export function MakeLink(source: { doc: Doc }, target: { doc: Doc }, linkRelationship: string = "", description: string = "", id?: string) {
         const sv = DocumentManager.Instance.getDocumentView(source.doc);
         if (sv && sv.props.ContainingCollectionDoc === target.doc) return;
         if (target.doc === Doc.UserDoc()) return undefined;
@@ -921,10 +921,6 @@ export namespace DocUtils {
         Doc.GetProto(source.doc).links = ComputedField.MakeFunction("links(self)");
         Doc.GetProto(target.doc).links = ComputedField.MakeFunction("links(self)");
 
-        if (linkedText) {
-            Doc.GetProto(linkDoc).linkedText = linkedText;
-        }
-
         return linkDoc;
     }
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 965bfdf24..9d79c0c89 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1181,17 +1181,19 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         const left = this.panY();
         this._lastClientY = e.detail.clientY;
 
+        console.log("draggg");
+
         const size = this.getTransform().transformDirection(this.props.PanelWidth(), this.props.PanelHeight());
         const scale = this.getLocalTransform().inverse().Scale;
 
         if (this._marqueeRef?.current) {
+
+            console.log("hellp");
+
             const dragX = e.detail.clientX;
             const dragY = e.detail.clientY;
             const bounds = this._marqueeRef.current?.getBoundingClientRect();
 
-            console.log("bottom: " + bounds.bottom);
-            console.log("right: " + bounds.right);
-
             if (dragX - bounds.left < 25) {
                 console.log("PAN left ");
 
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index d2363c7d3..f601432fe 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -189,7 +189,7 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
                             this.props.destinationDoc.type === "screenshot" ? "photo-video" :
                                 this.props.destinationDoc.type === "webcam" ? "video" :
                                     this.props.destinationDoc.type === "audio" ? "microphone" :
-                                        this.props.destinationDoc.type === "button" ? "lightning" :
+                                        this.props.destinationDoc.type === "button" ? "bolt" :
                                             this.props.destinationDoc.type === "presentation" ? "tv" :
                                                 this.props.destinationDoc.type === "query" ? "search" :
                                                     this.props.destinationDoc.type === "script" ? "terminal" :
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index bff6f1c8c..4f6927d3d 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -176,9 +176,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     doLinkOnDeselect() {
 
         console.log("link on deselect");
+
         Array.from(this.linkOnDeselect.entries()).map(entry => {
             const key = entry[0];
             const value = entry[1];
+
             const id = Utils.GenerateDeterministicGuid(this.dataDoc[Id] + key);
             DocServer.GetRefField(value).then(doc => {
                 DocServer.GetRefField(id).then(linkDoc => {
-- 
cgit v1.2.3-70-g09d2


From 20ab61d762e4c92e9ace6eb9b577667a9a8dc1e3 Mon Sep 17 00:00:00 2001
From: anika-ahluwalia <anika.ahluwalia@gmail.com>
Date: Fri, 10 Jul 2020 12:05:53 -0500
Subject: fixed tooltip error, label styling

---
 src/client/views/linking/LinkMenuItem.scss            |  3 +++
 src/client/views/linking/LinkMenuItem.tsx             | 19 +++++++++++++------
 src/client/views/nodes/DocumentLinksButton.tsx        |  2 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx    | 10 ++++++----
 .../nodes/formattedText/FormattedTextBoxComment.tsx   |  6 +++---
 5 files changed, 26 insertions(+), 14 deletions(-)

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

diff --git a/src/client/views/linking/LinkMenuItem.scss b/src/client/views/linking/LinkMenuItem.scss
index f759aef5e..3ecb306f9 100644
--- a/src/client/views/linking/LinkMenuItem.scss
+++ b/src/client/views/linking/LinkMenuItem.scss
@@ -77,6 +77,9 @@
                 color: rgb(95, 97, 102);
                 font-size: 10px;
                 margin-left: 20px;
+                max-width: 125px;
+                height: auto;
+                white-space: break-spaces;
             }
 
             p {
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index f1a744ff2..f7d189b20 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -15,7 +15,8 @@ import { setupMoveUpEvents, emptyFunction } from '../../../Utils';
 import { DocumentView } from '../nodes/DocumentView';
 import { DocumentLinksButton } from '../nodes/DocumentLinksButton';
 import { LinkDocPreview } from '../nodes/LinkDocPreview';
-import Tooltip from '@material-ui/core/Tooltip';
+import { Tooltip } from '@material-ui/core';
+import { RichTextField } from '../../../fields/RichTextField';
 library.add(faEye, faEdit, faTimes, faArrowRight, faChevronDown, faChevronUp, faPencilAlt, faEyeSlash);
 
 
@@ -203,6 +204,12 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
 
         console.log(StrCast(this.props.destinationDoc.title).length);
 
+        //  ...
+        // from anika to bob: here's where the text that is specifically linked would show up (linkDoc.storedText)
+        // ...
+        const source = this.props.sourceDoc.type === "rtf" ? this.props.linkDoc.storedText ?
+            "stored text would show up here" : undefined : undefined;
+
         return (
             <div className="linkMenu-item">
                 <div className={canExpand ? "linkMenu-item-content expand-three" : "linkMenu-item-content expand-two"}>
@@ -218,8 +225,8 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
                         onPointerDown={this.onLinkButtonDown}>
 
                         <div className="linkMenu-text">
-                            {this.props.linkDoc.linkedText ? <p className="linkMenu-source-title">
-                                <b>Source: {StrCast(this.props.linkDoc.linkedText)}</b></p> : null}
+                            {source ? <p className="linkMenu-source-title">
+                                <b>Source: {source}</b></p> : null}
                             <div className="linkMenu-title-wrapper">
                                 <div className="destination-icon-wrapper" >
                                     <FontAwesomeIcon className="destination-icon" icon={destinationIcon} size="sm" /></div>
@@ -236,16 +243,16 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
                                 <FontAwesomeIcon className="fa-icon" icon={this._showMore ? "chevron-up" : "chevron-down"} size="sm" /></div> : <></>}
 
                             <Tooltip title="Show Link">
-                                <div title="Show link" className="button" ref={this._editRef} onPointerDown={emptyFunction}>
+                                <div className="button" ref={this._editRef} onPointerDown={emptyFunction}>
                                     <FontAwesomeIcon className="fa-icon" icon={eyeIcon} size="sm" /></div>
                             </Tooltip>
 
                             <Tooltip title="Edit Link">
-                                <div title="Edit link" className="button" ref={this._editRef} onPointerDown={this.onEdit}>
+                                <div className="button" ref={this._editRef} onPointerDown={this.onEdit}>
                                     <FontAwesomeIcon className="fa-icon" icon="edit" size="sm" /></div>
                             </Tooltip>
                             <Tooltip title="Delete Link">
-                                <div title="Delete link" className="button" onPointerDown={this.deleteLink}>
+                                <div className="button" onPointerDown={this.deleteLink}>
                                     <FontAwesomeIcon className="fa-icon" icon="trash" size="sm" /></div>
                             </Tooltip>
                             {/* <div title="Follow link" className="button" onPointerDown={this.followDefault} onContextMenu={this.onContextMenu}>
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index f61ae2dd0..22431117e 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -150,7 +150,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
 
         const title = this.props.InMenu ? "Drag or tap to create links" : "Tap to view links";
 
-        const linkButton = <div title={title} ref={this._linkButton} style={{ minWidth: 20, minHeight: 20, position: "absolute", left: this.props.Offset?.[0] }}>
+        const linkButton = <div ref={this._linkButton} style={{ minWidth: 20, minHeight: 20, position: "absolute", left: this.props.Offset?.[0] }}>
             <div className={"documentLinksButton"} style={{
                 backgroundColor: DocumentLinksButton.StartLink && !!!this.props.InMenu ? "transparent" : this.props.InMenu ? "black" : "",
                 color: this.props.InMenu ? "white" : "black",
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 4f6927d3d..e3411ccf5 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -175,8 +175,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
 
     doLinkOnDeselect() {
 
-        console.log("link on deselect");
-
         Array.from(this.linkOnDeselect.entries()).map(entry => {
             const key = entry[0];
             const value = entry[1];
@@ -186,11 +184,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                 DocServer.GetRefField(id).then(linkDoc => {
                     this.dataDoc[key] = doc || Docs.Create.FreeformDocument([], { title: value, _width: 500, _height: 500 }, value);
                     DocUtils.Publish(this.dataDoc[key] as Doc, value, this.props.addDocument, this.props.removeDocument);
-                    if (linkDoc) { (linkDoc as Doc).anchor2 = this.dataDoc[key] as Doc; }
-                    else DocUtils.MakeLink({ doc: this.rootDoc }, { doc: this.dataDoc[key] as Doc }, "portal link", "link to named target", id);
+                    if (linkDoc) {
+                        (linkDoc as Doc).anchor2 = this.dataDoc[key] as Doc;
+                    } else {
+                        DocUtils.MakeLink({ doc: this.rootDoc }, { doc: this.dataDoc[key] as Doc }, "portal link", "link to named target", id);
+                    }
                 });
             });
         });
+
         this.linkOnDeselect.clear();
     }
 
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
index f1602bfbc..fa2548cb5 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -21,7 +21,7 @@ import { action } from "mobx";
 import { LinkManager } from "../../../util/LinkManager";
 import { LinkDocPreview } from "../LinkDocPreview";
 import { DocumentLinksButton } from "../DocumentLinksButton";
-import Tooltip from "@material-ui/core/Tooltip";
+import { Tooltip } from "@material-ui/core";
 
 export let formattedTextBoxCommentPlugin = new Plugin({
     view(editorView) { return new FormattedTextBoxComment(editorView); }
@@ -271,14 +271,14 @@ export class FormattedTextBoxComment {
                                         <div className="wrapper" style={{ float: "right" }}>
 
                                             <Tooltip title="Delete Link" placement="top">
-                                                <div title="Delete link" className="FormattedTextBoxComment-button"
+                                                <div className="FormattedTextBoxComment-button"
                                                     ref={(r) => this._deleteRef = r}>
                                                     <FontAwesomeIcon className="FormattedTextBoxComment-fa-icon" icon="trash" color="white"
                                                         size="sm" /></div>
                                             </Tooltip>
 
                                             <Tooltip title="Follow Link" placement="top">
-                                                <div title="Follow link" className="FormattedTextBoxComment-button"
+                                                <div className="FormattedTextBoxComment-button"
                                                     ref={(r) => this._followRef = r}>
                                                     <FontAwesomeIcon className="FormattedTextBoxComment-fa-icon" icon="arrow-right" color="white"
                                                         size="sm" />
-- 
cgit v1.2.3-70-g09d2


From 0b0183de3a13649819983203e2aba1dc6b84fdb1 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sun, 12 Jul 2020 23:34:34 -0400
Subject: fixed updating RichTextMenu with proper values for fontFamily, size,
 bullet, and alignment

---
 src/client/documents/Documents.ts                  |   2 +-
 src/client/util/CurrentUserUtils.ts                |  15 +--
 src/client/util/DragManager.ts                     |   2 +-
 src/client/views/animationtimeline/Timeline.tsx    |   4 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   2 +-
 .../collections/collectionFreeForm/MarqueeView.tsx |   2 +-
 src/client/views/nodes/DocumentView.tsx            |   2 +-
 src/client/views/nodes/LabelBox.tsx                |   2 +-
 src/client/views/nodes/SliderBox.tsx               |   2 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |   7 +-
 .../views/nodes/formattedText/RichTextMenu.tsx     | 111 ++++++++++++---------
 .../views/nodes/formattedText/RichTextRules.ts     |   2 +-
 src/fields/documentSchemas.ts                      |   2 +-
 13 files changed, 88 insertions(+), 67 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 72b716492..a415e17c8 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -123,7 +123,7 @@ export interface DocumentOptions {
     isBackground?: boolean;
     isLinkButton?: boolean;
     _columnWidth?: number;
-    _fontSize?: number;
+    _fontSize?: string;
     _fontFamily?: string;
     curPage?: number;
     currentTimecode?: number; // the current timecode of a time-based document (e.g., current time of a video)  value is in seconds
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index ce910c062..39781a5ce 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -211,7 +211,7 @@ export class CurrentUserUtils {
             details.text = new RichTextField(JSON.stringify(detailedTemplate), buxtonFieldKeys.join(" "));
 
             const shared = { _chromeStatus: "disabled", _autoHeight: true, _xMargin: 0 };
-            const detailViewOpts = { title: "detailView", _width: 300, _fontFamily: "Arial", _fontSize: 12 };
+            const detailViewOpts = { title: "detailView", _width: 300, _fontFamily: "Arial", _fontSize: "12pt" };
             const descriptionWrapperOpts = { title: "descriptions", _height: 300, _columnWidth: -1, treeViewHideTitle: true, _pivotField: "title" };
 
             const descriptionWrapper = MasonryDocument([details, short, long], { ...shared, ...descriptionWrapperOpts });
@@ -525,13 +525,13 @@ export class CurrentUserUtils {
     // Sets up the title of the button
     static mobileButtonText = (opts: DocumentOptions, buttonTitle: string) => Docs.Create.TextDocument(buttonTitle, {
         ...opts,
-        dropAction: undefined, title: buttonTitle, _fontSize: 37, _xMargin: 0, _yMargin: 0, ignoreClick: true, _chromeStatus: "disabled", backgroundColor: "rgba(0,0,0,0)"
+        dropAction: undefined, title: buttonTitle, _fontSize: "37pt", _xMargin: 0, _yMargin: 0, ignoreClick: true, _chromeStatus: "disabled", backgroundColor: "rgba(0,0,0,0)"
     }) as any as Doc
 
     // Sets up the description of the button
     static mobileButtonInfo = (opts: DocumentOptions, buttonInfo: string) => Docs.Create.TextDocument(buttonInfo, {
         ...opts,
-        dropAction: undefined, title: "info", _fontSize: 25, _xMargin: 0, _yMargin: 0, ignoreClick: true, _chromeStatus: "disabled", backgroundColor: "rgba(0,0,0,0)", _dimMagnitude: 2,
+        dropAction: undefined, title: "info", _fontSize: "25pt", _xMargin: 0, _yMargin: 0, ignoreClick: true, _chromeStatus: "disabled", backgroundColor: "rgba(0,0,0,0)", _dimMagnitude: 2,
     }) as any as Doc
 
 
@@ -598,7 +598,7 @@ export class CurrentUserUtils {
                 _width: 500, lockedPosition: true, _chromeStatus: "disabled", title: "tools stack", forceActive: true
             })) as any as Doc;
             doc["tabs-button-tools"] = new PrefetchProxy(Docs.Create.ButtonDocument({
-                _width: 35, _height: 25, title: "Tools", _fontSize: 10,
+                _width: 35, _height: 25, title: "Tools", _fontSize: "10pt",
                 letterSpacing: "0px", textTransform: "unset", borderRounding: "5px 5px 0px 0px", boxShadow: "3px 3px 0px rgb(34, 34, 34)",
                 sourcePanel: toolsStack,
                 onDragStart: ScriptField.MakeFunction('getAlias(this.dragFactory, true)'),
@@ -663,7 +663,7 @@ export class CurrentUserUtils {
                 lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true, targetDropAction: "same"
             })) as any as Doc;
             doc["tabs-button-library"] = new PrefetchProxy(Docs.Create.ButtonDocument({
-                _width: 50, _height: 25, title: "Library", _fontSize: 10, targetDropAction: "same",
+                _width: 50, _height: 25, title: "Library", _fontSize: "10pt", targetDropAction: "same",
                 letterSpacing: "0px", textTransform: "unset", borderRounding: "5px 5px 0px 0px", boxShadow: "3px 3px 0px rgb(34, 34, 34)",
                 sourcePanel: libraryStack,
                 onDragStart: ScriptField.MakeFunction('getAlias(this.dragFactory, true)'),
@@ -681,7 +681,7 @@ export class CurrentUserUtils {
     static setupSearchBtnPanel(doc: Doc, sidebarContainer: Doc) {
         if (doc["tabs-button-search"] === undefined) {
             doc["tabs-button-search"] = new PrefetchProxy(Docs.Create.ButtonDocument({
-                _width: 50, _height: 25, title: "Search", _fontSize: 10,
+                _width: 50, _height: 25, title: "Search", _fontSize: "10pt",
                 letterSpacing: "0px", textTransform: "unset", borderRounding: "5px 5px 0px 0px", boxShadow: "3px 3px 0px rgb(34, 34, 34)",
                 sourcePanel: new PrefetchProxy(Docs.Create.QueryDocument({ title: "search stack", })) as any as Doc,
                 searchFileTypes: new List<string>([DocumentType.RTF, DocumentType.IMG, DocumentType.PDF, DocumentType.VID, DocumentType.WEB, DocumentType.SCRIPTING]),
@@ -824,7 +824,8 @@ export class CurrentUserUtils {
         doc.activeArrowStart = StrCast(doc.activeArrowStart, "");
         doc.activeArrowEnd = StrCast(doc.activeArrowEnd, "");
         doc.activeDash = StrCast(doc.activeDash, "0");
-        doc.fontSize = NumCast(doc.fontSize, 12);
+        doc.fontSize = StrCast(doc.fontSize, "12pt");
+        doc.fontFamily = StrCast(doc.fontFamily, "Arial");
         doc["constants-snapThreshold"] = NumCast(doc["constants-snapThreshold"], 10); //
         doc["constants-dragThreshold"] = NumCast(doc["constants-dragThreshold"], 4); //
         Utils.DRAG_THRESHOLD = NumCast(doc["constants-dragThreshold"]);
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 64c3d8458..5f34509a1 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -323,7 +323,7 @@ export namespace DragManager {
             dragLabel = document.createElement("div");
             dragLabel.className = "dragManager-dragLabel";
             dragLabel.style.zIndex = "100001";
-            dragLabel.style.fontSize = "10";
+            dragLabel.style.fontSize = "10pt";
             dragLabel.style.position = "absolute";
             // dragLabel.innerText = "press 'a' to embed on drop"; // bcz: need to move this to a status bar
             dragDiv.appendChild(dragLabel);
diff --git a/src/client/views/animationtimeline/Timeline.tsx b/src/client/views/animationtimeline/Timeline.tsx
index f54bd3aff..eac30ca75 100644
--- a/src/client/views/animationtimeline/Timeline.tsx
+++ b/src/client/views/animationtimeline/Timeline.tsx
@@ -385,10 +385,10 @@ export class Timeline extends React.Component<FieldViewProps> {
             const ttime = `Total: ${this.toReadTime(this._time)}`;
             return (
                 <div style={{ flexDirection: "column" }}>
-                    <div className="animation-text" style={{ fontSize: "10px", width: "100%", display: !this.props.Document.isATOn ? "block" : "none" }}>
+                    <div className="animation-text" style={{ fontSize: "10pt", width: "100%", display: !this.props.Document.isATOn ? "block" : "none" }}>
                         {ctime}
                     </div>
-                    <div className="animation-text" style={{ fontSize: "10px", width: "100%", display: !this.props.Document.isATOn ? "block" : "none" }}>
+                    <div className="animation-text" style={{ fontSize: "10pt", width: "100%", display: !this.props.Document.isATOn ? "block" : "none" }}>
                         {ttime}
                     </div>
                 </div>
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 2191021d2..ef6ea8f99 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1034,7 +1034,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         const transform = `translate(${x}px, ${y}px)`;
         if (viewDef.type === "text") {
             const text = Cast(viewDef.text, "string"); // don't use NumCast, StrCast, etc since we want to test for undefined below
-            const fontSize = Cast(viewDef.fontSize, "number");
+            const fontSize = Cast(viewDef.fontSize, "string");
             return [text, x, y].some(val => val === undefined) ? undefined :
                 {
                     ele: <div className="collectionFreeform-customText" key={(text || "") + x + y + z + color} style={{ width, height, color, fontSize, transform }}>
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index b47236bea..2db665337 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -129,7 +129,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
             } else if (!e.ctrlKey && !e.metaKey) {
                 FormattedTextBox.SelectOnLoadChar = FormattedTextBox.DefaultLayout ? e.key : "";
                 const tbox = Docs.Create.TextDocument("", {
-                    _width: 200, _height: 100, x: x, y: y, _autoHeight: true, _fontSize: NumCast(Doc.UserDoc().fontSize),
+                    _width: 200, _height: 100, x: x, y: y, _autoHeight: true, _fontSize: StrCast(Doc.UserDoc().fontSize),
                     _fontFamily: StrCast(Doc.UserDoc().fontFamily),
                     title: "-typed text-"
                 });
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 2e492a865..6ef976ceb 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1247,7 +1247,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 background: finalColor,
                 opacity: finalOpacity,
                 fontFamily: StrCast(this.Document._fontFamily, "inherit"),
-                fontSize: Cast(this.Document._fontSize, "number", null)
+                fontSize: Cast(this.Document._fontSize, "string", null)
             }}>
             {this.onClickHandler && this.props.ContainingCollectionView?.props.Document._viewType === CollectionViewType.Time ? <>
                 {this.innards}
diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index 360ead18e..836ef4149 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -67,7 +67,7 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps, LabelDocument
                 <div className="labelBox-mainButton" style={{
                     background: StrCast(this.layoutDoc.backgroundColor),
                     color: StrCast(this.layoutDoc.color, "inherit"),
-                    fontSize: NumCast(this.layoutDoc._fontSize) || "inherit",
+                    fontSize: StrCast(this.layoutDoc._fontSize) || "inherit",
                     fontFamily: StrCast(this.layoutDoc._fontFamily) || "inherit",
                     letterSpacing: StrCast(this.layoutDoc.letterSpacing),
                     textTransform: StrCast(this.layoutDoc.textTransform) as any,
diff --git a/src/client/views/nodes/SliderBox.tsx b/src/client/views/nodes/SliderBox.tsx
index 9a1aefba9..45cdfc5ad 100644
--- a/src/client/views/nodes/SliderBox.tsx
+++ b/src/client/views/nodes/SliderBox.tsx
@@ -56,7 +56,7 @@ export class SliderBox extends ViewBoxBaseComponent<FieldViewProps, SliderDocume
                 style={{ boxShadow: this.layoutDoc.opacity === 0 ? undefined : StrCast(this.layoutDoc.boxShadow, "") }}>
                 <div className="sliderBox-mainButton" onContextMenu={this.specificContextMenu} style={{
                     background: StrCast(this.layoutDoc.backgroundColor), color: StrCast(this.layoutDoc.color, "black"),
-                    fontSize: NumCast(this.layoutDoc._fontSize), letterSpacing: StrCast(this.layoutDoc.letterSpacing)
+                    fontSize: StrCast(this.layoutDoc._fontSize), letterSpacing: StrCast(this.layoutDoc.letterSpacing)
                 }} >
                     <Slider
                         mode={2}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index d69f8e3db..01ac765b9 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1351,9 +1351,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         const scale = this.props.ContentScaling() * NumCast(this.layoutDoc._viewScale, 1);
         const rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : "";
         const interactive = Doc.GetSelectedTool() === InkTool.None && !this.layoutDoc.isBackground;
-        if (this.props.isSelected()) {
-            setTimeout(() => this._editorView && RichTextMenu.Instance.updateFromDash(this._editorView, undefined, this.props), 0);
-        } else if (FormattedTextBoxComment.textBox === this) {
+        setTimeout(() => this._editorView && RichTextMenu.Instance.updateFromDash(this._editorView, undefined, this.props), this.props.isSelected() ? 10 : 0); // need to make sure that we update a text box that is selected after updating the one that was deselected
+        if (!this.props.isSelected() && FormattedTextBoxComment.textBox === this) {
             setTimeout(() => FormattedTextBoxComment.Hide(), 0);
         }
         const selPad = this.props.isSelected() ? -10 : 0;
@@ -1376,7 +1375,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                         opacity: this.props.hideOnLeave ? (this._entered ? 1 : 0.1) : 1,
                         color: this.props.color ? this.props.color : StrCast(this.layoutDoc[this.props.fieldKey + "-color"], this.props.hideOnLeave ? "white" : "inherit"),
                         pointerEvents: interactive ? undefined : "none",
-                        fontSize: Cast(this.layoutDoc._fontSize, "number", null),
+                        fontSize: Cast(this.layoutDoc._fontSize, "string", null),
                         fontFamily: StrCast(this.layoutDoc._fontFamily, "inherit"),
                         transition: "opacity 1s"
                     }}
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 1a8a4ceb7..a70f879ff 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -11,7 +11,7 @@ import { EditorState, NodeSelection, TextSelection } from "prosemirror-state";
 import { EditorView } from "prosemirror-view";
 import { Doc } from "../../../../fields/Doc";
 import { DarkPastelSchemaPalette, PastelSchemaPalette } from '../../../../fields/SchemaHeaderField';
-import { Cast, StrCast, BoolCast } from "../../../../fields/Types";
+import { Cast, StrCast, BoolCast, NumCast } from "../../../../fields/Types";
 import { unimplementedFunction, Utils } from "../../../../Utils";
 import { DocServer } from "../../../DocServer";
 import { LinkManager } from "../../../util/LinkManager";
@@ -112,6 +112,7 @@ export default class RichTextMenu extends AntimodeMenu {
             { node: schema.nodes.ordered_list.create({ mapStyle: "bullet" }), title: "Set list type", label: ":", command: this.changeListType },
             { node: schema.nodes.ordered_list.create({ mapStyle: "decimal" }), title: "Set list type", label: "1.1", command: this.changeListType },
             { node: schema.nodes.ordered_list.create({ mapStyle: "multi" }), title: "Set list type", label: "A.1", command: this.changeListType },
+            { node: schema.nodes.ordered_list.create({ mapStyle: "" }), title: "Set list type", label: "<none>", command: this.changeListType },
             //{ node: undefined, title: "Set list type", label: "Remove", command: this.changeListType },
         ];
 
@@ -217,7 +218,7 @@ export default class RichTextMenu extends AntimodeMenu {
 
     // finds font sizes and families in selection
     getActiveAlignment() {
-        if (this.view) {
+        if (this.view && this.TextView.props.isSelected()) {
             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) {
@@ -230,15 +231,18 @@ export default class RichTextMenu extends AntimodeMenu {
 
     // finds font sizes and families in selection
     getActiveListStyle() {
-        if (this.view) {
+        if (this.view && this.TextView.props.isSelected()) {
             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) {
                     return path[i].attrs.mapStyle;
                 }
             }
+            if (this.view.state.selection.$from.nodeAfter?.type === this.view.state.schema.nodes.ordered_list) {
+                return this.view.state.selection.$from.nodeAfter?.attrs.mapStyle;
+            }
         }
-        return "decimal";
+        return "";
     }
 
     // finds font sizes and families in selection
@@ -247,16 +251,21 @@ export default class RichTextMenu extends AntimodeMenu {
 
         const activeFamilies: string[] = [];
         const activeSizes: string[] = [];
-        const state = this.view.state;
-        const pos = this.view.state.selection.$from;
-        const ref_node = this.reference_node(pos);
-        if (ref_node && ref_node !== this.view.state.doc && ref_node.isText) {
-            ref_node.marks.forEach(m => {
-                m.type === state.schema.marks.pFontFamily && activeFamilies.push(m.attrs.family);
-                m.type === state.schema.marks.pFontSize && activeSizes.push(String(m.attrs.fontSize) + "pt");
-            });
+        if (this.TextView.props.isSelected()) {
+            const state = this.view.state;
+            const pos = this.view.state.selection.$from;
+            const ref_node = this.reference_node(pos);
+            if (ref_node && ref_node !== this.view.state.doc && ref_node.isText) {
+                ref_node.marks.forEach(m => {
+                    m.type === state.schema.marks.pFontFamily && activeFamilies.push(m.attrs.family);
+                    m.type === state.schema.marks.pFontSize && activeSizes.push(String(m.attrs.fontSize) + "pt");
+                });
+            }
+            !activeFamilies.length && (activeFamilies.push(StrCast(this.TextView.layoutDoc._fontFamily, StrCast(Doc.UserDoc().fontFamily))));
+            !activeSizes.length && (activeSizes.push(StrCast(this.TextView.layoutDoc._fontSize, StrCast(Doc.UserDoc().fontSize))));
         }
-
+        !activeFamilies.length && (activeFamilies.push(StrCast(Doc.UserDoc().fontFamily)));
+        !activeSizes.length && (activeSizes.push(StrCast(Doc.UserDoc().fontSize)));
         return { activeFamilies, activeSizes };
     }
 
@@ -269,14 +278,14 @@ export default class RichTextMenu extends AntimodeMenu {
 
     //finds all active marks on selection in given group
     getActiveMarksOnSelection() {
-        if (!this.view) return;
+        let activeMarks: MarkType[] = [];
+        if (!this.view || !this.TextView.props.isSelected()) 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);
         //current selection
         const { empty, ranges, $to } = this.view.state.selection as TextSelection;
         const state = this.view.state;
-        let activeMarks: MarkType[] = [];
         if (!empty) {
             activeMarks = markGroup.filter(mark => {
                 const has = false;
@@ -357,13 +366,9 @@ export default class RichTextMenu extends AntimodeMenu {
     createMarksDropdown(activeOption: string, options: { mark: Mark | null, title: string, label: string, command: (mark: Mark, view: EditorView) => void, hidden?: boolean, style?: {} }[], key: string): JSX.Element {
         const items = options.map(({ title, label, hidden, style }) => {
             if (hidden) {
-                return label === activeOption ?
-                    <option value={label} title={title} key={label} style={style ? style : {}} hidden>{label}</option> :
-                    <option value={label} title={title} key={label} style={style ? style : {}} hidden>{label}</option>;
+                return <option value={label} title={title} key={label} style={style ? style : {}} hidden>{label}</option>;
             }
-            return label === activeOption ?
-                <option value={label} title={title} key={label} style={style ? style : {}}>{label}</option> :
-                <option value={label} title={title} key={label} style={style ? style : {}}>{label}</option>;
+            return <option value={label} title={title} key={label} style={style ? style : {}}>{label}</option>;
         });
 
         const self = this;
@@ -372,37 +377,42 @@ export default class RichTextMenu extends AntimodeMenu {
             e.preventDefault();
             self.TextView.endUndoTypingBatch();
             options.forEach(({ label, mark, command }) => {
-                if (e.target.value === label) {
-                    UndoManager.RunInBatch(() => self.view && mark && command(mark, self.view), "text mark dropdown");
+                if (e.target.value === label && mark) {
+                    if (!self.TextView.props.isSelected()) {
+                        switch (mark.type) {
+                            case schema.marks.pFontFamily: Doc.UserDoc().fontFamily = mark.attrs.family; break;
+                            case schema.marks.pFontSize: Doc.UserDoc().fontSize = mark.attrs.fontSize.toString() + "pt"; break;
+                        }
+                    }
+                    else UndoManager.RunInBatch(() => self.view && mark && command(mark, self.view), "text mark dropdown");
                 }
             });
         }
-        return <select onChange={onChange} key={key}>{items}</select>;
+        return <select onChange={onChange} defaultValue={activeOption} key={key}>{items}</select>;
     }
 
-    createNodesDropdown(activeMap: string, options: { node: NodeType | any | null, title: string, label: string, command: (node: NodeType | any) => void, hidden?: boolean, style?: {} }[], key: string): JSX.Element {
-        const activeOption = activeMap === "bullet" ? ":" : activeMap === "decimal" ? "1.1" : "A.1";
+    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 {
+        const activeOption = activeMap === "bullet" ? ":" : activeMap === "decimal" ? "1.1" : activeMap === "multi" ? "A.1" : "<none>";
         const items = options.map(({ title, label, hidden, style }) => {
             if (hidden) {
-                return label === activeOption ?
-                    <option value={label} title={title} key={label} style={style ? style : {}} hidden>{label}</option> :
-                    <option value={label} title={title} key={label} style={style ? style : {}} hidden>{label}</option>;
+                return <option value={label} selected={label === activeOption} title={title} key={label} style={style ? style : {}} hidden>{label}</option>;
             }
-            return label === activeOption ?
-                <option value={label} title={title} key={label} style={style ? style : {}}>{label}</option> :
-                <option value={label} title={title} key={label} style={style ? style : {}}>{label}</option>;
+            return <option value={label} selected={label === activeOption} title={title} key={label} style={style ? style : {}}>{label}</option>;
         });
 
         const self = this;
         function onChange(val: string) {
             self.TextView.endUndoTypingBatch();
             options.forEach(({ label, node, command }) => {
-                if (val === label) {
-                    UndoManager.RunInBatch(() => self.view && node && command(node), "nodes dropdown");
+                if (val === label && node) {
+                    if (self.TextView.props.isSelected()) {
+                        UndoManager.RunInBatch(() => self.view && node && command(node), "nodes dropdown");
+                        setter(val);
+                    }
                 }
             });
         }
-        return <select onChange={e => onChange(e.target.value)} key={key}>{items}</select>;
+        return <select defaultValue={activeOption} onChange={e => onChange(e.target.value)} key={key}>{items}</select>;
     }
 
     changeFontSize = (mark: Mark, view: EditorView) => {
@@ -416,10 +426,21 @@ export default class RichTextMenu extends AntimodeMenu {
     // TODO: remove doesn't work
     //remove all node type and apply the passed-in one to the selected text
     changeListType = (nodeType: Node | undefined) => {
-        if (!this.view) return;
+        if (!this.view || (nodeType as any)?.attrs.mapStyle === "") return;
+
+        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);
+        for (let i = 0; i < path.length; i++) {
+            if (path[i]?.type === schema.nodes.ordered_list) {
+                inList = path[i];
+                fromList = path[i - 1];
+            }
+        }
 
         const marks = this.view.state.storedMarks || (this.view.state.selection.$to.parentOffset && this.view.state.selection.$from.marks());
-        if (!wrapInList(schema.nodes.ordered_list)(this.view.state, (tx2: any) => {
+        if (inList || !wrapInList(schema.nodes.ordered_list)(this.view.state, (tx2: any) => {
             const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle, this.view!.state.selection.from - 1, this.view!.state.selection.to + 1);
             marks && tx3.ensureMarks([...marks]);
             marks && tx3.setStoredMarks([...marks]);
@@ -427,12 +448,12 @@ export default class RichTextMenu extends AntimodeMenu {
             this.view!.dispatch(tx2);
         })) {
             const tx2 = this.view.state.tr;
-            if (nodeType && this.view.state.selection.$from.nodeAfter?.type === schema.nodes.ordered_list) {
-                const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle, this.view.state.selection.from, this.view.state.selection.to);
+            if (nodeType && (inList || nextIsOL)) {
+                const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle, inList ? fromList : this.view.state.selection.from,
+                    inList ? fromList + inList.nodeSize : this.view.state.selection.to);
                 marks && tx3.ensureMarks([...marks]);
                 marks && tx3.setStoredMarks([...marks]);
-
-                this.view.dispatch(tx3.setSelection(new NodeSelection(tx3.doc.resolve(this.view.state.selection.$from.pos))));
+                this.view.dispatch(tx3);
             }
         }
     }
@@ -448,13 +469,13 @@ export default class RichTextMenu extends AntimodeMenu {
         return true;
     }
     alignCenter = (state: EditorState<any>, dispatch: any) => {
-        return this.alignParagraphs(state, "center", dispatch);
+        return this.TextView.props.isSelected() && this.alignParagraphs(state, "center", dispatch);
     }
     alignLeft = (state: EditorState<any>, dispatch: any) => {
-        return this.alignParagraphs(state, "left", dispatch);
+        return this.TextView.props.isSelected() && this.alignParagraphs(state, "left", dispatch);
     }
     alignRight = (state: EditorState<any>, dispatch: any) => {
-        return this.alignParagraphs(state, "right", dispatch);
+        return this.TextView.props.isSelected() && this.alignParagraphs(state, "right", dispatch);
     }
 
     alignParagraphs(state: EditorState<any>, align: "left" | "right" | "center", dispatch: any) {
@@ -914,7 +935,7 @@ export default class RichTextMenu extends AntimodeMenu {
                 {[this.createMarksDropdown(this.activeFontSize, this.fontSizeOptions, "font size"),
                 this.createMarksDropdown(this.activeFontFamily, this.fontFamilyOptions, "font family"),
                 <div className="richTextMenu-divider" key="divider 4" />,
-                this.createNodesDropdown(this.activeListType, this.listTypeOptions, "nodes"),
+                this.createNodesDropdown(this.activeListType, this.listTypeOptions, "nodes", 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/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index ca30dde9d..ef0fead4a 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -90,7 +90,7 @@ export class RichTextRules {
                     textDoc.inlineTextCount = numInlines + 1;
                     const inlineFieldKey = "inline" + numInlines; // which field on the text document this annotation will write to
                     const inlineLayoutKey = "layout_" + inlineFieldKey; // the field holding the layout string that will render the inline annotation
-                    const textDocInline = Docs.Create.TextDocument("", { layoutKey: inlineLayoutKey, _width: 75, _height: 35, annotationOn: textDoc, _autoHeight: true, _fontSize: 9, title: "inline comment" });
+                    const textDocInline = Docs.Create.TextDocument("", { layoutKey: inlineLayoutKey, _width: 75, _height: 35, annotationOn: textDoc, _autoHeight: true, _fontSize: "9pt", title: "inline comment" });
                     textDocInline.title = inlineFieldKey; // give the annotation its own title
                     textDocInline.customTitle = true; // And make sure that it's 'custom' so that editing text doesn't change the title of the containing doc
                     textDocInline.isTemplateForField = inlineFieldKey; // this is needed in case the containing text doc is converted to a template at some point
diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts
index c1659d4d5..61a37ef8a 100644
--- a/src/fields/documentSchemas.ts
+++ b/src/fields/documentSchemas.ts
@@ -55,7 +55,7 @@ export const documentSchema = createSchema({
     _columnsHideIfEmpty: "boolean",   // whether empty stacking view column headings should be hidden
     _columnHeaders: listSpec(SchemaHeaderField), // header descriptions for stacking/masonry
     _schemaHeaders: listSpec(SchemaHeaderField), // header descriptions for schema views
-    _fontSize: "number",
+    _fontSize: "string",
     _fontFamily: "string",
     _sidebarWidthPercent: "string", // percent of text window width taken up by sidebar
 
-- 
cgit v1.2.3-70-g09d2


From db15b1d27a639af7a65f72dd5e4b6ea298412315 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 13 Jul 2020 11:46:35 -0400
Subject: fixed issues with ACLs and writing to playground fields when you have
 Edit permission.  Also fixed text editing by fixing fetchProto to use
 untracked references to fields

---
 src/client/util/SharingManager.tsx                        |  3 ++-
 src/client/views/nodes/formattedText/FormattedTextBox.tsx |  2 +-
 src/fields/Doc.ts                                         | 12 +++++-------
 src/fields/util.ts                                        |  8 +++++---
 4 files changed, 13 insertions(+), 12 deletions(-)

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

diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index af68edab6..050ff0c4e 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -146,6 +146,7 @@ export default class SharingManager extends React.Component<{}> {
         const ACL = `ACL-${StrCast(group.groupName)}`;
 
         target[ACL] = permission;
+        Doc.GetProto(target)[ACL] = permission;
 
         group.docsShared ? DocListCastAsync(group.docsShared).then(resolved => Doc.IndexOf(target, resolved!) === -1 && (group.docsShared as List<Doc>).push(target)) : group.docsShared = new List<Doc>([target]);
         // group.docsShared ? Doc.IndexOf(target, DocListCast(group.docsShared)) === -1 && (group.docsShared as List<Doc>).push(target) : group.docsShared = new List<Doc>([target]);
@@ -215,7 +216,7 @@ export default class SharingManager extends React.Component<{}> {
         // const permissions: { [key: string]: number } = target[ACL] ? JSON.parse(StrCast(target[ACL])) : {};
 
         target[ACL] = permission;
-
+        Doc.GetProto(target)[ACL] = permission;
 
         if (permission !== SharingPermissions.None) {
             console.log(target);
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 0af941182..01fbcb020 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -237,7 +237,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                     this._applyingChange = true;
                     (curText !== Cast(this.dataDoc[this.fieldKey], RichTextField)?.Text) && (this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now())));
                     if ((!curTemp && !curProto) || curText || curLayout?.Data.includes("dash")) { // if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended)
-                        if (json !== curLayout?.Data) {
+                        if (json.replace(/"selection":.*/, "") !== curLayout?.Data.replace(/"selection":.*/, "")) {
                             !curText && tx.storedMarks?.map(m => m.type.name === "pFontSize" && (Doc.UserDoc().fontSize = this.layoutDoc._fontSize = m.attrs.fontSize));
                             !curText && tx.storedMarks?.map(m => m.type.name === "pFontFamily" && (Doc.UserDoc().fontFamily = this.layoutDoc._fontFamily = m.attrs.fontFamily));
                             this.dataDoc[this.props.fieldKey] = new RichTextField(json, curText);
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index e8dca5fb6..8ab4735a7 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -1,4 +1,4 @@
-import { action, computed, observable, ObservableMap, runInAction } from "mobx";
+import { action, computed, observable, ObservableMap, runInAction, untracked } from "mobx";
 import { computedFn } from "mobx-utils";
 import { alias, map, serializable } from "serializr";
 import { DocServer } from "../client/DocServer";
@@ -110,15 +110,13 @@ const AclMap = new Map<string, symbol>([
 
 export function fetchProto(doc: Doc) {
     if (doc.author !== Doc.CurrentUserEmail) { // storing acls for groups needs to be extended here - AclSym should store a datastructure that stores information about permissions
+        untracked(() => {
+            const permissions: { [key: string]: symbol } = {};
 
-        const permissions: { [key: string]: symbol } = {};
+            Object.keys(doc).filter(key => key.startsWith("ACL")).forEach(key => permissions[key] = AclMap.get(StrCast(doc[key]))!);
 
-        Object.keys(doc).forEach(key => {
-            if (key.startsWith("ACL")) permissions[key] = AclMap.get(StrCast(doc[key]))!;
+            if (Object.keys(permissions).length) doc[AclSym] = permissions;
         });
-
-
-        if (Object.keys(permissions).length) doc[AclSym] = permissions;
     }
 
     if (doc.proto instanceof Promise) {
diff --git a/src/fields/util.ts b/src/fields/util.ts
index ebfc3933a..be7736413 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -70,8 +70,8 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
     const writeMode = DocServer.getFieldWriteMode(prop as string);
     const fromServer = target[UpdatingFromServer];
     const sameAuthor = fromServer || (receiver.author === Doc.CurrentUserEmail);
-    const writeToDoc = sameAuthor || (writeMode !== DocServer.WriteMode.LiveReadonly);
-    const writeToServer = (sameAuthor || (writeMode === DocServer.WriteMode.Default)) && !playgroundMode;
+    const writeToDoc = sameAuthor || GetEffectiveAcl(target) === AclEdit || (writeMode !== DocServer.WriteMode.LiveReadonly);
+    const writeToServer = (sameAuthor || GetEffectiveAcl(target) === AclEdit || writeMode === DocServer.WriteMode.Default) && !playgroundMode;
 
     if (writeToDoc) {
         if (value === undefined) {
@@ -91,8 +91,9 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
             redo: () => receiver[prop] = value,
             undo: () => receiver[prop] = curValue
         });
+        return true;
     }
-    return true;
+    return false;
 });
 
 let _setter: (target: any, prop: string | symbol | number, value: any, receiver: any) => boolean = _setterImpl;
@@ -126,6 +127,7 @@ export function setGroups(groups: string[]) {
 }
 
 export function GetEffectiveAcl(target: any, in_prop?: string | symbol | number): symbol {
+    if (in_prop === UpdatingFromServer || target[UpdatingFromServer]) return AclEdit;
 
     const HierarchyMapping = new Map<symbol, number>([
         [AclPrivate, 0],
-- 
cgit v1.2.3-70-g09d2


From 0e2a5196b47c05eab74d0aae06c2db4a9528f0fb Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 13 Jul 2020 20:44:03 -0400
Subject: switched context menus to be less hierarchical.

---
 src/client/util/CurrentUserUtils.ts                |  2 -
 src/client/util/SettingsManager.scss               |  2 +-
 src/client/util/SettingsManager.tsx                | 14 ++++++-
 src/client/views/ContextMenuItem.tsx               |  6 +++
 .../views/collections/CollectionCarousel3DView.tsx | 13 +-----
 .../views/collections/CollectionCarouselView.tsx   | 14 +------
 .../views/collections/CollectionTreeView.tsx       |  2 +-
 src/client/views/collections/CollectionView.tsx    |  6 +--
 .../collectionFreeForm/FormatShapePane.tsx         | 26 ++++++------
 .../collectionGrid/CollectionGridView.tsx          |  5 +--
 src/client/views/nodes/DocumentView.tsx            | 38 ++++++++---------
 src/client/views/nodes/ImageBox.tsx                | 48 +++++++++++-----------
 src/client/views/nodes/LabelBox.tsx                |  2 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx | 48 +++++++++++-----------
 14 files changed, 111 insertions(+), 115 deletions(-)

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

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 39781a5ce..048ab9edc 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -423,8 +423,6 @@ export class CurrentUserUtils {
             // { 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" })' },
-            { title: "Connect a Google Account", label: "Google Account", icon: "external-link-alt", click: 'GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken(true)' },
-            { title: "Connect a Hypothesis Account", label: "Hypothesis Account", icon: "heading", click: 'HypothesisAuthenticationManager.Instance.fetchOrGenerateAccessToken(true)' },
         ];
 
     }
diff --git a/src/client/util/SettingsManager.scss b/src/client/util/SettingsManager.scss
index 13c65042c..6d394a38d 100644
--- a/src/client/util/SettingsManager.scss
+++ b/src/client/util/SettingsManager.scss
@@ -56,7 +56,7 @@
         .settings-type {
             display: flex;
             flex-direction: column;
-            flex-basis: 30%;
+            flex-basis: 45%;
 
         }
 
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index 9888cce48..f7ca3942b 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -11,6 +11,8 @@ import { Networking } from "../Network";
 import { CurrentUserUtils } from "./CurrentUserUtils";
 import { Utils } from "../../Utils";
 import { Doc } from "../../fields/Doc";
+import HypothesisAuthenticationManager from "../apis/HypothesisAuthenticationManager";
+import GoogleAuthenticationManager from "../apis/GoogleAuthenticationManager";
 
 library.add(fa.faWindowClose);
 
@@ -83,6 +85,14 @@ export default class SettingsManager extends React.Component<{}> {
     noviceToggle = (event: any) => {
         Doc.UserDoc().noviceMode = !Doc.UserDoc().noviceMode;
     }
+    @action
+    googleAuthorize = (event: any) => {
+        GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken(true)
+    }
+    @action
+    hypothesisAuthorize = (event: any) => {
+        HypothesisAuthenticationManager.Instance.fetchAccessToken(true)
+    }
 
     private get settingsInterface() {
         return (
@@ -96,7 +106,9 @@ export default class SettingsManager extends React.Component<{}> {
                 <div className="settings-body">
                     <div className="settings-type">
                         <button onClick={this.onClick} value="password">reset password</button>
-                        <button onClick={this.noviceToggle} value="data">{`toggle ${Doc.UserDoc().noviceMode ? "developer" : "novice"} mode`}</button>
+                        <button onClick={this.noviceToggle} value="data">{`Set ${Doc.UserDoc().noviceMode ? "developer" : "novice"} mode`}</button>
+                        <button onClick={this.googleAuthorize} value="data">{`Link to Google`}</button>
+                        <button onClick={this.hypothesisAuthorize} value="data">{`Link to Hypothes.is`}</button>
                         <button onClick={() => window.location.assign(Utils.prepend("/logout"))}>
                             {CurrentUserUtils.GuestWorkspace ? "Exit" : "Log Out"}
                         </button>
diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx
index 99840047f..68ebd8e14 100644
--- a/src/client/views/ContextMenuItem.tsx
+++ b/src/client/views/ContextMenuItem.tsx
@@ -19,6 +19,7 @@ export interface OriginalMenuProps {
 export interface SubmenuProps {
     description: string;
     subitems: ContextMenuProps[];
+    noexpand?: boolean;
     icon: IconProp; //maybe should be optional (icon?)
     closeMenu?: () => void;
 }
@@ -96,6 +97,11 @@ export class ContextMenuItem extends React.Component<ContextMenuProps & { select
                 <div className="contextMenu-subMenu-cont" style={{ marginLeft: "25%", left: "0px", marginTop }}>
                     {this._items.map(prop => <ContextMenuItem {...prop} key={prop.description} closeMenu={this.props.closeMenu} />)}
                 </div>;
+            if (!("noexpand" in this.props)) {
+                return <>
+                    {this._items.map(prop => <ContextMenuItem {...prop} key={prop.description} closeMenu={this.props.closeMenu} />)}
+                </>;
+            }
             return (
                 <div className={"contextMenu-item" + (this.props.selected ? " contextMenu-itemSelected" : "")} style={{ alignItems: where }}
                     onMouseLeave={this.onPointerLeave} onMouseEnter={this.onPointerEnter}>
diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx
index 1344b70f4..8f1cd5311 100644
--- a/src/client/views/collections/CollectionCarousel3DView.tsx
+++ b/src/client/views/collections/CollectionCarousel3DView.tsx
@@ -108,17 +108,6 @@ export class CollectionCarousel3DView extends CollectionSubView(Carousel3DDocume
         }, 1500);
     }
 
-    onContextMenu = (e: React.MouseEvent): void => {
-        // need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout
-        if (!e.isPropagationStopped()) {
-            ContextMenu.Instance.addItem({
-                description: "Make Hero Image", event: () => {
-                    const index = NumCast(this.layoutDoc._itemIndex);
-                    (this.dataDoc || Doc.GetProto(this.props.Document)).hero = ObjectField.MakeCopy(this.childLayoutPairs[index].layout.data as ObjectField);
-                }, icon: "plus"
-            });
-        }
-    }
     _downX = 0;
     _downY = 0;
     onPointerDown = (e: React.PointerEvent) => {
@@ -184,7 +173,7 @@ export class CollectionCarousel3DView extends CollectionSubView(Carousel3DDocume
         const index = NumCast(this.layoutDoc._itemIndex);
         const translateX = (1 - index) / this.childLayoutPairs.length * 100;
 
-        return <div className="collectionCarousel3DView-outer" onClick={this.onClick} onPointerDown={this.onPointerDown} ref={this.createDashEventsTarget} onContextMenu={this.onContextMenu}>
+        return <div className="collectionCarousel3DView-outer" onClick={this.onClick} onPointerDown={this.onPointerDown} ref={this.createDashEventsTarget}>
             <div className="carousel-wrapper" style={{ transform: `translateX(calc(${translateX}%` }}>
                 {this.content}
             </div>
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index bd0e4fc9a..b3fecfb91 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -83,18 +83,6 @@ export class CollectionCarouselView extends CollectionSubView(CarouselDocument)
         </>;
     }
 
-
-    onContextMenu = (e: React.MouseEvent): void => {
-        // need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout
-        if (!e.isPropagationStopped()) {
-            ContextMenu.Instance?.addItem({
-                description: "Make Hero Image", event: () => {
-                    const index = NumCast(this.layoutDoc._itemIndex);
-                    (this.dataDoc || Doc.GetProto(this.props.Document)).hero = ObjectField.MakeCopy(this.childLayoutPairs[index].layout.data as ObjectField);
-                }, icon: "plus"
-            });
-        }
-    }
     _downX = 0;
     _downY = 0;
     onPointerDown = (e: React.PointerEvent) => {
@@ -119,7 +107,7 @@ export class CollectionCarouselView extends CollectionSubView(CarouselDocument)
     }
 
     render() {
-        return <div className="collectionCarouselView-outer" onClick={this.onClick} onPointerDown={this.onPointerDown} ref={this.createDashEventsTarget} onContextMenu={this.onContextMenu}>
+        return <div className="collectionCarouselView-outer" onClick={this.onClick} onPointerDown={this.onPointerDown} ref={this.createDashEventsTarget}>
             {this.content}
             {this.props.Document._chromeStatus !== "replaced" ? this.buttons : (null)}
         </div>;
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 26c41f524..dbd39d8df 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -780,7 +780,7 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
         onClicks.push({
             description: "Edit onChecked Script", event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.doc, undefined, "onCheckedClick"), "edit onCheckedClick"), icon: "edit"
         });
-        !existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
+        !existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", noexpand: true, subitems: onClicks, icon: "hand-point-right" });
     }
     outerXf = () => Utils.GetScreenTransform(this._mainEle!);
     onTreeDrop = (e: React.DragEvent) => this.onExternalDrop(e, {});
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index df21d6a28..cbd1ac9af 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -272,7 +272,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
             subItems.push({ description: "Custom", icon: "fingerprint", event: AddCustomFreeFormLayout(this.props.Document, this.props.fieldKey) });
         }
         addExtras && subItems.push({ description: "lightbox", event: action(() => this._isLightboxOpen = true), icon: "eye" });
-        !existingVm && ContextMenu.Instance.addItem({ description: category, subitems: subItems, icon: "eye" });
+        !existingVm && ContextMenu.Instance.addItem({ description: category, noexpand: true, subitems: subItems, icon: "eye" });
     }
 
     onContextMenu = (e: React.MouseEvent): void => {
@@ -294,7 +294,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
             if (this.props.Document.childClickedOpenTemplateView instanceof Doc) {
                 layoutItems.push({ description: "View Child Detailed Layout", event: () => this.props.addDocTab(this.props.Document.childClickedOpenTemplateView as Doc, "onRight"), icon: "project-diagram" });
             }
-            layoutItems.push({ description: `${this.props.Document.isInPlaceContainer ? "Unset" : "Set"} inPlace Container`, event: () => this.props.Document.isInPlaceContainer = !this.props.Document.isInPlaceContainer, icon: "project-diagram" });
+            !Doc.UserDoc().noviceMode && layoutItems.push({ description: `${this.props.Document.isInPlaceContainer ? "Unset" : "Set"} inPlace Container`, event: () => this.props.Document.isInPlaceContainer = !this.props.Document.isInPlaceContainer, icon: "project-diagram" });
 
             !existing && cm.addItem({ description: "Options...", subitems: layoutItems, icon: "hand-point-right" });
 
@@ -314,7 +314,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
                     icon: "edit",
                     event: () => this.props.Document[StrCast(childClick.targetScriptKey)] = ObjectField.MakeCopy(ScriptCast(childClick.data)),
                 }));
-            !existingOnClick && cm.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
+            !existingOnClick && cm.addItem({ description: "OnClick...", noexpand: true, subitems: onClicks, icon: "hand-point-right" });
 
             if (!Doc.UserDoc().noviceMode) {
                 const more = cm.findByDescription("More...");
diff --git a/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx b/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx
index 56ba9e96a..9d9ce7f36 100644
--- a/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx
+++ b/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx
@@ -11,6 +11,7 @@ import { DocumentType } from "../../../documents/DocumentTypes";
 import { SelectionManager } from "../../../util/SelectionManager";
 import AntimodeMenu from "../../AntimodeMenu";
 import "./FormatShapePane.scss";
+import { undoBatch } from "../../../util/UndoManager";
 
 @observer
 export default class FormatShapePane extends AntimodeMenu {
@@ -118,6 +119,7 @@ export default class FormatShapePane extends AntimodeMenu {
         }
     }
 
+    @undoBatch
     @action
     rotate = (degrees: number) => {
         this.selectedInk?.forEach(action(inkView => {
@@ -160,7 +162,7 @@ export default class FormatShapePane extends AntimodeMenu {
     colorPicker(setter: (color: string) => {}) {
         return <div className="btn-group-palette" key="colorpicker" >
             {this._palette.map(color =>
-                <button className="antimodeMenu-button" key={color} onPointerDown={action(() => setter(color))} style={{ zIndex: 1001, position: "relative" }}>
+                <button className="antimodeMenu-button" key={color} onPointerDown={undoBatch(action(() => setter(color)))} style={{ zIndex: 1001, position: "relative" }}>
                     <div className="color-previewII" style={{ backgroundColor: color }} />
                 </button>)}
         </div>;
@@ -171,11 +173,11 @@ export default class FormatShapePane extends AntimodeMenu {
                 type="text" value={value}
                 onChange={e => setter(e.target.value)}
                 autoFocus />
-            <button className="antiMenu-Buttonup" key="up" onPointerDown={action(() => this.upDownButtons("up", key))}>
+            <button className="antiMenu-Buttonup" key="up" onPointerDown={undoBatch(action(() => this.upDownButtons("up", key)))}>
                 ˄
             </button>
             <br />
-            <button className="antiMenu-Buttonup" key="down" onPointerDown={action(() => this.upDownButtons("down", key))} style={{ marginTop: -8 }}>
+            <button className="antiMenu-Buttonup" key="down" onPointerDown={undoBatch(action(() => this.upDownButtons("down", key)))} style={{ marginTop: -8 }}>
                 ˅
             </button>
         </>;
@@ -183,7 +185,7 @@ export default class FormatShapePane extends AntimodeMenu {
 
     colorButton(value: string, setter: () => {}) {
         return <>
-            <button className="antimodeMenu-button" key="fill" onPointerDown={action(e => setter())} style={{ position: "absolute", right: 80 }}>
+            <button className="antimodeMenu-button" key="fill" onPointerDown={undoBatch(action(e => setter()))} style={{ position: "absolute", right: 80 }}>
                 <FontAwesomeIcon icon="fill-drip" size="lg" />
                 <div className="color-previewI" style={{ backgroundColor: value ?? "121212" }} />
             </button>
@@ -206,10 +208,10 @@ export default class FormatShapePane extends AntimodeMenu {
 
     @computed get propertyGroupItems() {
         const fillCheck = <div key="fill" style={{ display: this._subOpen[0] ? "" : "none", width: "inherit", backgroundColor: "#323232", color: "white", }}>
-            <input className="formatShapePane-inputBtn" type="radio" checked={this.unFilled} onChange={action(() => this.unFilled = true)} />
+            <input className="formatShapePane-inputBtn" type="radio" checked={this.unFilled} onChange={undoBatch(action(() => this.unFilled = true))} />
                 No Fill
             <br />
-            <input className="formatShapePane-inputBtn" type="radio" checked={this.solidFil} onChange={action(() => this.solidFil = true)} />
+            <input className="formatShapePane-inputBtn" type="radio" checked={this.solidFil} onChange={undoBatch(action(() => this.solidFil = true))} />
                 Solid Fill
             <br /> <br />
             {this.solidFil ? "Color" : ""}
@@ -218,22 +220,22 @@ export default class FormatShapePane extends AntimodeMenu {
         </div>;
 
         const markers = <>
-            <input key="markHead" className="formatShapePane-inputBtn" type="checkbox" checked={this.markHead !== ""} onChange={action(() => this.markHead = this.markHead ? "" : "arrow")} />
+            <input key="markHead" className="formatShapePane-inputBtn" type="checkbox" checked={this.markHead !== ""} onChange={undoBatch(action(() => this.markHead = this.markHead ? "" : "arrow"))} />
                 Arrow Head
             <br />
-            <input key="markTail" className="formatShapePane-inputBtn" type="checkbox" checked={this.markTail !== ""} onChange={action(() => this.markTail = this.markTail ? "" : "arrow")} />
+            <input key="markTail" className="formatShapePane-inputBtn" type="checkbox" checked={this.markTail !== ""} onChange={undoBatch(action(() => this.markTail = this.markTail ? "" : "arrow"))} />
                 Arrow End
             <br />
         </>;
 
         const lineCheck = <div key="lineCheck" style={{ display: this._subOpen[1] ? "" : "none", width: "inherit", backgroundColor: "#323232", color: "white", }}>
-            <input className="formatShapePane-inputBtn" type="radio" checked={this.unStrokd} onChange={action(() => this.unStrokd = true)} />
+            <input className="formatShapePane-inputBtn" type="radio" checked={this.unStrokd} onChange={undoBatch(action(() => this.unStrokd = true))} />
                 No Line
             <br />
-            <input className="formatShapePane-inputBtn" type="radio" checked={this.solidStk} onChange={action(() => this.solidStk = true)} />
+            <input className="formatShapePane-inputBtn" type="radio" checked={this.solidStk} onChange={undoBatch(action(() => this.solidStk = true))} />
                 Solid Line
             <br />
-            <input className="formatShapePane-inputBtn" type="radio" checked={this.dashdStk ? true : false} onChange={action(() => this.dashdStk = "2")} />
+            <input className="formatShapePane-inputBtn" type="radio" checked={this.dashdStk ? true : false} onChange={undoBatch(action(() => this.dashdStk = "2"))} />
                 Dash Line
             <br />
             <br />
@@ -253,7 +255,7 @@ export default class FormatShapePane extends AntimodeMenu {
             <br /> <br />
                 Width {this.widInput}
             <br /> <br />
-            <input className="formatShapePane-inputBtn" style={{ right: 0 }} type="checkbox" checked={this._lock} onChange={action(() => this._lock = !this._lock)} />
+            <input className="formatShapePane-inputBtn" style={{ right: 0 }} type="checkbox" checked={this._lock} onChange={undoBatch(action(() => this._lock = !this._lock))} />
                 Lock Ratio
             <br />  <br />
                 Rotation {this.rotInput}
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
index 2015ca930..188b88c41 100644
--- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
@@ -1,7 +1,7 @@
 import { action, computed, Lambda, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from "react";
-import { Doc, Opt } from '../../../../fields/Doc';
+import { Doc, Opt, WidthSym } from '../../../../fields/Doc';
 import { documentSchema } from '../../../../fields/documentSchemas';
 import { Id } from '../../../../fields/FieldSymbols';
 import { makeInterface } from '../../../../fields/Schema';
@@ -248,8 +248,7 @@ export class CollectionGridView extends CollectionSubView(GridSchema) {
      */
     onContextMenu = () => {
         const displayOptionsMenu: ContextMenuProps[] = [];
-        displayOptionsMenu.push({ description: "Contents", event: () => this.props.Document.display = "contents", icon: "copy" });
-        displayOptionsMenu.push({ description: "Undefined", event: () => this.props.Document.display = undefined, icon: "exclamation" });
+        displayOptionsMenu.push({ description: "Toggle Content Display Style", event: () => this.props.Document.display = this.props.Document.display ? undefined : "contents", icon: "copy" });
         ContextMenu.Instance.addItem({ description: "Display", subitems: displayOptionsMenu, icon: "tv" });
     }
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 6ef976ceb..a586e81d8 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -770,6 +770,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         const options = cm.findByDescription("Options...");
         const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : [];
         const templateDoc = Cast(this.props.Document[StrCast(this.props.Document.layoutKey)], Doc, null);
+        optionItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" });
         templateDoc && optionItems.push({ description: "Open Template   ", event: () => this.props.addDocTab(templateDoc, "onRight"), icon: "eye" });
         !options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "compass" });
 
@@ -783,14 +784,14 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         onClicks.push({ description: this.Document.isLinkButton ? "Remove Follow Behavior" : "Follow Link on Right", event: this.toggleFollowOnRight, icon: "concierge-bell" });
         onClicks.push({ description: this.Document.isLinkButton || this.onClickHandler ? "Remove Click Behavior" : "Follow Link", event: this.toggleLinkButtonBehavior, icon: "concierge-bell" });
         onClicks.push({ description: "Edit onClick Script", event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.props.Document, undefined, "onClick"), "edit onClick"), icon: "edit" });
-        !existingOnClick && cm.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
+        !existingOnClick && cm.addItem({ description: "OnClick...", noexpand: true, subitems: onClicks, icon: "hand-point-right" });
 
         const funcs: ContextMenuProps[] = [];
         if (this.layoutDoc.onDragStart) {
             funcs.push({ description: "Drag an Alias", icon: "edit", event: () => this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getAlias(this.dragFactory)')) });
             funcs.push({ description: "Drag a Copy", icon: "edit", event: () => this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)')) });
             funcs.push({ description: "Drag Document", icon: "edit", event: () => this.layoutDoc.onDragStart = undefined });
-            cm.addItem({ description: "OnDrag...", subitems: funcs, icon: "asterisk" });
+            cm.addItem({ description: "OnDrag...", noexpand: true, subitems: funcs, icon: "asterisk" });
         }
 
         const more = cm.findByDescription("More...");
@@ -817,29 +818,28 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 // 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" });
         }
-        moreItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" });
-        moreItems.push({ description: "Copy ID", event: () => Utils.CopyText(Utils.prepend("/doc/" + this.props.Document[Id])), icon: "fingerprint" });
         moreItems.push({ description: "Delete", event: this.deleteClicked, icon: "trash" });
         moreItems.push({ description: "Share", event: () => SharingManager.Instance.open(this), icon: "external-link-alt" });
         !more && cm.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" });
         cm.moveAfter(cm.findByDescription("More...")!, cm.findByDescription("OnClick...")!);
 
-        const help = cm.findByDescription("Help...");
-        const helpItems: ContextMenuProps[] = help && "subitems" in help ? help.subitems : [];
-        helpItems.push({ description: "Text Shortcuts Ctrl+/", event: () => this.props.addDocTab(Docs.Create.PdfDocument(Utils.prepend("/assets/cheat-sheet.pdf"), { _width: 300, _height: 300 }), "onRight"), icon: "keyboard" });
-        helpItems.push({ description: "Show Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "onRight"), icon: "layer-group" });
-        cm.addItem({ description: "Help...", subitems: helpItems, icon: "question" });
-
-        const existingAcls = cm.findByDescription("Privacy...");
-        const aclItems: ContextMenuProps[] = existingAcls && "subitems" in existingAcls ? existingAcls.subitems : [];
-        aclItems.push({ description: "Make Add Only", event: () => this.setAcl("addOnly"), icon: "concierge-bell" });
-        aclItems.push({ description: "Make Read Only", event: () => this.setAcl("readOnly"), icon: "concierge-bell" });
-        aclItems.push({ description: "Make Private", event: () => this.setAcl("ownerOnly"), icon: "concierge-bell" });
-        aclItems.push({ description: "Make Editable", event: () => this.setAcl("write"), icon: "concierge-bell" });
-        aclItems.push({ description: "Test Private", event: () => this.testAcl("ownerOnly"), icon: "concierge-bell" });
-        aclItems.push({ description: "Test Readonly", event: () => this.testAcl("readOnly"), icon: "concierge-bell" });
-        !existingAcls && cm.addItem({ description: "Privacy...", subitems: aclItems, icon: "question" });
+        // const help = cm.findByDescription("Help...");
+        // const helpItems: ContextMenuProps[] = help && "subitems" in help ? help.subitems : [];
+        // helpItems.push({ description: "Text Shortcuts Ctrl+/", event: () => this.props.addDocTab(Docs.Create.PdfDocument(Utils.prepend("/assets/cheat-sheet.pdf"), { _width: 300, _height: 300 }), "onRight"), icon: "keyboard" });
+        // helpItems.push({ description: "Show Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "onRight"), icon: "layer-group" });
+        // cm.addItem({ description: "Help...", subitems: helpItems, icon: "question" });
+
+        // const existingAcls = cm.findByDescription("Privacy...");
+        // const aclItems: ContextMenuProps[] = existingAcls && "subitems" in existingAcls ? existingAcls.subitems : [];
+        // aclItems.push({ description: "Make Add Only", event: () => this.setAcl("addOnly"), icon: "concierge-bell" });
+        // aclItems.push({ description: "Make Read Only", event: () => this.setAcl("readOnly"), icon: "concierge-bell" });
+        // aclItems.push({ description: "Make Private", event: () => this.setAcl("ownerOnly"), icon: "concierge-bell" });
+        // aclItems.push({ description: "Make Editable", event: () => this.setAcl("write"), icon: "concierge-bell" });
+        // aclItems.push({ description: "Test Private", event: () => this.testAcl("ownerOnly"), icon: "concierge-bell" });
+        // aclItems.push({ description: "Test Readonly", event: () => this.testAcl("readOnly"), icon: "concierge-bell" });
+        // !existingAcls && cm.addItem({ description: "Privacy...", subitems: aclItems, icon: "question" });
 
         // const recommender_subitems: ContextMenuProps[] = [];
 
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index d16aa528c..4eba21eab 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -157,29 +157,31 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
         const field = Cast(this.dataDoc[this.fieldKey], ImageField);
         if (field) {
             const funcs: ContextMenuProps[] = [];
-            funcs.push({ description: "Rotate", event: this.rotate, icon: "expand-arrows-alt" });
-            funcs.push({ description: "Export to Google Photos", event: () => GooglePhotos.Transactions.UploadImages([this.props.Document]), icon: "caret-square-right" });
-            funcs.push({ description: "Copy path", event: () => Utils.CopyText(field.url.href), icon: "expand-arrows-alt" });
-            // funcs.push({
-            //     description: "Reset Native Dimensions", event: action(async () => {
-            //         const curNW = NumCast(this.dataDoc[this.fieldKey + "-nativeWidth"]);
-            //         const curNH = NumCast(this.dataDoc[this.fieldKey + "-nativeHeight"]);
-            //         if (this.props.PanelWidth() / this.props.PanelHeight() > curNW / curNH) {
-            //             this.dataDoc[this.fieldKey + "-nativeWidth"] = this.props.PanelHeight() * curNW / curNH;
-            //             this.dataDoc[this.fieldKey + "-nativeHeight"] = this.props.PanelHeight();
-            //         } else {
-            //             this.dataDoc[this.fieldKey + "-nativeWidth"] = this.props.PanelWidth();
-            //             this.dataDoc[this.fieldKey + "-nativeHeight"] = this.props.PanelWidth() * curNH / curNW;
-            //         }
-            //     }), icon: "expand-arrows-alt"
-            // });
-
-            const existingAnalyze = ContextMenu.Instance?.findByDescription("Analyzers...");
-            const modes: ContextMenuProps[] = existingAnalyze && "subitems" in existingAnalyze ? existingAnalyze.subitems : [];
-            modes.push({ description: "Generate Tags", event: this.generateMetadata, icon: "tag" });
-            modes.push({ description: "Find Faces", event: this.extractFaces, icon: "camera" });
-            //modes.push({ description: "Recommend", event: this.extractText, icon: "brain" });
-            !existingAnalyze && ContextMenu.Instance?.addItem({ description: "Analyzers...", subitems: modes, icon: "hand-point-right" });
+            funcs.push({ description: "Rotate Clockwise 90", event: this.rotate, icon: "expand-arrows-alt" });
+            if (!Doc.UserDoc().noviceMode) {
+                funcs.push({ description: "Export to Google Photos", event: () => GooglePhotos.Transactions.UploadImages([this.props.Document]), icon: "caret-square-right" });
+                funcs.push({ description: "Copy path", event: () => Utils.CopyText(field.url.href), icon: "expand-arrows-alt" });
+                // funcs.push({
+                //     description: "Reset Native Dimensions", event: action(async () => {
+                //         const curNW = NumCast(this.dataDoc[this.fieldKey + "-nativeWidth"]);
+                //         const curNH = NumCast(this.dataDoc[this.fieldKey + "-nativeHeight"]);
+                //         if (this.props.PanelWidth() / this.props.PanelHeight() > curNW / curNH) {
+                //             this.dataDoc[this.fieldKey + "-nativeWidth"] = this.props.PanelHeight() * curNW / curNH;
+                //             this.dataDoc[this.fieldKey + "-nativeHeight"] = this.props.PanelHeight();
+                //         } else {
+                //             this.dataDoc[this.fieldKey + "-nativeWidth"] = this.props.PanelWidth();
+                //             this.dataDoc[this.fieldKey + "-nativeHeight"] = this.props.PanelWidth() * curNH / curNW;
+                //         }
+                //     }), icon: "expand-arrows-alt"
+                // });
+
+                const existingAnalyze = ContextMenu.Instance?.findByDescription("Analyzers...");
+                const modes: ContextMenuProps[] = existingAnalyze && "subitems" in existingAnalyze ? existingAnalyze.subitems : [];
+                modes.push({ description: "Generate Tags", event: this.generateMetadata, icon: "tag" });
+                modes.push({ description: "Find Faces", event: this.extractFaces, icon: "camera" });
+                //modes.push({ description: "Recommend", event: this.extractText, icon: "brain" });
+                !existingAnalyze && ContextMenu.Instance?.addItem({ description: "Analyzers...", subitems: modes, icon: "hand-point-right" });
+            }
 
             ContextMenu.Instance?.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
         }
diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index 836ef4149..0dfbdc5cf 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -41,7 +41,7 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps, LabelDocument
             }, icon: "trash"
         });
 
-        ContextMenu.Instance.addItem({ description: "OnClick...", subitems: funcs, icon: "asterisk" });
+        ContextMenu.Instance.addItem({ description: "OnClick...", noexpand: true, subitems: funcs, icon: "asterisk" });
     }
 
     @undoBatch
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 01ac765b9..f87b28c7f 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -479,7 +479,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             });
         });
         changeItems.push({ description: "FreeForm", event: () => DocUtils.makeCustomViewClicked(this.rootDoc, Docs.Create.FreeformDocument, "freeform"), icon: "eye" });
-        appearanceItems.push({ description: "Change Perspective...", subitems: changeItems, icon: "external-link-alt" });
+        appearanceItems.push({ description: "Change Perspective...", noexpand: true, subitems: changeItems, icon: "external-link-alt" });
         const uicontrols: ContextMenuProps[] = [];
         uicontrols.push({ description: "Toggle Sidebar", event: () => this.layoutDoc._showSidebar = !this.layoutDoc._showSidebar, icon: "expand-arrows-alt" });
         uicontrols.push({ description: "Toggle Dictation Icon", event: () => this.layoutDoc._showAudio = !this.layoutDoc._showAudio, icon: "expand-arrows-alt" });
@@ -489,10 +489,29 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                 proto instanceof Doc && (proto.BROADCAST_MESSAGE = Cast(this.rootDoc[this.fieldKey], RichTextField)?.Text)), icon: "expand-arrows-alt"
         });
 
-        appearanceItems.push({ description: "UI Controls...", subitems: uicontrols, icon: "asterisk" });
+        appearanceItems.push({ description: "UI Controls...", noexpand: true, subitems: uicontrols, icon: "asterisk" });
         this.rootDoc.isTemplateDoc && appearanceItems.push({ description: "Make Default Layout", event: async () => Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.rootDoc), icon: "eye" });
         Doc.UserDoc().defaultTextLayout && appearanceItems.push({ description: "Reset default note style", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" });
-        appearanceItems.push({
+
+        !appearance && ContextMenu.Instance.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "eye" });
+
+        const funcs: ContextMenuProps[] = [];
+
+        const highlighting: ContextMenuProps[] = [];
+        ["My Text", "Text from Others", "Todo Items", "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"].forEach(option =>
+            highlighting.push({
+                description: (FormattedTextBox._highlights.indexOf(option) === -1 ? "Highlight " : "Unhighlight ") + option, event: () => {
+                    e.stopPropagation();
+                    if (FormattedTextBox._highlights.indexOf(option) === -1) {
+                        FormattedTextBox._highlights.push(option);
+                    } else {
+                        FormattedTextBox._highlights.splice(FormattedTextBox._highlights.indexOf(option), 1);
+                    }
+                    this.updateHighlights();
+                }, icon: "expand-arrows-alt"
+            }));
+        funcs.push({ description: "highlighting...", noexpand: true, subitems: highlighting, icon: "hand-point-right" });
+        funcs.push({
             description: "Convert to be a template style", event: () => {
                 if (!this.layoutDoc.isTemplateDoc) {
                     const title = StrCast(this.rootDoc.title);
@@ -517,29 +536,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                 Doc.AddDocToList(Cast(Doc.UserDoc()["template-notes"], Doc, null), "data", this.rootDoc);
             }, icon: "eye"
         });
-        !appearance && ContextMenu.Instance.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "eye" });
+        funcs.push({ description: `${this.Document._autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" });
 
-        const funcs: ContextMenuProps[] = [];
-
-        //funcs.push({ description: `${this.Document._autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" });
-        funcs.push({ description: (!this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" });
         funcs.push({ description: "Toggle Single Line", event: () => this.layoutDoc._singleLine = !this.layoutDoc._singleLine, icon: "expand-arrows-alt" });
-
-        const highlighting: ContextMenuProps[] = [];
-        ["My Text", "Text from Others", "Todo Items", "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"].forEach(option =>
-            highlighting.push({
-                description: (FormattedTextBox._highlights.indexOf(option) === -1 ? "Highlight " : "Unhighlight ") + option, event: () => {
-                    e.stopPropagation();
-                    if (FormattedTextBox._highlights.indexOf(option) === -1) {
-                        FormattedTextBox._highlights.push(option);
-                    } else {
-                        FormattedTextBox._highlights.splice(FormattedTextBox._highlights.indexOf(option), 1);
-                    }
-                    this.updateHighlights();
-                }, icon: "expand-arrows-alt"
-            }));
-        funcs.push({ description: "highlighting...", subitems: highlighting, icon: "hand-point-right" });
-
+        funcs.push({ description: (!this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" });
         ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
         this._downX = this._downY = Number.NaN;
     }
-- 
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/FormattedTextBox.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 49e1dccee78af835ef48723bb708f1c2b47c9228 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 22 Jul 2020 21:09:20 -0400
Subject: fixed highlighting of text based on author.  made download /import
 document novice menu items.

---
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 69 +++++++++++-----------
 src/client/views/nodes/DocumentView.tsx            | 20 +++----
 .../views/nodes/formattedText/FormattedTextBox.tsx | 15 ++---
 src/client/views/nodes/formattedText/marks_rts.ts  | 10 ++--
 4 files changed, 58 insertions(+), 56 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index bee9e7009..dc32ecb07 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1258,41 +1258,44 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         if (!Doc.UserDoc().noviceMode) {
             optionItems.push({ description: (!this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" });
             optionItems.push({ description: `${this.Document._freeformLOD ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document._freeformLOD = !this.Document._freeformLOD, icon: "table" });
-            optionItems.push({
-                description: "Import document", icon: "upload", event: ({ x, y }) => {
-                    const input = document.createElement("input");
-                    input.type = "file";
-                    input.accept = ".zip";
-                    input.onchange = async _e => {
-                        const upload = Utils.prepend("/uploadDoc");
-                        const formData = new FormData();
-                        const file = input.files && input.files[0];
-                        if (file) {
-                            formData.append('file', file);
-                            formData.append('remap', "true");
-                            const response = await fetch(upload, { method: "POST", body: formData });
-                            const json = await response.json();
-                            if (json !== "error") {
-                                const doc = await DocServer.GetRefField(json);
-                                if (doc instanceof Doc) {
-                                    const [xx, yy] = this.props.ScreenToLocalTransform().transformPoint(x, y);
-                                    doc.x = xx, doc.y = yy;
-                                    this.props.addDocument?.(doc);
-                                    setTimeout(() => {
-                                        SearchUtil.Search(`{!join from=id to=proto_i}id:link*`, true, {}).then(docs => {
-                                            docs.docs.forEach(d => LinkManager.Instance.addLink(d));
-                                        })
-                                    }, 2000); // need to give solr some time to update so that this query will find any link docs we've added.
-                                }
-                            }
-                        }
-                    };
-                    input.click();
-                }
-            });
+
         }
         !options && ContextMenu.Instance.addItem({ description: "Options...", subitems: optionItems, icon: "eye" });
-
+        const mores = ContextMenu.Instance.findByDescription("More...");
+        const moreItems = mores && "subitems" in mores ? mores.subitems : [];
+        moreItems.push({
+            description: "Import document", icon: "upload", event: ({ x, y }) => {
+                const input = document.createElement("input");
+                input.type = "file";
+                input.accept = ".zip";
+                input.onchange = async _e => {
+                    const upload = Utils.prepend("/uploadDoc");
+                    const formData = new FormData();
+                    const file = input.files && input.files[0];
+                    if (file) {
+                        formData.append('file', file);
+                        formData.append('remap', "true");
+                        const response = await fetch(upload, { method: "POST", body: formData });
+                        const json = await response.json();
+                        if (json !== "error") {
+                            const doc = await DocServer.GetRefField(json);
+                            if (doc instanceof Doc) {
+                                const [xx, yy] = this.props.ScreenToLocalTransform().transformPoint(x, y);
+                                doc.x = xx, doc.y = yy;
+                                this.props.addDocument?.(doc);
+                                setTimeout(() => {
+                                    SearchUtil.Search(`{!join from=id to=proto_i}id:link*`, true, {}).then(docs => {
+                                        docs.docs.forEach(d => LinkManager.Instance.addLink(d));
+                                    })
+                                }, 2000); // need to give solr some time to update so that this query will find any link docs we've added.
+                            }
+                        }
+                    }
+                };
+                input.click();
+            }
+        });
+        !mores && ContextMenu.Instance.addItem({ description: "More...", subitems: moreItems, icon: "eye" });
     }
     @observable showTimeline = false;
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 3b46b70ea..748938699 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -782,6 +782,16 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
         const more = cm.findByDescription("More...");
         const moreItems = more && "subitems" in more ? more.subitems : [];
+        moreItems.push({
+            description: "Download document", icon: "download", event: async () => {
+                Doc.Zip(this.props.Document);
+                // 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();
+            }
+        });
         if (!Doc.UserDoc().noviceMode) {
             moreItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc), icon: "concierge-bell" });
             moreItems.push({ description: `${this.Document._chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.Document._chromeStatus = (this.Document._chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" });
@@ -791,16 +801,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 moreItems.push({ description: "Tag Child Images via Google Photos", event: () => GooglePhotos.Query.TagChildImages(this.props.Document), icon: "caret-square-right" });
                 moreItems.push({ description: "Write Back Link to Album", event: () => GooglePhotos.Transactions.AddTextEnrichment(this.props.Document), icon: "caret-square-right" });
             }
-            moreItems.push({
-                description: "Download document", icon: "download", event: async () => {
-                    Doc.Zip(this.props.Document);
-                    // 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" });
         }
         GetEffectiveAcl(this.props.Document) === AclEdit && moreItems.push({ description: "Delete", event: this.deleteClicked, icon: "trash" });
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 7906e2533..e703a81e2 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -425,16 +425,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { background: "moccasin" });
         }
         if (FormattedTextBox._highlights.indexOf("Todo Items") !== -1) {
-            addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "todo", { outline: "black solid 1px" });
+            addStyleSheetRule(FormattedTextBox._userStyleSheet, "UT-" + "todo", { outline: "black solid 1px" });
         }
         if (FormattedTextBox._highlights.indexOf("Important Items") !== -1) {
-            addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "important", { "font-size": "larger" });
+            addStyleSheetRule(FormattedTextBox._userStyleSheet, "UT-" + "important", { "font-size": "larger" });
         }
         if (FormattedTextBox._highlights.indexOf("Disagree Items") !== -1) {
-            addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "disagree", { "text-decoration": "line-through" });
+            addStyleSheetRule(FormattedTextBox._userStyleSheet, "UT-" + "disagree", { "text-decoration": "line-through" });
         }
         if (FormattedTextBox._highlights.indexOf("Ignore Items") !== -1) {
-            addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "ignore", { "font-size": "1" });
+            addStyleSheetRule(FormattedTextBox._userStyleSheet, "UT-" + "ignore", { "font-size": "1" });
         }
         if (FormattedTextBox._highlights.indexOf("By Recent Minute") !== -1) {
             addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" });
@@ -1303,9 +1303,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         if (e.key === "Tab" || e.key === "Enter") {
             e.preventDefault();
         }
-        const mark = e.key !== " " && this._lastTimedMark ? this._lastTimedMark : schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) });
-        this._lastTimedMark = mark;
-        // this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(mark));
+        if (e.key === " " || this._lastTimedMark?.attrs.userid !== Doc.CurrentUserEmail) {
+            const mark = schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) });
+            this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(mark));
+        }
 
         if (!this._undoTyping) {
             this.startUndoTypingBatch();
diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts
index 3d7d71b14..f95f46104 100644
--- a/src/client/views/nodes/formattedText/marks_rts.ts
+++ b/src/client/views/nodes/formattedText/marks_rts.ts
@@ -258,9 +258,7 @@ export const marks: { [index: string]: MarkSpec } = {
         },
         parseDOM: [{ style: 'background: yellow' }],
         toDOM(node: any) {
-            return ['span', {
-                style: `background: ${node.attrs.selected ? "orange" : "yellow"}`
-            }];
+            return ['span', { style: `background: ${node.attrs.selected ? "orange" : "yellow"}` }];
         }
     },
 
@@ -277,8 +275,8 @@ export const marks: { [index: string]: MarkSpec } = {
             const min = Math.round(node.attrs.modified / 12);
             const hr = Math.round(min / 60);
             const day = Math.round(hr / 60 / 24);
-            const remote = node.attrs.userid !== Doc.CurrentUserEmail ? " userMark-remote" : "";
-            return ['span', { class: "userMark-" + uid + remote + " userMark-min-" + min + " userMark-hr-" + hr + " userMark-day-" + day }, 0];
+            const remote = node.attrs.userid !== Doc.CurrentUserEmail ? " UM-remote" : "";
+            return ['span', { class: "UM-" + uid + remote + " UM-min-" + min + " UM-hr-" + hr + " UM-day-" + day }, 0];
         }
     },
     // the id of the user who entered the text
@@ -292,7 +290,7 @@ export const marks: { [index: string]: MarkSpec } = {
         inclusive: false,
         toDOM(node: any) {
             const uid = node.attrs.userid.replace(".", "").replace("@", "");
-            return ['span', { class: "userTag-" + uid + " userTag-" + node.attrs.tag }, 0];
+            return ['span', { class: "UT-" + uid + " UT-" + node.attrs.tag }, 0];
         }
     },
 
-- 
cgit v1.2.3-70-g09d2


From 00e853027010db1ce9ccf0ed041ecb74c6529b53 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 23 Jul 2020 09:44:59 -0400
Subject: cleaned up contextMenus a bit

---
 src/client/views/ContextMenu.scss                  | 16 ++---
 src/client/views/ContextMenuItem.tsx               |  4 +-
 src/client/views/InkingStroke.tsx                  |  7 +-
 src/client/views/collections/CollectionView.tsx    | 14 ++--
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 82 ++++++++++++----------
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  7 +-
 src/client/views/nodes/DocumentView.tsx            |  8 ++-
 .../views/nodes/formattedText/FormattedTextBox.tsx | 59 +++++++---------
 8 files changed, 100 insertions(+), 97 deletions(-)

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

diff --git a/src/client/views/ContextMenu.scss b/src/client/views/ContextMenu.scss
index 1bf242d93..86e0a568a 100644
--- a/src/client/views/ContextMenu.scss
+++ b/src/client/views/ContextMenu.scss
@@ -42,7 +42,7 @@
 
 .contextMenu-item {
     // width: 11vw; //10vw
-    height: 30px; //2vh
+    height: 25px; //2vh
     background: whitesmoke;
     display: flex; //comment out to allow search icon to be inline with search text
     justify-content: left;
@@ -70,9 +70,6 @@
         text-align: center;
         font-size: 20px;
         margin-left: 5px;
-        margin-top: 5px;
-        margin-bottom: 5px;
-        height: 20px;
     }
 }
 .contextMenu-description {
@@ -90,14 +87,11 @@
     border-style: none;
     // padding: 10px 0px 10px 0px;
     white-space: nowrap;
-    font-size: 13px;
+    font-size: 10px;
     color: grey;
-    letter-spacing: 2px;
+    letter-spacing: 1px;
     text-transform: uppercase;
     padding-right: 30px;
-    margin-top: 5px;
-    height: 20px;
-    margin-bottom: 5px;
 }
 
 .contextMenu-item:hover {
@@ -138,6 +132,10 @@
     padding-left: 5px;
 }
 
+.contextMenu-inlineMenu {
+    border-top: solid 1px;
+}
+
 .contextMenu-item:hover {
     transition: all 0.1s ease;
     background: $lighter-alt-accent;
diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx
index 68ebd8e14..86d1f22e1 100644
--- a/src/client/views/ContextMenuItem.tsx
+++ b/src/client/views/ContextMenuItem.tsx
@@ -98,9 +98,9 @@ export class ContextMenuItem extends React.Component<ContextMenuProps & { select
                     {this._items.map(prop => <ContextMenuItem {...prop} key={prop.description} closeMenu={this.props.closeMenu} />)}
                 </div>;
             if (!("noexpand" in this.props)) {
-                return <>
+                return <div className="contextMenu-inlineMenu">
                     {this._items.map(prop => <ContextMenuItem {...prop} key={prop.description} closeMenu={this.props.closeMenu} />)}
-                </>;
+                </div>;
             }
             return (
                 <div className={"contextMenu-item" + (this.props.selected ? " contextMenu-itemSelected" : "")} style={{ alignItems: where }}
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index abc698e62..e26ad47f9 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -45,6 +45,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
         FormatShapePane.Instance.Pinned = true;
     }
 
+    public static MaskDim = 50000;
     render() {
         TraceMobx();
         const data: InkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? [];
@@ -75,16 +76,16 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
                 height={height}
                 style={{
                     pointerEvents: this.props.Document.isInkMask ? "all" : "none",
-                    transform: this.props.Document.isInkMask ? "translate(2500px, 2500px)" : undefined,
+                    transform: this.props.Document.isInkMask ? `translate(${InkingStroke.MaskDim / 2}px, ${InkingStroke.MaskDim / 2}px)` : undefined,
                     mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? "multiply" : "unset",
                     overflow: "visible",
                 }}
                 onContextMenu={() => {
                     const cm = ContextMenu.Instance;
                     if (cm) {
-                        cm.addItem({ description: "Analyze Stroke", event: this.analyzeStrokes, icon: "paint-brush" });
+                        !Doc.UserDoc().noviceMode && cm.addItem({ description: "Recognize Writing", event: this.analyzeStrokes, icon: "paint-brush" });
                         cm.addItem({ description: "Make Mask", event: this.makeMask, icon: "paint-brush" });
-                        cm.addItem({ description: "Format Shape", event: this.formatShape, icon: "paint-brush" });
+                        cm.addItem({ description: "Format Shape...", event: this.formatShape, icon: "paint-brush" });
                     }
                 }}
             ><defs>
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 9b04deff5..53fd83f26 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -305,18 +305,18 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
                 return newRendition;
             }, false);
 
-            const existing = cm.findByDescription("Options...");
-            const layoutItems = existing && "subitems" in existing ? existing.subitems : [];
-            layoutItems.push({ description: `${this.props.Document.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.props.Document.forceActive = !this.props.Document.forceActive, icon: "project-diagram" });
+            const options = cm.findByDescription("Options...");
+            const optionItems = options && "subitems" in options ? options.subitems : [];
+            optionItems.splice(0, 0, { description: `${this.props.Document.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.props.Document.forceActive = !this.props.Document.forceActive, icon: "project-diagram" });
             if (this.props.Document.childLayout instanceof Doc) {
-                layoutItems.push({ description: "View Child Layout", event: () => this.props.addDocTab(this.props.Document.childLayout as Doc, "onRight"), icon: "project-diagram" });
+                optionItems.push({ description: "View Child Layout", event: () => this.props.addDocTab(this.props.Document.childLayout as Doc, "onRight"), icon: "project-diagram" });
             }
             if (this.props.Document.childClickedOpenTemplateView instanceof Doc) {
-                layoutItems.push({ description: "View Child Detailed Layout", event: () => this.props.addDocTab(this.props.Document.childClickedOpenTemplateView as Doc, "onRight"), icon: "project-diagram" });
+                optionItems.push({ description: "View Child Detailed Layout", event: () => this.props.addDocTab(this.props.Document.childClickedOpenTemplateView as Doc, "onRight"), icon: "project-diagram" });
             }
-            !Doc.UserDoc().noviceMode && layoutItems.push({ description: `${this.props.Document.isInPlaceContainer ? "Unset" : "Set"} inPlace Container`, event: () => this.props.Document.isInPlaceContainer = !this.props.Document.isInPlaceContainer, icon: "project-diagram" });
+            !Doc.UserDoc().noviceMode && optionItems.push({ description: `${this.props.Document.isInPlaceContainer ? "Unset" : "Set"} inPlace Container`, event: () => this.props.Document.isInPlaceContainer = !this.props.Document.isInPlaceContainer, icon: "project-diagram" });
 
-            !existing && cm.addItem({ description: "Options...", subitems: layoutItems, icon: "hand-point-right" });
+            !options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "hand-point-right" });
 
             const existingOnClick = cm.findByDescription("OnClick...");
             const onClicks = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index dc32ecb07..3b19a2ab8 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1240,21 +1240,25 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
 
         const appearance = ContextMenu.Instance.findByDescription("Appearance...");
         const appearanceItems = appearance && "subitems" in appearance ? appearance.subitems : [];
-        appearanceItems.push({ description: "reset view", event: () => { this.props.Document._panX = this.props.Document._panY = 0; this.props.Document[this.scaleFieldKey] = 1; }, icon: "compress-arrows-alt" });
-        appearanceItems.push({ description: `${this.fitToContent ? "Unset" : "Set"} Fit To Container`, event: () => this.Document._fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" });
-        appearanceItems.push({ description: `${this.Document.useClusters ? "Uncluster" : "Use Clusters"}`, event: () => this.updateClusters(!this.Document.useClusters), icon: "braille" });
-        appearanceItems.push({ description: "Use Background Color as Default", event: () => Cast(Doc.UserDoc().emptyCollection, Doc, null)._backgroundColor = StrCast(this.layoutDoc._backgroundColor), icon: "palette" });
+        appearanceItems.push({ description: "Reset View", event: () => { this.props.Document._panX = this.props.Document._panY = 0; this.props.Document[this.scaleFieldKey] = 1; }, icon: "compress-arrows-alt" });
+        appearanceItems.push({ description: `${this.fitToContent ? "Make Zoomable" : "Scale to Window"}`, event: () => this.Document._fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" });
+        appearanceItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" });
         !appearance && ContextMenu.Instance.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "eye" });
 
+        const viewctrls = ContextMenu.Instance.findByDescription("View Controls...");
+        const viewCtrlItems = viewctrls && "subitems" in viewctrls ? viewctrls.subitems : [];
+        viewCtrlItems.push({ description: (Doc.UserDoc().showSnapLines ? "Hide" : "Show") + " Snap Lines", event: () => Doc.UserDoc().showSnapLines = !Doc.UserDoc().showSnapLines, icon: "compress-arrows-alt" });
+        viewCtrlItems.push({ description: (this.Document.useClusters ? "Hide" : "Show") + " Clusters", event: () => this.updateClusters(!this.Document.useClusters), icon: "braille" });
+        !viewctrls && ContextMenu.Instance.addItem({ description: "View Controls...", subitems: viewCtrlItems, icon: "eye" });
+
         const options = ContextMenu.Instance.findByDescription("Options...");
         const optionItems = options && "subitems" in options ? options.subitems : [];
-        !this.props.isAnnotationOverlay &&
+        !this.props.isAnnotationOverlay && !Doc.UserDoc().noviceMode &&
             optionItems.push({ description: (this.showTimeline ? "Close" : "Open") + " Animation Timeline", event: action(() => this.showTimeline = !this.showTimeline), icon: faEye });
         this.props.ContainingCollectionView &&
             optionItems.push({ description: "Promote Collection", event: this.promoteCollection, icon: "table" });
-        optionItems.push({ description: (Doc.UserDoc().showSnapLines ? "Hide" : "Show") + " snap lines", event: () => Doc.UserDoc().showSnapLines = !Doc.UserDoc().showSnapLines, icon: "compress-arrows-alt" });
         optionItems.push({ description: this.layoutDoc._lockedTransform ? "Unlock Transform" : "Lock Transform", event: this.toggleLockTransform, icon: this.layoutDoc._lockedTransform ? "unlock" : "lock" });
-        optionItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" });
+        appearanceItems.push({ description: "Use Background Color as Default", event: () => Cast(Doc.UserDoc().emptyCollection, Doc, null)._backgroundColor = StrCast(this.layoutDoc._backgroundColor), icon: "palette" });
         if (!Doc.UserDoc().noviceMode) {
             optionItems.push({ description: (!this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" });
             optionItems.push({ description: `${this.Document._freeformLOD ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document._freeformLOD = !this.Document._freeformLOD, icon: "table" });
@@ -1263,40 +1267,42 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         !options && ContextMenu.Instance.addItem({ description: "Options...", subitems: optionItems, icon: "eye" });
         const mores = ContextMenu.Instance.findByDescription("More...");
         const moreItems = mores && "subitems" in mores ? mores.subitems : [];
-        moreItems.push({
-            description: "Import document", icon: "upload", event: ({ x, y }) => {
-                const input = document.createElement("input");
-                input.type = "file";
-                input.accept = ".zip";
-                input.onchange = async _e => {
-                    const upload = Utils.prepend("/uploadDoc");
-                    const formData = new FormData();
-                    const file = input.files && input.files[0];
-                    if (file) {
-                        formData.append('file', file);
-                        formData.append('remap', "true");
-                        const response = await fetch(upload, { method: "POST", body: formData });
-                        const json = await response.json();
-                        if (json !== "error") {
-                            const doc = await DocServer.GetRefField(json);
-                            if (doc instanceof Doc) {
-                                const [xx, yy] = this.props.ScreenToLocalTransform().transformPoint(x, y);
-                                doc.x = xx, doc.y = yy;
-                                this.props.addDocument?.(doc);
-                                setTimeout(() => {
-                                    SearchUtil.Search(`{!join from=id to=proto_i}id:link*`, true, {}).then(docs => {
-                                        docs.docs.forEach(d => LinkManager.Instance.addLink(d));
-                                    })
-                                }, 2000); // need to give solr some time to update so that this query will find any link docs we've added.
-                            }
-                        }
+        moreItems.push({ description: "Import document", icon: "upload", event: ({ x, y }) => this.importDocument(x, y) });
+        !mores && ContextMenu.Instance.addItem({ description: "More...", subitems: moreItems, icon: "eye" });
+    }
+
+    importDocument = (x: number, y: number) => {
+        const input = document.createElement("input");
+        input.type = "file";
+        input.accept = ".zip";
+        input.onchange = async _e => {
+            const upload = Utils.prepend("/uploadDoc");
+            const formData = new FormData();
+            const file = input.files && input.files[0];
+            if (file) {
+                formData.append('file', file);
+                formData.append('remap', "true");
+                const response = await fetch(upload, { method: "POST", body: formData });
+                const json = await response.json();
+                if (json !== "error") {
+                    const doc = await DocServer.GetRefField(json);
+                    if (doc instanceof Doc) {
+                        const [xx, yy] = this.props.ScreenToLocalTransform().transformPoint(x, y);
+                        doc.x = xx, doc.y = yy;
+                        this.props.addDocument?.(doc);
+                        setTimeout(() => {
+                            SearchUtil.Search(`{!join from=id to=proto_i}id:link*`, true, {}).then(docs => {
+                                docs.docs.forEach(d => LinkManager.Instance.addLink(d));
+                            })
+                        }, 2000); // need to give solr some time to update so that this query will find any link docs we've added.
                     }
-                };
-                input.click();
+                }
             }
-        });
-        !mores && ContextMenu.Instance.addItem({ description: "More...", subitems: moreItems, icon: "eye" });
+        };
+        input.click();
     }
+
+
     @observable showTimeline = false;
 
     intersectRect(r1: { left: number, top: number, width: number, height: number },
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index d79e2c9ff..ce39c3735 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -15,6 +15,7 @@ import { numberRange } from "../../../Utils";
 import { ComputedField } from "../../../fields/ScriptField";
 import { listSpec } from "../../../fields/Schema";
 import { DocumentType } from "../../documents/DocumentTypes";
+import { InkingStroke } from "../InkingStroke";
 
 export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
     dataProvider?: (doc: Doc, replica: string) => { x: number, y: number, zIndex?: number, opacity?: number, highlight?: boolean, z: number, transition?: string } | undefined;
@@ -37,7 +38,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         return min + rnd * (max - min);
     }
     get displayName() { return "CollectionFreeFormDocumentView(" + this.rootDoc.title + ")"; } // this makes mobx trace() statements more descriptive
-    get maskCentering() { return this.props.Document.isInkMask ? 2500 : 0; }
+    get maskCentering() { return this.props.Document.isInkMask ? InkingStroke.MaskDim / 2 : 0; }
     get transform() { return `scale(${this.props.ContentScaling()}) translate(${this.X - this.maskCentering}px, ${this.Y - this.maskCentering}px) rotate(${this.random(-1, 1) * this.props.jitterRotation}deg)`; }
     get X() { return this.dataProvider ? this.dataProvider.x : (this.Document.x || 0); }
     get Y() { return this.dataProvider ? this.dataProvider.y : (this.Document.y || 0); }
@@ -157,8 +158,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                 outline: this.Highlight ? "orange solid 2px" : "",
                 transform: this.transform,
                 transition: this.props.dataTransition ? this.props.dataTransition : this.dataProvider ? this.dataProvider.transition : StrCast(this.layoutDoc.dataTransition),
-                width: this.props.Document.isInkMask ? 5000 : this.width,
-                height: this.props.Document.isInkMask ? 5000 : this.height,
+                width: this.props.Document.isInkMask ? InkingStroke.MaskDim : this.width,
+                height: this.props.Document.isInkMask ? InkingStroke.MaskDim : this.height,
                 zIndex: this.ZInd,
                 mixBlendMode: StrCast(this.layoutDoc.mixBlendMode) as any,
                 display: this.ZInd === -99 ? "none" : undefined,
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 748938699..9e492650b 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -753,11 +753,15 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         this.props.contextMenuItems?.().forEach(item =>
             cm.addItem({ description: item.label, event: () => item.script.script.run({ this: this.layoutDoc, self: this.rootDoc }), icon: "sticky-note" }));
 
+        const templateDoc = Cast(this.props.Document[StrCast(this.props.Document.layoutKey)], Doc, null);
+        const appearance = cm.findByDescription("Appearance...");
+        const appearanceItems: ContextMenuProps[] = appearance && "subitems" in appearance ? appearance.subitems : [];
+        templateDoc && appearanceItems.push({ description: "Open Template   ", event: () => this.props.addDocTab(templateDoc, "onRight"), icon: "eye" });
+        !appearance && cm.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "compass" });
+
         const options = cm.findByDescription("Options...");
         const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : [];
-        const templateDoc = Cast(this.props.Document[StrCast(this.props.Document.layoutKey)], Doc, null);
         optionItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" });
-        templateDoc && optionItems.push({ description: "Open Template   ", event: () => this.props.addDocTab(templateDoc, "onRight"), icon: "eye" });
         !options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "compass" });
 
 
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index e703a81e2..38fa66d65 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -470,9 +470,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     specificContextMenu = (e: React.MouseEvent): void => {
         const cm = ContextMenu.Instance;
 
-        const appearance = ContextMenu.Instance.findByDescription("Appearance...");
-        const appearanceItems = appearance && "subitems" in appearance ? appearance.subitems : [];
-
         const changeItems: ContextMenuProps[] = [];
         const noteTypesDoc = Cast(Doc.UserDoc()["template-notes"], Doc, null);
         DocListCast(noteTypesDoc?.data).forEach(note => {
@@ -484,24 +481,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             });
         });
         changeItems.push({ description: "FreeForm", event: () => DocUtils.makeCustomViewClicked(this.rootDoc, Docs.Create.FreeformDocument, "freeform"), icon: "eye" });
-        appearanceItems.push({ description: "Change Perspective...", noexpand: true, subitems: changeItems, icon: "external-link-alt" });
-        const uicontrols: ContextMenuProps[] = [];
-        uicontrols.push({ description: "Toggle Sidebar", event: () => this.layoutDoc._showSidebar = !this.layoutDoc._showSidebar, icon: "expand-arrows-alt" });
-        uicontrols.push({ description: "Toggle Dictation Icon", event: () => this.layoutDoc._showAudio = !this.layoutDoc._showAudio, icon: "expand-arrows-alt" });
-        uicontrols.push({ description: "Toggle Menubar", event: () => this.toggleMenubar(), icon: "expand-arrows-alt" });
-        !Doc.UserDoc().noviceMode && uicontrols.push({
-            description: "Broadcast Message", event: () => DocServer.GetRefField("rtfProto").then(proto =>
-                proto instanceof Doc && (proto.BROADCAST_MESSAGE = Cast(this.rootDoc[this.fieldKey], RichTextField)?.Text)), icon: "expand-arrows-alt"
-        });
-
-        appearanceItems.push({ description: "UI Controls...", noexpand: true, subitems: uicontrols, icon: "asterisk" });
-        this.rootDoc.isTemplateDoc && appearanceItems.push({ description: "Make Default Layout", event: async () => Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.rootDoc), icon: "eye" });
-        Doc.UserDoc().defaultTextLayout && appearanceItems.push({ description: "Reset default note style", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" });
-
-        !appearance && ContextMenu.Instance.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "eye" });
-
-        const funcs: ContextMenuProps[] = [];
-
         const highlighting: ContextMenuProps[] = [];
         ["My Text", "Text from Others", "Todo Items", "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"].forEach(option =>
             highlighting.push({
@@ -515,8 +494,24 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                     this.updateHighlights();
                 }, icon: "expand-arrows-alt"
             }));
-        funcs.push({ description: "highlighting...", noexpand: true, subitems: highlighting, icon: "hand-point-right" });
-        funcs.push({
+
+
+        const uicontrols: ContextMenuProps[] = [];
+        uicontrols.push({ description: `${this.layoutDoc._showSidebar ? "Hide" : "Show"} Sidebar`, event: () => this.layoutDoc._showSidebar = !this.layoutDoc._showSidebar, icon: "expand-arrows-alt" });
+        uicontrols.push({ description: `${this.layoutDoc._showAudio ? "Hide" : "Show"} Dictation Icon`, event: () => this.layoutDoc._showAudio = !this.layoutDoc._showAudio, icon: "expand-arrows-alt" });
+        uicontrols.push({ description: "Show Highlights...", noexpand: true, subitems: highlighting, icon: "hand-point-right" });
+        !Doc.UserDoc().noviceMode && uicontrols.push({
+            description: "Broadcast Message", event: () => DocServer.GetRefField("rtfProto").then(proto =>
+                proto instanceof Doc && (proto.BROADCAST_MESSAGE = Cast(this.rootDoc[this.fieldKey], RichTextField)?.Text)), icon: "expand-arrows-alt"
+        });
+        cm.addItem({ description: "UI Controls...", subitems: uicontrols, icon: "asterisk" });
+
+        const appearance = cm.findByDescription("Appearance...");
+        const appearanceItems = appearance && "subitems" in appearance ? appearance.subitems : [];
+        appearanceItems.push({ description: "Change Perspective...", noexpand: true, subitems: changeItems, icon: "external-link-alt" });
+        this.rootDoc.isTemplateDoc && appearanceItems.push({ description: "Make Default Layout", event: async () => Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.rootDoc), icon: "eye" });
+        Doc.UserDoc().defaultTextLayout && appearanceItems.push({ description: "Reset default note style", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" });
+        appearanceItems.push({
             description: "Convert to be a template style", event: () => {
                 if (!this.layoutDoc.isTemplateDoc) {
                     const title = StrCast(this.rootDoc.title);
@@ -541,11 +536,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                 Doc.AddDocToList(Cast(Doc.UserDoc()["template-notes"], Doc, null), "data", this.rootDoc);
             }, icon: "eye"
         });
-        funcs.push({ description: `${this.Document._autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" });
-
-        funcs.push({ description: "Toggle Single Line", event: () => this.layoutDoc._singleLine = !this.layoutDoc._singleLine, icon: "expand-arrows-alt" });
-        funcs.push({ description: (!this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" });
-        ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
+        cm.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "eye" });
+
+        const options = cm.findByDescription("Options...");
+        const optionItems = options && "subitems" in options ? options.subitems : [];
+        !Doc.UserDoc().noviceMode && optionItems.push({ description: this.Document._singleLine ? "Make Single Line" : "Make Multi Line", event: () => this.layoutDoc._singleLine = !this.layoutDoc._singleLine, icon: "expand-arrows-alt" });
+        optionItems.push({ description: `${this.Document._autoHeight ? "Lock" : "Auto"} Height`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" });
+        optionItems.push({ description: `${!this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Lock" : "Unlock"} Aspect`, event: this.toggleNativeDimensions, icon: "snowflake" });
+        !options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "eye" });
         this._downX = this._downY = Number.NaN;
     }
 
@@ -562,11 +560,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     }
     stopDictation = (abort: boolean) => { DictationManager.Controls.stop(!abort); };
 
-    @action
-    toggleMenubar = () => {
-        this.layoutDoc._chromeStatus = this.layoutDoc._chromeStatus === "disabled" ? "enabled" : "disabled";
-    }
-
     recordBullet = async () => {
         const completedCue = "end session";
         const results = await DictationManager.Controls.listen({
-- 
cgit v1.2.3-70-g09d2


From 150d1b3ea263dd0d6d8189d8bf6fd87c5505cfc4 Mon Sep 17 00:00:00 2001
From: usodhi <61431818+usodhi@users.noreply.github.com>
Date: Fri, 24 Jul 2020 19:15:52 +0530
Subject: fixed instances of AclEdit to include AclAdmin

---
 src/client/views/DocComponent.tsx                               | 2 +-
 src/client/views/collections/CollectionView.tsx                 | 5 +++--
 src/client/views/collections/collectionFreeForm/MarqueeView.tsx | 4 ++--
 src/client/views/nodes/DocumentView.tsx                         | 5 +++--
 src/client/views/nodes/formattedText/FormattedTextBox.tsx       | 5 +++--
 src/fields/util.ts                                              | 7 +++++--
 6 files changed, 17 insertions(+), 11 deletions(-)

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

diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 00d570670..cfabae8a1 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -150,7 +150,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
             const effectiveAcl = GetEffectiveAcl(this.dataDoc);
 
             if (added.length) {
-                if (effectiveAcl === AclReadonly && !getPlaygroundMode()) {
+                if (effectiveAcl === AclPrivate || (effectiveAcl === AclReadonly && !getPlaygroundMode())) {
                     return false;
                 }
                 else {
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 4848508be..031fa433f 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -144,7 +144,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
         const effectiveAcl = GetEffectiveAcl(this.props.Document);
 
         if (added.length) {
-            if (effectiveAcl === AclReadonly && !getPlaygroundMode()) {
+            if (effectiveAcl === AclPrivate || (effectiveAcl === AclReadonly && !getPlaygroundMode())) {
                 return false;
             }
             else {
@@ -191,7 +191,8 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
 
     @action.bound
     removeDocument = (doc: any): boolean => {
-        if (GetEffectiveAcl(this.props.Document) === AclEdit || getPlaygroundMode()) {
+        const effectiveAcl = GetEffectiveAcl(this.props.Document);
+        if (effectiveAcl === AclEdit || effectiveAcl === AclAdmin || getPlaygroundMode()) {
             const docs = doc instanceof Doc ? [doc] : doc as Doc[];
             const targetDataDoc = this.props.Document[DataSym];
             const value = DocListCast(targetDataDoc[this.props.fieldKey]);
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 84719b2c9..abe9ccd5e 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -1,6 +1,6 @@
 import { action, computed, observable } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, Opt, DocListCast, DataSym, AclEdit, AclAddonly } from "../../../../fields/Doc";
+import { Doc, Opt, DocListCast, DataSym, AclEdit, AclAddonly, AclAdmin } from "../../../../fields/Doc";
 import { GetEffectiveAcl, getPlaygroundMode } from "../../../../fields/util";
 import { InkData, InkField, InkTool } from "../../../../fields/InkField";
 import { List } from "../../../../fields/List";
@@ -281,7 +281,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
             this._downX = x;
             this._downY = y;
             const effectiveAcl = GetEffectiveAcl(this.props.Document);
-            if (effectiveAcl === AclEdit || effectiveAcl === AclAddonly || getPlaygroundMode()) PreviewCursor.Show(x, y, this.onKeyPress, this.props.addLiveTextDocument, this.props.getTransform, this.props.addDocument, this.props.nudge);
+            if ([AclAdmin, AclEdit, AclAddonly].includes(effectiveAcl) || getPlaygroundMode()) PreviewCursor.Show(x, y, this.onKeyPress, this.props.addLiveTextDocument, this.props.getTransform, this.props.addDocument, this.props.nudge);
             this.clearSelection();
         }
     });
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 1d4b61545..975c5b318 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -3,7 +3,7 @@ import * as fa from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, observable, runInAction } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, DocListCast, HeightSym, Opt, WidthSym, DataSym, AclPrivate, AclEdit } from "../../../fields/Doc";
+import { Doc, DocListCast, HeightSym, Opt, WidthSym, DataSym, AclPrivate, AclEdit, AclAdmin } from "../../../fields/Doc";
 import { Document } from '../../../fields/documentSchemas';
 import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
@@ -807,7 +807,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             }
             moreItems.push({ description: "Copy ID", event: () => Utils.CopyText(Utils.prepend("/doc/" + this.props.Document[Id])), icon: "fingerprint" });
         }
-        GetEffectiveAcl(this.props.Document) === AclEdit && moreItems.push({ description: "Delete", event: this.deleteClicked, icon: "trash" });
+        const effectiveAcl = GetEffectiveAcl(this.props.Document);
+        (effectiveAcl === AclEdit || effectiveAcl === AclAdmin) && moreItems.push({ description: "Delete", event: this.deleteClicked, icon: "trash" });
         moreItems.push({ description: "Share", event: () => SharingManager.Instance.open(this), icon: "external-link-alt" });
         !more && cm.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" });
         cm.moveAfter(cm.findByDescription("More...")!, cm.findByDescription("OnClick...")!);
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 38fa66d65..2ac53ce6a 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -13,7 +13,7 @@ import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from "
 import { ReplaceStep } from 'prosemirror-transform';
 import { EditorView } from "prosemirror-view";
 import { DateField } from '../../../../fields/DateField';
-import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym, AclEdit } from "../../../../fields/Doc";
+import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym, AclEdit, AclAdmin } from "../../../../fields/Doc";
 import { documentSchema } from '../../../../fields/documentSchemas';
 import applyDevTools = require("prosemirror-dev-tools");
 import { removeMarkWithAttrs } from "./prosemirrorPatches";
@@ -233,7 +233,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             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 (GetEffectiveAcl(this.dataDoc) === AclEdit) {
+            const effectiveAcl = GetEffectiveAcl(this.dataDoc);
+            if (effectiveAcl === AclEdit || effectiveAcl === AclAdmin) {
                 if (!this._applyingChange && json.replace(/"selection":.*/, "") !== curProto?.Data.replace(/"selection":.*/, "")) {
                     this._applyingChange = true;
                     (curText !== Cast(this.dataDoc[this.fieldKey], RichTextField)?.Text) && (this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now())));
diff --git a/src/fields/util.ts b/src/fields/util.ts
index eba398dfb..a8b284658 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -67,11 +67,14 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
         delete curValue[Parent];
         delete curValue[OnUpdate];
     }
+
+    const effectiveAcl = GetEffectiveAcl(target);
+
     const writeMode = DocServer.getFieldWriteMode(prop as string);
     const fromServer = target[UpdatingFromServer];
     const sameAuthor = fromServer || (receiver.author === Doc.CurrentUserEmail);
-    const writeToDoc = sameAuthor || GetEffectiveAcl(target) === AclEdit || (writeMode !== DocServer.WriteMode.LiveReadonly);
-    const writeToServer = (sameAuthor || GetEffectiveAcl(target) === AclEdit || writeMode === DocServer.WriteMode.Default) && !playgroundMode;
+    const writeToDoc = sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAdmin || (writeMode !== DocServer.WriteMode.LiveReadonly);
+    const writeToServer = (sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAdmin || writeMode === DocServer.WriteMode.Default) && !playgroundMode;
 
     if (writeToDoc) {
         if (value === undefined) {
-- 
cgit v1.2.3-70-g09d2


From 760c51c493ef190aa5283944b083b55f7c057e6c Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 24 Jul 2020 23:59:09 -0400
Subject: several siggnificant changes to how acls work with makin documents
 updated interactively when permissions chane.

---
 src/client/DocServer.ts                            | 80 +++++++++++++---------
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  5 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  2 +-
 src/fields/Doc.ts                                  | 48 +++++++++----
 src/fields/Schema.ts                               |  2 +-
 src/fields/util.ts                                 | 10 ++-
 6 files changed, 95 insertions(+), 52 deletions(-)

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

diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index bac324c77..8ded43468 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -1,13 +1,14 @@
 import * as io from 'socket.io-client';
 import { MessageStore, YoutubeQueryTypes, GestureContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent, MobileDocumentUploadContent } from "./../server/Message";
-import { Opt, Doc, fetchProto } from '../fields/Doc';
+import { Opt, Doc, fetchProto, FieldsSym } from '../fields/Doc';
 import { Utils, emptyFunction } from '../Utils';
 import { SerializationHelper } from './util/SerializationHelper';
 import { RefField } from '../fields/RefField';
-import { Id, HandleUpdate } from '../fields/FieldSymbols';
+import { Id, HandleUpdate, Parent } from '../fields/FieldSymbols';
 import GestureOverlay from './views/GestureOverlay';
 import MobileInkOverlay from '../mobile/MobileInkOverlay';
 import { runInAction } from 'mobx';
+import { ObjectField } from '../fields/ObjectField';
 
 /**
  * This class encapsulates the transfer and cross-client synchronization of
@@ -207,12 +208,12 @@ export namespace DocServer {
      * the server if the document has not been cached.
      * @param id the id of the requested document
      */
-    const _GetRefFieldImpl = (id: string): Promise<Opt<RefField>> => {
+    const _GetRefFieldImpl = (id: string, force: boolean = false): Promise<Opt<RefField>> => {
         // an initial pass through the cache to determine whether the document needs to be fetched,
         // is already in the process of being fetched or already exists in the
         // cache
         const cached = _cache[id];
-        if (cached === undefined) {
+        if (cached === undefined || force) {
             // NOT CACHED => we'll have to send a request to the server
 
             // synchronously, we emit a single callback to the server requesting the serialized (i.e. represented by a string)
@@ -226,7 +227,16 @@ export namespace DocServer {
             const deserializeField = getSerializedField.then(async fieldJson => {
                 // deserialize
                 const field = await SerializationHelper.Deserialize(fieldJson);
-                if (field !== undefined) {
+                if (force && field instanceof Doc && cached instanceof Doc) {
+                    Array.from(Object.keys(field)).forEach(key => {
+                        const fieldval = field[key];
+                        if (fieldval instanceof ObjectField) {
+                            fieldval[Parent] = undefined;
+                        }
+                        cached[key] = field[key];
+                    });
+                }
+                else if (field !== undefined) {
                     _cache[id] = field;
                 } else {
                     delete _cache[id];
@@ -238,8 +248,8 @@ export namespace DocServer {
             });
             // here, indicate that the document associated with this id is currently
             // being retrieved and cached
-            _cache[id] = deserializeField;
-            return deserializeField;
+            !force && (_cache[id] = deserializeField);
+            return force ? cached as any : deserializeField;
         } else if (cached instanceof Promise) {
             // BEING RETRIEVED AND CACHED => some other caller previously (likely recently) called GetRefField(s),
             // and requested the document I'm looking for. Shouldn't fetch again, just
@@ -260,11 +270,11 @@ export namespace DocServer {
         }
     };
 
-    let _GetRefField: (id: string) => Promise<Opt<RefField>> = errorFunc;
+    let _GetRefField: (id: string, force: boolean) => Promise<Opt<RefField>> = errorFunc;
     let _GetCachedRefField: (id: string) => Opt<RefField> = errorFunc;
 
-    export function GetRefField(id: string): Promise<Opt<RefField>> {
-        return _GetRefField(id);
+    export function GetRefField(id: string, force = false): Promise<Opt<RefField>> {
+        return _GetRefField(id, force);
     }
     export function GetCachedRefField(id: string): Opt<RefField> {
         return _GetCachedRefField(id);
@@ -330,29 +340,35 @@ export namespace DocServer {
             const proms: Promise<void>[] = [];
             runInAction(() => {
                 for (const field of fields) {
-                    if (field !== undefined && field !== null) {
+                    if (field !== undefined && field !== null && !_cache[field.id]) {
                         // deserialize
-                        const prom = SerializationHelper.Deserialize(field).then(deserialized => {
-                            fieldMap[field.id] = deserialized;
-
-                            //overwrite or delete any promises (that we inserted as flags
-                            // to indicate that the field was in the process of being fetched). Now everything
-                            // should be an actual value within or entirely absent from the cache.
-                            if (deserialized !== undefined) {
-                                _cache[field.id] = deserialized;
-                            } else {
-                                delete _cache[field.id];
-                            }
-                            return deserialized;
-                        });
-                        // 4) here, for each of the documents we've requested *ourselves* (i.e. weren't promises or found in the cache)
-                        // we set the value at the field's id to a promise that will resolve to the field. 
-                        // When we find that promises exist at keys in the cache, THIS is where they were set, just by some other caller (method).
-                        // The mapping in the .then call ensures that when other callers await these promises, they'll
-                        // get the resolved field
-                        _cache[field.id] = prom;
-                        // adds to a list of promises that will be awaited asynchronously
-                        proms.push(prom);
+                        const cached = _cache[field.id];
+                        if (!cached) {
+                            const prom = SerializationHelper.Deserialize(field).then(deserialized => {
+                                fieldMap[field.id] = deserialized;
+
+                                //overwrite or delete any promises (that we inserted as flags
+                                // to indicate that the field was in the process of being fetched). Now everything
+                                // should be an actual value within or entirely absent from the cache.
+                                if (deserialized !== undefined) {
+                                    _cache[field.id] = deserialized;
+                                } else {
+                                    delete _cache[field.id];
+                                }
+                                return deserialized;
+                            });
+                            // 4) here, for each of the documents we've requested *ourselves* (i.e. weren't promises or found in the cache)
+                            // we set the value at the field's id to a promise that will resolve to the field. 
+                            // When we find that promises exist at keys in the cache, THIS is where they were set, just by some other caller (method).
+                            // The mapping in the .then call ensures that when other callers await these promises, they'll
+                            // get the resolved field
+                            _cache[field.id] = prom;
+
+                            // adds to a list of promises that will be awaited asynchronously
+                            proms.push(prom);
+                        } else if (cached instanceof Promise) {
+                            proms.push(cached as any);
+                        }
                     }
                 }
             });
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 979b21321..38c1edde2 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -944,7 +944,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     parentActive = (outsideReaction: boolean) => this.props.active(outsideReaction) || this.backgroundActive ? true : false;
     getChildDocumentViewProps(childLayout: Doc, childData?: Doc): DocumentViewProps {
         return {
-            ...this.props,
+            pinToPres: this.props.pinToPres,
+            whenActiveChanged: this.props.whenActiveChanged,
             NativeHeight: returnZero,
             NativeWidth: returnZero,
             fitToBox: false,
@@ -1145,7 +1146,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     @action
     componentDidMount() {
         super.componentDidMount?.();
-        this._layoutComputeReaction = reaction(() => this.doLayoutComputation,
+        this._layoutComputeReaction = reaction(() => { TraceMobx(); return this.doLayoutComputation },
             (elements) => this._layoutElements = elements || [],
             { fireImmediately: true, name: "doLayout" });
 
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 2ac53ce6a..53234d819 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -690,7 +690,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         );
         this._disposers.editorState = reaction(
             () => {
-                if (this.dataDoc[this.props.fieldKey + "-noTemplate"] || !this.layoutDoc[this.props.fieldKey + "-textTemplate"]) {
+                if (this.dataDoc?.[this.props.fieldKey + "-noTemplate"] || !this.layoutDoc[this.props.fieldKey + "-textTemplate"]) {
                     return Cast(this.dataDoc[this.props.fieldKey], RichTextField, null)?.Data;
                 }
                 return Cast(this.layoutDoc[this.props.fieldKey + "-textTemplate"], RichTextField, null)?.Data;
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index c9231753f..9020dcc84 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -104,7 +104,7 @@ export const AclAddonly = Symbol("AclAddonly");
 export const AclEdit = Symbol("AclEdit");
 export const AclAdmin = Symbol("AclAdmin");
 export const UpdatingFromServer = Symbol("UpdatingFromServer");
-const CachedUpdates = Symbol("Cached updates");
+export const CachedUpdates = Symbol("Cached updates");
 
 const AclMap = new Map<string, symbol>([
     [SharingPermissions.None, AclPrivate],
@@ -115,15 +115,15 @@ const AclMap = new Map<string, symbol>([
 ]);
 
 export function fetchProto(doc: Doc) {
-    // if (doc.author !== Doc.CurrentUserEmail) {
-    untracked(() => {
-        const permissions: { [key: string]: symbol } = {};
+    const permissions: { [key: string]: symbol } = {};
 
-        Object.keys(doc).filter(key => key.startsWith("ACL")).forEach(key => permissions[key] = AclMap.get(StrCast(doc[key]))!);
+    Object.keys(doc).filter(key => key.startsWith("ACL")).forEach(key => permissions[key] = AclMap.get(StrCast(doc[key]))!);
 
-        if (Object.keys(permissions).length) doc[AclSym] = permissions;
-    });
-    // }
+    if (Object.keys(permissions).length) doc[AclSym] = permissions;
+
+    if (GetEffectiveAcl(doc) === AclPrivate) {
+        runInAction(() => doc[FieldsSym](true));
+    }
 
     if (doc.proto instanceof Promise) {
         doc.proto.then(fetchProto);
@@ -143,7 +143,7 @@ export class Doc extends RefField {
             has: (target, key) => GetEffectiveAcl(target) !== AclPrivate && key in target.__fields,
             ownKeys: target => {
                 const obj = {} as any;
-                if (GetEffectiveAcl(target) !== AclPrivate) Object.assign(obj, target.___fields);
+                if (GetEffectiveAcl(target) !== AclPrivate) Object.assign(obj, target.___fieldKeys);
                 runInAction(() => obj.__LAYOUT__ = target.__LAYOUT__);
                 return Object.keys(obj);
             },
@@ -151,11 +151,11 @@ export class Doc extends RefField {
                 if (prop.toString() === "__LAYOUT__") {
                     return Reflect.getOwnPropertyDescriptor(target, prop);
                 }
-                if (prop in target.__fields) {
+                if (prop in target.__fieldKeys) {
                     return {
                         configurable: true,//TODO Should configurable be true?
                         enumerable: true,
-                        value: target.__fields[prop]
+                        value: 0//() => target.__fields[prop])
                     };
                 }
                 return Reflect.getOwnPropertyDescriptor(target, prop);
@@ -179,15 +179,23 @@ export class Doc extends RefField {
         this.___fields = value;
         for (const key in value) {
             const field = value[key];
+            field && (this.__fieldKeys[key] = true);
             if (!(field instanceof ObjectField)) continue;
             field[Parent] = this[Self];
             field[OnUpdate] = updateFunction(this[Self], key, field, this[SelfProxy]);
         }
     }
+    private get __fieldKeys() { return this.___fieldKeys; }
+    private set __fieldKeys(value) {
+        this.___fieldKeys = value;
+    }
 
     @observable
     private ___fields: any = {};
 
+    @observable
+    private ___fieldKeys: any = {};
+
     private [UpdatingFromServer]: boolean = false;
 
     private [Update] = (diff: any) => {
@@ -196,7 +204,14 @@ export class Doc extends RefField {
 
     private [Self] = this;
     private [SelfProxy]: any;
-    public [FieldsSym] = () => this.___fields;
+    public [FieldsSym] = (clear?: boolean) => {
+        if (clear) {
+            this.___fields = {};
+            this.___fieldKeys = {};
+        }
+        return this.___fields;
+    }
+    @observable
     public [AclSym]: { [key: string]: symbol };
     public [WidthSym] = () => NumCast(this[SelfProxy]._width);
     public [HeightSym] = () => NumCast(this[SelfProxy]._height);
@@ -237,11 +252,18 @@ export class Doc extends RefField {
                 const fKey = key.substring(7);
                 const fn = async () => {
                     const value = await SerializationHelper.Deserialize(set[key]);
+                    const prev = GetEffectiveAcl(this);
                     this[UpdatingFromServer] = true;
                     this[fKey] = value;
+                    if (fKey.startsWith("ACL")) {
+                        fetchProto(this);
+                    }
                     this[UpdatingFromServer] = false;
+                    if (prev === AclPrivate && GetEffectiveAcl(this) !== AclPrivate) {
+                        DocServer.GetRefField(this[Id], true);
+                    }
                 };
-                if (sameAuthor || DocServer.getFieldWriteMode(fKey) !== DocServer.WriteMode.Playground) {
+                if (sameAuthor || fKey.startsWith("ACL") || DocServer.getFieldWriteMode(fKey) !== DocServer.WriteMode.Playground) {
                     delete this[CachedUpdates][fKey];
                     await fn();
                 } else {
diff --git a/src/fields/Schema.ts b/src/fields/Schema.ts
index 98ef3e087..c6a8d6ae9 100644
--- a/src/fields/Schema.ts
+++ b/src/fields/Schema.ts
@@ -31,7 +31,7 @@ export function makeInterface<T extends Interface[]>(...schemas: T): InterfaceFu
     }
     const proto = new Proxy({}, {
         get(target: any, prop, receiver) {
-            const field = receiver.doc[prop];
+            const field = receiver.doc?.[prop];
             if (prop in schema) {
                 const desc = prop === "proto" ? Doc : (schema as any)[prop]; // bcz: proto doesn't appear in schemas ... maybe it should?
                 if (typeof desc === "object" && "defaultVal" in desc && "type" in desc) {//defaultSpec
diff --git a/src/fields/util.ts b/src/fields/util.ts
index 2b970d342..e0b1843a1 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -1,11 +1,11 @@
 import { UndoManager } from "../client/util/UndoManager";
-import { Doc, FieldResult, UpdatingFromServer, LayoutSym, AclPrivate, AclEdit, AclReadonly, AclAddonly, AclSym, fetchProto, DataSym, DocListCast, AclAdmin } from "./Doc";
+import { Doc, FieldResult, UpdatingFromServer, LayoutSym, AclPrivate, AclEdit, AclReadonly, AclAddonly, AclSym, CachedUpdates, DataSym, DocListCast, AclAdmin, FieldsSym } from "./Doc";
 import { SerializationHelper } from "../client/util/SerializationHelper";
 import { ProxyField, PrefetchProxy } from "./Proxy";
 import { RefField } from "./RefField";
 import { ObjectField } from "./ObjectField";
 import { action, trace } from "mobx";
-import { Parent, OnUpdate, Update, Id, SelfProxy, Self } from "./FieldSymbols";
+import { Parent, OnUpdate, Update, Id, SelfProxy, Self, HandleUpdate } from "./FieldSymbols";
 import { DocServer } from "../client/DocServer";
 import { ComputedField } from "./ScriptField";
 import { ScriptCast, StrCast } from "./Types";
@@ -77,8 +77,10 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
 
     if (writeToDoc) {
         if (value === undefined) {
+            delete target.__fieldKeys[prop];
             delete target.__fields[prop];
         } else {
+            target.__fieldKeys[prop] = true;
             target.__fields[prop] = value;
         }
         //if (typeof value === "object" && !(value instanceof ObjectField)) debugger;
@@ -137,7 +139,7 @@ export enum SharingPermissions {
 }
 
 export function GetEffectiveAcl(target: any, in_prop?: string | symbol | number): symbol {
-    if (in_prop === UpdatingFromServer || target[UpdatingFromServer]) return AclEdit;
+    if (in_prop === UpdatingFromServer || target[UpdatingFromServer]) return AclAdmin;
 
     if (target[AclSym] && Object.keys(target[AclSym]).length) {
 
@@ -241,6 +243,8 @@ export function setter(target: any, in_prop: string | symbol | number, value: an
 
 export function getter(target: any, in_prop: string | symbol | number, receiver: any): any {
     let prop = in_prop;
+
+    if (in_prop === FieldsSym || in_prop === Id || in_prop === HandleUpdate || in_prop === CachedUpdates) return target.__fields[prop] || target[prop];
     if (in_prop === AclSym) return _overrideAcl ? undefined : target[AclSym];
     if (GetEffectiveAcl(target) === AclPrivate && !_overrideAcl) return undefined;
     if (prop === LayoutSym) {
-- 
cgit v1.2.3-70-g09d2


From 99e0b01dbe41e56cfdb748b53f03fe71d73a80b7 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 25 Jul 2020 10:54:12 -0400
Subject: pushpin annotations can follow links to multple targets at once. 
 chaned text boxes so that they can be made isBackround .fixed addin/remoin
 documents from freeform collections.

---
 src/client/util/DocumentManager.ts                 | 44 +++++++++++-----------
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  3 ++
 .../views/nodes/formattedText/FormattedTextBox.tsx |  2 +-
 src/fields/Doc.ts                                  |  1 -
 4 files changed, 27 insertions(+), 23 deletions(-)

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

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 51b50878d..523dbfca0 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -152,18 +152,18 @@ export class DocumentManager {
             const first = getFirstDocView(annotatedDoc);
             if (first) {
                 annotatedDoc = first.props.Document;
-                if (docView) {
-                    docView.props.focus(annotatedDoc, false);
-                }
+                docView?.props.focus(annotatedDoc, false);
             }
         }
         if (docView) {  // we have a docView already and aren't forced to create a new one ... just focus on the document.  TODO move into view if necessary otherwise just highlight?
-            if (originatingDoc?.isPushpin) docView.props.Document.hidden = !docView.props.Document.hidden;
+            if (originatingDoc?.isPushpin) {
+                docView.props.Document.hidden = !docView.props.Document.hidden;
+            }
             else {
                 docView.props.Document.hidden && (docView.props.Document.hidden = undefined);
                 docView.props.focus(docView.props.Document, willZoom, undefined, focusAndFinish);
+                highlight();
             }
-            highlight();
         } else {
             const contextDocs = docContext ? await DocListCastAsync(docContext.data) : undefined;
             const contextDoc = contextDocs?.find(doc => Doc.AreProtosEqual(doc, targetDoc)) ? docContext : undefined;
@@ -218,25 +218,27 @@ export class DocumentManager {
         const backLinkWithoutTargetView = secondDocs.find(d => DocumentManager.Instance.getDocumentViews(d.anchor1 as Doc).length === 0);
         const linkWithoutTargetDoc = traverseBacklink === undefined ? fwdLinkWithoutTargetView || backLinkWithoutTargetView : traverseBacklink ? backLinkWithoutTargetView : fwdLinkWithoutTargetView;
         const linkDocList = linkWithoutTargetDoc ? [linkWithoutTargetDoc] : (traverseBacklink === undefined ? firstDocs.concat(secondDocs) : traverseBacklink ? secondDocs : firstDocs);
-        const linkDoc = linkDocList.length && linkDocList[0];
-        if (linkDoc) {
-            const target = (doc === linkDoc.anchor1 ? linkDoc.anchor2 : doc === linkDoc.anchor2 ? linkDoc.anchor1 :
-                (Doc.AreProtosEqual(doc, linkDoc.anchor1 as Doc) ? linkDoc.anchor2 : linkDoc.anchor1)) as Doc;
-            const targetTimecode = (doc === linkDoc.anchor1 ? Cast(linkDoc.anchor2_timecode, "number") :
-                doc === linkDoc.anchor2 ? Cast(linkDoc.anchor1_timecode, "number") :
-                    (Doc.AreProtosEqual(doc, linkDoc.anchor1 as Doc) ? Cast(linkDoc.anchor2_timecode, "number") : Cast(linkDoc.anchor1_timecode, "number")));
-            if (target) {
-                const containerDoc = (await Cast(target.annotationOn, Doc)) || target;
-                containerDoc.currentTimecode = targetTimecode;
-                const targetContext = await target?.context as Doc;
-                const targetNavContext = !Doc.AreProtosEqual(targetContext, currentContext) ? targetContext : undefined;
-                DocumentManager.Instance.jumpToDocument(target, zoom, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, "onRight"), finished), targetNavContext, linkDoc, undefined, doc, finished);
+        const followLinks = linkDocList.length ? (doc.isPushpin ? linkDocList : [linkDocList[0]]) : [];
+        followLinks.forEach(async linkDoc => {
+            if (linkDoc) {
+                const target = (doc === linkDoc.anchor1 ? linkDoc.anchor2 : doc === linkDoc.anchor2 ? linkDoc.anchor1 :
+                    (Doc.AreProtosEqual(doc, linkDoc.anchor1 as Doc) ? linkDoc.anchor2 : linkDoc.anchor1)) as Doc;
+                const targetTimecode = (doc === linkDoc.anchor1 ? Cast(linkDoc.anchor2_timecode, "number") :
+                    doc === linkDoc.anchor2 ? Cast(linkDoc.anchor1_timecode, "number") :
+                        (Doc.AreProtosEqual(doc, linkDoc.anchor1 as Doc) ? Cast(linkDoc.anchor2_timecode, "number") : Cast(linkDoc.anchor1_timecode, "number")));
+                if (target) {
+                    const containerDoc = (await Cast(target.annotationOn, Doc)) || target;
+                    containerDoc.currentTimecode = targetTimecode;
+                    const targetContext = await target?.context as Doc;
+                    const targetNavContext = !Doc.AreProtosEqual(targetContext, currentContext) ? targetContext : undefined;
+                    DocumentManager.Instance.jumpToDocument(target, zoom, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, "onRight"), finished), targetNavContext, linkDoc, undefined, doc, finished);
+                } else {
+                    finished?.();
+                }
             } else {
                 finished?.();
             }
-        } else {
-            finished?.();
-        }
+        });
     }
 }
 Scripting.addGlobal(function DocFocus(doc: any) { DocumentManager.Instance.getDocumentViews(Doc.GetProto(doc)).map(view => view.props.focus(doc, true)); });
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 38c1edde2..079add4b0 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -944,6 +944,9 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     parentActive = (outsideReaction: boolean) => this.props.active(outsideReaction) || this.backgroundActive ? true : false;
     getChildDocumentViewProps(childLayout: Doc, childData?: Doc): DocumentViewProps {
         return {
+            addDocument: this.props.addDocument,
+            removeDocument: this.props.removeDocument,
+            moveDocument: this.props.moveDocument,
             pinToPres: this.props.pinToPres,
             whenActiveChanged: this.props.whenActiveChanged,
             NativeHeight: returnZero,
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 53234d819..966aaa895 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1406,7 +1406,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                         <div className={`formattedTextBox-inner${rounded}${selclass}`} ref={this.createDropTarget}
                             style={{
                                 padding: this.layoutDoc._textBoxPadding ? StrCast(this.layoutDoc._textBoxPadding) : `${Math.max(0, NumCast(this.layoutDoc._yMargin, this.props.yMargin || 0) + selPad)}px  ${NumCast(this.layoutDoc._xMargin, this.props.xMargin || 0) + selPad}px`,
-                                pointerEvents: !this.props.isSelected() ? ((this.layoutDoc.isLinkButton || this.props.onClick) ? "none" : "all") : undefined
+                                pointerEvents: !this.props.isSelected() ? ((this.layoutDoc.isLinkButton || this.props.onClick) ? "none" : undefined) : undefined
                             }}
                         />
                     </div>
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 9020dcc84..3ea53a9e0 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -916,7 +916,6 @@ export namespace Doc {
         return doc;
     }
     export function UnBrushDoc(doc: Doc) {
-
         if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return doc;
         brushManager.BrushedDoc.delete(doc);
         brushManager.BrushedDoc.delete(Doc.GetProto(doc));
-- 
cgit v1.2.3-70-g09d2


From 5454385b6fabbcafee4d797829fb9ceaa8a71ed1 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 25 Jul 2020 20:14:16 -0400
Subject: made buttons turn into fonticons on iconify.  droppin fonticons onto
 linear views keeps them as buttons.  droppin buttons on linear view makes
 them fonticons. fixed ColletionMenu scripts to work with templates by usin
 self instead of this. fixed pointer events with text boxes for filterview &
 normal.  fixed fieldKey deletin and addin

---
 src/client/util/CurrentUserUtils.ts                | 12 +++++++++--
 src/client/util/DragManager.ts                     |  7 +++----
 src/client/util/DropConverter.ts                   |  2 +-
 src/client/views/MainView.tsx                      |  4 ++--
 src/client/views/collections/CollectionMenu.tsx    | 23 ++++++++++++++--------
 src/client/views/nodes/DocumentView.tsx            |  2 +-
 src/client/views/nodes/FontIconBox.tsx             |  2 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  4 ++--
 src/fields/util.ts                                 |  4 ++--
 9 files changed, 37 insertions(+), 23 deletions(-)

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

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index b47cfb33a..6d752832a 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -346,6 +346,14 @@ export class CurrentUserUtils {
             iconRtfView.isTemplateDoc = makeTemplate(iconRtfView, true, "icon_" + DocumentType.RTF);
             doc["template-icon-view-rtf"] = new PrefetchProxy(iconRtfView);
         }
+        if (doc["template-icon-view-button"] === undefined) {
+            const iconBtnView = Docs.Create.FontIconDocument({
+                title: "icon_" + DocumentType.BUTTON, _nativeHeight: 30, _nativeWidth: 30,
+                _width: 30, _height: 30, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)")
+            });
+            iconBtnView.isTemplateDoc = makeTemplate(iconBtnView, true, "icon_" + DocumentType.BUTTON);
+            doc["template-icon-view-button"] = new PrefetchProxy(iconBtnView);
+        }
         if (doc["template-icon-view-img"] === undefined) {
             const iconImageView = Docs.Create.ImageDocument("http://www.cs.brown.edu/~bcz/face.gif", {
                 title: "data", _width: 50, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)")
@@ -359,11 +367,11 @@ export class CurrentUserUtils {
             doc["template-icon-view-col"] = new PrefetchProxy(iconColView);
         }
         if (doc["template-icons"] === undefined) {
-            doc["template-icons"] = new PrefetchProxy(Docs.Create.TreeDocument([doc["template-icon-view"] as Doc, doc["template-icon-view-img"] as Doc,
+            doc["template-icons"] = new PrefetchProxy(Docs.Create.TreeDocument([doc["template-icon-view"] as Doc, doc["template-icon-view-img"] as Doc, doc["template-icon-view-button"] as Doc,
             doc["template-icon-view-col"] as Doc, doc["template-icon-view-rtf"] as Doc, doc["template-icon-view-pdf"] as Doc], { title: "icon templates", _height: 75 }));
         } else {
             const templateIconsDoc = Cast(doc["template-icons"], Doc, null);
-            const requiredTypes = [doc["template-icon-view"] as Doc, doc["template-icon-view-img"] as Doc,
+            const requiredTypes = [doc["template-icon-view"] as Doc, doc["template-icon-view-img"] as Doc, doc["template-icon-view-button"] as Doc,
             doc["template-icon-view-col"] as Doc, doc["template-icon-view-rtf"] as Doc];
             DocListCastAsync(templateIconsDoc.data).then(async curIcons => {
                 await Promise.all(curIcons!);
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 837f0b1db..4b1860b5c 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -224,11 +224,10 @@ 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) {
+    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 = 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) });
+            const bd = Docs.Create.ButtonDocument({ toolTip: title, z: 1, _width: 150, _height: 50, 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);
diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts
index f9837298d..d0acf14c3 100644
--- a/src/client/util/DropConverter.ts
+++ b/src/client/util/DropConverter.ts
@@ -57,7 +57,7 @@ export function convertDropDataToButtons(data: DragManager.DocumentDragData) {
     data?.draggedDocuments.map((doc, i) => {
         let dbox = doc;
         // bcz: isButtonBar is intended to allow a collection of linear buttons to be dropped and nested into another collection of buttons... it's not being used yet, and isn't very elegant
-        if (doc.type === DocumentType.FONTICON) {
+        if (doc.type === DocumentType.FONTICON || StrCast(Doc.Layout(doc).layout).includes("FontIconBox")) {
             dbox = Doc.MakeAlias(doc);
         } else if (!doc.onDragStart && !doc.isButtonBar) {
             const layoutDoc = doc.layout instanceof Doc && doc.layout.isTemplateForField ? doc.layout : doc;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 81f2feee0..ae3f05fb7 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -8,7 +8,7 @@ import {
     faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faTimesCircle,
     faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faArrowsAlt, faQuoteLeft, faSortAmountDown, faAlignLeft, faAlignCenter, faAlignRight,
     faHeading, faRulerCombined, faFillDrip, faLink, faUnlink, faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper,
-    faPaintRoller, faBars, faBrush, faShapes, faEllipsisH, faHandPaper, faMap
+    faPaintRoller, faBars, faBrush, faShapes, faEllipsisH, faHandPaper, faMap, faUser
 } from '@fortawesome/free-solid-svg-icons';
 import { ANTIMODEMENU_HEIGHT } from './globalCssVariables.scss';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
@@ -150,7 +150,7 @@ export class MainView extends React.Component {
             faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faTrashAlt, faAngleRight, faBell,
             faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faArrowsAlt, faQuoteLeft, faSortAmountDown, faAlignLeft, faAlignCenter, faAlignRight,
             faHeading, faRulerCombined, faFillDrip, faLink, faUnlink, faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper,
-            faPaintRoller, faBars, faBrush, faShapes, faEllipsisH, faHandPaper, faMap);
+            faPaintRoller, faBars, faBrush, faShapes, faEllipsisH, faHandPaper, faMap, faUser);
         this.initEventListeners();
         this.initAuthenticationRouters();
     }
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 992c1f600..81c349b26 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -2,7 +2,7 @@ import React = require("react");
 import { FontAwesomeIcon, FontAwesomeIconProps } from "@fortawesome/react-fontawesome";
 import { action, computed, observable, reaction, runInAction, Lambda } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, DocListCast, Opt } from "../../../fields/Doc";
+import { Doc, DocListCast, Opt, Field } from "../../../fields/Doc";
 import { BoolCast, Cast, StrCast, NumCast } from "../../../fields/Types";
 import AntimodeMenu from "../AntimodeMenu";
 import "./CollectionMenu.scss";
@@ -24,6 +24,7 @@ import { Document } from "../../../fields/documentSchemas";
 import { SelectionManager } from "../../util/SelectionManager";
 import { DocumentView } from "../nodes/DocumentView";
 import { ColorState } from "react-color";
+import { ObjectField } from "../../../fields/ObjectField";
 
 @observer
 export default class CollectionMenu extends AntimodeMenu {
@@ -72,42 +73,48 @@ 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.childLayoutTemplate = getDocTemplate(this.source?.[0])",
+        script: "self.target.childLayoutTemplate = getDocTemplate(self.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",
-        script: "this.target.childClickedOpenTemplateView = getDocTemplate(this.source?.[0])",
+        script: "self.target.childClickedOpenTemplateView = getDocTemplate(self.source?.[0])",
         immediate: undoBatch((source: Doc[]) => source.length && (this.target.childClickedOpenTemplateView = Doc.getDocTemplate(source?.[0]))),
         initialize: emptyFunction,
     };
     _contentCommand = {
         params: ["target", "source"], title: "clear content",
-        script: "getProto(this.target).data = copyField(this.source);",
+        script: "getProto(self.target).data = copyField(self.source);",
         immediate: undoBatch((source: Doc[]) => Doc.GetProto(this.target).data = new List<Doc>(source)), // Doc.aliasDocs(source),
         initialize: emptyFunction,
     };
     _viewCommand = {
         params: ["target"], title: "bookmark view",
-        script: "this.target._panX = this['target-panX']; this.target._panY = this['target-panY']; this.target._viewScale = this['target-viewScale'];",
+        script: "self.target._panX = self['target-panX']; self.target._panY = self['target-panY']; self.target._viewScale = self['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",
-        script: "this.target._fitToBox = !this.target._fitToBox;",
+        script: "self.target._fitToBox = !self.target._fitToBox;",
         immediate: undoBatch((source: Doc[]) => this.target._fitToBox = !this.target._fitToBox),
         initialize: emptyFunction
     };
     _fitContentCommand = {
         params: ["target"], title: "toggle clusters",
-        script: "this.target.useClusters = !this.target.useClusters;",
+        script: "self.target.useClusters = !self.target.useClusters;",
         immediate: undoBatch((source: Doc[]) => this.target.useClusters = !this.target.useClusters),
         initialize: emptyFunction
     };
+    _saveFilterCommand = {
+        params: ["target"], title: "save filter",
+        script: "self.target._docFilters = copyField(self['target-docFilters']);",
+        immediate: undoBatch((source: Doc[]) => this.target._docFilters = undefined),
+        initialize: (button: Doc) => { button['target-docFilters'] = this.target._docFilters instanceof ObjectField && ObjectField.MakeCopy(this.target._docFilters as any as ObjectField); },
+    };
 
-    _freeform_commands = [this._viewCommand, this._fitContentCommand, this._clusterCommand, this._contentCommand, this._templateCommand, this._narrativeCommand];
+    _freeform_commands = [this._viewCommand, this._saveFilterCommand, this._fitContentCommand, this._clusterCommand, this._contentCommand, this._templateCommand, this._narrativeCommand];
     _stacking_commands = [this._contentCommand, this._templateCommand];
     _masonry_commands = [this._contentCommand, this._templateCommand];
     _schema_commands = [this._templateCommand, this._narrativeCommand];
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 8cadafccd..df4576243 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -294,7 +294,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             let stopPropagate = true;
             let preventDefault = true;
             !this.props.Document.isBackground && this.props.bringToFront(this.props.Document);
-            if (this._doubleTap && this.props.renderDepth && !this.onClickHandler?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click
+            if (this._doubleTap && this.props.renderDepth) {// && !this.onClickHandler?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click
                 if (!(e.nativeEvent as any).formattedHandled) {
                     if (this.onDoubleClickHandler?.script && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes("ScriptingBox")) { // bcz: hack? don't execute script if you're clicking on a scripting box itself
                         const func = () => this.onDoubleClickHandler.script.run({
diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx
index ab34e13b0..2611d2ca7 100644
--- a/src/client/views/nodes/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox.tsx
@@ -67,7 +67,7 @@ export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>(
                 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" />
+            <FontAwesomeIcon className="fontIconBox-icon" icon={StrCast(this.dataDoc.icon, "user") as any} color={StrCast(this.layoutDoc.color, this._foregroundColor)} size="sm" />
             {!this.rootDoc.title ? (null) : <div className="fontIconBox-label" style={{ width: this.rootDoc.label ? "max-content" : undefined }}> {StrCast(this.rootDoc.label, StrCast(this.rootDoc.title).substring(0, 6))} </div>}
         </button>;
         return !this.layoutDoc.toolTip ? button :
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 966aaa895..6b6fc5da2 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1401,12 +1401,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                     onDoubleClick={this.onDoubleClick}
                 >
                     <div className={`formattedTextBox-outer`} ref={this._scrollRef}
-                        style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, pointerEvents: !this.props.isSelected() ? "none" : undefined }}
+                        style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, pointerEvents: !this.props.active() ? "none" : undefined }}
                         onScroll={this.onscrolled} onDrop={this.ondrop} >
                         <div className={`formattedTextBox-inner${rounded}${selclass}`} ref={this.createDropTarget}
                             style={{
                                 padding: this.layoutDoc._textBoxPadding ? StrCast(this.layoutDoc._textBoxPadding) : `${Math.max(0, NumCast(this.layoutDoc._yMargin, this.props.yMargin || 0) + selPad)}px  ${NumCast(this.layoutDoc._xMargin, this.props.xMargin || 0) + selPad}px`,
-                                pointerEvents: !this.props.isSelected() ? ((this.layoutDoc.isLinkButton || this.props.onClick) ? "none" : undefined) : undefined
+                                pointerEvents: !this.props.active() ? ((this.layoutDoc.isLinkButton || this.props.onClick) ? "none" : undefined) : undefined
                             }}
                         />
                     </div>
diff --git a/src/fields/util.ts b/src/fields/util.ts
index e0b1843a1..47da7d7bf 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -77,10 +77,10 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
 
     if (writeToDoc) {
         if (value === undefined) {
-            delete target.__fieldKeys[prop];
+            target.__fieldKeys && (delete target.__fieldKeys[prop]);
             delete target.__fields[prop];
         } else {
-            target.__fieldKeys[prop] = true;
+            target.__fieldKeys && (target.__fieldKeys[prop] = true);
             target.__fields[prop] = value;
         }
         //if (typeof value === "object" && !(value instanceof ObjectField)) debugger;
-- 
cgit v1.2.3-70-g09d2


From ec3a3adb0f9c6706e963432170d59bf54f95702c Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 28 Jul 2020 01:05:19 -0400
Subject: chaned # text syntax to create a field startin with #. chaned pivot
 views to aggreate #keys as if the value were the key after the #

---
 src/client/documents/Documents.ts                        |  3 ---
 .../CollectionFreeFormLayoutEngines.tsx                  | 16 ++++++++--------
 src/client/views/nodes/formattedText/DashFieldView.tsx   |  4 +---
 .../views/nodes/formattedText/FormattedTextBox.tsx       |  2 +-
 src/client/views/nodes/formattedText/RichTextRules.ts    |  7 +++----
 5 files changed, 13 insertions(+), 19 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index cd2792226..a1c66c2fe 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -926,9 +926,6 @@ export namespace DocUtils {
         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');
 
-        Doc.GetProto(source.doc).links = ComputedField.MakeFunction("links(self)");
-        Doc.GetProto(target.doc).links = ComputedField.MakeFunction("links(self)");
-
         return linkDoc;
     }
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index a4fd5384f..183ed3119 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -142,9 +142,16 @@ export function computePivotLayout(
     const fieldKey = "data";
     const pivotColumnGroups = new Map<FieldResult<Field>, PivotColumn>();
 
+    let nonNumbers = 0;
     const pivotFieldKey = toLabel(pivotDoc._pivotField);
     childPairs.map(pair => {
-        const lval = Cast(pair.layout[pivotFieldKey], listSpec("string"), null);
+        const lval = pivotFieldKey === "#" ? Array.from(Object.keys(Doc.GetProto(pair.layout))).filter(k => k.startsWith("#")).map(k => k.substring(1)) :
+            Cast(pair.layout[pivotFieldKey], listSpec("string"), null);
+
+        const num = toNumber(pair.layout[pivotFieldKey]);
+        if (num === undefined || Number.isNaN(num)) {
+            nonNumbers++;
+        }
         const val = Field.toString(pair.layout[pivotFieldKey] as Field);
         if (lval) {
             lval.forEach((val, i) => {
@@ -168,13 +175,6 @@ export function computePivotLayout(
             });
         }
     });
-    let nonNumbers = 0;
-    childPairs.map(pair => {
-        const num = toNumber(pair.layout[pivotFieldKey]);
-        if (num === undefined || Number.isNaN(num)) {
-            nonNumbers++;
-        }
-    });
     const pivotNumbers = nonNumbers / childPairs.length < .1;
     if (pivotColumnGroups.size > 10) {
         const arrayofKeys = Array.from(pivotColumnGroups.keys());
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index 8718bf329..958a37568 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -205,9 +205,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
                     {this._fieldKey}
                 </span>}
 
-            {/* <div className="dashFieldView-fieldSpan"> */}
-            {this.fieldValueContent}
-            {/* </div> */}
+            {this.props.fieldKey.startsWith("#") ? (null) : this.fieldValueContent}
 
             {!this._showEnumerables ? (null) : <div className="dashFieldView-enumerables" onPointerDown={this.onPointerDownEnumerables} />}
 
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 6b6fc5da2..c1a50268a 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -994,7 +994,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                 clipboardTextSerializer: this.clipboardTextSerializer,
                 handlePaste: this.handlePaste,
             });
-            !Doc.UserDoc().noviceMode && applyDevTools.applyDevTools(this._editorView);
+            // !Doc.UserDoc().noviceMode && applyDevTools.applyDevTools(this._editorView);
             const startupText = !rtfField && this._editorView && Field.toString(this.dataDoc[fieldKey] as Field);
             if (startupText) {
                 const { state: { tr }, dispatch } = this._editorView;
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index ef0fead4a..dc1d8a2c8 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -317,13 +317,12 @@ export class RichTextRules {
 
             // create an inline view of a tag stored under the '#' field
             new InputRule(
-                new RegExp(/#([a-zA-Z_\-]+[a-zA-Z_;\-0-9]*)\s$/),
+                new RegExp(/#([a-zA-Z_\-]+[a-zA-Z_\-0-9]*)\s$/),
                 (state, match, start, end) => {
                     const tag = match[1];
                     if (!tag) return state.tr;
-                    const multiple = tag.split(";");
-                    this.Document[DataSym]["#"] = multiple.length > 1 ? new List(multiple) : tag;
-                    const fieldView = state.schema.nodes.dashField.create({ fieldKey: "#" });
+                    this.Document[DataSym]["#" + tag] = ".";
+                    const fieldView = state.schema.nodes.dashField.create({ fieldKey: "#" + tag });
                     return state.tr.deleteRange(start, end).insert(start, fieldView);
                 }),
 
-- 
cgit v1.2.3-70-g09d2


From f56e30a222b808801923354417a5fe500d4d91d3 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 28 Jul 2020 15:16:18 -0400
Subject: cleaned up formattedTextBox storedmarks abit

---
 src/client/views/nodes/formattedText/FormattedTextBox.tsx | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

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

diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index c1a50268a..627c6e363 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -223,7 +223,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             }
             const state = this._editorView.state.apply(tx);
             this._editorView.updateState(state);
-            (tx.storedMarks && !this._editorView.state.storedMarks) && (this._editorView.state.storedMarks = tx.storedMarks);
 
             const tsel = this._editorView.state.selection.$from;
             tsel.marks().filter(m => m.type === this._editorView!.state.schema.marks.user_mark).map(m => AudioBox.SetScrubTime(Math.max(0, m.attrs.modified * 1000)));
@@ -741,7 +740,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
 
         this._disposers.search = reaction(() => this.rootDoc.searchMatch,
             search => search ? this.highlightSearchTerms([Doc.SearchQuery()]) : this.unhighlightSearchTerms(),
-            { fireImmediately: true });
+            { fireImmediately: this.rootDoc.searchMatch ? true : false });
 
         this._disposers.record = reaction(() => this._recording,
             () => {
@@ -1011,10 +1010,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             FormattedTextBox.SelectOnLoadChar = "";
 
         }
-        (selectOnLoad /* || !rtfField?.Text*/) && this._editorView!.focus();
+        selectOnLoad && this._editorView!.focus();
         // add user mark for any first character that was typed since the user mark that gets set in KeyPress won't have been called yet.
-        if (!this._editorView!.state.storedMarks || !this._editorView!.state.storedMarks.some(mark => mark.type === schema.marks.user_mark)) {
-            this._editorView!.state.storedMarks = [...(this._editorView!.state.storedMarks ? this._editorView!.state.storedMarks : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })];
+        if (!this._editorView!.state.storedMarks?.some(mark => mark.type === schema.marks.user_mark)) {
+            this._editorView!.state.storedMarks = [...(this._editorView!.state.storedMarks ?? []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })];
         }
     }
     getFont(font: string) {
-- 
cgit v1.2.3-70-g09d2

