From 2a2013648be7cc396137b69e5ac20f5d52973452 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 18 Jun 2020 14:46:51 -0400
Subject: added a major hack to allow overriding of ACLs for adding a hyperlink
 to a readonly text box.

---
 .../views/nodes/formattedText/FormattedTextBox.tsx | 40 +++++++++++++---------
 1 file changed, 24 insertions(+), 16 deletions(-)

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

diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 1f1e51e66..96c5ca58f 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -13,7 +13,7 @@ import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from "
 import { ReplaceStep } from 'prosemirror-transform';
 import { EditorView } from "prosemirror-view";
 import { DateField } from '../../../../fields/DateField';
-import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym } from "../../../../fields/Doc";
+import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym, AclSym } from "../../../../fields/Doc";
 import { documentSchema } from '../../../../fields/documentSchemas';
 import { Id } from '../../../../fields/FieldSymbols';
 import { InkTool } from '../../../../fields/InkField';
@@ -22,7 +22,7 @@ import { RichTextField } from "../../../../fields/RichTextField";
 import { RichTextUtils } from '../../../../fields/RichTextUtils';
 import { createSchema, makeInterface } from "../../../../fields/Schema";
 import { Cast, DateCast, NumCast, StrCast } from "../../../../fields/Types";
-import { TraceMobx } from '../../../../fields/util';
+import { TraceMobx, OVERRIDE_ACL } from '../../../../fields/util';
 import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnOne, returnZero, Utils, setupMoveUpEvents } from '../../../../Utils';
 import { GoogleApiClientUtils, Pulls, Pushes } from '../../../apis/google_docs/GoogleApiClientUtils';
 import { DocServer } from "../../../DocServer";
@@ -199,22 +199,28 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             const curProto = Cast(Cast(this.dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null);              // the default text inherited from a prototype
             const curLayout = this.rootDoc !== this.layoutDoc ? Cast(this.layoutDoc[this.fieldKey], RichTextField, null) : undefined; // the default text stored in a layout template
             const json = JSON.stringify(state.toJSON());
-            if (!this._applyingChange && json.replace(/"selection":.*/, "") !== curProto?.Data.replace(/"selection":.*/, "")) {
-                this._applyingChange = true;
-                this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()));
-                if ((!curTemp && !curProto) || curText || curLayout?.Data.includes("dash")) { // if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended)
-                    if (json !== curLayout?.Data) {
-                        !curText && tx.storedMarks?.map(m => m.type.name === "pFontSize" && (Doc.UserDoc().fontSize = this.layoutDoc._fontSize = m.attrs.fontSize));
-                        !curText && tx.storedMarks?.map(m => m.type.name === "pFontFamily" && (Doc.UserDoc().fontFamily = this.layoutDoc._fontFamily = m.attrs.fontFamily));
-                        this.dataDoc[this.props.fieldKey] = new RichTextField(json, curText);
-                        this.dataDoc[this.props.fieldKey + "-noTemplate"] = (curTemp?.Text || "") !== curText; // mark the data field as being split from the template if it has been edited
+            if (!this.dataDoc[AclSym]) {
+                if (!this._applyingChange && json.replace(/"selection":.*/, "") !== curProto?.Data.replace(/"selection":.*/, "")) {
+                    this._applyingChange = true;
+                    this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()));
+                    if ((!curTemp && !curProto) || curText || curLayout?.Data.includes("dash")) { // if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended)
+                        if (json !== curLayout?.Data) {
+                            !curText && tx.storedMarks?.map(m => m.type.name === "pFontSize" && (Doc.UserDoc().fontSize = this.layoutDoc._fontSize = m.attrs.fontSize));
+                            !curText && tx.storedMarks?.map(m => m.type.name === "pFontFamily" && (Doc.UserDoc().fontFamily = this.layoutDoc._fontFamily = m.attrs.fontFamily));
+                            this.dataDoc[this.props.fieldKey] = new RichTextField(json, curText);
+                            this.dataDoc[this.props.fieldKey + "-noTemplate"] = (curTemp?.Text || "") !== curText; // mark the data field as being split from the template if it has been edited
+                        }
+                    } else { // if we've deleted all the text in a note driven by a template, then restore the template data
+                        this.dataDoc[this.props.fieldKey] = undefined;
+                        this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse((curProto || curTemp).Data)));
+                        this.dataDoc[this.props.fieldKey + "-noTemplate"] = undefined; // mark the data field as not being split from any template it might have
                     }
-                } else { // if we've deleted all the text in a note driven by a template, then restore the template data
-                    this.dataDoc[this.props.fieldKey] = undefined;
-                    this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse((curProto || curTemp).Data)));
-                    this.dataDoc[this.props.fieldKey + "-noTemplate"] = undefined; // mark the data field as not being split from any template it might have
+                    this._applyingChange = false;
                 }
-                this._applyingChange = false;
+            } else {
+                const json = JSON.parse(Cast(this.dataDoc[this.fieldKey], RichTextField)?.Data!);
+                json.selection = state.toJSON().selection;
+                this._editorView.updateState(EditorState.fromJSON(this.config, json));
             }
             this.updateTitle();
             this.tryUpdateHeight();
@@ -598,7 +604,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                     tr = tr.addMark(pos, pos + node.nodeSize, link);
                 }
             });
+            OVERRIDE_ACL(true);
             this._editorView!.dispatch(tr.removeMark(sel.from, sel.to, splitter));
+            OVERRIDE_ACL(false);
         }
     }
     componentDidMount() {
-- 
cgit v1.2.3-70-g09d2


From 29936187e801d851caf7a09260f1c801d6d30b59 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 19 Jun 2020 01:20:17 -0400
Subject: fixed a bunch of minor bugs with pdf ui, text box selection, menus,
 etc.

---
 src/client/util/CurrentUserUtils.ts                      | 16 +++++++++++++---
 src/client/util/DragManager.ts                           |  2 +-
 src/client/views/ContextMenu.tsx                         |  2 +-
 src/client/views/GlobalKeyHandler.ts                     |  3 ++-
 .../collectionFreeForm/CollectionFreeFormView.tsx        |  2 +-
 src/client/views/nodes/LabelBox.tsx                      |  2 +-
 src/client/views/nodes/PDFBox.tsx                        | 10 +++++++---
 .../views/nodes/formattedText/FormattedTextBox.scss      | 12 ++++++++++--
 .../views/nodes/formattedText/FormattedTextBox.tsx       |  9 ++++++---
 9 files changed, 42 insertions(+), 16 deletions(-)

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

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 37eaa5f5e..0855cc573 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -21,6 +21,7 @@ import { MainView } from "../views/MainView";
 import { DocumentType } from "../documents/DocumentTypes";
 import { SchemaHeaderField } from "../../fields/SchemaHeaderField";
 import { DimUnit } from "../views/collections/collectionMulticolumn/CollectionMulticolumnView";
+import { LabelBox } from "../views/nodes/LabelBox";
 
 export class CurrentUserUtils {
     private static curr_id: string;
@@ -312,9 +313,17 @@ export class CurrentUserUtils {
             iconView.isTemplateDoc = makeTemplate(iconView);
             doc["template-icon-view"] = new PrefetchProxy(iconView);
         }
+        if (doc["template-icon-view-pdf"] === undefined) {
+            const iconPdfView = Docs.Create.LabelDocument({
+                title: "icon_" + DocumentType.PDF, textTransform: "unset", letterSpacing: "unset", layout: LabelBox.LayoutString("title"), _backgroundColor: "dimGray",
+                _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)")
+            });
+            iconPdfView.isTemplateDoc = makeTemplate(iconPdfView, true, "icon_" + DocumentType.PDF);
+            doc["template-icon-view-pdf"] = new PrefetchProxy(iconPdfView);
+        }
         if (doc["template-icon-view-rtf"] === undefined) {
             const iconRtfView = Docs.Create.LabelDocument({
-                title: "icon_" + DocumentType.RTF, textTransform: "unset", letterSpacing: "unset",
+                title: "icon_" + DocumentType.RTF, textTransform: "unset", letterSpacing: "unset", layout: LabelBox.LayoutString("text"),
                 _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)")
             });
             iconRtfView.isTemplateDoc = makeTemplate(iconRtfView, true, "icon_" + DocumentType.RTF);
@@ -334,11 +343,11 @@ export class CurrentUserUtils {
         }
         if (doc["template-icons"] === undefined) {
             doc["template-icons"] = new PrefetchProxy(Docs.Create.TreeDocument([doc["template-icon-view"] as Doc, doc["template-icon-view-img"] as Doc,
-            doc["template-icon-view-col"] as Doc, doc["template-icon-view-rtf"] as Doc], { title: "icon templates", _height: 75 }));
+            doc["template-icon-view-col"] as Doc, doc["template-icon-view-rtf"] as Doc, doc["template-icon-view-pdf"] as Doc], { title: "icon templates", _height: 75 }));
         } else {
             const templateIconsDoc = Cast(doc["template-icons"], Doc, null);
             const requiredTypes = [doc["template-icon-view"] as Doc, doc["template-icon-view-img"] as Doc,
-            doc["template-icon-view-col"] as Doc, doc["template-icon-view-rtf"] as Doc];
+            doc["template-icon-view-col"] as Doc, doc["template-icon-view-rtf"] as Doc, doc["template-icon-view-pdf"] as Doc];
             DocListCastAsync(templateIconsDoc.data).then(async curIcons => {
                 await Promise.all(curIcons!);
                 requiredTypes.map(ntype => Doc.AddDocToList(templateIconsDoc, "data", ntype));
@@ -748,6 +757,7 @@ export class CurrentUserUtils {
     }
 
     static async updateUserDocument(doc: Doc) {
+        doc.noviceMode = doc.noviceMode === undefined ? "true" : doc.noviceMode;
         doc.title = Doc.CurrentUserEmail;
         doc.activeInkPen = doc;
         doc.activeInkColor = StrCast(doc.activeInkColor, "rgb(0, 0, 0)");
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 21564c92e..26e7250f4 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -328,7 +328,7 @@ export namespace DragManager {
             dragLabel.style.zIndex = "100001";
             dragLabel.style.fontSize = "10";
             dragLabel.style.position = "absolute";
-            dragLabel.innerText = "press 'a' to embed on drop";
+            // dragLabel.innerText = "press 'a' to embed on drop"; // bcz: need to move this to a status bar
             dragDiv.appendChild(dragLabel);
             DragManager.Root().appendChild(dragDiv);
         }
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index 5b66b63ed..941d7b44a 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -236,7 +236,7 @@ export class ContextMenu extends React.Component {
                     <span className="icon-background">
                         <FontAwesomeIcon icon="search" size="lg" />
                     </span>
-                    <input className="contextMenu-item contextMenu-description search" type="text" placeholder="Search . . ." value={this._searchString} onKeyDown={this.onKeyDown} onChange={this.onChange} autoFocus />
+                    <input className="contextMenu-item contextMenu-description search" type="text" placeholder="Search Menu..." value={this._searchString} onKeyDown={this.onKeyDown} onChange={this.onChange} autoFocus />
                 </span>
                 {this.menuItems}
             </>
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index f5adeeb00..c696625db 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -74,7 +74,7 @@ export default class KeyManager {
             case "a": DragManager.CanEmbed = true;
                 break;
             case " ":
-                MarqueeView.DragMarquee = !MarqueeView.DragMarquee;
+                // MarqueeView.DragMarquee = !MarqueeView.DragMarquee; // bcz: this needs a better disclosure UI
                 break;
             case "escape":
                 const main = MainView.Instance;
@@ -103,6 +103,7 @@ export default class KeyManager {
                 }
                 UndoManager.RunInBatch(() =>
                     SelectionManager.SelectedDocuments().map(dv => dv.props.removeDocument?.(dv.props.Document)), "delete");
+                SelectionManager.DeselectAll();
                 break;
             case "arrowleft":
                 UndoManager.RunInBatch(() => SelectionManager.SelectedDocuments().map(dv => dv.props.nudge?.(-1, 0)), "nudge left");
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 00260745d..c0c88d540 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1197,7 +1197,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     onContextMenu = (e: React.MouseEvent) => {
         if (this.props.annotationsKey) return;
 
-        ContextMenu.Instance.addItem({
+        !this.props.isAnnotationOverlay && ContextMenu.Instance.addItem({
             description: (this._timelineVisible ? "Close" : "Open") + " Animation Timeline", event: action(() => {
                 this._timelineVisible = !this._timelineVisible;
             }), icon: this._timelineVisible ? faEyeSlash : faEye
diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index ad9e49369..360ead18e 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -77,7 +77,7 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps, LabelDocument
                     paddingBottom: NumCast(this.layoutDoc._yPadding),
                     whiteSpace: this.layoutDoc._singleLine ? "pre" : "pre-wrap"
                 }} >
-                    {StrCast(this.rootDoc.text, StrCast(this.rootDoc.title))}
+                    {StrCast(this.rootDoc[this.fieldKey], StrCast(this.rootDoc.title))}
                 </div>
                 <div className="labelBox-fieldKeyParams" >
                     {!missingParams?.length ? (null) : missingParams.map(m => <div key={m} className="labelBox-missingParam">{m}</div>)}
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index aabaac39e..3f6fc07a1 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -146,11 +146,12 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum
                 <FontAwesomeIcon style={{ color: "white" }} icon={"arrow-right"} size="sm" />
             </button>
         </>;
+        const searchTitle = `${!this._searching ? "Open" : "Close"} Search Bar`;
         return !this.active() ? (null) :
             (<div className="pdfBox-ui" onKeyDown={e => e.keyCode === KeyCodes.BACKSPACE || e.keyCode === KeyCodes.DELETE ? e.stopPropagation() : true}
                 onPointerDown={e => e.stopPropagation()} style={{ display: this.active() ? "flex" : "none" }}>
                 <div className="pdfBox-overlayCont" key="cont" onPointerDown={(e) => e.stopPropagation()} style={{ left: `${this._searching ? 0 : 100}%` }}>
-                    <button className="pdfBox-overlayButton" title="Open Search Bar" />
+                    <button className="pdfBox-overlayButton" title={searchTitle} />
                     <input className="pdfBox-searchBar" placeholder="Search" ref={this._searchRef} onChange={this.searchStringChanged} onKeyDown={e => e.keyCode === KeyCodes.ENTER && this.search(this._searchString, !e.shiftKey)} />
                     <button title="Search" onClick={e => this.search(this._searchString, !e.shiftKey)}>
                         <FontAwesomeIcon icon="search" size="sm" color="white" /></button>
@@ -161,14 +162,17 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum
                         <FontAwesomeIcon style={{ color: "white" }} icon={"arrow-down"} size="lg" />
                     </button>
                 </div>
-                <button className="pdfBox-overlayButton" key="search" onClick={action(() => this._searching = !this._searching)} title="Open Search Bar" style={{ bottom: 0, right: 0 }}>
+                <button className="pdfBox-overlayButton" key="search" onClick={action(() => {
+                    this._searching = !this._searching;
+                    this.search("mxytzlaf", true);
+                })} title={searchTitle} style={{ bottom: 0, right: 0 }}>
                     <div className="pdfBox-overlayButton-arrow" onPointerDown={(e) => e.stopPropagation()}></div>
                     <div className="pdfBox-overlayButton-iconCont" onPointerDown={(e) => e.stopPropagation()}>
                         <FontAwesomeIcon style={{ color: "white" }} icon={this._searching ? "times" : "search"} size="lg" /></div>
                 </button>
                 <input value={`${(this.Document.curPage || 1)}`}
                     onChange={e => this.gotoPage(Number(e.currentTarget.value))}
-                    style={{ left: 5, top: 5, height: "20px", width: "20px", position: "absolute", pointerEvents: "all" }}
+                    style={{ left: 5, top: 5, height: "20px", width: "3ch", position: "absolute", pointerEvents: "all" }}
                     onClick={action(() => this._pageControls = !this._pageControls)} />
                 {this._pageControls ? pageBtns : (null)}
                 <div className="pdfBox-settingsCont" key="settings" onPointerDown={(e) => e.stopPropagation()}>
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
index df4a05dd7..06ecc8944 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -92,8 +92,8 @@
     left: 10%;
 }
 
-.formattedTextBox-inner-rounded,
-.formattedTextBox-inner {
+.formattedTextBox-inner-rounded, .formattedTextBox-inner-rounded-selected,
+.formattedTextBox-inner, .formattedTextBox-inner-selected {
     height: 100%;
     white-space: pre-wrap; 
     hr {
@@ -276,6 +276,7 @@ footnote::after {
   }
 
 .ProseMirror {
+    padding: 0px;
     touch-action: none;
     span {
         font-family: inherit;
@@ -316,4 +317,11 @@ footnote::after {
     .multi2:before { transition: 0.5s;counter-increment: multi2; display: inline-block; margin-left: -2em;    width:   2em;  content: counter(multi1, upper-alpha) "."counter(multi2, decimal) ". "; }
     .multi3:before { transition: 0.5s;counter-increment: multi3; display: inline-block; margin-left: -2.85em; width:2.85em;  content: counter(multi1, upper-alpha) "."counter(multi2, decimal) "."counter(multi3, lower-alpha) ". "; }
     .multi4:before { transition: 0.5s;counter-increment: multi4; display: inline-block; margin-left:   -4.2em;  width: 4.2em;    content: counter(multi1, upper-alpha) "."counter(multi2, decimal) "."counter(multi3, lower-alpha) "."counter(multi4, lower-roman) ". "; }
+}
+
+.formattedTextBox-inner-rounded-selected,
+.formattedTextBox-inner-selected {
+    .ProseMirror {
+        padding:10px;
+    }
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 96c5ca58f..b06121caf 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1227,6 +1227,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         } else if (FormattedTextBoxComment.textBox === this) {
             setTimeout(() => FormattedTextBoxComment.Hide(), 0);
         }
+        const selPad = this.props.isSelected() ? -10 : 0;
+        const selclass = this.props.isSelected() ? "-selected" : ""
         return (
             <div className={"formattedTextBox-cont"} style={{
                 transform: `scale(${scale})`,
@@ -1268,11 +1270,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                     })}
                 >
                     <div className={`formattedTextBox-outer`} style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, }} onScroll={this.onscrolled} ref={this._scrollRef}>
-                        <div className={`formattedTextBox-inner${rounded}`} ref={this.createDropTarget}
+                        <div className={`formattedTextBox-inner${rounded}${selclass}`} ref={this.createDropTarget}
                             style={{
-                                padding: `${NumCast(this.layoutDoc._yMargin, this.props.yMargin || 0)}px  ${NumCast(this.layoutDoc._xMargin, this.props.xMargin || 0)}px`,
+                                padding: `${Math.max(0, NumCast(this.layoutDoc._yMargin, this.props.yMargin || 0) + selPad)}px  ${NumCast(this.layoutDoc._xMargin, this.props.xMargin || 0) + selPad}px`,
                                 pointerEvents: ((this.layoutDoc.isLinkButton || this.props.onClick) && !this.props.isSelected()) ? "none" : undefined
-                            }} />
+                            }}
+                        />
                     </div>
                     {!this.layoutDoc._showSidebar ? (null) : this.sidebarWidthPercent === "0%" ?
                         <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown} /> :
-- 
cgit v1.2.3-70-g09d2


From 8e8c2b1256b79b8325cc411f9a8c09add8cda39b Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 19 Jun 2020 08:43:29 -0400
Subject: fixed selecting textboxes to reset the selection instead of
 potentially triggering a hyperlink preview if that was the last thing
 selected.

---
 src/client/views/nodes/formattedText/FormattedTextBox.tsx | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

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

diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index b06121caf..42c2d25ab 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -996,8 +996,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         if (!FormattedTextBox._downEvent) return;
         FormattedTextBox._downEvent = false;
         if (!(e.nativeEvent as any).formattedHandled) {
+            const editor = this._editorView!;
             FormattedTextBoxComment.textBox = this;
-            FormattedTextBoxComment.update(this._editorView!, undefined, (e.target as any)?.className === "prosemirror-dropdownlink" ? (e.target as any).href : "");
+            const pcords = editor.posAtCoords({ left: e.clientX, top: e.clientY });
+            const node = pcords && editor.state.doc.nodeAt(pcords.pos); // get what prosemirror thinks the clicked node is (if it's null, then we didn't click on any text)
+            !this.props.isSelected(true) && editor.dispatch(editor.state.tr.setSelection(node && pcords ?
+                new NodeSelection(editor.state.doc.resolve(pcords.pos)) : new TextSelection(editor.state.doc.resolve(0))));
+            FormattedTextBoxComment.update(editor, undefined, (e.target as any)?.className === "prosemirror-dropdownlink" ? (e.target as any).href : "");
         }
         (e.nativeEvent as any).formattedHandled = true;
 
-- 
cgit v1.2.3-70-g09d2


From 1c6e701796f31064dc1b823341f9f79d83524141 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 19 Jun 2020 09:14:09 -0400
Subject: fixed textboxes so they don't scroll when not selected  and scrolling
 passes through to outer collection

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

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

diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
index 9538cac32..17421b1e3 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -280,6 +280,7 @@ footnote::after {
 
 .ProseMirror {
     padding: 0px;
+    height: max-content;
     touch-action: none;
     span {
         font-family: inherit;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 42c2d25ab..cba2f5a02 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1274,11 +1274,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                         }
                     })}
                 >
-                    <div className={`formattedTextBox-outer`} style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, }} onScroll={this.onscrolled} ref={this._scrollRef}>
+                    <div className={`formattedTextBox-outer`} style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, pointerEvents: !this.props.isSelected() ? "none" : undefined }} onScroll={this.onscrolled} ref={this._scrollRef}>
                         <div className={`formattedTextBox-inner${rounded}${selclass}`} ref={this.createDropTarget}
                             style={{
                                 padding: `${Math.max(0, NumCast(this.layoutDoc._yMargin, this.props.yMargin || 0) + selPad)}px  ${NumCast(this.layoutDoc._xMargin, this.props.xMargin || 0) + selPad}px`,
-                                pointerEvents: ((this.layoutDoc.isLinkButton || this.props.onClick) && !this.props.isSelected()) ? "none" : undefined
+                                pointerEvents: !this.props.isSelected() ? ((this.layoutDoc.isLinkButton || this.props.onClick) ? "none" : "all") : undefined
                             }}
                         />
                     </div>
-- 
cgit v1.2.3-70-g09d2


From 825122ee8c8dc450a64f8bd5582b1cc5026042b4 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 19 Jun 2020 10:11:54 -0400
Subject: fixed insertion point when selecting text box.

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

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

diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index cba2f5a02..02f28a4ac 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1001,7 +1001,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             const pcords = editor.posAtCoords({ left: e.clientX, top: e.clientY });
             const node = pcords && editor.state.doc.nodeAt(pcords.pos); // get what prosemirror thinks the clicked node is (if it's null, then we didn't click on any text)
             !this.props.isSelected(true) && editor.dispatch(editor.state.tr.setSelection(node && pcords ?
-                new NodeSelection(editor.state.doc.resolve(pcords.pos)) : new TextSelection(editor.state.doc.resolve(0))));
+                new NodeSelection(editor.state.doc.resolve(pcords.pos)) : new TextSelection(editor.state.doc.resolve(pcords?.pos || 0))));
             FormattedTextBoxComment.update(editor, undefined, (e.target as any)?.className === "prosemirror-dropdownlink" ? (e.target as any).href : "");
         }
         (e.nativeEvent as any).formattedHandled = true;
-- 
cgit v1.2.3-70-g09d2


From e0c2836639110d0c7bdea311c722f406850b794d Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 19 Jun 2020 15:50:40 -0400
Subject: added close back to dcument decorations.  moved iconify to the right
 of doc decs.

---
 src/client/views/DocumentDecorations.scss          | 14 +++++++++++
 src/client/views/DocumentDecorations.tsx           | 28 ++++++++++++----------
 .../views/nodes/formattedText/FormattedTextBox.tsx |  1 -
 3 files changed, 30 insertions(+), 13 deletions(-)

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

diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index 29204569b..11aaaaf8c 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -172,6 +172,20 @@ $linkGap : 3px;
 }
 
 
+.documentDecorations-iconifyButton {
+    opacity: 1;
+    grid-column-start: 4;
+    grid-column-end: 6;
+    pointer-events: all;
+    text-align: center;
+    left: -20px;
+    top: -2px;
+    cursor: pointer;
+    position: absolute;
+    background: transparent;
+    width: 20px;
+}
+
 .documentDecorations-closeButton {
     opacity: 1;
     grid-column-start: 4;
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 9b3c16a59..d92fe5203 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -175,7 +175,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
     }
     @undoBatch
     @action
-    onCloseClick = async (e: PointerEvent | undefined) => {
+    onCloseClick = async (e: React.MouseEvent) => {
         if (!e?.button) {
             const recent = Cast(Doc.UserDoc().myRecentlyClosed, Doc) as Doc;
             const selected = SelectionManager.SelectedDocuments().slice();
@@ -460,7 +460,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
             <div className="documentDecorations-contextMenu" title="Show context menu" onPointerDown={this.onSettingsDown}>
                 <FontAwesomeIcon size="lg" icon="cog" />
             </div>) : (
-                <div className="documentDecorations-minimizeButton" title="Iconify" onPointerDown={this.onIconifyDown}>
+                <div className="documentDecorations-minimizeButton" title="Iconify" onClick={this.onCloseClick}>
                     {/* Currently, this is set to be enabled if there is no ink selected. It might be interesting to think about minimizing ink if it's useful? -syip2*/}
                     <FontAwesomeIcon className="documentdecorations-times" icon={faTimes} size="lg" />
                 </div>);
@@ -469,16 +469,17 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
             <>
                 <input ref={this._keyinput} className="documentDecorations-title" type="text" name="dynbox" autoComplete="on" value={this._accumulatedTitle}
                     onBlur={e => this.titleBlur(true)} onChange={action(e => this._accumulatedTitle = e.target.value)} onKeyPress={this.titleEntered} />
-                {minimal ? (null) : <div className="publishBox" title="make document referenceable by its title"
-                    onPointerDown={action(e => {
-                        if (!seldoc.props.Document.customTitle) {
-                            seldoc.props.Document.customTitle = true;
-                            StrCast(Doc.GetProto(seldoc.props.Document).title).startsWith("-") && (Doc.GetProto(seldoc.props.Document).title = StrCast(seldoc.props.Document.title).substring(1));
-                            this._accumulatedTitle = StrCast(seldoc.props.Document.title);
-                        }
-                        DocUtils.Publish(seldoc.props.Document, this._accumulatedTitle, seldoc.props.addDocument, seldoc.props.removeDocument);
-                    })}>
-                    <FontAwesomeIcon size="lg" color={SelectionManager.SelectedDocuments()[0].props.Document.title === SelectionManager.SelectedDocuments()[0].props.Document[Id] ? "green" : undefined} icon="sticky-note"></FontAwesomeIcon>
+                {minimal ? (null) : <div className="publishBox" // title="make document referenceable by its title"
+                // onPointerDown={action(e => {
+                //     if (!seldoc.props.Document.customTitle) {
+                //         seldoc.props.Document.customTitle = true;
+                //         StrCast(Doc.GetProto(seldoc.props.Document).title).startsWith("-") && (Doc.GetProto(seldoc.props.Document).title = StrCast(seldoc.props.Document.title).substring(1));
+                //         this._accumulatedTitle = StrCast(seldoc.props.Document.title);
+                //     }
+                //     DocUtils.Publish(seldoc.props.Document, this._accumulatedTitle, seldoc.props.addDocument, seldoc.props.removeDocument);
+                // })}
+                >
+                    {/* <FontAwesomeIcon size="lg" color={SelectionManager.SelectedDocuments()[0].props.Document.title === SelectionManager.SelectedDocuments()[0].props.Document[Id] ? "green" : undefined} icon="sticky-note"></FontAwesomeIcon> */}
                 </div>}
             </> :
             <>
@@ -519,6 +520,9 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
             }}>
                 {maximizeIcon}
                 {titleArea}
+                <div className="documentDecorations-iconifyButton" title={`${seldoc.finalLayoutKey.includes("icon") ? "De" : ""}Iconify Document`} onPointerDown={this.onIconifyDown}>
+                    {"_"}
+                </div>
                 <div className="documentDecorations-closeButton" title="Open Document in Tab" onPointerDown={this.onMaximizeDown}>
                     {SelectionManager.SelectedDocuments().length === 1 ? DocumentDecorations.DocumentIcon(StrCast(seldoc.props.Document.layout, "...")) : "..."}
                 </div>
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 02f28a4ac..c23ecb8ac 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -57,7 +57,6 @@ import { FieldView, FieldViewProps } from "../FieldView";
 import "./FormattedTextBox.scss";
 import { FormattedTextBoxComment, formattedTextBoxCommentPlugin } from './FormattedTextBoxComment';
 import React = require("react");
-import { InkingStroke } from '../../InkingStroke';
 
 library.add(faEdit);
 library.add(faSmile, faTextHeight, faUpload);
-- 
cgit v1.2.3-70-g09d2


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

---
 package-lock.json                                  | 183 ++++++++++++++++++++-
 package.json                                       |   1 +
 src/client/util/DictationManager.ts                |   2 +-
 .../nodes/formattedText/FormattedTextBox.scss      |   2 +
 .../views/nodes/formattedText/FormattedTextBox.tsx |  52 +++---
 .../formattedText/ProsemirrorExampleTransfer.ts    |  10 +-
 .../views/nodes/formattedText/RichTextMenu.tsx     |  32 ++--
 .../views/nodes/formattedText/RichTextRules.ts     |   2 +-
 src/client/views/nodes/formattedText/marks_rts.ts  |  70 ++++----
 src/client/views/nodes/formattedText/nodes_rts.ts  |  85 +++++++---
 src/fields/RichTextUtils.ts                        |   2 +-
 11 files changed, 318 insertions(+), 123 deletions(-)

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

diff --git a/package-lock.json b/package-lock.json
index b2abac245..9ef83c537 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -173,11 +173,42 @@
                 "@emotion/weak-memoize": "0.2.5"
             }
         },
+        "@emotion/core": {
+            "version": "10.0.28",
+            "resolved": "https://registry.npmjs.org/@emotion/core/-/core-10.0.28.tgz",
+            "integrity": "sha512-pH8UueKYO5jgg0Iq+AmCLxBsvuGtvlmiDCOuv8fGNYn3cowFpLN98L8zO56U0H1PjDIyAlXymgL3Wu7u7v6hbA==",
+            "requires": {
+                "@babel/runtime": "^7.5.5",
+                "@emotion/cache": "^10.0.27",
+                "@emotion/css": "^10.0.27",
+                "@emotion/serialize": "^0.11.15",
+                "@emotion/sheet": "0.9.4",
+                "@emotion/utils": "0.11.3"
+            }
+        },
+        "@emotion/css": {
+            "version": "10.0.27",
+            "resolved": "https://registry.npmjs.org/@emotion/css/-/css-10.0.27.tgz",
+            "integrity": "sha512-6wZjsvYeBhyZQYNrGoR5yPMYbMBNEnanDrqmsqS1mzDm1cOTu12shvl2j4QHNS36UaTE0USIJawCH9C8oW34Zw==",
+            "requires": {
+                "@emotion/serialize": "^0.11.15",
+                "@emotion/utils": "0.11.3",
+                "babel-plugin-emotion": "^10.0.27"
+            }
+        },
         "@emotion/hash": {
             "version": "0.8.0",
             "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz",
             "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow=="
         },
+        "@emotion/is-prop-valid": {
+            "version": "0.8.8",
+            "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz",
+            "integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==",
+            "requires": {
+                "@emotion/memoize": "0.7.4"
+            }
+        },
         "@emotion/memoize": {
             "version": "0.7.4",
             "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz",
@@ -200,6 +231,26 @@
             "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-0.9.4.tgz",
             "integrity": "sha512-zM9PFmgVSqBw4zL101Q0HrBVTGmpAxFZH/pYx/cjJT5advXguvcgjHFTCaIO3enL/xr89vK2bh0Mfyj9aa0ANA=="
         },
+        "@emotion/styled": {
+            "version": "10.0.27",
+            "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-10.0.27.tgz",
+            "integrity": "sha512-iK/8Sh7+NLJzyp9a5+vIQIXTYxfT4yB/OJbjzQanB2RZpvmzBQOHZWhpAMZWYEKRNNbsD6WfBw5sVWkb6WzS/Q==",
+            "requires": {
+                "@emotion/styled-base": "^10.0.27",
+                "babel-plugin-emotion": "^10.0.27"
+            }
+        },
+        "@emotion/styled-base": {
+            "version": "10.0.31",
+            "resolved": "https://registry.npmjs.org/@emotion/styled-base/-/styled-base-10.0.31.tgz",
+            "integrity": "sha512-wTOE1NcXmqMWlyrtwdkqg87Mu6Rj1MaukEoEmEkHirO5IoHDJ8LgCQL4MjJODgxWxXibGR3opGp1p7YvkNEdXQ==",
+            "requires": {
+                "@babel/runtime": "^7.5.5",
+                "@emotion/is-prop-valid": "0.8.8",
+                "@emotion/serialize": "^0.11.15",
+                "@emotion/utils": "0.11.3"
+            }
+        },
         "@emotion/stylis": {
             "version": "0.8.5",
             "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz",
@@ -2159,6 +2210,11 @@
                 }
             }
         },
+        "base16": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/base16/-/base16-1.0.0.tgz",
+            "integrity": "sha1-4pf2DX7BAUp6lxo568ipjAtoHnA="
+        },
         "base64-arraybuffer": {
             "version": "0.1.5",
             "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz",
@@ -2516,8 +2572,7 @@
         "buffer-from": {
             "version": "1.1.1",
             "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
-            "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
-            "dev": true
+            "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A=="
         },
         "buffer-indexof": {
             "version": "1.1.1",
@@ -3639,7 +3694,6 @@
             "version": "1.6.2",
             "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
             "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
-            "dev": true,
             "requires": {
                 "buffer-from": "^1.0.0",
                 "inherits": "^2.0.3",
@@ -3651,7 +3705,6 @@
                     "version": "2.3.7",
                     "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
                     "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
-                    "dev": true,
                     "requires": {
                         "core-util-is": "~1.0.0",
                         "inherits": "~2.0.3",
@@ -4754,6 +4807,11 @@
             "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz",
             "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA=="
         },
+        "diff-match-patch": {
+            "version": "1.0.5",
+            "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz",
+            "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw=="
+        },
         "diffie-hellman": {
             "version": "5.0.3",
             "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
@@ -6754,6 +6812,14 @@
                 }
             }
         },
+        "html": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/html/-/html-1.0.0.tgz",
+            "integrity": "sha1-pUT6nqVJK/s6LMqCEKEL57WvH2E=",
+            "requires": {
+                "concat-stream": "^1.4.7"
+            }
+        },
         "html-encoding-sniffer": {
             "version": "1.0.2",
             "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz",
@@ -7753,6 +7819,15 @@
             "integrity": "sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA==",
             "dev": true
         },
+        "jsondiffpatch": {
+            "version": "0.4.1",
+            "resolved": "https://registry.npmjs.org/jsondiffpatch/-/jsondiffpatch-0.4.1.tgz",
+            "integrity": "sha512-t0etAxTUk1w5MYdNOkZBZ8rvYYN5iL+2dHCCx/DpkFm/bW28M6y5nUS83D4XdZiHy35Fpaw6LBb+F88fHZnVCw==",
+            "requires": {
+                "chalk": "^2.3.0",
+                "diff-match-patch": "^1.0.0"
+            }
+        },
         "jsonfile": {
             "version": "2.4.0",
             "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz",
@@ -7993,11 +8068,29 @@
             "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
             "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
         },
+        "lodash._getnative": {
+            "version": "3.9.1",
+            "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz",
+            "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U="
+        },
         "lodash.chunk": {
             "version": "4.2.0",
             "resolved": "https://registry.npmjs.org/lodash.chunk/-/lodash.chunk-4.2.0.tgz",
             "integrity": "sha1-ZuXOH3btJ7QwPYxlEujRIW6BBrw="
         },
+        "lodash.curry": {
+            "version": "4.1.1",
+            "resolved": "https://registry.npmjs.org/lodash.curry/-/lodash.curry-4.1.1.tgz",
+            "integrity": "sha1-JI42By7ekGUB11lmIAqG2riyMXA="
+        },
+        "lodash.debounce": {
+            "version": "3.1.1",
+            "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-3.1.1.tgz",
+            "integrity": "sha1-gSIRw3ipTMKdWqTjNGzwv846ffU=",
+            "requires": {
+                "lodash._getnative": "^3.0.0"
+            }
+        },
         "lodash.defaults": {
             "version": "4.2.0",
             "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
@@ -8013,6 +8106,11 @@
             "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz",
             "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8="
         },
+        "lodash.flow": {
+            "version": "3.5.0",
+            "resolved": "https://registry.npmjs.org/lodash.flow/-/lodash.flow-3.5.0.tgz",
+            "integrity": "sha1-h79AKSuM+D5OjOGjrkIJ4gBxZ1o="
+        },
         "lodash.get": {
             "version": "4.4.2",
             "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
@@ -8748,6 +8846,11 @@
             "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz",
             "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw=="
         },
+        "nanoid": {
+            "version": "3.1.10",
+            "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.10.tgz",
+            "integrity": "sha512-iZFMXKeXWkxzlfmMfM91gw7YhN2sdJtixY+eZh9V6QWJWTOiurhpKhBMgr82pfzgSqglQgqYSCowEYsz8D++6w=="
+        },
         "nanomatch": {
             "version": "1.2.13",
             "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz",
@@ -13574,6 +13677,25 @@
                 "prosemirror-transform": "^1.0.0"
             }
         },
+        "prosemirror-dev-tools": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/prosemirror-dev-tools/-/prosemirror-dev-tools-3.0.0.tgz",
+            "integrity": "sha512-yW0qc9OMxvy6gYKIYK4FAqrfLWqAKnOcxuGszrCBjxJtGKe6APWaYqGJn+u943+QIByjO1oNHGz5h4x3ekP/lQ==",
+            "requires": {
+                "@emotion/core": "^10.0.28",
+                "@emotion/styled": "^10.0.27",
+                "emotion": "^10.0.27",
+                "html": "^1.0.0",
+                "jsondiffpatch": "^0.4.1",
+                "nanoid": "^3.1.6",
+                "prop-types": "^15.7.2",
+                "prosemirror-model": ">=1.0.0",
+                "prosemirror-state": ">=1.0.0",
+                "react-dock": "^0.2.4",
+                "react-json-tree": "^0.11.2",
+                "unstated": "^2.1.1"
+            }
+        },
         "prosemirror-find-replace": {
             "version": "0.9.0",
             "resolved": "https://registry.npmjs.org/prosemirror-find-replace/-/prosemirror-find-replace-0.9.0.tgz",
@@ -13897,6 +14019,11 @@
                 }
             }
         },
+        "pure-color": {
+            "version": "1.3.0",
+            "resolved": "https://registry.npmjs.org/pure-color/-/pure-color-1.3.0.tgz",
+            "integrity": "sha1-H+Bk+wrIUfDeYTIKi/eWg2Qi8z4="
+        },
         "q": {
             "version": "1.5.1",
             "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
@@ -14047,6 +14174,17 @@
                 "section-iterator": "^2.0.0"
             }
         },
+        "react-base16-styling": {
+            "version": "0.5.3",
+            "resolved": "https://registry.npmjs.org/react-base16-styling/-/react-base16-styling-0.5.3.tgz",
+            "integrity": "sha1-OFjyTpxN2MvT9wLz901YHKKRcmk=",
+            "requires": {
+                "base16": "^1.0.0",
+                "lodash.curry": "^4.0.1",
+                "lodash.flow": "^3.3.0",
+                "pure-color": "^1.2.0"
+            }
+        },
         "react-color": {
             "version": "2.18.0",
             "resolved": "https://registry.npmjs.org/react-color/-/react-color-2.18.0.tgz",
@@ -14071,6 +14209,15 @@
                 "warning": "^3.0.0"
             }
         },
+        "react-dock": {
+            "version": "0.2.4",
+            "resolved": "https://registry.npmjs.org/react-dock/-/react-dock-0.2.4.tgz",
+            "integrity": "sha1-5yfcdVCztzEWY13LnA4E0Lev4Xw=",
+            "requires": {
+                "lodash.debounce": "^3.1.1",
+                "prop-types": "^15.5.8"
+            }
+        },
         "react-dom": {
             "version": "16.13.1",
             "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.13.1.tgz",
@@ -14118,6 +14265,16 @@
             "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
             "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
         },
+        "react-json-tree": {
+            "version": "0.11.2",
+            "resolved": "https://registry.npmjs.org/react-json-tree/-/react-json-tree-0.11.2.tgz",
+            "integrity": "sha512-aYhUPj1y5jR3ZQ+G3N7aL8FbTyO03iLwnVvvEikLcNFqNTyabdljo9xDftZndUBFyyyL0aK3qGO9+8EilILHUw==",
+            "requires": {
+                "babel-runtime": "^6.6.1",
+                "prop-types": "^15.5.8",
+                "react-base16-styling": "^0.5.1"
+            }
+        },
         "react-jsx-parser": {
             "version": "1.23.0",
             "resolved": "https://registry.npmjs.org/react-jsx-parser/-/react-jsx-parser-1.23.0.tgz",
@@ -16565,8 +16722,7 @@
         "typedarray": {
             "version": "0.0.6",
             "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
-            "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=",
-            "dev": true
+            "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
         },
         "typescript": {
             "version": "3.8.3",
@@ -16818,6 +16974,21 @@
                 }
             }
         },
+        "unstated": {
+            "version": "2.1.1",
+            "resolved": "https://registry.npmjs.org/unstated/-/unstated-2.1.1.tgz",
+            "integrity": "sha512-fORlTWMZxq7NuMJDxyIrrYIZKN7wEWYQ9SiaJfIRcSpsowr6Ph/JIfK2tgtXLW614JfPG/t5q9eEIhXRCf55xg==",
+            "requires": {
+                "create-react-context": "^0.1.5"
+            },
+            "dependencies": {
+                "create-react-context": {
+                    "version": "0.1.6",
+                    "resolved": "https://registry.npmjs.org/create-react-context/-/create-react-context-0.1.6.tgz",
+                    "integrity": "sha512-eCnYYEUEc5i32LHwpE/W7NlddOB9oHwsPaWtWzYtflNkkwa3IfindIcoXdVWs12zCbwaMCavKNu84EXogVIWHw=="
+                }
+            }
+        },
         "unzip-response": {
             "version": "2.0.1",
             "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz",
diff --git a/package.json b/package.json
index a9f5d872a..096c26cd0 100644
--- a/package.json
+++ b/package.json
@@ -189,6 +189,7 @@
         "pdfjs-dist": "^2.3.200",
         "probe-image-size": "^4.0.0",
         "prosemirror-commands": "^1.1.3",
+        "prosemirror-dev-tools": "^3.0.0",
         "prosemirror-find-replace": "^0.9.0",
         "prosemirror-history": "^1.1.3",
         "prosemirror-inputrules": "^1.1.2",
diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts
index d8a5657c3..28b1ca6cf 100644
--- a/src/client/util/DictationManager.ts
+++ b/src/client/util/DictationManager.ts
@@ -335,7 +335,7 @@ export namespace DictationManager {
                     const prompt = "Press alt + r to start dictating here...";
                     const head = 3;
                     const anchor = head + prompt.length;
-                    const proseMirrorState = `{"doc":{"type":"doc","content":[{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"type":"text","text":"${prompt}"}]}]}]}]},"selection":{"type":"text","anchor":${anchor},"head":${head}}}`;
+                    const proseMirrorState = `{"doc":{"type":"doc","content":[{"type":"ordered_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"type":"text","text":"${prompt}"}]}]}]}]},"selection":{"type":"text","anchor":${anchor},"head":${head}}}`;
                     proto.data = new RichTextField(proseMirrorState);
                     proto.backgroundColor = "#eeffff";
                     target.props.addDocTab(newBox, "onRight");
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
index 17421b1e3..173befdc1 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -295,6 +295,8 @@ footnote::after {
         margin-left: 1em;
         font-family: inherit;
     }
+    .bullet {  p {display: inline; font-family: inherit} margin-left: 0; }
+    .bullet1,.bullet2,.bullet3,.bullet4,.bullet5,.bullet6 { p {display: inline; font-family: inherit} font-size: smaller; }
 
     .decimal1-ol { counter-reset: deci1; p {display: inline; font-family: inherit} margin-left: 0; }
     .decimal2-ol { counter-reset: deci2; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 1em;}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index c23ecb8ac..82334688b 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -15,6 +15,7 @@ import { EditorView } from "prosemirror-view";
 import { DateField } from '../../../../fields/DateField';
 import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym, AclSym } from "../../../../fields/Doc";
 import { documentSchema } from '../../../../fields/documentSchemas';
+import applyDevTools = require("prosemirror-dev-tools");
 import { Id } from '../../../../fields/FieldSymbols';
 import { InkTool } from '../../../../fields/InkField';
 import { PrefetchProxy } from '../../../../fields/Proxy';
@@ -57,6 +58,7 @@ import { FieldView, FieldViewProps } from "../FieldView";
 import "./FormattedTextBox.scss";
 import { FormattedTextBoxComment, formattedTextBoxCommentPlugin } from './FormattedTextBoxComment';
 import React = require("react");
+import requestPromise = require('request-promise');
 
 library.add(faEdit);
 library.add(faSmile, faTextHeight, faUpload);
@@ -488,6 +490,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         });
         changeItems.push({ description: "FreeForm", event: () => DocUtils.makeCustomViewClicked(this.rootDoc, Docs.Create.FreeformDocument, "freeform"), icon: "eye" });
         !change && cm.addItem({ description: "Change Perspective...", subitems: changeItems, icon: "external-link-alt" });
+        this._downX = this._downY = Number.NaN;
     }
 
     recordDictation = () => {
@@ -920,6 +923,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                 clipboardTextSerializer: this.clipboardTextSerializer,
                 handlePaste: this.handlePaste,
             });
+            // applyDevTools.applyDevTools(this._editorView);
             const startupText = !rtfField && this._editorView && Field.toString(this.dataDoc[fieldKey] as Field);
             if (startupText) {
                 const { state: { tr }, dispatch } = this._editorView;
@@ -1008,7 +1012,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         if (e.buttons === 1 && this.props.isSelected(true) && !e.altKey) {
             e.stopPropagation();
         }
-        this._downX = this._downY = Number.NaN;
     }
 
     @action
@@ -1073,38 +1076,35 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     }
 
     // this hackiness handles clicking on the list item bullets to do expand/collapse.  the bullets are ::before pseudo elements so there's no real way to hit test against them.
-    hitBulletTargets(x: number, y: number, select: boolean, highlightOnly: boolean) {
+    hitBulletTargets(x: number, y: number, collapse: boolean, highlightOnly: boolean) {
         clearStyleSheetRules(FormattedTextBox._bulletStyleSheet);
-        const pos = this._editorView!.posAtCoords({ left: x, top: y });
-        if (pos && this.props.isSelected(true)) {
-            // let beforeEle = document.querySelector("." + hit.className) as Element; // const before = hit ? window.getComputedStyle(hit, ':before') : undefined;
-            //const node = this._editorView!.state.doc.nodeAt(pos.pos);
-            const $pos = this._editorView!.state.doc.resolve(pos.pos);
-            let list_node = $pos.node().type === schema.nodes.list_item ? $pos.node() : undefined;
-            if ($pos.node().type === schema.nodes.ordered_list) {
-                for (let off = 1; off < 100; off++) {
-                    const pos = this._editorView!.posAtCoords({ left: x + off, top: y });
-                    const node = pos && this._editorView!.state.doc.nodeAt(pos.pos);
-                    if (node?.type === schema.nodes.list_item) {
-                        list_node = node;
-                        break;
-                    }
+        const clickPos = this._editorView!.posAtCoords({ left: x, top: y });
+        let olistPos = clickPos?.pos;
+        if (clickPos && olistPos && this.props.isSelected(true)) {
+            const clickNode = this._editorView?.state.doc.nodeAt(olistPos);
+            let nodeBef = this._editorView?.state.doc.nodeAt(Math.max(0, olistPos - 1));
+            olistPos = nodeBef?.type === this._editorView?.state.schema.nodes.ordered_list ? olistPos - 1 : olistPos;
+            let $olistPos = this._editorView?.state.doc.resolve(olistPos);
+            let olistNode = (nodeBef !== null || clickNode?.type === this._editorView?.state.schema.nodes.list_item) && olistPos === clickPos?.pos ? clickNode : nodeBef;
+            if (olistNode?.type === this._editorView?.state.schema.nodes.list_item) {
+                if ($olistPos && ($olistPos as any).path.length > 3) {
+                    olistNode = $olistPos.parent;
+                    $olistPos = this._editorView?.state.doc.resolve(($olistPos as any).path[($olistPos as any).path.length - 4]);
                 }
             }
-            if (list_node && pos.inside >= 0 && this._editorView!.state.doc.nodeAt(pos.inside)!.attrs.bulletStyle === list_node.attrs.bulletStyle) {
-                if (select) {
-                    const $olist_pos = this._editorView!.state.doc.resolve($pos.pos - $pos.parentOffset - 1);
+            const listNode = this._editorView?.state.doc.nodeAt(clickPos.pos!);
+            if (olistNode && olistNode.type === this._editorView?.state.schema.nodes.ordered_list) {
+                if (!collapse) {
                     if (!highlightOnly) {
-                        this._editorView!.dispatch(this._editorView!.state.tr.setSelection(new NodeSelection($olist_pos)));
+                        this._editorView!.dispatch(this._editorView!.state.tr.setSelection(new NodeSelection($olistPos!)));
                     }
-                    addStyleSheetRule(FormattedTextBox._bulletStyleSheet, list_node.attrs.mapStyle + list_node.attrs.bulletStyle + ":hover:before", { background: "lightgray" });
-                } else if (Math.abs(pos.pos - pos.inside) < 2) {
+                    addStyleSheetRule(FormattedTextBox._bulletStyleSheet, olistNode.attrs.mapStyle + olistNode.attrs.bulletStyle + ":hover:before", { background: "lightgray" });
+                } else if (listNode && listNode.type === this._editorView.state.schema.nodes.list_item) {
                     if (!highlightOnly) {
-                        const offset = this._editorView!.state.doc.nodeAt(pos.inside)?.type === schema.nodes.ordered_list ? 1 : 0;
-                        this._editorView!.dispatch(this._editorView!.state.tr.setNodeMarkup(pos.inside + offset, list_node.type, { ...list_node.attrs, visibility: !list_node.attrs.visibility }));
-                        this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, pos.inside + offset)));
+                        this._editorView!.dispatch(this._editorView!.state.tr.setNodeMarkup(clickPos.pos!, listNode.type, { ...listNode.attrs, visibility: !listNode.attrs.visibility }));
+                        this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, clickPos.pos!)));
                     }
-                    addStyleSheetRule(FormattedTextBox._bulletStyleSheet, list_node.attrs.mapStyle + list_node.attrs.bulletStyle + ":hover:before", { background: "lightgray" });
+                    addStyleSheetRule(FormattedTextBox._bulletStyleSheet, olistNode.attrs.mapStyle + olistNode.attrs.bulletStyle + ":hover:before", { background: "lightgray" });
                 }
             }
         }
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index 0a4c52ef9..29bd1da67 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -16,16 +16,13 @@ const mac = typeof navigator !== "undefined" ? /Mac/.test(navigator.platform) :
 
 export type KeyMap = { [key: string]: any };
 
-export let updateBullets = (tx2: Transaction, schema: Schema, mapStyle?: string) => {
-    let fontSize: number | undefined = undefined;
+export let updateBullets = (tx2: Transaction, schema: Schema, mapStyle?: string, from?: number, to?: number) => {
     tx2.doc.descendants((node: any, offset: any, index: any) => {
-        if (node.type === schema.nodes.ordered_list || node.type === schema.nodes.list_item) {
+        if ((!from || !to || (from <= offset && to >= offset)) && (node.type === schema.nodes.ordered_list || node.type === schema.nodes.list_item)) {
             const path = (tx2.doc.resolve(offset) as any).path;
             let depth = Array.from(path).reduce((p: number, c: any) => p + (c.hasOwnProperty("type") && c.type === schema.nodes.ordered_list ? 1 : 0), 0);
             if (node.type === schema.nodes.ordered_list) depth++;
-            fontSize = depth === 1 && node.attrs.setFontSize ? Number(node.attrs.setFontSize) : fontSize;
-            const fsize = fontSize && node.type === schema.nodes.ordered_list ? Math.max(6, fontSize - (depth - 1) * 4) : undefined;
-            tx2.setNodeMarkup(offset, node.type, { ...node.attrs, mapStyle: mapStyle ? mapStyle : node.attrs.mapStyle, bulletStyle: depth, inheritedFontSize: fsize }, node.marks);
+            tx2.setNodeMarkup(offset, node.type, { ...node.attrs, mapStyle: mapStyle || node.attrs.mapStyle, bulletStyle: depth, }, node.marks);
         }
     });
     return tx2;
@@ -62,7 +59,6 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, props: any
     bind("Mod-U", toggleMark(schema.marks.underline));
 
     //Commands for lists
-    bind("Ctrl-.", wrapInList(schema.nodes.bullet_list));
     bind("Ctrl-i", wrapInList(schema.nodes.ordered_list));
 
     bind("Tab", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 03d393cde..456ac7770 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -189,9 +189,9 @@ export default class RichTextMenu extends AntimodeMenu {
             const node = (state.selection as NodeSelection).node;
             if (node?.type === schema.nodes.ordered_list) {
                 let attrs = node.attrs;
-                if (mark.type === schema.marks.pFontFamily) attrs = { ...attrs, setFontFamily: mark.attrs.family };
-                if (mark.type === schema.marks.pFontSize) attrs = { ...attrs, setFontSize: mark.attrs.fontSize };
-                if (mark.type === schema.marks.pFontColor) attrs = { ...attrs, setFontColor: mark.attrs.color };
+                if (mark.type === schema.marks.pFontFamily) attrs = { ...attrs, fontFamily: mark.attrs.family };
+                if (mark.type === schema.marks.pFontSize) attrs = { ...attrs, fontSize: `${mark.attrs.fontSize}px` };
+                if (mark.type === schema.marks.pFontColor) attrs = { ...attrs, fontColor: mark.attrs.color };
                 const tr = updateBullets(state.tr.setNodeMarkup(state.selection.from, node.type, attrs), state.schema);
                 dispatch(tr.setSelection(new NodeSelection(tr.doc.resolve(state.selection.from))));
             } else {
@@ -378,22 +378,20 @@ export default class RichTextMenu extends AntimodeMenu {
 
     // TODO: remove doesn't work
     //remove all node type and apply the passed-in one to the selected text
-    changeListType = (nodeType: NodeType | undefined) => {
+    changeListType = (nodeType: Node | undefined) => {
         if (!this.view) return;
 
-        if (nodeType === schema.nodes.bullet_list) {
-            wrapInList(nodeType)(this.view.state, this.view.dispatch);
-        } else {
-            const marks = this.view.state.storedMarks || (this.view.state.selection.$to.parentOffset && this.view.state.selection.$from.marks());
-            if (!wrapInList(schema.nodes.ordered_list)(this.view.state, (tx2: any) => {
-                const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle);
-                marks && tx3.ensureMarks([...marks]);
-                marks && tx3.setStoredMarks([...marks]);
-
-                this.view!.dispatch(tx2);
-            })) {
-                const tx2 = this.view.state.tr;
-                const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle);
+        const marks = this.view.state.storedMarks || (this.view.state.selection.$to.parentOffset && this.view.state.selection.$from.marks());
+        if (!wrapInList(schema.nodes.ordered_list)(this.view.state, (tx2: any) => {
+            const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle, this.view!.state.selection.from - 1, this.view!.state.selection.to + 1);
+            marks && tx3.ensureMarks([...marks]);
+            marks && tx3.setStoredMarks([...marks]);
+
+            this.view!.dispatch(tx2);
+        })) {
+            const tx2 = this.view.state.tr;
+            if (nodeType && this.view.state.selection.$from.nodeAfter?.type === schema.nodes.ordered_list) {
+                const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle, this.view.state.selection.from, this.view.state.selection.to);
                 marks && tx3.ensureMarks([...marks]);
                 marks && tx3.setStoredMarks([...marks]);
 
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index 91187edf9..e442149d6 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -59,7 +59,7 @@ export class RichTextRules {
             ),
 
             // * + - create bullet list
-            wrappingInputRule(/^\s*([-+*])\s$/, schema.nodes.bullet_list),
+            wrappingInputRule(/^\s*([-+*])\s$/, schema.nodes.ordered_list),
 
             // ``` create code block
             textblockTypeInputRule(/^```$/, schema.nodes.code_block),
diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts
index 49d5c96a4..1de211f28 100644
--- a/src/client/views/nodes/formattedText/marks_rts.ts
+++ b/src/client/views/nodes/formattedText/marks_rts.ts
@@ -53,21 +53,45 @@ export const marks: { [index: string]: MarkSpec } = {
         }
     },
 
+    /** FONT SIZES */
+    pFontSize: {
+        attrs: { fontSize: { default: 10 } },
+        parseDOM: [{
+            tag: "span", getAttrs(dom: any) {
+                return { fontSize: dom.style.fontSize ? Number(dom.style.fontSize.replace("px", "")) : "" };
+            }
+        }],
+        toDOM: (node) => node.attrs.fontSize ? ['span', { style: `font-size: ${node.attrs.fontSize}px;` }] : ['span', 0]
+    },
 
+    /* FONTS */
+    pFontFamily: {
+        attrs: { family: { default: "" } },
+        parseDOM: [{
+            tag: "span", getAttrs(dom: any) {
+                const cstyle = getComputedStyle(dom);
+                if (cstyle.font) {
+                    if (cstyle.font.indexOf("Times New Roman") !== -1) return { family: "Times New Roman" };
+                    if (cstyle.font.indexOf("Arial") !== -1) return { family: "Arial" };
+                    if (cstyle.font.indexOf("Georgia") !== -1) return { family: "Georgia" };
+                    if (cstyle.font.indexOf("Comic Sans") !== -1) return { family: "Comic Sans MS" };
+                    if (cstyle.font.indexOf("Tahoma") !== -1) return { family: "Tahoma" };
+                    if (cstyle.font.indexOf("Crimson") !== -1) return { family: "Crimson Text" };
+                }
+            }
+        }],
+        toDOM: (node) => node.attrs.family ? ['span', { style: `font-family: "${node.attrs.family}";` }] : ['span', 0]
+    },
     // :: MarkSpec Coloring on text. Has `color` attribute that defined the color of the marked text.
     pFontColor: {
-        attrs: {
-            color: { default: "#000" }
-        },
+        attrs: { color: { default: "" } },
         inclusive: true,
         parseDOM: [{
             tag: "span", getAttrs(dom: any) {
                 return { color: dom.getAttribute("color") };
             }
         }],
-        toDOM(node: any) {
-            return node.attrs.color ? ['span', { style: 'color:' + node.attrs.color }] : ['span', 0];
-        }
+        toDOM: (node) => node.attrs.color ? ['span', { style: 'color:' + node.attrs.color }] : ['span', 0]
     },
 
     marker: {
@@ -277,38 +301,4 @@ export const marks: { [index: string]: MarkSpec } = {
         parseDOM: [{ tag: "code" }],
         toDOM() { return codeDOM; }
     },
-
-    /* FONTS */
-    pFontFamily: {
-        attrs: {
-            family: { default: "Crimson Text" },
-        },
-        parseDOM: [{
-            tag: "span", getAttrs(dom: any) {
-                const cstyle = getComputedStyle(dom);
-                if (cstyle.font) {
-                    if (cstyle.font.indexOf("Times New Roman") !== -1) return { family: "Times New Roman" };
-                    if (cstyle.font.indexOf("Arial") !== -1) return { family: "Arial" };
-                    if (cstyle.font.indexOf("Georgia") !== -1) return { family: "Georgia" };
-                    if (cstyle.font.indexOf("Comic Sans") !== -1) return { family: "Comic Sans MS" };
-                    if (cstyle.font.indexOf("Tahoma") !== -1) return { family: "Tahoma" };
-                    if (cstyle.font.indexOf("Crimson") !== -1) return { family: "Crimson Text" };
-                }
-            }
-        }],
-        toDOM: (node) => ['span', {
-            style: `font-family: "${node.attrs.family}";`
-        }]
-    },
-
-    /** FONT SIZES */
-    pFontSize: {
-        attrs: {
-            fontSize: { default: 10 }
-        },
-        parseDOM: [{ style: 'font-size: 10px;' }],
-        toDOM: (node) => ['span', {
-            style: `font-size: ${node.attrs.fontSize}px;`
-        }]
-    },
 };
diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts
index af39ef9c7..b37e61d3f 100644
--- a/src/client/views/nodes/formattedText/nodes_rts.ts
+++ b/src/client/views/nodes/formattedText/nodes_rts.ts
@@ -218,48 +218,85 @@ export const nodes: { [index: string]: NodeSpec } = {
         group: 'block',
         attrs: {
             bulletStyle: { default: 0 },
-            mapStyle: { default: "decimal" },
-            setFontSize: { default: undefined },
-            setFontFamily: { default: "inherit" },
-            setFontColor: { default: "inherit" },
-            inheritedFontSize: { default: undefined },
+            mapStyle: { default: "decimal" },// "decimal", "multi", "bullet"
+            fontColor: { default: "inherit" },
+            fontSize: { default: undefined },
+            fontFamily: { default: undefined },
             visibility: { default: true },
             indent: { default: undefined }
         },
+        parseDOM: [
+            {
+                tag: "ul", getAttrs(dom: any) {
+                    return {
+                        bulletStyle: dom.getAttribute("data-bulletStyle"),
+                        mapStyle: dom.getAttribute("data-mapStyle"),
+                        fontColor: dom.style.color,
+                        fontSize: dom.style["font-size"],
+                        fontFamily: dom.style["font-family"],
+                        indent: dom.style["margin-left"]
+                    };
+                }
+            },
+            {
+                style: 'list-style-type=disc', getAttrs(dom: any) {
+                    return { mapStyle: "bullet" }
+                }
+            },
+            {
+                tag: "ol", getAttrs(dom: any) {
+                    return {
+                        bulletStyle: dom.getAttribute("data-bulletStyle"),
+                        mapStyle: dom.getAttribute("data-mapStyle"),
+                        fontColor: dom.style.color,
+                        fontSize: dom.style["font-size"],
+                        fontFamily: dom.style["font-family"],
+                        indent: dom.style["margin-left"]
+                    };
+                }
+            }],
         toDOM(node: Node<any>) {
-            if (node.attrs.mapStyle === "bullet") return ['ul', 0];
             const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : "";
-            const fsize = node.attrs.setFontSize ? node.attrs.setFontSize : node.attrs.inheritedFontSize;
-            const ffam = node.attrs.setFontFamily;
-            const color = node.attrs.setFontColor;
+            const fsize = node.attrs.fontSize ? `font-size: ${node.attrs.fontSize};` : "";
+            const ffam = node.attrs.fontFamily ? `font-family:${node.attrs.fontFamily};` : "";
+            const fcol = node.attrs.fontColor ? `color: ${node.attrs.fontColor};` : "";
+            const marg = node.attrs.indent ? `margin-left: ${node.attrs.indent};` : "";
+            if (node.attrs.mapStyle === "bullet") {
+                return ['ul', {
+                    "data-mapStyle": node.attrs.mapStyle,
+                    "data-bulletStyle": node.attrs.bulletStyle,
+                    style: `${fsize} ${ffam} ${fcol} ${marg}`
+                }, 0];
+            }
             return node.attrs.visibility ?
-                ['ol', { class: `${map}-ol`, style: `list-style: none; font-size: ${fsize}; font-family: ${ffam}; color:${color}; margin-left: ${node.attrs.indent}` }, 0] :
+                ['ol', {
+                    class: `${map}-ol`,
+                    "data-mapStyle": node.attrs.mapStyle,
+                    "data-bulletStyle": node.attrs.bulletStyle,
+                    style: `list-style: none; ${fsize} ${ffam} ${fcol} ${marg}`
+                }, 0] :
                 ['ol', { class: `${map}-ol`, style: `list-style: none;` }];
         }
     },
 
-    bullet_list: {
-        ...bulletList,
-        content: 'list_item+',
-        group: 'block',
-        // parseDOM: [{ tag: "ul" }, { style: 'list-style-type=disc' }],
-        toDOM(node: Node<any>) {
-            return ['ul', 0];
-        }
-    },
-
     list_item: {
+        ...listItem,
         attrs: {
             bulletStyle: { default: 0 },
-            mapStyle: { default: "decimal" },
+            mapStyle: { default: "decimal" }, // "decimal", "multi", "bullet"
             visibility: { default: true }
         },
-        ...listItem,
         content: 'paragraph block*',
+        parseDOM: [{
+            tag: "li", getAttrs(dom: any) {
+                return { mapStyle: dom.getAttribute("data-mapStyle"), bulletStyle: dom.getAttribute("data-bulletStyle") };
+            }
+        }],
         toDOM(node: any) {
             const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : "";
-            return node.attrs.visibility ? ["li", { class: `${map}` }, 0] : ["li", { class: `${map}` }, "..."];
-            //return ["li", { class: `${map}` }, 0];
+            return node.attrs.visibility ?
+                ["li", { class: `${map}`, "data-mapStyle": node.attrs.mapStyle, "data-bulletStyle": node.attrs.bulletStyle }, 0] :
+                ["li", { class: `${map}`, "data-mapStyle": node.attrs.mapStyle, "data-bulletStyle": node.attrs.bulletStyle }, "..."];
         }
     },
 };
\ No newline at end of file
diff --git a/src/fields/RichTextUtils.ts b/src/fields/RichTextUtils.ts
index 66959882d..7c7bf3e12 100644
--- a/src/fields/RichTextUtils.ts
+++ b/src/fields/RichTextUtils.ts
@@ -256,7 +256,7 @@ export namespace RichTextUtils {
         };
 
         const list = (schema: any, items: Node[]): Node => {
-            return schema.node("bullet_list", null, items);
+            return schema.node("ordered_list", { mapStyle: "bullet" }, items);
         };
 
         const paragraphNode = (schema: any, runs: docs_v1.Schema$TextRun[]): Node => {
-- 
cgit v1.2.3-70-g09d2

