From 155cf5c5a1b45aafe7a5632f2ee6ecf957f04dde Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 3 Sep 2019 23:07:42 -0400
Subject: fixes for bullets

---
 src/client/util/RichTextSchema.tsx | 11 +++++++----
 1 file changed, 7 insertions(+), 4 deletions(-)

(limited to 'src/client/util/RichTextSchema.tsx')

diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 25d972857..75e982872 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -203,7 +203,6 @@ export const nodes: { [index: string]: NodeSpec } = {
             const decMap = bs ? "decimal" + bs : "";
             const multiMap = bs === 1 ? "decimal1" : bs === 2 ? "upper-alpha" : bs === 3 ? "lower-roman" : bs === 4 ? "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 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"];
@@ -216,7 +215,6 @@ export const nodes: { [index: string]: NodeSpec } = {
         group: 'block',
         // 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];
         }
     },
@@ -231,12 +229,17 @@ export const nodes: { [index: string]: NodeSpec } = {
     // },
     list_item: {
         attrs: {
-            className: { default: "" }
+            bulletStyle: { default: 0 },
+            mapStyle: { default: "decimal" },
         },
         ...listItem,
         content: 'paragraph block*',
         toDOM(node: any) {
-            return ["li", { class: node.attrs.className }, 0];
+            const bs = node.attrs.bulletStyle;
+            const decMap = bs ? "decimal" + bs : "";
+            const multiMap = bs === 1 ? "decimal1" : bs === 2 ? "upper-alpha" : bs === 3 ? "lower-roman" : bs === 4 ? "lower-alpha" : "";
+            let map = node.attrs.mapStyle === "decimal" ? decMap : multiMap;
+            return ["li", { class: `${map}` }, 0];
         }
     },
 };
-- 
cgit v1.2.3-70-g09d2


From 2262d7562b57c37c238fc8a7c373304984a5fe3b Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Wed, 4 Sep 2019 17:49:19 -0400
Subject: prosemirror restructuring.

---
 src/client/util/ProsemirrorExampleTransfer.ts      | 71 +++++-----------------
 src/client/util/RichTextSchema.tsx                 | 71 +++++++++++-----------
 src/client/util/TooltipTextMenu.tsx                | 37 ++++++-----
 src/client/util/prosemirrorPatches.js              | 30 +++++++++
 src/client/views/OverlayView.tsx                   |  2 +-
 src/client/views/nodes/FormattedTextBox.tsx        |  6 +-
 src/client/views/nodes/FormattedTextBoxComment.tsx |  4 +-
 src/client/views/pdf/Page.tsx                      |  2 +-
 8 files changed, 107 insertions(+), 116 deletions(-)

(limited to 'src/client/util/RichTextSchema.tsx')

diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts
index 5016e72c6..9c4b2d3d1 100644
--- a/src/client/util/ProsemirrorExampleTransfer.ts
+++ b/src/client/util/ProsemirrorExampleTransfer.ts
@@ -2,8 +2,8 @@ import { chainCommands, exitCode, joinDown, joinUp, lift, selectParentNode, setB
 import { redo, undo } from "prosemirror-history";
 import { undoInputRule } from "prosemirror-inputrules";
 import { Schema } from "prosemirror-model";
-import { liftListItem, } from "./prosemirrorPatches.js";
-import { splitListItem, wrapInList, sinkListItem } from "prosemirror-schema-list";
+import { liftListItem, sinkListItem } from "./prosemirrorPatches.js";
+import { splitListItem, wrapInList, } from "prosemirror-schema-list";
 import { EditorState, Transaction, TextSelection, NodeSelection } from "prosemirror-state";
 import { TooltipTextMenu } from "./TooltipTextMenu";
 
@@ -62,7 +62,7 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?:
             return true;
         }
         return false;
-    })
+    });
 
 
     let cmd = chainCommands(exitCode, (state, dispatch) => {
@@ -93,54 +93,16 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?:
 
     bind("Mod-s", TooltipTextMenu.insertStar);
 
-    let updateOrderedList = (start: number, rangeStart: any, delta: number, tx2: Transaction, forward: boolean) => {
-        let bs = rangeStart.attrs.bulletStyle;
-        bs + delta > 0 && tx2.setNodeMarkup(start, rangeStart.type, { mapStyle: rangeStart.attrs.mapStyle, bulletStyle: rangeStart.attrs.bulletStyle + delta }, rangeStart.marks);
-
-        let brk = false;
-        rangeStart.descendants((node: any, offset: any, index: any) => {
+    let updateBullets = (tx2: Transaction) => {
+        tx2.doc.descendants((node: any, offset: any, index: any) => {
             if (node.type === schema.nodes.ordered_list || node.type === schema.nodes.list_item) {
-                if (!brk && (bs !== node.attrs.bulletStyle || delta > 0 || (forward && bs > 1))) {
-                    tx2.setNodeMarkup(start + offset + 1, node.type, { mapStyle: node.attrs.mapStyle, bulletStyle: node.attrs.bulletStyle + delta }, node.marks);
-                } else {
-                    brk = true;
-                }
+                let path = (tx2.doc.resolve(offset) as any).path;
+                let depth = Array.from(path).reduce((p: number, c: any) => p + (c.hasOwnProperty("type") && (c as any).type === schema.nodes.ordered_list ? 1 : 0), 0);
+                if (node.type === schema.nodes.ordered_list) depth++;
+                tx2.setNodeMarkup(offset, node.type, { mapStyle: node.attrs.mapStyle, bulletStyle: depth }, node.marks);
             }
         });
-    }
-
-    let updateBullets = (tx2: Transaction, refStart: number, delta: number) => {
-        let i = refStart;
-        for (let i = refStart; i >= 0; i--) {
-            let testPos = tx2.doc.nodeAt(i);
-            if (!testPos) {
-                for (let i = refStart + 1; i <= tx2.doc.nodeSize; i++) {
-                    try {
-                        let testPos = tx2.doc.nodeAt(i);
-                        if (testPos && testPos.type === schema.nodes.ordered_list) {
-                            updateOrderedList(i, testPos, delta, tx2, true);
-                            break;
-                        }
-                    } catch (e) {
-                        break;
-                    }
-                }
-                break;
-            }
-            if ((testPos.type === schema.nodes.list_item || testPos.type === schema.nodes.ordered_list)) {
-                let start = i;
-                let preve = i > 0 && tx2.doc.nodeAt(start - 1);
-                if (preve && preve.type === schema.nodes.ordered_list) {
-                    start = start - 1;
-                }
-                let rangeStart = tx2.doc.nodeAt(start);
-                if (rangeStart) {
-                    updateOrderedList(start, rangeStart, delta, tx2, false);
-                }
-                break;
-            }
-        }
-    }
+    };
 
 
     bind("Tab", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
@@ -148,8 +110,7 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?:
         var range = ref.$from.blockRange(ref.$to);
         var marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
         if (!sinkListItem(schema.nodes.list_item)(state, (tx2: Transaction) => {
-            var range = state.selection.$from.blockRange(state.selection.$to);
-            updateBullets(tx2, range!.start - 1, 1);
+            updateBullets(tx2);
             marks && tx2.ensureMarks([...marks]);
             marks && tx2.setStoredMarks([...marks]);
             dispatch(tx2);
@@ -157,7 +118,7 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?:
             let sxf = state.tr.setSelection(TextSelection.create(state.doc, range!.start, range!.end));
             let newstate = state.applyTransaction(sxf);
             if (!wrapInList(schema.nodes.ordered_list)(newstate.state, (tx2: Transaction) => {
-                updateBullets(tx2, range!.start, 1);
+                updateBullets(tx2);
                 // when promoting to a list, assume list will format things so don't copy the stored marks.
                 marks && tx2.ensureMarks([...marks]);
                 marks && tx2.setStoredMarks([...marks]);
@@ -169,14 +130,10 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?:
     });
 
     bind("Shift-Tab", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
-        var range = state.selection.$from.blockRange(state.selection.$to);
         var marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
 
-        let tr = state.tr;
-
-        if (!liftListItem(schema.nodes.list_item)(tr, (tx2: Transaction) => {
-            var range = tx2.selection.$from.blockRange(tx2.selection.$to);
-            updateBullets(tx2, range!.start, -1);
+        if (!liftListItem(schema.nodes.list_item)(state.tr, (tx2: Transaction) => {
+            updateBullets(tx2);
             marks && tx2.ensureMarks([...marks]);
             marks && tx2.setStoredMarks([...marks]);
             dispatch(tx2);
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 75e982872..3174d668c 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -597,8 +597,6 @@ 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
     }
@@ -613,33 +611,33 @@ export class FootnoteView {
 
     constructor(node: any, view: any, getPos: any) {
         // We'll need these later
-        this.node = node
-        this.outerView = view
-        this.getPos = getPos
+        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
+        this.innerView = null;
     }
     selectNode() {
         const attrs = { ...this.node.attrs };
         attrs.visibility = true;
-        this.dom.classList.add("ProseMirror-selectednode")
-        if (!this.innerView) this.open()
+        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()
+        this.dom.classList.remove("ProseMirror-selectednode");
+        if (this.innerView) this.close();
     }
     open() {
-        if (!(this.outerView as any).isOverlay) return;
+        if (!this.outerView.isOverlay) return;
         // Append a tooltip to the outer node
-        let tooltip = this.dom.appendChild(document.createElement("div"))
+        let tooltip = this.dom.appendChild(document.createElement("div"));
         tooltip.className = "footnote-tooltip";
         // And put a sub-ProseMirror into that
         this.innerView = new EditorView(tooltip, {
@@ -679,56 +677,55 @@ export class FootnoteView {
         if (this.innerView) this.close();
         else {
             this.open();
-
         }
     }
     close() {
-        this.innerView && this.innerView.destroy()
-        this.innerView = null
-        this.dom.textContent = ""
+        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)
+        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))
+            let outerTr = this.outerView.state.tr, offsetMap = StepMap.offset(this.getPos() + 1);
+            for (let steps of transactions) {
+                for (let step of steps) {
+                    outerTr.step(step.map(offsetMap));
+                }
             }
-            if (outerTr.docChanged) this.outerView.dispatch(outerTr)
+            if (outerTr.docChanged) this.outerView.dispatch(outerTr);
         }
     }
     update(node: any) {
-        if (!node.sameMarkup(this.node)) return false
-        this.node = node
+        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 }
+            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))
+                        .setMeta("fromOutside", true));
             }
         }
-        return true
+        return true;
     }
 
     destroy() {
-        if (this.innerView) this.close()
+        if (this.innerView) this.close();
     }
 
     stopEvent(event: any) {
-        return this.innerView && this.innerView.dom.contains(event.target)
+        return this.innerView && this.innerView.dom.contains(event.target);
     }
 
-    ignoreMutation() { return true }
+    ignoreMutation() { return true; }
 }
 
 export class SummarizedView {
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index ce7b04e31..6d375fc1d 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -4,7 +4,7 @@ import { action, observable } from "mobx";
 import { Dropdown, icons, MenuItem } from "prosemirror-menu"; //no import css
 import { Mark, MarkType, Node as ProsNode, NodeType, ResolvedPos, Schema } from "prosemirror-model";
 import { wrapInList } from 'prosemirror-schema-list';
-import { EditorState, NodeSelection, TextSelection } from "prosemirror-state";
+import { EditorState, NodeSelection, TextSelection, Transaction } from "prosemirror-state";
 import { EditorView } from "prosemirror-view";
 import { Doc, Field, Opt } from "../../new_fields/Doc";
 import { Id } from "../../new_fields/FieldSymbols";
@@ -509,30 +509,37 @@ export class TooltipTextMenu {
         }
     }
 
+    updateBullets = (tx2: Transaction, style: string) => {
+        tx2.doc.descendants((node: any, offset: any, index: any) => {
+            if (node.type === schema.nodes.ordered_list || node.type === schema.nodes.list_item) {
+                let path = (tx2.doc.resolve(offset) as any).path;
+                let depth = Array.from(path).reduce((p: number, c: any) => p + (c.hasOwnProperty("type") && (c as any).type === schema.nodes.ordered_list ? 1 : 0), 0);
+                if (node.type === schema.nodes.ordered_list) depth++;
+                tx2.setNodeMarkup(offset, node.type, { mapStyle: style, bulletStyle: depth }, node.marks);
+            }
+        });
+    };
     //remove all node typeand apply the passed-in one to the selected text
-    changeToNodeType(nodeType: NodeType | undefined, view: EditorView) {
+    changeToNodeType = (nodeType: NodeType | undefined, view: EditorView) => {
         //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 = 1;
-                        path[i].attrs.mapStyle = (nodeType as any).attrs.mapStyle;
-                        break;
-                    }
-                }
+            if (!wrapInList(schema.nodes.ordered_list)(view.state, (tx2: any) => {
+                this.updateBullets(tx2, (nodeType as any).attrs.mapStyle);
                 marks && tx2.ensureMarks([...marks]);
                 marks && tx2.setStoredMarks([...marks]);
 
                 view.dispatch(tx2);
-            });
+            })) {
+                let tx2 = view.state.tr;
+                this.updateBullets(tx2, (nodeType as any).attrs.mapStyle);
+                marks && tx2.ensureMarks([...marks]);
+                marks && tx2.setStoredMarks([...marks]);
+
+                view.dispatch(tx2);
+            }
         }
     }
 
diff --git a/src/client/util/prosemirrorPatches.js b/src/client/util/prosemirrorPatches.js
index c273c2323..6bf4395ad 100644
--- a/src/client/util/prosemirrorPatches.js
+++ b/src/client/util/prosemirrorPatches.js
@@ -6,6 +6,7 @@ var prosemirrorTransform = require('prosemirror-transform');
 var prosemirrorModel = require('prosemirror-model');
 
 exports.liftListItem = liftListItem;
+exports.sinkListItem = sinkListItem;
 // :: (NodeType) → (state: EditorState, dispatch: ?(tr: Transaction)) → bool
 // Create a command to lift the list item around the selection up into
 // a wrapping list.
@@ -59,4 +60,33 @@ function liftOutOfList(tr, dispatch, range) {
             atStart ? 0 : 1, atEnd ? 0 : 1), atStart ? 0 : 1));
     dispatch(tr.scrollIntoView());
     return true
+}
+
+// :: (NodeType) → (state: EditorState, dispatch: ?(tr: Transaction)) → bool
+// Create a command to sink the list item around the selection down
+// into an inner list.
+function sinkListItem(itemType) {
+    return function (state, dispatch) {
+        var ref = state.selection;
+        var $from = ref.$from;
+        var $to = ref.$to;
+        var range = $from.blockRange($to, function (node) { return node.childCount && node.firstChild.type == itemType; });
+        if (!range) { return false }
+        var startIndex = range.startIndex;
+        if (startIndex == 0) { return false }
+        var parent = range.parent, nodeBefore = parent.child(startIndex - 1);
+        if (nodeBefore.type != itemType) { return false; }
+
+        if (dispatch) {
+            var nestedBefore = nodeBefore.lastChild && nodeBefore.lastChild.type == parent.type;
+            var inner = prosemirrorModel.Fragment.from(nestedBefore ? itemType.create() : null);
+            let slice = new prosemirrorModel.Slice(prosemirrorModel.Fragment.from(itemType.create(null, prosemirrorModel.Fragment.from(parent.type.create(parent.attrs, inner)))),
+                nestedBefore ? 3 : 1, 0);
+            var before = range.start, after = range.end;
+            dispatch(state.tr.step(new prosemirrorTransform.ReplaceAroundStep(before - (nestedBefore ? 3 : 1), after,
+                before, after, slice, 1, true))
+                .scrollIntoView());
+        }
+        return true
+    }
 }
\ No newline at end of file
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index 54ab8696e..fe06e4440 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -191,7 +191,7 @@ export class OverlayView extends React.Component {
                     zoomToScale={emptyFunction}
                     getScale={returnOne} />
             </div>;
-        })
+        });
     }
 
     render() {
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 4cb52ec78..f9ad040a2 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -174,7 +174,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     syncNodeSelection(view: any, sel: any) {
         if (sel instanceof NodeSelection) {
             var desc = view.docView.descAt(sel.from);
-            if (desc != view.lastSelectedViewDesc) {
+            if (desc !== view.lastSelectedViewDesc) {
                 if (view.lastSelectedViewDesc) {
                     view.lastSelectedViewDesc.deselectNode();
                     view.lastSelectedViewDesc = null;
@@ -635,8 +635,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); },
-                    footnote(node, view, getPos) { return new FootnoteView(node, view, getPos) }
+                    ordered_list(node, view, getPos) { return new OrderedListView(); },
+                    footnote(node, view, getPos) { return new FootnoteView(node, view, getPos); }
                 },
                 clipboardTextSerializer: this.clipboardTextSerializer,
                 handlePaste: this.handlePaste,
diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx
index 255000936..2d2fff06d 100644
--- a/src/client/views/nodes/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/FormattedTextBoxComment.tsx
@@ -92,7 +92,7 @@ export class FormattedTextBoxComment {
         if (lastState && lastState.doc.eq(state.doc) &&
             lastState.selection.eq(state.selection)) return;
 
-        let set = "none"
+        let set = "none";
         if (state.selection.$from) {
             let nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark);
             let naft = findEndOfMark(state.selection.$from, view, findOtherUserMark);
@@ -130,7 +130,7 @@ export class FormattedTextBoxComment {
                 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) && (FormattedTextBoxComment.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);
diff --git a/src/client/views/pdf/Page.tsx b/src/client/views/pdf/Page.tsx
index 8df2dce29..856e883e7 100644
--- a/src/client/views/pdf/Page.tsx
+++ b/src/client/views/pdf/Page.tsx
@@ -64,7 +64,7 @@ export default class Page extends React.Component<IPageProps> {
         // lower scale = easier to read at small sizes, higher scale = easier to read at large sizes
         if (this._state !== "rendering" && !this._page && this._canvas.current && this._textLayer.current) {
             this._state = "rendering";
-            let viewport = page.getViewport({ scale: scale });
+            let viewport = page.getViewport(scale);
             this._canvas.current.width = this._width = viewport.width;
             this._canvas.current.height = this._height = viewport.height;
             this.props.pageLoaded(viewport);
-- 
cgit v1.2.3-70-g09d2


From a017d20fb40ffb5b99b06b716ac5a6f8635c45e6 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 4 Sep 2019 20:43:28 -0400
Subject: added collapsing bullets

---
 src/client/util/ProsemirrorExampleTransfer.ts |  2 +-
 src/client/util/RichTextSchema.tsx            |  8 ++++++--
 src/client/views/nodes/FormattedTextBox.tsx   | 10 ++++++++++
 3 files changed, 17 insertions(+), 3 deletions(-)

(limited to 'src/client/util/RichTextSchema.tsx')

diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts
index 9c4b2d3d1..9f6da7ade 100644
--- a/src/client/util/ProsemirrorExampleTransfer.ts
+++ b/src/client/util/ProsemirrorExampleTransfer.ts
@@ -99,7 +99,7 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?:
                 let path = (tx2.doc.resolve(offset) as any).path;
                 let depth = Array.from(path).reduce((p: number, c: any) => p + (c.hasOwnProperty("type") && (c as any).type === schema.nodes.ordered_list ? 1 : 0), 0);
                 if (node.type === schema.nodes.ordered_list) depth++;
-                tx2.setNodeMarkup(offset, node.type, { mapStyle: node.attrs.mapStyle, bulletStyle: depth }, node.marks);
+                tx2.setNodeMarkup(offset, node.type, { ...node.attrs, mapStyle: node.attrs.mapStyle, bulletStyle: depth }, node.marks);
             }
         });
     };
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 3174d668c..2b689b7ef 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -197,13 +197,15 @@ export const nodes: { [index: string]: NodeSpec } = {
         attrs: {
             bulletStyle: { default: 0 },
             mapStyle: { default: "decimal" },
+            visibility: { default: true }
         },
         toDOM(node: Node<any>) {
             const bs = node.attrs.bulletStyle;
             const decMap = bs ? "decimal" + bs : "";
             const multiMap = bs === 1 ? "decimal1" : bs === 2 ? "upper-alpha" : bs === 3 ? "lower-roman" : bs === 4 ? "lower-alpha" : "";
             let map = node.attrs.mapStyle === "decimal" ? decMap : multiMap;
-            return ['ol', { class: `${map}-ol`, style: `list-style: none;` }, 0];
+            return node.attrs.visibility ? ['ol', { class: `${map}-ol`, style: `list-style: none;` }, 0] :
+                 ['ol', { class: `${map}-ol`, style: `list-style: none;` }];
             //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"];
         }
@@ -231,6 +233,7 @@ export const nodes: { [index: string]: NodeSpec } = {
         attrs: {
             bulletStyle: { default: 0 },
             mapStyle: { default: "decimal" },
+            visibility: { default: true }
         },
         ...listItem,
         content: 'paragraph block*',
@@ -239,7 +242,8 @@ export const nodes: { [index: string]: NodeSpec } = {
             const decMap = bs ? "decimal" + bs : "";
             const multiMap = bs === 1 ? "decimal1" : bs === 2 ? "upper-alpha" : bs === 3 ? "lower-roman" : bs === 4 ? "lower-alpha" : "";
             let map = node.attrs.mapStyle === "decimal" ? decMap : multiMap;
-            return ["li", { class: `${map}` }, 0];
+            return node.attrs.visibility ? ["li", { class: `${map}` }, 0] : ["li", { class: `${map}` }, "..."];
+            //return ["li", { class: `${map}` }, 0];
         }
     },
 };
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index f9ad040a2..2dbfeeba7 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -672,6 +672,16 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     }
 
     onPointerDown = (e: React.PointerEvent): void => {
+        let pos = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY });
+        if (pos) {
+            if (e.nativeEvent.offsetX < 40) {
+                let node = this._editorView!.state.doc.nodeAt(pos.pos);
+                let node2 = node && node.type === schema.nodes.paragraph ? this._editorView!.state.doc.nodeAt(pos.pos - 1) : undefined;
+                if (node2 && (node2.type === schema.nodes.ordered_list || node2.type === schema.nodes.list_item)) {
+                    this._editorView!.dispatch(this._editorView!.state.tr.setNodeMarkup(pos.pos - 1, node2.type, { ...node2.attrs, visibility: !node2.attrs.visibility }));
+                }
+            }
+        }
         if (this.props.onClick && e.button === 0) {
             e.preventDefault();
         }
-- 
cgit v1.2.3-70-g09d2


From 179b2c7c68e3d240f3f39083bdb686ad31761c04 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 5 Sep 2019 12:00:18 -0400
Subject: fixed some template issues.  added attribution for pasted text

---
 src/client/util/RichTextSchema.tsx                 | 11 ++++--
 .../views/collections/CollectionDockingView.tsx    |  2 +-
 .../views/collections/CollectionStackingView.tsx   |  4 ++
 src/client/views/nodes/FormattedTextBox.scss       |  3 ++
 src/client/views/nodes/FormattedTextBox.tsx        | 44 ++++------------------
 src/client/views/nodes/PDFBox.tsx                  | 15 +++++---
 src/new_fields/Doc.ts                              |  4 +-
 7 files changed, 35 insertions(+), 48 deletions(-)

(limited to 'src/client/util/RichTextSchema.tsx')

diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 2b689b7ef..9c38ee458 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -205,7 +205,7 @@ export const nodes: { [index: string]: NodeSpec } = {
             const multiMap = bs === 1 ? "decimal1" : bs === 2 ? "upper-alpha" : bs === 3 ? "lower-roman" : bs === 4 ? "lower-alpha" : "";
             let map = node.attrs.mapStyle === "decimal" ? decMap : multiMap;
             return node.attrs.visibility ? ['ol', { class: `${map}-ol`, style: `list-style: none;` }, 0] :
-                 ['ol', { class: `${map}-ol`, style: `list-style: none;` }];
+                ['ol', { class: `${map}-ol`, style: `list-style: none;` }];
             //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"];
         }
@@ -262,7 +262,8 @@ export const marks: { [index: string]: MarkSpec } = {
         attrs: {
             href: {},
             location: { default: null },
-            title: { default: null }
+            title: { default: null },
+            docref: { default: false }
         },
         inclusive: false,
         parseDOM: [{
@@ -270,7 +271,11 @@ export const marks: { [index: string]: MarkSpec } = {
                 return { href: dom.getAttribute("href"), location: dom.getAttribute("location"), title: dom.getAttribute("title") };
             }
         }],
-        toDOM(node: any) { return ["a", node.attrs, 0]; }
+        toDOM(node: any) {
+            return node.attrs.docref && node.attrs.title ?
+                ["div", ["span", `"`], ["span", 0], ["span", `"`], ["br"], ["a", { ...node.attrs, class: "prosemirror-attribution" }, node.attrs.title], ["br"]] :
+                ["a", { ...node.attrs }, 0];
+        }
     },
 
     // :: MarkSpec An emphasis mark. Rendered as an `<em>` element.
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 95f94875c..fb8b0c41b 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -607,7 +607,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
         }
         return Transform.Identity();
     }
-    get previewPanelCenteringOffset() { return this.nativeWidth && !BoolCast(this._document!.ignoreAspect) ? (this._panelWidth - this.nativeWidth()) / 2 : 0; }
+    get previewPanelCenteringOffset() { return this.nativeWidth() && !BoolCast(this._document!.ignoreAspect) ? (this._panelWidth - this.nativeWidth()) / 2 : 0; }
 
     addDocTab = (doc: Doc, dataDoc: Doc | undefined, location: string) => {
         if (doc.dockingConfig) {
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 97b31bf2a..654ff2279 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -310,6 +310,10 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     sectionMasonry(heading: SchemaHeaderField | undefined, docList: Doc[]) {
         let cols = Math.max(1, Math.min(docList.length,
             Math.floor((this.props.PanelWidth() - 2 * this.xMargin) / (this.columnWidth + this.gridGap))));
+        if (isNaN(cols)) {
+            console.log("naN");
+            cols = 1;
+        }
         return <div key={heading ? heading.heading : "empty"} className="collectionStackingView-masonrySection">
             {!heading ? (null) :
                 <div key={`${heading.heading}`} className="collectionStackingView-sectionHeader" style={{ background: heading.color }}
diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss
index 8f47402c4..b27ff0fb9 100644
--- a/src/client/views/nodes/FormattedTextBox.scss
+++ b/src/client/views/nodes/FormattedTextBox.scss
@@ -118,6 +118,9 @@ footnote::after {
     width: max-content;
 }
 
+.prosemirror-attribution {
+    font-size: 8px;
+}
 .footnote-tooltip::before {
     border: 5px solid silver;
     border-top-width: 0px;
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index d811273e8..7c3895336 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -141,37 +141,10 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     @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); }
 
 
-    paste = (e: ClipboardEvent) => {
-        if (e.clipboardData && this._editorView) {
-            // let pdfPasteText = `${Utils.GenerateDeterministicGuid("pdf paste")}`;
-            // for (let i = 0; i < e.clipboardData.items.length; i++) {
-            //     let item = e.clipboardData.items.item(i);
-            //     console.log(item)
-            //     if (item.type === "text/plain") {
-            //         console.log("plain")
-            //         item.getAsString((text) => {
-            //             let pdfPasteIndex = text.indexOf(pdfPasteText);
-            //             if (pdfPasteIndex > -1) {
-            //                 let insertText = text.substr(0, pdfPasteIndex);
-            //                 const tx = this._editorView!.state.tr.insertText(insertText);
-            //                 // tx.setSelection(new Selection(tx.))
-            //                 const state = this._editorView!.state;
-            //                 this._editorView!.dispatch(tx);
-            //                 if (FormattedTextBox._toolTipTextMenu) {
-            //                     // this._toolTipTextMenu.makeLinkWithState(state)
-            //                 }
-            //                 e.stopPropagation();
-            //                 e.preventDefault();
-            //             }
-            //         });
-            //     }
-            // }
-        }
-    }
-
     // 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);
@@ -363,8 +336,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     }
 
     componentDidMount() {
-        document.addEventListener("paste", this.paste);
-
         if (!this.props.isOverlay) {
             this._proxyReactionDisposer = reaction(() => this.props.isSelected(),
                 () => {
@@ -584,7 +555,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 if (link) {
                     cbe.clipboardData!.setData("dash/linkDoc", link[Id]);
                     linkId = link[Id];
-                    let frag = addMarkToFrag(slice.content);
+                    let frag = addMarkToFrag(slice.content, (node: Node) => addLinkMark(node, StrCast(doc.title)));
                     slice = new Slice(frag, slice.openStart, slice.openEnd);
                     var tr = view.state.tr.replaceSelection(slice);
                     view.dispatch(tr.scrollIntoView().setMeta("paste", true).setMeta("uiEvent", "paste"));
@@ -594,19 +565,19 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
 
         return true;
 
-        function addMarkToFrag(frag: Fragment) {
+        function addMarkToFrag(frag: Fragment, marker: (node: Node) => Node) {
             const nodes: Node[] = [];
-            frag.forEach(node => nodes.push(addLinkMark(node)));
+            frag.forEach(node => nodes.push(marker(node)));
             return Fragment.fromArray(nodes);
         }
-        function addLinkMark(node: Node) {
+        function addLinkMark(node: Node, title: string) {
             if (!node.isText) {
-                const content = addMarkToFrag(node.content);
+                const content = addMarkToFrag(node.content, (node: Node) => addLinkMark(node, title));
                 return node.copy(content);
             }
             const marks = [...node.marks];
             const linkIndex = marks.findIndex(mark => mark.type.name === "link");
-            const link = view.state.schema.mark(view.state.schema.marks.link, { href: `http://localhost:1050/doc/${linkId}`, location: "onRight" });
+            const link = view.state.schema.mark(view.state.schema.marks.link, { href: `http://localhost:1050/doc/${linkId}`, location: "onRight", title: title, docref: true });
             if (linkIndex !== -1) {
                 marks.splice(linkIndex, 1, link);
             } else {
@@ -668,7 +639,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         this._pullReactionDisposer && this._pullReactionDisposer();
         this._heightReactionDisposer && this._heightReactionDisposer();
         this._searchReactionDisposer && this._searchReactionDisposer();
-        document.removeEventListener("paste", this.paste);
         this._editorView && this._editorView.destroy();
     }
 
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 18f82ff47..df35b603c 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -33,7 +33,9 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
     @observable private _pdf: Opt<Pdfjs.PDFDocumentProxy>;
 
     @computed get containingCollectionDocument() { return this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.Document; }
-    @computed get dataDoc() { return BoolCast(this.props.Document.isTemplate) && this.props.DataDoc ? this.props.DataDoc : this.props.Document; }
+    @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); }
+
+
     @computed get fieldExtensionDoc() { return Doc.resolvedFieldDataDoc(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, "true"); }
 
     private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
@@ -48,7 +50,7 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
     componentDidMount() {
         this.props.setPdfBox && this.props.setPdfBox(this);
 
-        const pdfUrl = Cast(this.props.Document.data, PdfField);
+        const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField);
         if (pdfUrl instanceof PdfField) {
             Pdfjs.getDocument(pdfUrl.url.pathname).promise.then(pdf => runInAction(() => this._pdf = pdf));
         }
@@ -78,7 +80,7 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
 
     @action
     public GotoPage(p: number) {
-        if (p > 0 && p <= NumCast(this.props.Document.numPages)) {
+        if (p > 0 && p <= NumCast(this.dataDoc.numPages)) {
             this.props.Document.curPage = p;
             this.props.Document.panY = (p - 1) * NumCast(this.dataDoc.nativeHeight);
         }
@@ -87,7 +89,7 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
     @action
     public ForwardPage() {
         let cp = this.GetPage() + 1;
-        if (cp <= NumCast(this.props.Document.numPages)) {
+        if (cp <= NumCast(this.dataDoc.numPages)) {
             this.props.Document.curPage = cp;
             this.props.Document.panY = (cp - 1) * NumCast(this.dataDoc.nativeHeight);
         }
@@ -185,11 +187,12 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
         }
     }
 
+
     render() {
-        const pdfUrl = Cast(this.props.Document.data, PdfField);
+        const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField);
         let classname = "pdfBox-cont" + (this.props.active() && !InkingControl.Instance.selectedTool && !this._alt ? "-interactive" : "");
         return (!(pdfUrl instanceof PdfField) || !this._pdf ?
-            <div>{`pdf, ${this.props.Document.data}, not found`}</div> :
+            <div>{`pdf, ${this.dataDoc[this.props.fieldKey]}, not found`}</div> :
             <div className={classname}
                 onScroll={this.onScroll}
                 style={{ marginTop: `${this.containingCollectionDocument ? NumCast(this.containingCollectionDocument.panY) : 0}px` }}
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index a703f1cef..e7ef1dcd3 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -522,6 +522,7 @@ export namespace Doc {
         otherdoc.type = DocumentType.TEMPLATE;
         !templateDoc.nativeWidth && (otherdoc.nativeWidth = 0);
         !templateDoc.nativeHeight && (otherdoc.nativeHeight = 0);
+        !templateDoc.nativeWidth && (otherdoc.ignoreAspect = true);
         return otherdoc;
     }
     export function ApplyTemplateTo(templateDoc: Doc, target: Doc, targetData?: Doc) {
@@ -538,6 +539,7 @@ export namespace Doc {
         target.nativeHeight = Doc.GetProto(target).nativeHeight = undefined;
         !templateDoc.nativeWidth && (target.nativeWidth = 0);
         !templateDoc.nativeHeight && (target.nativeHeight = 0);
+        !templateDoc.nativeHeight && (target.ignoreAspect = true);
         target.width = templateDoc.width;
         target.height = templateDoc.height;
         target.onClick = templateDoc.onClick instanceof ObjectField && templateDoc.onClick[Copy]();
@@ -576,7 +578,7 @@ export namespace Doc {
         /* move certain layout properties from the original data doc to the template layout to avoid
            inheriting them from the template's data doc which may also define these fields for its own use.
         */
-        fieldTemplate.ignoreAspect = BoolCast(fieldTemplate.ignoreAspect);
+        fieldTemplate.ignoreAspect = fieldTemplate.ignoreAspect === undefined ? undefined : BoolCast(fieldTemplate.ignoreAspect);
         fieldTemplate.singleColumn = BoolCast(fieldTemplate.singleColumn);
         fieldTemplate.nativeWidth = Cast(fieldTemplate.nativeWidth, "number");
         fieldTemplate.nativeHeight = Cast(fieldTemplate.nativeHeight, "number");
-- 
cgit v1.2.3-70-g09d2


From e5b52b1083c86b946a2758983e397826579d2282 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 6 Sep 2019 01:41:06 -0400
Subject: fixed summarizing in text notes.

---
 src/Utils.ts                                 |  14 +++
 src/client/util/RichTextSchema.tsx           | 157 ++++++++++++---------------
 src/client/util/TooltipTextMenu.tsx          |  18 ++-
 src/client/views/nodes/FormattedTextBox.scss |  20 ++++
 src/client/views/nodes/FormattedTextBox.tsx  |  41 ++-----
 src/new_fields/Doc.ts                        |   2 +-
 6 files changed, 120 insertions(+), 132 deletions(-)

(limited to 'src/client/util/RichTextSchema.tsx')

diff --git a/src/Utils.ts b/src/Utils.ts
index 959b89fe5..f805ae872 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -133,6 +133,20 @@ export function WithKeys(obj: any, keys: string[], addKeyFunc?: (dup: any) => vo
     return dup;
 }
 
+export 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;
+    return now.toLocaleDateString() + ' ' + h + ':' + m + ' ' + ampm;
+}
+
 export function numberRange(num: number) { return Array.from(Array(num)).map((v, i) => i); }
 
 export function returnTrue() { return true; }
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 2b689b7ef..a8ba0a4be 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -1,4 +1,4 @@
-import { DOMOutputSpecArray, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model";
+import { DOMOutputSpecArray, MarkSpec, Node, NodeSpec, Schema, Slice, Fragment } from "prosemirror-model";
 import { bulletList, listItem, orderedList } from 'prosemirror-schema-list';
 import { TextSelection, EditorState } from "prosemirror-state";
 import { Doc } from "../../new_fields/Doc";
@@ -7,6 +7,8 @@ import { EditorView } from "prosemirror-view";
 import { keymap } from "prosemirror-keymap";
 import { undo, redo } from "prosemirror-history";
 import { toggleMark, splitBlock, selectAll, baseKeymap } from "prosemirror-commands";
+import { Domain } from "domain";
+import { DOM } from "@fortawesome/fontawesome-svg-core";
 
 const pDOM: DOMOutputSpecArray = ["p", 0], blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"],
     preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0];
@@ -42,14 +44,6 @@ export const nodes: { [index: string]: NodeSpec } = {
         toDOM() { return pDOM; }
     },
 
-    // starmine: {
-    //     inline: true,
-    //     attrs: { oldtext: { default: "" } },
-    //     group: "inline",
-    //     toDOM() { return ["star", "㊉"]; },
-    //     parseDOM: [{ tag: "star" }]
-    // },
-
     // :: NodeSpec A blockquote (`<blockquote>`) wrapping one or more blocks.
     blockquote: {
         content: "block+",
@@ -107,10 +101,9 @@ export const nodes: { [index: string]: NodeSpec } = {
             visibility: { default: false },
             text: { default: undefined },
             textslice: { default: undefined },
-            textlen: { default: 0 }
-
         },
         group: "inline",
+        inclusive: false,
         toDOM(node) {
             const attrs = { style: `width: 40px` };
             return ["span", { ...node.attrs, ...attrs }];
@@ -205,12 +198,10 @@ export const nodes: { [index: string]: NodeSpec } = {
             const multiMap = bs === 1 ? "decimal1" : bs === 2 ? "upper-alpha" : bs === 3 ? "lower-roman" : bs === 4 ? "lower-alpha" : "";
             let map = node.attrs.mapStyle === "decimal" ? decMap : multiMap;
             return node.attrs.visibility ? ['ol', { class: `${map}-ol`, style: `list-style: none;` }, 0] :
-                 ['ol', { class: `${map}-ol`, style: `list-style: none;` }];
-            //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"];
+                ['ol', { class: `${map}-ol`, style: `list-style: none;` }];
         }
     },
-    //this doesn't currently work for some reason
+
     bullet_list: {
         ...bulletList,
         content: 'list_item+',
@@ -221,14 +212,6 @@ export const nodes: { [index: string]: NodeSpec } = {
         }
     },
 
-    //bullet_list: {
-    //  content: 'list_item+',
-    // group: 'block',
-    //active: blockActive(schema.nodes.bullet_list),
-    //enable: wrapInList(schema.nodes.bullet_list),
-    //run: wrapInList(schema.nodes.bullet_list),
-    //select: state => true,
-    // },
     list_item: {
         attrs: {
             bulletStyle: { default: 0 },
@@ -251,7 +234,6 @@ export const nodes: { [index: string]: NodeSpec } = {
 const emDOM: DOMOutputSpecArray = ["em", 0];
 const strongDOM: DOMOutputSpecArray = ["strong", 0];
 const codeDOM: DOMOutputSpecArray = ["code", 0];
-const underlineDOM: DOMOutputSpecArray = ["underline", 0];
 
 // :: Object [Specs](#model.MarkSpec) for the marks in the schema.
 export const marks: { [index: string]: MarkSpec } = {
@@ -289,16 +271,6 @@ export const marks: { [index: string]: MarkSpec } = {
         toDOM() { return strongDOM; }
     },
 
-    underline: {
-        parseDOM: [
-            { tag: 'u' },
-            { style: 'text-decoration=underline' }
-        ],
-        toDOM: () => ['span', {
-            style: 'text-decoration:underline'
-        }]
-    },
-
     strikethrough: {
         parseDOM: [
             { tag: 'strike' },
@@ -340,14 +312,52 @@ export const marks: { [index: string]: MarkSpec } = {
     },
 
     highlight: {
-        parseDOM: [{ style: 'text-decoration: underline' }],
+        parseDOM: [
+            {
+                tag: "span",
+                getAttrs: (p: any) => {
+                    if (typeof (p) !== "string") {
+                        let style = getComputedStyle(p);
+                        if (style.textDecoration === "underline") return null;
+                        if (p.parentElement.outerHTML.indexOf("text-decoration: underline") !== -1 &&
+                            p.parentElement.outerHTML.indexOf("text-decoration-style: dotted") !== -1)
+                            return null;
+                    }
+                    return false;
+                }
+            },
+        ],
+        inclusive: false,
+        priority: 100,
         toDOM() {
             return ['span', {
-                style: 'text-decoration: underline; text-decoration-color: rgba(204, 206, 210, 0.92)'
+                style: 'text-decoration: underline; text-decoration-style: dotted; text-decoration-color: rgba(204, 206, 210, 0.92)'
             }];
         }
     },
 
+    underline: {
+        parseDOM: [
+            {
+                tag: "span",
+                getAttrs: (p: any) => {
+                    if (typeof (p) !== "string") {
+                        let style = getComputedStyle(p);
+                        if (style.textDecoration === "underline")
+                            return null;
+                        if (p.parentElement.outerHTML.indexOf("text-decoration-style:line") !== -1)
+                            return null;
+                    }
+                    return false;
+                }
+            }
+            // { style: "text-decoration=underline" }
+        ],
+        toDOM: () => ['span', {
+            style: 'text-decoration:underline;text-decoration-style:line'
+        }]
+    },
+
     search_highlight: {
         parseDOM: [{ style: 'background: yellow' }],
         toDOM() {
@@ -530,9 +540,6 @@ export const marks: { [index: string]: MarkSpec } = {
         }]
     },
 };
-function getFontSize(element: any) {
-    return parseFloat((getComputedStyle(element) as any).fontSize);
-}
 
 export class ImageResizeView {
     _handle: HTMLElement;
@@ -733,73 +740,52 @@ export class FootnoteView {
 }
 
 export class SummarizedView {
-    // TODO: highlight text that is summarized. to find end of region, walk along mark
     _collapsed: HTMLElement;
     _view: any;
     constructor(node: any, view: any, getPos: any) {
         this._collapsed = document.createElement("span");
-        this._collapsed.textContent = node.attrs.visibility ? "㊀" : "㊉";
-        this._collapsed.style.opacity = "0.5";
-        this._collapsed.style.position = "relative";
-        this._collapsed.style.width = "40px";
-        this._collapsed.style.height = "20px";
-        let self = this;
+        this._collapsed.className = this.className(node.attrs.visibility);
         this._view = view;
         const js = node.toJSON;
         node.toJSON = function () {
-
             return js.apply(this, arguments);
         };
-        this._collapsed.onpointerdown = function (e: any) {
-            if (node.attrs.visibility) {
-                // node.attrs.visibility = !node.attrs.visibility;
-                let y = getPos();
-                const attrs = { ...node.attrs };
-                attrs.visibility = !attrs.visibility;
-                let { from, to } = self.updateSummarizedText(y + 1, view.state.schema.marks.highlight);
-                let length = to - from;
-                let newSelection = TextSelection.create(view.state.doc, y + 1, y + 1 + length);
-                // update attrs of node
-                attrs.text = newSelection.content();
-                attrs.textslice = newSelection.content().toJSON();
-                view.dispatch(view.state.tr.setNodeMarkup(y, undefined, attrs));
-                view.dispatch(view.state.tr.setSelection(newSelection).deleteSelection(view.state, () => { }));
-                let marks = view.state.storedMarks.filter((m: any) => m.type !== view.state.schema.marks.highlight);
-                view.state.storedMarks = marks;
-                self._collapsed.textContent = "㊉";
-            } else {
-                // node.attrs.visibility = !node.attrs.visibility;
-                let y = getPos();
-                const attrs = { ...node.attrs };
-                attrs.visibility = !attrs.visibility;
-                view.dispatch(view.state.tr.setNodeMarkup(y, undefined, attrs));
-                let mark = view.state.schema.mark(view.state.schema.marks.highlight);
-                view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.doc, y + 1, y + 1)));
-                const from = view.state.selection.from;
-                let size = node.attrs.text.size;
-                view.dispatch(view.state.tr.replaceSelection(node.attrs.text).addMark(from, from + size, mark).removeStoredMark(mark));
-                self._collapsed.textContent = "㊀";
+
+        this._collapsed.onpointerdown = (e: any) => {
+            const visible = !node.attrs.visibility;
+            const attrs = { ...node.attrs, visibility: visible };
+            let textSelection = TextSelection.create(view.state.doc, getPos() + 1, getPos() + 1);
+            if (!visible) { // update summarized text and save in attrs
+                textSelection = this.updateSummarizedText(getPos() + 1);
+                attrs.text = textSelection.content();
+                attrs.textslice = attrs.text.toJSON();
             }
+            view.dispatch(view.state.tr.
+                setSelection(textSelection). // select the current summarized text (or where it will be if its collapsed)
+                replaceSelection(!visible ? new Slice(Fragment.fromArray([]), 0, 0) : node.attrs.text). // collapse/expand it
+                setNodeMarkup(getPos(), undefined, attrs)); // update the attrs 
             e.preventDefault();
             e.stopPropagation();
+            this._collapsed.className = this.className(visible);
         };
         (this as any).dom = this._collapsed;
-
-    }
-    selectNode() {
     }
+    selectNode() { }
+
+    deselectNode() { }
 
-    updateSummarizedText(start?: any, mark?: any) {
-        let $start = this._view.state.doc.resolve(start);
+    className = (visible: boolean) => "formattedTextBox-summarizer" + (visible ? "" : "-collapsed");
+
+    updateSummarizedText(start?: any) {
+        let mark = this._view.state.schema.marks.highlight.create();
         let endPos = start;
 
-        let _mark = this._view.state.schema.mark(this._view.state.schema.marks.highlight);
         let visited = new Set();
         for (let i: number = start + 1; i < this._view.state.doc.nodeSize - 1; i++) {
             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.find((m: any) => m.type === _mark.type)) {
+                    if (node.marks.find((m: any) => m.type === mark.type)) {
                         visited.add(node);
                         endPos = i + node.nodeSize - 1;
                     }
@@ -807,10 +793,7 @@ export class SummarizedView {
                 }
             });
         }
-        return { from: start, to: endPos };
-    }
-
-    deselectNode() {
+        return TextSelection.create(this._view.state.doc, start, endPos);
     }
 }
 // :: Schema
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 6d375fc1d..020c51c36 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -307,10 +307,9 @@ export class TooltipTextMenu {
                     {
                         handlers: {
                             dragComplete: action(() => {
-                                // let m = dragData.droppedDocuments;
                                 let linkDoc = dragData.linkDocument;
                                 let proto = Doc.GetProto(linkDoc);
-                                if (docView && docView.props.ContainingCollectionView) {
+                                if (proto && docView && docView.props.ContainingCollectionView) {
                                     proto.sourceContext = docView.props.ContainingCollectionView.props.Document;
                                 }
                                 linkDoc instanceof Doc && this.makeLink(Utils.prepend("/doc/" + linkDoc[Id]), ctrlKey ? "onRight" : "inTab");
@@ -322,8 +321,6 @@ export class TooltipTextMenu {
                 e.preventDefault();
             };
             this.linkEditor.appendChild(this.linkDrag);
-            // this.linkEditor.appendChild(this.linkText);
-            // this.linkEditor.appendChild(linkBtn);
             this.tooltip.appendChild(this.linkEditor);
         }
 
@@ -432,11 +429,13 @@ export class TooltipTextMenu {
     }
 
     public static insertStar(state: EditorState<any>, dispatch: any) {
-        let newNode = schema.nodes.star.create({ visibility: false, text: state.selection.content(), textslice: state.selection.content().toJSON(), textlen: state.selection.to - state.selection.from });
-        if (dispatch) {
-            //console.log(newNode.attrs.text.toString());
-            dispatch(state.tr.replaceSelectionWith(newNode));
-        }
+        if (state.selection.empty) return false;
+        let mark = state.schema.marks.highlight.create();
+        let tr = state.tr;
+        tr.addMark(state.selection.from, state.selection.to, mark);
+        let content = tr.selection.content();
+        let newNode = schema.nodes.star.create({ visibility: false, text: content, textslice: content.toJSON() });
+        dispatch && dispatch(tr.replaceSelectionWith(newNode).removeMark(tr.selection.from - 1, tr.selection.from, mark));
         return true;
     }
 
@@ -637,7 +636,6 @@ export class TooltipTextMenu {
                         Array.from(this._brushMarks).filter(m => m.type !== schema.marks.user_mark).forEach((mark: Mark) => {
                             const markType = mark.type;
                             this.changeToMarkInGroup(markType, this.view, []);
-
                         });
                     }
                 }
diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss
index 8f47402c4..0de050e79 100644
--- a/src/client/views/nodes/FormattedTextBox.scss
+++ b/src/client/views/nodes/FormattedTextBox.scss
@@ -131,6 +131,26 @@ footnote::after {
     width: 0;
 }
 
+.formattedTextBox-summarizer {
+    opacity :0.5;
+    position: relative;
+    width:40px;
+    height:20px;
+}
+.formattedTextBox-summarizer::after{
+    content: "←" ;
+}
+
+.formattedTextBox-summarizer-collapsed {
+    opacity :0.5;
+    position: relative;
+    width:40px;
+    height:20px;
+}
+.formattedTextBox-summarizer-collapsed::after {
+    content: "...";
+}
+
 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 d811273e8..006a33011 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, numberRange } from '../../../Utils';
+import { Utils, numberRange, timenow } from '../../../Utils';
 import { DocServer } from "../../DocServer";
 import { Docs, DocUtils } from '../../documents/Documents';
 import { DocumentManager } from '../../util/DocumentManager';
@@ -117,6 +117,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
 
     @undoBatch
     public setFontColor(color: string) {
+        this._editorView!.state.storedMarks
         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;
@@ -654,10 +655,8 @@ 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;
-
+        this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark).
+            addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() })));
     }
 
     componentWillUnmount() {
@@ -681,10 +680,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         }
         if (e.button === 0 && this.props.isSelected() && !e.altKey && !e.ctrlKey && !e.metaKey) {
             e.stopPropagation();
-            if (FormattedTextBox._toolTipTextMenu && FormattedTextBox._toolTipTextMenu.tooltip) {
-                //this._toolTipTextMenu.tooltip.style.opacity = "0";
-            }
         }
+        this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark).
+            addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() }));
         let ctrlKey = e.ctrlKey;
         if (e.button === 0 && ((!this.props.isSelected() && !e.ctrlKey) || (this.props.isSelected() && e.ctrlKey)) && !e.metaKey && e.target) {
             let href = (e.target as any).href;
@@ -744,13 +742,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         }
     }
 
-    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);
@@ -774,7 +765,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     onClick = (e: React.MouseEvent): void => {
         if (this.props.isSelected() && e.nativeEvent.offsetX < 40) {
             let pos = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY });
-            if (pos) {
+            if (pos && pos.pos > 0) {
                 let node = this._editorView!.state.doc.nodeAt(pos.pos);
                 let node2 = node && node.type === schema.nodes.paragraph ? this._editorView!.state.doc.nodeAt(pos.pos - 1) : undefined;
                 if (node === this._nodeClicked && node2 && (node2.type === schema.nodes.ordered_list || node2.type === schema.nodes.list_item)) {
@@ -833,24 +824,6 @@ 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;
-            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() })];
-        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;
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index a703f1cef..1462cdfad 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -308,7 +308,7 @@ export namespace Doc {
 
     // gets the document's prototype or returns the document if it is a prototype
     export function GetProto(doc: Doc) {
-        return Doc.GetT(doc, "isPrototype", "boolean", true) ? doc : (doc.proto || doc);
+        return doc && (Doc.GetT(doc, "isPrototype", "boolean", true) ? doc : (doc.proto || doc));
     }
     export function GetDataDoc(doc: Doc): Doc {
         let proto = Doc.GetProto(doc);
-- 
cgit v1.2.3-70-g09d2


From 95f773b56b8ed4fa95b1fd308c19baee1744275a Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 6 Sep 2019 09:18:11 -0400
Subject: added note types. fixed user_mark

---
 src/client/util/ProsemirrorExampleTransfer.ts      |  5 +++++
 src/client/util/RichTextSchema.tsx                 |  3 +--
 src/client/views/ContextMenu.tsx                   |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  8 ++++++++
 src/client/views/nodes/FormattedTextBox.tsx        | 22 ++++++++++------------
 5 files changed, 25 insertions(+), 15 deletions(-)

(limited to 'src/client/util/RichTextSchema.tsx')

diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts
index 9f6da7ade..55e07cfb9 100644
--- a/src/client/util/ProsemirrorExampleTransfer.ts
+++ b/src/client/util/ProsemirrorExampleTransfer.ts
@@ -14,6 +14,7 @@ export type KeyMap = { [key: string]: any };
 export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?: KeyMap): KeyMap {
     let keys: { [key: string]: any } = {}, type;
 
+    keys["ACTIVE"] = false;
     function bind(key: string, cmd: any) {
         if (mapKeys) {
             let mapped = mapKeys[key];
@@ -143,6 +144,10 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?:
     });
 
     bind("Enter", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
+        if (!keys["ACTIVE"]) {
+            dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from - 1, state.selection.from)).deleteSelection());
+            return true;
+        }
         var marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
         if (!splitListItem(schema.nodes.list_item)(state, (tx3: Transaction) => {
             // marks && tx3.ensureMarks(marks);
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 31a65dd3a..675c1d387 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -381,7 +381,6 @@ export const marks: { [index: string]: MarkSpec } = {
             modified: { default: "when?" }
         },
         group: "inline",
-        inclusive: false,
         toDOM(node: any) {
             let hideUsers = node.attrs.hide_users;
             let hidden = hideUsers.indexOf(node.attrs.userid) !== -1 || (hideUsers.length === 0 && node.attrs.userid !== Doc.CurrentUserEmail);
@@ -759,7 +758,7 @@ export class SummarizedView {
         this._collapsed.onpointerdown = (e: any) => {
             const visible = !node.attrs.visibility;
             const attrs = { ...node.attrs, visibility: visible };
-            let textSelection = TextSelection.create(view.state.doc, getPos() + 1, getPos() + 1);
+            let textSelection = TextSelection.create(view.state.doc, getPos() + 1);
             if (!visible) { // update summarized text and save in attrs
                 textSelection = this.updateSummarizedText(getPos() + 1);
                 attrs.text = textSelection.content();
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index 760736501..e27318429 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -246,7 +246,7 @@ export class ContextMenu extends React.Component {
                 this.selectedIndex--;
             }
             e.preventDefault();
-        } else if (e.key === "Enter") {
+        } else if (e.key === "Enter" || e.key === "Tab") {
             const item = this.flatItems[this.selectedIndex];
             item.event();
             this.closeMenu();
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index a7acd9e91..aadb7f6e9 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -879,9 +879,17 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         });
         layoutItems.push({ description: "Arrange contents in grid", event: this.arrangeContents, icon: "table" });
         layoutItems.push({ description: "Analyze Strokes", event: this.analyzeStrokes, icon: "paint-brush" });
+        layoutItems.push({ description: "1: Note", event: () => this.createText("yellow"), icon: "eye" });
+        layoutItems.push({ description: "2: Idea", event: () => this.createText("pink"), icon: "eye" });
+        layoutItems.push({ description: "3: Topic", event: () => this.createText("lightBlue"), icon: "eye" });
+        layoutItems.push({ description: "4: Person", event: () => this.createText("lightGreen"), icon: "eye" });
         ContextMenu.Instance.addItem({ description: "Freeform Options ...", subitems: layoutItems, icon: "eye" });
     }
 
+    createText = (color: string) => {
+        let pt = this.getTransform().transformPoint(ContextMenu.Instance.pageX, ContextMenu.Instance.pageY);
+        this.addLiveTextBox(Docs.Create.TextDocument({ x: pt[0], y: pt[1], backgroundColor: color }))
+    }
 
     private childViews = () => [
         <CollectionFreeFormBackgroundView key="backgroundView" {...this.props} {...this.getDocumentViewProps(this.props.Document)} />,
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 0d530c9a1..944cc3211 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -173,6 +173,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             if (state.selection.empty && FormattedTextBox._toolTipTextMenu && tx.storedMarks) {
                 FormattedTextBox._toolTipTextMenu.mark_key_pressed(tx.storedMarks);
             }
+            this._keymap["ACTIVE"] = true;
 
             this._applyingChange = true;
             this.extensionDoc && (this.extensionDoc.text = state.doc.textBetween(0, state.doc.content.size, "\n\n"));
@@ -307,14 +308,17 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         return numberRange(count).map(x => schema.nodes.list_item.create(undefined, schema.nodes.paragraph.create()));
     }
 
+    _keymap: any = undefined;
     @computed get config() {
+        this._keymap = buildKeymap(schema);
+        this._keymap["ACTIVE"] = this.extensionDoc.text;
         return {
             schema,
             inpRules, //these currently don't do anything, but could eventually be helpful
             plugins: this.props.isOverlay ? [
                 this.tooltipTextMenuPlugin(),
                 history(),
-                keymap(buildKeymap(schema)),
+                keymap(this._keymap),
                 keymap(baseKeymap),
                 // this.tooltipLinkingMenuPlugin(),
                 new Plugin({
@@ -325,7 +329,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 formattedTextBoxCommentPlugin
             ] : [
                     history(),
-                    keymap(buildKeymap(schema)),
+                    keymap(this._keymap),
                     keymap(baseKeymap),
                 ]
         };
@@ -626,8 +630,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             this.props.select(false);
         }
         else if (this.props.isOverlay) this._editorView!.focus();
-        this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark).
-            addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() })));
+        this._editorView!.state.storedMarks = [...(this._editorView!.state.storedMarks ? this._editorView!.state.storedMarks : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() })];
     }
 
     componentWillUnmount() {
@@ -651,8 +654,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         if (e.button === 0 && this.props.isSelected() && !e.altKey && !e.ctrlKey && !e.metaKey) {
             e.stopPropagation();
         }
-        this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark).
-            addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() }));
         let ctrlKey = e.ctrlKey;
         if (e.button === 0 && ((!this.props.isSelected() && !e.ctrlKey) || (this.props.isSelected() && e.ctrlKey)) && !e.metaKey && e.target) {
             let href = (e.target as any).href;
@@ -786,14 +787,11 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             SelectionManager.DeselectAll();
         }
         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();
+        if (e.key === "Tab" || e.key === "Enter") {
             e.preventDefault();
-            // bcz: if we use this, it fixes some problesm with bullet numbers but kills undo/redo
-            // 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);
         }
+        //this._editorView!.state.tr.addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() }));
+        this._editorView!.state.storedMarks = [...(this._editorView!.state.storedMarks ? this._editorView!.state.storedMarks : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() })];
         // 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;
-- 
cgit v1.2.3-70-g09d2


From eb05b987d7a1b2ca2e50268a0c15f2de7d44c5bd Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Fri, 6 Sep 2019 10:11:23 -0400
Subject: cleanup of prosemirror stuff.

---
 src/client/util/ProsemirrorExampleTransfer.ts | 11 ++-----
 src/client/util/RichTextSchema.tsx            |  2 --
 src/client/views/PreviewCursor.tsx            |  6 ++--
 src/client/views/nodes/FormattedTextBox.tsx   | 47 +++++++++++----------------
 4 files changed, 24 insertions(+), 42 deletions(-)

(limited to 'src/client/util/RichTextSchema.tsx')

diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts
index 55e07cfb9..e7566e3a4 100644
--- a/src/client/util/ProsemirrorExampleTransfer.ts
+++ b/src/client/util/ProsemirrorExampleTransfer.ts
@@ -116,8 +116,7 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?:
             marks && tx2.setStoredMarks([...marks]);
             dispatch(tx2);
         })) { // couldn't sink into an existing list, so wrap in a new one
-            let sxf = state.tr.setSelection(TextSelection.create(state.doc, range!.start, range!.end));
-            let newstate = state.applyTransaction(sxf);
+            let newstate = state.applyTransaction(state.tr.setSelection(TextSelection.create(state.doc, range!.start, range!.end)));
             if (!wrapInList(schema.nodes.ordered_list)(newstate.state, (tx2: Transaction) => {
                 updateBullets(tx2);
                 // when promoting to a list, assume list will format things so don't copy the stored marks.
@@ -144,16 +143,12 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?:
     });
 
     bind("Enter", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
-        if (!keys["ACTIVE"]) {
+        if (!keys["ACTIVE"]) {// hack to ignore an initial carriage return when creating a textbox from the action menu
             dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from - 1, state.selection.from)).deleteSelection());
             return true;
         }
         var marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
-        if (!splitListItem(schema.nodes.list_item)(state, (tx3: Transaction) => {
-            // marks && tx3.ensureMarks(marks);
-            // marks && tx3.setStoredMarks(marks);
-            dispatch(tx3);
-        })) {
+        if (!splitListItem(schema.nodes.list_item)(state, (tx3: Transaction) => dispatch(tx3))) {
             if (!splitBlockKeepMarks(state, (tx3: Transaction) => {
                 marks && tx3.ensureMarks(marks);
                 marks && tx3.setStoredMarks(marks);
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 675c1d387..baa95acb0 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -103,7 +103,6 @@ export const nodes: { [index: string]: NodeSpec } = {
             textslice: { default: undefined },
         },
         group: "inline",
-        inclusive: false,
         toDOM(node) {
             const attrs = { style: `width: 40px` };
             return ["span", { ...node.attrs, ...attrs }];
@@ -333,7 +332,6 @@ export const marks: { [index: string]: MarkSpec } = {
             },
         ],
         inclusive: false,
-        priority: 100,
         toDOM() {
             return ['span', {
                 style: 'text-decoration: underline; text-decoration-style: dotted; text-decoration-color: rgba(204, 206, 210, 0.92)'
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index 1329dc02c..45a8556bf 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -93,9 +93,7 @@ export class PreviewCursor extends React.Component<{}> {
 
     @action
     onKeyPress = (e: KeyboardEvent) => {
-        // Mixing events between React and Native is finicky.  In FormattedTextBox, we set the
-        // DASHFormattedTextBoxHandled flag when a text box consumes a key press so that we can ignore
-        // the keyPress here. 112-
+        // Mixing events between React and Native is finicky. 
         //if not these keys, make a textbox if preview cursor is active!
         if (e.key !== "Escape" && e.key !== "Backspace" && e.key !== "Delete" && e.key !== "CapsLock" &&
             e.key !== "Alt" && e.key !== "Shift" && e.key !== "Meta" && e.key !== "Control" &&
@@ -103,7 +101,7 @@ export class PreviewCursor extends React.Component<{}> {
             e.key !== "NumLock" &&
             (e.keyCode < 112 || e.keyCode > 123) && // F1 thru F12 keys
             !e.key.startsWith("Arrow") &&
-            !e.defaultPrevented && !(e as any).DASHFormattedTextBoxHandled) {
+            !e.defaultPrevented) {
             if (!e.ctrlKey && !e.metaKey) {//  /^[a-zA-Z0-9$*^%#@+-=_|}{[]"':;?/><.,}]$/.test(e.key)) {
                 PreviewCursor.Visible && PreviewCursor._onKeyPress && PreviewCursor._onKeyPress(e);
                 PreviewCursor.Visible = false;
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 944cc3211..8d3286d71 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -173,19 +173,23 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             if (state.selection.empty && FormattedTextBox._toolTipTextMenu && tx.storedMarks) {
                 FormattedTextBox._toolTipTextMenu.mark_key_pressed(tx.storedMarks);
             }
-            this._keymap["ACTIVE"] = true;
+            this._keymap["ACTIVE"] = true; // hack to ignore an initial carriage return when creating a textbox from the action menu
 
             this._applyingChange = true;
             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;
+            this.updateTitle();
             let title = StrCast(this.dataDoc.title);
-            if (title && title.startsWith("-") && this._editorView && !this.Document.customTitle) {
-                let str = this._editorView.state.doc.textContent;
-                let titlestr = str.substr(0, Math.min(40, str.length));
-                this.dataDoc.title = "-" + titlestr + (str.length > 40 ? "..." : "");
-            }
+        }
+    }
+
+    updateTitle = () => {
+        if (StrCast(this.dataDoc.title).startsWith("-") && this._editorView && !this.Document.customTitle) {
+            let str = this._editorView.state.doc.textContent;
+            let titlestr = str.substr(0, Math.min(40, str.length));
+            this.dataDoc.title = "-" + titlestr + (str.length > 40 ? "..." : "");
         }
     }
 
@@ -311,7 +315,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     _keymap: any = undefined;
     @computed get config() {
         this._keymap = buildKeymap(schema);
-        this._keymap["ACTIVE"] = this.extensionDoc.text;
+        this._keymap["ACTIVE"] = this.extensionDoc.text;  // hack to ignore an initial carriage return only when creating a textbox from the action menu
         return {
             schema,
             inpRules, //these currently don't do anything, but could eventually be helpful
@@ -335,11 +339,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         };
     }
 
-    @action
-    rebuildEditor() {
-        this.setupEditor(this.config, this.dataDoc, this.props.fieldKey);
-    }
-
     componentDidMount() {
         if (!this.props.isOverlay) {
             this._proxyReactionDisposer = reaction(() => this.props.isSelected(),
@@ -583,11 +582,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             const marks = [...node.marks];
             const linkIndex = marks.findIndex(mark => mark.type.name === "link");
             const link = view.state.schema.mark(view.state.schema.marks.link, { href: `http://localhost:1050/doc/${linkId}`, location: "onRight", title: title, docref: true });
-            if (linkIndex !== -1) {
-                marks.splice(linkIndex, 1, link);
-            } else {
-                marks.push(link);
-            }
+            marks.splice(linkIndex === -1 ? 0 : linkIndex, 1, link);
             return node.mark(marks);
         }
     }
@@ -630,6 +625,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             this.props.select(false);
         }
         else if (this.props.isOverlay) this._editorView!.focus();
+        // add user mark for any first character that was typed since the user mark that gets set in KeyPress won't have been called yet.
         this._editorView!.state.storedMarks = [...(this._editorView!.state.storedMarks ? this._editorView!.state.storedMarks : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() })];
     }
 
@@ -734,6 +730,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     }
 
     onClick = (e: React.MouseEvent): void => {
+        // 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.
         if (this.props.isSelected() && e.nativeEvent.offsetX < 40) {
             let pos = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY });
             if (pos && pos.pos > 0) {
@@ -790,16 +787,11 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         if (e.key === "Tab" || e.key === "Enter") {
             e.preventDefault();
         }
-        //this._editorView!.state.tr.addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() }));
-        this._editorView!.state.storedMarks = [...(this._editorView!.state.storedMarks ? this._editorView!.state.storedMarks : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() })];
-        // 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;
-        if (StrCast(this.dataDoc.title).startsWith("-") && this._editorView && !this.Document.customTitle) {
-            let str = this._editorView.state.doc.textContent;
-            let titlestr = str.substr(0, Math.min(40, str.length));
-            this.dataDoc.title = "-" + titlestr + (str.length > 40 ? "..." : "");
-        }
+
+        this._editorView!.state.tr.addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() }));
+
+        this.updateTitle();
+
         if (!this._undoTyping) {
             this._undoTyping = UndoManager.StartBatch("undoTyping");
         }
@@ -820,7 +812,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
 
 
     render() {
-        let self = this;
         let style = this.props.isOverlay ? "scroll" : "hidden";
         let rounded = StrCast(this.props.Document.borderRounding) === "100%" ? "-rounded" : "";
         let interactive: "all" | "none" = InkingControl.Instance.selectedTool || this.props.Document.isBackground ||
-- 
cgit v1.2.3-70-g09d2


From 2707e0898d535cc143272b7bf3b80f829368c097 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Fri, 6 Sep 2019 11:37:59 -0400
Subject: added metadata ui for text

---
 src/client/util/ProsemirrorExampleTransfer.ts      | 27 ++++++++++++++++++++--
 src/client/util/RichTextSchema.tsx                 |  6 +++++
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 12 +++++-----
 src/client/views/nodes/FormattedTextBox.tsx        | 11 ++++++++-
 4 files changed, 47 insertions(+), 9 deletions(-)

(limited to 'src/client/util/RichTextSchema.tsx')

diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts
index e7566e3a4..da26da4f9 100644
--- a/src/client/util/ProsemirrorExampleTransfer.ts
+++ b/src/client/util/ProsemirrorExampleTransfer.ts
@@ -150,8 +150,8 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?:
         var marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
         if (!splitListItem(schema.nodes.list_item)(state, (tx3: Transaction) => dispatch(tx3))) {
             if (!splitBlockKeepMarks(state, (tx3: Transaction) => {
-                marks && tx3.ensureMarks(marks);
-                marks && tx3.setStoredMarks(marks);
+                marks && tx3.ensureMarks(marks.filter((val: any) => val.type !== schema.marks.metadata));
+                marks && tx3.setStoredMarks(marks.filter((val: any) => val.type !== schema.marks.metadata));
                 if (!liftListItem(schema.nodes.list_item)(tx3, dispatch as ((tx: Transaction<Schema<any, any>>) => void))) {
                     dispatch(tx3);
                 }
@@ -161,6 +161,29 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?:
         }
         return true;
     });
+    bind("Space", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
+        var marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
+        let tx = state.tr;
+        marks && tx.ensureMarks(marks.filter((val: any) => val.type !== schema.marks.metadata));
+        marks && tx.setStoredMarks(marks.filter((val: any) => val.type !== schema.marks.metadata));
+        dispatch(tx);
+        return false;
+    });
+    bind(":", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
+        let range = state.selection.$from.blockRange(state.selection.$to, (node: any) => {
+            return !node.marks || !node.marks.find((m: any) => m.type === schema.marks.metadata);
+        });
+        let path = (state.doc.resolve(state.selection.from - 1) as any).path;
+        let spaceSeparator = path[path.length - 3].childCount > 1 ? 0 : -1;
+        let textsel = TextSelection.create(state.doc, range!.end - path[path.length - 3].lastChild.nodeSize + spaceSeparator, range!.end);
+        let text = range ? state.doc.textBetween(textsel.from, textsel.to) : "";
+        let whitespace = text.length - 1;
+        for (; whitespace >= 0 && text[whitespace] !== " "; whitespace--) { }
+        if (text.endsWith(":")) {
+            dispatch(state.tr.addMark(textsel.from + whitespace + 1, textsel.to, schema.marks.metadata.create() as any));
+        }
+        return false;
+    });
 
 
     return keys;
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index baa95acb0..5ee445590 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -315,6 +315,12 @@ export const marks: { [index: string]: MarkSpec } = {
         }
     },
 
+    metadata: {
+        toDOM() {
+            return ['span', { style: 'border-radius:5px; background:rgba(100, 100, 100, 0.1); box-shadow: black 1px 1px 1px' }];
+        }
+    },
+
     highlight: {
         parseDOM: [
             {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index aadb7f6e9..074bc1822 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -879,16 +879,16 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         });
         layoutItems.push({ description: "Arrange contents in grid", event: this.arrangeContents, icon: "table" });
         layoutItems.push({ description: "Analyze Strokes", event: this.analyzeStrokes, icon: "paint-brush" });
-        layoutItems.push({ description: "1: Note", event: () => this.createText("yellow"), icon: "eye" });
-        layoutItems.push({ description: "2: Idea", event: () => this.createText("pink"), icon: "eye" });
-        layoutItems.push({ description: "3: Topic", event: () => this.createText("lightBlue"), icon: "eye" });
-        layoutItems.push({ description: "4: Person", event: () => this.createText("lightGreen"), icon: "eye" });
+        layoutItems.push({ description: "1: Note", event: () => this.createText("Note", "yellow"), icon: "eye" });
+        layoutItems.push({ description: "2: Idea", event: () => this.createText("Idea", "pink"), icon: "eye" });
+        layoutItems.push({ description: "3: Topic", event: () => this.createText("Topic", "lightBlue"), icon: "eye" });
+        layoutItems.push({ description: "4: Person", event: () => this.createText("Person", "lightGreen"), icon: "eye" });
         ContextMenu.Instance.addItem({ description: "Freeform Options ...", subitems: layoutItems, icon: "eye" });
     }
 
-    createText = (color: string) => {
+    createText = (noteStyle: string, color: string) => {
         let pt = this.getTransform().transformPoint(ContextMenu.Instance.pageX, ContextMenu.Instance.pageY);
-        this.addLiveTextBox(Docs.Create.TextDocument({ x: pt[0], y: pt[1], backgroundColor: color }))
+        this.addLiveTextBox(Docs.Create.TextDocument({ title: noteStyle, x: pt[0], y: pt[1], backgroundColor: color }))
     }
 
     private childViews = () => [
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 8d3286d71..c09e88592 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -173,6 +173,16 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             if (state.selection.empty && FormattedTextBox._toolTipTextMenu && tx.storedMarks) {
                 FormattedTextBox._toolTipTextMenu.mark_key_pressed(tx.storedMarks);
             }
+
+            let metadata = this._editorView!.state.selection.$from.marks().find((m: Mark) => m.type === schema.marks.metadata);
+            if (metadata) {
+                let range = this._editorView!.state.selection.$from.blockRange(this._editorView!.state.selection.$to);
+                let text = range ? this._editorView!.state.doc.textBetween(range.start, range.end) : "";
+                let key = text.split("::")[0];
+                let value = text.split("::")[text.split("::").length - 1];
+                this.dataDoc[key] = value;
+            }
+
             this._keymap["ACTIVE"] = true; // hack to ignore an initial carriage return when creating a textbox from the action menu
 
             this._applyingChange = true;
@@ -787,7 +797,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         if (e.key === "Tab" || e.key === "Enter") {
             e.preventDefault();
         }
-
         this._editorView!.state.tr.addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() }));
 
         this.updateTitle();
-- 
cgit v1.2.3-70-g09d2


From 173863d85ee590c276bf22b1cfe91e0d00986720 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Fri, 6 Sep 2019 13:45:04 -0400
Subject: added named target docs from rich text.

---
 src/client/documents/Documents.ts                  |  4 +--
 src/client/util/ProsemirrorExampleTransfer.ts      | 16 +++++----
 src/client/util/RichTextSchema.tsx                 | 12 ++++++-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  2 +-
 .../collections/collectionFreeForm/MarqueeView.tsx |  5 ++-
 src/client/views/nodes/FormattedTextBox.tsx        | 40 ++++++++++++++++------
 6 files changed, 54 insertions(+), 25 deletions(-)

(limited to 'src/client/util/RichTextSchema.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index ef8b68c2f..fbdfa8966 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -419,8 +419,8 @@ export namespace Docs {
             return InstanceFromProto(Prototypes.get(DocumentType.KVP), document, { title: document.title + ".kvp", ...options });
         }
 
-        export function FreeformDocument(documents: Array<Doc>, options: DocumentOptions) {
-            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, viewType: CollectionViewType.Freeform });
+        export function FreeformDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
+            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, viewType: CollectionViewType.Freeform }, id);
         }
 
         export function SchemaDocument(schemaColumns: SchemaHeaderField[], documents: Array<Doc>, options: DocumentOptions) {
diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts
index da26da4f9..cc2ae7d38 100644
--- a/src/client/util/ProsemirrorExampleTransfer.ts
+++ b/src/client/util/ProsemirrorExampleTransfer.ts
@@ -142,6 +142,11 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?:
         }
     });
 
+    let splitMetadata = (marks: any, tx: Transaction) => {
+        marks && tx.ensureMarks(marks.filter((val: any) => val.type !== schema.marks.metadata && val.type !== schema.marks.metadataKey && val.type !== schema.marks.metadataVal));
+        marks && tx.setStoredMarks(marks.filter((val: any) => val.type !== schema.marks.metadata && val.type !== schema.marks.metadataKey && val.type !== schema.marks.metadataVal));
+        return tx;
+    }
     bind("Enter", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
         if (!keys["ACTIVE"]) {// hack to ignore an initial carriage return when creating a textbox from the action menu
             dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from - 1, state.selection.from)).deleteSelection());
@@ -150,8 +155,7 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?:
         var marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
         if (!splitListItem(schema.nodes.list_item)(state, (tx3: Transaction) => dispatch(tx3))) {
             if (!splitBlockKeepMarks(state, (tx3: Transaction) => {
-                marks && tx3.ensureMarks(marks.filter((val: any) => val.type !== schema.marks.metadata));
-                marks && tx3.setStoredMarks(marks.filter((val: any) => val.type !== schema.marks.metadata));
+                splitMetadata(marks, tx3);
                 if (!liftListItem(schema.nodes.list_item)(tx3, dispatch as ((tx: Transaction<Schema<any, any>>) => void))) {
                     dispatch(tx3);
                 }
@@ -163,10 +167,7 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?:
     });
     bind("Space", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
         var marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
-        let tx = state.tr;
-        marks && tx.ensureMarks(marks.filter((val: any) => val.type !== schema.marks.metadata));
-        marks && tx.setStoredMarks(marks.filter((val: any) => val.type !== schema.marks.metadata));
-        dispatch(tx);
+        dispatch(splitMetadata(marks, state.tr));
         return false;
     });
     bind(":", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
@@ -180,7 +181,8 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?:
         let whitespace = text.length - 1;
         for (; whitespace >= 0 && text[whitespace] !== " "; whitespace--) { }
         if (text.endsWith(":")) {
-            dispatch(state.tr.addMark(textsel.from + whitespace + 1, textsel.to, schema.marks.metadata.create() as any));
+            dispatch(state.tr.addMark(textsel.from + whitespace + 1, textsel.to, schema.marks.metadata.create() as any).
+                addMark(textsel.from + whitespace + 1, textsel.to - 2, schema.marks.metadataKey.create() as any));
         }
         return false;
     });
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 5ee445590..6bae63174 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -317,7 +317,17 @@ export const marks: { [index: string]: MarkSpec } = {
 
     metadata: {
         toDOM() {
-            return ['span', { style: 'border-radius:5px; background:rgba(100, 100, 100, 0.1); box-shadow: black 1px 1px 1px' }];
+            return ['span', { style: 'font-size:75%; background:rgba(100, 100, 100, 0.2); ' }];
+        }
+    },
+    metadataKey: {
+        toDOM() {
+            return ['span', { style: 'font-style:italic; ' }];
+        }
+    },
+    metadataVal: {
+        toDOM() {
+            return ['span', { style: 'background:rgba(100, 100, 100, 0.1);' }];
         }
     },
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 074bc1822..fac4d4970 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -888,7 +888,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
     createText = (noteStyle: string, color: string) => {
         let pt = this.getTransform().transformPoint(ContextMenu.Instance.pageX, ContextMenu.Instance.pageY);
-        this.addLiveTextBox(Docs.Create.TextDocument({ title: noteStyle, x: pt[0], y: pt[1], backgroundColor: color }))
+        this.addLiveTextBox(Docs.Create.TextDocument({ title: noteStyle, x: pt[0], y: pt[1], autoHeight: true, backgroundColor: color }))
     }
 
     private childViews = () => [
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 27eafd769..5015ee39a 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -93,9 +93,8 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                 }
             });
         } else if (!e.ctrlKey) {
-            let newBox = Docs.Create.TextDocument({ width: 200, height: 100, x: x, y: y, title: "-typed text-" });
-            newBox.proto!.autoHeight = true;
-            this.props.addLiveTextDocument(newBox);
+            this.props.addLiveTextDocument(
+                Docs.Create.TextDocument({ width: 200, height: 100, x: x, y: y, autoHeight: true, title: "-typed text-" }));
         }
         e.stopPropagation();
     }
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index c09e88592..1dd84a3db 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -167,6 +167,27 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
 
     dispatchTransaction = (tx: Transaction) => {
         if (this._editorView) {
+            let metadata = tx.selection.$from.marks().find((m: Mark) => m.type === schema.marks.metadata);
+            if (metadata) {
+                let range = tx.selection.$from.blockRange(tx.selection.$to);
+                let text = range ? tx.doc.textBetween(range.start, range.end) : "";
+                let textEndSelection = tx.selection.to;
+                for (; textEndSelection < range!.end && text[textEndSelection - range!.start] != " "; textEndSelection++) { }
+                text = text.substr(0, textEndSelection - range!.start);
+                text = text.split(" ")[text.split(" ").length - 1];
+                let split = text.split("::");
+                if (split.length > 1 && split[1]) {
+                    let key = split[0];
+                    let value = split[split.length - 1];
+
+                    DocServer.GetRefField(value).then(doc => this.dataDoc[key] = doc || Docs.Create.FreeformDocument([], { title: value, width: 500, height: 500 }, value));
+                    const link = this._editorView!.state.schema.marks.link.create({ href: `http://localhost:1050/doc/${value}`, location: "onRight", title: value });
+                    const mval = this._editorView!.state.schema.marks.metadataVal.create();
+                    let offset = (tx.selection.to === range!.end - 1 ? -1 : 0);
+                    tx = tx.addMark(textEndSelection - value.length + offset, textEndSelection, link).addMark(textEndSelection - value.length + offset, textEndSelection, mval);
+                    this.dataDoc[key] = value;
+                }
+            }
             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
@@ -174,15 +195,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 FormattedTextBox._toolTipTextMenu.mark_key_pressed(tx.storedMarks);
             }
 
-            let metadata = this._editorView!.state.selection.$from.marks().find((m: Mark) => m.type === schema.marks.metadata);
-            if (metadata) {
-                let range = this._editorView!.state.selection.$from.blockRange(this._editorView!.state.selection.$to);
-                let text = range ? this._editorView!.state.doc.textBetween(range.start, range.end) : "";
-                let key = text.split("::")[0];
-                let value = text.split("::")[text.split("::").length - 1];
-                this.dataDoc[key] = value;
-            }
-
             this._keymap["ACTIVE"] = true; // hack to ignore an initial carriage return when creating a textbox from the action menu
 
             this._applyingChange = true;
@@ -191,7 +203,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             this.dataDoc[this.props.fieldKey] = new RichTextField(JSON.stringify(state.toJSON()));
             this._applyingChange = false;
             this.updateTitle();
-            let title = StrCast(this.dataDoc.title);
         }
     }
 
@@ -670,6 +681,12 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             for (let parent = (e.target as any).parentNode; !href && parent; parent = parent.parentNode) {
                 href = parent.childNodes[0].href ? parent.childNodes[0].href : parent.href;
             }
+            let node = this._editorView!.state.doc.nodeAt(this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY })!.pos);
+            if (node) {
+                let link = node.marks.find(m => m.type === this._editorView!.state.schema.marks.link);
+                href = link && link.attrs.href;
+                location = link && link.attrs.location;
+            }
             if (href) {
                 if (href.indexOf(Utils.prepend("/doc/")) === 0) {
                     this._linkClicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
@@ -690,7 +707,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                                     DocumentManager.Instance.jumpToDocument(targetContext, ctrlKey, false, document => this.props.addDocTab(document, undefined, location ? location : "inTab"));
                                 } else if (jumpToDoc) {
                                     DocumentManager.Instance.jumpToDocument(jumpToDoc, ctrlKey, false, document => this.props.addDocTab(document, undefined, location ? location : "inTab"));
-
+                                } else {
+                                    DocumentManager.Instance.jumpToDocument(linkDoc, ctrlKey, false, document => this.props.addDocTab(document, undefined, location ? location : "inTab"));
                                 }
                             }
                         });
-- 
cgit v1.2.3-70-g09d2


From 0d9133bbd8417e68dfd62706369067c1e2e30c97 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Fri, 6 Sep 2019 15:59:10 -0400
Subject: turned link lines back on...  switched metadata links into real links

---
 src/client/documents/Documents.ts                         |  4 ++--
 src/client/util/RichTextSchema.tsx                        |  4 ++--
 .../collectionFreeForm/CollectionFreeFormLinksView.tsx    |  4 ++--
 src/client/views/nodes/FormattedTextBox.tsx               | 15 ++++++++++-----
 4 files changed, 16 insertions(+), 11 deletions(-)

(limited to 'src/client/util/RichTextSchema.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index fbdfa8966..ae65fde1e 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -606,13 +606,13 @@ export namespace Docs {
 
 export namespace DocUtils {
 
-    export function MakeLink(source: Doc, target: Doc, targetContext?: Doc, title: string = "", description: string = "", sourceContext?: Doc) {
+    export function MakeLink(source: Doc, target: Doc, targetContext?: Doc, title: string = "", description: string = "", sourceContext?: Doc, id?: string) {
         if (LinkManager.Instance.doesLinkExist(source, target)) return undefined;
         let sv = DocumentManager.Instance.getDocumentView(source);
         if (sv && sv.props.ContainingCollectionView && sv.props.ContainingCollectionView.props.Document === target) return;
         if (target === CurrentUserUtils.UserDocument) return undefined;
 
-        let linkDocProto = new Doc();
+        let linkDocProto = new Doc(id, true);
         UndoManager.RunInBatch(() => {
             linkDocProto.type = DocumentType.LINK;
 
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 6bae63174..8851839a2 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -327,7 +327,7 @@ export const marks: { [index: string]: MarkSpec } = {
     },
     metadataVal: {
         toDOM() {
-            return ['span', { style: 'background:rgba(100, 100, 100, 0.1);' }];
+            return ['span'];
         }
     },
 
@@ -347,7 +347,7 @@ export const marks: { [index: string]: MarkSpec } = {
                 }
             },
         ],
-        inclusive: false,
+        inclusive: true,
         toDOM() {
             return ['span', {
                 style: 'text-decoration: underline; text-decoration-style: dotted; text-decoration-color: rgba(204, 206, 210, 0.92)'
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index 2d94f1b8e..a593128be 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -120,9 +120,9 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP
     render() {
         return (
             <div className="collectionfreeformlinksview-container">
-                {/* <svg className="collectionfreeformlinksview-svgCanvas">
+                <svg className="collectionfreeformlinksview-svgCanvas">
                     {this.uniqueConnections}
-                </svg> */}
+                </svg>
                 {this.props.children}
             </div>
         );
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 1dd84a3db..5f185d8ae 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -180,8 +180,15 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                     let key = split[0];
                     let value = split[split.length - 1];
 
-                    DocServer.GetRefField(value).then(doc => this.dataDoc[key] = doc || Docs.Create.FreeformDocument([], { title: value, width: 500, height: 500 }, value));
-                    const link = this._editorView!.state.schema.marks.link.create({ href: `http://localhost:1050/doc/${value}`, location: "onRight", title: value });
+                    let id = Utils.GenerateDeterministicGuid(this.dataDoc[Id] + key);
+                    DocServer.GetRefField(value).then(doc => {
+                        DocServer.GetRefField(id).then(linkDoc => {
+                            this.dataDoc[key] = doc || Docs.Create.FreeformDocument([], { title: value, width: 500, height: 500 }, value);
+                            if (linkDoc) { (linkDoc as Doc).anchor2 = this.dataDoc[key] as Doc; }
+                            else DocUtils.MakeLink(this.dataDoc, this.dataDoc[key] as Doc, undefined, "Ref:" + value, undefined, undefined, id);
+                        })
+                    });
+                    const link = this._editorView!.state.schema.marks.link.create({ href: `http://localhost:1050/doc/${id}`, location: "onRight", title: value });
                     const mval = this._editorView!.state.schema.marks.metadataVal.create();
                     let offset = (tx.selection.to === range!.end - 1 ? -1 : 0);
                     tx = tx.addMark(textEndSelection - value.length + offset, textEndSelection, link).addMark(textEndSelection - value.length + offset, textEndSelection, mval);
@@ -203,6 +210,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             this.dataDoc[this.props.fieldKey] = new RichTextField(JSON.stringify(state.toJSON()));
             this._applyingChange = false;
             this.updateTitle();
+            this.tryUpdateHeight();
         }
     }
 
@@ -817,12 +825,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         }
         this._editorView!.state.tr.addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() }));
 
-        this.updateTitle();
-
         if (!this._undoTyping) {
             this._undoTyping = UndoManager.StartBatch("undoTyping");
         }
-        this.tryUpdateHeight();
     }
 
     @action
-- 
cgit v1.2.3-70-g09d2


From ee8f7e6ff149defe919a5ac219ed700a4688b46c Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Fri, 6 Sep 2019 17:46:02 -0400
Subject: fixed image resizing within text

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

(limited to 'src/client/util/RichTextSchema.tsx')

diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 8851839a2..153aaa791 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -598,9 +598,9 @@ export class ImageResizeView {
                 document.removeEventListener("pointermove", onpointermove);
                 document.removeEventListener("pointerup", onpointerup);
                 view.dispatch(
-                    view.state.tr.setNodeMarkup(getPos(), null,
+                    view.state.tr.setSelection(view.state.selection).setNodeMarkup(getPos(), null,
                         { src: node.attrs.src, width: self._outer.style.width })
-                        .setSelection(view.state.selection));
+                        );
             };
 
             document.addEventListener("pointermove", onpointermove);
diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss
index 43220df71..fbe9bf063 100644
--- a/src/client/views/nodes/WebBox.scss
+++ b/src/client/views/nodes/WebBox.scss
@@ -30,6 +30,7 @@
     width: 100%;
     height: 100%;
     position: absolute;
+    pointer-events: all;
 }
 
 .webBox-button {
-- 
cgit v1.2.3-70-g09d2


From 8b7bfb4f9ecc9f85e9a413f57decd74e88179a6e Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 7 Sep 2019 01:37:13 -0400
Subject: fixed image dragging within textboxes

---
 src/client/util/RichTextSchema.tsx          | 17 +++++++++--------
 src/client/views/nodes/FormattedTextBox.tsx | 11 +++++++++--
 2 files changed, 18 insertions(+), 10 deletions(-)

(limited to 'src/client/util/RichTextSchema.tsx')

diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 153aaa791..6ded78b4d 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -9,6 +9,7 @@ import { undo, redo } from "prosemirror-history";
 import { toggleMark, splitBlock, selectAll, baseKeymap } from "prosemirror-commands";
 import { Domain } from "domain";
 import { DOM } from "@fortawesome/fontawesome-svg-core";
+import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
 
 const pDOM: DOMOutputSpecArray = ["p", 0], blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"],
     preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0];
@@ -124,9 +125,10 @@ export const nodes: { [index: string]: NodeSpec } = {
         inline: true,
         attrs: {
             src: {},
-            width: { default: "100px" },
+            width: { default: 100 },
             alt: { default: null },
-            title: { default: null }
+            title: { default: null },
+            float: { default: "left" }
         },
         group: "inline",
         draggable: true,
@@ -140,11 +142,6 @@ export const nodes: { [index: string]: NodeSpec } = {
                 };
             }
         }],
-        // TODO if we don't define toDom, something weird happens: dragging the image will not move it but clone it. Why?
-        toDOM(node) {
-            const attrs = { style: `width: ${node.attrs.width}` };
-            return ["img", { ...node.attrs, ...attrs }];
-        }
     },
 
     video: {
@@ -571,6 +568,7 @@ export class ImageResizeView {
         this._outer.style.width = node.attrs.width;
         this._outer.style.display = "inline-block";
         this._outer.style.overflow = "hidden";
+        (this._outer.style as any).float = node.attrs.float;
 
         this._img.setAttribute("src", node.attrs.src);
         this._img.style.width = "100%";
@@ -592,6 +590,8 @@ export class ImageResizeView {
                 const currentX = e.pageX;
                 const diffInPx = currentX - startX;
                 self._outer.style.width = `${startWidth + diffInPx}`;
+                //Array.from(FormattedTextBox.InputBoxOverlay!.CurrentDiv.getElementsByTagName("img")).map((img: any) => img.opacity = "0.1");
+                FormattedTextBox.InputBoxOverlay!.CurrentDiv.style.opacity = "0";
             };
 
             const onpointerup = () => {
@@ -600,7 +600,8 @@ export class ImageResizeView {
                 view.dispatch(
                     view.state.tr.setSelection(view.state.selection).setNodeMarkup(getPos(), null,
                         { src: node.attrs.src, width: self._outer.style.width })
-                        );
+                );
+                FormattedTextBox.InputBoxOverlay!.CurrentDiv.style.opacity = "1";
             };
 
             document.addEventListener("pointermove", onpointermove);
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 5f185d8ae..794d3a573 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -261,7 +261,12 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             // }
         }
     }
-
+    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)));
+    }
     protected createDropTarget = (ele: HTMLDivElement) => {
         this._proseRef = ele;
         this.dropDisposer && this.dropDisposer();
@@ -276,7 +281,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             // We're dealing with an internal document drop
             let url = de.data.urlField.url.href;
             let model: NodeType = (url.includes(".mov") || url.includes(".mp4")) ? schema.nodes.video : schema.nodes.image;
-            this._editorView!.dispatch(this._editorView!.state.tr.insert(0, model.create({ src: url })));
+            let pos = this._editorView!.posAtCoords({ left: de.x, top: de.y });
+            this._editorView!.dispatch(this._editorView!.state.tr.insert(pos!.pos, model.create({ src: url })));
             e.stopPropagation();
         } else if (de.data instanceof DragManager.DocumentDragData) {
             const draggedDoc = de.data.draggedDocuments.length && de.data.draggedDocuments[0];
@@ -629,6 +635,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             }
         }
         if (this._proseRef) {
+            let self = this;
             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),
-- 
cgit v1.2.3-70-g09d2


From af34c087bf3226c09a657959cc14fc4ae291feb0 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 7 Sep 2019 13:51:11 -0400
Subject: added linking to images in text boxes

---
 src/client/util/RichTextSchema.tsx          | 51 ++++++++++++++++++++++-------
 src/client/views/MainOverlayTextBox.tsx     |  1 -
 src/client/views/nodes/FormattedTextBox.tsx |  9 +++--
 3 files changed, 46 insertions(+), 15 deletions(-)

(limited to 'src/client/util/RichTextSchema.tsx')

diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 6ded78b4d..05a37759f 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -1,15 +1,16 @@
-import { DOMOutputSpecArray, MarkSpec, Node, NodeSpec, Schema, Slice, Fragment } from "prosemirror-model";
+import { baseKeymap, toggleMark } from "prosemirror-commands";
+import { redo, undo } from "prosemirror-history";
+import { keymap } from "prosemirror-keymap";
+import { DOMOutputSpecArray, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model";
 import { bulletList, listItem, orderedList } from 'prosemirror-schema-list';
-import { TextSelection, EditorState } from "prosemirror-state";
-import { Doc } from "../../new_fields/Doc";
+import { EditorState, TextSelection } from "prosemirror-state";
 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";
-import { Domain } from "domain";
-import { DOM } from "@fortawesome/fontawesome-svg-core";
+import { Doc } from "../../new_fields/Doc";
 import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
+import { DocServer } from "../DocServer";
+import { Cast, NumCast } from "../../new_fields/Types";
+import { DocumentManager } from "./DocumentManager";
 
 const pDOM: DOMOutputSpecArray = ["p", 0], blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"],
     preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0];
@@ -128,7 +129,8 @@ export const nodes: { [index: string]: NodeSpec } = {
             width: { default: 100 },
             alt: { default: null },
             title: { default: null },
-            float: { default: "left" }
+            float: { default: "left" },
+            docid: { default: "" }
         },
         group: "inline",
         draggable: true,
@@ -560,7 +562,7 @@ export class ImageResizeView {
     _handle: HTMLElement;
     _img: HTMLElement;
     _outer: HTMLElement;
-    constructor(node: any, view: any, getPos: any) {
+    constructor(node: any, view: any, getPos: any, addDocTab: any) {
         this._handle = document.createElement("span");
         this._img = document.createElement("img");
         this._outer = document.createElement("span");
@@ -581,6 +583,33 @@ export class ImageResizeView {
         this._handle.style.bottom = "-10px";
         this._handle.style.right = "-10px";
         let self = this;
+        this._img.onpointerdown = function (e: any) {
+            if (!view.isOverlay || e.ctrlKey) {
+                e.preventDefault();
+                e.stopPropagation();
+                DocServer.GetRefField(node.attrs.docid).then(async linkDoc => {
+                    if (linkDoc instanceof Doc) {
+                        let proto = Doc.GetProto(linkDoc);
+                        let targetContext = await Cast(proto.targetContext, Doc);
+                        let jumpToDoc = await Cast(linkDoc.anchor2, Doc);
+                        if (jumpToDoc) {
+                            if (DocumentManager.Instance.getDocumentView(jumpToDoc)) {
+
+                                DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, undefined, undefined, NumCast((jumpToDoc === linkDoc.anchor2 ? linkDoc.anchor2Page : linkDoc.anchor1Page)));
+                                return;
+                            }
+                        }
+                        if (targetContext) {
+                            DocumentManager.Instance.jumpToDocument(targetContext, e.ctrlKey, false, document => addDocTab(document, undefined, location ? location : "inTab"));
+                        } else if (jumpToDoc) {
+                            DocumentManager.Instance.jumpToDocument(jumpToDoc, e.ctrlKey, false, document => addDocTab(document, undefined, location ? location : "inTab"));
+                        } else {
+                            DocumentManager.Instance.jumpToDocument(linkDoc, e.ctrlKey, false, document => addDocTab(document, undefined, location ? location : "inTab"));
+                        } e.ctrlKey
+                    }
+                });
+            }
+        }
         this._handle.onpointerdown = function (e: any) {
             e.preventDefault();
             e.stopPropagation();
@@ -599,7 +628,7 @@ export class ImageResizeView {
                 document.removeEventListener("pointerup", onpointerup);
                 view.dispatch(
                     view.state.tr.setSelection(view.state.selection).setNodeMarkup(getPos(), null,
-                        { src: node.attrs.src, width: self._outer.style.width })
+                        { ...node.attrs, width: self._outer.style.width })
                 );
                 FormattedTextBox.InputBoxOverlay!.CurrentDiv.style.opacity = "1";
             };
diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx
index 27e0d181f..c3a2cb214 100644
--- a/src/client/views/MainOverlayTextBox.tsx
+++ b/src/client/views/MainOverlayTextBox.tsx
@@ -25,7 +25,6 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
     private _textTargetDiv: HTMLDivElement | undefined;
     private _textProxyDiv: React.RefObject<HTMLDivElement>;
     private _textBottom: boolean | undefined;
-    private _textAutoHeight: boolean | undefined;
     private _setouterdiv = (outerdiv: HTMLElement | null) => { this._outerdiv = outerdiv; this.updateTooltip(); };
     private _outerdiv: HTMLElement | null = null;
     private _textBox: FormattedTextBox | undefined;
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 794d3a573..ae1393fc4 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -278,11 +278,13 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     drop = async (e: Event, de: DragManager.DropEvent) => {
         // We're dealing with a link to a document
         if (de.data instanceof DragManager.EmbedDragData && de.data.urlField) {
+            let target = de.data.embeddableSourceDoc;
             // We're dealing with an internal document drop
             let url = de.data.urlField.url.href;
             let model: NodeType = (url.includes(".mov") || url.includes(".mp4")) ? schema.nodes.video : schema.nodes.image;
             let pos = this._editorView!.posAtCoords({ left: de.x, top: de.y });
-            this._editorView!.dispatch(this._editorView!.state.tr.insert(pos!.pos, model.create({ src: url })));
+            this._editorView!.dispatch(this._editorView!.state.tr.insert(pos!.pos, model.create({ src: url, docid: target[Id] })));
+            DocUtils.MakeLink(this.dataDoc, target, undefined, "ImgRef:" + target.title, undefined, undefined, target[Id]);
             e.stopPropagation();
         } else if (de.data instanceof DragManager.DocumentDragData) {
             const draggedDoc = de.data.draggedDocuments.length && de.data.draggedDocuments[0];
@@ -641,7 +643,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 state: field && field.Data ? EditorState.fromJSON(config, JSON.parse(field.Data)) : EditorState.create(config),
                 dispatchTransaction: this.dispatchTransaction,
                 nodeViews: {
-                    image(node, view, getPos) { return new ImageResizeView(node, view, getPos); },
+                    image(node, view, getPos) { return new ImageResizeView(node, view, getPos, self.props.addDocTab); },
                     star(node, view, getPos) { return new SummarizedView(node, view, getPos); },
                     ordered_list(node, view, getPos) { return new OrderedListView(); },
                     footnote(node, view, getPos) { return new FootnoteView(node, view, getPos); }
@@ -696,7 +698,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             for (let parent = (e.target as any).parentNode; !href && parent; parent = parent.parentNode) {
                 href = parent.childNodes[0].href ? parent.childNodes[0].href : parent.href;
             }
-            let node = this._editorView!.state.doc.nodeAt(this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY })!.pos);
+            let pcords = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY });
+            let node = pcords && this._editorView!.state.doc.nodeAt(pcords.pos);
             if (node) {
                 let link = node.marks.find(m => m.type === this._editorView!.state.schema.marks.link);
                 href = link && link.attrs.href;
-- 
cgit v1.2.3-70-g09d2


From 82b0b08979f63b88b93bf1419cde659cb262e2a3 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 9 Sep 2019 18:16:12 -0400
Subject: need toDOM for images or you cant paste them

---
 src/client/util/RichTextSchema.tsx | 5 +++++
 1 file changed, 5 insertions(+)

(limited to 'src/client/util/RichTextSchema.tsx')

diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 05a37759f..e71031a87 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -144,6 +144,11 @@ export const nodes: { [index: string]: NodeSpec } = {
                 };
             }
         }],
+        // TODO if we don't define toDom, dragging the image crashes. Why?
+        toDOM(node) {
+            const attrs = { style: `width: ${node.attrs.width}` };
+            return ["img", { ...node.attrs, ...attrs }];
+        }
     },
 
     video: {
-- 
cgit v1.2.3-70-g09d2


From a1e08c0be1a1067232ea71ed3344ce44d9522bf7 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 9 Sep 2019 23:17:50 -0400
Subject: added center alignment for text using ^^

---
 src/client/util/ParagraphNodeSpec.ts          | 133 ++++++++++++++++++++++++++
 src/client/util/ProsemirrorExampleTransfer.ts |  24 ++---
 src/client/util/RichTextRules.ts              |  23 +++++
 src/client/util/RichTextSchema.tsx            |  20 ++--
 src/client/util/clamp.js                      |  15 +++
 src/client/util/convertToCSSPTValue.js        |  43 +++++++++
 src/client/util/toCSSLineSpacing.js           |  64 +++++++++++++
 7 files changed, 302 insertions(+), 20 deletions(-)
 create mode 100644 src/client/util/ParagraphNodeSpec.ts
 create mode 100644 src/client/util/clamp.js
 create mode 100644 src/client/util/convertToCSSPTValue.js
 create mode 100644 src/client/util/toCSSLineSpacing.js

(limited to 'src/client/util/RichTextSchema.tsx')

diff --git a/src/client/util/ParagraphNodeSpec.ts b/src/client/util/ParagraphNodeSpec.ts
new file mode 100644
index 000000000..3a993e1ff
--- /dev/null
+++ b/src/client/util/ParagraphNodeSpec.ts
@@ -0,0 +1,133 @@
+import clamp from './clamp';
+import convertToCSSPTValue from './convertToCSSPTValue';
+import toCSSLineSpacing from './toCSSLineSpacing';
+import { Node, DOMOutputSpec } from 'prosemirror-model';
+
+//import type { NodeSpec } from './Types';
+type NodeSpec = {
+    attrs?: { [key: string]: any },
+    content?: string,
+    draggable?: boolean,
+    group?: string,
+    inline?: boolean,
+    name?: string,
+    parseDOM?: Array<any>,
+    toDOM?: (node: any) => DOMOutputSpec,
+};
+
+// This assumes that every 36pt maps to one indent level.
+export const INDENT_MARGIN_PT_SIZE = 36;
+export const MIN_INDENT_LEVEL = 0;
+export const MAX_INDENT_LEVEL = 7;
+export const ATTRIBUTE_INDENT = 'data-indent';
+
+export const EMPTY_CSS_VALUE = new Set(['', '0%', '0pt', '0px']);
+
+const ALIGN_PATTERN = /(left|right|center|justify)/;
+
+// https://github.com/ProseMirror/prosemirror-schema-basic/blob/master/src/schema-basic.js
+// :: NodeSpec A plain paragraph textblock. Represented in the DOM
+// as a `<p>` element.
+const ParagraphNodeSpec: NodeSpec = {
+    attrs: {
+        align: { default: null },
+        color: { default: null },
+        id: { default: null },
+        indent: { default: null },
+        lineSpacing: { default: null },
+        // TODO: Add UI to let user edit / clear padding.
+        paddingBottom: { default: null },
+        // TODO: Add UI to let user edit / clear padding.
+        paddingTop: { default: null },
+    },
+    content: 'inline*',
+    group: 'block',
+    parseDOM: [{ tag: 'p', getAttrs }],
+    toDOM,
+};
+
+function getAttrs(dom: HTMLElement): Object {
+    const {
+        lineHeight,
+        textAlign,
+        marginLeft,
+        paddingTop,
+        paddingBottom,
+    } = dom.style;
+
+    let align = dom.getAttribute('align') || textAlign || '';
+    align = ALIGN_PATTERN.test(align) ? align : "";
+
+    let indent = parseInt(dom.getAttribute(ATTRIBUTE_INDENT) || "", 10);
+
+    if (!indent && marginLeft) {
+        indent = convertMarginLeftToIndentValue(marginLeft);
+    }
+
+    indent = indent || MIN_INDENT_LEVEL;
+
+    const lineSpacing = lineHeight ? toCSSLineSpacing(lineHeight) : null;
+
+    const id = dom.getAttribute('id') || '';
+    return { align, indent, lineSpacing, paddingTop, paddingBottom, id };
+}
+
+function toDOM(node: Node): DOMOutputSpec {
+    const {
+        align,
+        indent,
+        lineSpacing,
+        paddingTop,
+        paddingBottom,
+        id,
+    } = node.attrs;
+    const attrs: { [key: string]: any } | null = {};
+
+    let style = '';
+    if (align && align !== 'left') {
+        style += `text-align: ${align};`;
+    }
+
+    if (lineSpacing) {
+        const cssLineSpacing = toCSSLineSpacing(lineSpacing);
+        style +=
+            `line-height: ${cssLineSpacing};` +
+            // This creates the local css variable `--czi-content-line-height`
+            // that its children may apply.
+            `--czi-content-line-height: ${cssLineSpacing}`;
+    }
+
+    if (paddingTop && !EMPTY_CSS_VALUE.has(paddingTop)) {
+        style += `padding-top: ${paddingTop};`;
+    }
+
+    if (paddingBottom && !EMPTY_CSS_VALUE.has(paddingBottom)) {
+        style += `padding-bottom: ${paddingBottom};`;
+    }
+
+    style && (attrs.style = style);
+
+    if (indent) {
+        attrs[ATTRIBUTE_INDENT] = String(indent);
+    }
+
+    if (id) {
+        attrs.id = id;
+    }
+
+    return ['p', attrs, 0];
+}
+
+export const toParagraphDOM = toDOM;
+export const getParagraphNodeAttrs = getAttrs;
+
+export function convertMarginLeftToIndentValue(marginLeft: string): number {
+    const ptValue = convertToCSSPTValue(marginLeft);
+    return clamp(
+        MIN_INDENT_LEVEL,
+        Math.floor(ptValue / INDENT_MARGIN_PT_SIZE),
+        MAX_INDENT_LEVEL
+    );
+}
+
+export default ParagraphNodeSpec;
\ No newline at end of file
diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts
index cc2ae7d38..bac0177ad 100644
--- a/src/client/util/ProsemirrorExampleTransfer.ts
+++ b/src/client/util/ProsemirrorExampleTransfer.ts
@@ -52,18 +52,18 @@ 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;
-    });
+    // 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) => {
diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts
index 979b76988..5d1131410 100644
--- a/src/client/util/RichTextRules.ts
+++ b/src/client/util/RichTextRules.ts
@@ -1,6 +1,7 @@
 import { textblockTypeInputRule, smartQuotes, emDash, ellipsis, InputRule } from "prosemirror-inputrules";
 import { schema } from "./RichTextSchema";
 import { wrappingInputRule } from "./prosemirrorPatches";
+import { NodeSelection } from "prosemirror-state";
 
 export const inpRules = {
     rules: [
@@ -58,5 +59,27 @@ export const inpRules = {
             (state, match, start, end) => {
                 return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontSize.create({ fontSize: Number(match[1]) }))
             }),
+        new InputRule(
+            new RegExp(/^\^\^\s$/),
+            (state, match, start, end) => {
+                let node = (state.doc.resolve(start) as any).nodeAfter;
+                let sm = state.storedMarks || undefined;
+                return node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "center" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
+                    state.tr;
+            }),
+        new InputRule(
+            new RegExp(/\^f\s$/),
+            (state, match, start, end) => {
+                let newNode = schema.nodes.footnote.create({});
+                let tr = state.tr;
+                tr.deleteRange(start, end).replaceSelectionWith(newNode); // replace insertion with a footnote.
+                return 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)));
+            }),
+        // let newNode = schema.nodes.footnote.create({});
+        // if (dispatch && state.selection.from === state.selection.to) {
+        //     return true;
+        // }
     ]
 };
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index e71031a87..5081c81d8 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -11,6 +11,7 @@ import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
 import { DocServer } from "../DocServer";
 import { Cast, NumCast } from "../../new_fields/Types";
 import { DocumentManager } from "./DocumentManager";
+import ParagraphNodeSpec from "./ParagraphNodeSpec";
 
 const pDOM: DOMOutputSpecArray = ["p", 0], blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"],
     preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0];
@@ -23,6 +24,7 @@ export const nodes: { [index: string]: NodeSpec } = {
         content: "block+"
     },
 
+
     footnote: {
         group: "inline",
         content: "inline*",
@@ -37,14 +39,16 @@ export const nodes: { [index: string]: NodeSpec } = {
         parseDOM: [{ tag: "footnote" }]
     },
 
-    // :: NodeSpec A plain paragraph textblock. Represented in the DOM
-    // as a `<p>` element.
-    paragraph: {
-        content: "inline*",
-        group: "block",
-        parseDOM: [{ tag: "p" }],
-        toDOM() { return pDOM; }
-    },
+    // // :: NodeSpec A plain paragraph textblock. Represented in the DOM
+    // // as a `<p>` element.
+    // paragraph: {
+    //     content: "inline*",
+    //     group: "block",
+    //     parseDOM: [{ tag: "p" }],
+    //     toDOM() { return pDOM; }
+    // },
+
+    paragraph: ParagraphNodeSpec,
 
     // :: NodeSpec A blockquote (`<blockquote>`) wrapping one or more blocks.
     blockquote: {
diff --git a/src/client/util/clamp.js b/src/client/util/clamp.js
new file mode 100644
index 000000000..9c7fd78a4
--- /dev/null
+++ b/src/client/util/clamp.js
@@ -0,0 +1,15 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.default = clamp;
+function clamp(min, val, max) {
+  if (val < min) {
+    return min;
+  }
+  if (val > max) {
+    return max;
+  }
+  return val;
+}
\ No newline at end of file
diff --git a/src/client/util/convertToCSSPTValue.js b/src/client/util/convertToCSSPTValue.js
new file mode 100644
index 000000000..179557953
--- /dev/null
+++ b/src/client/util/convertToCSSPTValue.js
@@ -0,0 +1,43 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+    value: true
+});
+exports.PT_TO_PX_RATIO = exports.PX_TO_PT_RATIO = undefined;
+exports.default = convertToCSSPTValue;
+exports.toClosestFontPtSize = toClosestFontPtSize;
+
+// var _FontSizeCommandMenuButton = require('./ui/FontSizeCommandMenuButton');
+
+var SIZE_PATTERN = /([\d\.]+)(px|pt)/i;
+
+var PX_TO_PT_RATIO = exports.PX_TO_PT_RATIO = 0.7518796992481203; // 1 / 1.33.
+var PT_TO_PX_RATIO = exports.PT_TO_PX_RATIO = 1.33;
+
+function convertToCSSPTValue(styleValue) {
+    var matches = styleValue.match(SIZE_PATTERN);
+    if (!matches) {
+        return 0;
+    }
+    var value = parseFloat(matches[1]);
+    var unit = matches[2];
+    if (!value || !unit) {
+        return 0;
+    }
+    if (unit === 'px') {
+        value = PX_TO_PT_RATIO * value;
+    }
+    return value;
+}
+
+function toClosestFontPtSize(styleValue) {
+    var originalPTValue = convertToCSSPTValue(styleValue);
+
+    // if (_FontSizeCommandMenuButton.FONT_PT_SIZES.includes(originalPTValue)) {
+    //     return originalPTValue;
+    // }
+
+    return _FontSizeCommandMenuButton.FONT_PT_SIZES.reduce(function (prev, curr) {
+        return Math.abs(curr - originalPTValue) < Math.abs(prev - originalPTValue) ? curr : prev;
+    }, Number.NEGATIVE_INFINITY);
+}
\ No newline at end of file
diff --git a/src/client/util/toCSSLineSpacing.js b/src/client/util/toCSSLineSpacing.js
new file mode 100644
index 000000000..939d11a0e
--- /dev/null
+++ b/src/client/util/toCSSLineSpacing.js
@@ -0,0 +1,64 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+    value: true
+});
+exports.default = toCSSLineSpacing;
+
+
+// Line spacing names and their values.
+var LINE_SPACING_100 = exports.LINE_SPACING_100 = '125%';
+var LINE_SPACING_115 = exports.LINE_SPACING_115 = '138%';
+var LINE_SPACING_150 = exports.LINE_SPACING_150 = '165%';
+var LINE_SPACING_200 = exports.LINE_SPACING_200 = '232%';
+
+var SINGLE_LINE_SPACING = exports.SINGLE_LINE_SPACING = LINE_SPACING_100;
+var DOUBLE_LINE_SPACING = exports.DOUBLE_LINE_SPACING = LINE_SPACING_200;
+
+var NUMBER_VALUE_PATTERN = /^\d+(.\d+)?$/;
+
+// Normalize the css line-height vlaue to percentage-based value if applicable.
+// Also, it calibrates the incorrect line spacing value exported from Google
+// Doc.
+function toCSSLineSpacing(source) {
+    if (!source) {
+        return '';
+    }
+
+    var strValue = String(source);
+
+    // e.g. line-height: 1.5;
+    if (NUMBER_VALUE_PATTERN.test(strValue)) {
+        var numValue = parseFloat(strValue);
+        strValue = String(Math.round(numValue * 100)) + '%';
+    }
+
+    // Google Doc exports line spacing with wrong values. For instance:
+    // - Single => 100%
+    // - 1.15 => 115%
+    // - Double => 200%
+    // But the actual CSS value measured in Google Doc is like this:
+    // - Single => 125%
+    // - 1.15 => 138%
+    // - Double => 232%
+    // The following `if` block will calibrate the value if applicable.
+
+    if (strValue === '100%') {
+        return LINE_SPACING_100;
+    }
+
+    if (strValue === '115%') {
+        return LINE_SPACING_115;
+    }
+
+    if (strValue === '150%') {
+        return LINE_SPACING_150;
+    }
+
+    if (strValue === '200%') {
+        return LINE_SPACING_200;
+    }
+
+    // e.g. line-height: 15px;
+    return strValue;
+}
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From 149fa3116cdbb58f2eed144cc0bb90c1b1cd2b2a Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 9 Sep 2019 23:49:13 -0400
Subject: fixed footnote saving

---
 src/client/util/RichTextSchema.tsx | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

(limited to 'src/client/util/RichTextSchema.tsx')

diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 5081c81d8..3def4a579 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -757,10 +757,11 @@ export class FootnoteView {
         this.innerView.updateState(state);
 
         if (!tr.getMeta("fromOutside")) {
-            let outerTr = this.outerView.state.tr, offsetMap = StepMap.offset(this.getPos() + 1);
-            for (let steps of transactions) {
-                for (let step of steps) {
-                    outerTr.step(step.map(offsetMap));
+            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);
-- 
cgit v1.2.3-70-g09d2


From 5ea7e3318620865146318e0f3826b6f13aec0675 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Tue, 10 Sep 2019 13:38:16 -0400
Subject: added more support for creating stylized layouts

---
 src/Utils.ts                                       | 92 ++++++++++++++++++++++
 src/client/util/ProsemirrorExampleTransfer.ts      |  5 --
 src/client/util/RichTextRules.ts                   | 40 ++++++++++
 src/client/util/RichTextSchema.tsx                 | 19 +++++
 src/client/views/ContextMenu.tsx                   |  1 +
 src/client/views/DocumentDecorations.tsx           |  5 ++
 src/client/views/InkingControl.tsx                 | 32 +++++++-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 27 ++++---
 .../collections/collectionFreeForm/MarqueeView.tsx | 26 +++++-
 src/client/views/nodes/FormattedTextBox.tsx        | 48 ++++++++---
 .../authentication/models/current_user_utils.ts    | 20 ++---
 11 files changed, 270 insertions(+), 45 deletions(-)

(limited to 'src/client/util/RichTextSchema.tsx')

diff --git a/src/Utils.ts b/src/Utils.ts
index f805ae872..3921a49c3 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -53,6 +53,98 @@ export class Utils {
         document.body.removeChild(textArea);
     }
 
+    public static fromRGBAstr(rgba: string) {
+        let rm = rgba.match(/rgb[a]?\(([0-9]+)/);
+        let r = rm ? Number(rm[1]) : 0;
+        let gm = rgba.match(/rgb[a]?\([0-9]+,([0-9]+)/);
+        let g = gm ? Number(gm[1]) : 0;
+        let bm = rgba.match(/rgb[a]?\([0-9]+,[0-9]+,([0-9]+)/);
+        let b = bm ? Number(bm[1]) : 0;
+        let am = rgba.match(/rgba?\([0-9]+,[0-9]+,[0-9]+,([0-9]+)/);
+        let a = am ? Number(am[1]) : 0;
+        return { r: r, g: g, b: b, a: a };
+    }
+    public static toRGBAstr(col: { r: number, g: number, b: number, a?: number }) {
+        return "rgba(" + col.r + "," + col.g + "," + col.b + (col.a !== undefined ? "," + col.a : "") + ")";
+    }
+
+    public static HSLtoRGB(h: number, s: number, l: number) {
+        // Must be fractions of 1
+        // s /= 100;
+        // l /= 100;
+
+        let c = (1 - Math.abs(2 * l - 1)) * s,
+            x = c * (1 - Math.abs((h / 60) % 2 - 1)),
+            m = l - c / 2,
+            r = 0,
+            g = 0,
+            b = 0;
+        if (0 <= h && h < 60) {
+            r = c; g = x; b = 0;
+        } else if (60 <= h && h < 120) {
+            r = x; g = c; b = 0;
+        } else if (120 <= h && h < 180) {
+            r = 0; g = c; b = x;
+        } else if (180 <= h && h < 240) {
+            r = 0; g = x; b = c;
+        } else if (240 <= h && h < 300) {
+            r = x; g = 0; b = c;
+        } else if (300 <= h && h < 360) {
+            r = c; g = 0; b = x;
+        }
+        r = Math.round((r + m) * 255);
+        g = Math.round((g + m) * 255);
+        b = Math.round((b + m) * 255);
+        return { r: r, g: g, b: b };
+    }
+
+    public static RGBToHSL(r: number, g: number, b: number) {
+        // Make r, g, and b fractions of 1
+        r /= 255;
+        g /= 255;
+        b /= 255;
+
+        // Find greatest and smallest channel values
+        let cmin = Math.min(r, g, b),
+            cmax = Math.max(r, g, b),
+            delta = cmax - cmin,
+            h = 0,
+            s = 0,
+            l = 0;
+        // Calculate hue
+
+        // No difference
+        if (delta == 0)
+            h = 0;
+        // Red is max
+        else if (cmax == r)
+            h = ((g - b) / delta) % 6;
+        // Green is max
+        else if (cmax == g)
+            h = (b - r) / delta + 2;
+        // Blue is max
+        else
+            h = (r - g) / delta + 4;
+
+        h = Math.round(h * 60);
+
+        // Make negative hues positive behind 360°
+        if (h < 0)
+            h += 360; // Calculate lightness
+
+        l = (cmax + cmin) / 2;
+
+        // Calculate saturation
+        s = delta == 0 ? 0 : delta / (1 - Math.abs(2 * l - 1));
+
+        // Multiply l and s by 100
+        // s = +(s * 100).toFixed(1);
+        // l = +(l * 100).toFixed(1);
+
+        return { h: h, s: s, l: l };
+    }
+
+
     public static GetClipboardText(): string {
         var textArea = document.createElement("textarea");
         document.body.appendChild(textArea);
diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts
index bac0177ad..1d2d33800 100644
--- a/src/client/util/ProsemirrorExampleTransfer.ts
+++ b/src/client/util/ProsemirrorExampleTransfer.ts
@@ -14,7 +14,6 @@ export type KeyMap = { [key: string]: any };
 export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?: KeyMap): KeyMap {
     let keys: { [key: string]: any } = {}, type;
 
-    keys["ACTIVE"] = false;
     function bind(key: string, cmd: any) {
         if (mapKeys) {
             let mapped = mapKeys[key];
@@ -148,10 +147,6 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?:
         return tx;
     }
     bind("Enter", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
-        if (!keys["ACTIVE"]) {// hack to ignore an initial carriage return when creating a textbox from the action menu
-            dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from - 1, state.selection.from)).deleteSelection());
-            return true;
-        }
         var marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
         if (!splitListItem(schema.nodes.list_item)(state, (tx3: Transaction) => dispatch(tx3))) {
             if (!splitBlockKeepMarks(state, (tx3: Transaction) => {
diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts
index 5d1131410..00e671db9 100644
--- a/src/client/util/RichTextRules.ts
+++ b/src/client/util/RichTextRules.ts
@@ -2,6 +2,9 @@ import { textblockTypeInputRule, smartQuotes, emDash, ellipsis, InputRule } from
 import { schema } from "./RichTextSchema";
 import { wrappingInputRule } from "./prosemirrorPatches";
 import { NodeSelection } from "prosemirror-state";
+import { NumCast, Cast } from "../../new_fields/Types";
+import { Doc } from "../../new_fields/Doc";
+import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
 
 export const inpRules = {
     rules: [
@@ -57,6 +60,12 @@ export const inpRules = {
         new InputRule(
             new RegExp(/^#([0-9]+)\s$/),
             (state, match, start, end) => {
+                let size = Number(match[1]);
+                let ruleProvider = Cast(FormattedTextBox.InputBoxOverlay!.props.Document.ruleProvider, Doc) as Doc;
+                let heading = NumCast(FormattedTextBox.InputBoxOverlay!.props.Document.heading);
+                if (ruleProvider && heading) {
+                    ruleProvider["ruleSize_" + heading] = size;
+                }
                 return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontSize.create({ fontSize: Number(match[1]) }))
             }),
         new InputRule(
@@ -64,9 +73,40 @@ export const inpRules = {
             (state, match, start, end) => {
                 let node = (state.doc.resolve(start) as any).nodeAfter;
                 let sm = state.storedMarks || undefined;
+                let ruleProvider = Cast(FormattedTextBox.InputBoxOverlay!.props.Document.ruleProvider, Doc) as Doc;
+                let heading = NumCast(FormattedTextBox.InputBoxOverlay!.props.Document.heading);
+                if (ruleProvider && heading) {
+                    ruleProvider["ruleAlign_" + heading] = "center";
+                }
                 return node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "center" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
                     state.tr;
             }),
+        new InputRule(
+            new RegExp(/^\[\[\s$/),
+            (state, match, start, end) => {
+                let node = (state.doc.resolve(start) as any).nodeAfter;
+                let sm = state.storedMarks || undefined;
+                let ruleProvider = Cast(FormattedTextBox.InputBoxOverlay!.props.Document.ruleProvider, Doc) as Doc;
+                let heading = NumCast(FormattedTextBox.InputBoxOverlay!.props.Document.heading);
+                if (ruleProvider && heading) {
+                    ruleProvider["ruleAlign_" + heading] = "left";
+                }
+                return node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "left" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
+                    state.tr;
+            }),
+        new InputRule(
+            new RegExp(/^\]\]\s$/),
+            (state, match, start, end) => {
+                let node = (state.doc.resolve(start) as any).nodeAfter;
+                let sm = state.storedMarks || undefined;
+                let ruleProvider = Cast(FormattedTextBox.InputBoxOverlay!.props.Document.ruleProvider, Doc) as Doc;
+                let heading = NumCast(FormattedTextBox.InputBoxOverlay!.props.Document.heading);
+                if (ruleProvider && heading) {
+                    ruleProvider["ruleAlign_" + heading] = "right";
+                }
+                return node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "right" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
+                    state.tr;
+            }),
         new InputRule(
             new RegExp(/\^f\s$/),
             (state, match, start, end) => {
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 3def4a579..f027a4bf7 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -12,6 +12,7 @@ import { DocServer } from "../DocServer";
 import { Cast, NumCast } from "../../new_fields/Types";
 import { DocumentManager } from "./DocumentManager";
 import ParagraphNodeSpec from "./ParagraphNodeSpec";
+import { times } from "async";
 
 const pDOM: DOMOutputSpecArray = ["p", 0], blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"],
     preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0];
@@ -422,6 +423,24 @@ export const marks: { [index: string]: MarkSpec } = {
         toDOM() { return codeDOM; }
     },
 
+    // pFontFamily: {
+    //     attrs: {
+    //         style: { default: 'font-family: "Times New Roman", Times, serif;' },
+    //     },
+    //     parseDOM: [{
+    //         tag: "span", getAttrs(dom: any) {
+    //             if (getComputedStyle(dom).font === "Times New Roman") return { style: `font-family: "Times New Roman", Times, serif;` };
+    //             if (getComputedStyle(dom).font === "Arial, Helvetica") return { style: `font-family: Arial, Helvetica, sans-serif;` };
+    //             if (getComputedStyle(dom).font === "Georgia") return { style: `font-family: Georgia, serif;` };
+    //             if (getComputedStyle(dom).font === "Comic Sans") return { style: `font-family: "Comic Sans MS", cursive, sans-serif;` };
+    //             if (getComputedStyle(dom).font === "Tahoma, Geneva") return { style: `font-family: Tahoma, Geneva, sans-serif;` };
+    //         }
+    //     }],
+    //     toDOM: (node: any) => ['span', {
+    //         style: node.attrs.style
+    //     }]
+    // },
+
 
     /* FONTS */
     timesNewRoman: {
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index 890bfdfb7..68b97f2b6 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -250,6 +250,7 @@ export class ContextMenu extends React.Component {
             const item = this.flatItems[this.selectedIndex];
             item && item.event();
             this.closeMenu();
+            e.preventDefault();
         }
     }
 
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 7cdb16f52..94aab8b2f 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -621,6 +621,11 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 doc.y = (doc.y || 0) + dY * (actualdH - height);
                 let proto = doc.isTemplate ? doc : Doc.GetProto(element.props.Document); // bcz: 'doc' didn't work here...
                 let fixedAspect = e.ctrlKey || (!BoolCast(doc.ignoreAspect) && nwidth && nheight);
+                if (fixedAspect && e.ctrlKey && BoolCast(doc.ignoreAspect)) {
+                    doc.ignoreAspect = false;
+                    proto.nativeWidth = nwidth = doc.width || 0;
+                    proto.nativeHeight = nheight = doc.height || 0;
+                }
                 if (fixedAspect && (!nwidth || !nheight)) {
                     proto.nativeWidth = nwidth = doc.width || 0;
                     proto.nativeHeight = nheight = doc.height || 0;
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index eb6312e78..519792308 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -10,8 +10,10 @@ import { InkTool } from "../../new_fields/InkField";
 import { Doc } from "../../new_fields/Doc";
 import { undoBatch, UndoManager } from "../util/UndoManager";
 import { StrCast, NumCast, Cast } from "../../new_fields/Types";
-import { FormattedTextBox } from "./nodes/FormattedTextBox";
 import { MainOverlayTextBox } from "./MainOverlayTextBox";
+import { listSpec } from "../../new_fields/Schema";
+import { List } from "../../new_fields/List";
+import { Utils } from "../../Utils";
 
 library.add(faPen, faHighlighter, faEraser, faBan);
 
@@ -49,11 +51,34 @@ export class InkingControl extends React.Component {
             let oldColors = selected.map(view => {
                 let targetDoc = view.props.Document.layout instanceof Doc ? view.props.Document.layout : view.props.Document.isTemplate ? view.props.Document : Doc.GetProto(view.props.Document);
                 let oldColor = StrCast(targetDoc.backgroundColor);
-                targetDoc.backgroundColor = this._selectedColor;
+                if (view.props.ContainingCollectionView && view.props.ContainingCollectionView.props.Document.colorPalette) {
+                    let cp = Cast(view.props.ContainingCollectionView.props.Document.colorPalette, listSpec("string")) as string[];
+                    let closest = 0;
+                    let dist = 10000000;
+                    let ccol = Utils.fromRGBAstr(StrCast(targetDoc.backgroundColor));
+                    for (let i = 0; i < cp.length; i++) {
+                        let cpcol = Utils.fromRGBAstr(cp[i]);
+                        let d = Math.sqrt((ccol.r - cpcol.r) * (ccol.r - cpcol.r) + (ccol.b - cpcol.b) * (ccol.b - cpcol.b) + (ccol.g - cpcol.g) * (ccol.g - cpcol.g));
+                        if (d < dist) {
+                            dist = d;
+                            closest = i;
+                        }
+                    }
+                    cp[closest] = "rgb(" + color.rgb.r + "," + color.rgb.g + "," + color.rgb.b + ")";
+                    view.props.ContainingCollectionView.props.Document.colorPalette = new List(cp);
+                    targetDoc.backgroundColor = cp[closest];
+                } else
+                    targetDoc.backgroundColor = this._selectedColor;
                 if (view.props.Document.heading) {
                     let cv = view.props.ContainingCollectionView;
                     let ruleProvider = cv && (Cast(cv.props.Document.ruleProvider, Doc) as Doc);
-                    cv && ((ruleProvider ? ruleProvider : cv.props.Document)["ruleColor_" + NumCast(view.props.Document.heading)] = this._selectedColor);
+                    let parback = cv && StrCast(cv.props.Document.backgroundColor);
+                    cv && parback && ((ruleProvider ? ruleProvider : cv.props.Document)["ruleColor_" + NumCast(view.props.Document.heading)] = Utils.toRGBAstr(color.rgb));
+                    // if (parback && cv && parback.indexOf("rgb") !== -1) {
+                    //     let parcol = Utils.fromRGBAstr(parback);
+                    //     let hsl = Utils.RGBToHSL(parcol.r, parcol.g, parcol.b);
+                    //     cv && ((ruleProvider ? ruleProvider : cv.props.Document)["ruleColor_" + NumCast(view.props.Document.heading)] = color.hsl.s - hsl.s);
+                    // }
                 }
                 return {
                     target: targetDoc,
@@ -67,7 +92,6 @@ export class InkingControl extends React.Component {
             });
         }
     });
-
     @action
     switchWidth = (width: string): void => {
         this._selectedWidth = width;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index f7c1bedbb..c9b2c30b1 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -8,7 +8,7 @@ import { Id } from "../../../../new_fields/FieldSymbols";
 import { InkField, StrokeData } from "../../../../new_fields/InkField";
 import { createSchema, makeInterface } from "../../../../new_fields/Schema";
 import { ScriptField } from "../../../../new_fields/ScriptField";
-import { BoolCast, Cast, FieldValue, NumCast, StrCast } from "../../../../new_fields/Types";
+import { BoolCast, Cast, FieldValue, NumCast, StrCast, PromiseValue } from "../../../../new_fields/Types";
 import { emptyFunction, returnEmptyString, returnOne, Utils } from "../../../../Utils";
 import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
 import { Docs } from "../../../documents/Documents";
@@ -262,14 +262,23 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 newBox.heading = 2;
             }
         }
-        let ruleProvider = Cast(this.props.Document.ruleProvider, Doc);
-        if (!(ruleProvider instanceof Doc)) ruleProvider = this.props.Document;
-        let col = StrCast(ruleProvider["ruleColor_" + NumCast(newBox.heading)]);
-        let round = StrCast(ruleProvider["ruleRounding_" + NumCast(newBox.heading)]);
-        round && (newBox.borderRounding = round);
-        col && (newBox.backgroundColor = col);
-        newBox.ruleProvider = ruleProvider;
-        this.addDocument(newBox, false);
+        PromiseValue(Cast(this.props.Document.ruleProvider, Doc)).then(ruleProvider => {
+            if (!ruleProvider) ruleProvider = this.props.Document;
+            // saturation shift
+            // let col = NumCast(ruleProvider["ruleColor_" + NumCast(newBox.heading)]);
+            // let back = Utils.fromRGBAstr(StrCast(this.props.Document.backgroundColor));
+            // let hsl = Utils.RGBToHSL(back.r, back.g, back.b);
+            // let newcol = { h: hsl.h, s: hsl.s + col, l: hsl.l };
+            // col && (Doc.GetProto(newBox).backgroundColor = Utils.toRGBAstr(Utils.HSLtoRGB(newcol.h, newcol.s, newcol.l)));
+            // OR transparency set
+            let col = StrCast(ruleProvider["ruleColor_" + NumCast(newBox.heading)]);
+            col && (Doc.GetProto(newBox).backgroundColor = col);
+
+            let round = StrCast(ruleProvider["ruleRounding_" + NumCast(newBox.heading)]);
+            round && (newBox.borderRounding = round);
+            newBox.ruleProvider = ruleProvider;
+            this.addDocument(newBox, false);
+        });
     }
     private addDocument = (newBox: Doc, allowDuplicates: boolean) => {
         this.props.addDocument(newBox, false);
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 100e6d817..56d8127e2 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -1,11 +1,11 @@
 import * as htmlToImage from "html-to-image";
 import { action, computed, observable } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, FieldResult } from "../../../../new_fields/Doc";
+import { Doc, FieldResult, DocListCast } from "../../../../new_fields/Doc";
 import { Id } from "../../../../new_fields/FieldSymbols";
 import { InkField, StrokeData } from "../../../../new_fields/InkField";
 import { List } from "../../../../new_fields/List";
-import { Cast, NumCast } from "../../../../new_fields/Types";
+import { Cast, NumCast, StrCast } from "../../../../new_fields/Types";
 import { Utils } from "../../../../Utils";
 import { DocServer } from "../../../DocServer";
 import { Docs } from "../../../documents/Documents";
@@ -20,6 +20,8 @@ import { CollectionFreeFormView } from "./CollectionFreeFormView";
 import "./MarqueeView.scss";
 import React = require("react");
 import { SchemaHeaderField, RandomPastel } from "../../../../new_fields/SchemaHeaderField";
+import { string } from "prop-types";
+import { listSpec } from "../../../../new_fields/Schema";
 
 interface MarqueeViewProps {
     getContainerTransform: () => Transform;
@@ -272,14 +274,30 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                     return d;
                 });
             }
+            let defaultPalette = ["rgb(114,229,239)", "rgb(255,246,209)", "rgb(255,188,156)", "rgb(247,220,96)", "rgb(122,176,238)",
+                "rgb(209,150,226)", "rgb(127,235,144)", "rgb(252,188,189)", "rgb(247,175,81)",];
+            let colorPalette = Cast(this.props.container.props.Document.colorPalette, listSpec("string"));
+            if (!colorPalette) this.props.container.props.Document.colorPalette = new List<string>(defaultPalette);
+            let palette = Array.from(Cast(this.props.container.props.Document.colorPalette, listSpec("string")) as string[]);
+            let usedPaletted = new Map<string, number>();
+            [...this.props.activeDocuments(), this.props.container.props.Document].map(child => {
+                let bg = StrCast(child.backgroundColor);
+                if (palette.indexOf(bg) !== -1) {
+                    palette.splice(palette.indexOf(bg), 1);
+                    if (usedPaletted.get(bg)) usedPaletted.set(bg, usedPaletted.get(bg)! + 1);
+                    else usedPaletted.set(bg, 1);
+                }
+            });
+            let usedSequnce = Array.from(usedPaletted.keys()).sort((a, b) => usedPaletted.get(a)! < usedPaletted.get(b)! ? -1 : usedPaletted.get(a)! > usedPaletted.get(b)! ? 1 : 0);
+            let chosenColor = usedPaletted.get("white") || usedPaletted.get("rgb(255,255,255)") && usedPaletted.size === 1 ? "white" : palette.length ? palette[0] : usedSequnce[0];
             let inkData = this.ink ? this.ink.inkData : undefined;
             let newCollection = Docs.Create.FreeformDocument(selected, {
                 x: bounds.left,
                 y: bounds.top,
                 panX: 0,
                 panY: 0,
-                backgroundColor: this.props.container.isAnnotationOverlay ? undefined : "white",
-                defaultBackgroundColor: this.props.container.isAnnotationOverlay ? undefined : "white",
+                backgroundColor: this.props.container.isAnnotationOverlay ? undefined : chosenColor,
+                defaultBackgroundColor: this.props.container.isAnnotationOverlay ? undefined : chosenColor,
                 width: bounds.width,
                 height: bounds.height,
                 title: e.key === "s" || e.key === "S" ? "-summary-" : "a nested collection",
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 6a6000dc5..0ea36cdc2 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -13,7 +13,7 @@ import { Doc, DocListCast, Opt, WidthSym } from "../../../new_fields/Doc";
 import { Copy, Id } from '../../../new_fields/FieldSymbols';
 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 { BoolCast, Cast, NumCast, StrCast, DateCast, PromiseValue } from "../../../new_fields/Types";
 import { createSchema, makeInterface } from "../../../new_fields/Schema";
 import { Utils, numberRange, timenow } from '../../../Utils';
 import { DocServer } from "../../DocServer";
@@ -39,6 +39,7 @@ import { ReplaceStep } from 'prosemirror-transform';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { formattedTextBoxCommentPlugin, FormattedTextBoxComment } from './FormattedTextBoxComment';
 import { inputRules } from 'prosemirror-inputrules';
+import { select } from 'async';
 
 library.add(faEdit);
 library.add(faSmile, faTextHeight, faUpload);
@@ -203,8 +204,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 FormattedTextBox._toolTipTextMenu.mark_key_pressed(tx.storedMarks);
             }
 
-            this._keymap["ACTIVE"] = true; // hack to ignore an initial carriage return when creating a textbox from the action menu
-
             this._applyingChange = true;
             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())));
@@ -353,7 +352,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     _keymap: any = undefined;
     @computed get config() {
         this._keymap = buildKeymap(schema);
-        this._keymap["ACTIVE"] = this.extensionDoc.text;  // hack to ignore an initial carriage return only when creating a textbox from the action menu
         return {
             schema,
             plugins: this.props.isOverlay ? [
@@ -659,7 +657,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             }
         }
 
-        if (this.props.Document[Id] === FormattedTextBox.SelectOnLoad) {
+        let selectOnLoad = this.props.Document[Id] === FormattedTextBox.SelectOnLoad;
+        if (selectOnLoad) {
             FormattedTextBox.SelectOnLoad = "";
             this.props.select(false);
         }
@@ -667,15 +666,38 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         // add user mark for any first character that was typed since the user mark that gets set in KeyPress won't have been called yet.
         this._editorView!.state.storedMarks = [...(this._editorView!.state.storedMarks ? this._editorView!.state.storedMarks : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() })];
         let heading = this.props.Document.heading;
-        if (heading) {
-            let ruleProvider = Cast(this.props.Document.ruleProvider, Doc);
-            if (ruleProvider instanceof Doc) {
-                let font = StrCast(ruleProvider["ruleFont_" + heading]);
-                let size = NumCast(ruleProvider["ruleSize_" + heading]);
-                size && (this._editorView!.state.storedMarks = [...this._editorView!.state.storedMarks, schema.marks.pFontSize.create({ fontSize: size })]);
-                font && (this._editorView!.state.storedMarks = [...this._editorView!.state.storedMarks, font === "Arial" ? schema.marks.arial.create() : schema.marks.comicSans.create()]);
-            }
+        if (heading && selectOnLoad) {
+            PromiseValue(Cast(this.props.Document.ruleProvider, Doc)).then(ruleProvider => {
+                if (ruleProvider) {
+                    let align = StrCast(ruleProvider["ruleAlign_" + heading]);
+                    let font = StrCast(ruleProvider["ruleFont_" + heading]);
+                    let size = NumCast(ruleProvider["ruleSize_" + heading]);
+                    if (align) {
+                        let tr = this._editorView!.state.tr;
+                        tr = tr.setSelection(new TextSelection(tr.doc.resolve(0), tr.doc.resolve(2))).
+                            replaceSelectionWith(this._editorView!.state.schema.nodes.paragraph.create({ align: align }), true).
+                            setSelection(new TextSelection(tr.doc.resolve(0), tr.doc.resolve(0)));
+                        this._editorView!.dispatch(tr);
+                    }
+                    let sm = [...(this._editorView!.state.storedMarks ? this._editorView!.state.storedMarks : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() })];
+                    size && (sm = [...sm, schema.marks.pFontSize.create({ fontSize: size })]);
+                    font && (sm = [...sm, this.getFont(font)]);
+                    this._editorView!.dispatch(this._editorView!.state.tr.setStoredMarks(sm));
+                }
+            });
+        }
+    }
+    getFont(font: string) {
+        switch (font) {
+            case "Arial": return schema.marks.arial.create();
+            case "Times New Roman": return schema.marks.timesNewRoman.create();
+            case "Georgia": return schema.marks.georgia.create();
+            case "Comic Sans MS": return schema.marks.comicSans.create();
+            case "Tahoma": return schema.marks.tahoma.create();
+            case "Impact": return schema.marks.impact.create();
+            case "ACrimson Textrial": return schema.marks.crimson.create();
         }
+        return schema.marks.arial.create();
     }
 
     componentWillUnmount() {
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 9866e22eb..9d35d36d3 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -115,17 +115,17 @@ export class CurrentUserUtils {
                 throw new Error("There should be a user id! Why does Dash think there isn't one?");
             }
         });
-        try {
-            const getEnvironment = await fetch("/assets/env.json", { redirect: "follow", method: "GET", credentials: "include" });
-            NorthstarSettings.Instance.UpdateEnvironment(await getEnvironment.json());
-            await Gateway.Instance.ClearCatalog();
-            const extraSchemas = Cast(CurrentUserUtils.UserDocument.DBSchemas, listSpec("string"), []);
-            let extras = await Promise.all(extraSchemas.map(sc => Gateway.Instance.GetSchema("", sc)));
-            let catprom = CurrentUserUtils.SetNorthstarCatalog(await Gateway.Instance.GetCatalog(), extras);
-            // if (catprom) await Promise.all(catprom);
-        } catch (e) {
+        // try {
+        //     const getEnvironment = await fetch("/assets/env.json", { redirect: "follow", method: "GET", credentials: "include" });
+        //     NorthstarSettings.Instance.UpdateEnvironment(await getEnvironment.json());
+        //     await Gateway.Instance.ClearCatalog();
+        //     const extraSchemas = Cast(CurrentUserUtils.UserDocument.DBSchemas, listSpec("string"), []);
+        //     let extras = await Promise.all(extraSchemas.map(sc => Gateway.Instance.GetSchema("", sc)));
+        //     let catprom = CurrentUserUtils.SetNorthstarCatalog(await Gateway.Instance.GetCatalog(), extras);
+        //     // if (catprom) await Promise.all(catprom);
+        // } catch (e) {
 
-        }
+        // }
     }
 
     /* Northstar catalog ... really just for testing so this should eventually go away */
-- 
cgit v1.2.3-70-g09d2

