From 094bbd52f2b55f501357a9f6b057042ad7684f27 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Mon, 26 Aug 2019 16:58:24 -0400
Subject: playing more with bulleted lists.

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

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

diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index d6ba1700a..acfd2a3b8 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -115,7 +115,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
 
     @undoBatch
     public setFontColor(color: string) {
-        let self = this;
         if (this._editorView!.state.selection.from === this._editorView!.state.selection.to) return false;
         if (this._editorView!.state.selection.to - this._editorView!.state.selection.from > this._editorView!.state.doc.nodeSize - 3) {
             this.props.Document.color = color;
@@ -176,15 +175,13 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             FormattedTextBox._toolTipTextMenu && (FormattedTextBox._toolTipTextMenu.HackToFixTextSelectionGlitch = true);
             this._editorView.updateState(state);
             FormattedTextBox._toolTipTextMenu && (FormattedTextBox._toolTipTextMenu.HackToFixTextSelectionGlitch = false);
-            if (state.selection.empty && FormattedTextBox._toolTipTextMenu) {
-                const marks = tx.storedMarks;
-                if (marks) { FormattedTextBox._toolTipTextMenu.mark_key_pressed(marks); }
+            if (state.selection.empty && FormattedTextBox._toolTipTextMenu && tx.storedMarks) {
+                FormattedTextBox._toolTipTextMenu.mark_key_pressed(tx.storedMarks);
             }
 
             this._applyingChange = true;
-            const fieldkey = "preview";
-            if (this.extensionDoc) this.extensionDoc.text = state.doc.textBetween(0, state.doc.content.size, "\n\n");
-            if (this.extensionDoc) this.extensionDoc.lastModified = new DateField(new Date(Date.now()));
+            this.extensionDoc && (this.extensionDoc.text = state.doc.textBetween(0, state.doc.content.size, "\n\n"));
+            this.extensionDoc && (this.extensionDoc.lastModified = new DateField(new Date(Date.now())));
             this.dataDoc[this.props.fieldKey] = new RichTextField(JSON.stringify(state.toJSON()));
             this._applyingChange = false;
             let title = StrCast(this.dataDoc.title);
@@ -198,7 +195,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
 
     public highlightSearchTerms = (terms: String[]) => {
         if (this._editorView && (this._editorView as any).docView) {
-            const fieldkey = "preview";
             const doc = this._editorView.state.doc;
             const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight);
             doc.nodesBetween(0, doc.content.size, (node: ProsNode, pos: number, parent: ProsNode, index: number) => {
-- 
cgit v1.2.3-70-g09d2


From 34e0b1cf34474ec6765e212b6a35defefbfb49c9 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Tue, 27 Aug 2019 15:46:03 -0400
Subject: removed selectOnLoad prop.  fixed textboxes from deselecting on
 carriage return.

---
 src/client/views/MainOverlayTextBox.tsx                    |  2 +-
 src/client/views/MainView.tsx                              |  2 --
 src/client/views/OverlayView.tsx                           |  1 -
 src/client/views/collections/CollectionDockingView.tsx     |  1 -
 src/client/views/collections/CollectionSchemaCells.tsx     |  1 -
 src/client/views/collections/CollectionSchemaView.tsx      |  1 -
 .../collectionFreeForm/CollectionFreeFormView.tsx          | 14 +++-----------
 src/client/views/nodes/DocumentView.tsx                    |  4 +---
 src/client/views/nodes/FieldView.tsx                       |  2 --
 src/client/views/nodes/FormattedTextBox.tsx                |  9 ++++++---
 src/client/views/nodes/KeyValuePair.tsx                    |  1 -
 src/client/views/presentationview/PresentationElement.tsx  |  1 -
 src/client/views/search/SearchItem.tsx                     |  1 -
 13 files changed, 11 insertions(+), 29 deletions(-)

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

diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx
index 65a291d99..f15b60347 100644
--- a/src/client/views/MainOverlayTextBox.tsx
+++ b/src/client/views/MainOverlayTextBox.tsx
@@ -156,7 +156,7 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
                                 DataDoc={FormattedTextBox.InputBoxOverlay.props.DataDoc}
                                 onClick={undefined}
                                 ChromeHeight={this.ChromeHeight}
-                                isSelected={returnTrue} select={emptyFunction} renderDepth={0} selectOnLoad={true}
+                                isSelected={returnTrue} select={emptyFunction} renderDepth={0}
                                 ContainingCollectionView={undefined} whenActiveChanged={emptyFunction} active={returnTrue} ContentScaling={returnOne}
                                 ScreenToLocalTransform={this._textXf} PanelWidth={returnZero} PanelHeight={returnZero} focus={emptyFunction}
                                 pinToPres={returnZero} addDocTab={this.addDocTab} outer_div={(tooltip: HTMLElement) => { this._tooltip = tooltip; this.updateTooltip(); }} />
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index a02214deb..6b856443b 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -326,7 +326,6 @@ export class MainView extends React.Component {
                             PanelHeight={this.getPHeight}
                             renderDepth={0}
                             backgroundColor={returnEmptyString}
-                            selectOnLoad={false}
                             focus={emptyFunction}
                             parentActive={returnTrue}
                             whenActiveChanged={emptyFunction}
@@ -389,7 +388,6 @@ export class MainView extends React.Component {
             PanelWidth={this.flyoutWidthFunc}
             PanelHeight={this.getPHeight}
             renderDepth={0}
-            selectOnLoad={false}
             focus={emptyFunction}
             backgroundColor={returnEmptyString}
             parentActive={returnTrue}
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index a60dc591c..538614089 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -153,7 +153,6 @@ export class OverlayView extends React.Component {
                         PanelHeight={returnOne}
                         ScreenToLocalTransform={Transform.Identity}
                         renderDepth={1}
-                        selectOnLoad={false}
                         parentActive={returnTrue}
                         whenActiveChanged={emptyFunction}
                         focus={emptyFunction}
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index dc0cbda0b..95f94875c 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -636,7 +636,6 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
             PanelHeight={this.panelHeight}
             ScreenToLocalTransform={this.ScreenToLocalTransform}
             renderDepth={0}
-            selectOnLoad={false}
             parentActive={returnTrue}
             whenActiveChanged={emptyFunction}
             focus={emptyFunction}
diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx
index 551b485e7..9c26a08f0 100644
--- a/src/client/views/collections/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/CollectionSchemaCells.tsx
@@ -153,7 +153,6 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
             isSelected: returnFalse,
             select: emptyFunction,
             renderDepth: this.props.renderDepth + 1,
-            selectOnLoad: false,
             ScreenToLocalTransform: Transform.Identity,
             focus: emptyFunction,
             active: returnFalse,
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 4008dea51..9d83aa6c1 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -1006,7 +1006,6 @@ export class CollectionSchemaPreview extends React.Component<CollectionSchemaPre
                         parentActive={this.props.active}
                         ScreenToLocalTransform={this.getTransform}
                         renderDepth={this.props.renderDepth + 1}
-                        selectOnLoad={false}
                         ContentScaling={this.contentScaling}
                         PanelWidth={this.PanelWidth}
                         PanelHeight={this.PanelHeight}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index f185222ce..62f848a2b 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,7 +1,7 @@
 import { library } from "@fortawesome/fontawesome-svg-core";
 import { faEye } from "@fortawesome/free-regular-svg-icons";
 import { faBraille, faChalkboard, faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faPaintBrush, faTable, faUpload } from "@fortawesome/free-solid-svg-icons";
-import { action, computed, IReactionDisposer, observable, reaction } from "mobx";
+import { action, computed, IReactionDisposer, observable, reaction, trace } from "mobx";
 import { observer } from "mobx-react";
 import { Doc, DocListCastAsync, Field, FieldResult, HeightSym, Opt, WidthSym } from "../../../../new_fields/Doc";
 import { Id } from "../../../../new_fields/FieldSymbols";
@@ -38,6 +38,7 @@ import "./CollectionFreeFormView.scss";
 import { MarqueeView } from "./MarqueeView";
 import React = require("react");
 import { DocServer } from "../../../DocServer";
+import { FormattedTextBox } from "../../nodes/FormattedTextBox";
 
 library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard);
 
@@ -165,8 +166,6 @@ export namespace PivotView {
             return prev;
         }, elements);
 
-        target.resetSelectOnLoaded();
-
         return docViews;
     };
 
@@ -177,7 +176,6 @@ const PanZoomDocument = makeInterface(panZoomSchema, positionSchema, pageSchema)
 
 @observer
 export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
-    private _selectOnLoaded: string = ""; // id of document that should be selected once it's loaded (used for click-to-type)
     private _lastX: number = 0;
     private _lastY: number = 0;
     private get _pwidth() { return this.props.PanelWidth(); }
@@ -241,7 +239,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     private getContainerTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth, -this.borderWidth);
     private getLocalTransform = (): Transform => Transform.Identity().scale(1 / this.zoomScaling()).translate(this.panX(), this.panY());
     private addLiveTextBox = (newBox: Doc) => {
-        this._selectOnLoaded = newBox[Id];// track the new text box so we can give it a prop that tells it to focus itself when it's displayed
+        FormattedTextBox.SelectOnLoad = newBox[Id];// track the new text box so we can give it a prop that tells it to focus itself when it's displayed
         this.addDocument(newBox, false);
     }
     private addDocument = (newBox: Doc, allowDuplicates: boolean) => {
@@ -642,7 +640,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             onClick: this.props.onClick,
             ScreenToLocalTransform: childLayout.z ? this.getTransformOverlay : this.getTransform,
             renderDepth: this.props.renderDepth + 1,
-            selectOnLoad: childLayout[Id] === this._selectOnLoaded,
             PanelWidth: childLayout[WidthSym],
             PanelHeight: childLayout[HeightSym],
             ContentScaling: returnOne,
@@ -668,7 +665,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             onClick: this.props.onClick,
             ScreenToLocalTransform: this.getTransform,
             renderDepth: this.props.renderDepth,
-            selectOnLoad: layoutDoc[Id] === this._selectOnLoaded,
             PanelWidth: layoutDoc[WidthSym],
             PanelHeight: layoutDoc[HeightSym],
             ContentScaling: returnOne,
@@ -768,13 +764,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             return prev;
         }, elements);
 
-        this.resetSelectOnLoaded();
-
         return docviews;
     }
 
-    resetSelectOnLoaded = () => setTimeout(() => this._selectOnLoaded = "", 600);// bcz: surely there must be a better way ....
-
     @computed.struct
     get views() {
         let source = this.elements;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 3cf86b6f9..f7ebfb75a 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -92,7 +92,6 @@ export interface DocumentViewProps {
     PanelWidth: () => number;
     PanelHeight: () => number;
     focus: (doc: Doc, willZoom: boolean, scale?: number) => void;
-    selectOnLoad: boolean;
     parentActive: () => boolean;
     whenActiveChanged: (isActive: boolean) => void;
     bringToFront: (doc: Doc, sendToBack?: boolean) => void;
@@ -717,7 +716,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             isSelected={this.isSelected}
             select={this.select}
             onClick={this.onClickHandler}
-            selectOnLoad={this.props.selectOnLoad}
             layoutKey={"layout"}
             fitToBox={BoolCast(this.props.Document.fitToBox) ? true : this.props.fitToBox}
             DataDoc={this.dataDoc} />);
@@ -808,7 +806,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                         }
                         {!showCaption ? (null) :
                             <div style={{ position: "absolute", bottom: 0, transformOrigin: "bottom left", width: `${100 * this.props.ContentScaling()}%`, transform: `scale(${1 / this.props.ContentScaling()})` }}>
-                                <FormattedTextBox {...this.props} onClick={this.onClickHandler} DataDoc={this.dataDoc} active={returnTrue} isSelected={this.isSelected} focus={emptyFunction} select={this.select} selectOnLoad={this.props.selectOnLoad} fieldExt={""} hideOnLeave={true} fieldKey={showCaption} />
+                                <FormattedTextBox {...this.props} onClick={this.onClickHandler} DataDoc={this.dataDoc} active={returnTrue} isSelected={this.isSelected} focus={emptyFunction} select={this.select} fieldExt={""} hideOnLeave={true} fieldKey={showCaption} />
                             </div>
                         }
                     </div>
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index f0f1b3b73..d9774303b 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -36,7 +36,6 @@ export interface FieldViewProps {
     isSelected: () => boolean;
     select: (isCtrlPressed: boolean) => void;
     renderDepth: number;
-    selectOnLoad: boolean;
     addDocument?: (document: Doc, allowDuplicates?: boolean) => boolean;
     addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void;
     pinToPres: (document: Doc) => void;
@@ -108,7 +107,6 @@ export class FieldView extends React.Component<FieldViewProps> {
             //         PanelWidth={returnHundred}
             //         PanelHeight={returnHundred}
             //         renderDepth={0} //TODO Why is renderDepth reset?
-            //         selectOnLoad={false}
             //         focus={emptyFunction}
             //         isSelected={this.props.isSelected}
             //         select={returnFalse}
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index acfd2a3b8..c28bf1821 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -622,12 +622,15 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             }
         }
 
-        if (this.props.selectOnLoad) {
-            if (!this.props.isOverlay) this.props.select(false);
-            else this._editorView!.focus();
+        if (this.props.Document[Id] == FormattedTextBox.SelectOnLoad) {
+            FormattedTextBox.SelectOnLoad = "";
+            this.props.select(false);
         }
+        else if (this.props.isOverlay) this._editorView!.focus();
     }
 
+    public static SelectOnLoad = "";
+
     componentWillUnmount() {
         this._editorView && this._editorView.destroy();
         this._reactionDisposer && this._reactionDisposer();
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 8001b24a7..5afd4d834 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -60,7 +60,6 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
             isSelected: returnFalse,
             select: emptyFunction,
             renderDepth: 1,
-            selectOnLoad: false,
             active: returnFalse,
             whenActiveChanged: emptyFunction,
             ScreenToLocalTransform: Transform.Identity,
diff --git a/src/client/views/presentationview/PresentationElement.tsx b/src/client/views/presentationview/PresentationElement.tsx
index 83413814f..80aa25f48 100644
--- a/src/client/views/presentationview/PresentationElement.tsx
+++ b/src/client/views/presentationview/PresentationElement.tsx
@@ -359,7 +359,6 @@ export default class PresentationElement extends React.Component<PresentationEle
                     PanelHeight={() => 90}
                     focus={emptyFunction}
                     backgroundColor={returnEmptyString}
-                    selectOnLoad={false}
                     parentActive={returnFalse}
                     whenActiveChanged={returnFalse}
                     bringToFront={emptyFunction}
diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx
index 41fc49c2e..672892fdf 100644
--- a/src/client/views/search/SearchItem.tsx
+++ b/src/client/views/search/SearchItem.tsx
@@ -209,7 +209,6 @@ export class SearchItem extends React.Component<SearchItemProps> {
                     PanelHeight={returnYDimension}
                     focus={emptyFunction}
                     backgroundColor={returnEmptyString}
-                    selectOnLoad={false}
                     parentActive={returnFalse}
                     whenActiveChanged={returnFalse}
                     bringToFront={emptyFunction}
-- 
cgit v1.2.3-70-g09d2


From 656a08a130124870c9f652f7b1529b2b496cdac7 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Tue, 27 Aug 2019 15:52:25 -0400
Subject: lint fixes.

---
 src/client/views/PreviewCursor.tsx                     | 2 +-
 src/client/views/collections/CollectionViewChromes.tsx | 2 ++
 src/client/views/nodes/DocumentView.tsx                | 2 +-
 src/client/views/nodes/FormattedTextBox.tsx            | 2 +-
 src/client/views/nodes/WebBox.tsx                      | 2 +-
 src/client/views/pdf/PDFViewer.tsx                     | 2 +-
 6 files changed, 7 insertions(+), 5 deletions(-)

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

diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index d8e161ab6..1329dc02c 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -51,7 +51,7 @@ export class PreviewCursor extends React.Component<{}> {
                     // tests for URL and makes web document
                     let re: any = /^https?:\/\//g;
                     if (re.test(e.clipboardData.getData("text/plain"))) {
-                        const url = e.clipboardData.getData("text/plain")
+                        const url = e.clipboardData.getData("text/plain");
                         PreviewCursor._addDocument(Docs.Create.WebDocument(url, {
                             title: url, width: 300, height: 300,
                             // nativeWidth: 300, nativeHeight: 472.5,
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index b2df0e747..4b3f7c87e 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -42,11 +42,13 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
 
     _templateCommand = {
         title: "set template", script: "this.target.childLayout = this.source ? this.source[0] : undefined", params: ["target", "source"],
+        initialize: emptyFunction,
         immediate: (draggedDocs: Doc[]) => this.props.CollectionView.props.Document.childLayout = draggedDocs.length ? draggedDocs[0] : undefined
     };
     _contentCommand = {
         // title: "set content", script: "getProto(this.target).data = aliasDocs(this.source.map(async p => await p));", params: ["target", "source"],  // bcz: doesn't look like we can do async stuff in scripting...
         title: "set content", script: "getProto(this.target).data = aliasDocs(this.source);", params: ["target", "source"],
+        initialize: emptyFunction,
         immediate: (draggedDocs: Doc[]) => Doc.GetProto(this.props.CollectionView.props.Document).data = new List<Doc>(draggedDocs.map((d: any) => Doc.MakeAlias(d)))
     };
     _viewCommand = {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index f7ebfb75a..4d5307c88 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -600,7 +600,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 this.makeBtnClicked();
             }, icon: "window-restore"
         });
-        makes.push({ description: this.props.Document.ignoreClick ? "Selectable" : "Unselectable", event: () => this.props.Document.ignoreClick = !this.props.Document.ignoreClick, icon: this.props.Document.ignoreClick ? "unlock" : "lock" })
+        makes.push({ description: this.props.Document.ignoreClick ? "Selectable" : "Unselectable", event: () => this.props.Document.ignoreClick = !this.props.Document.ignoreClick, icon: this.props.Document.ignoreClick ? "unlock" : "lock" });
         !existingMake && cm.addItem({ description: "Make...", subitems: makes, icon: "hand-point-right" });
         let existing = ContextMenu.Instance.findByDescription("Layout...");
         let layoutItems: ContextMenuProps[] = existing && "subitems" in existing ? existing.subitems : [];
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index c28bf1821..49189c5f3 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -622,7 +622,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             }
         }
 
-        if (this.props.Document[Id] == FormattedTextBox.SelectOnLoad) {
+        if (this.props.Document[Id] === FormattedTextBox.SelectOnLoad) {
             FormattedTextBox.SelectOnLoad = "";
             this.props.select(false);
         }
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index f0140d04b..642f58daf 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -18,7 +18,7 @@ import { Docs } from "../../documents/Documents";
 import { faStickyNote } from "@fortawesome/free-solid-svg-icons";
 import { library } from "@fortawesome/fontawesome-svg-core";
 
-library.add(faStickyNote)
+library.add(faStickyNote);
 
 @observer
 export class WebBox extends React.Component<FieldViewProps> {
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index e3bfea237..e5917fefc 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -163,7 +163,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
                         <div key={`${this.props.url}-placeholder-${i + 1}`} className="pdfviewer-placeholder"
                             style={{ width: this._pageSizes[i].width, height: this._pageSizes[i].height }}>
                             "PAGE IS LOADING... "
-                </div>)
+                </div>);
                     this.getPlaceholderPage(i);
                 }))));
             this.props.loaded(Math.max(...this._pageSizes.map(i => i.width)), this._pageSizes[0].height, this.props.pdf.numPages);
-- 
cgit v1.2.3-70-g09d2


From 5da0459ad76c614e455ea99798c940d4e93707bb Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Tue, 27 Aug 2019 17:29:58 -0400
Subject: multi-user marks.

---
 src/client/util/RichTextSchema.tsx          | 13 +++++++++++++
 src/client/views/nodes/FormattedTextBox.tsx | 11 ++++++++++-
 2 files changed, 23 insertions(+), 1 deletion(-)

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

diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index bbced3b77..76c45e6c1 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -1,6 +1,7 @@
 import { DOMOutputSpecArray, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model";
 import { bulletList, listItem, orderedList } from 'prosemirror-schema-list';
 import { TextSelection } from "prosemirror-state";
+import { Doc } from "../../new_fields/Doc";
 
 const pDOM: DOMOutputSpecArray = ["p", 0], blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"],
     preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0];
@@ -324,6 +325,18 @@ export const marks: { [index: string]: MarkSpec } = {
         }
     },
 
+    // the id of the user who entered the text
+    user_mark: {
+        attrs: {
+            userid: { default: "" }
+        },
+        toDOM(node: any) {
+            return ['span', {
+                style: `background: ${node.attrs.userid.indexOf(Doc.CurrentUserEmail) === -1 ? "rgba(255, 255, 0, 0.267)" : undefined};`
+            }];
+        }
+    },
+
 
     // :: MarkSpec Code font mark. Represented as a `<code>` element.
     code: {
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 49189c5f3..667bac3be 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -5,7 +5,7 @@ import { observer } from "mobx-react";
 import { baseKeymap } from "prosemirror-commands";
 import { history } from "prosemirror-history";
 import { keymap } from "prosemirror-keymap";
-import { Fragment, Node, Node as ProsNode, NodeType, Slice } from "prosemirror-model";
+import { Fragment, Node, Node as ProsNode, NodeType, Slice, Mark } from "prosemirror-model";
 import { EditorState, Plugin, Transaction, TextSelection } from "prosemirror-state";
 import { EditorView } from "prosemirror-view";
 import { DateField } from '../../../new_fields/DateField';
@@ -37,6 +37,7 @@ import { DocumentDecorations } from '../DocumentDecorations';
 import { DictationManager } from '../../util/DictationManager';
 import { ReplaceStep } from 'prosemirror-transform';
 import { DocumentType } from '../../documents/DocumentTypes';
+import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
 
 library.add(faEdit);
 library.add(faSmile, faTextHeight, faUpload);
@@ -171,6 +172,10 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
 
     dispatchTransaction = (tx: Transaction) => {
         if (this._editorView) {
+            var markerss = tx.storedMarks || (tx.selection.$to.parentOffset && tx.selection.$from.marks());
+            let newMarks = [...(markerss ? markerss.filter(m => m.type !== schema.marks.user_mark) : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail })];
+            tx.ensureMarks(newMarks);
+            tx.setStoredMarks(newMarks);
             const state = this._editorView.state.apply(tx);
             FormattedTextBox._toolTipTextMenu && (FormattedTextBox._toolTipTextMenu.HackToFixTextSelectionGlitch = true);
             this._editorView.updateState(state);
@@ -267,6 +272,10 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         if (this.props.Document !== SelectionManager.SelectedDocuments()[0].props.Document) {
             return;
         }
+        var markerss = this._editorView!.state.storedMarks || (this._editorView!.state.selection.$to.parentOffset && this._editorView!.state.selection.$from.marks());
+        let newMarks = [...(markerss ? markerss.filter(m => m.type !== schema.marks.user_mark) : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail })];
+        this._editorView!.state.storedMarks = newMarks;
+
         if (e.key === "R" && e.altKey) {
             e.stopPropagation();
             e.preventDefault();
-- 
cgit v1.2.3-70-g09d2


From a7515d1e80e32fcc19096c73335f624042b85d51 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 27 Aug 2019 21:20:52 -0400
Subject: added auto-reformatting after shift-tab.

---
 src/client/views/MainOverlayTextBox.tsx     |  10 +-
 src/client/views/nodes/FormattedTextBox.tsx | 144 +++++++++++++---------------
 2 files changed, 67 insertions(+), 87 deletions(-)

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

diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx
index f15b60347..755e5de14 100644
--- a/src/client/views/MainOverlayTextBox.tsx
+++ b/src/client/views/MainOverlayTextBox.tsx
@@ -50,14 +50,8 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
             (box?: FormattedTextBox) => {
                 const tb = this._textBox;
                 const container = tb && tb.props.ContainingCollectionView;
-                if (tb && container) { // this hacky section is needed to force the edited text box to completely recreate itself since things can get out synch -- specifically, the bullet label state which is computed when the dom elements are created
-                    var dl = DocListCast(container.props.Document[container.props.fieldKey]);
-                    let dli = dl.indexOf(tb.props.Document);
-                    if (dli !== -1) {
-                        let prev = dli > 0 ? dl[dli - 1] : undefined;
-                        tb.props.removeDocument && tb.props.removeDocument(tb.props.Document);
-                        setTimeout(() => Doc.AddDocToList(container.props.Document, container.props.fieldKey, tb.props.Document, prev, false, dli === 0), 0);
-                    }
+                if (tb && container) {
+                    tb.rebuildEditor();// this forces the edited text box to completely recreate itself to avoid get out of sync -- e.g., bullet labels are only computed when the dom elements are created
                 }
                 this._textBox = box;
                 if (box) {
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 667bac3be..5cbbc3b55 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -15,7 +15,7 @@ import { List } from '../../../new_fields/List';
 import { RichTextField, ToPlainText, FromPlainText } from "../../../new_fields/RichTextField";
 import { BoolCast, Cast, NumCast, StrCast, DateCast } from "../../../new_fields/Types";
 import { createSchema, makeInterface } from "../../../new_fields/Schema";
-import { Utils } from '../../../Utils';
+import { Utils, numberRange } from '../../../Utils';
 import { DocServer } from "../../DocServer";
 import { Docs, DocUtils } from '../../documents/Documents';
 import { DocumentManager } from '../../util/DocumentManager';
@@ -37,7 +37,7 @@ import { DocumentDecorations } from '../DocumentDecorations';
 import { DictationManager } from '../../util/DictationManager';
 import { ReplaceStep } from 'prosemirror-transform';
 import { DocumentType } from '../../documents/DocumentTypes';
-import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
+import { number } from 'prop-types';
 
 library.add(faEdit);
 library.add(faSmile, faTextHeight, faUpload);
@@ -69,25 +69,25 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     public static LayoutString(fieldStr: string = "data") {
         return FieldView.LayoutString(FormattedTextBox, fieldStr);
     }
-    public static Instance: FormattedTextBox;
-    private _ref: React.RefObject<HTMLDivElement>;
+    private static _toolTipTextMenu: TooltipTextMenu | undefined = undefined;
+    private _ref: React.RefObject<HTMLDivElement> = React.createRef();
     private _proseRef?: HTMLDivElement;
     private _editorView: Opt<EditorView>;
-    private static _toolTipTextMenu: TooltipTextMenu | undefined = undefined;
     private _applyingChange: boolean = false;
     private _linkClicked = "";
+    private _undoTyping?: UndoManager.Batch;
     private _reactionDisposer: Opt<IReactionDisposer>;
     private _searchReactionDisposer?: Lambda;
     private _textReactionDisposer: Opt<IReactionDisposer>;
     private _heightReactionDisposer: Opt<IReactionDisposer>;
     private _proxyReactionDisposer: Opt<IReactionDisposer>;
-    private pullReactionDisposer: Opt<IReactionDisposer>;
-    private pushReactionDisposer: Opt<IReactionDisposer>;
+    private _pullReactionDisposer: Opt<IReactionDisposer>;
+    private _pushReactionDisposer: Opt<IReactionDisposer>;
     private dropDisposer?: DragManager.DragDropDisposer;
-    public get CurrentDiv(): HTMLDivElement { return this._ref.current!; }
-    @observable _entered = false;
 
+    @observable private _entered = false;
     @observable public static InputBoxOverlay?: FormattedTextBox = undefined;
+    public static SelectOnLoad = "";
     public static InputBoxOverlayScroll: number = 0;
     public static IsFragment(html: string) {
         return html.indexOf("data-pm-slice") !== -1;
@@ -128,15 +128,13 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
 
     constructor(props: FieldViewProps) {
         super(props);
-        FormattedTextBox.Instance = this;
-        this._ref = React.createRef();
         if (this.props.isOverlay) {
             DragManager.StartDragFunctions.push(() => FormattedTextBox.InputBoxOverlay = undefined);
         }
-
-        document.addEventListener("paste", this.paste);
     }
 
+    public get CurrentDiv(): HTMLDivElement { return this._ref.current!; }
+
     @computed get extensionDoc() { return Doc.resolvedFieldDataDoc(this.dataDoc, this.props.fieldKey, "dummy"); }
 
     @computed get dataDoc() { return this.props.DataDoc && (BoolCast(this.props.Document.isTemplate) || BoolCast(this.props.DataDoc.isTemplate) || this.props.DataDoc.layout === this.props.Document) ? this.props.DataDoc : Doc.GetProto(this.props.Document); }
@@ -172,10 +170,12 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
 
     dispatchTransaction = (tx: Transaction) => {
         if (this._editorView) {
-            var markerss = tx.storedMarks || (tx.selection.$to.parentOffset && tx.selection.$from.marks());
-            let newMarks = [...(markerss ? markerss.filter(m => m.type !== schema.marks.user_mark) : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail })];
-            tx.ensureMarks(newMarks);
-            tx.setStoredMarks(newMarks);
+            // var markerss = tx.storedMarks || (tx.selection.$to.parentOffset && tx.selection.$from.marks());
+            // let newMarks = [...(markerss ? markerss.filter(m => m.type !== schema.marks.user_mark) : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail })];
+            // if (!this._down) {  // if the pointer is down, we're likely doing a drag selection.  If setStoreMarks is called during
+            //     tx.ensureMarks(newMarks);  // this operation, then it is likely (but not guaranteed) that nothing will be selected due to strange prosemirror behavior.
+            //     tx.setStoredMarks(newMarks);
+            // }
             const state = this._editorView.state.apply(tx);
             FormattedTextBox._toolTipTextMenu && (FormattedTextBox._toolTipTextMenu.HackToFixTextSelectionGlitch = true);
             this._editorView.updateState(state);
@@ -240,12 +240,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
 
     protected createDropTarget = (ele: HTMLDivElement) => {
         this._proseRef = ele;
-        if (this.dropDisposer) {
-            this.dropDisposer();
-        }
-        if (ele) {
-            this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } });
-        }
+        this.dropDisposer && this.dropDisposer();
+        ele && (this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } }));
     }
 
     @undoBatch
@@ -269,17 +265,12 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     }
 
     recordKeyHandler = (e: KeyboardEvent) => {
-        if (this.props.Document !== SelectionManager.SelectedDocuments()[0].props.Document) {
-            return;
-        }
-        var markerss = this._editorView!.state.storedMarks || (this._editorView!.state.selection.$to.parentOffset && this._editorView!.state.selection.$from.marks());
-        let newMarks = [...(markerss ? markerss.filter(m => m.type !== schema.marks.user_mark) : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail })];
-        this._editorView!.state.storedMarks = newMarks;
-
-        if (e.key === "R" && e.altKey) {
-            e.stopPropagation();
-            e.preventDefault();
-            this.recordBullet();
+        if (this.props.Document === SelectionManager.SelectedDocuments()[0].props.Document) {
+            if (e.key === "R" && e.altKey) {
+                e.stopPropagation();
+                e.preventDefault();
+                this.recordBullet();
+            }
         }
     }
 
@@ -323,15 +314,11 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     }
 
     private newListItems = (count: number) => {
-        let listItems: any[] = [];
-        for (let i = 0; i < count; i++) {
-            listItems.push(schema.nodes.list_item.create(undefined, schema.nodes.paragraph.create()));
-        }
-        return listItems;
+        return numberRange(count).map(x => schema.nodes.list_item.create(undefined, schema.nodes.paragraph.create()));
     }
 
-    componentDidMount() {
-        const config = {
+    @computed get config() {
+        return {
             schema,
             inpRules, //these currently don't do anything, but could eventually be helpful
             plugins: this.props.isOverlay ? [
@@ -350,7 +337,16 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                     keymap(buildKeymap(schema)),
                     keymap(baseKeymap),
                 ]
-        };
+        }
+    };
+
+    @action
+    rebuildEditor() {
+        this.setupEditor(this.config, this.dataDoc, this.props.fieldKey);
+    }
+
+    componentDidMount() {
+        document.addEventListener("paste", this.paste);
 
         if (!this.props.isOverlay) {
             this._proxyReactionDisposer = reaction(() => this.props.isSelected(),
@@ -373,13 +369,13 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             incomingValue => {
                 if (this._editorView && !this._applyingChange) {
                     let updatedState = JSON.parse(incomingValue);
-                    this._editorView.updateState(EditorState.fromJSON(config, updatedState));
+                    this._editorView.updateState(EditorState.fromJSON(this.config, updatedState));
                     this.tryUpdateHeight();
                 }
             }
         );
 
-        this.pullReactionDisposer = reaction(
+        this._pullReactionDisposer = reaction(
             () => this.props.Document[Pulls],
             () => {
                 if (!DocumentDecorations.hasPulledHack) {
@@ -390,7 +386,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             }
         );
 
-        this.pushReactionDisposer = reaction(
+        this._pushReactionDisposer = reaction(
             () => this.props.Document[Pushes],
             () => {
                 if (!DocumentDecorations.hasPushedHack) {
@@ -415,7 +411,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                     this.dataDoc.lastModified = undefined;
                 }
             }, { fireImmediately: true });
-        this.setupEditor(config, this.dataDoc, this.props.fieldKey);
+
+
+        this.setupEditor(this.config, this.dataDoc, this.props.fieldKey);
 
         this._searchReactionDisposer = reaction(() => {
             return StrCast(this.props.Document.search_string);
@@ -515,7 +513,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         }
     }
 
-
     clipboardTextSerializer = (slice: Slice): string => {
         let text = "", separated = true;
         const from = 0, to = slice.content.size;
@@ -615,6 +612,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             }
         }
         if (this._proseRef) {
+            this._editorView && this._editorView.destroy();
             this._editorView = new EditorView(this._proseRef, {
                 state: field && field.Data ? EditorState.fromJSON(config, JSON.parse(field.Data)) : EditorState.create(config),
                 dispatchTransaction: this.dispatchTransaction,
@@ -638,20 +636,20 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         else if (this.props.isOverlay) this._editorView!.focus();
     }
 
-    public static SelectOnLoad = "";
-
     componentWillUnmount() {
-        this._editorView && this._editorView.destroy();
         this._reactionDisposer && this._reactionDisposer();
         this._proxyReactionDisposer && this._proxyReactionDisposer();
         this._textReactionDisposer && this._textReactionDisposer();
-        this.pushReactionDisposer && this.pushReactionDisposer();
-        this.pullReactionDisposer && this.pullReactionDisposer();
+        this._pushReactionDisposer && this._pushReactionDisposer();
+        this._pullReactionDisposer && this._pullReactionDisposer();
         this._heightReactionDisposer && this._heightReactionDisposer();
         this._searchReactionDisposer && this._searchReactionDisposer();
+        document.removeEventListener("paste", this.paste);
     }
 
+    _down = false;
     onPointerDown = (e: React.PointerEvent): void => {
+        this._down = true;
         if (this.props.onClick && e.button === 0) {
             e.preventDefault();
         }
@@ -712,10 +710,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             e.preventDefault();
         }
     }
+
     onPointerUp = (e: React.PointerEvent): void => {
-        if (FormattedTextBox._toolTipTextMenu && FormattedTextBox._toolTipTextMenu.tooltip) {
-            //this._toolTipTextMenu.tooltip.style.opacity = "1";
-        }
+        this._down = false;
         if (e.buttons === 1 && this.props.isSelected() && !e.altKey) {
             e.stopPropagation();
         }
@@ -763,7 +760,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 return self._toolTipTextMenu = new TooltipTextMenu(_editorView, myprops);
             }
         });
-        //this.props.Document.tooltip = self._toolTipTextMenu;
     }
 
     tooltipLinkingMenuPlugin() {
@@ -781,13 +777,22 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             this._undoTyping = undefined;
         }
     }
-    public _undoTyping?: UndoManager.Batch;
     onKeyPress = (e: React.KeyboardEvent) => {
         if (e.key === "Escape") {
             SelectionManager.DeselectAll();
         }
         e.stopPropagation();
-        if (e.key === "Tab") e.preventDefault();
+        if (e.key === "Tab") {
+            e.preventDefault();
+            setTimeout(() => { // force re-rendering of bullet numbers that may have had their bullet labels change.  (Our prosemirrior code re-"marks" the changed bullets, but nothing causes the Dom to be re-rendered which is where the nubering takes place)
+                SelectionManager.DeselectAll();
+                SelectionManager.SelectDoc(DocumentManager.Instance.getDocumentView(this.props.Document, this.props.ContainingCollectionView)!, false);
+            }, 0);
+        }
+        var markerss = this._editorView!.state.storedMarks || (this._editorView!.state.selection.$to.parentOffset && this._editorView!.state.selection.$from.marks());
+        let newMarks = [...(markerss ? markerss.filter(m => m.type !== schema.marks.user_mark) : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail })];
+        this._editorView!.state.storedMarks = newMarks;
+
         // stop propagation doesn't seem to stop propagation of native keyboard events.
         // so we set a flag on the native event that marks that the event's been handled.
         (e.nativeEvent as any).DASHFormattedTextBoxHandled = true;
@@ -814,24 +819,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         }
     }
 
-    @action
-    onPointerEnter = (e: React.PointerEvent) => {
-        this._entered = true;
-    }
-    @action
-    onPointerLeave = (e: React.PointerEvent) => {
-        this._entered = false;
-    }
-
-    specificContextMenu = (e: React.MouseEvent): void => {
-        // let subitems: ContextMenuProps[] = [];
-        // subitems.push({
-        //     description: BoolCast(this.props.Document.autoHeight) ? "Manual Height" : "Auto Height",
-        //     event: action(() => Doc.GetProto(this.props.Document).autoHeight = !BoolCast(this.props.Document.autoHeight)), icon: "expand-arrows-alt"
-        // });
-        // ContextMenu.Instance.addItem({ description: "Text Funcs...", subitems: subitems, icon: "text-height" });
-    }
-
 
     render() {
         let self = this;
@@ -854,14 +841,13 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 onKeyDown={this.onKeyPress}
                 onFocus={this.onFocused}
                 onClick={this.onClick}
-                onContextMenu={this.specificContextMenu}
                 onBlur={this.onBlur}
                 onPointerUp={this.onPointerUp}
                 onPointerDown={this.onPointerDown}
                 onMouseDown={this.onMouseDown}
                 onWheel={this.onPointerWheel}
-                onPointerEnter={this.onPointerEnter}
-                onPointerLeave={this.onPointerLeave}
+                onPointerEnter={action(() => this._entered = true)}
+                onPointerLeave={action(() => this._entered = false)}
             >
                 <div className={`formattedTextBox-inner${rounded}`} ref={this.createDropTarget} style={{ whiteSpace: "pre-wrap" }} />
             </div>
-- 
cgit v1.2.3-70-g09d2


From 41c79ceb4b52965f374db750d06a04a94ecc8212 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 27 Aug 2019 21:21:30 -0400
Subject: from last

---
 src/client/views/nodes/FormattedTextBox.tsx | 6 ------
 1 file changed, 6 deletions(-)

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

diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 5cbbc3b55..ba558a0b2 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -170,12 +170,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
 
     dispatchTransaction = (tx: Transaction) => {
         if (this._editorView) {
-            // var markerss = tx.storedMarks || (tx.selection.$to.parentOffset && tx.selection.$from.marks());
-            // let newMarks = [...(markerss ? markerss.filter(m => m.type !== schema.marks.user_mark) : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail })];
-            // if (!this._down) {  // if the pointer is down, we're likely doing a drag selection.  If setStoreMarks is called during
-            //     tx.ensureMarks(newMarks);  // this operation, then it is likely (but not guaranteed) that nothing will be selected due to strange prosemirror behavior.
-            //     tx.setStoredMarks(newMarks);
-            // }
             const state = this._editorView.state.apply(tx);
             FormattedTextBox._toolTipTextMenu && (FormattedTextBox._toolTipTextMenu.HackToFixTextSelectionGlitch = true);
             this._editorView.updateState(state);
-- 
cgit v1.2.3-70-g09d2


From 1fbf7d7e10bb4dfa7e3a323ee0641d7bbf97b6a8 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 27 Aug 2019 23:24:18 -0400
Subject: fixed several lint errors, and minor issues with bullets

---
 src/client/util/DocumentManager.ts                 |  2 +-
 src/client/util/ProsemirrorExampleTransfer.ts      | 17 ++++-----
 src/client/util/RichTextSchema.tsx                 | 13 ++++---
 src/client/util/TooltipTextMenu.tsx                | 31 ++++++++++++----
 src/client/views/DocumentDecorations.tsx           |  2 +-
 .../views/collections/CollectionTreeView.tsx       | 42 +++++++++++-----------
 .../views/collections/CollectionViewChromes.tsx    |  2 +-
 src/client/views/nodes/FormattedTextBox.tsx        |  6 ++--
 8 files changed, 71 insertions(+), 44 deletions(-)

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

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 124faf266..ec731da84 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -204,4 +204,4 @@ export class DocumentManager {
         }
     }
 }
-Scripting.addGlobal(function focus(doc: any) { DocumentManager.Instance.getDocumentViews(Doc.GetProto(doc)).map(view => view.props.focus(doc, true)) })
\ No newline at end of file
+Scripting.addGlobal(function focus(doc: any) { DocumentManager.Instance.getDocumentViews(Doc.GetProto(doc)).map(view => view.props.focus(doc, true)); });
\ No newline at end of file
diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts
index 8b6936748..12ad28199 100644
--- a/src/client/util/ProsemirrorExampleTransfer.ts
+++ b/src/client/util/ProsemirrorExampleTransfer.ts
@@ -79,8 +79,7 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?:
 
     bind("Mod-s", TooltipTextMenu.insertStar);
 
-    // let nodeTypeMark = depth == 2 ? "upper-alpha" : depth == 4 ? "lower-roman" : depth == 6 ? "lower-alpha" : "decimal";
-    let nodeTypeMark = (depth: number) => { return depth == 2 ? "decimal2" : depth == 4 ? "decimal3" : depth == 6 ? "decimal4" : "decimal" }
+    let nodeTypeMark = (depth: number) => depth === 2 ? "indent2" : depth === 4 ? "indent3" : depth === 6 ? "indent4" : "indent1";
 
     let bulletFunc = (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
         var ref = state.selection;
@@ -90,7 +89,7 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?:
         if (!sinkListItem(schema.nodes.list_item)(state, (tx2: Transaction) => {
             const resolvedPos = tx2.doc.resolve(range!.start);
 
-            let path = (resolvedPos as any).path as any;
+            let path = (resolvedPos as any).path;
             for (let i = path.length - 1; i > 0; i--) {
                 if (path[i].type === schema.nodes.ordered_list) {
                     path[i].attrs.bulletStyle = nodeTypeMark(depth);
@@ -105,7 +104,7 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?:
             let newstate = state.applyTransaction(sxf);
             if (!wrapInList(schema.nodes.ordered_list)(newstate.state, (tx2: Transaction) => {
                 const resolvedPos = tx2.doc.resolve(Math.round((range!.start + range!.end) / 2));
-                let path = (resolvedPos as any).path as any;
+                let path = (resolvedPos as any).path;
                 for (let i = path.length - 1; i > 0; i--) {
                     if (path[i].type === schema.nodes.ordered_list) {
                         path[i].attrs.bulletStyle = nodeTypeMark(depth);
@@ -120,8 +119,9 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?:
                 console.log("bullet fail");
             }
         }
-    }
-    bind("Tab", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => bulletFunc(state, dispatch));
+    };
+
+    bind("Tab", bulletFunc);
 
     bind("Shift-Tab", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
         var ref = state.selection;
@@ -132,7 +132,7 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?:
             try {
                 const resolvedPos = tx2.doc.resolve(Math.round((range!.start + range!.end) / 2));
 
-                let path = (resolvedPos as any).path as any;
+                let path = (resolvedPos as any).path;
                 for (let i = path.length - 1; i > 0; i--) {
                     if (path[i].type === schema.nodes.ordered_list) {
                         path[i].attrs.bulletStyle = nodeTypeMark(depth);
@@ -159,7 +159,8 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?:
             if (!splitBlockKeepMarks(state, (tx3: Transaction) => {
                 marks && tx3.ensureMarks(marks);
                 marks && tx3.setStoredMarks(marks);
-                if (!liftListItem(schema.nodes.list_item)(state, (tx4: Transaction) => dispatch(tx4))) {
+                if (!liftListItem(schema.nodes.list_item)(state, dispatch as ((tx: Transaction<Schema<any, any>>) => void))
+                ) {
                     dispatch(tx3);
                 }
             })) {
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 76c45e6c1..4e18f410d 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -177,10 +177,15 @@ export const nodes: { [index: string]: NodeSpec } = {
         group: 'block',
         attrs: {
             bulletStyle: { default: "" },
+            mapStyle: { default: "decimal" }
         },
         toDOM(node: Node<any>) {
-            for (let i = 0; i < node.childCount; i++) node.child(i).attrs.className = node.attrs.bulletStyle;
-            return ['ol', { class: `${node.attrs.bulletStyle}-ol`, style: `list-style: none;` }, 0]
+            const bs = node.attrs.bulletStyle;
+            const decMap = bs === "indent1" ? "decimal" : bs === "indent2" ? "decimal2" : bs === "indent3" ? "decimal3" : bs === "indent4" ? "decimal4" : "";
+            const multiMap = bs === "indent1" ? "decimal" : bs === "indent2" ? "upper-alpha" : bs === "indent3" ? "lower-roman" : bs === "indent4" ? "lower-alpha" : "";
+            let map = node.attrs.mapStyle === "decimal" ? decMap : multiMap;
+            for (let i = 0; i < node.childCount; i++) node.child(i).attrs.className = map;
+            return ['ol', { class: `${map}-ol`, style: `list-style: none;` }, 0];
             //return ['ol', { class: `${node.attrs.bulletStyle}`, style: `list-style: ${node.attrs.bulletStyle};`, 0]
         }
     },
@@ -192,7 +197,7 @@ export const nodes: { [index: string]: NodeSpec } = {
         // parseDOM: [{ tag: "ul" }, { style: 'list-style-type=disc' }],
         toDOM(node: Node<any>) {
             for (let i = 0; i < node.childCount; i++) node.child(i).attrs.className = "";
-            return ['ul', 0]
+            return ['ul', 0];
         }
     },
 
@@ -302,7 +307,7 @@ export const marks: { [index: string]: MarkSpec } = {
         },
         toDOM(node: any) {
             return ['span', {
-                style: `background: ${node.attrs.bulletType == "decimal" ? "yellow" : node.attrs.bulletType === "upper-alpha" ? "blue" : "green"}`
+                style: `background: ${node.attrs.bulletType === "decimal" ? "yellow" : node.attrs.bulletType === "upper-alpha" ? "blue" : "green"}`
             }];
         }
     },
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 7f6ba3aac..e979e6cde 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -28,11 +28,11 @@ export class TooltipTextMenu {
     private view: EditorView;
     private fontStyles: MarkType[];
     private fontSizes: MarkType[];
-    private listTypes: NodeType[];
+    private listTypes: (NodeType | any)[];
     private editorProps: FieldViewProps & FormattedTextBoxProps;
     private fontSizeToNum: Map<MarkType, number>;
     private fontStylesToName: Map<MarkType, string>;
-    private listTypeToIcon: Map<NodeType, string>;
+    private listTypeToIcon: Map<NodeType | any, string>;
     //private link: HTMLAnchorElement;
     private wrapper: HTMLDivElement;
     private extras: HTMLDivElement;
@@ -179,7 +179,8 @@ export class TooltipTextMenu {
         //list types
         this.listTypeToIcon = new Map();
         this.listTypeToIcon.set(schema.nodes.bullet_list, ":");
-        this.listTypeToIcon.set(schema.nodes.ordered_list, "1)");
+        this.listTypeToIcon.set(schema.nodes.ordered_list.create({ mapStyle: "decimal" }), "1.1");
+        this.listTypeToIcon.set(schema.nodes.ordered_list.create({ mapStyle: "multi" }), "1.A");
         // this.listTypeToIcon.set(schema.nodes.bullet_list, "⬜");
         this.listTypes = Array.from(this.listTypeToIcon.keys());
 
@@ -512,10 +513,28 @@ export class TooltipTextMenu {
 
     //remove all node typeand apply the passed-in one to the selected text
     changeToNodeType(nodeType: NodeType | undefined, view: EditorView) {
-        //remove old
-        liftListItem(schema.nodes.list_item)(view.state, view.dispatch);
-        if (nodeType) { //add new
+        //remove oldif (nodeType) { //add new
+        if (nodeType === schema.nodes.bullet_list) {
             wrapInList(nodeType)(view.state, view.dispatch);
+        } else {
+            var ref = view.state.selection;
+            var range = ref.$from.blockRange(ref.$to);
+            var marks = view.state.storedMarks || (view.state.selection.$to.parentOffset && view.state.selection.$from.marks());
+            wrapInList(schema.nodes.ordered_list)(view.state, (tx2: any) => {
+                const resolvedPos = tx2.doc.resolve(Math.round((range!.start + range!.end) / 2));
+                let path = resolvedPos.path;
+                for (let i = path.length - 1; i > 0; i--) {
+                    if (path[i].type === schema.nodes.ordered_list) {
+                        path[i].attrs.bulletStyle = "indent1";
+                        path[i].attrs.mapStyle = (nodeType as any).attrs.mapStyle;
+                        break;
+                    }
+                }
+                marks && tx2.ensureMarks([...marks]);
+                marks && tx2.setStoredMarks([...marks]);
+
+                view.dispatch(tx2);
+            });
         }
     }
 
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index e93893586..203227241 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -201,7 +201,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
     }
 
     @observable _forceUpdate = 0;
-    _lastBox = { x: 0, y: 0, r: 0, b: 0 }
+    _lastBox = { x: 0, y: 0, r: 0, b: 0 };
     @computed
     get Bounds(): { x: number, y: number, b: number, r: number } {
         let x = this._forceUpdate;
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 04133fb5b..50f03005c 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -432,26 +432,28 @@ class TreeView extends React.Component<TreeViewProps> {
         }
 
         let ascending = Cast(containingCollection.sortAscending, "boolean", null);
-        if (ascending !== undefined) docs.sort(function (a, b): 1 | -1 {
-            let descA = ascending ? b : a;
-            let descB = ascending ? a : b;
-            let first = descA.title;
-            let second = descB.title;
-            // TODO find better way to sort how to sort..................
-            if (typeof first === 'number' && typeof second === 'number') {
-                return (first - second) > 0 ? 1 : -1;
-            }
-            if (typeof first === 'string' && typeof second === 'string') {
-                return first > second ? 1 : -1;
-            }
-            if (typeof first === 'boolean' && typeof second === 'boolean') {
-                // if (first === second) { // bugfixing?: otherwise, the list "flickers" because the list is resorted during every load
-                //     return Number(descA.x) > Number(descB.x) ? 1 : -1;
-                // }
-                return first > second ? 1 : -1;
-            }
-            return ascending ? 1 : -1;
-        });
+        if (ascending !== undefined) {
+            docs.sort(function (a, b): 1 | -1 {
+                let descA = ascending ? b : a;
+                let descB = ascending ? a : b;
+                let first = descA.title;
+                let second = descB.title;
+                // TODO find better way to sort how to sort..................
+                if (typeof first === 'number' && typeof second === 'number') {
+                    return (first - second) > 0 ? 1 : -1;
+                }
+                if (typeof first === 'string' && typeof second === 'string') {
+                    return first > second ? 1 : -1;
+                }
+                if (typeof first === 'boolean' && typeof second === 'boolean') {
+                    // if (first === second) { // bugfixing?: otherwise, the list "flickers" because the list is resorted during every load
+                    //     return Number(descA.x) > Number(descB.x) ? 1 : -1;
+                    // }
+                    return first > second ? 1 : -1;
+                }
+                return ascending ? 1 : -1;
+            });
+        }
 
         let rowWidth = () => panelWidth() - 20;
         return docs.map((child, i) => {
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index 4b3f7c87e..c897af17e 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -53,7 +53,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
     };
     _viewCommand = {
         title: "restore view", script: "this.target.panX = this.restoredPanX; this.target.panY = this.restoredPanY; this.target.scale = this.restoredScale;", params: ["target"],
-        immediate: (draggedDocs: Doc[]) => { this.props.CollectionView.props.Document.panX = 0; this.props.CollectionView.props.Document.panY = 0; this.props.CollectionView.props.Document.scale = 1 },
+        immediate: (draggedDocs: Doc[]) => { this.props.CollectionView.props.Document.panX = 0; this.props.CollectionView.props.Document.panY = 0; this.props.CollectionView.props.Document.scale = 1; },
         initialize: (button: Doc) => { button.restoredPanX = this.props.CollectionView.props.Document.panX; button.restoredPanY = this.props.CollectionView.props.Document.panY; button.restoredScale = this.props.CollectionView.props.Document.scale; }
     };
     _freeform_commands = [this._contentCommand, this._templateCommand, this._viewCommand];
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index ba558a0b2..36740fc66 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -331,8 +331,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                     keymap(buildKeymap(schema)),
                     keymap(baseKeymap),
                 ]
-        }
-    };
+        };
+    }
 
     @action
     rebuildEditor() {
@@ -776,7 +776,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             SelectionManager.DeselectAll();
         }
         e.stopPropagation();
-        if (e.key === "Tab") {
+        if (e.key === "Tab" || e.key === "Enter") { // bullets typically change "levels" when tab or enter is used.  sometimes backspcae, so maybe that should be added.
             e.preventDefault();
             setTimeout(() => { // force re-rendering of bullet numbers that may have had their bullet labels change.  (Our prosemirrior code re-"marks" the changed bullets, but nothing causes the Dom to be re-rendered which is where the nubering takes place)
                 SelectionManager.DeselectAll();
-- 
cgit v1.2.3-70-g09d2


From be517c301997b617ba642d7f0745a254a6510bf1 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Wed, 28 Aug 2019 09:34:52 -0400
Subject: fixed initial storedmarks

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

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

diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 36740fc66..1c5224e12 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -628,6 +628,10 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             this.props.select(false);
         }
         else if (this.props.isOverlay) this._editorView!.focus();
+        var markerss = this._editorView!.state.storedMarks || (this._editorView!.state.selection.$to.parentOffset && this._editorView!.state.selection.$from.marks());
+        let newMarks = [...(markerss ? markerss.filter(m => m.type !== schema.marks.user_mark) : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail })];
+        this._editorView!.state.storedMarks = newMarks;
+
     }
 
     componentWillUnmount() {
-- 
cgit v1.2.3-70-g09d2


From 4b7672c75fe5cdf6afe534e67213917b24980c3e Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Wed, 28 Aug 2019 15:02:20 -0400
Subject: added better support for usermarks and fledliging for accept changes.

---
 src/client/util/RichTextSchema.tsx           | 14 ++++++---
 src/client/views/nodes/FormattedTextBox.scss | 17 ++++++++++
 src/client/views/nodes/FormattedTextBox.tsx  | 47 +++++++++++++++++++++++++---
 3 files changed, 69 insertions(+), 9 deletions(-)

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

diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 6c06cec4d..f567d803e 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -333,12 +333,18 @@ export const marks: { [index: string]: MarkSpec } = {
     // the id of the user who entered the text
     user_mark: {
         attrs: {
-            userid: { default: "" }
+            userid: { default: "" },
+            hide_users: { default: [] },
+            opened: { default: false }
         },
+        group: "inline",
+        inclusive: false,
         toDOM(node: any) {
-            return ['span', {
-                style: `background: ${node.attrs.userid.indexOf(Doc.CurrentUserEmail) === -1 ? "rgba(255, 255, 0, 0.267)" : undefined};`
-            }];
+            let hideUsers = node.attrs.hide_users;
+            let hidden = hideUsers.indexOf(node.attrs.userid) !== -1 || (hideUsers.length === 0 && node.attrs.userid !== Doc.CurrentUserEmail);
+            return hidden ?
+                ['span', { class: node.attrs.opened ? "userMarkOpen" : "userMark" }, 0] :
+                ['span', 0];
         }
     },
 
diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss
index e93ceda21..03e81bfca 100644
--- a/src/client/views/nodes/FormattedTextBox.scss
+++ b/src/client/views/nodes/FormattedTextBox.scss
@@ -67,6 +67,23 @@
     font-style: italic;
 }
 
+.userMarkOpen {
+    background:  rgba(255, 255, 0, 0.267);
+    display: inline;
+}
+.userMark {
+    background:  rgba(255, 255, 0, 0.267);
+    font-size: 2px;
+    display: inline-grid;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    width:10px;
+    min-height:10px;
+    text-align:center;
+    align-content: center;
+}
+
 ol { counter-reset: deci 0;}
 .decimal-ol { counter-reset: deci 0; p { display: inline }; font-size: 24 }
 .decimal2-ol {counter-reset: deci2; p { display: inline }; font-size: 18 }
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 1c5224e12..146281f2b 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -5,8 +5,8 @@ import { observer } from "mobx-react";
 import { baseKeymap } from "prosemirror-commands";
 import { history } from "prosemirror-history";
 import { keymap } from "prosemirror-keymap";
-import { Fragment, Node, Node as ProsNode, NodeType, Slice, Mark } from "prosemirror-model";
-import { EditorState, Plugin, Transaction, TextSelection } from "prosemirror-state";
+import { Fragment, Node, Node as ProsNode, NodeType, Slice, Mark, ResolvedPos } from "prosemirror-model";
+import { EditorState, Plugin, Transaction, TextSelection, NodeSelection } from "prosemirror-state";
 import { EditorView } from "prosemirror-view";
 import { DateField } from '../../../new_fields/DateField';
 import { Doc, DocListCast, Opt, WidthSym } from "../../../new_fields/Doc";
@@ -645,9 +645,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         document.removeEventListener("paste", this.paste);
     }
 
-    _down = false;
     onPointerDown = (e: React.PointerEvent): void => {
-        this._down = true;
         if (this.props.onClick && e.button === 0) {
             e.preventDefault();
         }
@@ -709,8 +707,47 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         }
     }
 
+    findUserMark(marks: Mark[]) {
+        return marks.find(m => m.attrs && m.attrs.userid && m.attrs.userid !== Doc.CurrentUserEmail);
+    }
+    findStartOfMark(rpos: ResolvedPos) {
+        let before = 0;
+        let nbef = rpos.nodeBefore;
+        while (nbef && this.findUserMark(nbef.marks)) {
+            before += nbef.nodeSize;
+            rpos = this._editorView!.state.doc.resolve(rpos.pos - nbef.nodeSize);
+            rpos && (nbef = rpos.nodeBefore);
+        }
+        return before;
+    }
+    findEndOfMark(rpos: ResolvedPos) {
+        let after = 0;
+        let naft = rpos.nodeAfter;
+        while (naft && this.findUserMark(naft.marks)) {
+            after += naft.nodeSize;
+            rpos = this._editorView!.state.doc.resolve(rpos.pos + naft.nodeSize);
+            rpos && (naft = rpos.nodeAfter);
+        }
+        return after;
+    }
+
     onPointerUp = (e: React.PointerEvent): void => {
-        this._down = false;
+        let view = this._editorView!;
+        const pos = view.posAtCoords({ left: e.clientX, top: e.clientY });
+        const rpos = pos && view.state.doc.resolve(pos.pos);
+        if (pos && rpos && view.state.selection.$from === view.state.selection.$to) {
+            let nbef = this.findStartOfMark(rpos);
+            let naft = this.findEndOfMark(rpos);
+            const spos = view.state.doc.resolve(pos.pos - nbef);
+            const epos = view.state.doc.resolve(pos.pos + naft);
+            let ts = new TextSelection(spos, epos);
+            let child = rpos.nodeBefore;
+            let mark = child && this.findUserMark(child.marks);
+            if (mark && child && nbef && naft) {
+                let nmark = view.state.schema.marks.user_mark.create({ ...mark.attrs, userid: e.button === 2 ? Doc.CurrentUserEmail : mark.attrs.userid, opened: e.button === 2 ? false : !mark.attrs.opened });
+                view.dispatch(view.state.tr.setSelection(ts).removeMark(ts.from, ts.to, nmark).addMark(ts.from, ts.to, nmark).setSelection(new TextSelection(epos, epos)));
+            }
+        }
         if (e.buttons === 1 && this.props.isSelected() && !e.altKey) {
             e.stopPropagation();
         }
-- 
cgit v1.2.3-70-g09d2


From 18cc66aaa90c5054268eac9520a963867c0c2f8e Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 29 Aug 2019 01:14:18 -0400
Subject: fixed expand/collapse of text.  added user tooltip

---
 src/client/util/RichTextSchema.tsx                 |  5 +-
 src/client/views/nodes/FormattedTextBox.tsx        | 50 +++++---------
 .../views/nodes/FormattedTextBoxComment.scss       | 34 ++++++++++
 src/client/views/nodes/FormattedTextBoxComment.tsx | 78 ++++++++++++++++++++++
 4 files changed, 132 insertions(+), 35 deletions(-)
 create mode 100644 src/client/views/nodes/FormattedTextBoxComment.scss
 create mode 100644 src/client/views/nodes/FormattedTextBoxComment.tsx

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

diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index f567d803e..ee0c0870a 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -343,7 +343,10 @@ export const marks: { [index: string]: MarkSpec } = {
             let hideUsers = node.attrs.hide_users;
             let hidden = hideUsers.indexOf(node.attrs.userid) !== -1 || (hideUsers.length === 0 && node.attrs.userid !== Doc.CurrentUserEmail);
             return hidden ?
-                ['span', { class: node.attrs.opened ? "userMarkOpen" : "userMark" }, 0] :
+                (node.attrs.opened ?
+                    ['span', { class: "userMarkOpen" }, 0] :
+                    ['span', { class: "userMark" }, ['span', { style: "font-size:2" }, 0]]
+                ) :
                 ['span', 0];
         }
     },
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 146281f2b..2485760df 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -37,7 +37,7 @@ import { DocumentDecorations } from '../DocumentDecorations';
 import { DictationManager } from '../../util/DictationManager';
 import { ReplaceStep } from 'prosemirror-transform';
 import { DocumentType } from '../../documents/DocumentTypes';
-import { number } from 'prop-types';
+import { selectionSizePlugin, findStartOfMark, findUserMark, findEndOfMark, findOtherUserMark, SelectionSizeTooltip } from './FormattedTextBoxComment';
 
 library.add(faEdit);
 library.add(faSmile, faTextHeight, faUpload);
@@ -325,7 +325,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                     props: {
                         attributes: { class: "ProseMirror-example-setup-style" }
                     }
-                })
+                }),
+                selectionSizePlugin
             ] : [
                     history(),
                     keymap(buildKeymap(schema)),
@@ -643,6 +644,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         this._heightReactionDisposer && this._heightReactionDisposer();
         this._searchReactionDisposer && this._searchReactionDisposer();
         document.removeEventListener("paste", this.paste);
+        this._editorView && this._editorView.destroy();
     }
 
     onPointerDown = (e: React.PointerEvent): void => {
@@ -707,45 +709,25 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         }
     }
 
-    findUserMark(marks: Mark[]) {
-        return marks.find(m => m.attrs && m.attrs.userid && m.attrs.userid !== Doc.CurrentUserEmail);
-    }
-    findStartOfMark(rpos: ResolvedPos) {
-        let before = 0;
-        let nbef = rpos.nodeBefore;
-        while (nbef && this.findUserMark(nbef.marks)) {
-            before += nbef.nodeSize;
-            rpos = this._editorView!.state.doc.resolve(rpos.pos - nbef.nodeSize);
-            rpos && (nbef = rpos.nodeBefore);
-        }
-        return before;
-    }
-    findEndOfMark(rpos: ResolvedPos) {
-        let after = 0;
-        let naft = rpos.nodeAfter;
-        while (naft && this.findUserMark(naft.marks)) {
-            after += naft.nodeSize;
-            rpos = this._editorView!.state.doc.resolve(rpos.pos + naft.nodeSize);
-            rpos && (naft = rpos.nodeAfter);
-        }
-        return after;
-    }
-
     onPointerUp = (e: React.PointerEvent): void => {
         let view = this._editorView!;
         const pos = view.posAtCoords({ left: e.clientX, top: e.clientY });
         const rpos = pos && view.state.doc.resolve(pos.pos);
-        if (pos && rpos && view.state.selection.$from === view.state.selection.$to) {
-            let nbef = this.findStartOfMark(rpos);
-            let naft = this.findEndOfMark(rpos);
+        let noselection = view.state.selection.$from === view.state.selection.$to;
+        if (pos && rpos) {
+            let nbef = findStartOfMark(rpos, view, findOtherUserMark);
+            let naft = findEndOfMark(rpos, view, findOtherUserMark);
             const spos = view.state.doc.resolve(pos.pos - nbef);
             const epos = view.state.doc.resolve(pos.pos + naft);
             let ts = new TextSelection(spos, epos);
-            let child = rpos.nodeBefore;
-            let mark = child && this.findUserMark(child.marks);
-            if (mark && child && nbef && naft) {
-                let nmark = view.state.schema.marks.user_mark.create({ ...mark.attrs, userid: e.button === 2 ? Doc.CurrentUserEmail : mark.attrs.userid, opened: e.button === 2 ? false : !mark.attrs.opened });
-                view.dispatch(view.state.tr.setSelection(ts).removeMark(ts.from, ts.to, nmark).addMark(ts.from, ts.to, nmark).setSelection(new TextSelection(epos, epos)));
+            let child = rpos.nodeBefore || rpos.nodeAfter;
+            let mark = child && findOtherUserMark(child.marks);
+            if (mark && child && (nbef || naft) && (!mark.attrs.opened || noselection)) {
+                let opened = e.button === 2 ? false : !mark.attrs.opened;
+                SelectionSizeTooltip.tooltip.style.display = opened ? "" : "none";
+                let mid = opened ? epos : view.state.doc.resolve((spos.pos + epos.pos) / 2);
+                let nmark = view.state.schema.marks.user_mark.create({ ...mark.attrs, userid: e.button === 2 ? Doc.CurrentUserEmail : mark.attrs.userid, opened: opened });
+                view.dispatch(view.state.tr.addMark(ts.from, ts.to, nmark).setSelection(new TextSelection(mid, mid)));
             }
         }
         if (e.buttons === 1 && this.props.isSelected() && !e.altKey) {
diff --git a/src/client/views/nodes/FormattedTextBoxComment.scss b/src/client/views/nodes/FormattedTextBoxComment.scss
new file mode 100644
index 000000000..792cee182
--- /dev/null
+++ b/src/client/views/nodes/FormattedTextBoxComment.scss
@@ -0,0 +1,34 @@
+.FormattedTextBox-tooltip {
+    position: absolute;
+    pointer-events: none;
+    z-index: 20;
+    background: white;
+    border: 1px solid silver;
+    border-radius: 2px;
+    padding: 2px 10px;
+    margin-bottom: 7px;
+    -webkit-transform: translateX(-50%);
+    transform: translateX(-50%);
+  }
+  .FormattedTextBox-tooltip:before {
+    content: "";
+    height: 0; width: 0;
+    position: absolute;
+    left: 50%;
+    margin-left: -5px;
+    bottom: -6px;
+    border: 5px solid transparent;
+    border-bottom-width: 0;
+    border-top-color: silver;
+  }
+  .FormattedTextBox-tooltip:after {
+    content: "";
+    height: 0; width: 0;
+    position: absolute;
+    left: 50%;
+    margin-left: -5px;
+    bottom: -4.5px;
+    border: 5px solid transparent;
+    border-bottom-width: 0;
+    border-top-color: white;
+  }
\ No newline at end of file
diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx
new file mode 100644
index 000000000..e88c85a86
--- /dev/null
+++ b/src/client/views/nodes/FormattedTextBoxComment.tsx
@@ -0,0 +1,78 @@
+import { Plugin, EditorState, TextSelection } from "prosemirror-state"
+import './FormattedTextBoxComment.scss'
+import { DragManager } from "../../util/DragManager";
+import { ResolvedPos, Mark } from "prosemirror-model";
+import { EditorView } from "prosemirror-view";
+import { Doc } from "../../../new_fields/Doc";
+
+export let selectionSizePlugin = new Plugin({
+    view(editorView) { return new SelectionSizeTooltip(editorView); }
+})
+export function findOtherUserMark(marks: Mark[]): Mark | undefined {
+    return marks.find(m => m.attrs.userid && m.attrs.userid !== Doc.CurrentUserEmail);
+}
+export function findUserMark(marks: Mark[]): Mark | undefined {
+    return marks.find(m => m.attrs.userid);
+}
+export function findStartOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: Mark[]) => Mark | undefined) {
+    let before = 0;
+    let nbef = rpos.nodeBefore;
+    while (nbef && finder(nbef.marks)) {
+        before += nbef.nodeSize;
+        rpos = view.state.doc.resolve(rpos.pos - nbef.nodeSize);
+        rpos && (nbef = rpos.nodeBefore);
+    }
+    return before;
+}
+export function findEndOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: Mark[]) => Mark | undefined) {
+    let after = 0;
+    let naft = rpos.nodeAfter;
+    while (naft && finder(naft.marks)) {
+        after += naft.nodeSize;
+        rpos = view.state.doc.resolve(rpos.pos + naft.nodeSize);
+        rpos && (naft = rpos.nodeAfter);
+    }
+    return after;
+}
+
+export class SelectionSizeTooltip {
+    static tooltip: any;
+    constructor(view: any) {
+        if (!SelectionSizeTooltip.tooltip) {
+            SelectionSizeTooltip.tooltip = document.createElement("div");
+            SelectionSizeTooltip.tooltip.className = "FormattedTextBox-tooltip";
+            DragManager.Root().appendChild(SelectionSizeTooltip.tooltip);
+        }
+
+        this.update(view, undefined);
+    }
+
+
+    update(view: EditorView, lastState?: EditorState) {
+        let state = view.state;
+        // Don't do anything if the document/selection didn't change
+        if (lastState && lastState.doc.eq(state.doc) &&
+            lastState.selection.eq(state.selection)) return;
+
+        if (state.selection.$from) {
+            let nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark);
+            let naft = findEndOfMark(state.selection.$from, view, findOtherUserMark);
+            let child = state.selection.$from.nodeBefore;
+            let mark = child && findOtherUserMark(child.marks);
+            if (mark && child && nbef && naft && mark.attrs.opened && SelectionSizeTooltip.tooltip.offsetParent) {
+                SelectionSizeTooltip.tooltip.textContent = mark.attrs.userid;
+                // These are in screen coordinates
+                let start = view.coordsAtPos(state.selection.from), end = view.coordsAtPos(state.selection.to);
+                // The box in which the tooltip is positioned, to use as base
+                let box = SelectionSizeTooltip.tooltip.offsetParent.getBoundingClientRect();
+                // Find a center-ish x position from the selection endpoints (when
+                // crossing lines, end may be more to the left)
+                let left = Math.max((start.left + end.left) / 2, start.left + 3);
+                SelectionSizeTooltip.tooltip.style.left = (left - box.left) + "px";
+                SelectionSizeTooltip.tooltip.style.bottom = (box.bottom - start.top) + "px";
+            }
+        }
+    }
+
+    destroy() { SelectionSizeTooltip.tooltip.style.display = "none" }
+}
-- 
cgit v1.2.3-70-g09d2


From 1079d90c5d6752a0a2c06a25d42c1192cb433ed3 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 29 Aug 2019 10:09:03 -0400
Subject: made text author tooltip interactive.

---
 src/client/util/RichTextSchema.tsx                 |  2 +-
 src/client/views/nodes/FormattedTextBox.tsx        | 21 ++++++-----
 src/client/views/nodes/FormattedTextBoxComment.tsx | 41 +++++++++++++++++-----
 3 files changed, 47 insertions(+), 17 deletions(-)

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

diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index ee0c0870a..f8da98f17 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -335,7 +335,7 @@ export const marks: { [index: string]: MarkSpec } = {
         attrs: {
             userid: { default: "" },
             hide_users: { default: [] },
-            opened: { default: false }
+            opened: { default: true }
         },
         group: "inline",
         inclusive: false,
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 2485760df..c23b85e83 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -714,27 +714,32 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         const pos = view.posAtCoords({ left: e.clientX, top: e.clientY });
         const rpos = pos && view.state.doc.resolve(pos.pos);
         let noselection = view.state.selection.$from === view.state.selection.$to;
+        let set = false;
         if (pos && rpos) {
             let nbef = findStartOfMark(rpos, view, findOtherUserMark);
             let naft = findEndOfMark(rpos, view, findOtherUserMark);
-            const spos = view.state.doc.resolve(pos.pos - nbef);
-            const epos = view.state.doc.resolve(pos.pos + naft);
-            let ts = new TextSelection(spos, epos);
+            const spos = pos.pos - nbef;
+            const epos = pos.pos + naft;
             let child = rpos.nodeBefore || rpos.nodeAfter;
             let mark = child && findOtherUserMark(child.marks);
             if (mark && child && (nbef || naft) && (!mark.attrs.opened || noselection)) {
-                let opened = e.button === 2 ? false : !mark.attrs.opened;
-                SelectionSizeTooltip.tooltip.style.display = opened ? "" : "none";
-                let mid = opened ? epos : view.state.doc.resolve((spos.pos + epos.pos) / 2);
-                let nmark = view.state.schema.marks.user_mark.create({ ...mark.attrs, userid: e.button === 2 ? Doc.CurrentUserEmail : mark.attrs.userid, opened: opened });
-                view.dispatch(view.state.tr.addMark(ts.from, ts.to, nmark).setSelection(new TextSelection(mid, mid)));
+                SelectionSizeTooltip.SetState(this, mark.attrs.opened, spos, epos, mark);
+                set = true;
             }
         }
+        !set && SelectionSizeTooltip.Hide();
         if (e.buttons === 1 && this.props.isSelected() && !e.altKey) {
             e.stopPropagation();
         }
     }
 
+    setAnnotation = (start: number, end: number, mark: Mark, opened: boolean, keep: boolean = false) => {
+        let view = this._editorView!;
+        let mid = view.state.doc.resolve(Math.round((start + end) / 2));
+        let nmark = view.state.schema.marks.user_mark.create({ ...mark.attrs, userid: keep ? Doc.CurrentUserEmail : mark.attrs.userid, opened: opened });
+        view.dispatch(view.state.tr.removeMark(start, end, nmark).addMark(start, end, nmark).setSelection(new TextSelection(mid, mid)));
+    }
+
     @action
     onFocused = (e: React.FocusEvent): void => {
         document.removeEventListener("keypress", this.recordKeyHandler);
diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx
index e88c85a86..31eb06427 100644
--- a/src/client/views/nodes/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/FormattedTextBoxComment.tsx
@@ -1,6 +1,5 @@
-import { Plugin, EditorState, TextSelection } from "prosemirror-state"
+import { Plugin, EditorState } from "prosemirror-state"
 import './FormattedTextBoxComment.scss'
-import { DragManager } from "../../util/DragManager";
 import { ResolvedPos, Mark } from "prosemirror-model";
 import { EditorView } from "prosemirror-view";
 import { Doc } from "../../../new_fields/Doc";
@@ -35,18 +34,43 @@ export function findEndOfMark(rpos: ResolvedPos, view: EditorView, finder: (mark
     return after;
 }
 
+
 export class SelectionSizeTooltip {
-    static tooltip: any;
+    static tooltip: HTMLElement;
+    static start: number;
+    static end: number;
+    static mark: Mark;
+    static opened: boolean;
+    static textBox: any;
     constructor(view: any) {
         if (!SelectionSizeTooltip.tooltip) {
+            const root = document.getElementById("root");
             SelectionSizeTooltip.tooltip = document.createElement("div");
             SelectionSizeTooltip.tooltip.className = "FormattedTextBox-tooltip";
-            DragManager.Root().appendChild(SelectionSizeTooltip.tooltip);
+            SelectionSizeTooltip.tooltip.style.pointerEvents = "all";
+            SelectionSizeTooltip.tooltip.onpointerdown = (e: PointerEvent) => {
+                SelectionSizeTooltip.opened = !SelectionSizeTooltip.opened;
+                SelectionSizeTooltip.textBox.setAnnotation(
+                    SelectionSizeTooltip.start, SelectionSizeTooltip.end, SelectionSizeTooltip.mark,
+                    SelectionSizeTooltip.opened, e.button == 2);
+            };
+            root && root.appendChild(SelectionSizeTooltip.tooltip);
         }
-
         this.update(view, undefined);
     }
 
+    public static Hide() {
+        SelectionSizeTooltip.textBox = undefined;
+        SelectionSizeTooltip.tooltip && (SelectionSizeTooltip.tooltip.style.display = "none");
+    }
+    public static SetState(textBox: any, opened: boolean, start: number, end: number, mark: Mark) {
+        SelectionSizeTooltip.textBox = textBox;
+        SelectionSizeTooltip.start = start;
+        SelectionSizeTooltip.end = end;
+        SelectionSizeTooltip.mark = mark;
+        SelectionSizeTooltip.opened = opened;
+        SelectionSizeTooltip.textBox && SelectionSizeTooltip.tooltip && (SelectionSizeTooltip.tooltip.style.display = "");
+    }
 
     update(view: EditorView, lastState?: EditorState) {
         let state = view.state;
@@ -59,12 +83,13 @@ export class SelectionSizeTooltip {
             let naft = findEndOfMark(state.selection.$from, view, findOtherUserMark);
             let child = state.selection.$from.nodeBefore;
             let mark = child && findOtherUserMark(child.marks);
-            if (mark && child && nbef && naft && mark.attrs.opened && SelectionSizeTooltip.tooltip.offsetParent) {
+            if (mark && child && nbef && naft) {
                 SelectionSizeTooltip.tooltip.textContent = mark.attrs.userid;
                 // These are in screen coordinates
-                let start = view.coordsAtPos(state.selection.from), end = view.coordsAtPos(state.selection.to);
+                // let start = view.coordsAtPos(state.selection.from), end = view.coordsAtPos(state.selection.to);
+                let start = view.coordsAtPos(state.selection.from - nbef), end = view.coordsAtPos(state.selection.from - nbef);
                 // The box in which the tooltip is positioned, to use as base
-                let box = SelectionSizeTooltip.tooltip.offsetParent.getBoundingClientRect();
+                let box = (document.getElementById("main-div") as any).getBoundingClientRect();
                 // Find a center-ish x position from the selection endpoints (when
                 // crossing lines, end may be more to the left)
                 let left = Math.max((start.left + end.left) / 2, start.left + 3);
-- 
cgit v1.2.3-70-g09d2


From a46ae95ed6570342f03b7590ff70d6249e56f059 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 29 Aug 2019 10:34:08 -0400
Subject: added a timestamp for edit marks.

---
 src/client/util/RichTextSchema.tsx                 |  3 ++-
 src/client/views/nodes/FormattedTextBox.tsx        | 18 +++++++++++++++++-
 src/client/views/nodes/FormattedTextBoxComment.tsx |  2 +-
 3 files changed, 20 insertions(+), 3 deletions(-)

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

diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index f8da98f17..a642ee46c 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -335,7 +335,8 @@ export const marks: { [index: string]: MarkSpec } = {
         attrs: {
             userid: { default: "" },
             hide_users: { default: [] },
-            opened: { default: true }
+            opened: { default: true },
+            modified: { default: "when?" }
         },
         group: "inline",
         inclusive: false,
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index c23b85e83..0d4376d8d 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -38,6 +38,7 @@ import { DictationManager } from '../../util/DictationManager';
 import { ReplaceStep } from 'prosemirror-transform';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { selectionSizePlugin, findStartOfMark, findUserMark, findEndOfMark, findOtherUserMark, SelectionSizeTooltip } from './FormattedTextBoxComment';
+import { date } from 'serializr';
 
 library.add(faEdit);
 library.add(faSmile, faTextHeight, faUpload);
@@ -811,8 +812,23 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 SelectionManager.SelectDoc(DocumentManager.Instance.getDocumentView(this.props.Document, this.props.ContainingCollectionView)!, false);
             }, 0);
         }
+        function timenow() {
+            var now = new Date();
+            let ampm = 'am';
+            let h = now.getHours();
+            let m: any = now.getMinutes();
+            let s: any = now.getSeconds();
+            if (h >= 12) {
+                if (h > 12) h -= 12;
+                ampm = 'pm';
+            }
+
+            if (m < 10) m = '0' + m;
+            if (s < 10) s = '0' + s;
+            return now.toLocaleDateString() + ' ' + h + ':' + m + ':' + s + ' ' + ampm;
+        }
         var markerss = this._editorView!.state.storedMarks || (this._editorView!.state.selection.$to.parentOffset && this._editorView!.state.selection.$from.marks());
-        let newMarks = [...(markerss ? markerss.filter(m => m.type !== schema.marks.user_mark) : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail })];
+        let newMarks = [...(markerss ? markerss.filter(m => m.type !== schema.marks.user_mark) : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() })];
         this._editorView!.state.storedMarks = newMarks;
 
         // stop propagation doesn't seem to stop propagation of native keyboard events.
diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx
index 31eb06427..4ec0d6064 100644
--- a/src/client/views/nodes/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/FormattedTextBoxComment.tsx
@@ -84,7 +84,7 @@ export class SelectionSizeTooltip {
             let child = state.selection.$from.nodeBefore;
             let mark = child && findOtherUserMark(child.marks);
             if (mark && child && nbef && naft) {
-                SelectionSizeTooltip.tooltip.textContent = mark.attrs.userid;
+                SelectionSizeTooltip.tooltip.textContent = mark.attrs.userid + " " + mark.attrs.modified;
                 // These are in screen coordinates
                 // let start = view.coordsAtPos(state.selection.from), end = view.coordsAtPos(state.selection.to);
                 let start = view.coordsAtPos(state.selection.from - nbef), end = view.coordsAtPos(state.selection.from - nbef);
-- 
cgit v1.2.3-70-g09d2


From ea25f19b08fba66b1ec281afdadf719ae6a6ed54 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 31 Aug 2019 00:13:33 -0400
Subject: fixed some hacks so that undo/redo works for ordered lists

---
 src/client/util/RichTextSchema.tsx          | 8 ++++++++
 src/client/views/MainOverlayTextBox.tsx     | 5 -----
 src/client/views/nodes/FormattedTextBox.tsx | 8 +++-----
 3 files changed, 11 insertions(+), 10 deletions(-)

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

diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index c74881680..3161467b8 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -573,6 +573,14 @@ export class ImageResizeView {
     }
 }
 
+export class OrderedListView {
+    constructor(node: any, view: any, getPos: any) { }
+
+    update(node: any) {
+        return false; // if attr's of an ordered_list (e.g., bulletStyle) change, return false forces the dom node to be recreated which is necessary for the bullet labels to update
+    }
+}
+
 export class SummarizedView {
     // TODO: highlight text that is summarized. to find end of region, walk along mark
     _collapsed: HTMLElement;
diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx
index 755e5de14..27e0d181f 100644
--- a/src/client/views/MainOverlayTextBox.tsx
+++ b/src/client/views/MainOverlayTextBox.tsx
@@ -48,11 +48,6 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
         MainOverlayTextBox.Instance = this;
         reaction(() => FormattedTextBox.InputBoxOverlay,
             (box?: FormattedTextBox) => {
-                const tb = this._textBox;
-                const container = tb && tb.props.ContainingCollectionView;
-                if (tb && container) {
-                    tb.rebuildEditor();// this forces the edited text box to completely recreate itself to avoid get out of sync -- e.g., bullet labels are only computed when the dom elements are created
-                }
                 this._textBox = box;
                 if (box) {
                     this.ChromeHeight = box.props.ChromeHeight;
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 0d4376d8d..0f349780e 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -22,7 +22,7 @@ import { DocumentManager } from '../../util/DocumentManager';
 import { DragManager } from "../../util/DragManager";
 import buildKeymap from "../../util/ProsemirrorExampleTransfer";
 import { inpRules } from "../../util/RichTextRules";
-import { ImageResizeView, schema, SummarizedView } from "../../util/RichTextSchema";
+import { ImageResizeView, schema, SummarizedView, OrderedListView } from "../../util/RichTextSchema";
 import { SelectionManager } from "../../util/SelectionManager";
 import { TooltipLinkingMenu } from "../../util/TooltipLinkingMenu";
 import { TooltipTextMenu } from "../../util/TooltipTextMenu";
@@ -615,6 +615,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 nodeViews: {
                     image(node, view, getPos) { return new ImageResizeView(node, view, getPos); },
                     star(node, view, getPos) { return new SummarizedView(node, view, getPos); },
+                    ordered_list(node, view, getPos) { return new OrderedListView(node, view, getPos); }
+
                 },
                 clipboardTextSerializer: this.clipboardTextSerializer,
                 handlePaste: this.handlePaste,
@@ -807,10 +809,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         e.stopPropagation();
         if (e.key === "Tab" || e.key === "Enter") { // bullets typically change "levels" when tab or enter is used.  sometimes backspcae, so maybe that should be added.
             e.preventDefault();
-            setTimeout(() => { // force re-rendering of bullet numbers that may have had their bullet labels change.  (Our prosemirrior code re-"marks" the changed bullets, but nothing causes the Dom to be re-rendered which is where the nubering takes place)
-                SelectionManager.DeselectAll();
-                SelectionManager.SelectDoc(DocumentManager.Instance.getDocumentView(this.props.Document, this.props.ContainingCollectionView)!, false);
-            }, 0);
         }
         function timenow() {
             var now = new Date();
-- 
cgit v1.2.3-70-g09d2


From dc7990e969bff201fefe050ac321b2d2d1d58059 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 31 Aug 2019 13:27:17 -0400
Subject: added text tooltip for links

---
 src/client/views/nodes/FormattedTextBox.tsx        | 19 +--------
 src/client/views/nodes/FormattedTextBoxComment.tsx | 46 +++++++++++++++++++++-
 2 files changed, 45 insertions(+), 20 deletions(-)

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

diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 0f349780e..93ac2f06f 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -713,24 +713,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     }
 
     onPointerUp = (e: React.PointerEvent): void => {
-        let view = this._editorView!;
-        const pos = view.posAtCoords({ left: e.clientX, top: e.clientY });
-        const rpos = pos && view.state.doc.resolve(pos.pos);
-        let noselection = view.state.selection.$from === view.state.selection.$to;
-        let set = false;
-        if (pos && rpos) {
-            let nbef = findStartOfMark(rpos, view, findOtherUserMark);
-            let naft = findEndOfMark(rpos, view, findOtherUserMark);
-            const spos = pos.pos - nbef;
-            const epos = pos.pos + naft;
-            let child = rpos.nodeBefore || rpos.nodeAfter;
-            let mark = child && findOtherUserMark(child.marks);
-            if (mark && child && (nbef || naft) && (!mark.attrs.opened || noselection)) {
-                SelectionSizeTooltip.SetState(this, mark.attrs.opened, spos, epos, mark);
-                set = true;
-            }
-        }
-        !set && SelectionSizeTooltip.Hide();
+        SelectionSizeTooltip.textBox = this;
         if (e.buttons === 1 && this.props.isSelected() && !e.altKey) {
             e.stopPropagation();
         }
diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx
index 0287d93a9..c368a3f34 100644
--- a/src/client/views/nodes/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/FormattedTextBoxComment.tsx
@@ -3,6 +3,10 @@ import './FormattedTextBoxComment.scss';
 import { ResolvedPos, Mark } from "prosemirror-model";
 import { EditorView } from "prosemirror-view";
 import { Doc } from "../../../new_fields/Doc";
+import { schema } from "../../util/RichTextSchema";
+import { DocServer } from "../../DocServer";
+import { Utils } from "../../../Utils";
+import { StrCast } from "../../../new_fields/Types";
 
 export let selectionSizePlugin = new Plugin({
     view(editorView) { return new SelectionSizeTooltip(editorView); }
@@ -13,6 +17,9 @@ export function findOtherUserMark(marks: Mark[]): Mark | undefined {
 export function findUserMark(marks: Mark[]): Mark | undefined {
     return marks.find(m => m.attrs.userid);
 }
+export function findLinkMark(marks: Mark[]): Mark | undefined {
+    return marks.find(m => m.type === schema.marks.link);
+}
 export function findStartOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: Mark[]) => Mark | undefined) {
     let before = 0;
     let nbef = rpos.nodeBefore;
@@ -57,7 +64,7 @@ export class SelectionSizeTooltip {
             SelectionSizeTooltip.tooltip.onpointerdown = (e: PointerEvent) => {
                 let keep = e.target && (e.target as any).type === "checkbox";
                 SelectionSizeTooltip.opened = keep || !SelectionSizeTooltip.opened;
-                SelectionSizeTooltip.textBox.setAnnotation(
+                SelectionSizeTooltip.textBox && SelectionSizeTooltip.textBox.setAnnotation(
                     SelectionSizeTooltip.start, SelectionSizeTooltip.end, SelectionSizeTooltip.mark,
                     SelectionSizeTooltip.opened, keep);
             };
@@ -76,7 +83,7 @@ export class SelectionSizeTooltip {
         SelectionSizeTooltip.end = end;
         SelectionSizeTooltip.mark = mark;
         SelectionSizeTooltip.opened = opened;
-        SelectionSizeTooltip.textBox && SelectionSizeTooltip.tooltip && (SelectionSizeTooltip.tooltip.style.display = "");
+        SelectionSizeTooltip.tooltip && (SelectionSizeTooltip.tooltip.style.display = "");
     }
 
     update(view: EditorView, lastState?: EditorState) {
@@ -85,11 +92,18 @@ export class SelectionSizeTooltip {
         if (lastState && lastState.doc.eq(state.doc) &&
             lastState.selection.eq(state.selection)) return;
 
+        let set = "none"
         if (state.selection.$from) {
             let nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark);
             let naft = findEndOfMark(state.selection.$from, view, findOtherUserMark);
+            const spos = state.selection.$from.pos - nbef;
+            const epos = state.selection.$from.pos + naft;
             let child = state.selection.$from.nodeBefore;
             let mark = child && findOtherUserMark(child.marks);
+            let noselection = view.state.selection.$from === view.state.selection.$to;
+            if (mark && child && (nbef || naft) && (!mark.attrs.opened || noselection)) {
+                SelectionSizeTooltip.SetState(this, mark.attrs.opened, spos, epos, mark);
+            }
             if (mark && child && nbef && naft) {
                 SelectionSizeTooltip.tooltipText.textContent = mark.attrs.userid + " " + mark.attrs.modified;
                 // These are in screen coordinates
@@ -102,8 +116,36 @@ export class SelectionSizeTooltip {
                 let left = Math.max((start.left + end.left) / 2, start.left + 3);
                 SelectionSizeTooltip.tooltip.style.left = (left - box.left) + "px";
                 SelectionSizeTooltip.tooltip.style.bottom = (box.bottom - start.top) + "px";
+                set = "";
+            }
+        }
+        if (set === "none" && state.selection.$from) {
+            SelectionSizeTooltip.textBox = undefined;
+            let nbef = findStartOfMark(state.selection.$from, view, findLinkMark);
+            let naft = findEndOfMark(state.selection.$from, view, findLinkMark);
+            let child = state.selection.$from.nodeBefore;
+            let mark = child && findLinkMark(child.marks);
+            if (mark && child && nbef && naft) {
+                SelectionSizeTooltip.tooltipText.textContent = "link : " + (mark.attrs.title || mark.attrs.href);
+                if (mark.attrs.href.indexOf(Utils.prepend("/doc/")) === 0) {
+                    let docTarget = mark.attrs.href.replace(Utils.prepend("/doc/"), "").split("?")[0];
+                    docTarget && DocServer.GetRefField(docTarget).then(linkDoc =>
+                        (linkDoc as Doc) && (SelectionSizeTooltip.tooltipText.textContent = "link :" + StrCast((linkDoc as Doc)!.title)));
+                }
+                // These are in screen coordinates
+                // let start = view.coordsAtPos(state.selection.from), end = view.coordsAtPos(state.selection.to);
+                let start = view.coordsAtPos(state.selection.from - nbef), end = view.coordsAtPos(state.selection.from - nbef);
+                // The box in which the tooltip is positioned, to use as base
+                let box = (document.getElementById("main-div") as any).getBoundingClientRect();
+                // Find a center-ish x position from the selection endpoints (when
+                // crossing lines, end may be more to the left)
+                let left = Math.max((start.left + end.left) / 2, start.left + 3);
+                SelectionSizeTooltip.tooltip.style.left = (left - box.left) + "px";
+                SelectionSizeTooltip.tooltip.style.bottom = (box.bottom - start.top) + "px";
+                set = "";
             }
         }
+        SelectionSizeTooltip.tooltip && (SelectionSizeTooltip.tooltip.style.display = set);
     }
 
     destroy() { SelectionSizeTooltip.tooltip.style.display = "none"; }
-- 
cgit v1.2.3-70-g09d2


From 4a1d94325d5d1b5641cd280e89c442c114074e8d Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 3 Sep 2019 08:03:43 -0400
Subject: cleanup names for formattedTextBoxComment stuff.  fixed brushing bug
 with user_mark.

---
 src/client/util/RichTextSchema.tsx                 |  2 +-
 src/client/util/TooltipTextMenu.tsx                |  6 +-
 src/client/views/nodes/FormattedTextBox.tsx        | 12 ++--
 src/client/views/nodes/FormattedTextBoxComment.tsx | 70 +++++++++++-----------
 4 files changed, 41 insertions(+), 49 deletions(-)

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

diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 3161467b8..7911cf629 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -346,7 +346,7 @@ export const marks: { [index: string]: MarkSpec } = {
             return hidden ?
                 (node.attrs.opened ?
                     ['span', { class: "userMarkOpen" }, 0] :
-                    ['span', { class: "userMark" }, ['span', { style: "font-size:2" }, 0]]
+                    ['span', { class: "userMark" }, ['span', 0]]
                 ) :
                 ['span', 0];
         }
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 3b4b7f05a..ce7b04e31 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -60,8 +60,6 @@ export class TooltipTextMenu {
     @observable
     private _storedMarks: Mark<any>[] | null | undefined;
 
-    public HackToFixTextSelectionGlitch: boolean = false;
-
 
     constructor(view: EditorView, editorProps: FieldViewProps & FormattedTextBoxProps) {
         this.view = view;
@@ -629,7 +627,7 @@ export class TooltipTextMenu {
                 if (!this.view.state.selection.empty && $from && $from.nodeAfter) {
                     if (this._brushMarks && to - from > 0) {
                         this.view.dispatch(this.view.state.tr.removeMark(from, to));
-                        this._brushMarks.forEach((mark: Mark) => {
+                        Array.from(this._brushMarks).filter(m => m.type !== schema.marks.user_mark).forEach((mark: Mark) => {
                             const markType = mark.type;
                             this.changeToMarkInGroup(markType, this.view, []);
 
@@ -876,8 +874,6 @@ export class TooltipTextMenu {
                 this.updateFontSizeDropdown("Various");
             }
         }
-        !this.HackToFixTextSelectionGlitch &&
-            this.view.dispatch(this.view.state.tr.setStoredMarks(this._activeMarks)); // bcz: what's the purpose of this line?  It messes up text selection without the Hack.
 
         this.update_mark_doms();
     }
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 93ac2f06f..6232dd3ab 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -37,8 +37,7 @@ import { DocumentDecorations } from '../DocumentDecorations';
 import { DictationManager } from '../../util/DictationManager';
 import { ReplaceStep } from 'prosemirror-transform';
 import { DocumentType } from '../../documents/DocumentTypes';
-import { selectionSizePlugin, findStartOfMark, findUserMark, findEndOfMark, findOtherUserMark, SelectionSizeTooltip } from './FormattedTextBoxComment';
-import { date } from 'serializr';
+import { formattedTextBoxCommentPlugin, FormattedTextBoxComment } from './FormattedTextBoxComment';
 
 library.add(faEdit);
 library.add(faSmile, faTextHeight, faUpload);
@@ -172,9 +171,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     dispatchTransaction = (tx: Transaction) => {
         if (this._editorView) {
             const state = this._editorView.state.apply(tx);
-            FormattedTextBox._toolTipTextMenu && (FormattedTextBox._toolTipTextMenu.HackToFixTextSelectionGlitch = true);
             this._editorView.updateState(state);
-            FormattedTextBox._toolTipTextMenu && (FormattedTextBox._toolTipTextMenu.HackToFixTextSelectionGlitch = false);
             if (state.selection.empty && FormattedTextBox._toolTipTextMenu && tx.storedMarks) {
                 FormattedTextBox._toolTipTextMenu.mark_key_pressed(tx.storedMarks);
             }
@@ -327,7 +324,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                         attributes: { class: "ProseMirror-example-setup-style" }
                     }
                 }),
-                selectionSizePlugin
+                formattedTextBoxCommentPlugin
             ] : [
                     history(),
                     keymap(buildKeymap(schema)),
@@ -713,7 +710,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     }
 
     onPointerUp = (e: React.PointerEvent): void => {
-        SelectionSizeTooltip.textBox = this;
+        FormattedTextBoxComment.textBox = this;
         if (e.buttons === 1 && this.props.isSelected() && !e.altKey) {
             e.stopPropagation();
         }
@@ -805,8 +802,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             }
 
             if (m < 10) m = '0' + m;
-            if (s < 10) s = '0' + s;
-            return now.toLocaleDateString() + ' ' + h + ':' + m + ':' + s + ' ' + ampm;
+            return now.toLocaleDateString() + ' ' + h + ':' + m + ' ' + ampm;
         }
         var markerss = this._editorView!.state.storedMarks || (this._editorView!.state.selection.$to.parentOffset && this._editorView!.state.selection.$from.marks());
         let newMarks = [...(markerss ? markerss.filter(m => m.type !== schema.marks.user_mark) : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() })];
diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx
index c368a3f34..255000936 100644
--- a/src/client/views/nodes/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/FormattedTextBoxComment.tsx
@@ -8,8 +8,8 @@ import { DocServer } from "../../DocServer";
 import { Utils } from "../../../Utils";
 import { StrCast } from "../../../new_fields/Types";
 
-export let selectionSizePlugin = new Plugin({
-    view(editorView) { return new SelectionSizeTooltip(editorView); }
+export let formattedTextBoxCommentPlugin = new Plugin({
+    view(editorView) { return new FormattedTextBoxComment(editorView); }
 });
 export function findOtherUserMark(marks: Mark[]): Mark | undefined {
     return marks.find(m => m.attrs.userid && m.attrs.userid !== Doc.CurrentUserEmail);
@@ -42,7 +42,7 @@ export function findEndOfMark(rpos: ResolvedPos, view: EditorView, finder: (mark
 }
 
 
-export class SelectionSizeTooltip {
+export class FormattedTextBoxComment {
     static tooltip: HTMLElement;
     static tooltipText: HTMLElement;
     static start: number;
@@ -51,39 +51,39 @@ export class SelectionSizeTooltip {
     static opened: boolean;
     static textBox: any;
     constructor(view: any) {
-        if (!SelectionSizeTooltip.tooltip) {
+        if (!FormattedTextBoxComment.tooltip) {
             const root = document.getElementById("root");
             let input = document.createElement("input");
             input.type = "checkbox";
-            SelectionSizeTooltip.tooltip = document.createElement("div");
-            SelectionSizeTooltip.tooltipText = document.createElement("div");
-            SelectionSizeTooltip.tooltip.appendChild(SelectionSizeTooltip.tooltipText);
-            SelectionSizeTooltip.tooltip.className = "FormattedTextBox-tooltip";
-            SelectionSizeTooltip.tooltip.style.pointerEvents = "all";
-            SelectionSizeTooltip.tooltip.appendChild(input);
-            SelectionSizeTooltip.tooltip.onpointerdown = (e: PointerEvent) => {
+            FormattedTextBoxComment.tooltip = document.createElement("div");
+            FormattedTextBoxComment.tooltipText = document.createElement("div");
+            FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipText);
+            FormattedTextBoxComment.tooltip.className = "FormattedTextBox-tooltip";
+            FormattedTextBoxComment.tooltip.style.pointerEvents = "all";
+            FormattedTextBoxComment.tooltip.appendChild(input);
+            FormattedTextBoxComment.tooltip.onpointerdown = (e: PointerEvent) => {
                 let keep = e.target && (e.target as any).type === "checkbox";
-                SelectionSizeTooltip.opened = keep || !SelectionSizeTooltip.opened;
-                SelectionSizeTooltip.textBox && SelectionSizeTooltip.textBox.setAnnotation(
-                    SelectionSizeTooltip.start, SelectionSizeTooltip.end, SelectionSizeTooltip.mark,
-                    SelectionSizeTooltip.opened, keep);
+                FormattedTextBoxComment.opened = keep || !FormattedTextBoxComment.opened;
+                FormattedTextBoxComment.textBox && FormattedTextBoxComment.textBox.setAnnotation(
+                    FormattedTextBoxComment.start, FormattedTextBoxComment.end, FormattedTextBoxComment.mark,
+                    FormattedTextBoxComment.opened, keep);
             };
-            root && root.appendChild(SelectionSizeTooltip.tooltip);
+            root && root.appendChild(FormattedTextBoxComment.tooltip);
         }
         this.update(view, undefined);
     }
 
     public static Hide() {
-        SelectionSizeTooltip.textBox = undefined;
-        SelectionSizeTooltip.tooltip && (SelectionSizeTooltip.tooltip.style.display = "none");
+        FormattedTextBoxComment.textBox = undefined;
+        FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = "none");
     }
     public static SetState(textBox: any, opened: boolean, start: number, end: number, mark: Mark) {
-        SelectionSizeTooltip.textBox = textBox;
-        SelectionSizeTooltip.start = start;
-        SelectionSizeTooltip.end = end;
-        SelectionSizeTooltip.mark = mark;
-        SelectionSizeTooltip.opened = opened;
-        SelectionSizeTooltip.tooltip && (SelectionSizeTooltip.tooltip.style.display = "");
+        FormattedTextBoxComment.textBox = textBox;
+        FormattedTextBoxComment.start = start;
+        FormattedTextBoxComment.end = end;
+        FormattedTextBoxComment.mark = mark;
+        FormattedTextBoxComment.opened = opened;
+        FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = "");
     }
 
     update(view: EditorView, lastState?: EditorState) {
@@ -102,10 +102,10 @@ export class SelectionSizeTooltip {
             let mark = child && findOtherUserMark(child.marks);
             let noselection = view.state.selection.$from === view.state.selection.$to;
             if (mark && child && (nbef || naft) && (!mark.attrs.opened || noselection)) {
-                SelectionSizeTooltip.SetState(this, mark.attrs.opened, spos, epos, mark);
+                FormattedTextBoxComment.SetState(this, mark.attrs.opened, spos, epos, mark);
             }
             if (mark && child && nbef && naft) {
-                SelectionSizeTooltip.tooltipText.textContent = mark.attrs.userid + " " + mark.attrs.modified;
+                FormattedTextBoxComment.tooltipText.textContent = mark.attrs.userid + " " + mark.attrs.modified;
                 // These are in screen coordinates
                 // let start = view.coordsAtPos(state.selection.from), end = view.coordsAtPos(state.selection.to);
                 let start = view.coordsAtPos(state.selection.from - nbef), end = view.coordsAtPos(state.selection.from - nbef);
@@ -114,23 +114,23 @@ export class SelectionSizeTooltip {
                 // Find a center-ish x position from the selection endpoints (when
                 // crossing lines, end may be more to the left)
                 let left = Math.max((start.left + end.left) / 2, start.left + 3);
-                SelectionSizeTooltip.tooltip.style.left = (left - box.left) + "px";
-                SelectionSizeTooltip.tooltip.style.bottom = (box.bottom - start.top) + "px";
+                FormattedTextBoxComment.tooltip.style.left = (left - box.left) + "px";
+                FormattedTextBoxComment.tooltip.style.bottom = (box.bottom - start.top) + "px";
                 set = "";
             }
         }
         if (set === "none" && state.selection.$from) {
-            SelectionSizeTooltip.textBox = undefined;
+            FormattedTextBoxComment.textBox = undefined;
             let nbef = findStartOfMark(state.selection.$from, view, findLinkMark);
             let naft = findEndOfMark(state.selection.$from, view, findLinkMark);
             let child = state.selection.$from.nodeBefore;
             let mark = child && findLinkMark(child.marks);
             if (mark && child && nbef && naft) {
-                SelectionSizeTooltip.tooltipText.textContent = "link : " + (mark.attrs.title || mark.attrs.href);
+                FormattedTextBoxComment.tooltipText.textContent = "link : " + (mark.attrs.title || mark.attrs.href);
                 if (mark.attrs.href.indexOf(Utils.prepend("/doc/")) === 0) {
                     let docTarget = mark.attrs.href.replace(Utils.prepend("/doc/"), "").split("?")[0];
                     docTarget && DocServer.GetRefField(docTarget).then(linkDoc =>
-                        (linkDoc as Doc) && (SelectionSizeTooltip.tooltipText.textContent = "link :" + StrCast((linkDoc as Doc)!.title)));
+                        (linkDoc as Doc) && (FormattedTextBoxComment.tooltipText.textContent = "link :" + StrCast((linkDoc as Doc)!.title)));
                 }
                 // These are in screen coordinates
                 // let start = view.coordsAtPos(state.selection.from), end = view.coordsAtPos(state.selection.to);
@@ -140,13 +140,13 @@ export class SelectionSizeTooltip {
                 // Find a center-ish x position from the selection endpoints (when
                 // crossing lines, end may be more to the left)
                 let left = Math.max((start.left + end.left) / 2, start.left + 3);
-                SelectionSizeTooltip.tooltip.style.left = (left - box.left) + "px";
-                SelectionSizeTooltip.tooltip.style.bottom = (box.bottom - start.top) + "px";
+                FormattedTextBoxComment.tooltip.style.left = (left - box.left) + "px";
+                FormattedTextBoxComment.tooltip.style.bottom = (box.bottom - start.top) + "px";
                 set = "";
             }
         }
-        SelectionSizeTooltip.tooltip && (SelectionSizeTooltip.tooltip.style.display = set);
+        FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = set);
     }
 
-    destroy() { SelectionSizeTooltip.tooltip.style.display = "none"; }
+    destroy() { FormattedTextBoxComment.tooltip.style.display = "none"; }
 }
-- 
cgit v1.2.3-70-g09d2


From a4d36f835b5c43351d1761034b61513b000445ba Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Tue, 3 Sep 2019 16:19:20 -0400
Subject: added inline footnote.

---
 src/client/util/ProsemirrorExampleTransfer.ts |  13 +++
 src/client/util/RichTextSchema.tsx            | 159 +++++++++++++++++++++++++-
 src/client/views/nodes/FormattedTextBox.scss  |  47 ++++++++
 src/client/views/nodes/FormattedTextBox.tsx   |  30 ++++-
 4 files changed, 240 insertions(+), 9 deletions(-)

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

diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts
index bcb8b404b..419311df8 100644
--- a/src/client/util/ProsemirrorExampleTransfer.ts
+++ b/src/client/util/ProsemirrorExampleTransfer.ts
@@ -51,6 +51,19 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?:
 
     bind("Ctrl->", wrapIn(schema.nodes.blockquote));
 
+    bind("^", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
+        let newNode = schema.nodes.footnote.create({});
+        if (dispatch && state.selection.from === state.selection.to) {
+            let tr = state.tr;
+            tr.replaceSelectionWith(newNode); // replace insertion with a footnote.
+            dispatch(tr.setSelection(new NodeSelection( // select the footnote node to open its display
+                tr.doc.resolve(  // get the location of the footnote node by subtracting the nodesize of the footnote from the current insertion point anchor (which will be immediately after the footnote node)
+                    tr.selection.anchor - tr.selection.$anchor.nodeBefore!.nodeSize))));
+            return true;
+        }
+        return false;
+    })
+
 
     let cmd = chainCommands(exitCode, (state, dispatch) => {
         if (dispatch) {
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 7911cf629..25d972857 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -1,7 +1,12 @@
 import { DOMOutputSpecArray, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model";
 import { bulletList, listItem, orderedList } from 'prosemirror-schema-list';
-import { TextSelection } from "prosemirror-state";
+import { TextSelection, EditorState } from "prosemirror-state";
 import { Doc } from "../../new_fields/Doc";
+import { StepMap } from "prosemirror-transform";
+import { EditorView } from "prosemirror-view";
+import { keymap } from "prosemirror-keymap";
+import { undo, redo } from "prosemirror-history";
+import { toggleMark, splitBlock, selectAll, baseKeymap } from "prosemirror-commands";
 
 const pDOM: DOMOutputSpecArray = ["p", 0], blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"],
     preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0];
@@ -14,6 +19,20 @@ export const nodes: { [index: string]: NodeSpec } = {
         content: "block+"
     },
 
+    footnote: {
+        group: "inline",
+        content: "inline*",
+        inline: true,
+        attrs: {
+            visibility: { default: false }
+        },
+        // This makes the view treat the node as a leaf, even though it
+        // technically has content
+        atom: true,
+        toDOM: () => ["footnote", 0],
+        parseDOM: [{ tag: "footnote" }]
+    },
+
     // :: NodeSpec A plain paragraph textblock. Represented in the DOM
     // as a `<p>` element.
     paragraph: {
@@ -177,7 +196,7 @@ export const nodes: { [index: string]: NodeSpec } = {
         group: 'block',
         attrs: {
             bulletStyle: { default: 0 },
-            mapStyle: { default: "decimal" }
+            mapStyle: { default: "decimal" },
         },
         toDOM(node: Node<any>) {
             const bs = node.attrs.bulletStyle;
@@ -186,7 +205,8 @@ export const nodes: { [index: string]: NodeSpec } = {
             let map = node.attrs.mapStyle === "decimal" ? decMap : multiMap;
             for (let i = 0; i < node.childCount; i++) node.child(i).attrs.className = map;
             return ['ol', { class: `${map}-ol`, style: `list-style: none;` }, 0];
-            //return ['ol', { class: `${node.attrs.bulletStyle}`, style: `list-style: ${node.attrs.bulletStyle};`, 0]
+            //return node.attrs.bulletStyle < 2 ? ['ol', { class: `${map}-ol`, style: `list-style: none;` }, 0] :
+            //     ['ol', { class: `${node.attrs.bulletStyle}`, style: `list-style: ${node.attrs.bulletStyle}; font-size: 5px` }, "hello"];
         }
     },
     //this doesn't currently work for some reason
@@ -313,10 +333,10 @@ export const marks: { [index: string]: MarkSpec } = {
     },
 
     highlight: {
-        parseDOM: [{ style: 'color: blue' }],
+        parseDOM: [{ style: 'text-decoration: underline' }],
         toDOM() {
             return ['span', {
-                style: 'color: blue'
+                style: 'text-decoration: underline; text-decoration-color: rgba(204, 206, 210, 0.92)'
             }];
         }
     },
@@ -581,6 +601,133 @@ export class OrderedListView {
     }
 }
 
+export class FootnoteView {
+    innerView: any;
+    outerView: any;
+    node: any;
+    dom: any;
+    getPos: any;
+
+    constructor(node: any, view: any, getPos: any) {
+        // We'll need these later
+        this.node = node
+        this.outerView = view
+        this.getPos = getPos
+
+        // The node's representation in the editor (empty, for now)
+        this.dom = document.createElement("footnote");
+        this.dom.addEventListener("pointerup", this.toggle, true);
+        // These are used when the footnote is selected
+        this.innerView = null
+    }
+    selectNode() {
+        const attrs = { ...this.node.attrs };
+        attrs.visibility = true;
+        this.dom.classList.add("ProseMirror-selectednode")
+        if (!this.innerView) this.open()
+    }
+
+    deselectNode() {
+        const attrs = { ...this.node.attrs };
+        attrs.visibility = false;
+        this.dom.classList.remove("ProseMirror-selectednode")
+        if (this.innerView) this.close()
+    }
+    open() {
+        if (!(this.outerView as any).isOverlay) return;
+        // Append a tooltip to the outer node
+        let tooltip = this.dom.appendChild(document.createElement("div"))
+        tooltip.className = "footnote-tooltip";
+        // And put a sub-ProseMirror into that
+        this.innerView = new EditorView(tooltip, {
+            // You can use any node as an editor document
+            state: EditorState.create({
+                doc: this.node,
+                plugins: [keymap(baseKeymap),
+                keymap({
+                    "Mod-z": () => undo(this.outerView.state, this.outerView.dispatch),
+                    "Mod-y": () => redo(this.outerView.state, this.outerView.dispatch),
+                    "Mod-b": toggleMark(schema.marks.strong)
+                })]
+            }),
+            // This is the magic part
+            dispatchTransaction: this.dispatchInner.bind(this),
+            handleDOMEvents: {
+                pointerdown: ((view: any, e: PointerEvent) => {
+                    // Kludge to prevent issues due to the fact that the whole
+                    // footnote is node-selected (and thus DOM-selected) when
+                    // the parent editor is focused.
+                    e.stopPropagation();
+                    document.addEventListener("pointerup", this.ignore, true);
+                    if (this.outerView.hasFocus()) this.innerView.focus();
+                }) as any
+            }
+
+        });
+        setTimeout(() => this.innerView && this.innerView.docView.setSelection(0, 0, this.innerView.root, true), 0);
+    }
+
+    ignore = (e: PointerEvent) => {
+        e.stopPropagation();
+        document.removeEventListener("pointerup", this.ignore, true);
+    }
+
+    toggle = () => {
+        if (this.innerView) this.close();
+        else {
+            this.open();
+
+        }
+    }
+    close() {
+        this.innerView && this.innerView.destroy()
+        this.innerView = null
+        this.dom.textContent = ""
+    }
+    dispatchInner(tr: any) {
+        let { state, transactions } = this.innerView.state.applyTransaction(tr)
+        this.innerView.updateState(state)
+
+        if (!tr.getMeta("fromOutside")) {
+            let outerTr = this.outerView.state.tr, offsetMap = StepMap.offset(this.getPos() + 1)
+            for (let i = 0; i < transactions.length; i++) {
+                let steps = transactions[i].steps
+                for (let j = 0; j < steps.length; j++)
+                    outerTr.step(steps[j].map(offsetMap))
+            }
+            if (outerTr.docChanged) this.outerView.dispatch(outerTr)
+        }
+    }
+    update(node: any) {
+        if (!node.sameMarkup(this.node)) return false
+        this.node = node
+        if (this.innerView) {
+            let state = this.innerView.state
+            let start = node.content.findDiffStart(state.doc.content)
+            if (start != null) {
+                let { a: endA, b: endB } = node.content.findDiffEnd(state.doc.content)
+                let overlap = start - Math.min(endA, endB)
+                if (overlap > 0) { endA += overlap; endB += overlap }
+                this.innerView.dispatch(
+                    state.tr
+                        .replace(start, endB, node.slice(start, endA))
+                        .setMeta("fromOutside", true))
+            }
+        }
+        return true
+    }
+
+    destroy() {
+        if (this.innerView) this.close()
+    }
+
+    stopEvent(event: any) {
+        return this.innerView && this.innerView.dom.contains(event.target)
+    }
+
+    ignoreMutation() { return true }
+}
+
 export class SummarizedView {
     // TODO: highlight text that is summarized. to find end of region, walk along mark
     _collapsed: HTMLElement;
@@ -648,7 +795,7 @@ export class SummarizedView {
             let skip = false;
             this._view.state.doc.nodesBetween(start, i, (node: Node, pos: number, parent: Node, index: number) => {
                 if (node.isLeaf && !visited.has(node) && !skip) {
-                    if (node.marks.includes(_mark)) {
+                    if (node.marks.find((m: any) => m.type === _mark.type)) {
                         visited.add(node);
                         endPos = i + node.nodeSize - 1;
                     }
diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss
index dd07f5924..8f47402c4 100644
--- a/src/client/views/nodes/FormattedTextBox.scss
+++ b/src/client/views/nodes/FormattedTextBox.scss
@@ -84,6 +84,53 @@
     align-content: center;
 }
 
+footnote {
+    display: inline-block;
+    position: relative;
+    cursor: pointer;
+    div {
+        padding : 0 !important;
+    }
+}
+
+footnote::after {
+    content: counter(prosemirror-footnote);
+    vertical-align: super;
+    font-size: 75%;
+    counter-increment: prosemirror-footnote;
+}
+
+.ProseMirror {
+    counter-reset: prosemirror-footnote;
+  }
+
+.footnote-tooltip {
+    cursor: auto;
+    font-size: 75%;
+    position: absolute;
+    left: -30px;
+    top: calc(100% + 10px);
+    background: silver;
+    padding: 3px;
+    border-radius: 2px;
+    max-width: 100px;
+    min-width: 50px;
+    width: max-content;
+}
+
+.footnote-tooltip::before {
+    border: 5px solid silver;
+    border-top-width: 0px;
+    border-left-color: transparent;
+    border-right-color: transparent;
+    position: absolute;
+    top: -5px;
+    left: 27px;
+    content: " ";
+    height: 0;
+    width: 0;
+}
+
 ol { counter-reset: deci1 0;}
 .decimal1-ol {counter-reset: deci1; p { display: inline }; font-size: 24 }
 .decimal2-ol {counter-reset: deci2; p { display: inline }; font-size: 18 }
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 6232dd3ab..bb44c5ac6 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -22,7 +22,7 @@ import { DocumentManager } from '../../util/DocumentManager';
 import { DragManager } from "../../util/DragManager";
 import buildKeymap from "../../util/ProsemirrorExampleTransfer";
 import { inpRules } from "../../util/RichTextRules";
-import { ImageResizeView, schema, SummarizedView, OrderedListView } from "../../util/RichTextSchema";
+import { ImageResizeView, schema, SummarizedView, OrderedListView, FootnoteView } from "../../util/RichTextSchema";
 import { SelectionManager } from "../../util/SelectionManager";
 import { TooltipLinkingMenu } from "../../util/TooltipLinkingMenu";
 import { TooltipTextMenu } from "../../util/TooltipTextMenu";
@@ -168,10 +168,33 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         }
     }
 
+    // this should be internal to prosemirror, but is needed
+    // here to make sure that footnote view nodes in the overlay editor
+    // get removed when they're not selected.
+    syncNodeSelection(view: any, sel: any) {
+        if (sel instanceof NodeSelection) {
+            var desc = view.docView.descAt(sel.from);
+            if (desc != view.lastSelectedViewDesc) {
+                if (view.lastSelectedViewDesc) {
+                    view.lastSelectedViewDesc.deselectNode();
+                    view.lastSelectedViewDesc = null;
+                }
+                if (desc) { desc.selectNode(); }
+                view.lastSelectedViewDesc = desc;
+            }
+        } else {
+            if (view.lastSelectedViewDesc) {
+                view.lastSelectedViewDesc.deselectNode();
+                view.lastSelectedViewDesc = null;
+            }
+        }
+    }
+
     dispatchTransaction = (tx: Transaction) => {
         if (this._editorView) {
             const state = this._editorView.state.apply(tx);
             this._editorView.updateState(state);
+            this.syncNodeSelection(this._editorView, this._editorView.state.selection); // bcz: ugh -- shouldn't be needed but without this the overlay view's footnote popup doesn't get deselected
             if (state.selection.empty && FormattedTextBox._toolTipTextMenu && tx.storedMarks) {
                 FormattedTextBox._toolTipTextMenu.mark_key_pressed(tx.storedMarks);
             }
@@ -612,12 +635,13 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 nodeViews: {
                     image(node, view, getPos) { return new ImageResizeView(node, view, getPos); },
                     star(node, view, getPos) { return new SummarizedView(node, view, getPos); },
-                    ordered_list(node, view, getPos) { return new OrderedListView(node, view, getPos); }
-
+                    ordered_list(node, view, getPos) { return new OrderedListView(node, view, getPos); },
+                    footnote(node, view, getPos) { return new FootnoteView(node, view, getPos) }
                 },
                 clipboardTextSerializer: this.clipboardTextSerializer,
                 handlePaste: this.handlePaste,
             });
+            (this._editorView as any).isOverlay = this.props.isOverlay;
             if (startup) {
                 Doc.GetProto(doc).documentText = undefined;
                 this._editorView.dispatch(this._editorView.state.tr.insertText(startup));
-- 
cgit v1.2.3-70-g09d2

