From dd9eda99662d1bfb921a8f1589c5f77ac9321172 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sun, 21 Jun 2020 03:00:43 -0400
Subject: cleaned up lists in prosemirror.  everything's an ordered_list now
 (even unordred lists).   things are read back from the dom correctly to allow
 copy & paste.  added prosemirror's dev tools.

---
 .../views/nodes/formattedText/FormattedTextBox.tsx | 52 +++++++++++-----------
 1 file changed, 26 insertions(+), 26 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 c23ecb8ac..82334688b 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -15,6 +15,7 @@ import { EditorView } from "prosemirror-view";
 import { DateField } from '../../../../fields/DateField';
 import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym, AclSym } from "../../../../fields/Doc";
 import { documentSchema } from '../../../../fields/documentSchemas';
+import applyDevTools = require("prosemirror-dev-tools");
 import { Id } from '../../../../fields/FieldSymbols';
 import { InkTool } from '../../../../fields/InkField';
 import { PrefetchProxy } from '../../../../fields/Proxy';
@@ -57,6 +58,7 @@ import { FieldView, FieldViewProps } from "../FieldView";
 import "./FormattedTextBox.scss";
 import { FormattedTextBoxComment, formattedTextBoxCommentPlugin } from './FormattedTextBoxComment';
 import React = require("react");
+import requestPromise = require('request-promise');
 
 library.add(faEdit);
 library.add(faSmile, faTextHeight, faUpload);
@@ -488,6 +490,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         });
         changeItems.push({ description: "FreeForm", event: () => DocUtils.makeCustomViewClicked(this.rootDoc, Docs.Create.FreeformDocument, "freeform"), icon: "eye" });
         !change && cm.addItem({ description: "Change Perspective...", subitems: changeItems, icon: "external-link-alt" });
+        this._downX = this._downY = Number.NaN;
     }
 
     recordDictation = () => {
@@ -920,6 +923,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                 clipboardTextSerializer: this.clipboardTextSerializer,
                 handlePaste: this.handlePaste,
             });
+            // applyDevTools.applyDevTools(this._editorView);
             const startupText = !rtfField && this._editorView && Field.toString(this.dataDoc[fieldKey] as Field);
             if (startupText) {
                 const { state: { tr }, dispatch } = this._editorView;
@@ -1008,7 +1012,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         if (e.buttons === 1 && this.props.isSelected(true) && !e.altKey) {
             e.stopPropagation();
         }
-        this._downX = this._downY = Number.NaN;
     }
 
     @action
@@ -1073,38 +1076,35 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     }
 
     // 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, select: boolean, highlightOnly: boolean) {
+    hitBulletTargets(x: number, y: number, collapse: boolean, highlightOnly: boolean) {
         clearStyleSheetRules(FormattedTextBox._bulletStyleSheet);
-        const pos = this._editorView!.posAtCoords({ left: x, top: y });
-        if (pos && this.props.isSelected(true)) {
-            // let beforeEle = document.querySelector("." + hit.className) as Element; // const before = hit ? window.getComputedStyle(hit, ':before') : undefined;
-            //const node = this._editorView!.state.doc.nodeAt(pos.pos);
-            const $pos = this._editorView!.state.doc.resolve(pos.pos);
-            let list_node = $pos.node().type === schema.nodes.list_item ? $pos.node() : undefined;
-            if ($pos.node().type === schema.nodes.ordered_list) {
-                for (let off = 1; off < 100; off++) {
-                    const pos = this._editorView!.posAtCoords({ left: x + off, top: y });
-                    const node = pos && this._editorView!.state.doc.nodeAt(pos.pos);
-                    if (node?.type === schema.nodes.list_item) {
-                        list_node = node;
-                        break;
-                    }
+        const clickPos = this._editorView!.posAtCoords({ left: x, top: y });
+        let olistPos = clickPos?.pos;
+        if (clickPos && olistPos && this.props.isSelected(true)) {
+            const clickNode = this._editorView?.state.doc.nodeAt(olistPos);
+            let nodeBef = this._editorView?.state.doc.nodeAt(Math.max(0, olistPos - 1));
+            olistPos = nodeBef?.type === this._editorView?.state.schema.nodes.ordered_list ? olistPos - 1 : olistPos;
+            let $olistPos = this._editorView?.state.doc.resolve(olistPos);
+            let olistNode = (nodeBef !== null || clickNode?.type === this._editorView?.state.schema.nodes.list_item) && olistPos === clickPos?.pos ? clickNode : nodeBef;
+            if (olistNode?.type === this._editorView?.state.schema.nodes.list_item) {
+                if ($olistPos && ($olistPos as any).path.length > 3) {
+                    olistNode = $olistPos.parent;
+                    $olistPos = this._editorView?.state.doc.resolve(($olistPos as any).path[($olistPos as any).path.length - 4]);
                 }
             }
-            if (list_node && pos.inside >= 0 && this._editorView!.state.doc.nodeAt(pos.inside)!.attrs.bulletStyle === list_node.attrs.bulletStyle) {
-                if (select) {
-                    const $olist_pos = this._editorView!.state.doc.resolve($pos.pos - $pos.parentOffset - 1);
+            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($olist_pos)));
+                        this._editorView!.dispatch(this._editorView!.state.tr.setSelection(new NodeSelection($olistPos!)));
                     }
-                    addStyleSheetRule(FormattedTextBox._bulletStyleSheet, list_node.attrs.mapStyle + list_node.attrs.bulletStyle + ":hover:before", { background: "lightgray" });
-                } else if (Math.abs(pos.pos - pos.inside) < 2) {
+                    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) {
-                        const offset = this._editorView!.state.doc.nodeAt(pos.inside)?.type === schema.nodes.ordered_list ? 1 : 0;
-                        this._editorView!.dispatch(this._editorView!.state.tr.setNodeMarkup(pos.inside + offset, list_node.type, { ...list_node.attrs, visibility: !list_node.attrs.visibility }));
-                        this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, pos.inside + offset)));
+                        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, list_node.attrs.mapStyle + list_node.attrs.bulletStyle + ":hover:before", { background: "lightgray" });
+                    addStyleSheetRule(FormattedTextBox._bulletStyleSheet, olistNode.attrs.mapStyle + olistNode.attrs.bulletStyle + ":hover:before", { background: "lightgray" });
                 }
             }
         }
-- 
cgit v1.2.3-70-g09d2


From e81ad333750a86e4dae51907adc489a73d08437a Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 22 Jun 2020 11:19:00 -0400
Subject: cleaned up errors.  added ctrl-a to select documents within a
 freeform collection.  fixed ctrl-x to remove documents properly.  fixed paste
 documents to clone only if copied.

---
 src/client/views/DocumentDecorations.tsx           |  2 +-
 src/client/views/GlobalKeyHandler.ts               | 14 ++++++-----
 src/client/views/PreviewCursor.tsx                 | 24 ++++++++++---------
 .../CollectionFreeFormLinkView.tsx                 |  8 +++----
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  2 +-
 .../collections/collectionFreeForm/MarqueeView.tsx | 27 +++++++++++++++-------
 src/client/views/nodes/ImageBox.tsx                |  8 +++----
 src/client/views/nodes/KeyValueBox.tsx             |  2 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx | 12 +++++-----
 src/client/views/nodes/formattedText/nodes_rts.ts  |  2 +-
 10 files changed, 58 insertions(+), 43 deletions(-)

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

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index d92fe5203..beb6155ca 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -175,7 +175,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
     }
     @undoBatch
     @action
-    onCloseClick = async (e: React.MouseEvent) => {
+    onCloseClick = async (e: React.MouseEvent | undefined) => {
         if (!e?.button) {
             const recent = Cast(Doc.UserDoc().myRecentlyClosed, Doc) as Doc;
             const selected = SelectionManager.SelectedDocuments().slice();
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index c696625db..27755737e 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -253,8 +253,8 @@ export default class KeyManager {
             case "x":
                 if (SelectionManager.SelectedDocuments().length) {
                     const bds = DocumentDecorations.Instance.Bounds;
-                    const pt = [bds.x + (bds.r - bds.x) / 2, bds.y + (bds.b - bds.y) / 2];
-                    const text = `__DashDocId(${pt[0]},${pt[1]}):` + SelectionManager.SelectedDocuments().map(dv => dv.Document[Id]).join(":");
+                    const pt = SelectionManager.SelectedDocuments()[0].props.ScreenToLocalTransform().transformPoint(bds.x + (bds.r - bds.x) / 2, bds.y + (bds.b - bds.y) / 2);
+                    const text = `__DashDocId(${pt?.[0] || 0},${pt?.[1] || 0}):` + SelectionManager.SelectedDocuments().map(dv => dv.Document[Id]).join(":");
                     SelectionManager.SelectedDocuments().length && navigator.clipboard.writeText(text);
                     DocumentDecorations.Instance.onCloseClick(undefined);
                     stopPropagation = false;
@@ -265,7 +265,7 @@ export default class KeyManager {
                 if (DocumentDecorations.Instance.Bounds.r - DocumentDecorations.Instance.Bounds.x > 2) {
                     const bds = DocumentDecorations.Instance.Bounds;
                     const pt = SelectionManager.SelectedDocuments()[0].props.ScreenToLocalTransform().transformPoint(bds.x + (bds.r - bds.x) / 2, bds.y + (bds.b - bds.y) / 2);
-                    const text = `__DashDocId(${pt?.[0] || 0},${pt?.[1] || 0}):` + SelectionManager.SelectedDocuments().map(dv => dv.Document[Id]).join(":");
+                    const text = `__DashCloneId(${pt?.[0] || 0},${pt?.[1] || 0}):` + SelectionManager.SelectedDocuments().map(dv => dv.Document[Id]).join(":");
                     SelectionManager.SelectedDocuments().length && navigator.clipboard.writeText(text);
                     stopPropagation = false;
                 }
@@ -280,10 +280,12 @@ export default class KeyManager {
     });
 
     public paste(e: ClipboardEvent) {
-        if (e.clipboardData?.getData("text/plain") !== "" && e.clipboardData?.getData("text/plain").startsWith("__DashDocId(")) {
+        const plain = e.clipboardData?.getData("text/plain");
+        const clone = plain?.startsWith("__DashCloneId(");
+        if (plain && (plain.startsWith("__DashDocId(") || clone)) {
             const first = SelectionManager.SelectedDocuments().length ? SelectionManager.SelectedDocuments()[0] : undefined;
             if (first?.props.Document.type === DocumentType.COL) {
-                const docids = e.clipboardData.getData("text/plain").split(":");
+                const docids = plain.split(":");
                 let count = 1;
                 const list: Doc[] = [];
                 const targetDataDoc = Doc.GetProto(first.props.Document);
@@ -295,7 +297,7 @@ export default class KeyManager {
                         list.push(doc);
                     }
                     if (count === docids.length) {
-                        const added = list.filter(d => !docList.includes(d));
+                        const added = list.filter(d => !docList.includes(d)).map(d => clone ? Doc.MakeClone(d) : d);
                         if (added.length) {
                             added.map(doc => doc.context = targetDataDoc);
                             undoBatch(() => {
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index dd65681d4..e27f6b95a 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -32,10 +32,11 @@ export class PreviewCursor extends React.Component<{}> {
 
             // tests for URL and makes web document
             const re: any = /^https?:\/\//g;
-            if (e.clipboardData.getData("text/plain") !== "") {
+            const plain = e.clipboardData.getData("text/plain");
+            if (plain) {
                 // tests for youtube and makes video document
-                if (e.clipboardData.getData("text/plain").indexOf("www.youtube.com/watch") !== -1) {
-                    const url = e.clipboardData.getData("text/plain").replace("youtube.com/watch?v=", "youtube.com/embed/");
+                if (plain.indexOf("www.youtube.com/watch") !== -1) {
+                    const url = plain.replace("youtube.com/watch?v=", "youtube.com/embed/");
                     undoBatch(() => PreviewCursor._addDocument(Docs.Create.VideoDocument(url, {
                         title: url, _width: 400, _height: 315,
                         _nativeWidth: 600, _nativeHeight: 472.5,
@@ -43,8 +44,8 @@ export class PreviewCursor extends React.Component<{}> {
                     })))();
                 }
 
-                else if (re.test(e.clipboardData.getData("text/plain"))) {
-                    const url = e.clipboardData.getData("text/plain");
+                else if (re.test(plain)) {
+                    const url = plain;
                     undoBatch(() => PreviewCursor._addDocument(Docs.Create.WebDocument(url, {
                         title: url, _width: 500, _height: 300, UseCors: true,
                         // nativeWidth: 300, nativeHeight: 472.5,
@@ -52,10 +53,11 @@ export class PreviewCursor extends React.Component<{}> {
                     })))();
                 }
 
-                else if (e.clipboardData.getData("text/plain").startsWith("__DashDocId(")) {
-                    const docids = e.clipboardData.getData("text/plain").split(":");
+                else if (plain.startsWith("__DashDocId(") || plain.startsWith("__DashCloneId(")) {
+                    const clone = plain.startsWith("__DashCloneId(");
+                    const docids = plain.split(":");
                     const strs = docids[0].split(",");
-                    const ptx = Number(strs[0].substring("__DashDocId(".length));
+                    const ptx = Number(strs[0].substring((clone ? "__DashCloneId(" : "__DashDocId(").length));
                     const pty = Number(strs[1].substring(0, strs[1].length - 1));
                     let count = 1;
                     const list: Doc[] = [];
@@ -65,7 +67,7 @@ export class PreviewCursor extends React.Component<{}> {
                         count++;
                         if (doc instanceof Doc) {
                             i === 1 && (first = doc);
-                            const alias = Doc.MakeClone(doc);
+                            const alias = clone ? Doc.MakeClone(doc) : doc;
                             const deltaX = NumCast(doc.x) - NumCast(first!.x) - ptx;
                             const deltaY = NumCast(doc.y) - NumCast(first!.y) - pty;
                             alias.x = newPoint[0] + deltaX;
@@ -115,9 +117,9 @@ export class PreviewCursor extends React.Component<{}> {
             (e.keyCode < 112 || e.keyCode > 123) && // F1 thru F12 keys
             !e.key.startsWith("Arrow") &&
             !e.defaultPrevented) {
-            if ((!e.ctrlKey || (e.keyCode >= 48 && e.keyCode <= 57)) && !e.metaKey) {//  /^[a-zA-Z0-9$*^%#@+-=_|}{[]"':;?/><.,}]$/.test(e.key)) {
+            if ((!e.metaKey && !e.ctrlKey) || (e.keyCode >= 48 && e.keyCode <= 57) || (e.keyCode >= 65 && e.keyCode <= 90)) {//  /^[a-zA-Z0-9$*^%#@+-=_|}{[]"':;?/><.,}]$/.test(e.key)) {
                 PreviewCursor.Visible && PreviewCursor._onKeyPress?.(e);
-                PreviewCursor.Visible = false;
+                ((!e.ctrlKey && !e.metaKey) || e.key !== "v") && (PreviewCursor.Visible = false);
             }
         } else if (PreviewCursor.Visible) {
             if (e.key === "ArrowRight") {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index 859bbc8f8..a24693c30 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -63,8 +63,8 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
                         (this.props.A.rootDoc[(this.props.A.props as any).fieldKey] as Doc);
                         const m = targetBhyperlink.getBoundingClientRect();
                         const mp = this.props.A.props.ScreenToLocalTransform().transformPoint(m.right, m.top + 5);
-                        this.props.A.rootDoc[afield + "_x"] = Math.min(1, mp![0] / this.props.A.props.PanelWidth()) * 100;
-                        this.props.A.rootDoc[afield + "_y"] = Math.min(1, mp![1] / this.props.A.props.PanelHeight()) * 100;
+                        this.props.A.rootDoc[afield + "_x"] = Math.min(1, mp[0] / this.props.A.props.PanelWidth()) * 100;
+                        this.props.A.rootDoc[afield + "_y"] = Math.min(1, mp[1] / this.props.A.props.PanelHeight()) * 100;
                     }, 0);
                 }
                 if (!targetAhyperlink) {
@@ -75,8 +75,8 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
                         (this.props.B.rootDoc[(this.props.B.props as any).fieldKey] as Doc);
                         const m = targetAhyperlink.getBoundingClientRect();
                         const mp = this.props.B.props.ScreenToLocalTransform().transformPoint(m.right, m.top + 5);
-                        this.props.B.rootDoc[bfield + "_x"] = Math.min(1, mp![0] / this.props.B.props.PanelWidth()) * 100;
-                        this.props.B.rootDoc[bfield + "_y"] = Math.min(1, mp![1] / this.props.B.props.PanelHeight()) * 100;
+                        this.props.B.rootDoc[bfield + "_x"] = Math.min(1, mp[0] / this.props.B.props.PanelWidth()) * 100;
+                        this.props.B.rootDoc[bfield + "_y"] = Math.min(1, mp[1] / this.props.B.props.PanelHeight()) * 100;
                     }, 0);
                 }
             })
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index d9d5c1bb3..bf425f654 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -45,7 +45,7 @@ import React = require("react");
 import { CollectionViewType } from "../CollectionView";
 import { Timeline } from "../../animationtimeline/Timeline";
 import { SnappingManager } from "../../../util/SnappingManager";
-import { InkingStroke, ActiveInkColor, ActiveInkWidth, ActiveInkBezierApprox } from "../../InkingStroke";
+import { ActiveInkColor, ActiveInkWidth, ActiveInkBezierApprox } from "../../InkingStroke";
 
 library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload);
 
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 5f09fa0ee..23efeec8d 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 } from "../../../../fields/Doc";
+import { Doc, Opt, DocListCast, DataSym } from "../../../../fields/Doc";
 import { InkData, InkField, InkTool } from "../../../../fields/InkField";
 import { List } from "../../../../fields/List";
 import { RichTextField } from "../../../../fields/RichTextField";
@@ -20,12 +20,14 @@ import { CollectionView } from "../CollectionView";
 import MarqueeOptionsMenu from "./MarqueeOptionsMenu";
 import "./MarqueeView.scss";
 import React = require("react");
+import { DateField } from "../../../../fields/DateField";
+import { DocServer } from "../../../DocServer";
 
 interface MarqueeViewProps {
     getContainerTransform: () => Transform;
     getTransform: () => Transform;
     activeDocuments: () => Doc[];
-    selectDocuments: (docs: Doc[], ink: { Document: Doc, Ink: Map<any, any> }[]) => void;
+    selectDocuments: (docs: Doc[]) => void;
     addLiveTextDocument: (doc: Doc) => void;
     isSelected: () => boolean;
     nudge: (x: number, y: number) => boolean;
@@ -80,11 +82,17 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
             });
 
             ContextMenu.Instance.displayMenu(this._downX, this._downY);
+            e.stopPropagation();
         } else
             if (e.key === ":") {
                 DocUtils.addDocumentCreatorMenuItems(this.props.addLiveTextDocument, this.props.addDocument, x, y);
 
                 ContextMenu.Instance.displayMenu(this._downX, this._downY);
+                e.stopPropagation();
+            } else if (e.key === "a" && (e.ctrlKey || e.metaKey)) {
+                e.preventDefault();
+                this.props.selectDocuments(this.props.activeDocuments());
+                e.stopPropagation();
             } else if (e.key === "q" && e.ctrlKey) {
                 e.preventDefault();
                 (async () => {
@@ -108,6 +116,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
                         y += 40 * this.props.getTransform().Scale;
                     });
                 })();
+                e.stopPropagation();
             } else if (e.key === "b" && e.ctrlKey) {
                 e.preventDefault();
                 navigator.clipboard.readText().then(text => {
@@ -118,7 +127,8 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
                         this.pasteTable(ns, x, y);
                     }
                 });
-            } else if (!e.ctrlKey) {
+                e.stopPropagation();
+            } 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),
@@ -132,8 +142,8 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
                     Doc.GetProto(tbox)[StrCast(tbox.layoutKey)] = template;
                 }
                 this.props.addLiveTextDocument(tbox);
+                e.stopPropagation();
             }
-        e.stopPropagation();
     }
     //heuristically converts pasted text into a table.
     // assumes each entry is separated by a tab
@@ -225,7 +235,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
             // let inkselect = this.ink ? this.marqueeInkSelect(this.ink.inkData) : new Map();
             // let inks = inkselect.size ? [{ Document: this.inkDoc, Ink: inkselect }] : [];
             const docs = mselect.length ? mselect : [this.props.Document];
-            this.props.selectDocuments(docs, []);
+            this.props.selectDocuments(docs);
         }
         const hideMarquee = () => {
             this.hideMarquee();
@@ -354,7 +364,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         selected.forEach(d => this.props.removeDocument(d));
         const newCollection = DocUtils.pileup(selected, this.Bounds.left + this.Bounds.width / 2, this.Bounds.top + this.Bounds.height / 2);
         this.props.addDocument(newCollection!);
-        this.props.selectDocuments([newCollection!], []);
+        this.props.selectDocuments([newCollection!]);
         MarqueeOptionsMenu.Instance.fadeOut(true);
         this.hideMarquee();
     }
@@ -379,7 +389,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         }
         const newCollection = this.getCollection(selected, (e as KeyboardEvent)?.key === "t" ? Docs.Create.StackingDocument : undefined);
         this.props.addDocument(newCollection);
-        this.props.selectDocuments([newCollection], []);
+        this.props.selectDocuments([newCollection]);
         MarqueeOptionsMenu.Instance.fadeOut(true);
         this.hideMarquee();
     }
@@ -492,7 +502,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         this.props.addDocument(newCollection);
         MarqueeOptionsMenu.Instance.fadeOut(true);
         this.hideMarquee();
-        setTimeout(() => this.props.selectDocuments([newCollection], []), 0);
+        setTimeout(() => this.props.selectDocuments([newCollection]), 0);
     }
 
     @undoBatch
@@ -731,6 +741,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         return <div className="marqueeView"
             style={{ overflow: StrCast(this.props.Document._overflow), cursor: MarqueeView.DragMarquee && this ? "crosshair" : "hand" }}
             onDragOver={e => e.preventDefault()}
+            onPaste={this.paste}
             onScroll={(e) => e.currentTarget.scrollTop = e.currentTarget.scrollLeft = 0} onClick={this.onClick} onPointerDown={this.onPointerDown}>
             {this._visible ? this.marqueeDiv : null}
             {this.props.children}
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index f0818c7b4..c1c6f6baf 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -397,15 +397,15 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
         const shift = (rotation % 180) ? (nativeHeight - nativeWidth) * (1 - 1 / aspect) : 0;
         this.resize(srcpath);
         let transformOrigin = "center center";
-        let transform = `translate(0%, 0%) rotate(${rotation}deg) scale(${aspect})`
+        let transform = `translate(0%, 0%) rotate(${rotation}deg) scale(${aspect})`;
         if (rotation === 90 || rotation === -270) {
             transformOrigin = "top left";
-            transform = `translate(100%, 0%) rotate(${rotation}deg) scale(${aspect})`
+            transform = `translate(100%, 0%) rotate(${rotation}deg) scale(${aspect})`;
         } else if (rotation === 180) {
-            transform = `rotate(${rotation}deg) scale(${aspect})`
+            transform = `rotate(${rotation}deg) scale(${aspect})`;
         } else if (rotation === 270 || rotation === -90) {
             transformOrigin = "right top";
-            transform = `translate(-100%, 0%) rotate(${rotation}deg) scale(${aspect})`
+            transform = `translate(-100%, 0%) rotate(${rotation}deg) scale(${aspect})`;
         }
 
         return <div className="imageBox-cont" key={this.layoutDoc[Id]} ref={this.createDropTarget}>
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 4442ee2eb..d375466c9 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -147,7 +147,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
             <td className="keyValueBox-td-value" onClick={(e) => { this._valInput.current!.select(); e.stopPropagation(); }} style={{ width: `${this.splitPercentage}%` }}>
                 <input style={{ width: "100%" }} ref={this._valInput} type="text" placeholder="Value" onKeyDown={this.onEnterKey} />
             </td>
-        </tr>
+        </tr>;
     }
 
     @action
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 82334688b..f45a86b56 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1082,7 +1082,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         let olistPos = clickPos?.pos;
         if (clickPos && olistPos && this.props.isSelected(true)) {
             const clickNode = this._editorView?.state.doc.nodeAt(olistPos);
-            let nodeBef = this._editorView?.state.doc.nodeAt(Math.max(0, olistPos - 1));
+            const nodeBef = this._editorView?.state.doc.nodeAt(Math.max(0, olistPos - 1));
             olistPos = nodeBef?.type === this._editorView?.state.schema.nodes.ordered_list ? olistPos - 1 : olistPos;
             let $olistPos = this._editorView?.state.doc.resolve(olistPos);
             let olistNode = (nodeBef !== null || clickNode?.type === this._editorView?.state.schema.nodes.list_item) && olistPos === clickPos?.pos ? clickNode : nodeBef;
@@ -1092,17 +1092,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                     $olistPos = this._editorView?.state.doc.resolve(($olistPos as any).path[($olistPos as any).path.length - 4]);
                 }
             }
-            const listNode = this._editorView?.state.doc.nodeAt(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!)));
+                        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) {
-                        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!)));
+                        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" });
                 }
@@ -1232,7 +1232,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             setTimeout(() => FormattedTextBoxComment.Hide(), 0);
         }
         const selPad = this.props.isSelected() ? -10 : 0;
-        const selclass = this.props.isSelected() ? "-selected" : ""
+        const selclass = this.props.isSelected() ? "-selected" : "";
         return (
             <div className={"formattedTextBox-cont"} style={{
                 transform: `scale(${scale})`,
diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts
index b37e61d3f..33ef67ff5 100644
--- a/src/client/views/nodes/formattedText/nodes_rts.ts
+++ b/src/client/views/nodes/formattedText/nodes_rts.ts
@@ -240,7 +240,7 @@ export const nodes: { [index: string]: NodeSpec } = {
             },
             {
                 style: 'list-style-type=disc', getAttrs(dom: any) {
-                    return { mapStyle: "bullet" }
+                    return { mapStyle: "bullet" };
                 }
             },
             {
-- 
cgit v1.2.3-70-g09d2


From b4b1a14f442bdfd41f79c67a6545335e43f85f2f Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 22 Jun 2020 11:37:47 -0400
Subject: fixed onClick events for text.

---
 src/client/views/nodes/DocumentView.tsx                   | 8 --------
 src/client/views/nodes/formattedText/FormattedTextBox.tsx | 5 +----
 2 files changed, 1 insertion(+), 12 deletions(-)

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

diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 9c8c05176..71cb18e08 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -323,20 +323,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 const alias = Doc.MakeAlias(this.props.Document);
                 DocUtils.makeCustomViewClicked(alias, undefined, "onClick");
                 this.props.addDocTab(alias, "onRight");
-                // UndoManager.RunInBatch(() => Doc.makeCustomViewClicked(this.props.Document, undefined, "onClick"), "edit onClick");
-                //ScriptBox.EditButtonScript("On Button Clicked ...", this.props.Document, "onClick", e.clientX, e.clientY), "on button click");
             } else if (this.props.Document.links && this.Document.isLinkButton && !e.shiftKey && !e.ctrlKey) {
                 DocListCast(this.props.Document.links).length && this.followLinkClick(e.altKey, e.ctrlKey, e.shiftKey);
             } else {
                 if ((this.props.Document.onDragStart || (this.props.Document.rootDocument)) && !(e.ctrlKey || e.button > 0)) {  // onDragStart implies a button doc that we don't want to select when clicking.   RootDocument & isTEmplaetForField implies we're clicking on part of a template instance and we want to select the whole template, not the part
                     stopPropagate = false; // don't stop propagation for field templates -- want the selection to propagate up to the root document of the template
                 } else {
-                    // if (this.props.Document.type === DocumentType.RTF) {
-                    //     DocumentView._focusHack = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY) || [0, 0];
-                    //     DocumentView._focusHack = [DocumentView._focusHack[0] + NumCast(this.props.Document.x), DocumentView._focusHack[1] + NumCast(this.props.Document.y)];
-
-                    //     this.props.focus(this.props.Document, false);
-                    // }
                     SelectionManager.SelectDoc(this, e.ctrlKey || e.shiftKey);
                 }
                 preventDefault = false;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index f45a86b56..86757296e 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1066,12 +1066,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         if ((e.nativeEvent as any).formattedHandled) { e.stopPropagation(); return; }
         (e.nativeEvent as any).formattedHandled = true;
 
-        if (Math.abs(e.clientX - this._downX) < 4 && Math.abs(e.clientX - this._downX) < 4) {
-            this.props.select(e.ctrlKey);
-            this.hitBulletTargets(e.clientX, e.clientY, e.shiftKey, false);
-        }
         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);
         }
     }
 
-- 
cgit v1.2.3-70-g09d2


From 3a8f534a9bea20cedbb194d3cc9dceff6afcef37 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 22 Jun 2020 12:19:21 -0400
Subject: added a way to broadcast a message to all users (e.g., for restarting
 server)

---
 src/client/views/MainView.tsx                             | 5 +++++
 src/client/views/collections/CollectionTreeView.tsx       | 2 +-
 src/client/views/nodes/formattedText/FormattedTextBox.tsx | 5 ++++-
 3 files changed, 10 insertions(+), 2 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 fcaa349cd..d6c46e3b0 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -80,6 +80,11 @@ export class MainView extends React.Component {
     componentDidMount() {
         const tag = document.createElement('script');
 
+        const proto = DocServer.GetRefField("rtfProto").then(proto => {
+            (proto instanceof Doc) && reaction(() => StrCast(proto.BROADCAST_MESSAGE),
+                msg => msg && alert(msg));
+        });
+
         tag.src = "https://www.youtube.com/iframe_api";
         const firstScriptTag = document.getElementsByTagName('script')[0];
         firstScriptTag.parentNode!.insertBefore(tag, firstScriptTag);
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 747eb36a1..d3d1c8929 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -732,7 +732,7 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
             layoutItems.push({ description: (this.props.Document.treeViewHideTitle ? "Show" : "Hide") + " Title", event: () => this.props.Document.treeViewHideTitle = !this.props.Document.treeViewHideTitle, icon: "paint-brush" });
             ContextMenu.Instance.addItem({ description: "Options...", subitems: layoutItems, icon: "eye" });
         }
-        ContextMenu.Instance.addItem({
+        !Doc.UserDoc().noviceMode && ContextMenu.Instance.addItem({
             description: "Buxton Layout", icon: "eye", event: () => {
                 const { ImageDocument, PdfDocument } = Docs.Create;
                 const { Document } = this.props;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 86757296e..134fe6e62 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -58,7 +58,6 @@ import { FieldView, FieldViewProps } from "../FieldView";
 import "./FormattedTextBox.scss";
 import { FormattedTextBoxComment, formattedTextBoxCommentPlugin } from './FormattedTextBoxComment';
 import React = require("react");
-import requestPromise = require('request-promise');
 
 library.add(faEdit);
 library.add(faSmile, faTextHeight, faUpload);
@@ -456,6 +455,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         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"
+        });
 
         funcs.push({ description: "UI Controls...", subitems: uicontrols, icon: "asterisk" });
 
-- 
cgit v1.2.3-70-g09d2


From aba93a47815d424c9fcfc044259281f8facfe6b5 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 22 Jun 2020 14:13:20 -0400
Subject: fixed tree view to allow textdocument to be typed while title is
 changing.  fixed tryUpdateHeight so that text boxes don't get a scroll bar if
 they're autoHeight

---
 .../views/collections/CollectionTreeView.tsx       |  7 +++--
 .../views/nodes/formattedText/FormattedTextBox.tsx | 34 +++++++++++-----------
 2 files changed, 21 insertions(+), 20 deletions(-)

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

diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index d3d1c8929..68dc0ced2 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -312,8 +312,8 @@ class TreeView extends React.Component<TreeViewProps> {
 
     @computed get renderContent() {
         TraceMobx();
-        const expandKey = this.treeViewExpandedView === this.fieldKey ? this.fieldKey : this.treeViewExpandedView === "links" ? "links" : undefined;
-        if (expandKey !== undefined) {
+        const expandKey = this.treeViewExpandedView;
+        if (["links", this.fieldKey].includes(expandKey)) {
             const remDoc = (doc: Doc | Doc[]) => this.remove(doc, expandKey);
             const addDoc = (doc: Doc | Doc[], addBefore?: Doc, before?: boolean) =>
                 (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && Doc.AddDocToList(this.dataDoc, expandKey, doc, addBefore, before, false, true), true);
@@ -338,7 +338,7 @@ class TreeView extends React.Component<TreeViewProps> {
             const layoutDoc = Doc.Layout(this.props.document);
             const panelHeight = layoutDoc.type === DocumentType.RTF ? this.rtfHeight : this.docHeight;
             const panelWidth = layoutDoc.type === DocumentType.RTF ? this.rtfWidth : this.docWidth;
-            return <div ref={this._dref} style={{ display: "inline-block", height: panelHeight() }} key={this.props.document[Id] + this.props.document.title}>
+            return <div ref={this._dref} style={{ display: "inline-block", height: panelHeight() }} key={this.props.document[Id]}>
                 <ContentFittingDocumentView
                     Document={layoutDoc}
                     DataDoc={this.dataDoc}
@@ -794,6 +794,7 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
         console.log(e);
     }
     render() {
+        TraceMobx();
         if (!(this.props.Document instanceof Doc)) return (null);
         const dropAction = StrCast(this.props.Document.childDropAction) as dropActionType;
         const addDoc = (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => this.addDoc(doc, relativeTo, before);
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 134fe6e62..29018edda 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1201,18 +1201,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             const nh = this.layoutDoc.isTemplateForField ? 0 : NumCast(this.dataDoc._nativeHeight, 0);
             const dh = NumCast(this.rootDoc._height, 0);
             const newHeight = Math.max(10, (nh ? dh / nh * scrollHeight : scrollHeight) + (this.props.ChromeHeight ? this.props.ChromeHeight() : 0));
-            if (Math.abs(newHeight - dh) > 1) { // bcz: Argh!  without this, we get into a React crash if the same document is opened in a freeform view and in the treeview.  no idea why, but after dragging the freeform document, selecting it, and selecting text, it will compute to 1 pixel higher than the treeview which causes a cycle
-                if (this.rootDoc !== this.layoutDoc.doc && !this.layoutDoc.resolvedDataDoc) {
-                    // if we have a template that hasn't been resolved yet, we can't set the height or we'd be setting it on the unresolved template.  So set a timeout and hope its arrived...
-                    console.log("Delayed height adjustment...");
-                    setTimeout(() => {
-                        this.rootDoc._height = newHeight;
-                        this.dataDoc._nativeHeight = nh ? scrollHeight : undefined;
-                    }, 10);
-                } else {
+            if (this.rootDoc !== this.layoutDoc.doc && !this.layoutDoc.resolvedDataDoc) {
+                // if we have a template that hasn't been resolved yet, we can't set the height or we'd be setting it on the unresolved template.  So set a timeout and hope its arrived...
+                console.log("Delayed height adjustment...");
+                setTimeout(() => {
                     this.rootDoc._height = newHeight;
                     this.dataDoc._nativeHeight = nh ? scrollHeight : undefined;
-                }
+                }, 10);
+            } else {
+                this.rootDoc._height = newHeight;
+                this.dataDoc._nativeHeight = nh ? scrollHeight : undefined;
             }
         }
     }
@@ -1234,15 +1232,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         const selPad = this.props.isSelected() ? -10 : 0;
         const selclass = this.props.isSelected() ? "-selected" : "";
         return (
-            <div className={"formattedTextBox-cont"} style={{
-                transform: `scale(${scale})`,
-                transformOrigin: "top left",
-                width: `${100 / scale}%`,
-                height: `calc(${100 / scale}% - ${this.props.ChromeHeight?.() || 0}px)`,
-                ...this.styleFromLayoutString(scale)
-            }}>
+            <div className={"formattedTextBox-cont"}
+                style={{
+                    transform: `scale(${scale})`,
+                    transformOrigin: "top left",
+                    width: `${100 / scale}%`,
+                    height: `calc(${100 / scale}% - ${this.props.ChromeHeight?.() || 0}px)`,
+                    ...this.styleFromLayoutString(scale)
+                }}>
                 <div className={`formattedTextBox-cont`} ref={this._ref}
                     style={{
+                        overflow: this.layoutDoc._autoHeight ? "hidden" : undefined,
                         width: "100%",
                         height: this.props.height ? this.props.height : this.layoutDoc._autoHeight && this.props.renderDepth ? "max-content" : undefined,
                         background: Doc.UserDoc().renderStyle === "comic" ? "transparent" : this.props.background ? this.props.background : StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], this.props.hideOnLeave ? "rgba(0,0,0 ,0.4)" : ""),
-- 
cgit v1.2.3-70-g09d2


From d5b0995f823f8e125c967edbd474bb2c7f106168 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 22 Jun 2020 16:15:37 -0400
Subject: cleaned up menus for formatted text & tempaltes.  fixed pdf menu
 behavior (not fading until unselected);

---
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 24 +++---
 src/client/views/nodes/PDFBox.scss                 | 41 +++++-----
 src/client/views/nodes/PDFBox.tsx                  | 14 ++--
 .../views/nodes/formattedText/FormattedTextBox.tsx | 93 ++++++++++++----------
 src/client/views/pdf/PDFMenu.tsx                   |  1 +
 src/client/views/pdf/PDFViewer.tsx                 | 14 +++-
 6 files changed, 104 insertions(+), 83 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 bf425f654..960b6efef 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1203,20 +1203,22 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             }), icon: this._timelineVisible ? faEyeSlash : faEye
         });
 
-        const options = ContextMenu.Instance.findByDescription("Options...");
-        const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : [];
+        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" });
 
-        optionItems.push({ description: "reset view", event: () => { this.props.Document._panX = this.props.Document._panY = 0; this.props.Document[this.scaleFieldKey] = 1; }, icon: "compress-arrows-alt" });
+        const options = ContextMenu.Instance.findByDescription("Options...");
+        const optionItems = options && "subitems" in options ? options.subitems : [];
         optionItems.push({ description: "toggle snap line display", event: () => Doc.UserDoc().showSnapLines = !Doc.UserDoc().showSnapLines, icon: "compress-arrows-alt" });
-        optionItems.push({ description: "Reset default note style", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" });
-        optionItems.push({ description: (!this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" });
-        optionItems.push({ description: `${this.fitToContent ? "Unset" : "Set"} Fit To Container`, event: () => this.Document._fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" });
-        optionItems.push({ description: `${this.Document.useClusters ? "Uncluster" : "Use Clusters"}`, event: () => this.updateClusters(!this.Document.useClusters), icon: "braille" });
+        !Doc.UserDoc().noviceMode && optionItems.push({ description: (!this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" });
         this.props.ContainingCollectionView && optionItems.push({ description: "Promote Collection", event: this.promoteCollection, icon: "table" });
         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" });
-        // layoutItems.push({ description: "Analyze Strokes", event: this.analyzeStrokes, icon: "paint-brush" });
-        optionItems.push({
+        !Doc.UserDoc().noviceMode && optionItems.push({
             description: "Import document", icon: "upload", event: ({ x, y }) => {
                 const input = document.createElement("input");
                 input.type = "file";
@@ -1243,8 +1245,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                 input.click();
             }
         });
-        optionItems.push({ description: `${this.Document._LODdisable ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document._LODdisable = !this.Document._LODdisable, icon: "table" });
-        ContextMenu.Instance.addItem({ description: "Options...", subitems: optionItems, icon: "eye" });
+        !Doc.UserDoc().noviceMode && optionItems.push({ description: `${this.Document._LODdisable ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document._LODdisable = !this.Document._LODdisable, icon: "table" });
+        !options && ContextMenu.Instance.addItem({ description: "Options...", subitems: optionItems, icon: "eye" });
 
     }
     @observable _timelineVisible = false;
diff --git a/src/client/views/nodes/PDFBox.scss b/src/client/views/nodes/PDFBox.scss
index 6f18b1321..3e09fe519 100644
--- a/src/client/views/nodes/PDFBox.scss
+++ b/src/client/views/nodes/PDFBox.scss
@@ -15,6 +15,27 @@
          z-index: 1;
          pointer-events: none;
 
+         .pdfBox-pageNums {
+            display: flex;
+            flex-direction: row;
+            height: 25px;
+            position: absolute;
+            left: 5px;
+            top: 5px;
+            .pdfBox-overlayButton-fwd, 
+            .pdfBox-overlayButton-back {
+                background: #121721;
+                height: 25px;
+                width: 25px;
+                display: flex;
+                position: relative;
+                align-items: center;
+                justify-content: center;
+                border-radius: 3px;
+                pointer-events: all;
+            }
+         }
+
          .pdfBox-overlayButton {
              border-bottom-left-radius: 50%;
              display: flex;
@@ -47,26 +68,6 @@
                 pointer-events: all;
              }
         }
-        .pdfBox-overlayButton-fwd, 
-        .pdfBox-overlayButton-back {
-            background: #121721;
-            height: 25px;
-            width: 25px;
-            display: flex;
-            position: relative;
-            align-items: center;
-            justify-content: center;
-            border-radius: 3px;
-            pointer-events: all;
-            position: absolute;
-            top: 5;
-        }
-        .pdfBox-overlayButton-fwd {
-            left: 45;
-        }
-        .pdfBox-overlayButton-back {
-            left: 25;
-        }
 
          .pdfBox-nextIcon, 
          .pdfBox-prevIcon {
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 985fb4363..6b1c9fcde 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -147,6 +147,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum
             </button>
         </>;
         const searchTitle = `${!this._searching ? "Open" : "Close"} Search Bar`;
+        const curPage = this.Document.curPage || 1;
         return !this.active() ? (null) :
             (<div className="pdfBox-ui" onKeyDown={e => e.keyCode === KeyCodes.BACKSPACE || e.keyCode === KeyCodes.DELETE ? e.stopPropagation() : true}
                 onPointerDown={e => e.stopPropagation()} style={{ display: this.active() ? "flex" : "none" }}>
@@ -170,11 +171,14 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum
                     <div className="pdfBox-overlayButton-iconCont" onPointerDown={(e) => e.stopPropagation()}>
                         <FontAwesomeIcon style={{ color: "white" }} icon={this._searching ? "times" : "search"} size="lg" /></div>
                 </button>
-                <input value={`${(this.Document.curPage || 1)}`}
-                    onChange={e => this.gotoPage(Number(e.currentTarget.value))}
-                    style={{ left: 5, top: 5, height: "20px", width: "3ch", position: "absolute", pointerEvents: "all" }}
-                    onClick={action(() => this._pageControls = !this._pageControls)} />
-                {this._pageControls ? pageBtns : (null)}
+
+                <div className="pdfBox-pageNums">
+                    <input value={curPage}
+                        onChange={e => this.gotoPage(Number(e.currentTarget.value))}
+                        style={{ width: `${curPage > 99 ? 4 : 3}ch`, pointerEvents: "all" }}
+                        onClick={action(() => this._pageControls = !this._pageControls)} />
+                    {this._pageControls ? pageBtns : (null)}
+                </div>
                 <div className="pdfBox-settingsCont" key="settings" onPointerDown={(e) => e.stopPropagation()}>
                     <button className="pdfBox-settingsButton" onClick={action(() => this._flyout = !this._flyout)} title="Open Annotation Settings" >
                         <div className="pdfBox-settingsButton-arrow" style={{ transform: `scaleX(${this._flyout ? -1 : 1})` }} />
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 29018edda..5e0585169 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -422,35 +422,21 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     specificContextMenu = (e: React.MouseEvent): void => {
         const cm = ContextMenu.Instance;
 
-        const funcs: ContextMenuProps[] = [];
-        this.rootDoc.isTemplateDoc && funcs.push({ description: "Make Default Layout", event: async () => Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.rootDoc), icon: "eye" });
-        !this.layoutDoc.isTemplateDoc && funcs.push({
-            description: "Convert to use as a style", event: () => {
-                this.rootDoc.isTemplateDoc = makeTemplate(this.rootDoc);
-                Doc.AddDocToList(Cast(Doc.UserDoc()["template-notes"], Doc, null), "data", this.rootDoc);
-            }, icon: "eye"
-        });
-        this.layoutDoc.isTemplateDoc && funcs.push({
-            description: "Make New Template", event: () => {
-                const title = this.rootDoc.title as string;
-                this.rootDoc.layout = (this.layoutDoc as Doc).layout as string;
-                this.rootDoc.title = this.layoutDoc.isTemplateForField as string;
-                this.rootDoc.isTemplateDoc = false;
-                this.rootDoc.isTemplateForField = "";
-                this.rootDoc.layoutKey = "layout";
-                this.rootDoc.isTemplateDoc = makeTemplate(this.rootDoc, true, title);
-                setTimeout(() => {
-                    this.rootDoc._width = this.layoutDoc._width || 300;  // the width and height are stored on the template, since we're getting rid of the old template
-                    this.rootDoc._height = this.layoutDoc._height || 200;  // we need to copy them over to the root.  This should probably apply to all '_' fields
-                    this.rootDoc._backgroundColor = Cast(this.layoutDoc._backgroundColor, "string", null);
-                }, 10);
-                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: (!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 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 => {
+            changeItems.push({
+                description: StrCast(note.title), event: undoBatch(() => {
+                    Doc.setNativeView(this.rootDoc);
+                    DocUtils.makeCustomViewClicked(this.rootDoc, Docs.Create.TreeDocument, StrCast(note.title), note);
+                }), icon: "eye"
+            });
+        });
+        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" });
         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" });
@@ -460,7 +446,41 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                 proto instanceof Doc && (proto.BROADCAST_MESSAGE = Cast(this.rootDoc[this.fieldKey], RichTextField)?.Text)), icon: "expand-arrows-alt"
         });
 
-        funcs.push({ description: "UI Controls...", subitems: uicontrols, icon: "asterisk" });
+        appearanceItems.push({ description: "UI Controls...", 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({
+            description: "Convert to be a template style", event: () => {
+                if (!this.layoutDoc.isTemplateDoc) {
+                    const title = StrCast(this.rootDoc.title)
+                    this.rootDoc.title = "text";
+                    this.rootDoc.isTemplateDoc = makeTemplate(this.rootDoc, true, title);
+                } else {
+                    const title = StrCast(this.rootDoc.title);
+                    this.rootDoc.title = "text";
+                    this.rootDoc.layout = (this.layoutDoc as Doc).layout as string;
+                    this.rootDoc.title = this.layoutDoc.isTemplateForField as string;
+                    this.rootDoc.isTemplateDoc = false;
+                    this.rootDoc.isTemplateForField = "";
+                    this.rootDoc.layoutKey = "layout";
+                    this.rootDoc.isTemplateDoc = makeTemplate(this.rootDoc, true, title);
+                    setTimeout(() => {
+                        this.rootDoc._autoHeight = this.layoutDoc._autoHeight; // autoHeight, width and height
+                        this.rootDoc._width = this.layoutDoc._width || 300;  // are stored on the template, since we're getting rid of the old template
+                        this.rootDoc._height = this.layoutDoc._height || 200;  // we need to copy them over to the root.  This should probably apply to all '_' fields
+                        this.rootDoc._backgroundColor = Cast(this.layoutDoc._backgroundColor, "string", null);
+                    }, 10);
+                }
+                Doc.AddDocToList(Cast(Doc.UserDoc()["template-notes"], Doc, null), "data", this.rootDoc);
+            }, icon: "eye"
+        });
+        !appearance && ContextMenu.Instance.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "eye" });
+
+        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 =>
@@ -478,21 +498,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         funcs.push({ description: "highlighting...", subitems: highlighting, icon: "hand-point-right" });
 
         ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
-
-        const change = cm.findByDescription("Change Perspective...");
-        const changeItems: ContextMenuProps[] = change && "subitems" in change ? change.subitems : [];
-
-        const noteTypesDoc = Cast(Doc.UserDoc()["template-notes"], Doc, null);
-        DocListCast(noteTypesDoc?.data).forEach(note => {
-            changeItems.push({
-                description: StrCast(note.title), event: undoBatch(() => {
-                    Doc.setNativeView(this.rootDoc);
-                    DocUtils.makeCustomViewClicked(this.rootDoc, Docs.Create.TreeDocument, StrCast(note.title), note);
-                }), icon: "eye"
-            });
-        });
-        changeItems.push({ description: "FreeForm", event: () => DocUtils.makeCustomViewClicked(this.rootDoc, Docs.Create.FreeformDocument, "freeform"), icon: "eye" });
-        !change && cm.addItem({ description: "Change Perspective...", subitems: changeItems, icon: "external-link-alt" });
         this._downX = this._downY = Number.NaN;
     }
 
diff --git a/src/client/views/pdf/PDFMenu.tsx b/src/client/views/pdf/PDFMenu.tsx
index ff328068b..a641bce67 100644
--- a/src/client/views/pdf/PDFMenu.tsx
+++ b/src/client/views/pdf/PDFMenu.tsx
@@ -31,6 +31,7 @@ export default class PDFMenu extends AntimodeMenu {
         super(props);
 
         PDFMenu.Instance = this;
+        PDFMenu.Instance._canFade = false;
     }
 
     pointerDown = (e: React.PointerEvent) => {
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 516774f44..50ffd57fe 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -111,6 +111,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
     private _downX: number = 0;
     private _downY: number = 0;
     private _coverPath: any;
+    private _viewerIsSetup = false;
 
     @computed get allAnnotations() {
         return DocListCast(this.dataDoc[this.props.fieldKey + "-annotations"]).filter(
@@ -160,7 +161,14 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
         }, { fireImmediately: true });
 
         this._selectionReactionDisposer = reaction(() => this.props.isSelected(),
-            () => (SelectionManager.SelectedDocuments().length === 1) && this.setupPdfJsViewer(),
+            selected => {
+                if (!selected) {
+                    this._savedAnnotations.values().forEach(v => v.forEach(a => a.remove()));
+                    this._savedAnnotations.keys().forEach(k => this._savedAnnotations.setValue(k, []));
+                    PDFMenu.Instance.fadeOut(true);
+                }
+                (SelectionManager.SelectedDocuments().length === 1) && this.setupPdfJsViewer();
+            },
             { fireImmediately: true });
         this._reactionDisposer = reaction(
             () => this.Document._scrollY,
@@ -216,8 +224,8 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
 
     @action
     setupPdfJsViewer = async () => {
-        this._selectionReactionDisposer && this._selectionReactionDisposer();
-        this._selectionReactionDisposer = undefined;
+        if (this._viewerIsSetup) return;
+        else this._viewerIsSetup = true;
         this._showWaiting = true;
         this.props.setPdfViewer(this);
         await this.initialLoad();
-- 
cgit v1.2.3-70-g09d2


From d7dbc15ecc211ab9533c0cffa40227523b6a5831 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 22 Jun 2020 21:07:45 -0400
Subject: made chrome commands fire by clicking bullseye.  added commands for
 freeformview.  made backgroundHalo() work diffrently for ink strokes

---
 src/client/views/InkingStroke.tsx                  |  4 +-
 .../views/collections/CollectionViewChromes.tsx    | 32 ++++++---
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 76 +++++++++++-----------
 .../collections/collectionFreeForm/MarqueeView.tsx |  1 -
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  2 +-
 src/client/views/nodes/DocumentView.tsx            |  1 +
 src/client/views/nodes/FieldView.tsx               |  1 +
 .../views/nodes/formattedText/FormattedTextBox.tsx |  2 +-
 8 files changed, 68 insertions(+), 51 deletions(-)

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

diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 7e3bd1c17..cdabd213b 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -4,7 +4,7 @@ import { observer } from "mobx-react";
 import { documentSchema } from "../../fields/documentSchemas";
 import { InkData, InkField, InkTool } from "../../fields/InkField";
 import { makeInterface } from "../../fields/Schema";
-import { Cast, StrCast } from "../../fields/Types";
+import { Cast, StrCast, BoolCast } from "../../fields/Types";
 import { TraceMobx } from "../../fields/util";
 import { CognitiveServices } from "../cognitive_services/CognitiveServices";
 import { InteractionUtils } from "../util/InteractionUtils";
@@ -54,7 +54,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
         const scaleY = (this.props.PanelHeight() - strokeWidth) / (height - strokeWidth);
         const strokeColor = StrCast(this.layoutDoc.color, ActiveInkColor());
         const points = InteractionUtils.CreatePolyline(data, left, top, strokeColor, strokeWidth, strokeWidth,
-            StrCast(this.layoutDoc.strokeBezier, ActiveInkBezierApprox()), scaleX, scaleY, "", "none", this.props.isSelected() && strokeWidth <= 5);
+            StrCast(this.layoutDoc.strokeBezier, ActiveInkBezierApprox()), scaleX, scaleY, "", "none", (this.props.isSelected() || BoolCast(this.props.backgroundHalo?.())) && strokeWidth <= 5);
         const hpoints = InteractionUtils.CreatePolyline(data, left, top,
             this.props.isSelected() && strokeWidth > 5 ? strokeColor : "transparent", strokeWidth, (strokeWidth + 15),
             StrCast(this.layoutDoc.strokeBezier, ActiveInkBezierApprox()), scaleX, scaleY, "", this.props.active() ? "visiblestroke" : "none", false);
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index 52fb63386..ac7136d4d 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -40,28 +40,41 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
     _templateCommand = {
         params: ["target", "source"], title: "=> item view",
         script: "this.target.childLayout = getDocTemplate(this.source?.[0])",
-        immediate: (source: Doc[]) => this.target.childLayout = Doc.getDocTemplate(source?.[0]),
+        immediate: undoBatch((source: Doc[]) => source.length && (this.target.childLayout = Doc.getDocTemplate(source?.[0]))),
         initialize: emptyFunction,
     };
     _narrativeCommand = {
         params: ["target", "source"], title: "=> child click view",
         script: "this.target.childClickedOpenTemplateView = getDocTemplate(this.source?.[0])",
-        immediate: (source: Doc[]) => this.target.childClickedOpenTemplateView = Doc.getDocTemplate(source?.[0]),
+        immediate: undoBatch((source: Doc[]) => source.length && (this.target.childClickedOpenTemplateView = Doc.getDocTemplate(source?.[0]))),
         initialize: emptyFunction,
     };
     _contentCommand = {
-        params: ["target", "source"], title: "=> content",
+        params: ["target", "source"], title: "=> clear content",
         script: "getProto(this.target).data = copyField(this.source);",
-        immediate: (source: Doc[]) => Doc.GetProto(this.target).data = new List<Doc>(source), // Doc.aliasDocs(source),
+        immediate: undoBatch((source: Doc[]) => Doc.GetProto(this.target).data = new List<Doc>(source)), // Doc.aliasDocs(source),
         initialize: emptyFunction,
     };
     _viewCommand = {
-        params: ["target"], title: "=> saved view",
+        params: ["target"], title: "=> reset view",
         script: "this.target._panX = this.restoredPanX; this.target._panY = this.restoredPanY; this.target.scale = this.restoredScale;",
-        immediate: (source: Doc[]) => { this.target._panX = 0; this.target._panY = 0; this.target.scale = 1; },
+        immediate: undoBatch((source: Doc[]) => { this.target._panX = 0; this.target._panY = 0; this.target.scale = 1; }),
         initialize: (button: Doc) => { button.restoredPanX = this.target._panX; button.restoredPanY = this.target._panY; button.restoredScale = this.target.scale; },
     };
-    _freeform_commands = [this._contentCommand, this._templateCommand, this._narrativeCommand, this._viewCommand];
+    _clusterCommand = {
+        params: ["target"], title: "=> fit content",
+        script: "this.target._fitToBox = !this.target._fitToBox;",
+        immediate: undoBatch((source: Doc[]) => this.target._fitToBox = !this.target._fitToBox),
+        initialize: emptyFunction
+    };
+    _fitContentCommand = {
+        params: ["target"], title: "=> toggle clusters",
+        script: "this.target.useClusters = !this.target.useClusters;",
+        immediate: undoBatch((source: Doc[]) => this.target.useClusters = !this.target.useClusters),
+        initialize: emptyFunction
+    };
+
+    _freeform_commands = [this._viewCommand, 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];
@@ -84,6 +97,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
     @observable private _currentKey: string = "";
 
     componentDidMount = action(() => {
+        this._currentKey = this._currentKey || (this._buttonizableCommands.length ? this._buttonizableCommands[0]?.title : "");
         // chrome status is one of disabled, collapsed, or visible. this determines initial state from document
         switch (this.props.CollectionView.props.Document._chromeStatus) {
             case "disabled":
@@ -248,7 +262,9 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
                 DragManager.StartButtonDrag([this._commandRef.current!], c.script, c.title,
                     { target: this.props.CollectionView.props.Document }, c.params, c.initialize, e.clientX, e.clientY));
             return true;
-        }, emptyFunction, emptyFunction);
+        }, emptyFunction, () => {
+            this._buttonizableCommands.filter(c => c.title === this._currentKey).map(c => c.immediate([]));
+        });
     }
 
     @computed get templateChrome() {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 960b6efef..ac1456970 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1128,6 +1128,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             this.props.Document[this.scaleFieldKey] = Math.max(1, NumCast(this.props.Document[this.scaleFieldKey]));
         }
 
+        this.Document.useClusters && !this._clusterSets.length && this.childDocs.length && this.updateClusters(true);
         return elements;
     }
 
@@ -1197,59 +1198,58 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     onContextMenu = (e: React.MouseEvent) => {
         if (this.props.annotationsKey) return;
 
-        !this.props.isAnnotationOverlay && ContextMenu.Instance.addItem({
-            description: (this._timelineVisible ? "Close" : "Open") + " Animation Timeline", event: action(() => {
-                this._timelineVisible = !this._timelineVisible;
-            }), icon: this._timelineVisible ? faEyeSlash : faEye
-        });
-
         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: "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" });
 
         const options = ContextMenu.Instance.findByDescription("Options...");
         const optionItems = options && "subitems" in options ? options.subitems : [];
-        optionItems.push({ description: "toggle snap line display", event: () => Doc.UserDoc().showSnapLines = !Doc.UserDoc().showSnapLines, icon: "compress-arrows-alt" });
-        !Doc.UserDoc().noviceMode && optionItems.push({ description: (!this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" });
-        this.props.ContainingCollectionView && optionItems.push({ description: "Promote Collection", event: this.promoteCollection, icon: "table" });
+        !this.props.isAnnotationOverlay &&
+            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" });
-        !Doc.UserDoc().noviceMode && 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);
+        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._LODdisable ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document._LODdisable = !this.Document._LODdisable, 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);
+                                }
                             }
                         }
-                    }
-                };
-                input.click();
-            }
-        });
-        !Doc.UserDoc().noviceMode && optionItems.push({ description: `${this.Document._LODdisable ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document._LODdisable = !this.Document._LODdisable, icon: "table" });
+                    };
+                    input.click();
+                }
+            });
+        }
         !options && ContextMenu.Instance.addItem({ description: "Options...", subitems: optionItems, icon: "eye" });
 
     }
-    @observable _timelineVisible = false;
+    @observable showTimeline = false;
 
     intersectRect(r1: { left: number, top: number, width: number, height: number },
         r2: { left: number, top: number, width: number, height: number }) {
@@ -1350,7 +1350,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                 zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}>
                 {this.children}
             </CollectionFreeFormViewPannableContents>
-            {this._timelineVisible ? <Timeline ref={this._timelineRef} {...this.props} /> : (null)}
+            {this.showTimeline ? <Timeline ref={this._timelineRef} {...this.props} /> : (null)}
         </MarqueeView>;
     }
 
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 23efeec8d..1bc7c6fb5 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -741,7 +741,6 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         return <div className="marqueeView"
             style={{ overflow: StrCast(this.props.Document._overflow), cursor: MarqueeView.DragMarquee && this ? "crosshair" : "hand" }}
             onDragOver={e => e.preventDefault()}
-            onPaste={this.paste}
             onScroll={(e) => e.currentTarget.scrollTop = e.currentTarget.scrollLeft = 0} onClick={this.onClick} onPointerDown={this.onPointerDown}>
             {this._visible ? this.marqueeDiv : null}
             {this.props.children}
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index f934945a6..404d69730 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -144,7 +144,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                 boxShadow:
                     this.Opacity === 0 ? undefined :  // if it's not visible, then no shadow
                         this.layoutDoc.z ? `#9c9396  ${StrCast(this.layoutDoc.boxShadow, "10px 10px 0.9vw")}` :  // if it's a floating doc, give it a big shadow
-                            this.props.backgroundHalo?.() ? (`${this.props.backgroundColor?.(this.props.Document)} ${StrCast(this.layoutDoc.boxShadow, `0vw 0vw ${(this.layoutDoc.isBackground ? 100 : 50) / this.props.ContentScaling()}px`)}`) :  // if it's just in a cluster, make the shadown roughly match the cluster border extent
+                            this.props.backgroundHalo?.() && this.props.Document.type !== DocumentType.INK ? (`${this.props.backgroundColor?.(this.props.Document)} ${StrCast(this.layoutDoc.boxShadow, `0vw 0vw ${(this.layoutDoc.isBackground ? 100 : 50) / this.props.ContentScaling()}px`)}`) :  // if it's just in a cluster, make the shadown roughly match the cluster border extent
                                 this.layoutDoc.isBackground ? undefined :  // if it's a background & has a cluster color, make the shadow spread really big
                                     StrCast(this.layoutDoc.boxShadow, ""),
                 borderRadius: StrCast(Doc.Layout(this.layoutDoc).borderRounding),
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 71cb18e08..4e4c859ef 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1007,6 +1007,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 LayoutTemplate={this.props.LayoutTemplate}
                 makeLink={this.makeLink}
                 rootSelected={this.rootSelected}
+                backgroundHalo={this.props.backgroundHalo}
                 dontRegisterView={this.props.dontRegisterView}
                 fitToBox={this.props.fitToBox}
                 LibraryPath={this.props.LibraryPath}
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 305c04a90..c57738361 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -27,6 +27,7 @@ export interface FieldViewProps {
     LibraryPath: Doc[];
     onClick?: ScriptField;
     dropAction: dropActionType;
+    backgroundHalo?: () => boolean;
     docFilters: () => string[];
     isSelected: (outsideReaction?: boolean) => boolean;
     select: (isCtrlPressed: boolean) => void;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 5e0585169..2a019ada7 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -452,7 +452,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         appearanceItems.push({
             description: "Convert to be a template style", event: () => {
                 if (!this.layoutDoc.isTemplateDoc) {
-                    const title = StrCast(this.rootDoc.title)
+                    const title = StrCast(this.rootDoc.title);
                     this.rootDoc.title = "text";
                     this.rootDoc.isTemplateDoc = makeTemplate(this.rootDoc, true, title);
                 } else {
-- 
cgit v1.2.3-70-g09d2


From 477ba37f5728888e940119e4bc798a416565f960 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 23 Jun 2020 00:36:02 -0400
Subject: fixed mult link menu. fixed linking to external urls. fixed draggers
 for text menu.

---
 deploy/index.html                                  |  2 +-
 src/client/views/AntimodeMenu.tsx                  |  6 ++--
 .../collectionFreeForm/InkOptionsMenu.tsx          |  1 -
 .../nodes/formattedText/FormattedTextBox.scss      |  9 +++---
 .../views/nodes/formattedText/FormattedTextBox.tsx |  8 ++++--
 .../views/nodes/formattedText/RichTextMenu.tsx     | 32 +++++++++-------------
 6 files changed, 27 insertions(+), 31 deletions(-)

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

diff --git a/deploy/index.html b/deploy/index.html
index be0658da2..fdfd77cc2 100644
--- a/deploy/index.html
+++ b/deploy/index.html
@@ -9,7 +9,7 @@
 </head>
 
 <body>
-    <script src="https://hypothes.is/embed.js" async></script>
+    <!-- <script src="https://hypothes.is/embed.js" async></script> -->
     <div id="root"></div>
     <script src="/bundle.js"></script>
 </body>
diff --git a/src/client/views/AntimodeMenu.tsx b/src/client/views/AntimodeMenu.tsx
index 9a6121d20..2d26afcf6 100644
--- a/src/client/views/AntimodeMenu.tsx
+++ b/src/client/views/AntimodeMenu.tsx
@@ -131,15 +131,15 @@ export default abstract class AntimodeMenu extends React.Component {
     }
 
     protected getDragger = () => {
-        return <div className="antimodeMenu-dragger" onPointerDown={this.dragStart} style={{ width: this.Pinned ? "20px" : "0px" }} />;
+        return <div className="antimodeMenu-dragger" onPointerDown={this.dragStart} style={{ width: "20px" }} />;
     }
 
     protected getElement(buttons: JSX.Element[]) {
         return (
             <div className="antimodeMenu-cont" onPointerLeave={this.pointerLeave} onPointerEnter={this.pointerEntered} ref={this._mainCont} onContextMenu={this.handleContextMenu}
                 style={{ left: this._left, top: this._top, opacity: this._opacity, transitionProperty: this._transitionProperty, transitionDuration: this._transitionDuration, transitionDelay: this._transitionDelay }}>
+                <div className="antimodeMenu-dragger" onPointerDown={this.dragStart} style={{ width: "20px" }} />
                 {buttons}
-                <div className="antimodeMenu-dragger" onPointerDown={this.dragStart} style={{ width: this.Pinned ? "20px" : "0px" }} />
             </div>
         );
     }
@@ -148,8 +148,8 @@ export default abstract class AntimodeMenu extends React.Component {
         return (
             <div className="antimodeMenu-cont with-rows" onPointerLeave={this.pointerLeave} onPointerEnter={this.pointerEntered} ref={this._mainCont} onContextMenu={this.handleContextMenu}
                 style={{ left: this._left, top: this._top, opacity: this._opacity, transitionProperty: this._transitionProperty, transitionDuration: this._transitionDuration, transitionDelay: this._transitionDelay, height: "auto" }}>
+                {hasDragger ? <div className="antimodeMenu-dragger" onPointerDown={this.dragStart} style={{ width: "20px" }} /> : (null)}
                 {rows}
-                {hasDragger ? <div className="antimodeMenu-dragger" onPointerDown={this.dragStart} style={{ width: this.Pinned ? "20px" : "0px" }} /> : <></>}
             </div>
         );
     }
diff --git a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx
index ae82c6a65..4fac87ca5 100644
--- a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx
+++ b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx
@@ -118,7 +118,6 @@ export default class InkOptionsMenu extends AntimodeMenu {
 
     render() {
         const buttons = [
-            <button className="antimodeMenu-button" title="Drag" key="drag" onPointerDown={e => this.dragStart(e)}>  ✜  </button>,
             ...this.shapeButtons,
             this.bezierButton,
             this.widthPicker,
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
index 348ed4ba5..ccdf41233 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -251,22 +251,23 @@ footnote::after {
 .prosemirror-links {
     display: none;
     position: absolute;
-    background-color: gray;
-    padding-bottom: 10px;
-    margin-top: 1em;
+    background-color: dimgray;
+    margin-top: 1.5em;
     z-index: 1;
+    padding: 5;
+    border-radius: 2px;
   }
   .prosemirror-hrefoptions{
       width:0px;
       border:unset;
       padding:0px;
-
   }
   
   .prosemirror-links a {
     float: left;
     color: white;
     text-decoration: none;
+    border-radius: 3px;
   }
   
   .prosemirror-links a:hover {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 2a019ada7..3ee5603e5 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -599,10 +599,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         };
     }
 
-    makeLinkToSelection(linkId: string, title: string, location: string, targetId: string) {
+    makeLinkToSelection(linkId: string, title: string, location: string, targetId: string, targetHref?: string) {
         const state = this._editorView?.state;
         if (state) {
-            const href = Utils.prepend("/doc/" + linkId);
+            const href = targetHref ?? Utils.prepend("/doc/" + linkId);
             const sel = state.selection;
             const splitter = state.schema.marks.splitter.create({ id: Utils.GenerateGuid() });
             let tr = state.tr.addMark(sel.from, sel.to, splitter);
@@ -610,7 +610,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                 if (node.firstChild === null && node.marks.find((m: Mark) => m.type.name === schema.marks.splitter.name)) {
                     const allHrefs = [{ href, title, targetId, linkId }];
                     allHrefs.push(...(node.marks.find((m: Mark) => m.type.name === schema.marks.link.name)?.attrs.allHrefs ?? []));
-                    const link = state.schema.marks.link.create({ href, allHrefs, title, location, linkId, targetId });
+                    const link = state.schema.marks.link.create({ allHrefs, title, location, linkId });
                     tr = tr.addMark(pos, pos + node.nodeSize, link);
                 }
             });
@@ -937,6 +937,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                 const { state: { tr }, dispatch } = this._editorView;
                 dispatch(tr.insertText(startupText));
             }
+            (this._editorView as any).TextView = this;
         }
 
         const selectOnLoad = this.rootDoc[Id] === FormattedTextBox.SelectOnLoad;
@@ -1040,6 +1041,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         if (bounds && this.layoutDoc._chromeStatus !== "disabled") {
             const x = Math.min(Math.max(bounds.left, 0), window.innerWidth - RichTextMenu.Instance.width);
             let y = Math.min(Math.max(0, bounds.top - RichTextMenu.Instance.height - 50), window.innerHeight - RichTextMenu.Instance.height);
+            console.log("y = " + y + " hgt = " + RichTextMenu.Instance.height + " cords = " + coords.top);
             if (coords && coords.left > x && coords.left < x + RichTextMenu.Instance.width && coords.top > y && coords.top < y + RichTextMenu.Instance.height + 50) {
                 y = Math.min(bounds.bottom, window.innerHeight - RichTextMenu.Instance.height);
             }
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 1e14b8237..fecf2de85 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -12,7 +12,7 @@ import { faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSubscr
 import { updateBullets } from "./ProsemirrorExampleTransfer";
 import { FieldViewProps } from "../FieldView";
 import { Cast, StrCast } from "../../../../fields/Types";
-import { FormattedTextBoxProps } from "./FormattedTextBox";
+import { FormattedTextBoxProps, FormattedTextBox } from "./FormattedTextBox";
 import { unimplementedFunction, Utils } from "../../../../Utils";
 import { wrapInList } from "prosemirror-schema-list";
 import { PastelSchemaPalette, DarkPastelSchemaPalette } from '../../../../fields/SchemaHeaderField';
@@ -307,7 +307,7 @@ export default class RichTextMenu extends AntimodeMenu {
         function onClick(e: React.PointerEvent) {
             e.preventDefault();
             e.stopPropagation();
-            self.view && self.view.focus();
+            //self.view?.focus();
             self.view && command && command(self.view.state, self.view.dispatch, self.view);
             self.view && onclick && onclick(self.view.state, self.view.dispatch, self.view);
             self.setActiveMarkButtons(self.getActiveMarksOnSelection());
@@ -427,7 +427,7 @@ export default class RichTextMenu extends AntimodeMenu {
         function onBrushClick(e: React.PointerEvent) {
             e.preventDefault();
             e.stopPropagation();
-            self.view && self.view.focus();
+            // self.view?.focus();
             self.view && self.fillBrush(self.view.state, self.view.dispatch);
         }
 
@@ -501,13 +501,13 @@ export default class RichTextMenu extends AntimodeMenu {
         function onColorClick(e: React.PointerEvent) {
             e.preventDefault();
             e.stopPropagation();
-            self.view && self.view.focus();
+            // self.view?.focus();
             self.view && self.insertColor(self.activeFontColor, self.view.state, self.view.dispatch);
         }
         function changeColor(e: React.PointerEvent, color: string) {
             e.preventDefault();
             e.stopPropagation();
-            self.view && self.view.focus();
+            //self.view?.focus();
             self.setActiveColor(color);
             self.view && self.insertColor(self.activeFontColor, self.view.state, self.view.dispatch);
         }
@@ -554,13 +554,13 @@ export default class RichTextMenu extends AntimodeMenu {
         function onHighlightClick(e: React.PointerEvent) {
             e.preventDefault();
             e.stopPropagation();
-            self.view && self.view.focus();
+            // self.view?.focus();
             self.view && self.insertHighlight(self.activeHighlightColor, self.view.state, self.view.dispatch);
         }
         function changeHighlight(e: React.PointerEvent, color: string) {
             e.preventDefault();
             e.stopPropagation();
-            self.view && self.view.focus();
+            // self.view?.focus();
             self.setActiveHighlight(color);
             self.view && self.insertHighlight(self.activeHighlightColor, self.view.state, self.view.dispatch);
         }
@@ -659,15 +659,8 @@ export default class RichTextMenu extends AntimodeMenu {
     }
 
     // TODO: should check for valid URL
-    makeLinkToURL = (target: String, lcoation: string) => {
-        if (!this.view) return;
-
-        let node = this.view.state.selection.$from.nodeAfter;
-        let link = this.view.state.schema.mark(this.view.state.schema.marks.link, { href: target, location: location });
-        this.view.dispatch(this.view.state.tr.removeMark(this.view.state.selection.from, this.view.state.selection.to, this.view.state.schema.marks.link));
-        this.view.dispatch(this.view.state.tr.addMark(this.view.state.selection.from, this.view.state.selection.to, link));
-        node = this.view.state.selection.$from.nodeAfter;
-        link = node && node.marks.find(m => m.type.name === "link");
+    makeLinkToURL = (target: string, lcoation: string) => {
+        ((this.view as any)?.TextView as FormattedTextBox).makeLinkToSelection("", target, "onRight", "", target);
     }
 
     deleteLink = () => {
@@ -760,13 +753,14 @@ export default class RichTextMenu extends AntimodeMenu {
         this.collapsed = !this.collapsed;
         setTimeout(() => {
             const x = Math.min(this._left, window.innerWidth - RichTextMenu.Instance.width);
-            RichTextMenu.Instance.jumpTo(x, this._top);
+            RichTextMenu.Instance.jumpTo(x, this._top, true);
         }, 0);
     }
 
     render() {
 
         const row1 = <div className="antimodeMenu-row" key="row1" style={{ display: this.collapsed ? "none" : undefined }}>{[
+            !this.collapsed ? this.getDragger() : (null),
             this.createButton("bold", "Bold", this.boldActive, toggleMark(schema.marks.strong)),
             this.createButton("italic", "Italic", this.italicsActive, toggleMark(schema.marks.em)),
             this.createButton("underline", "Underline", this.underlineActive, toggleMark(schema.marks.underline)),
@@ -781,6 +775,7 @@ export default class RichTextMenu extends AntimodeMenu {
         ]}</div>;
 
         const row2 = <div className="antimodeMenu-row row-2" key="antimodemenu row2">
+            {this.collapsed ? this.getDragger() : (null)}
             <div key="row" style={{ display: this.collapsed ? "none" : undefined }}>
                 {[this.createMarksDropdown(this.activeFontSize, this.fontSizeOptions, "font size"),
                 this.createMarksDropdown(this.activeFontFamily, this.fontFamilyOptions, "font family"),
@@ -795,7 +790,6 @@ export default class RichTextMenu extends AntimodeMenu {
                 <button className="antimodeMenu-button" key="pin menu" title="Pin menu" onClick={this.toggleMenuPin} style={{ backgroundColor: this.Pinned ? "#121212" : "", display: this.collapsed ? "none" : undefined }}>
                     <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.1s", transform: `rotate(${this.Pinned ? 45 : 0}deg)` }} />
                 </button>
-                {this.getDragger()}
             </div>
         </div>;
 
@@ -840,7 +834,7 @@ class ButtonDropdown extends React.Component<ButtonDropdownProps> {
     onDropdownClick = (e: React.PointerEvent) => {
         e.preventDefault();
         e.stopPropagation();
-        this.props.view && this.props.view.focus();
+        //this.props.view?.focus();
         this.toggleDropdown();
     }
 
-- 
cgit v1.2.3-70-g09d2


From 71ba25ed627be46d02878e58bd791bf817d552ad Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 24 Jun 2020 01:53:31 -0400
Subject: changed titles to just look at first node of text box.

---
 src/client/views/nodes/formattedText/FormattedTextBox.tsx | 4 +++-
 1 file changed, 3 insertions(+), 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 3ee5603e5..e01144f82 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -230,7 +230,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     updateTitle = () => {
         if ((this.props.Document.isTemplateForField === "text" || !this.props.Document.isTemplateForField) && // only update the title if the data document's data field is changing
             StrCast(this.dataDoc.title).startsWith("-") && this._editorView && !this.rootDoc.customTitle) {
-            const str = this._editorView.state.doc.textContent;
+            let node = this._editorView.state.doc;
+            while (node.firstChild) node = node.firstChild;
+            const str = node.textContent;
             const titlestr = str.substr(0, Math.min(40, str.length));
             this.dataDoc.title = "-" + titlestr + (str.length > 40 ? "..." : "");
         }
-- 
cgit v1.2.3-70-g09d2


From ae6315813d718f8e4913ed98f5aec33464eb8164 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 24 Jun 2020 11:45:22 -0400
Subject: fixed warnings.  fixed blockquote menu function.

---
 src/client/util/DragManager.ts                     |  2 +-
 .../views/collections/CollectionDockingView.tsx    |  3 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  5 +--
 .../views/nodes/formattedText/ParagraphNodeSpec.ts |  2 +-
 .../views/nodes/formattedText/RichTextMenu.tsx     | 50 +++++++++++++---------
 src/client/views/nodes/formattedText/nodes_rts.ts  | 20 ++++++++-
 .../nodes/formattedText/prosemirrorPatches.js      |  4 +-
 7 files changed, 56 insertions(+), 30 deletions(-)

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

diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 533bc8485..91bc51101 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -218,7 +218,7 @@ export namespace DragManager {
                 docDragData.dropAction !== "same" && docDragData.droppedDocuments.forEach((drop: Doc, i: number) => {
                     const dragProps = Cast(dragData.draggedDocuments[i].removeDropProperties, listSpec("string"), []);
                     const remProps = (dragData?.removeDropProperties || []).concat(Array.from(dragProps));
-                    remProps.map(prop => drop[prop] = undefined)
+                    remProps.map(prop => drop[prop] = undefined);
                 });
                 batch.end();
             }
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index ee987abdb..79c577b6d 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -527,7 +527,8 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                             dragData.dropAction = doc.dropAction as dropActionType;
                             DragManager.StartDocumentDrag([gearSpan], dragData, e.clientX, e.clientY);
                             return true;
-                        } return false
+                        }
+                        return false;
                     }, returnFalse, emptyFunction);
                 };
 
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index e01144f82..cdf231985 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -933,7 +933,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                 clipboardTextSerializer: this.clipboardTextSerializer,
                 handlePaste: this.handlePaste,
             });
-            // applyDevTools.applyDevTools(this._editorView);
+            //applyDevTools.applyDevTools(this._editorView);
             const startupText = !rtfField && this._editorView && Field.toString(this.dataDoc[fieldKey] as Field);
             if (startupText) {
                 const { state: { tr }, dispatch } = this._editorView;
@@ -1043,11 +1043,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         if (bounds && this.layoutDoc._chromeStatus !== "disabled") {
             const x = Math.min(Math.max(bounds.left, 0), window.innerWidth - RichTextMenu.Instance.width);
             let y = Math.min(Math.max(0, bounds.top - RichTextMenu.Instance.height - 50), window.innerHeight - RichTextMenu.Instance.height);
-            console.log("y = " + y + " hgt = " + RichTextMenu.Instance.height + " cords = " + coords.top);
             if (coords && coords.left > x && coords.left < x + RichTextMenu.Instance.width && coords.top > y && coords.top < y + RichTextMenu.Instance.height + 50) {
                 y = Math.min(bounds.bottom, window.innerHeight - RichTextMenu.Instance.height);
             }
-            RichTextMenu.Instance.jumpTo(x, y);
+            setTimeout(() => window.document.activeElement === this.ProseRef?.children[0] && RichTextMenu.Instance.jumpTo(x, y), 250);
         }
     }
     onPointerWheel = (e: React.WheelEvent): void => {
diff --git a/src/client/views/nodes/formattedText/ParagraphNodeSpec.ts b/src/client/views/nodes/formattedText/ParagraphNodeSpec.ts
index d80e64634..30da91710 100644
--- a/src/client/views/nodes/formattedText/ParagraphNodeSpec.ts
+++ b/src/client/views/nodes/formattedText/ParagraphNodeSpec.ts
@@ -28,7 +28,7 @@ const ALIGN_PATTERN = /(left|right|center|justify)/;
 // https://github.com/ProseMirror/prosemirror-schema-basic/blob/master/src/schema-basic.js
 // :: NodeSpec A plain paragraph textblock. Represented in the DOM
 // as a `<p>` element.
-const ParagraphNodeSpec: NodeSpec = {
+export const ParagraphNodeSpec: NodeSpec = {
     attrs: {
         align: { default: null },
         color: { default: null },
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 68101fbde..5e32b3ef8 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -1,30 +1,33 @@
 import React = require("react");
-import AntimodeMenu from "../../AntimodeMenu";
-import { observable, action, } from "mobx";
+import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
+import { faBold, faCaretDown, faChevronLeft, faEyeDropper, faHighlighter, faIndent, faItalic, faLink, faPaintRoller, faPalette, faStrikethrough, faSubscript, faSuperscript, faUnderline } from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { action, observable } from "mobx";
 import { observer } from "mobx-react";
-import { Mark, MarkType, Node as ProsNode, NodeType, ResolvedPos, Schema } from "prosemirror-model";
-import { schema } from "./schema_rts";
-import { EditorView } from "prosemirror-view";
+import { lift, wrapIn } from "prosemirror-commands";
+import { Mark, MarkType, Node as ProsNode, NodeType, ResolvedPos } from "prosemirror-model";
+import { wrapInList } from "prosemirror-schema-list";
 import { EditorState, NodeSelection, TextSelection } from "prosemirror-state";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
-import { faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSubscript, faSuperscript, faIndent, faEyeDropper, faCaretDown, faPalette, faHighlighter, faLink, faPaintRoller, faSleigh } from "@fortawesome/free-solid-svg-icons";
-import { updateBullets } from "./ProsemirrorExampleTransfer";
-import { FieldViewProps } from "../FieldView";
+import { EditorView } from "prosemirror-view";
+import { Doc } from "../../../../fields/Doc";
+import { DarkPastelSchemaPalette, PastelSchemaPalette } from '../../../../fields/SchemaHeaderField';
 import { Cast, StrCast } from "../../../../fields/Types";
-import { FormattedTextBoxProps, FormattedTextBox } from "./FormattedTextBox";
 import { unimplementedFunction, Utils } from "../../../../Utils";
-import { wrapInList } from "prosemirror-schema-list";
-import { PastelSchemaPalette, DarkPastelSchemaPalette } from '../../../../fields/SchemaHeaderField';
-import "./RichTextMenu.scss";
 import { DocServer } from "../../../DocServer";
-import { Doc } from "../../../../fields/Doc";
-import { SelectionManager } from "../../../util/SelectionManager";
 import { LinkManager } from "../../../util/LinkManager";
-const { toggleMark, setBlockType } = require("prosemirror-commands");
+import { SelectionManager } from "../../../util/SelectionManager";
+import AntimodeMenu from "../../AntimodeMenu";
+import { FieldViewProps } from "../FieldView";
+import { FormattedTextBox, FormattedTextBoxProps } from "./FormattedTextBox";
+import { updateBullets } from "./ProsemirrorExampleTransfer";
+import "./RichTextMenu.scss";
+import { schema } from "./schema_rts";
+import { TraceMobx } from "../../../../fields/util";
+const { toggleMark } = require("prosemirror-commands");
 
 library.add(faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper, faCaretDown, faPalette, faHighlighter, faLink, faPaintRoller);
 
+
 @observer
 export default class RichTextMenu extends AntimodeMenu {
     static Instance: RichTextMenu;
@@ -69,6 +72,7 @@ export default class RichTextMenu extends AntimodeMenu {
         super(props);
         RichTextMenu.Instance = this;
         this._canFade = false;
+        this.Pinned = true;
 
         this.fontSizeOptions = [
             { mark: schema.marks.pFontSize.create({ fontSize: 7 }), title: "Set font size", label: "7pt", command: this.changeFontSize },
@@ -201,7 +205,7 @@ export default class RichTextMenu extends AntimodeMenu {
                     } else dispatch(tx);
                 });
             } else {
-                toggleMark(mark.type, mark.attrs)(state, (tx: any) => dispatch(tx));
+                toggleMark(mark.type, mark.attrs)(state, dispatch);
             }
         }
     }
@@ -411,8 +415,12 @@ export default class RichTextMenu extends AntimodeMenu {
     }
 
     insertBlockquote(state: EditorState<any>, dispatch: any) {
-        if (state.selection.empty) return false;
-        setBlockType(state.schema.nodes.blockquote)(state, (tx: any) => dispatch(tx));
+        const path = (state.selection.$from as any).path;
+        if (path.length > 6 && path[path.length - 6].type === schema.nodes.blockquote) {
+            lift(state, dispatch);
+        } else {
+            wrapIn(schema.nodes.blockquote)(state, dispatch);
+        }
         return true;
     }
 
@@ -758,7 +766,7 @@ export default class RichTextMenu extends AntimodeMenu {
     }
 
     render() {
-
+        TraceMobx();
         const row1 = <div className="antimodeMenu-row" key="row1" style={{ display: this.collapsed ? "none" : undefined }}>{[
             !this.collapsed ? this.getDragger() : (null),
             this.createButton("bold", "Bold", this.boldActive, toggleMark(schema.marks.strong)),
diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts
index 1a292d9af..0a867912f 100644
--- a/src/client/views/nodes/formattedText/nodes_rts.ts
+++ b/src/client/views/nodes/formattedText/nodes_rts.ts
@@ -1,7 +1,7 @@
 import React = require("react");
 import { DOMOutputSpecArray, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model";
 import { bulletList, listItem, orderedList } from 'prosemirror-schema-list';
-import ParagraphNodeSpec from "./ParagraphNodeSpec";
+import { ParagraphNodeSpec, toParagraphDOM, getParagraphNodeAttrs } from "./ParagraphNodeSpec";
 
 const blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"],
     preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0];
@@ -32,13 +32,29 @@ export const nodes: { [index: string]: NodeSpec } = {
 
     // :: NodeSpec A blockquote (`<blockquote>`) wrapping one or more blocks.
     blockquote: {
-        content: "block+",
+        content: "block*",
         group: "block",
         defining: true,
         parseDOM: [{ tag: "blockquote" }],
         toDOM() { return blockquoteDOM; }
     },
 
+
+    // blockquote: {
+    //     ...ParagraphNodeSpec,
+    //     defining: true,
+    //     parseDOM: [{
+    //         tag: "blockquote", getAttrs(dom: any) {
+    //             return getParagraphNodeAttrs(dom);
+    //         }
+    //     }],
+    //     toDOM(node: any) {
+    //         const dom = toParagraphDOM(node);
+    //         (dom as any)[0] = 'blockquote';
+    //         return dom;
+    //     },
+    // },
+
     // :: NodeSpec A horizontal rule (`<hr>`).
     horizontal_rule: {
         group: "block",
diff --git a/src/client/views/nodes/formattedText/prosemirrorPatches.js b/src/client/views/nodes/formattedText/prosemirrorPatches.js
index 269423482..763961958 100644
--- a/src/client/views/nodes/formattedText/prosemirrorPatches.js
+++ b/src/client/views/nodes/formattedText/prosemirrorPatches.js
@@ -136,4 +136,6 @@ function wrappingInputRule(regexp, nodeType, getAttrs, joinPredicate, customWith
             (!joinPredicate || joinPredicate(match, before))) { tr.join(start - 1); }
         return tr
     })
-}
\ No newline at end of file
+}
+
+
-- 
cgit v1.2.3-70-g09d2

