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

---
 .../collections/collectionFreeForm/CollectionFreeFormView.tsx     | 8 ++++++++
 1 file changed, 8 insertions(+)

(limited to 'src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx')

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)} />,
-- 
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/views/collections/collectionFreeForm/CollectionFreeFormView.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/views/collections/collectionFreeForm/CollectionFreeFormView.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 4d4bebb5d2e7555fac4224a497b815c40365fe91 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 6 Sep 2019 21:10:31 -0400
Subject: fixed clustering to be persistent

---
 src/client/views/ContextMenu.tsx                   |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 41 +++++++++++++++++++---
 src/new_fields/Doc.ts                              |  9 +++--
 3 files changed, 44 insertions(+), 8 deletions(-)

(limited to 'src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx')

diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index e27318429..890bfdfb7 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -248,7 +248,7 @@ export class ContextMenu extends React.Component {
             e.preventDefault();
         } else if (e.key === "Enter" || e.key === "Tab") {
             const item = this.flatItems[this.selectedIndex];
-            item.event();
+            item && item.event();
             this.closeMenu();
         }
     }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index fac4d4970..4f6055260 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -256,7 +256,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     private addDocument = (newBox: Doc, allowDuplicates: boolean) => {
         this.props.addDocument(newBox, false);
         this.bringToFront(newBox);
-        this.updateClusters();
+        this.updateCluster(newBox);
         return true;
     }
     private selectDocuments = (docs: Doc[]) => {
@@ -324,7 +324,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                         this.bringToFront(d);
                     });
 
-                    this.updateClusters();
+                    de.data.droppedDocuments.length == 1 && this.updateCluster(de.data.droppedDocuments[0]);
                 }
             }
             else if (de.data instanceof DragManager.AnnotationDragData) {
@@ -384,6 +384,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         return false;
     }
     @observable sets: (Doc[])[] = [];
+
+    @undoBatch
     @action
     updateClusters() {
         this.sets.length = 0;
@@ -412,12 +414,42 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         this.sets.map((set, i) => set.map(member => member.cluster = i));
     }
 
+    @undoBatch
+    @action
+    updateCluster(doc: Doc) {
+        if (this.props.Document.useClusters) {
+            this.sets.map(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1));
+            let preferredInd = NumCast(doc.cluster);
+            doc.cluster = -1;
+            this.sets.map((set, i) => set.map(member => {
+                if (doc.cluster === -1 && Doc.IndexOf(member, this.childDocs) !== -1 && this.boundsOverlap(doc, member)) {
+                    doc.cluster = i;
+                }
+            }));
+            if (doc.cluster === -1 && preferredInd !== -1 && (!this.sets[preferredInd] || !this.sets[preferredInd].filter(member => Doc.IndexOf(member, this.childDocs) !== -1).length)) {
+                doc.cluster = preferredInd;
+            }
+            this.sets.map((set, i) => {
+                if (doc.cluster === -1 && !set.filter(member => Doc.IndexOf(member, this.childDocs) !== -1).length) {
+                    doc.cluster = i;
+                }
+            });
+            if (doc.cluster === -1) {
+                doc.cluster = this.sets.length;
+                this.sets.push([doc]);
+            } else {
+                for (let i = this.sets.length; i <= doc.cluster; i++) !this.sets[i] && this.sets.push([]);
+                this.sets[doc.cluster].push(doc);
+            }
+        }
+    }
+
     getClusterColor = (doc: Doc) => {
         if (this.props.Document.useClusters) {
             let cluster = NumCast(doc.cluster);
             if (this.sets.length <= cluster) {
-                setTimeout(() => this.updateClusters(), 0);
-                return;
+                setTimeout(() => this.updateCluster(doc), 0);//  this.updateClusters(), 0);
+                return "";
             }
             let set = this.sets.length > cluster ? this.sets[cluster] : undefined;
             let colors = ["#da42429e", "#31ea318c", "#8c4000", "#4a7ae2c4", "#d809ff", "#ff7601", "#1dffff", "yellow", "#1b8231f2", "#000000ad"];
@@ -869,6 +901,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 Docs.Prototypes.get(DocumentType.TEXT).defaultBackgroundColor = "#f1efeb"; // backward compatibility with databases that didn't have a default background color on prototypes
                 Docs.Prototypes.get(DocumentType.COL).defaultBackgroundColor = "white";
                 this.props.Document.useClusters = !this.props.Document.useClusters;
+                this.updateClusters();
             },
             icon: !this.props.Document.useClusters ? "braille" : "braille"
         });
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index be4bf232b..e3d7cc9ed 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -300,9 +300,9 @@ export namespace Doc {
     export function AreProtosEqual(doc?: Doc, other?: Doc) {
         if (!doc || !other) return false;
         let r = (doc === other);
-        let r2 = (doc.proto === other);
-        let r3 = (other.proto === doc);
-        let r4 = (doc.proto === other.proto && other.proto !== undefined);
+        let r2 = (Doc.GetProto(doc) === other);
+        let r3 = (Doc.GetProto(other) === doc);
+        let r4 = (Doc.GetProto(doc) === Doc.GetProto(other) && Doc.GetProto(other) !== undefined);
         return r || r2 || r3 || r4;
     }
 
@@ -327,6 +327,9 @@ export namespace Doc {
         return Array.from(results);
     }
 
+    export function IndexOf(toFind: Doc, list: Doc[]) {
+        return list.findIndex(doc => doc === toFind || Doc.AreProtosEqual(doc, toFind))
+    }
     export function AddDocToList(target: Doc, key: string, doc: Doc, relativeTo?: Doc, before?: boolean, first?: boolean, allowDuplicates?: boolean, reversed?: boolean) {
         if (target[key] === undefined) {
             Doc.GetProto(target)[key] = new List<Doc>();
-- 
cgit v1.2.3-70-g09d2


From 0fea8592b5bd790334d0557c3ef30eb03973c601 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 6 Sep 2019 23:19:21 -0400
Subject: changed sub-menu pop up location.  added rotation jitter

---
 src/client/views/ContextMenuItem.tsx                       |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx          | 14 ++++++++++----
 src/client/views/nodes/CollectionFreeFormDocumentView.tsx  |  4 +++-
 3 files changed, 14 insertions(+), 6 deletions(-)

(limited to 'src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx')

diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx
index 90f7be33f..1a0839060 100644
--- a/src/client/views/ContextMenuItem.tsx
+++ b/src/client/views/ContextMenuItem.tsx
@@ -89,7 +89,7 @@ export class ContextMenuItem extends React.Component<ContextMenuProps & { select
             );
         } else if ("subitems" in this.props) {
             let submenu = !this.overItem ? (null) :
-                <div className="contextMenu-subMenu-cont" style={{ marginLeft: "100.5%", left: "0px" }}>
+                <div className="contextMenu-subMenu-cont" style={{ marginLeft: "10.5%", left: "0px" }}>
                     {this._items.map(prop => <ContextMenuItem {...prop} key={prop.description} closeMenu={this.props.closeMenu} />)}
                 </div>;
             return (
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 4f6055260..2df2a3464 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -152,6 +152,7 @@ export namespace PivotView {
                         y={pos.y}
                         width={pos.width}
                         height={pos.height}
+                        jitterRotation={NumCast(target.props.Document.jitterRotation)}
                         {...target.getChildDocumentViewProps(doc)}
                     />,
                     bounds: {
@@ -797,6 +798,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                     if (pair.layout && !(pair.data instanceof Promise)) {
                         prev.push({
                             ele: <CollectionFreeFormDocumentView key={doc[Id]}
+                                jitterRotation={NumCast(this.props.Document.jitterRotation)}
                                 x={script ? pos.x : undefined} y={script ? pos.y : undefined}
                                 width={script ? pos.width : undefined} height={script ? pos.height : undefined} {...this.getChildDocumentViewProps(pair.layout, pair.data)} />,
                             bounds: { x: pos.x || 0, y: pos.y || 0, z: pos.z, width: NumCast(pos.width), height: NumCast(pos.height) }
@@ -912,10 +914,14 @@ 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("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" });
+        layoutItems.push({ description: "Jitter Rotation", event: action(() => this.props.Document.jitterRotation = 10), icon: "paint-brush" });
+
+        let noteItems: ContextMenuProps[] = [];
+        noteItems.push({ description: "1: Note", event: () => this.createText("Note", "yellow"), icon: "eye" });
+        noteItems.push({ description: "2: Idea", event: () => this.createText("Idea", "pink"), icon: "eye" });
+        noteItems.push({ description: "3: Topic", event: () => this.createText("Topic", "lightBlue"), icon: "eye" });
+        noteItems.push({ description: "4: Person", event: () => this.createText("Person", "lightGreen"), icon: "eye" });
+        layoutItems.push({ description: "Add Note ...", subitems: noteItems, icon: "eye" })
         ContextMenu.Instance.addItem({ description: "Freeform Options ...", subitems: layoutItems, icon: "eye" });
     }
 
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index c9c394960..f07584b4f 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -8,12 +8,14 @@ import { DocumentView, DocumentViewProps, positionSchema } from "./DocumentView"
 import "./DocumentView.scss";
 import React = require("react");
 import { Doc } from "../../../new_fields/Doc";
+import { random } from "animejs";
 
 export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
     x?: number;
     y?: number;
     width?: number;
     height?: number;
+    jitterRotation: number;
 }
 
 const schema = createSchema({
@@ -27,7 +29,7 @@ const FreeformDocument = makeInterface(schema, positionSchema);
 
 @observer
 export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps, FreeformDocument>(FreeformDocument) {
-    @computed get transform() { return `scale(${this.props.ContentScaling()}) translate(${this.X}px, ${this.Y}px) scale(${this.zoom}) `; }
+    @computed get transform() { return `scale(${this.props.ContentScaling()}) translate(${this.X}px, ${this.Y}px) rotate(${random(-1, 1) * this.props.jitterRotation}deg) scale(${this.zoom}) `; }
     @computed get X() { return this.props.x !== undefined ? this.props.x : this.Document.x || 0; }
     @computed get Y() { return this.props.y !== undefined ? this.props.y : this.Document.y || 0; }
     @computed get width(): number { return BoolCast(this.props.Document.willMaximize) ? 0 : this.props.width !== undefined ? this.props.width : this.Document.width || 0; }
-- 
cgit v1.2.3-70-g09d2


From 3865ccd688015d92c8c551bf78ee2a9b6ece5500 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Mon, 9 Sep 2019 15:40:49 -0400
Subject: added local overlay for free form views.   added start of minimap.

---
 src/client/views/OverlayView.tsx                   |  4 +++-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 16 ++++++++++++---
 .../views/nodes/CollectionFreeFormDocumentView.tsx | 23 ++++++++++++++++++----
 src/client/views/nodes/DocumentView.tsx            |  2 --
 src/server/index.ts                                |  2 +-
 5 files changed, 36 insertions(+), 11 deletions(-)

(limited to 'src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx')

diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index fe06e4440..da4b71e5c 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -197,7 +197,9 @@ export class OverlayView extends React.Component {
     render() {
         return (
             <div className="overlayView" id="overlayView">
-                {this._elements}
+                <div>
+                    {this._elements}
+                </div>
                 {this.overlayDocs}
             </div>
         );
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 2df2a3464..2ec2b0671 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -3,7 +3,7 @@ import { faEye } from "@fortawesome/free-regular-svg-icons";
 import { faBraille, faChalkboard, faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faPaintBrush, faTable, faUpload } from "@fortawesome/free-solid-svg-icons";
 import { action, computed, IReactionDisposer, observable, reaction, trace } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, DocListCastAsync, Field, FieldResult, HeightSym, Opt, WidthSym } from "../../../../new_fields/Doc";
+import { Doc, DocListCastAsync, Field, FieldResult, HeightSym, Opt, WidthSym, DocListCast } from "../../../../new_fields/Doc";
 import { Id } from "../../../../new_fields/FieldSymbols";
 import { InkField, StrokeData } from "../../../../new_fields/InkField";
 import { createSchema, makeInterface } from "../../../../new_fields/Schema";
@@ -225,8 +225,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         return bounds;
     }
 
+    @computed get actualContentBounds() {
+        return this.fitToBox && !this.isAnnotationOverlay ? this.ComputeContentBounds(this.elements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!)) : undefined;
+    }
+
     @computed get contentBounds() {
-        let bounds = this.fitToBox && !this.isAnnotationOverlay ? this.ComputeContentBounds(this.elements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!)) : undefined;
+        let bounds = this.actualContentBounds;
         let res = {
             panX: bounds ? (bounds.x + bounds.r) / 2 : this.Document.panX || 0,
             panY: bounds ? (bounds.y + bounds.b) / 2 : this.Document.panY || 0,
@@ -775,7 +779,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         const initScript = this.Document.arrangeInit;
         const script = this.Document.arrangeScript;
         let state: any = undefined;
-        const docs = this.childDocs;
+        let docs = this.childDocs;
+        let overlayDocs = DocListCast(this.props.Document.localOverlays);
+        overlayDocs && docs.push(...overlayDocs);
         let elements: ViewDefResult[] = [];
         if (initScript) {
             const initResult = initScript.script.run({ docs, collection: this.Document });
@@ -967,6 +973,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     }
 
     render() {
+        this.props.Document.fitX = this.actualContentBounds && this.actualContentBounds.x;
+        this.props.Document.fitY = this.actualContentBounds && this.actualContentBounds.y;
+        this.props.Document.fitW = this.actualContentBounds && (this.actualContentBounds.r - this.actualContentBounds.x);
+        this.props.Document.fitH = this.actualContentBounds && (this.actualContentBounds.b - this.actualContentBounds.y);
         const easing = () => this.props.Document.panTransformType === "Ease";
         Doc.UpdateDocumentExtensionForField(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey);
         return (
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index f07584b4f..c059ff50d 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -1,7 +1,7 @@
 import { computed } from "mobx";
 import { observer } from "mobx-react";
 import { createSchema, makeInterface } from "../../../new_fields/Schema";
-import { BoolCast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types";
+import { BoolCast, FieldValue, NumCast, StrCast, Cast } from "../../../new_fields/Types";
 import { Transform } from "../../util/Transform";
 import { DocComponent } from "../DocComponent";
 import { DocumentView, DocumentViewProps, positionSchema } from "./DocumentView";
@@ -77,6 +77,21 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     clusterColorFunc = (doc: Doc) => this.clusterColor;
 
     render() {
+        let txf = this.transform;
+        let w = this.width;
+        let h = this.height;
+        let renderScript = this.Document.renderScript;
+        if (renderScript) {
+            let someView = Cast(this.Document.someView, Doc);
+            let minimap = Cast(this.Document.minimap, Doc);
+            if (someView instanceof Doc && minimap instanceof Doc) {
+                let x = (NumCast(someView.panX) - NumCast(someView.width) / 2 / NumCast(someView.scale) - (NumCast(minimap.fitX) - NumCast(minimap.fitW) / 2)) / NumCast(minimap.fitW) * NumCast(minimap.width) - NumCast(minimap.width) / 2;
+                let y = (NumCast(someView.panY) - NumCast(someView.height) / 2 / NumCast(someView.scale) - (NumCast(minimap.fitY) - NumCast(minimap.fitH) / 2)) / NumCast(minimap.fitH) * NumCast(minimap.height) - NumCast(minimap.height) / 2;
+                w = NumCast(someView.width) / NumCast(someView.scale) / NumCast(minimap.fitW) * NumCast(minimap.width);
+                h = NumCast(someView.height) / NumCast(someView.scale) / NumCast(minimap.fitH) * NumCast(minimap.height);
+                txf = `translate(${x}px,${y}px)`;
+            }
+        }
         const hasPosition = this.props.x !== undefined || this.props.y !== undefined;
         return (
             <div className="collectionFreeFormDocumentView-container"
@@ -89,10 +104,10 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                             this.props.Document.isBackground ? `0px 0px 50px 50px ${this.clusterColor}` :
                                 `${this.clusterColor} ${StrCast(this.props.Document.boxShadow, `0vw 0vw ${50 / this.props.ContentScaling()}px`)}`) : undefined,
                     borderRadius: this.borderRounding(),
-                    transform: this.transform,
+                    transform: txf,
                     transition: hasPosition ? "transform 1s" : StrCast(this.props.Document.transition),
-                    width: this.width,
-                    height: this.height,
+                    width: w,
+                    height: h,
                     zIndex: this.Document.zIndex || 0,
                 }} >
                 <DocumentView {...this.props}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index b60730a6b..1763c664e 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -758,9 +758,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             });
         }
         let showTextTitle = showTitle && StrCast(this.layoutDoc.layout).startsWith("<FormattedTextBox") ? showTitle : undefined;
-        let brushDegree = Doc.IsBrushedDegree(this.props.Document);
         let fullDegree = Doc.isBrushedHighlightedDegree(this.props.Document);
-        // console.log(fullDegree)
         let borderRounding = StrCast(Doc.GetProto(this.props.Document).borderRounding);
         let localScale = this.props.ScreenToLocalTransform().Scale * fullDegree;
         return (
diff --git a/src/server/index.ts b/src/server/index.ts
index d2c6584cc..6472ffb93 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -447,7 +447,7 @@ function LoadPage(file: string, pageNumber: number, res: Response) {
             console.log(pageNumber);
             pdf.getPage(pageNumber).then((page: Pdfjs.PDFPageProxy) => {
                 console.log("reading " + page);
-                let viewport = page.getViewport({scale: 1});
+                let viewport = page.getViewport(1 as any);
                 let canvasAndContext = factory.create(viewport.width, viewport.height);
                 let renderContext = {
                     canvasContext: canvasAndContext.context,
-- 
cgit v1.2.3-70-g09d2


From 2d21fff15510d6eeb8975cc2459f69ca28d86d1d Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Mon, 9 Sep 2019 17:26:27 -0400
Subject: added stand-in spatial parser.

---
 src/client/util/TooltipTextMenu.tsx                         | 11 +++++++++++
 src/client/views/DocumentDecorations.tsx                    |  6 ++++++
 src/client/views/InkingControl.tsx                          |  7 ++++++-
 .../collectionFreeForm/CollectionFreeFormView.tsx           | 13 +++++++++++++
 .../views/collections/collectionFreeForm/MarqueeView.tsx    |  1 +
 src/client/views/nodes/FormattedTextBox.tsx                 | 10 ++++++++++
 6 files changed, 47 insertions(+), 1 deletion(-)

(limited to 'src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx')

diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 020c51c36..c376b6f86 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -18,6 +18,7 @@ import { DragManager } from "./DragManager";
 import { LinkManager } from "./LinkManager";
 import { schema } from "./RichTextSchema";
 import "./TooltipTextMenu.scss";
+import { Cast, NumCast } from '../../new_fields/Types';
 const { toggleMark, setBlockType } = require("prosemirror-commands");
 const { openPrompt, TextField } = require("./ProsemirrorCopy/prompt.js");
 
@@ -495,10 +496,20 @@ export class TooltipTextMenu {
             if (markType.name[0] === 'p') {
                 let size = this.fontSizeToNum.get(markType);
                 if (size) { this.updateFontSizeDropdown(String(size) + " pt"); }
+                let ruleProvider = Cast(this.editorProps.Document.ruleProvider, Doc) as Doc;
+                let heading = NumCast(this.editorProps.Document.heading);
+                if (ruleProvider && heading) {
+                    ruleProvider["ruleSize_" + heading] = size;
+                }
             }
             else {
                 let fontName = this.fontStylesToName.get(markType);
                 if (fontName) { this.updateFontStyleDropdown(fontName); }
+                let ruleProvider = Cast(this.editorProps.Document.ruleProvider, Doc) as Doc;
+                let heading = NumCast(this.editorProps.Document.heading);
+                if (ruleProvider && heading) {
+                    ruleProvider["ruleFont_" + heading] = fontName;
+                }
             }
             //actually apply font
             return toggleMark(markType)(view.state, view.dispatch, view);
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 700a4b49d..7cdb16f52 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -428,6 +428,12 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         let dist = Math.sqrt((e.clientX - this._radiusDown[0]) * (e.clientX - this._radiusDown[0]) + (e.clientY - this._radiusDown[1]) * (e.clientY - this._radiusDown[1]));
         SelectionManager.SelectedDocuments().map(dv => dv.props.Document.layout instanceof Doc ? dv.props.Document.layout : dv.props.Document.isTemplate ? dv.props.Document : Doc.GetProto(dv.props.Document)).
             map(d => d.borderRounding = `${Math.min(100, dist)}%`);
+        SelectionManager.SelectedDocuments().map(dv => {
+            let cv = dv.props.ContainingCollectionView;
+            let ruleProvider = cv && (Cast(cv.props.Document.ruleProvider, Doc) as Doc);
+            let heading = NumCast(dv.props.Document.heading);
+            cv && ((ruleProvider ? ruleProvider : cv.props.Document)["ruleRounding_" + heading] = StrCast(dv.props.Document.borderRounding));
+        })
         e.stopPropagation();
         e.preventDefault();
     }
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index 3f40642b5..eb6312e78 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -9,7 +9,7 @@ import { SelectionManager } from "../util/SelectionManager";
 import { InkTool } from "../../new_fields/InkField";
 import { Doc } from "../../new_fields/Doc";
 import { undoBatch, UndoManager } from "../util/UndoManager";
-import { StrCast } from "../../new_fields/Types";
+import { StrCast, NumCast, Cast } from "../../new_fields/Types";
 import { FormattedTextBox } from "./nodes/FormattedTextBox";
 import { MainOverlayTextBox } from "./MainOverlayTextBox";
 
@@ -50,6 +50,11 @@ export class InkingControl extends React.Component {
                 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.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);
+                }
                 return {
                     target: targetDoc,
                     previous: oldColor
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 2ec2b0671..f7c1bedbb 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -256,6 +256,19 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     private getLocalTransform = (): Transform => Transform.Identity().scale(1 / this.zoomScaling()).translate(this.panX(), this.panY());
     private addLiveTextBox = (newBox: Doc) => {
         FormattedTextBox.SelectOnLoad = newBox[Id];// track the new text box so we can give it a prop that tells it to focus itself when it's displayed
+        newBox.heading = 1;
+        for (let i = 0; i < this.childDocs.length; i++) {
+            if (this.childDocs[i].heading == 1) {
+                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);
     }
     private addDocument = (newBox: Doc, allowDuplicates: boolean) => {
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 5015ee39a..100e6d817 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -329,6 +329,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                 this.props.addLiveTextDocument(summary);
             }
             else {
+                newCollection.ruleProvider = this.props.container.props.Document;
                 this.props.addDocument(newCollection, false);
                 this.props.selectDocuments([newCollection]);
             }
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index ae1393fc4..93c97fa23 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -665,6 +665,16 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         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() })];
+        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()]);
+            }
+        }
     }
 
     componentWillUnmount() {
-- 
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/views/collections/collectionFreeForm/CollectionFreeFormView.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


From be9562ef9d2ad2675641e6215f1567f7e0b9c0e3 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 10 Sep 2019 18:34:34 -0400
Subject: write backgroundColor to data doc

---
 .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx   | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

(limited to 'src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx')

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index f7c1bedbb..3ebbe0046 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -266,8 +266,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         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);
+        round && (Doc.GetProto(newBox).borderRounding = round);
+        col && (Doc.GetProto(newBox).backgroundColor = col);
         newBox.ruleProvider = ruleProvider;
         this.addDocument(newBox, false);
     }
-- 
cgit v1.2.3-70-g09d2

