From 2b829d1028a61869858ecd48e2f1801819e17488 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Tue, 13 Aug 2019 22:15:47 -0400
Subject: robust google doc export api and cleanup

---
 src/client/views/nodes/FormattedTextBox.tsx | 24 ++++++++++++++++++++++--
 1 file changed, 22 insertions(+), 2 deletions(-)

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

diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 44b5d2c21..8c2af7c9e 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -1,5 +1,5 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
-import { faEdit, faSmile, faTextHeight } from '@fortawesome/free-solid-svg-icons';
+import { faEdit, faSmile, faTextHeight, faUpload } from '@fortawesome/free-solid-svg-icons';
 import { action, IReactionDisposer, observable, reaction, runInAction, computed, Lambda, trace } from "mobx";
 import { observer } from "mobx-react";
 import { baseKeymap } from "prosemirror-commands";
@@ -38,9 +38,10 @@ import { For } from 'babel-types';
 import { DateField } from '../../../new_fields/DateField';
 import { Utils } from '../../../Utils';
 import { MainOverlayTextBox } from '../MainOverlayTextBox';
+import { GoogleApiClientUtils } from '../../apis/google_docs/GoogleApiClientUtils';
 
 library.add(faEdit);
-library.add(faSmile, faTextHeight);
+library.add(faSmile, faTextHeight, faUpload);
 
 // FormattedTextBox: Displays an editable plain text node that maps to a specified Key of a Document
 //
@@ -58,6 +59,8 @@ const richTextSchema = createSchema({
     documentText: "string"
 });
 
+const googleDocKey = "googleDocId";
+
 type RichTextDocument = makeInterface<[typeof richTextSchema]>;
 const RichTextDocument = makeInterface(richTextSchema);
 
@@ -661,7 +664,24 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             event: action(() => Doc.GetProto(this.props.Document).autoHeight = !BoolCast(this.props.Document.autoHeight)), icon: "expand-arrows-alt"
         });
         ContextMenu.Instance.addItem({ description: "Text Funcs...", subitems: subitems, icon: "text-height" });
+        if (!(googleDocKey in Doc.GetProto(this.props.Document))) {
+            ContextMenu.Instance.addItem({ description: "Export to Google Doc...", event: this.exportToGoogleDoc, icon: "upload" });
+        }
     }
+
+    exportToGoogleDoc = () => {
+        let dataDoc = Doc.GetProto(this.props.Document);
+        let data = Cast(dataDoc.data, RichTextField);
+        let content: string | undefined;
+        if (data && (content = data.plainText())) {
+            GoogleApiClientUtils.Docs.Write({
+                title: StrCast(dataDoc.title),
+                store: { receiver: dataDoc, key: googleDocKey },
+                content
+            });
+        }
+    }
+
     render() {
         let self = this;
         let style = this.props.isOverlay ? "scroll" : "hidden";
-- 
cgit v1.2.3-70-g09d2


From dc766185075b5861686c68a704a8e49213eacbc6 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Wed, 14 Aug 2019 01:00:29 -0400
Subject: cleanup and streamlined / robust type requirements

---
 .../apis/google_docs/GoogleApiClientUtils.ts       | 88 +++++++++++-----------
 src/client/views/MainView.tsx                      | 12 ---
 src/client/views/nodes/FormattedTextBox.tsx        | 20 ++---
 src/new_fields/RichTextField.ts                    |  2 +-
 4 files changed, 58 insertions(+), 64 deletions(-)

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

diff --git a/src/client/apis/google_docs/GoogleApiClientUtils.ts b/src/client/apis/google_docs/GoogleApiClientUtils.ts
index dda36f05a..f4fb87e0b 100644
--- a/src/client/apis/google_docs/GoogleApiClientUtils.ts
+++ b/src/client/apis/google_docs/GoogleApiClientUtils.ts
@@ -17,32 +17,32 @@ export namespace GoogleApiClientUtils {
         export namespace Utils {
 
             export const extractText = (document: docs_v1.Schema$Document, removeNewlines = false) => {
-                let fragments: string[] = [];
+                const fragments: string[] = [];
                 if (document.body && document.body.content) {
-                    for (let element of document.body.content) {
+                    for (const element of document.body.content) {
                         if (element.paragraph && element.paragraph.elements) {
-                            for (let inner of element.paragraph.elements) {
+                            for (const inner of element.paragraph.elements) {
                                 if (inner && inner.textRun) {
-                                    let fragment = inner.textRun.content;
+                                    const fragment = inner.textRun.content;
                                     fragment && fragments.push(fragment);
                                 }
                             }
                         }
                     }
                 }
-                let text = fragments.join("");
+                const text = fragments.join("");
                 return removeNewlines ? text.ReplaceAll("\n", "") : text;
             };
 
             export const EndOf = (schema: docs_v1.Schema$Document): Opt<number> => {
                 if (schema.body && schema.body.content) {
-                    let paragraphs = schema.body.content.filter(el => el.paragraph);
+                    const paragraphs = schema.body.content.filter(el => el.paragraph);
                     if (paragraphs.length) {
-                        let target = paragraphs[paragraphs.length - 1];
+                        const target = paragraphs[paragraphs.length - 1];
                         if (target.paragraph && target.paragraph.elements) {
                             length = target.paragraph.elements.length;
                             if (length) {
-                                let final = target.paragraph.elements[length - 1];
+                                const final = target.paragraph.elements[length - 1];
                                 return final.endIndex ? final.endIndex - 1 : undefined;
                             }
                         }
@@ -52,17 +52,25 @@ export namespace GoogleApiClientUtils {
 
         }
 
+        export type IdHandler = (id: DocumentId) => any;
+        export interface CreateOptions {
+            handler: IdHandler;
+            // if excluded, will use a default title annotated with the current date
+            title?: string;
+        }
+
         export interface ReadOptions {
             documentId: string;
+            // if exluded, will preserve newlines
             removeNewlines?: boolean;
         }
 
+        export type DocumentId = string;
         export interface WriteOptions {
-            documentId?: string;
-            title?: string;
             content: string | string[];
+            reference: DocumentId | CreateOptions;
+            // if excluded, will compute the last index of the document and append the content there
             index?: number;
-            store?: { receiver: Doc, key: string };
         }
 
         /**
@@ -70,33 +78,36 @@ export namespace GoogleApiClientUtils {
          * and grants the appropriate permissions, this function programmatically creates an arbitrary Google Doc which
          * should appear in the user's Google Doc library instantaneously.
          * 
-         * @param schema whatever subset of a docs_v1.Schema$Document is required to properly initialize your
-         * Google Doc. This schema defines all aspects of a Google Doc, from the title to headers / footers to the
-         * actual document body and its styling!
+         * @param options the title to assign to the new document, and the information necessary
+         * to store the new documentId returned from the creation process
          * @returns the documentId of the newly generated document, or undefined if the creation process fails.
          */
-        const Create = async (title?: string): Promise<string | undefined> => {
-            let path = RouteStore.googleDocs + Actions.Create;
-            let parameters = {
+        const Create = async (options: CreateOptions): Promise<string | undefined> => {
+            const path = RouteStore.googleDocs + Actions.Create;
+            const parameters = {
                 requestBody: {
-                    title: title || `Dash Export (${new Date().toDateString()})`
+                    title: options.title || `Dash Export (${new Date().toDateString()})`
                 }
             };
             try {
-                let schema: docs_v1.Schema$Document = await PostToServer(path, parameters);
-                return schema.documentId;
+                const schema: docs_v1.Schema$Document = await PostToServer(path, parameters);
+                const generatedId = schema.documentId;
+                if (generatedId) {
+                    options.handler(generatedId);
+                    return generatedId;
+                }
             } catch {
                 return undefined;
             }
         };
 
         const Retrieve = async (documentId: string): Promise<docs_v1.Schema$Document | undefined> => {
-            let path = RouteStore.googleDocs + Actions.Retrieve;
-            let parameters = {
+            const path = RouteStore.googleDocs + Actions.Retrieve;
+            const parameters = {
                 documentId
             };
             try {
-                let schema: docs_v1.Schema$Document = await PostToServer(path, parameters);
+                const schema: docs_v1.Schema$Document = await PostToServer(path, parameters);
                 return schema;
             } catch {
                 return undefined;
@@ -104,16 +115,15 @@ export namespace GoogleApiClientUtils {
         };
 
         const Update = async (documentId: string, requests: docs_v1.Schema$Request[]): Promise<docs_v1.Schema$BatchUpdateDocumentResponse | undefined> => {
-            let path = RouteStore.googleDocs + Actions.Update;
-            let parameters = {
+            const path = RouteStore.googleDocs + Actions.Update;
+            const parameters = {
                 documentId,
                 requestBody: {
                     requests
                 }
             };
             try {
-                let replies: docs_v1.Schema$BatchUpdateDocumentResponse = await PostToServer(path, parameters);
-                console.log(replies);
+                const replies: docs_v1.Schema$BatchUpdateDocumentResponse = await PostToServer(path, parameters);
                 return replies;
             } catch {
                 return undefined;
@@ -131,38 +141,32 @@ export namespace GoogleApiClientUtils {
                 if (!schema) {
                     return undefined;
                 }
-                let lines = Utils.extractText(schema).split("\n");
+                const lines = Utils.extractText(schema).split("\n");
                 return options.removeNewlines ? lines.filter(line => line.length) : lines;
             });
         };
 
         export const Write = async (options: WriteOptions): Promise<docs_v1.Schema$BatchUpdateDocumentResponse | undefined> => {
-            let target = options.documentId;
-            if (!target) {
-                if (!(target = await Create(options.title))) {
-                    return undefined;
-                }
+            let documentId: string | undefined;
+            const ref = options.reference;
+            if (!(documentId = typeof ref === "string" ? ref : await Create(ref))) {
+                return undefined;
             }
             let index = options.index;
             if (!index) {
-                let schema = await Retrieve(target);
+                let schema = await Retrieve(documentId);
                 if (!schema || !(index = Utils.EndOf(schema))) {
                     return undefined;
                 }
             }
-            let text = options.content;
-            let request = {
+            const text = options.content;
+            const request = {
                 insertText: {
                     text: isArray(text) ? text.join("\n") : text,
                     location: { index }
                 }
             };
-            return Update(target, [request]).then(res => {
-                if (res && options.store) {
-                    options.store.receiver[options.store.key] = res.documentId;
-                }
-                return res;
-            });
+            return Update(documentId, [request]);
         };
 
     }
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 7b15e9624..77f0e3d60 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -149,18 +149,6 @@ export class MainView extends React.Component {
         }, { fireImmediately: true });
     }
 
-    componentDidMount() {
-        reaction(() => this.mainContainer, () => {
-            let main = this.mainContainer, documentId;
-            if (main && (documentId = StrCast(main.googleDocId))) {
-                let options = { documentId, removeNewlines: true };
-                GoogleApiClientUtils.Docs.ReadLines(options).then(lines => {
-                    console.log(lines);
-                });
-            }
-        });
-    }
-
     componentWillUnMount() {
         window.removeEventListener("keydown", KeyManager.Instance.handle);
         window.removeEventListener("pointerdown", this.globalPointerDown);
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 8c2af7c9e..50ec27259 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -670,16 +670,18 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     }
 
     exportToGoogleDoc = () => {
-        let dataDoc = Doc.GetProto(this.props.Document);
-        let data = Cast(dataDoc.data, RichTextField);
-        let content: string | undefined;
-        if (data && (content = data.plainText())) {
-            GoogleApiClientUtils.Docs.Write({
-                title: StrCast(dataDoc.title),
-                store: { receiver: dataDoc, key: googleDocKey },
-                content
-            });
+        const dataDoc = Doc.GetProto(this.props.Document);
+        const data = Cast(dataDoc.data, RichTextField);
+        if (!data) {
+            return;
         }
+        GoogleApiClientUtils.Docs.Write({
+            reference: {
+                title: StrCast(dataDoc.title),
+                handler: id => dataDoc[googleDocKey] = id
+            },
+            content: data.plainText()
+        });
     }
 
     render() {
diff --git a/src/new_fields/RichTextField.ts b/src/new_fields/RichTextField.ts
index dc66813e0..3e8803a34 100644
--- a/src/new_fields/RichTextField.ts
+++ b/src/new_fields/RichTextField.ts
@@ -33,6 +33,6 @@ export class RichTextField extends ObjectField {
             considering = considering.substring(matches.index + matches[0].length);
             this.Extractor.lastIndex = 0;
         }
-        return contents.length ? contents : undefined;
+        return contents;
     }
 }
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From 48fcec82fa384ec260a02965f9f78c2e41256dd9 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Wed, 14 Aug 2019 03:41:11 -0400
Subject: clean up and regex improvement

---
 .../apis/google_docs/GoogleApiClientUtils.ts       | 111 ++++++++++++---------
 src/client/views/nodes/FormattedTextBox.tsx        |  15 ++-
 src/new_fields/RichTextField.ts                    |   4 +-
 src/server/index.ts                                |  35 ++++---
 4 files changed, 99 insertions(+), 66 deletions(-)

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

diff --git a/src/client/apis/google_docs/GoogleApiClientUtils.ts b/src/client/apis/google_docs/GoogleApiClientUtils.ts
index f4fb87e0b..c6c7d7bd4 100644
--- a/src/client/apis/google_docs/GoogleApiClientUtils.ts
+++ b/src/client/apis/google_docs/GoogleApiClientUtils.ts
@@ -1,7 +1,7 @@
 import { docs_v1 } from "googleapis";
 import { PostToServer } from "../../../Utils";
 import { RouteStore } from "../../../server/RouteStore";
-import { Opt, Doc } from "../../../new_fields/Doc";
+import { Opt } from "../../../new_fields/Doc";
 import { isArray } from "util";
 
 export namespace GoogleApiClientUtils {
@@ -14,9 +14,42 @@ export namespace GoogleApiClientUtils {
             Update = "update"
         }
 
+        export type DocumentId = string;
+        export type Reference = DocumentId | CreateOptions;
+        export type TextContent = string | string[];
+        export type IdHandler = (id: DocumentId) => any;
+
+        export type CreationResult = Opt<DocumentId>;
+        export type RetrievalResult = Opt<docs_v1.Schema$Document>;
+        export type UpdateResult = Opt<docs_v1.Schema$BatchUpdateDocumentResponse>;
+        export type ReadLinesResult = Opt<string[]>;
+        export type ReadResult = Opt<string>;
+
+        export interface CreateOptions {
+            handler: IdHandler; // callback to process the documentId of the newly created Google Doc
+            title?: string; // if excluded, will use a default title annotated with the current date
+        }
+
+        export interface RetrieveOptions {
+            documentId: DocumentId;
+        }
+
+        export type ReadOptions = RetrieveOptions & { removeNewlines?: boolean };
+
+        export interface WriteOptions {
+            content: TextContent;
+            reference: Reference;
+            index?: number; // if excluded, will compute the last index of the document and append the content there
+        }
+
+        export interface UpdateOptions {
+            documentId: DocumentId;
+            requests: docs_v1.Schema$Request[];
+        }
+
         export namespace Utils {
 
-            export const extractText = (document: docs_v1.Schema$Document, removeNewlines = false) => {
+            export const extractText = (document: docs_v1.Schema$Document, removeNewlines = false): string => {
                 const fragments: string[] = [];
                 if (document.body && document.body.content) {
                     for (const element of document.body.content) {
@@ -34,7 +67,7 @@ export namespace GoogleApiClientUtils {
                 return removeNewlines ? text.ReplaceAll("\n", "") : text;
             };
 
-            export const EndOf = (schema: docs_v1.Schema$Document): Opt<number> => {
+            export const endOf = (schema: docs_v1.Schema$Document): number | undefined => {
                 if (schema.body && schema.body.content) {
                     const paragraphs = schema.body.content.filter(el => el.paragraph);
                     if (paragraphs.length) {
@@ -50,27 +83,8 @@ export namespace GoogleApiClientUtils {
                 }
             };
 
-        }
+            export const initialize = async (reference: Reference) => typeof reference === "string" ? reference : create(reference);
 
-        export type IdHandler = (id: DocumentId) => any;
-        export interface CreateOptions {
-            handler: IdHandler;
-            // if excluded, will use a default title annotated with the current date
-            title?: string;
-        }
-
-        export interface ReadOptions {
-            documentId: string;
-            // if exluded, will preserve newlines
-            removeNewlines?: boolean;
-        }
-
-        export type DocumentId = string;
-        export interface WriteOptions {
-            content: string | string[];
-            reference: DocumentId | CreateOptions;
-            // if excluded, will compute the last index of the document and append the content there
-            index?: number;
         }
 
         /**
@@ -82,7 +96,7 @@ export namespace GoogleApiClientUtils {
          * to store the new documentId returned from the creation process
          * @returns the documentId of the newly generated document, or undefined if the creation process fails.
          */
-        const Create = async (options: CreateOptions): Promise<string | undefined> => {
+        export const create = async (options: CreateOptions): Promise<CreationResult> => {
             const path = RouteStore.googleDocs + Actions.Create;
             const parameters = {
                 requestBody: {
@@ -101,43 +115,40 @@ export namespace GoogleApiClientUtils {
             }
         };
 
-        const Retrieve = async (documentId: string): Promise<docs_v1.Schema$Document | undefined> => {
+        export const retrieve = async (options: RetrieveOptions): Promise<RetrievalResult> => {
             const path = RouteStore.googleDocs + Actions.Retrieve;
-            const parameters = {
-                documentId
-            };
             try {
-                const schema: docs_v1.Schema$Document = await PostToServer(path, parameters);
+                const schema: RetrievalResult = await PostToServer(path, options);
                 return schema;
             } catch {
                 return undefined;
             }
         };
 
-        const Update = async (documentId: string, requests: docs_v1.Schema$Request[]): Promise<docs_v1.Schema$BatchUpdateDocumentResponse | undefined> => {
+        export const update = async (options: UpdateOptions): Promise<UpdateResult> => {
             const path = RouteStore.googleDocs + Actions.Update;
             const parameters = {
-                documentId,
+                documentId: options.documentId,
                 requestBody: {
-                    requests
+                    requests: options.requests
                 }
             };
             try {
-                const replies: docs_v1.Schema$BatchUpdateDocumentResponse = await PostToServer(path, parameters);
+                const replies: UpdateResult = await PostToServer(path, parameters);
                 return replies;
             } catch {
                 return undefined;
             }
         };
 
-        export const Read = async (options: ReadOptions): Promise<string | undefined> => {
-            return Retrieve(options.documentId).then(schema => {
+        export const read = async (options: ReadOptions): Promise<ReadResult> => {
+            return retrieve(options).then(schema => {
                 return schema ? Utils.extractText(schema, options.removeNewlines) : undefined;
             });
         };
 
-        export const ReadLines = async (options: ReadOptions) => {
-            return Retrieve(options.documentId).then(schema => {
+        export const readLines = async (options: ReadOptions): Promise<ReadLinesResult> => {
+            return retrieve(options).then(schema => {
                 if (!schema) {
                     return undefined;
                 }
@@ -146,27 +157,29 @@ export namespace GoogleApiClientUtils {
             });
         };
 
-        export const Write = async (options: WriteOptions): Promise<docs_v1.Schema$BatchUpdateDocumentResponse | undefined> => {
-            let documentId: string | undefined;
-            const ref = options.reference;
-            if (!(documentId = typeof ref === "string" ? ref : await Create(ref))) {
+        export const write = async (options: WriteOptions): Promise<UpdateResult> => {
+            const documentId = await Utils.initialize(options.reference);
+            if (!documentId) {
                 return undefined;
             }
             let index = options.index;
             if (!index) {
-                let schema = await Retrieve(documentId);
-                if (!schema || !(index = Utils.EndOf(schema))) {
+                let schema = await retrieve({ documentId });
+                if (!schema || !(index = Utils.endOf(schema))) {
                     return undefined;
                 }
             }
             const text = options.content;
-            const request = {
-                insertText: {
-                    text: isArray(text) ? text.join("\n") : text,
-                    location: { index }
-                }
+            const updateOptions = {
+                documentId,
+                requests: [{
+                    insertText: {
+                        text: isArray(text) ? text.join("\n") : text,
+                        location: { index }
+                    }
+                }]
             };
-            return Update(documentId, [request]);
+            return update(updateOptions);
         };
 
     }
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 50ec27259..46aed9b2d 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -288,6 +288,19 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         }
     }
 
+    componentWillMount() {
+        this.pollExportedCounterpart();
+    }
+
+    pollExportedCounterpart = async () => {
+        let dataDoc = Doc.GetProto(this.props.Document);
+        let documentId = StrCast(dataDoc[googleDocKey]);
+        if (documentId) {
+            let contents = await GoogleApiClientUtils.Docs.read({ documentId });
+            contents ? console.log(contents) : delete dataDoc[googleDocKey];
+        }
+    }
+
     componentDidMount() {
         const config = {
             schema,
@@ -675,7 +688,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         if (!data) {
             return;
         }
-        GoogleApiClientUtils.Docs.Write({
+        GoogleApiClientUtils.Docs.write({
             reference: {
                 title: StrCast(dataDoc.title),
                 handler: id => dataDoc[googleDocKey] = id
diff --git a/src/new_fields/RichTextField.ts b/src/new_fields/RichTextField.ts
index 3e8803a34..4f782816c 100644
--- a/src/new_fields/RichTextField.ts
+++ b/src/new_fields/RichTextField.ts
@@ -9,7 +9,7 @@ import { scriptingGlobal } from "../client/util/Scripting";
 export class RichTextField extends ObjectField {
     @serializable(true)
     readonly Data: string;
-    private Extractor = /,\"text\":\"([^\"\}]*)\"\}/g;
+    private Extractor = /,\"text\":\"([^\}]*)\"\}/g;
 
     constructor(data: string) {
         super();
@@ -33,6 +33,6 @@ export class RichTextField extends ObjectField {
             considering = considering.substring(matches.index + matches[0].length);
             this.Extractor.lastIndex = 0;
         }
-        return contents;
+        return contents.ReplaceAll("\\", "");
     }
 }
\ No newline at end of file
diff --git a/src/server/index.ts b/src/server/index.ts
index abaa29658..ef1829f30 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -43,6 +43,8 @@ import * as YoutubeApi from "./apis/youtube/youtubeApiSample";
 import { Response } from 'express-serve-static-core';
 import { GoogleApiServerUtils } from "./apis/google/GoogleApiServerUtils";
 import { GaxiosResponse } from 'gaxios';
+import { Opt } from '../new_fields/Doc';
+import { docs_v1 } from 'googleapis';
 const MongoStore = require('connect-mongo')(session);
 const mongoose = require('mongoose');
 const probe = require("probe-image-size");
@@ -797,23 +799,28 @@ function HandleYoutubeQuery([query, callback]: [YoutubeQueryInput, (result?: any
 const credentials = path.join(__dirname, "./credentials/google_docs_credentials.json");
 const token = path.join(__dirname, "./credentials/google_docs_token.json");
 
+type ApiResponse = Promise<GaxiosResponse>;
+type ApiHandler = (endpoint: docs_v1.Resource$Documents, parameters: any) => ApiResponse;
+type Action = "create" | "retrieve" | "update";
+
+const EndpointHandlerMap = new Map<Action, ApiHandler>([
+    ["create", (api, params) => api.create(params)],
+    ["retrieve", (api, params) => api.get(params)],
+    ["update", (api, params) => api.batchUpdate(params)],
+]);
+
 app.post(RouteStore.googleDocs + ":action", (req, res) => {
     GoogleApiServerUtils.Docs.GetEndpoint({ credentials, token }).then(endpoint => {
-        let results: Promise<GaxiosResponse> | undefined;
-        let documents = endpoint.documents;
-        let parameters = req.body;
-        switch (req.params.action) {
-            case "create":
-                results = documents.create(parameters);
-                break;
-            case "retrieve":
-                results = documents.get(parameters);
-                break;
-            case "update":
-                results = documents.batchUpdate(parameters);
-                break;
+        let handler = EndpointHandlerMap.get(req.params.action);
+        if (handler) {
+            let execute = handler(endpoint.documents, req.body).then(
+                response => res.send(response.data),
+                rejection => res.send(rejection)
+            );
+            execute.catch(exception => res.send(exception));
+            return;
         }
-        !results ? res.send(undefined) : results.then(response => res.send(response.data));
+        res.send(undefined);
     });
 });
 
-- 
cgit v1.2.3-70-g09d2


From 9ea032b9ab14ba17511b1014044dba0236e93837 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Wed, 14 Aug 2019 10:48:23 -0400
Subject: refactored plain text parsing and setting

---
 src/client/views/nodes/FormattedTextBox.tsx | 13 +++++---
 src/new_fields/RichTextField.ts             | 50 ++++++++++++++++++++++-------
 2 files changed, 48 insertions(+), 15 deletions(-)

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

diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 46aed9b2d..5ba2aa0cf 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -12,7 +12,7 @@ import { EditorView } from "prosemirror-view";
 import { Doc, Opt, DocListCast } from "../../../new_fields/Doc";
 import { Id, Copy } from '../../../new_fields/FieldSymbols';
 import { List } from '../../../new_fields/List';
-import { RichTextField } from "../../../new_fields/RichTextField";
+import { RichTextField, ToPlainText, FromPlainText } from "../../../new_fields/RichTextField";
 import { createSchema, listSpec, makeInterface } from "../../../new_fields/Schema";
 import { BoolCast, Cast, NumCast, StrCast, DateCast } from "../../../new_fields/Types";
 import { DocServer } from "../../DocServer";
@@ -296,8 +296,13 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         let dataDoc = Doc.GetProto(this.props.Document);
         let documentId = StrCast(dataDoc[googleDocKey]);
         if (documentId) {
-            let contents = await GoogleApiClientUtils.Docs.read({ documentId });
-            contents ? console.log(contents) : delete dataDoc[googleDocKey];
+            let exportState = await GoogleApiClientUtils.Docs.read({ documentId });
+            if (exportState) {
+                let data = Cast(dataDoc.data, RichTextField);
+                data && data[FromPlainText](exportState);
+            } else {
+                delete dataDoc[googleDocKey];
+            }
         }
     }
 
@@ -693,7 +698,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 title: StrCast(dataDoc.title),
                 handler: id => dataDoc[googleDocKey] = id
             },
-            content: data.plainText()
+            content: data[ToPlainText]()
         });
     }
 
diff --git a/src/new_fields/RichTextField.ts b/src/new_fields/RichTextField.ts
index 4f782816c..9d8a1cecb 100644
--- a/src/new_fields/RichTextField.ts
+++ b/src/new_fields/RichTextField.ts
@@ -4,12 +4,14 @@ import { Deserializable } from "../client/util/SerializationHelper";
 import { Copy, ToScriptString } from "./FieldSymbols";
 import { scriptingGlobal } from "../client/util/Scripting";
 
+export const ToPlainText = Symbol("PlainText");
+export const FromPlainText = Symbol("PlainText");
+
 @scriptingGlobal
 @Deserializable("RichTextField")
 export class RichTextField extends ObjectField {
     @serializable(true)
-    readonly Data: string;
-    private Extractor = /,\"text\":\"([^\}]*)\"\}/g;
+    Data: string;
 
     constructor(data: string) {
         super();
@@ -24,15 +26,41 @@ export class RichTextField extends ObjectField {
         return `new RichTextField("${this.Data}")`;
     }
 
-    plainText = () => {
-        let contents = "";
-        let matches: RegExpExecArray | null;
-        let considering = this.Data;
-        while ((matches = this.Extractor.exec(considering)) !== null) {
-            contents += matches[1];
-            considering = considering.substring(matches.index + matches[0].length);
-            this.Extractor.lastIndex = 0;
+    [ToPlainText]() {
+        let content = JSON.parse(this.Data).doc.content;
+        let paragraphs = content.filter((item: any) => item.type === "paragraph");
+        let output = "";
+        for (let i = 0; i < paragraphs.length; i++) {
+            let paragraph = paragraphs[i];
+            if (paragraph.content) {
+                output += paragraph.content.map((block: any) => block.text).join("");
+            } else {
+                output += i > 0 && paragraphs[i - 1].content ? "\n\n" : "\n";
+            }
         }
-        return contents.ReplaceAll("\\", "");
+        return output;
+    }
+
+    [FromPlainText](plainText: string) {
+        let elements = plainText.split("\n");
+        let parsed = JSON.parse(this.Data);
+        parsed.doc.content = elements.map(text => {
+            let paragraph: any = { type: "paragraph" };
+            if (text.length) {
+                paragraph.content = [{
+                    type: "text",
+                    marks: [],
+                    text
+                }];
+            }
+            return paragraph;
+        });
+        parsed.selection = {
+            type: "text",
+            anchor: 0,
+            head: 0
+        };
+        this.Data = JSON.stringify(parsed);
     }
+
 }
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From 13d2be7f66c01ee9f09cb65f567eb3da760df670 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Wed, 14 Aug 2019 15:18:13 -0400
Subject: semi-synchronous editing between dash notes and google docs

---
 package.json                                       |  2 +
 .../apis/google_docs/GoogleApiClientUtils.ts       | 40 ++++++++++++++------
 src/client/views/nodes/FormattedTextBox.tsx        | 44 ++++++++++++++--------
 src/new_fields/RichTextField.ts                    | 25 +++++++-----
 4 files changed, 76 insertions(+), 35 deletions(-)

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

diff --git a/package.json b/package.json
index 326468ab9..7a629d813 100644
--- a/package.json
+++ b/package.json
@@ -63,6 +63,7 @@
         "@types/cookie-parser": "^1.4.1",
         "@types/cookie-session": "^2.0.36",
         "@types/d3-format": "^1.3.1",
+        "@types/diff": "^4.0.2",
         "@types/dotenv": "^6.1.1",
         "@types/express": "^4.16.1",
         "@types/express-flash": "0.0.0",
@@ -127,6 +128,7 @@
         "cookie-session": "^2.0.0-beta.3",
         "crypto-browserify": "^3.11.0",
         "d3-format": "^1.3.2",
+        "diff": "^4.0.1",
         "dotenv": "^8.0.0",
         "express": "^4.16.4",
         "express-flash": "0.0.2",
diff --git a/src/client/apis/google_docs/GoogleApiClientUtils.ts b/src/client/apis/google_docs/GoogleApiClientUtils.ts
index c6c7d7bd4..c1cbba31f 100644
--- a/src/client/apis/google_docs/GoogleApiClientUtils.ts
+++ b/src/client/apis/google_docs/GoogleApiClientUtils.ts
@@ -14,6 +14,11 @@ export namespace GoogleApiClientUtils {
             Update = "update"
         }
 
+        export enum WriteMode {
+            Insert,
+            Replace
+        }
+
         export type DocumentId = string;
         export type Reference = DocumentId | CreateOptions;
         export type TextContent = string | string[];
@@ -37,6 +42,7 @@ export namespace GoogleApiClientUtils {
         export type ReadOptions = RetrieveOptions & { removeNewlines?: boolean };
 
         export interface WriteOptions {
+            mode: WriteMode;
             content: TextContent;
             reference: Reference;
             index?: number; // if excluded, will compute the last index of the document and append the content there
@@ -158,28 +164,40 @@ export namespace GoogleApiClientUtils {
         };
 
         export const write = async (options: WriteOptions): Promise<UpdateResult> => {
+            const requests: docs_v1.Schema$Request[] = [];
             const documentId = await Utils.initialize(options.reference);
             if (!documentId) {
                 return undefined;
             }
             let index = options.index;
-            if (!index) {
+            const mode = options.mode;
+            if (!(index && mode === WriteMode.Insert)) {
                 let schema = await retrieve({ documentId });
                 if (!schema || !(index = Utils.endOf(schema))) {
                     return undefined;
                 }
             }
-            const text = options.content;
-            const updateOptions = {
-                documentId,
-                requests: [{
-                    insertText: {
-                        text: isArray(text) ? text.join("\n") : text,
-                        location: { index }
+            if (mode === WriteMode.Replace) {
+                requests.push({
+                    deleteContentRange: {
+                        range: {
+                            startIndex: 1,
+                            endIndex: index
+                        }
                     }
-                }]
-            };
-            return update(updateOptions);
+                });
+                index = 1;
+            }
+            const text = options.content;
+            requests.push({
+                insertText: {
+                    text: isArray(text) ? text.join("\n") : text,
+                    location: { index }
+                }
+            });
+            let replies = await update({ documentId, requests });
+            console.log(replies);
+            return replies;
         };
 
     }
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 5ba2aa0cf..ad29ce775 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -12,7 +12,7 @@ import { EditorView } from "prosemirror-view";
 import { Doc, Opt, DocListCast } from "../../../new_fields/Doc";
 import { Id, Copy } from '../../../new_fields/FieldSymbols';
 import { List } from '../../../new_fields/List';
-import { RichTextField, ToPlainText, FromPlainText } from "../../../new_fields/RichTextField";
+import { RichTextField, ToGoogleDocText, FromGoogleDocText } from "../../../new_fields/RichTextField";
 import { createSchema, listSpec, makeInterface } from "../../../new_fields/Schema";
 import { BoolCast, Cast, NumCast, StrCast, DateCast } from "../../../new_fields/Types";
 import { DocServer } from "../../DocServer";
@@ -39,6 +39,7 @@ import { DateField } from '../../../new_fields/DateField';
 import { Utils } from '../../../Utils';
 import { MainOverlayTextBox } from '../MainOverlayTextBox';
 import { GoogleApiClientUtils } from '../../apis/google_docs/GoogleApiClientUtils';
+import * as diff from "diff";
 
 library.add(faEdit);
 library.add(faSmile, faTextHeight, faUpload);
@@ -299,7 +300,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             let exportState = await GoogleApiClientUtils.Docs.read({ documentId });
             if (exportState) {
                 let data = Cast(dataDoc.data, RichTextField);
-                data && data[FromPlainText](exportState);
+                if (data) {
+                    dataDoc.data = new RichTextField(data[FromGoogleDocText](exportState));
+                }
             } else {
                 delete dataDoc[googleDocKey];
             }
@@ -629,6 +632,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             this._undoTyping.end();
             this._undoTyping = undefined;
         }
+        this.updateGoogleDoc();
     }
     public _undoTyping?: UndoManager.Batch;
     onKeyPress = (e: React.KeyboardEvent) => {
@@ -683,23 +687,33 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         });
         ContextMenu.Instance.addItem({ description: "Text Funcs...", subitems: subitems, icon: "text-height" });
         if (!(googleDocKey in Doc.GetProto(this.props.Document))) {
-            ContextMenu.Instance.addItem({ description: "Export to Google Doc...", event: this.exportToGoogleDoc, icon: "upload" });
+            ContextMenu.Instance.addItem({
+                description: "Export to Google Doc...",
+                event: this.updateGoogleDoc,
+                icon: "upload"
+            });
         }
     }
 
-    exportToGoogleDoc = () => {
-        const dataDoc = Doc.GetProto(this.props.Document);
-        const data = Cast(dataDoc.data, RichTextField);
-        if (!data) {
-            return;
+    updateGoogleDoc = () => {
+        let modes = GoogleApiClientUtils.Docs.WriteMode;
+        let mode = modes.Replace;
+        let reference: Opt<GoogleApiClientUtils.Docs.Reference> = Cast(this.dataDoc[googleDocKey], "string");
+        if (!reference) {
+            mode = modes.Insert;
+            reference = {
+                title: StrCast(this.dataDoc.title),
+                handler: id => this.dataDoc[googleDocKey] = id
+            };
+        }
+        const data = Cast(this.dataDoc.data, RichTextField);
+        if (data) {
+            GoogleApiClientUtils.Docs.write({
+                mode,
+                content: data[ToGoogleDocText](),
+                reference
+            });
         }
-        GoogleApiClientUtils.Docs.write({
-            reference: {
-                title: StrCast(dataDoc.title),
-                handler: id => dataDoc[googleDocKey] = id
-            },
-            content: data[ToPlainText]()
-        });
     }
 
     render() {
diff --git a/src/new_fields/RichTextField.ts b/src/new_fields/RichTextField.ts
index f0284b1b8..92b19b921 100644
--- a/src/new_fields/RichTextField.ts
+++ b/src/new_fields/RichTextField.ts
@@ -4,14 +4,14 @@ import { Deserializable } from "../client/util/SerializationHelper";
 import { Copy, ToScriptString } from "./FieldSymbols";
 import { scriptingGlobal } from "../client/util/Scripting";
 
-export const ToPlainText = Symbol("PlainText");
-export const FromPlainText = Symbol("PlainText");
+export const ToGoogleDocText = Symbol("PlainText");
+export const FromGoogleDocText = Symbol("PlainText");
 
 @scriptingGlobal
 @Deserializable("RichTextField")
 export class RichTextField extends ObjectField {
     @serializable(true)
-    Data: string;
+    readonly Data: string;
 
     constructor(data: string) {
         super();
@@ -26,24 +26,28 @@ export class RichTextField extends ObjectField {
         return `new RichTextField("${this.Data}")`;
     }
 
-    [ToPlainText]() {
+    [ToGoogleDocText]() {
         let content = JSON.parse(this.Data).doc.content;
         let paragraphs = content.filter((item: any) => item.type === "paragraph");
         let output = "";
         for (let i = 0; i < paragraphs.length; i++) {
             let paragraph = paragraphs[i];
+            let addNewLine = i > 0 ? paragraphs[i - 1].content : false;
             if (paragraph.content) {
                 output += paragraph.content.map((block: any) => block.text).join("");
             } else {
-                output += i > 0 && paragraphs[i - 1].content ? "\n\n" : "\n";
+                output += "\n";
             }
+            addNewLine && (output += "\n");
         }
         return output;
     }
 
-    [FromPlainText](plainText: string) {
+    [FromGoogleDocText](plainText: string) {
         let elements = plainText.split("\n");
+        !elements[elements.length - 1].length && elements.pop();
         let parsed = JSON.parse(this.Data);
+        let blankCount = 0;
         parsed.doc.content = elements.map(text => {
             let paragraph: any = { type: "paragraph" };
             if (text.length) {
@@ -52,15 +56,18 @@ export class RichTextField extends ObjectField {
                     marks: [],
                     text
                 }];
+            } else {
+                blankCount++;
             }
             return paragraph;
         });
+        let selection = plainText.length + 2 * blankCount;
         parsed.selection = {
             type: "text",
-            anchor: plainText.length,
-            head: plainText.length
+            anchor: selection,
+            head: selection
         };
-        this.Data = JSON.stringify(parsed);
+        return JSON.stringify(parsed);
     }
 
 }
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From 6bbf8df1b246647ae478103504e73c98df7b8183 Mon Sep 17 00:00:00 2001
From: monikahedman <monika_hedman@brown.edu>
Date: Thu, 15 Aug 2019 18:07:04 -0400
Subject: text paste working!

---
 src/client/views/PreviewCursor.tsx                 | 31 +++++++++------
 .../collections/collectionFreeForm/MarqueeView.tsx |  3 +-
 src/client/views/nodes/FormattedTextBox.tsx        | 46 +++++++++++-----------
 3 files changed, 46 insertions(+), 34 deletions(-)

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

diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index 04ec51218..dbda46dbc 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -4,13 +4,15 @@ import "normalize.css";
 import * as React from 'react';
 import "./PreviewCursor.scss";
 import { Docs } from '../documents/Documents';
-import { Transform } from 'prosemirror-transform';
+// import { Transform } from 'prosemirror-transform';
 import { Doc } from '../../new_fields/Doc';
+import { Transform } from "../util/Transform";
 
 @observer
 export class PreviewCursor extends React.Component<{}> {
     private _prompt = React.createRef<HTMLDivElement>();
     static _onKeyPress?: (e: KeyboardEvent) => void;
+    static _getTransform: () => Transform;
     static _addLiveTextDoc: (doc: Doc) => void;
     @observable static _clickPoint = [0, 0];
     @observable public static Visible = false;
@@ -26,22 +28,28 @@ export class PreviewCursor extends React.Component<{}> {
     }
 
     paste = (e: ClipboardEvent) => {
-        console.log("pasting")
         if (PreviewCursor.Visible) {
-            console.log("preview is visible")
             if (e.clipboardData) {
-                //what needs to be done with this?
-                console.log(e.clipboardData.getData("text/html"));
+                //keeping these just to hold onto types of pastes
+                //what needs to be done with html?
+                // console.log(e.clipboardData.getData("text/html"));
                 // console.log(e.clipboardData.getData("text/csv"));
-                console.log(e.clipboardData.getData("text/plain"));
-                console.log(e.clipboardData.getData("image/png"));
-                console.log(e.clipboardData.getData("image/jpg"));
-                console.log(e.clipboardData.getData("image/jpeg"));
+                // console.log(e.clipboardData.getData("text/plain"));
+                // console.log(e.clipboardData.getData("image/png"));
+                // console.log(e.clipboardData.getData("image/jpg"));
+                // console.log(e.clipboardData.getData("image/jpeg"));
 
                 // pasting in text
                 if (e.clipboardData.getData("text/plain") !== "") {
                     let text = e.clipboardData.getData("text/plain");
-                    let newBox = Docs.Create.TextDocument({ width: 200, height: 100, x: PreviewCursor._clickPoint[0], y: PreviewCursor._clickPoint[1], title: "-typed text-" });
+                    let newPoint = PreviewCursor._getTransform().transformPoint(PreviewCursor._clickPoint[0], PreviewCursor._clickPoint[1]);
+                    let newBox = Docs.Create.TextDocument({
+                        width: 200, height: 100,
+                        x: newPoint[0],
+                        y: newPoint[1],
+                        title: "-typed text-"
+                    });
+
                     newBox.proto!.autoHeight = true;
                     PreviewCursor._addLiveTextDoc(newBox);
                 }
@@ -69,10 +77,11 @@ export class PreviewCursor extends React.Component<{}> {
         }
     }
     @action
-    public static Show(x: number, y: number, onKeyPress: (e: KeyboardEvent) => void, addLiveText: (doc: Doc) => void) {
+    public static Show(x: number, y: number, onKeyPress: (e: KeyboardEvent) => void, addLiveText: (doc: Doc) => void, getTransform: () => Transform) {
         this._clickPoint = [x, y];
         this._onKeyPress = onKeyPress;
         this._addLiveTextDoc = addLiveText;
+        this._getTransform = getTransform;
         setTimeout(action(() => this.Visible = true), (1));
     }
     render() {
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 08d2a8adf..72444983c 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -203,7 +203,8 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
     onClick = (e: React.MouseEvent): void => {
         if (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD &&
             Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
-            PreviewCursor.Show(e.clientX, e.clientY, this.onKeyPress, this.props.addLiveTextDocument);
+            //this is probably the wrong transform
+            PreviewCursor.Show(e.clientX, e.clientY, this.onKeyPress, this.props.addLiveTextDocument, this.props.getTransform);
             // let the DocumentView stopPropagation of this event when it selects this document
         } else {  // why do we get a click event when the cursor have moved a big distance?
             // let's cut it off here so no one else has to deal with it.
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index a304cb297..25f611f19 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -134,28 +134,31 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
 
 
     paste = (e: ClipboardEvent) => {
+        //this is throwing a ton of erros so i had to comment it out
         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);
-                if (item.type === "text/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();
-                        }
-                    });
-                }
-            }
+            // 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();
+            //             }
+            //         });
+            //     }
+            // }
         }
     }
 
@@ -167,7 +170,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             FormattedTextBox._toolTipTextMenu && (FormattedTextBox._toolTipTextMenu.HackToFixTextSelectionGlitch = false);
             if (state.selection.empty && FormattedTextBox._toolTipTextMenu) {
                 const marks = tx.storedMarks;
-                console.log(marks)
                 if (marks) { FormattedTextBox._toolTipTextMenu.mark_key_pressed(marks); }
             }
             this._applyingChange = true;
-- 
cgit v1.2.3-70-g09d2


From 7ea6b44b10e1bf23287ba33e0081cacbfc595780 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Mon, 19 Aug 2019 14:58:02 -0400
Subject: seemed to fix syncing with remote google doc

---
 src/client/views/DocumentDecorations.tsx    | 14 ++++++++
 src/client/views/nodes/FormattedTextBox.tsx | 41 ++++++++++++++---------
 src/new_fields/RichTextField.ts             | 52 +++++++++++++----------------
 3 files changed, 63 insertions(+), 44 deletions(-)

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

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 6616d5d58..963722fe3 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -1,5 +1,6 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
 import { faLink, faTag } from '@fortawesome/free-solid-svg-icons';
+import * as fa from '@fortawesome/free-brands-svg-icons';
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { action, computed, observable, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
@@ -36,6 +37,7 @@ export const Flyout = higflyout.default;
 
 library.add(faLink);
 library.add(faTag);
+library.add(fa.faGoogleDrive as any);
 
 @observer
 export class DocumentDecorations extends React.Component<{}, { value: string }> {
@@ -618,6 +620,17 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         );
     }
 
+    considerGoogleDoc = () => {
+        let thisDoc = SelectionManager.SelectedDocuments()[0].props.Document;
+        let canEmbed = thisDoc.data && thisDoc.data instanceof RichTextField;
+        if (!canEmbed) return (null);
+        return (
+            <div className={"linkButtonWrapper"}>
+                <FontAwesomeIcon className="documentdecorations-icon" icon="image" size="sm" />
+            </div>
+        );
+    }
+
     considerTooltip = () => {
         let thisDoc = SelectionManager.SelectedDocuments()[0].props.Document;
         let isTextDoc = thisDoc.data && thisDoc.data instanceof RichTextField;
@@ -768,6 +781,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                     </div>
                     {this.metadataMenu}
                     {this.considerEmbed()}
+                    {this.considerGoogleDoc()}
                     {/* {this.considerTooltip()} */}
                 </div>
             </div >
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index ad29ce775..bc057bb5f 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -60,7 +60,7 @@ const richTextSchema = createSchema({
     documentText: "string"
 });
 
-const googleDocKey = "googleDocId";
+const googleDocId = "googleDocId";
 
 type RichTextDocument = makeInterface<[typeof richTextSchema]>;
 const RichTextDocument = makeInterface(richTextSchema);
@@ -84,6 +84,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     private _proxyReactionDisposer: Opt<IReactionDisposer>;
     private dropDisposer?: DragManager.DragDropDisposer;
     public get CurrentDiv(): HTMLDivElement { return this._ref.current!; }
+    private isOpening = false;
     @observable _entered = false;
 
     @observable public static InputBoxOverlay?: FormattedTextBox = undefined;
@@ -295,17 +296,19 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
 
     pollExportedCounterpart = async () => {
         let dataDoc = Doc.GetProto(this.props.Document);
-        let documentId = StrCast(dataDoc[googleDocKey]);
+        let documentId = StrCast(dataDoc[googleDocId]);
         if (documentId) {
             let exportState = await GoogleApiClientUtils.Docs.read({ documentId });
             if (exportState) {
                 let data = Cast(dataDoc.data, RichTextField);
                 if (data) {
+                    this.isOpening = true;
                     dataDoc.data = new RichTextField(data[FromGoogleDocText](exportState));
                 }
             } else {
-                delete dataDoc[googleDocKey];
+                delete dataDoc[googleDocId];
             }
+            this.tryUpdateHeight();
         }
     }
 
@@ -346,9 +349,18 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 const field = this.dataDoc ? Cast(this.dataDoc[this.props.fieldKey], RichTextField) : undefined;
                 return field ? field.Data : `{"doc":{"type":"doc","content":[]},"selection":{"type":"text","anchor":0,"head":0}}`;
             },
-            field2 => {
-                this._editorView && !this._applyingChange &&
-                    this._editorView.updateState(EditorState.fromJSON(config, JSON.parse(field2)));
+            incomingValue => {
+                if (this._editorView && !this._applyingChange) {
+                    let updatedState = JSON.parse(incomingValue);
+                    this._editorView.updateState(EditorState.fromJSON(config, updatedState));
+                    // manually sets cursor selection at the end of the text on focus
+                    if (this.isOpening) {
+                        this.isOpening = false;
+                        let end = this._editorView.state.doc.content.size - 1;
+                        updatedState.selection = { type: "text", anchor: end, head: end };
+                        this._editorView.updateState(EditorState.fromJSON(config, updatedState));
+                    }
+                }
             }
         );
 
@@ -686,7 +698,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             event: action(() => Doc.GetProto(this.props.Document).autoHeight = !BoolCast(this.props.Document.autoHeight)), icon: "expand-arrows-alt"
         });
         ContextMenu.Instance.addItem({ description: "Text Funcs...", subitems: subitems, icon: "text-height" });
-        if (!(googleDocKey in Doc.GetProto(this.props.Document))) {
+        if (!(googleDocId in Doc.GetProto(this.props.Document))) {
             ContextMenu.Instance.addItem({
                 description: "Export to Google Doc...",
                 event: this.updateGoogleDoc,
@@ -698,21 +710,18 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     updateGoogleDoc = () => {
         let modes = GoogleApiClientUtils.Docs.WriteMode;
         let mode = modes.Replace;
-        let reference: Opt<GoogleApiClientUtils.Docs.Reference> = Cast(this.dataDoc[googleDocKey], "string");
+        let reference: Opt<GoogleApiClientUtils.Docs.Reference> = Cast(this.dataDoc[googleDocId], "string");
         if (!reference) {
             mode = modes.Insert;
             reference = {
                 title: StrCast(this.dataDoc.title),
-                handler: id => this.dataDoc[googleDocKey] = id
+                handler: id => this.dataDoc[googleDocId] = id
             };
         }
-        const data = Cast(this.dataDoc.data, RichTextField);
-        if (data) {
-            GoogleApiClientUtils.Docs.write({
-                mode,
-                content: data[ToGoogleDocText](),
-                reference
-            });
+        if (this._editorView) {
+            let data = Cast(this.dataDoc.data, RichTextField);
+            let content = data ? data[ToGoogleDocText]() : this._editorView.state.doc.textContent;
+            GoogleApiClientUtils.Docs.write({ reference, content, mode });
         }
     }
 
diff --git a/src/new_fields/RichTextField.ts b/src/new_fields/RichTextField.ts
index 8963682c3..ec08293e9 100644
--- a/src/new_fields/RichTextField.ts
+++ b/src/new_fields/RichTextField.ts
@@ -6,6 +6,8 @@ import { scriptingGlobal } from "../client/util/Scripting";
 
 export const ToGoogleDocText = Symbol("PlainText");
 export const FromGoogleDocText = Symbol("PlainText");
+const delimiter = "\n";
+const joiner = "";
 
 @scriptingGlobal
 @Deserializable("RichTextField")
@@ -27,45 +29,39 @@ export class RichTextField extends ObjectField {
     }
 
     [ToGoogleDocText]() {
-        let state = JSON.parse(this.Data);
-        let text = state.doc.textBetween(0, state.doc.content.size, "\n\n");
-        console.log(text);
+        // Because we're working with plain text, just concatenate all paragraphs
         let content = JSON.parse(this.Data).doc.content;
         let paragraphs = content.filter((item: any) => item.type === "paragraph");
-        let output = "";
-        for (let i = 0; i < paragraphs.length; i++) {
-            let paragraph = paragraphs[i];
-            let addNewLine = i > 0 ? paragraphs[i - 1].content : false;
-            if (paragraph.content) {
-                output += paragraph.content.map((block: any) => block.text).join("");
-            } else {
-                output += i === 0 ? "" : "\n";
-            }
-            addNewLine && (output += "\n");
-        }
-        return output;
+
+        // Functions to flatten ProseMirror paragraph objects (and their components) to plain text
+        // While this function already exists in state.doc.textBeteen(), it doesn't account for newlines 
+        let blockText = (block: any) => block.text;
+        let concatenateParagraph = (p: any) => (p.content ? p.content.map(blockText).join(joiner) : "") + delimiter;
+
+        // Concatentate paragraphs and string the result together. Trim the last newline, an artifact.
+        let textParagraphs = paragraphs.map(concatenateParagraph);
+        return textParagraphs.join(joiner).trimEnd(delimiter);
     }
 
     [FromGoogleDocText](plainText: string) {
-        let elements = plainText.split("\n");
+        // Remap the text, creating blocks split on newlines
+        let elements = plainText.split(delimiter);
+
+        // Google Docs adds in an extra carriage return automatically, so this counteracts it
         !elements[elements.length - 1].length && elements.pop();
+
+        // Preserve the current state, but re-write the content to be the blocks
         let parsed = JSON.parse(this.Data);
         parsed.doc.content = elements.map(text => {
             let paragraph: any = { type: "paragraph" };
-            if (text.length) {
-                paragraph.content = [{
-                    type: "text",
-                    marks: [],
-                    text
-                }];
-            }
+            text.length && (paragraph.content = [{ type: "text", marks: [], text }]); // An empty paragraph gets treated as a line break
             return paragraph;
         });
-        parsed.selection = {
-            type: "text",
-            anchor: 1,
-            head: 1
-        };
+
+        // If the new content is shorter than the previous content and selection is unchanged, may throw an out of bounds exception, so we reset it
+        parsed.selection = { type: "text", anchor: 1, head: 1 };
+
+        // Export the ProseMirror-compatible state object we've jsut built
         return JSON.stringify(parsed);
     }
 
-- 
cgit v1.2.3-70-g09d2


From df11d1afdb271b37618ab9e1d362b2f4a1145982 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Mon, 19 Aug 2019 17:48:57 -0400
Subject: cleanup, pull push

---
 .../apis/google_docs/GoogleApiClientUtils.ts       | 27 +++++---
 src/client/views/DocumentDecorations.tsx           | 32 +++++++---
 src/client/views/collections/CollectionSubView.tsx | 14 +++-
 src/client/views/nodes/FormattedTextBox.tsx        | 74 +++++++++++++---------
 4 files changed, 98 insertions(+), 49 deletions(-)

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

diff --git a/src/client/apis/google_docs/GoogleApiClientUtils.ts b/src/client/apis/google_docs/GoogleApiClientUtils.ts
index 9d78b632d..55b4a76f8 100644
--- a/src/client/apis/google_docs/GoogleApiClientUtils.ts
+++ b/src/client/apis/google_docs/GoogleApiClientUtils.ts
@@ -27,8 +27,8 @@ export namespace GoogleApiClientUtils {
         export type CreationResult = Opt<DocumentId>;
         export type RetrievalResult = Opt<docs_v1.Schema$Document>;
         export type UpdateResult = Opt<docs_v1.Schema$BatchUpdateDocumentResponse>;
-        export type ReadLinesResult = Opt<string[]>;
-        export type ReadResult = Opt<string>;
+        export type ReadLinesResult = Opt<{ title?: string, bodyLines?: string[] }>;
+        export type ReadResult = { title?: string, body?: string };
 
         export interface CreateOptions {
             handler: IdHandler; // callback to process the documentId of the newly created Google Doc
@@ -148,18 +148,27 @@ export namespace GoogleApiClientUtils {
         };
 
         export const read = async (options: ReadOptions): Promise<ReadResult> => {
-            return retrieve(options).then(schema => {
-                return schema ? Utils.extractText(schema, options.removeNewlines) : undefined;
+            return retrieve(options).then(document => {
+                let result: ReadResult = {};
+                if (document) {
+                    let title = document.title;
+                    let body = Utils.extractText(document, options.removeNewlines);
+                    result = { title, body };
+                }
+                return result;
             });
         };
 
         export const readLines = async (options: ReadOptions): Promise<ReadLinesResult> => {
-            return retrieve(options).then(schema => {
-                if (!schema) {
-                    return undefined;
+            return retrieve(options).then(document => {
+                let result: ReadLinesResult = {};
+                if (document) {
+                    let title = document.title;
+                    let bodyLines = Utils.extractText(document).split("\n");
+                    options.removeNewlines && (bodyLines = bodyLines.filter(line => line.length));
+                    result = { title, bodyLines };
                 }
-                const lines = Utils.extractText(schema).split("\n");
-                return options.removeNewlines ? lines.filter(line => line.length) : lines;
+                return result;
             });
         };
 
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 963722fe3..80d4ecb9b 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -1,6 +1,5 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
-import { faLink, faTag } from '@fortawesome/free-solid-svg-icons';
-import * as fa from '@fortawesome/free-brands-svg-icons';
+import { faLink, faTag, faArrowAltCircleDown, faArrowAltCircleUp } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { action, computed, observable, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
@@ -37,7 +36,8 @@ export const Flyout = higflyout.default;
 
 library.add(faLink);
 library.add(faTag);
-library.add(fa.faGoogleDrive as any);
+library.add(faArrowAltCircleDown);
+library.add(faArrowAltCircleUp);
 
 @observer
 export class DocumentDecorations extends React.Component<{}, { value: string }> {
@@ -620,13 +620,28 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         );
     }
 
-    considerGoogleDoc = () => {
+    considerGoogleDocsPush = () => {
         let thisDoc = SelectionManager.SelectedDocuments()[0].props.Document;
-        let canEmbed = thisDoc.data && thisDoc.data instanceof RichTextField;
-        if (!canEmbed) return (null);
+        let canPush = thisDoc.data && thisDoc.data instanceof RichTextField;
+        if (!canPush) return (null);
+        return (
+            <div className={"linkButtonWrapper"}>
+                <div title="Push to Google Docs" className="linkButton-linker" onClick={() => thisDoc.pushToGoogleDocsTrigger = !thisDoc.pushToGoogleDocsTrigger}>
+                    <FontAwesomeIcon className="documentdecorations-icon" icon="arrow-alt-circle-up" size="sm" />
+                </div>
+            </div>
+        );
+    }
+
+    considerGoogleDocsPull = () => {
+        let thisDoc = SelectionManager.SelectedDocuments()[0].props.Document;
+        let canPull = thisDoc.data && thisDoc.data instanceof RichTextField;
+        if (!canPull) return (null);
         return (
             <div className={"linkButtonWrapper"}>
-                <FontAwesomeIcon className="documentdecorations-icon" icon="image" size="sm" />
+                <div title="Pull From Google Docs" className="linkButton-linker" onClick={() => thisDoc.pullFromGoogleDocsTrigger = !thisDoc.pullFromGoogleDocsTrigger}>
+                    <FontAwesomeIcon className="documentdecorations-icon" icon="arrow-alt-circle-down" size="sm" />
+                </div>
             </div>
         );
     }
@@ -781,7 +796,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                     </div>
                     {this.metadataMenu}
                     {this.considerEmbed()}
-                    {this.considerGoogleDoc()}
+                    {this.considerGoogleDocsPush()}
+                    {this.considerGoogleDocsPull()}
                     {/* {this.considerTooltip()} */}
                 </div>
             </div >
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 077f3f941..70c0632d1 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -16,7 +16,7 @@ import { DragManager } from "../../util/DragManager";
 import { undoBatch, UndoManager } from "../../util/UndoManager";
 import { DocComponent } from "../DocComponent";
 import { FieldViewProps } from "../nodes/FieldView";
-import { FormattedTextBox } from "../nodes/FormattedTextBox";
+import { FormattedTextBox, Blank } from "../nodes/FormattedTextBox";
 import { CollectionPDFView } from "./CollectionPDFView";
 import { CollectionVideoView } from "./CollectionVideoView";
 import { CollectionView } from "./CollectionView";
@@ -206,7 +206,17 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
                 this.props.addDocument(Docs.Create.VideoDocument(url, { ...options, title: url, width: 400, height: 315, nativeWidth: 600, nativeHeight: 472.5 }));
                 return;
             }
-
+            let matches: RegExpExecArray | null;
+            if ((matches = /(https:\/\/)?docs\.google\.com\/document\/d\/([^\\]+)\/edit/g.exec(text)) !== null) {
+                let newBox = Docs.Create.TextDocument({ ...options, width: 600, height: 400, title: "Fetching title from Google Docs..." });
+                let proto = newBox.proto!;
+                proto.autoHeight = true;
+                proto.googleDocId = matches[2];
+                proto.data = "Fetching contents from Google Docs...";
+                proto.backgroundColor = "#eeeeff";
+                this.props.addDocument(newBox);
+                return;
+            }
             let batch = UndoManager.StartBatch("collection view drop");
             let promises: Promise<void>[] = [];
             // tslint:disable-next-line:prefer-for-of
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index bc057bb5f..406c2c4a6 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -1,6 +1,6 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
 import { faEdit, faSmile, faTextHeight, faUpload } from '@fortawesome/free-solid-svg-icons';
-import { action, IReactionDisposer, observable, reaction, runInAction, computed, Lambda, trace } from "mobx";
+import { action, IReactionDisposer, observable, reaction, runInAction, computed, Lambda, trace, untracked } from "mobx";
 import { observer } from "mobx-react";
 import { baseKeymap } from "prosemirror-commands";
 import { history } from "prosemirror-history";
@@ -47,6 +47,8 @@ library.add(faSmile, faTextHeight, faUpload);
 // FormattedTextBox: Displays an editable plain text node that maps to a specified Key of a Document
 //
 
+export const Blank = `{"doc":{"type":"doc","content":[]},"selection":{"type":"text","anchor":0,"head":0}}`;
+
 export interface FormattedTextBoxProps {
     isOverlay?: boolean;
     hideOnLeave?: boolean;
@@ -84,7 +86,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     private _proxyReactionDisposer: Opt<IReactionDisposer>;
     private dropDisposer?: DragManager.DragDropDisposer;
     public get CurrentDiv(): HTMLDivElement { return this._ref.current!; }
-    private isOpening = false;
+    private isGoogleDocsUpdate = false;
     @observable _entered = false;
 
     @observable public static InputBoxOverlay?: FormattedTextBox = undefined;
@@ -290,28 +292,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         }
     }
 
-    componentWillMount() {
-        this.pollExportedCounterpart();
-    }
-
-    pollExportedCounterpart = async () => {
-        let dataDoc = Doc.GetProto(this.props.Document);
-        let documentId = StrCast(dataDoc[googleDocId]);
-        if (documentId) {
-            let exportState = await GoogleApiClientUtils.Docs.read({ documentId });
-            if (exportState) {
-                let data = Cast(dataDoc.data, RichTextField);
-                if (data) {
-                    this.isOpening = true;
-                    dataDoc.data = new RichTextField(data[FromGoogleDocText](exportState));
-                }
-            } else {
-                delete dataDoc[googleDocId];
-            }
-            this.tryUpdateHeight();
-        }
-    }
-
     componentDidMount() {
         const config = {
             schema,
@@ -347,23 +327,32 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         this._reactionDisposer = reaction(
             () => {
                 const field = this.dataDoc ? Cast(this.dataDoc[this.props.fieldKey], RichTextField) : undefined;
-                return field ? field.Data : `{"doc":{"type":"doc","content":[]},"selection":{"type":"text","anchor":0,"head":0}}`;
+                return field ? field.Data : Blank;
             },
             incomingValue => {
                 if (this._editorView && !this._applyingChange) {
                     let updatedState = JSON.parse(incomingValue);
                     this._editorView.updateState(EditorState.fromJSON(config, updatedState));
                     // manually sets cursor selection at the end of the text on focus
-                    if (this.isOpening) {
-                        this.isOpening = false;
+                    if (this.isGoogleDocsUpdate) {
+                        this.isGoogleDocsUpdate = false;
                         let end = this._editorView.state.doc.content.size - 1;
                         updatedState.selection = { type: "text", anchor: end, head: end };
                         this._editorView.updateState(EditorState.fromJSON(config, updatedState));
                     }
+                    this.tryUpdateHeight();
                 }
             }
         );
 
+        reaction(() => this.props.Document.pullFromGoogleDocsTrigger, () => {
+            this.pullFromGoogleDoc();
+        });
+
+        reaction(() => this.props.Document.pushToGoogleDocsTrigger, () => {
+            this.pushToGoogleDoc();
+        });
+
         this._textReactionDisposer = reaction(
             () => this.extensionDoc,
             () => {
@@ -391,6 +380,13 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 this.unhighlightSearchTerms();
             }
         }, { fireImmediately: true });
+
+        ["pushToGoogleDocsTrigger", "pullFromGoogleDocsTrigger"].map(key => {
+            let doc = this.props.Document;
+            if (doc[key] === undefined) {
+                untracked(() => doc[key] = false);
+            }
+        });
     }
 
     clipboardTextSerializer = (slice: Slice): string => {
@@ -644,7 +640,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             this._undoTyping.end();
             this._undoTyping = undefined;
         }
-        this.updateGoogleDoc();
     }
     public _undoTyping?: UndoManager.Batch;
     onKeyPress = (e: React.KeyboardEvent) => {
@@ -701,13 +696,13 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         if (!(googleDocId in Doc.GetProto(this.props.Document))) {
             ContextMenu.Instance.addItem({
                 description: "Export to Google Doc...",
-                event: this.updateGoogleDoc,
+                event: this.pushToGoogleDoc,
                 icon: "upload"
             });
         }
     }
 
-    updateGoogleDoc = () => {
+    pushToGoogleDoc = () => {
         let modes = GoogleApiClientUtils.Docs.WriteMode;
         let mode = modes.Replace;
         let reference: Opt<GoogleApiClientUtils.Docs.Reference> = Cast(this.dataDoc[googleDocId], "string");
@@ -725,6 +720,25 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         }
     }
 
+    pullFromGoogleDoc = async () => {
+        let dataDoc = Doc.GetProto(this.props.Document);
+        let documentId = StrCast(dataDoc[googleDocId]);
+        if (documentId) {
+            let exportState = await GoogleApiClientUtils.Docs.read({ documentId });
+            if (exportState && exportState.body && exportState.title) {
+                let data = Cast(dataDoc.data, RichTextField);
+                if (data) {
+                    this.isGoogleDocsUpdate = true;
+                    dataDoc.data = new RichTextField(data[FromGoogleDocText](exportState.body));
+                    dataDoc.title = exportState.title;
+                }
+            } else {
+                delete dataDoc[googleDocId];
+            }
+        }
+    }
+
+
     render() {
         let self = this;
         let style = this.props.isOverlay ? "scroll" : "hidden";
-- 
cgit v1.2.3-70-g09d2


From 00cecb10d80bad8d717cd06ddc4bf156664b9f2d Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Mon, 19 Aug 2019 22:39:56 -0400
Subject: fixed push pull UI

---
 .../apis/google_docs/GoogleApiClientUtils.ts       |   3 +
 src/client/views/DocumentDecorations.tsx           |  14 ++-
 src/client/views/nodes/FormattedTextBox.tsx        | 122 +++++++++++----------
 src/new_fields/RichTextField.ts                    |   8 +-
 4 files changed, 81 insertions(+), 66 deletions(-)

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

diff --git a/src/client/apis/google_docs/GoogleApiClientUtils.ts b/src/client/apis/google_docs/GoogleApiClientUtils.ts
index 55b4a76f8..821c52270 100644
--- a/src/client/apis/google_docs/GoogleApiClientUtils.ts
+++ b/src/client/apis/google_docs/GoogleApiClientUtils.ts
@@ -4,6 +4,9 @@ import { RouteStore } from "../../../server/RouteStore";
 import { Opt } from "../../../new_fields/Doc";
 import { isArray } from "util";
 
+export const Pulls = "googleDocsPullCount";
+export const Pushes = "googleDocsPushCount";
+
 export namespace GoogleApiClientUtils {
 
     export namespace Docs {
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 80d4ecb9b..797b43add 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -30,6 +30,7 @@ import { ObjectField } from '../../new_fields/ObjectField';
 import { MetadataEntryMenu } from './MetadataEntryMenu';
 import { ImageBox } from './nodes/ImageBox';
 import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils';
+import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils';
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -626,7 +627,10 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         if (!canPush) return (null);
         return (
             <div className={"linkButtonWrapper"}>
-                <div title="Push to Google Docs" className="linkButton-linker" onClick={() => thisDoc.pushToGoogleDocsTrigger = !thisDoc.pushToGoogleDocsTrigger}>
+                <div title="Push to Google Docs" className="linkButton-linker" onClick={() => {
+                    DocumentDecorations.hasPushedHack = false;
+                    thisDoc[Pushes] = NumCast(thisDoc[Pushes]) + 1;
+                }}>
                     <FontAwesomeIcon className="documentdecorations-icon" icon="arrow-alt-circle-up" size="sm" />
                 </div>
             </div>
@@ -639,13 +643,19 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         if (!canPull) return (null);
         return (
             <div className={"linkButtonWrapper"}>
-                <div title="Pull From Google Docs" className="linkButton-linker" onClick={() => thisDoc.pullFromGoogleDocsTrigger = !thisDoc.pullFromGoogleDocsTrigger}>
+                <div title="Pull From Google Docs" className="linkButton-linker" onClick={() => {
+                    DocumentDecorations.hasPulledHack = false;
+                    thisDoc[Pulls] = NumCast(thisDoc[Pulls]) + 1;
+                }}>
                     <FontAwesomeIcon className="documentdecorations-icon" icon="arrow-alt-circle-down" size="sm" />
                 </div>
             </div>
         );
     }
 
+    public static hasPushedHack = false;
+    public static hasPulledHack = false;
+
     considerTooltip = () => {
         let thisDoc = SelectionManager.SelectedDocuments()[0].props.Document;
         let isTextDoc = thisDoc.data && thisDoc.data instanceof RichTextField;
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 406c2c4a6..d2eb71350 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -12,7 +12,7 @@ import { EditorView } from "prosemirror-view";
 import { Doc, Opt, DocListCast } from "../../../new_fields/Doc";
 import { Id, Copy } from '../../../new_fields/FieldSymbols';
 import { List } from '../../../new_fields/List';
-import { RichTextField, ToGoogleDocText, FromGoogleDocText } from "../../../new_fields/RichTextField";
+import { RichTextField, ToPlainText, FromPlainText } from "../../../new_fields/RichTextField";
 import { createSchema, listSpec, makeInterface } from "../../../new_fields/Schema";
 import { BoolCast, Cast, NumCast, StrCast, DateCast } from "../../../new_fields/Types";
 import { DocServer } from "../../DocServer";
@@ -30,16 +30,14 @@ import { ContextMenu } from "../../views/ContextMenu";
 import { ContextMenuProps } from '../ContextMenuItem';
 import { DocComponent } from "../DocComponent";
 import { InkingControl } from "../InkingControl";
-import { Templates } from '../Templates';
 import { FieldView, FieldViewProps } from "./FieldView";
 import "./FormattedTextBox.scss";
 import React = require("react");
-import { For } from 'babel-types';
 import { DateField } from '../../../new_fields/DateField';
 import { Utils } from '../../../Utils';
 import { MainOverlayTextBox } from '../MainOverlayTextBox';
-import { GoogleApiClientUtils } from '../../apis/google_docs/GoogleApiClientUtils';
-import * as diff from "diff";
+import { GoogleApiClientUtils, Pulls, Pushes } from '../../apis/google_docs/GoogleApiClientUtils';
+import { DocumentDecorations } from '../DocumentDecorations';
 
 library.add(faEdit);
 library.add(faSmile, faTextHeight, faUpload);
@@ -84,6 +82,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     private _searchReactionDisposer?: Lambda;
     private _textReactionDisposer: Opt<IReactionDisposer>;
     private _proxyReactionDisposer: Opt<IReactionDisposer>;
+    private pullReactionDisposer: Opt<IReactionDisposer>;
+    private pushReactionDisposer: Opt<IReactionDisposer>;
     private dropDisposer?: DragManager.DragDropDisposer;
     public get CurrentDiv(): HTMLDivElement { return this._ref.current!; }
     private isGoogleDocsUpdate = false;
@@ -345,13 +345,25 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             }
         );
 
-        reaction(() => this.props.Document.pullFromGoogleDocsTrigger, () => {
-            this.pullFromGoogleDoc();
-        });
+        this.pullReactionDisposer = reaction(
+            () => this.props.Document[Pulls],
+            () => {
+                if (!DocumentDecorations.hasPulledHack) {
+                    DocumentDecorations.hasPulledHack = true;
+                    this.pullFromGoogleDoc();
+                }
+            }
+        );
 
-        reaction(() => this.props.Document.pushToGoogleDocsTrigger, () => {
-            this.pushToGoogleDoc();
-        });
+        this.pushReactionDisposer = reaction(
+            () => this.props.Document[Pushes],
+            () => {
+                if (!DocumentDecorations.hasPushedHack) {
+                    DocumentDecorations.hasPushedHack = true;
+                    this.pushToGoogleDoc();
+                }
+            }
+        );
 
         this._textReactionDisposer = reaction(
             () => this.extensionDoc,
@@ -380,13 +392,44 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 this.unhighlightSearchTerms();
             }
         }, { fireImmediately: true });
+    }
 
-        ["pushToGoogleDocsTrigger", "pullFromGoogleDocsTrigger"].map(key => {
-            let doc = this.props.Document;
-            if (doc[key] === undefined) {
-                untracked(() => doc[key] = false);
-            }
-        });
+    pushToGoogleDoc = () => {
+        let modes = GoogleApiClientUtils.Docs.WriteMode;
+        let mode = modes.Replace;
+        let reference: Opt<GoogleApiClientUtils.Docs.Reference> = Cast(this.dataDoc[googleDocId], "string");
+        if (!reference) {
+            mode = modes.Insert;
+            reference = {
+                title: StrCast(this.dataDoc.title),
+                handler: id => this.dataDoc[googleDocId] = id
+            };
+        }
+        if (this._editorView) {
+            let data = Cast(this.dataDoc.data, RichTextField);
+            let content = data ? data[ToPlainText]() : this._editorView.state.doc.textContent;
+            GoogleApiClientUtils.Docs.write({ reference, content, mode });
+        }
+    }
+
+    pullFromGoogleDoc = async () => {
+        let dataDoc = Doc.GetProto(this.props.Document);
+        let documentId = StrCast(dataDoc[googleDocId]);
+        if (documentId) {
+            let exportState = await GoogleApiClientUtils.Docs.read({ documentId });
+            UndoManager.RunInBatch(() => {
+                if (exportState && exportState.body && exportState.title) {
+                    let data = Cast(dataDoc.data, RichTextField);
+                    if (data) {
+                        this.isGoogleDocsUpdate = true;
+                        dataDoc.data = new RichTextField(data[FromPlainText](exportState.body));
+                        dataDoc.title = exportState.title;
+                    }
+                } else {
+                    delete dataDoc[googleDocId];
+                }
+            }, Pulls);
+        }
     }
 
     clipboardTextSerializer = (slice: Slice): string => {
@@ -516,6 +559,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         this._reactionDisposer && this._reactionDisposer();
         this._proxyReactionDisposer && this._proxyReactionDisposer();
         this._textReactionDisposer && this._textReactionDisposer();
+        this.pushReactionDisposer && this.pushReactionDisposer();
+        this.pullReactionDisposer && this.pullReactionDisposer();
     }
 
     onPointerDown = (e: React.PointerEvent): void => {
@@ -693,49 +738,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             event: action(() => Doc.GetProto(this.props.Document).autoHeight = !BoolCast(this.props.Document.autoHeight)), icon: "expand-arrows-alt"
         });
         ContextMenu.Instance.addItem({ description: "Text Funcs...", subitems: subitems, icon: "text-height" });
-        if (!(googleDocId in Doc.GetProto(this.props.Document))) {
-            ContextMenu.Instance.addItem({
-                description: "Export to Google Doc...",
-                event: this.pushToGoogleDoc,
-                icon: "upload"
-            });
-        }
-    }
-
-    pushToGoogleDoc = () => {
-        let modes = GoogleApiClientUtils.Docs.WriteMode;
-        let mode = modes.Replace;
-        let reference: Opt<GoogleApiClientUtils.Docs.Reference> = Cast(this.dataDoc[googleDocId], "string");
-        if (!reference) {
-            mode = modes.Insert;
-            reference = {
-                title: StrCast(this.dataDoc.title),
-                handler: id => this.dataDoc[googleDocId] = id
-            };
-        }
-        if (this._editorView) {
-            let data = Cast(this.dataDoc.data, RichTextField);
-            let content = data ? data[ToGoogleDocText]() : this._editorView.state.doc.textContent;
-            GoogleApiClientUtils.Docs.write({ reference, content, mode });
-        }
-    }
-
-    pullFromGoogleDoc = async () => {
-        let dataDoc = Doc.GetProto(this.props.Document);
-        let documentId = StrCast(dataDoc[googleDocId]);
-        if (documentId) {
-            let exportState = await GoogleApiClientUtils.Docs.read({ documentId });
-            if (exportState && exportState.body && exportState.title) {
-                let data = Cast(dataDoc.data, RichTextField);
-                if (data) {
-                    this.isGoogleDocsUpdate = true;
-                    dataDoc.data = new RichTextField(data[FromGoogleDocText](exportState.body));
-                    dataDoc.title = exportState.title;
-                }
-            } else {
-                delete dataDoc[googleDocId];
-            }
-        }
     }
 
 
diff --git a/src/new_fields/RichTextField.ts b/src/new_fields/RichTextField.ts
index ec08293e9..ab58329f9 100644
--- a/src/new_fields/RichTextField.ts
+++ b/src/new_fields/RichTextField.ts
@@ -4,8 +4,8 @@ import { Deserializable } from "../client/util/SerializationHelper";
 import { Copy, ToScriptString } from "./FieldSymbols";
 import { scriptingGlobal } from "../client/util/Scripting";
 
-export const ToGoogleDocText = Symbol("PlainText");
-export const FromGoogleDocText = Symbol("PlainText");
+export const ToPlainText = Symbol("PlainText");
+export const FromPlainText = Symbol("PlainText");
 const delimiter = "\n";
 const joiner = "";
 
@@ -28,7 +28,7 @@ export class RichTextField extends ObjectField {
         return `new RichTextField("${this.Data}")`;
     }
 
-    [ToGoogleDocText]() {
+    [ToPlainText]() {
         // Because we're working with plain text, just concatenate all paragraphs
         let content = JSON.parse(this.Data).doc.content;
         let paragraphs = content.filter((item: any) => item.type === "paragraph");
@@ -43,7 +43,7 @@ export class RichTextField extends ObjectField {
         return textParagraphs.join(joiner).trimEnd(delimiter);
     }
 
-    [FromGoogleDocText](plainText: string) {
+    [FromPlainText](plainText: string) {
         // Remap the text, creating blocks split on newlines
         let elements = plainText.split(delimiter);
 
-- 
cgit v1.2.3-70-g09d2


From c92761206957aebdba1cc2e77f6b662ae42db847 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Tue, 20 Aug 2019 01:44:18 -0400
Subject: interactive ui

---
 src/client/views/DocumentDecorations.tsx    | 84 +++++++++++++++++++++++------
 src/client/views/nodes/FormattedTextBox.tsx | 63 +++++++++++++++-------
 2 files changed, 112 insertions(+), 35 deletions(-)

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

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 797b43add..1db452b45 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -1,5 +1,5 @@
-import { library } from '@fortawesome/fontawesome-svg-core';
-import { faLink, faTag, faArrowAltCircleDown, faArrowAltCircleUp } from '@fortawesome/free-solid-svg-icons';
+import { library, IconProp } from '@fortawesome/fontawesome-svg-core';
+import { faLink, faTag, faArrowAltCircleDown, faArrowAltCircleUp, faCheckCircle, faStopCircle, faCloudUploadAlt } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { action, computed, observable, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
@@ -18,7 +18,7 @@ import { CollectionView } from "./collections/CollectionView";
 import './DocumentDecorations.scss';
 import { DocumentView, PositionDocument } from "./nodes/DocumentView";
 import { FieldView } from "./nodes/FieldView";
-import { FormattedTextBox } from "./nodes/FormattedTextBox";
+import { FormattedTextBox, GoogleRef } from "./nodes/FormattedTextBox";
 import { IconBox } from "./nodes/IconBox";
 import { LinkMenu } from "./nodes/LinkMenu";
 import { TemplateMenu } from "./TemplateMenu";
@@ -26,7 +26,6 @@ import { Template, Templates } from "./Templates";
 import React = require("react");
 import { RichTextField } from '../../new_fields/RichTextField';
 import { LinkManager } from '../util/LinkManager';
-import { ObjectField } from '../../new_fields/ObjectField';
 import { MetadataEntryMenu } from './MetadataEntryMenu';
 import { ImageBox } from './nodes/ImageBox';
 import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils';
@@ -39,6 +38,11 @@ library.add(faLink);
 library.add(faTag);
 library.add(faArrowAltCircleDown);
 library.add(faArrowAltCircleUp);
+library.add(faStopCircle);
+library.add(faCheckCircle);
+library.add(faCloudUploadAlt);
+
+const cloud: IconProp = "cloud-upload-alt";
 
 @observer
 export class DocumentDecorations extends React.Component<{}, { value: string }> {
@@ -69,6 +73,52 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
     @observable private _removeIcon = false;
     @observable public Interacting = false;
 
+    @observable public pushIcon: IconProp = "arrow-alt-circle-up";
+    @observable public pullIcon: IconProp = "arrow-alt-circle-down";
+    @observable public pullColor: string = "white";
+    public pullColorAnimating = false;
+
+    private pullAnimating = false;
+    private pushAnimating = false;
+
+    public startPullOutcome = action((success: boolean) => {
+        if (this.pullAnimating) {
+            return;
+        }
+        this.pullAnimating = true;
+        this.pullIcon = success ? "check-circle" : "stop-circle";
+        setTimeout(() => runInAction(() => {
+            this.pullIcon = "arrow-alt-circle-down";
+            this.pullAnimating = false;
+        }), 1000);
+    });
+
+    public startPushOutcome = action((success: boolean) => {
+        if (this.pushAnimating) {
+            return;
+        }
+        this.pushAnimating = true;
+        this.pushIcon = success ? "check-circle" : "stop-circle";
+        setTimeout(() => runInAction(() => {
+            this.pushIcon = "arrow-alt-circle-up";
+            this.pushAnimating = false;
+        }), 1000);
+    });
+
+    public setPullState = (unchanged: boolean) => {
+        if (this.pullColorAnimating) {
+            return;
+        }
+        this.pullColorAnimating = true;
+        this.pullColor = unchanged ? "lawngreen" : "red";
+        setTimeout(() => {
+            runInAction(() => {
+                this.pullColor = "white";
+                this.pullColorAnimating = false;
+            });
+        }, 2000);
+    }
+
     constructor(props: Readonly<{}>) {
         super(props);
         DocumentDecorations.Instance = this;
@@ -621,33 +671,37 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         );
     }
 
+    private get targetDoc() {
+        return SelectionManager.SelectedDocuments()[0].props.Document;
+    }
+
     considerGoogleDocsPush = () => {
-        let thisDoc = SelectionManager.SelectedDocuments()[0].props.Document;
-        let canPush = thisDoc.data && thisDoc.data instanceof RichTextField;
+        let canPush = this.targetDoc.data && this.targetDoc.data instanceof RichTextField;
         if (!canPush) return (null);
+        let published = Doc.GetProto(this.targetDoc)[GoogleRef] !== undefined;
+        let icon: IconProp = published ? (this.pushIcon as any) : (cloud as any);
         return (
             <div className={"linkButtonWrapper"}>
-                <div title="Push to Google Docs" className="linkButton-linker" onClick={() => {
+                <div title={`${published ? "Push" : "Publish"} to Google Docs`} className="linkButton-linker" onClick={() => {
                     DocumentDecorations.hasPushedHack = false;
-                    thisDoc[Pushes] = NumCast(thisDoc[Pushes]) + 1;
+                    this.targetDoc[Pushes] = NumCast(this.targetDoc[Pushes]) + 1;
                 }}>
-                    <FontAwesomeIcon className="documentdecorations-icon" icon="arrow-alt-circle-up" size="sm" />
+                    <FontAwesomeIcon className="documentdecorations-icon" icon={icon} size={published ? "sm" : "xs"} />
                 </div>
             </div>
         );
     }
 
     considerGoogleDocsPull = () => {
-        let thisDoc = SelectionManager.SelectedDocuments()[0].props.Document;
-        let canPull = thisDoc.data && thisDoc.data instanceof RichTextField;
-        if (!canPull) return (null);
+        let canPull = this.targetDoc.data && this.targetDoc.data instanceof RichTextField;
+        if (!canPull || !Doc.GetProto(this.targetDoc)[GoogleRef]) return (null);
         return (
             <div className={"linkButtonWrapper"}>
-                <div title="Pull From Google Docs" className="linkButton-linker" onClick={() => {
+                <div style={{ backgroundColor: this.pullColor, transition: "1s ease all" }} title="Pull From Google Docs" className="linkButton-linker" onClick={() => {
                     DocumentDecorations.hasPulledHack = false;
-                    thisDoc[Pulls] = NumCast(thisDoc[Pulls]) + 1;
+                    this.targetDoc[Pulls] = NumCast(this.targetDoc[Pulls]) + 1;
                 }}>
-                    <FontAwesomeIcon className="documentdecorations-icon" icon="arrow-alt-circle-down" size="sm" />
+                    <FontAwesomeIcon className="documentdecorations-icon" icon={this.pullIcon} size="sm" />
                 </div>
             </div>
         );
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index d2eb71350..33d222813 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -60,11 +60,13 @@ const richTextSchema = createSchema({
     documentText: "string"
 });
 
-const googleDocId = "googleDocId";
+export const GoogleRef = "googleDocId";
 
 type RichTextDocument = makeInterface<[typeof richTextSchema]>;
 const RichTextDocument = makeInterface(richTextSchema);
 
+type PullHandler = (exportState: GoogleApiClientUtils.Docs.ReadResult, dataDoc: Doc) => void;
+
 @observer
 export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTextBoxProps), RichTextDocument>(RichTextDocument) {
     public static LayoutString(fieldStr: string = "data") {
@@ -324,6 +326,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 }, { fireImmediately: true });
         }
 
+        this.pullFromGoogleDoc(this.checkState);
+
         this._reactionDisposer = reaction(
             () => {
                 const field = this.dataDoc ? Cast(this.dataDoc[this.props.fieldKey], RichTextField) : undefined;
@@ -350,7 +354,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             () => {
                 if (!DocumentDecorations.hasPulledHack) {
                     DocumentDecorations.hasPulledHack = true;
-                    this.pullFromGoogleDoc();
+                    this.pullFromGoogleDoc(this.updateState);
                 }
             }
         );
@@ -394,44 +398,63 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         }, { fireImmediately: true });
     }
 
-    pushToGoogleDoc = () => {
+    pushToGoogleDoc = async () => {
         let modes = GoogleApiClientUtils.Docs.WriteMode;
         let mode = modes.Replace;
-        let reference: Opt<GoogleApiClientUtils.Docs.Reference> = Cast(this.dataDoc[googleDocId], "string");
+        let reference: Opt<GoogleApiClientUtils.Docs.Reference> = Cast(this.dataDoc[GoogleRef], "string");
         if (!reference) {
             mode = modes.Insert;
             reference = {
                 title: StrCast(this.dataDoc.title),
-                handler: id => this.dataDoc[googleDocId] = id
+                handler: id => this.dataDoc[GoogleRef] = id
             };
         }
         if (this._editorView) {
             let data = Cast(this.dataDoc.data, RichTextField);
             let content = data ? data[ToPlainText]() : this._editorView.state.doc.textContent;
-            GoogleApiClientUtils.Docs.write({ reference, content, mode });
+            let response = await GoogleApiClientUtils.Docs.write({ reference, content, mode });
+            let pushSuccess = response !== undefined && !("errors" in response);
+            DocumentDecorations.Instance.startPushOutcome(pushSuccess);
         }
     }
 
-    pullFromGoogleDoc = async () => {
+    pullFromGoogleDoc = async (handler: PullHandler) => {
         let dataDoc = Doc.GetProto(this.props.Document);
-        let documentId = StrCast(dataDoc[googleDocId]);
+        let documentId = StrCast(dataDoc[GoogleRef]);
         if (documentId) {
             let exportState = await GoogleApiClientUtils.Docs.read({ documentId });
-            UndoManager.RunInBatch(() => {
-                if (exportState && exportState.body && exportState.title) {
-                    let data = Cast(dataDoc.data, RichTextField);
-                    if (data) {
-                        this.isGoogleDocsUpdate = true;
-                        dataDoc.data = new RichTextField(data[FromPlainText](exportState.body));
-                        dataDoc.title = exportState.title;
-                    }
-                } else {
-                    delete dataDoc[googleDocId];
-                }
-            }, Pulls);
+            UndoManager.RunInBatch(() => handler(exportState, dataDoc), Pulls);
         }
     }
 
+    updateState = (exportState: GoogleApiClientUtils.Docs.ReadResult, dataDoc: Doc) => {
+        let pullSuccess = false;
+        if (exportState !== undefined && exportState.body !== undefined && exportState.title !== undefined) {
+            let data = Cast(dataDoc.data, RichTextField);
+            if (data) {
+                pullSuccess = true;
+                this.isGoogleDocsUpdate = true;
+                dataDoc.data = new RichTextField(data[FromPlainText](exportState.body));
+                dataDoc.title = exportState.title;
+            }
+        } else {
+            delete dataDoc[GoogleRef];
+        }
+        DocumentDecorations.Instance.startPullOutcome(pullSuccess);
+    }
+
+    checkState = (exportState: GoogleApiClientUtils.Docs.ReadResult, dataDoc: Doc) => {
+        if (exportState !== undefined && exportState.body !== undefined && exportState.title !== undefined) {
+            let data = Cast(dataDoc.data, RichTextField);
+            if (data) {
+                let storedPlainText = data[ToPlainText]() + "\n";
+                let receivedPlainText = exportState.body;
+                DocumentDecorations.Instance.setPullState(storedPlainText === receivedPlainText);
+            }
+        }
+    }
+
+
     clipboardTextSerializer = (slice: Slice): string => {
         let text = "", separated = true;
         const from = 0, to = slice.content.size;
-- 
cgit v1.2.3-70-g09d2


From aa0e6f5ffc30fdffc3be13a1948981b754544a01 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Tue, 20 Aug 2019 12:03:50 -0400
Subject: automation of pull push button visibility

---
 src/client/views/DocumentDecorations.tsx    |  3 +-
 src/client/views/nodes/FormattedTextBox.tsx | 61 +++++++++++++++++++----------
 2 files changed, 42 insertions(+), 22 deletions(-)

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

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 1db452b45..05239073b 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -694,7 +694,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
 
     considerGoogleDocsPull = () => {
         let canPull = this.targetDoc.data && this.targetDoc.data instanceof RichTextField;
-        if (!canPull || !Doc.GetProto(this.targetDoc)[GoogleRef]) return (null);
+        let dataDoc = Doc.GetProto(this.targetDoc);
+        if (!canPull || !dataDoc[GoogleRef] || dataDoc.unchanged) return (null);
         return (
             <div className={"linkButtonWrapper"}>
                 <div style={{ backgroundColor: this.pullColor, transition: "1s ease all" }} title="Pull From Google Docs" className="linkButton-linker" onClick={() => {
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 33d222813..8a355a425 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -1,6 +1,6 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
 import { faEdit, faSmile, faTextHeight, faUpload } from '@fortawesome/free-solid-svg-icons';
-import { action, IReactionDisposer, observable, reaction, runInAction, computed, Lambda, trace, untracked } from "mobx";
+import { action, IReactionDisposer, observable, reaction, runInAction, computed, Lambda, trace, untracked, autorun } from "mobx";
 import { observer } from "mobx-react";
 import { baseKeymap } from "prosemirror-commands";
 import { history } from "prosemirror-history";
@@ -399,32 +399,46 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     }
 
     pushToGoogleDoc = async () => {
-        let modes = GoogleApiClientUtils.Docs.WriteMode;
-        let mode = modes.Replace;
-        let reference: Opt<GoogleApiClientUtils.Docs.Reference> = Cast(this.dataDoc[GoogleRef], "string");
-        if (!reference) {
-            mode = modes.Insert;
-            reference = {
-                title: StrCast(this.dataDoc.title),
-                handler: id => this.dataDoc[GoogleRef] = id
+        this.pullFromGoogleDoc(async (exportState: GoogleApiClientUtils.Docs.ReadResult, dataDoc: Doc) => {
+            let modes = GoogleApiClientUtils.Docs.WriteMode;
+            let mode = modes.Replace;
+            let reference: Opt<GoogleApiClientUtils.Docs.Reference> = Cast(this.dataDoc[GoogleRef], "string");
+            if (!reference) {
+                mode = modes.Insert;
+                reference = {
+                    title: StrCast(this.dataDoc.title),
+                    handler: id => this.dataDoc[GoogleRef] = id
+                };
+            }
+            let redo = async () => {
+                if (this._editorView && reference) {
+                    let data = Cast(this.dataDoc.data, RichTextField);
+                    let content = data ? data[ToPlainText]() : this._editorView.state.doc.textContent;
+                    let response = await GoogleApiClientUtils.Docs.write({ reference, content, mode });
+                    let pushSuccess = response !== undefined && !("errors" in response);
+                    dataDoc.unchanged = pushSuccess;
+                    DocumentDecorations.Instance.startPushOutcome(pushSuccess);
+                }
             };
-        }
-        if (this._editorView) {
-            let data = Cast(this.dataDoc.data, RichTextField);
-            let content = data ? data[ToPlainText]() : this._editorView.state.doc.textContent;
-            let response = await GoogleApiClientUtils.Docs.write({ reference, content, mode });
-            let pushSuccess = response !== undefined && !("errors" in response);
-            DocumentDecorations.Instance.startPushOutcome(pushSuccess);
-        }
+            let undo = () => {
+                let content = exportState.body;
+                if (reference && content) {
+                    GoogleApiClientUtils.Docs.write({ reference, content, mode });
+                }
+            };
+            UndoManager.AddEvent({ undo, redo });
+            redo();
+        });
     }
 
     pullFromGoogleDoc = async (handler: PullHandler) => {
-        let dataDoc = Doc.GetProto(this.props.Document);
+        let dataDoc = this.dataDoc;
         let documentId = StrCast(dataDoc[GoogleRef]);
+        let exportState: GoogleApiClientUtils.Docs.ReadResult = {};
         if (documentId) {
-            let exportState = await GoogleApiClientUtils.Docs.read({ documentId });
-            UndoManager.RunInBatch(() => handler(exportState, dataDoc), Pulls);
+            exportState = await GoogleApiClientUtils.Docs.read({ documentId });
         }
+        UndoManager.RunInBatch(() => handler(exportState, dataDoc), Pulls);
     }
 
     updateState = (exportState: GoogleApiClientUtils.Docs.ReadResult, dataDoc: Doc) => {
@@ -436,6 +450,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 this.isGoogleDocsUpdate = true;
                 dataDoc.data = new RichTextField(data[FromPlainText](exportState.body));
                 dataDoc.title = exportState.title;
+                dataDoc.unchanged = true;
             }
         } else {
             delete dataDoc[GoogleRef];
@@ -449,7 +464,11 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             if (data) {
                 let storedPlainText = data[ToPlainText]() + "\n";
                 let receivedPlainText = exportState.body;
-                DocumentDecorations.Instance.setPullState(storedPlainText === receivedPlainText);
+                let storedTitle = dataDoc.title;
+                let receivedTitle = exportState.title;
+                let unchanged = storedPlainText === receivedPlainText && storedTitle === receivedTitle;
+                dataDoc.unchanged = unchanged;
+                DocumentDecorations.Instance.setPullState(unchanged);
             }
         }
     }
-- 
cgit v1.2.3-70-g09d2


From 700dfc5add1ecd9c2b1ecafcdc593ff821b7a6a6 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Tue, 20 Aug 2019 17:20:27 -0400
Subject: UI enhancements and other backend fixes for templates.

---
 src/client/util/DragManager.ts                     |  3 +-
 src/client/views/MetadataEntryMenu.scss            | 12 +++++
 src/client/views/MetadataEntryMenu.tsx             | 52 +++++++++++---------
 .../views/collections/CollectionStackingView.tsx   |  3 +-
 .../CollectionStackingViewFieldColumn.tsx          |  9 ++--
 src/client/views/collections/CollectionSubView.tsx |  2 +-
 .../views/collections/CollectionTreeView.tsx       |  3 ++
 .../views/collections/CollectionViewChromes.scss   |  1 -
 .../views/collections/CollectionViewChromes.tsx    | 57 +++++++++++-----------
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 30 ++++++------
 src/client/views/nodes/DocumentContentsView.tsx    |  2 +-
 src/client/views/nodes/DocumentView.tsx            | 25 +++++++---
 src/client/views/nodes/FormattedTextBox.tsx        | 12 ++---
 src/client/views/nodes/ImageBox.tsx                |  7 +--
 src/new_fields/Doc.ts                              | 11 +++--
 15 files changed, 131 insertions(+), 98 deletions(-)

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

diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 0b6d9b5e5..894b366ef 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -9,8 +9,6 @@ import { DocumentManager } from "./DocumentManager";
 import { LinkManager } from "./LinkManager";
 import { SelectionManager } from "./SelectionManager";
 import { SchemaHeaderField } from "../../new_fields/SchemaHeaderField";
-import { DocumentDecorations } from "../views/DocumentDecorations";
-import { NumberLiteralType } from "typescript";
 
 export type dropActionType = "alias" | "copy" | undefined;
 export function SetupDrag(
@@ -211,6 +209,7 @@ export namespace DragManager {
         dropAction: dropActionType;
         userDropAction: dropActionType;
         moveDocument?: MoveFunction;
+        applyAsTemplate?: boolean;
         [id: string]: any;
     }
 
diff --git a/src/client/views/MetadataEntryMenu.scss b/src/client/views/MetadataEntryMenu.scss
index bcfc9a82d..e28c4d0e0 100644
--- a/src/client/views/MetadataEntryMenu.scss
+++ b/src/client/views/MetadataEntryMenu.scss
@@ -1,8 +1,20 @@
 .metadataEntry-outerDiv {
     display: flex;
+    flex-direction: column;
     width: 300px;
 }
 
+.metadataEntry-keys {
+    max-height: 80;
+    overflow-y: auto; 
+    display: flex;
+    flex-direction: column;
+}
+.metadataEntry-inputArea {
+    display:flex;
+    flex-direction: row;
+}
+
 .react-autosuggest__container {
     position: relative;
 }
diff --git a/src/client/views/MetadataEntryMenu.tsx b/src/client/views/MetadataEntryMenu.tsx
index 36c240dd8..4a45eede9 100644
--- a/src/client/views/MetadataEntryMenu.tsx
+++ b/src/client/views/MetadataEntryMenu.tsx
@@ -1,11 +1,12 @@
 import * as React from 'react';
 import "./MetadataEntryMenu.scss";
 import { observer } from 'mobx-react';
-import { observable, action, runInAction, trace } from 'mobx';
+import { observable, action, runInAction, trace, computed, IReactionDisposer, reaction } from 'mobx';
 import { KeyValueBox } from './nodes/KeyValueBox';
 import { Doc, Field } from '../../new_fields/Doc';
 import * as Autosuggest from 'react-autosuggest';
 import { undoBatch } from '../util/UndoManager';
+import { emptyFunction } from '../../Utils';
 
 export type DocLike = Doc | Doc[] | Promise<Doc> | Promise<Doc[]>;
 export interface MetadataEntryProps {
@@ -18,7 +19,8 @@ export interface MetadataEntryProps {
 export class MetadataEntryMenu extends React.Component<MetadataEntryProps>{
     @observable private _currentKey: string = "";
     @observable private _currentValue: string = "";
-    @observable private suggestions: string[] = [];
+    @observable _allSuggestions: string[] = [];
+    _suggestionDispser: IReactionDisposer | undefined;
     private userModified = false;
 
     private autosuggestRef = React.createRef<Autosuggest>();
@@ -140,35 +142,39 @@ export class MetadataEntryMenu extends React.Component<MetadataEntryProps>{
     getSuggestionValue = (suggestion: string) => suggestion;
 
     renderSuggestion = (suggestion: string) => {
-        return <p>{suggestion}</p>;
+        return (null);
     }
+    componentDidMount() {
 
-    onSuggestionFetch = async ({ value }: { value: string }) => {
-        const sugg = await this.getKeySuggestions(value);
-        runInAction(() => {
-            this.suggestions = sugg;
-        });
+        this._suggestionDispser = reaction(() => this._currentKey,
+            () => this.getKeySuggestions(this._currentKey).then(action((s: string[]) => this._allSuggestions = s)),
+            { fireImmediately: true });
     }
-
-    @action
-    onSuggestionClear = () => {
-        this.suggestions = [];
+    componentWillUnmount() {
+        this._suggestionDispser && this._suggestionDispser();
     }
 
     render() {
         return (
             <div className="metadataEntry-outerDiv">
-                Key:
-                <Autosuggest inputProps={{ value: this._currentKey, onChange: this.onKeyChange }}
-                    getSuggestionValue={this.getSuggestionValue}
-                    suggestions={this.suggestions}
-                    alwaysRenderSuggestions
-                    renderSuggestion={this.renderSuggestion}
-                    onSuggestionsFetchRequested={this.onSuggestionFetch}
-                    onSuggestionsClearRequested={this.onSuggestionClear}
-                    ref={this.autosuggestRef} />
-                Value:
-                <input className="metadataEntry-input" value={this._currentValue} onChange={this.onValueChange} onKeyDown={this.onValueKeyDown} />
+                <div className="metadataEntry-inputArea">
+                    Key:
+                    <Autosuggest inputProps={{ value: this._currentKey, onChange: this.onKeyChange }}
+                        getSuggestionValue={this.getSuggestionValue}
+                        suggestions={[]}
+                        alwaysRenderSuggestions={false}
+                        renderSuggestion={this.renderSuggestion}
+                        onSuggestionsFetchRequested={emptyFunction}
+                        onSuggestionsClearRequested={emptyFunction}
+                        ref={this.autosuggestRef} />
+                    Value:
+                    <input className="metadataEntry-input" value={this._currentValue} onChange={this.onValueChange} onKeyDown={this.onValueKeyDown} />
+                </div>
+                <div className="metadataEntry-keys" >
+                    <ul>
+                        {this._allSuggestions.map(s => <li key={s} onClick={action(() => { this._currentKey = s; this.previewValue(); })} >{s}</li>)}
+                    </ul>
+                </div>
             </div>
         );
     }
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 1f7ef0689..c74c60d8f 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -160,7 +160,8 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
             previewScript={undefined}>
         </CollectionSchemaPreview>;
     }
-    getDocHeight(d: Doc) {
+    getDocHeight(d?: Doc) {
+        if (!d) return 0;
         let nw = NumCast(d.nativeWidth);
         let nh = NumCast(d.nativeHeight);
         if (!d.ignoreAspect && nw && nh) {
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 74c7ef305..cc8476548 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -78,11 +78,14 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
         let parent = this.props.parent;
         parent._docXfs.length = 0;
         return docs.map((d, i) => {
-            let pair = Doc.GetLayoutDataDocPair(parent.props.Document, parent.props.DataDoc, parent.props.fieldKey, d);
+            const pair = Doc.GetLayoutDataDocPair(parent.props.Document, parent.props.DataDoc, parent.props.fieldKey, d);
+            if (!pair.layout || pair.data instanceof Promise) {
+                return (null);
+            }
             let width = () => Math.min(d.nativeWidth && !d.ignoreAspect && !parent.props.Document.fillColumn ? d[WidthSym]() : Number.MAX_VALUE, parent.columnWidth / parent.numGroupColumns);
-            let height = () => parent.getDocHeight(pair.layout);
+            let height = () => parent.getDocHeight(pair!.layout);
             let dref = React.createRef<HTMLDivElement>();
-            let dxf = () => this.getDocTransform(pair.layout, dref.current!);
+            let dxf = () => this.getDocTransform(pair!.layout, dref.current!);
             this.props.parent._docXfs.push({ dxf: dxf, width: width, height: height });
             let rowSpan = Math.ceil((height() + parent.gridGap) / parent.gridGap);
             let style = parent.isStackingView ? { width: width(), margin: "auto", marginTop: i === 0 ? 0 : parent.gridGap, height: height() } : { gridRowEnd: `span ${rowSpan}` };
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index cfe3fefb3..818e76619 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -113,7 +113,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
         @undoBatch
         @action
         protected drop(e: Event, de: DragManager.DropEvent): boolean {
-            if (de.data instanceof DragManager.DocumentDragData) {
+            if (de.data instanceof DragManager.DocumentDragData && !de.data.applyAsTemplate) {
                 if (de.mods === "AltKey" && de.data.draggedDocuments.length) {
                     this.childDocs.map(doc =>
                         Doc.ApplyTemplateTo(de.data.draggedDocuments[0], doc, undefined)
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index b6a14238e..ebd385743 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -443,6 +443,9 @@ class TreeView extends React.Component<TreeViewProps> {
         let rowWidth = () => panelWidth() - 20;
         return docs.map((child, i) => {
             let pair = Doc.GetLayoutDataDocPair(containingCollection, dataDoc, key, child);
+            if (!pair.layout || pair.data instanceof Promise) {
+                return (null);
+            }
 
             let indent = i === 0 ? undefined : () => {
                 if (StrCast(docs[i - 1].layout).indexOf("CollectionView") !== -1) {
diff --git a/src/client/views/collections/CollectionViewChromes.scss b/src/client/views/collections/CollectionViewChromes.scss
index 595f079d1..f39bd877a 100644
--- a/src/client/views/collections/CollectionViewChromes.scss
+++ b/src/client/views/collections/CollectionViewChromes.scss
@@ -7,7 +7,6 @@
     z-index: 9001;
     transition: top .5s;
     background: lightgrey;
-    padding: 10px;
 
     .collectionViewChrome {
         display: grid;
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index b92bcc7cb..25b152d4e 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -1,30 +1,25 @@
-import * as React from "react";
-import { CollectionView } from "./CollectionView";
-import "./CollectionViewChromes.scss";
-import { CollectionViewType } from "./CollectionBaseView";
-import { undoBatch } from "../../util/UndoManager";
-import { action, observable, runInAction, computed, IObservable, IObservableValue, reaction, autorun } from "mobx";
-import { observer } from "mobx-react";
-import { Doc, DocListCast, FieldResult } from "../../../new_fields/Doc";
-import { DocLike } from "../MetadataEntryMenu";
-import * as Autosuggest from 'react-autosuggest';
-import { EditableView } from "../EditableView";
-import { StrCast, NumCast, BoolCast, Cast } from "../../../new_fields/Types";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { action, computed, observable, runInAction } from "mobx";
+import { observer } from "mobx-react";
+import * as React from "react";
+import { Doc, DocListCast } from "../../../new_fields/Doc";
+import { Id } from "../../../new_fields/FieldSymbols";
+import { List } from "../../../new_fields/List";
+import { listSpec } from "../../../new_fields/Schema";
+import { ScriptField } from "../../../new_fields/ScriptField";
+import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types";
 import { Utils } from "../../../Utils";
-import KeyRestrictionRow from "./KeyRestrictionRow";
+import { DragManager } from "../../util/DragManager";
 import { CompileScript } from "../../util/Scripting";
-import { ScriptField } from "../../../new_fields/ScriptField";
-import { CollectionSchemaView } from "./CollectionSchemaView";
+import { undoBatch } from "../../util/UndoManager";
+import { EditableView } from "../EditableView";
 import { COLLECTION_BORDER_WIDTH } from "../globalCssVariables.scss";
-import { listSpec } from "../../../new_fields/Schema";
-import { List } from "../../../new_fields/List";
-import { Id } from "../../../new_fields/FieldSymbols";
-import { threadId } from "worker_threads";
-import { DragManager } from "../../util/DragManager";
+import { DocLike } from "../MetadataEntryMenu";
+import { CollectionViewType } from "./CollectionBaseView";
+import { CollectionView } from "./CollectionView";
+import "./CollectionViewChromes.scss";
+import KeyRestrictionRow from "./KeyRestrictionRow";
 const datepicker = require('js-datepicker');
-import * as $ from 'jquery';
-import { firebasedynamiclinks } from "googleapis/build/src/apis/firebasedynamiclinks";
 
 interface CollectionViewChromeProps {
     CollectionView: CollectionView;
@@ -51,7 +46,6 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
     @computed private get filterValue() { return Cast(this.props.CollectionView.props.Document.viewSpecScript, ScriptField); }
 
     private _picker: any;
-    private _datePickerElGuid = Utils.GenerateGuid();
 
     getFilters = (script: string) => {
         let re: any = /(!)?\(\(\(doc\.(\w+)\s+&&\s+\(doc\.\w+\s+as\s+\w+\)\.includes\(\"(\w+)\"\)/g;
@@ -91,11 +85,6 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
     }
 
     componentDidMount = () => {
-        setTimeout(() => this._picker = datepicker("#" + this._datePickerElGuid, {
-            disabler: (date: Date) => date > new Date(),
-            onSelect: (instance: any, date: Date) => runInAction(() => this._dateValue = date),
-            dateSelected: new Date()
-        }), 1000);
 
         let fields: Filter[] = [];
         if (this.filterValue) {
@@ -306,6 +295,15 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
         return true;
     }
 
+    datePickerRef = (node: HTMLInputElement) => {
+        if (node) {
+            this._picker = datepicker("#" + node.id, {
+                disabler: (date: Date) => date > new Date(),
+                onSelect: (instance: any, date: Date) => runInAction(() => this._dateValue = date),
+                dateSelected: new Date()
+            });
+        }
+    }
     render() {
         let collapsed = this.props.CollectionView.props.Document.chromeStatus !== "enabled";
         return (
@@ -366,7 +364,8 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
                                         <option value="1y">1 year of</option>
                                     </select>
                                     <input className="collectionViewBaseChrome-viewSpecsMenu-rowRight"
-                                        id={this._datePickerElGuid}
+                                        id={Utils.GenerateGuid()}
+                                        ref={this.datePickerRef}
                                         value={this._dateValue instanceof Date ? this._dateValue.toLocaleDateString() : this._dateValue}
                                         onChange={(e) => runInAction(() => this._dateValue = e.target.value)}
                                         onPointerDown={this.openDatePicker}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index d69ece421..3be6aa3d3 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -622,20 +622,19 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
     getScale = () => this.Document.scale ? this.Document.scale : 1;
 
-    getChildDocumentViewProps(childDocLayout: Doc): DocumentViewProps {
-        let pair = Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, childDocLayout);
+    getChildDocumentViewProps(childLayout: Doc, childData?: Doc): DocumentViewProps {
         return {
-            DataDoc: pair.data,
-            Document: pair.layout,
+            DataDoc: childData,
+            Document: childLayout,
             addDocument: this.props.addDocument,
             removeDocument: this.props.removeDocument,
             moveDocument: this.props.moveDocument,
             onClick: this.props.onClick,
-            ScreenToLocalTransform: pair.layout.z ? this.getTransformOverlay : this.getTransform,
+            ScreenToLocalTransform: childLayout.z ? this.getTransformOverlay : this.getTransform,
             renderDepth: this.props.renderDepth + 1,
-            selectOnLoad: pair.layout[Id] === this._selectOnLoaded,
-            PanelWidth: pair.layout[WidthSym],
-            PanelHeight: pair.layout[HeightSym],
+            selectOnLoad: childLayout[Id] === this._selectOnLoaded,
+            PanelWidth: childLayout[WidthSym],
+            PanelHeight: childLayout[HeightSym],
             ContentScaling: returnOne,
             ContainingCollectionView: this.props.CollectionView,
             focus: this.focusDocument,
@@ -745,12 +744,15 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                     const pos = script ? this.getCalculatedPositions(script, { doc, index: prev.length, collection: this.Document, docs, state }) :
                         { x: Cast(doc.x, "number"), y: Cast(doc.y, "number"), z: Cast(doc.z, "number"), width: Cast(doc.width, "number"), height: Cast(doc.height, "number") };
                     state = pos.state === undefined ? state : pos.state;
-                    prev.push({
-                        ele: <CollectionFreeFormDocumentView key={doc[Id]}
-                            x={script ? pos.x : undefined} y={script ? pos.y : undefined}
-                            width={script ? pos.width : undefined} height={script ? pos.height : undefined} {...this.getChildDocumentViewProps(doc)} />,
-                        bounds: (pos.x !== undefined && pos.y !== undefined) ? { x: pos.x, y: pos.y, z: pos.z, width: NumCast(pos.width), height: NumCast(pos.height) } : undefined
-                    });
+                    let pair = Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, doc);
+                    if (pair.layout && !(pair.data instanceof Promise)) {
+                        prev.push({
+                            ele: <CollectionFreeFormDocumentView key={doc[Id]}
+                                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: (pos.x !== undefined && pos.y !== undefined) ? { x: pos.x, y: pos.y, z: pos.z, width: NumCast(pos.width), height: NumCast(pos.height) } : undefined
+                        });
+                    }
                 }
             }
             return prev;
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index b901bdcfb..d77662355 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -68,7 +68,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
     }
 
     get dataDoc() {
-        if (this.props.DataDoc === undefined && this.props.Document.layout instanceof Doc) {
+        if (this.props.DataDoc === undefined && (this.props.Document.layout instanceof Doc || this.props.Document instanceof Promise)) {
             // if there is no dataDoc (ie, we're not rendering a template layout), but this document
             // has a template layout document, then we will render the template layout but use 
             // this document as the data document for the layout.
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 9e71094b0..fd53262d7 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -139,6 +139,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     private _lastTap: number = 0;
     private _doubleTap = false;
     private _hitExpander = false;
+    private _hitTemplateDrag = false;
     private _mainCont = React.createRef<HTMLDivElement>();
     private _dropDisposer?: DragManager.DragDropDisposer;
     _animateToIconDisposer?: IReactionDisposer;
@@ -228,7 +229,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     }
 
     get dataDoc() {
-        if (this.props.DataDoc === undefined && this.props.Document.layout instanceof Doc) {
+        if (this.props.DataDoc === undefined && (this.props.Document.layout instanceof Doc || this.props.Document instanceof Promise)) {
             // if there is no dataDoc (ie, we're not rendering a temlplate layout), but this document
             // has a template layout document, then we will render the template layout but use 
             // this document as the data document for the layout.
@@ -236,7 +237,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         }
         return this.props.DataDoc !== this.props.Document ? this.props.DataDoc : undefined;
     }
-    startDragging(x: number, y: number, dropAction: dropActionType, dragSubBullets: boolean) {
+    startDragging(x: number, y: number, dropAction: dropActionType, dragSubBullets: boolean, applyAsTemplate?: boolean) {
         if (this._mainCont.current) {
             let allConnected = [this.props.Document, ...(dragSubBullets ? DocListCast(this.props.Document.subBulletDocs) : [])];
             let alldataConnected = [this.dataDoc, ...(dragSubBullets ? DocListCast(this.props.Document.subBulletDocs) : [])];
@@ -247,6 +248,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             dragData.xOffset = xoff;
             dragData.yOffset = yoff;
             dragData.moveDocument = this.props.moveDocument;
+            dragData.applyAsTemplate = applyAsTemplate;
             DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, {
                 handlers: {
                     dragComplete: action(emptyFunction)
@@ -377,15 +379,19 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             }
         }
     }
+
+
     onPointerDown = (e: React.PointerEvent): void => {
         if (e.nativeEvent.cancelBubble) return;
         this._downX = e.clientX;
         this._downY = e.clientY;
         this._hitExpander = DocListCast(this.props.Document.subBulletDocs).length > 0;
-        // if (e.shiftKey && e.buttons === 1 && CollectionDockingView.Instance) {
-        // CollectionDockingView.Instance.StartOtherDrag(e, [Doc.MakeAlias(this.props.Document)], [this.dataDoc]);
-        // e.stopPropagation();
-        // } else {
+        this._hitTemplateDrag = false;
+        for (let element = (e.target as any); element && !this._hitTemplateDrag; element = element.parentElement) {
+            if (element.className && element.className.toString() === "collectionViewBaseChrome-collapse") {
+                this._hitTemplateDrag = true;
+            }
+        }
         if (this.active) e.stopPropagation(); // events stop at the lowest document that is active.  
         document.removeEventListener("pointermove", this.onPointerMove);
         document.addEventListener("pointermove", this.onPointerMove);
@@ -399,7 +405,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 if (!e.altKey && !this.topMost && e.buttons === 1 && !BoolCast(this.props.Document.lockedPosition)) {
                     document.removeEventListener("pointermove", this.onPointerMove);
                     document.removeEventListener("pointerup", this.onPointerUp);
-                    this.startDragging(this._downX, this._downY, e.ctrlKey || e.altKey ? "alias" : undefined, this._hitExpander);
+                    this.startDragging(this._downX, this._downY, e.ctrlKey || e.altKey ? "alias" : undefined, this._hitExpander, this._hitTemplateDrag);
                 }
             }
             e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document'  however it does mark the event as cancelBubble=true which we test for in the move event handlers
@@ -457,6 +463,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
             DocUtils.MakeLink(annotationDoc, targetDoc, this.props.ContainingCollectionView!.props.Document, `Link from ${StrCast(annotationDoc.title)}`);
         }
+        if (de.data instanceof DragManager.DocumentDragData && de.data.applyAsTemplate) {
+            Doc.ApplyTemplateTo(de.data.draggedDocuments[0], this.props.Document, this.props.DataDoc);
+            e.stopPropagation();
+        }
         if (de.data instanceof DragManager.LinkDragData) {
             let sourceDoc = de.data.linkSourceDocument;
             let destDoc = this.props.Document;
@@ -679,7 +689,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             cm.addItem({ description: "Share...", subitems: usersMenu, icon: "share" });
             if (!ClientUtils.RELEASE) {
                 let setWriteMode = (mode: DocServer.WriteMode) => {
-                    console.log(DocServer.WriteMode[mode]);
                     DocServer.AclsMode = mode;
                     const mode1 = mode;
                     const mode2 = mode === DocServer.WriteMode.Default ? mode : DocServer.WriteMode.Playground;
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index f7890e5a6..0e347ca67 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -167,7 +167,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             FormattedTextBox._toolTipTextMenu && (FormattedTextBox._toolTipTextMenu.HackToFixTextSelectionGlitch = false);
             if (state.selection.empty && FormattedTextBox._toolTipTextMenu) {
                 const marks = tx.storedMarks;
-                console.log(marks);
                 if (marks) { FormattedTextBox._toolTipTextMenu.mark_key_pressed(marks); }
             }
             this._applyingChange = true;
@@ -296,10 +295,10 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 this._editorView.updateState(EditorState.fromJSON(config, JSON.parse(field2)))
         );
 
-        this.props.isOverlay && (this._heightReactionDisposer = reaction(
+        this._heightReactionDisposer = reaction(
             () => this.props.Document[WidthSym](),
             () => this.tryUpdateHeight()
-        ));
+        );
 
         this._textReactionDisposer = reaction(
             () => this.extensionDoc,
@@ -448,8 +447,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         if (this.props.selectOnLoad) {
             if (!this.props.isOverlay) this.props.select(false);
             else this._editorView!.focus();
-            this.tryUpdateHeight();
         }
+        this.tryUpdateHeight();
     }
 
     componentWillUnmount() {
@@ -610,9 +609,10 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
 
     @action
     tryUpdateHeight() {
-        if (this.props.Document.autoHeight && this.props.isOverlay) {
+        if (this.props.Document.autoHeight && this._ref.current!.scrollHeight !== 0) {
+            console.log("DT = " + this.props.Document.title + " " + this._ref.current!.clientHeight + " " + this._ref.current!.scrollHeight + " " + this._ref.current!.textContent)
             let xf = this._ref.current!.getBoundingClientRect();
-            let scrBounds = this.props.ScreenToLocalTransform().transformBounds(0, 0, xf.width, xf.height);
+            let scrBounds = this.props.ScreenToLocalTransform().transformBounds(0, 0, xf.width, this._ref.current!.textContent === "" ? 35 : this._ref.current!.scrollHeight);
             let nh = this.props.Document.isTemplate ? 0 : NumCast(this.dataDoc.nativeHeight, 0);
             let dh = NumCast(this.props.Document.height, 0);
             let sh = scrBounds.height;
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 708e00576..6fc94a140 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -89,10 +89,7 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
     drop = (e: Event, de: DragManager.DropEvent) => {
         if (de.data instanceof DragManager.DocumentDragData) {
             de.data.droppedDocuments.forEach(action((drop: Doc) => {
-                if (de.mods === "CtrlKey") {
-                    Doc.ApplyTemplateTo(drop, this.props.Document, this.props.DataDoc);
-                    e.stopPropagation();
-                } else if (de.mods === "AltKey" && /*this.dataDoc !== this.props.Document &&*/ drop.data instanceof ImageField) {
+                if (de.mods === "AltKey" && /*this.dataDoc !== this.props.Document &&*/ drop.data instanceof ImageField) {
                     Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new ImageField(drop.data.url);
                     e.stopPropagation();
                 } else if (de.mods === "MetaKey") {
@@ -321,7 +318,7 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
                 let rotation = NumCast(this.dataDoc.rotation) % 180;
                 let realsize = rotation === 90 || rotation === 270 ? { height: size.width, width: size.height } : size;
                 let aspect = realsize.height / realsize.width;
-                if (Math.abs(NumCast(layoutdoc.height) - realsize.height) > 1 || Math.abs(NumCast(layoutdoc.width) - realsize.width) > 1) {
+                if (layoutdoc.nativeHeight !== 0 && layoutdoc.nativeWidth !== 0 && (Math.abs(NumCast(layoutdoc.height) - realsize.height) > 1 || Math.abs(NumCast(layoutdoc.width) - realsize.width) > 1)) {
                     setTimeout(action(() => {
                         layoutdoc.height = layoutdoc[WidthSym]() * aspect;
                         layoutdoc.nativeHeight = realsize.height;
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 31f1f7a12..ca05dfa45 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -446,20 +446,23 @@ export namespace Doc {
         if (expandedTemplateLayout instanceof Doc) {
             return expandedTemplateLayout;
         }
+        if (expandedTemplateLayout instanceof Promise) {
+            return undefined;
+        }
         let expandedLayoutFieldKey = "Layout[" + templateLayoutDoc[Id] + "]";
         expandedTemplateLayout = dataDoc[expandedLayoutFieldKey];
         if (expandedTemplateLayout instanceof Doc) {
             return expandedTemplateLayout;
         }
         if (expandedTemplateLayout === undefined) {
-            setTimeout(() =>
-                dataDoc[expandedLayoutFieldKey] = Doc.MakeDelegate(templateLayoutDoc, undefined, "[" + templateLayoutDoc.title + "]"), 0);
+            setTimeout(() => dataDoc[expandedLayoutFieldKey] === undefined &&
+                (dataDoc[expandedLayoutFieldKey] = Doc.MakeDelegate(templateLayoutDoc, undefined, "[" + templateLayoutDoc.title + "]")), 0);
         }
-        return templateLayoutDoc; // use the templateLayout when it's not a template or the expandedTemplate is pending.
+        return undefined; // use the templateLayout when it's not a template or the expandedTemplate is pending.
     }
 
     export function GetLayoutDataDocPair(doc: Doc, dataDoc: Doc | undefined, fieldKey: string, childDocLayout: Doc) {
-        let layoutDoc = childDocLayout;
+        let layoutDoc: Doc | undefined = childDocLayout;
         let resolvedDataDoc = !doc.isTemplate && dataDoc !== doc && dataDoc ? Doc.GetDataDoc(dataDoc) : undefined;
         if (resolvedDataDoc && Doc.WillExpandTemplateLayout(childDocLayout, resolvedDataDoc)) {
             Doc.UpdateDocumentExtensionForField(resolvedDataDoc, fieldKey);
-- 
cgit v1.2.3-70-g09d2


From dc587b0fb493d869e6cd38fa94b81105da4fbaab Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Tue, 20 Aug 2019 20:03:08 -0400
Subject: finished UI

---
 src/client/views/DocumentDecorations.scss   |  6 +-
 src/client/views/DocumentDecorations.tsx    | 92 ++++++++++++++++++-----------
 src/client/views/nodes/FormattedTextBox.tsx |  3 +-
 src/new_fields/RichTextField.ts             |  7 ++-
 4 files changed, 67 insertions(+), 41 deletions(-)

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

diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index 0b7411fca..ef7159370 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -255,4 +255,8 @@ $linkGap : 3px;
     input {
         margin-right: 10px;
     }
-}
\ No newline at end of file
+}
+
+@-moz-keyframes spin { 100% { -moz-transform: rotate(360deg); } }
+@-webkit-keyframes spin { 100% { -webkit-transform: rotate(360deg); } }
+@keyframes spin { 100% { -webkit-transform: rotate(360deg); transform: rotate(360deg); } }
\ No newline at end of file
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 05239073b..7645af054 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -1,5 +1,5 @@
 import { library, IconProp } from '@fortawesome/fontawesome-svg-core';
-import { faLink, faTag, faArrowAltCircleDown, faArrowAltCircleUp, faCheckCircle, faStopCircle, faCloudUploadAlt } from '@fortawesome/free-solid-svg-icons';
+import { faLink, faTag, faArrowAltCircleDown, faArrowAltCircleUp, faCheckCircle, faStopCircle, faCloudUploadAlt, faSyncAlt } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { action, computed, observable, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
@@ -41,8 +41,10 @@ library.add(faArrowAltCircleUp);
 library.add(faStopCircle);
 library.add(faCheckCircle);
 library.add(faCloudUploadAlt);
+library.add(faSyncAlt);
 
 const cloud: IconProp = "cloud-upload-alt";
+const fetch: IconProp = "sync-alt";
 
 @observer
 export class DocumentDecorations extends React.Component<{}, { value: string }> {
@@ -76,48 +78,47 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
     @observable public pushIcon: IconProp = "arrow-alt-circle-up";
     @observable public pullIcon: IconProp = "arrow-alt-circle-down";
     @observable public pullColor: string = "white";
+    @observable public isAnimatingFetch = false;
     public pullColorAnimating = false;
 
     private pullAnimating = false;
     private pushAnimating = false;
 
     public startPullOutcome = action((success: boolean) => {
-        if (this.pullAnimating) {
-            return;
+        if (!this.pullAnimating) {
+            this.pullAnimating = true;
+            this.pullIcon = success ? "check-circle" : "stop-circle";
+            setTimeout(() => runInAction(() => {
+                this.pullIcon = "arrow-alt-circle-down";
+                this.pullAnimating = false;
+            }), 1000);
         }
-        this.pullAnimating = true;
-        this.pullIcon = success ? "check-circle" : "stop-circle";
-        setTimeout(() => runInAction(() => {
-            this.pullIcon = "arrow-alt-circle-down";
-            this.pullAnimating = false;
-        }), 1000);
     });
 
     public startPushOutcome = action((success: boolean) => {
-        if (this.pushAnimating) {
-            return;
+        if (!this.pushAnimating) {
+            this.pushAnimating = true;
+            this.pushIcon = success ? "check-circle" : "stop-circle";
+            setTimeout(() => runInAction(() => {
+                this.pushIcon = "arrow-alt-circle-up";
+                this.pushAnimating = false;
+            }), 1000);
         }
-        this.pushAnimating = true;
-        this.pushIcon = success ? "check-circle" : "stop-circle";
-        setTimeout(() => runInAction(() => {
-            this.pushIcon = "arrow-alt-circle-up";
-            this.pushAnimating = false;
-        }), 1000);
     });
 
-    public setPullState = (unchanged: boolean) => {
-        if (this.pullColorAnimating) {
-            return;
+    public setPullState = action((unchanged: boolean) => {
+        this.isAnimatingFetch = false;
+        if (!this.pullColorAnimating) {
+            this.pullColorAnimating = true;
+            this.pullColor = unchanged ? "lawngreen" : "red";
+            setTimeout(this.clearPullColor, 1000);
         }
-        this.pullColorAnimating = true;
-        this.pullColor = unchanged ? "lawngreen" : "red";
-        setTimeout(() => {
-            runInAction(() => {
-                this.pullColor = "white";
-                this.pullColorAnimating = false;
-            });
-        }, 2000);
-    }
+    });
+
+    private clearPullColor = action(() => {
+        this.pullColor = "white";
+        this.pullColorAnimating = false;
+    });
 
     constructor(props: Readonly<{}>) {
         super(props);
@@ -679,7 +680,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         let canPush = this.targetDoc.data && this.targetDoc.data instanceof RichTextField;
         if (!canPush) return (null);
         let published = Doc.GetProto(this.targetDoc)[GoogleRef] !== undefined;
-        let icon: IconProp = published ? (this.pushIcon as any) : (cloud as any);
+        let icon: IconProp = published ? (this.pushIcon as any) : cloud;
         return (
             <div className={"linkButtonWrapper"}>
                 <div title={`${published ? "Push" : "Publish"} to Google Docs`} className="linkButton-linker" onClick={() => {
@@ -695,14 +696,33 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
     considerGoogleDocsPull = () => {
         let canPull = this.targetDoc.data && this.targetDoc.data instanceof RichTextField;
         let dataDoc = Doc.GetProto(this.targetDoc);
-        if (!canPull || !dataDoc[GoogleRef] || dataDoc.unchanged) return (null);
+        if (!canPull || !dataDoc[GoogleRef]) return (null);
+        let icon = !dataDoc.unchanged ? (this.pullIcon as any) : fetch;
+        let animation = this.isAnimatingFetch ? "spin 0.5s linear infinite" : "none";
         return (
             <div className={"linkButtonWrapper"}>
-                <div style={{ backgroundColor: this.pullColor, transition: "1s ease all" }} title="Pull From Google Docs" className="linkButton-linker" onClick={() => {
-                    DocumentDecorations.hasPulledHack = false;
-                    this.targetDoc[Pulls] = NumCast(this.targetDoc[Pulls]) + 1;
-                }}>
-                    <FontAwesomeIcon className="documentdecorations-icon" icon={this.pullIcon} size="sm" />
+                <div
+                    title="Pull From Google Docs"
+                    className="linkButton-linker"
+                    style={{
+                        backgroundColor: this.pullColor,
+                        transition: "0.2s ease all"
+                    }}
+                    onClick={() => {
+                        this.clearPullColor();
+                        DocumentDecorations.hasPulledHack = false;
+                        this.targetDoc[Pulls] = NumCast(this.targetDoc[Pulls]) + 1;
+                        dataDoc.unchanged && runInAction(() => this.isAnimatingFetch = true);
+                    }}>
+                    <FontAwesomeIcon
+                        style={{
+                            WebkitAnimation: animation,
+                            MozAnimation: animation
+                        }}
+                        className="documentdecorations-icon"
+                        icon={icon}
+                        size="sm"
+                    />
                 </div>
             </div>
         );
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 8a355a425..e06be7079 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -354,7 +354,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             () => {
                 if (!DocumentDecorations.hasPulledHack) {
                     DocumentDecorations.hasPulledHack = true;
-                    this.pullFromGoogleDoc(this.updateState);
+                    let unchanged = this.dataDoc.unchanged;
+                    this.pullFromGoogleDoc(unchanged ? this.checkState : this.updateState);
                 }
             }
         );
diff --git a/src/new_fields/RichTextField.ts b/src/new_fields/RichTextField.ts
index ab58329f9..cae5623e6 100644
--- a/src/new_fields/RichTextField.ts
+++ b/src/new_fields/RichTextField.ts
@@ -38,9 +38,10 @@ export class RichTextField extends ObjectField {
         let blockText = (block: any) => block.text;
         let concatenateParagraph = (p: any) => (p.content ? p.content.map(blockText).join(joiner) : "") + delimiter;
 
-        // Concatentate paragraphs and string the result together. Trim the last newline, an artifact.
-        let textParagraphs = paragraphs.map(concatenateParagraph);
-        return textParagraphs.join(joiner).trimEnd(delimiter);
+        // Concatentate paragraphs and string the result together
+        let textParagraphs: string[] = paragraphs.map(concatenateParagraph);
+        let plainText = textParagraphs.join(joiner);
+        return plainText.substring(0, plainText.length - 1);
     }
 
     [FromPlainText](plainText: string) {
-- 
cgit v1.2.3-70-g09d2


From 820ad7343816ee9d5881eefdbcbe8783fe903257 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Tue, 20 Aug 2019 21:07:43 -0400
Subject: animation fix

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

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

diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 2705fac14..d4cbb5116 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -1,6 +1,6 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
 import { faEdit, faSmile, faTextHeight, faUpload } from '@fortawesome/free-solid-svg-icons';
-import { action, computed, IReactionDisposer, Lambda, observable, reaction } from "mobx";
+import { action, computed, IReactionDisposer, Lambda, observable, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import { baseKeymap } from "prosemirror-commands";
 import { history } from "prosemirror-history";
@@ -298,6 +298,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         }
 
         this.pullFromGoogleDoc(this.checkState);
+        runInAction(() => DocumentDecorations.Instance.isAnimatingFetch = true);
 
         this._reactionDisposer = reaction(
             () => {
-- 
cgit v1.2.3-70-g09d2


From c0439996ec8b4858c48a7871120e19bb7c06c67d Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Tue, 20 Aug 2019 21:37:32 -0400
Subject: drag and drop google docs tweaks

---
 package.json                                       | 4 +---
 src/client/DocServer.ts                            | 1 -
 src/client/views/collections/CollectionSubView.tsx | 8 ++++----
 src/client/views/nodes/FormattedTextBox.tsx        | 1 +
 4 files changed, 6 insertions(+), 8 deletions(-)

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

diff --git a/package.json b/package.json
index e6a0a54b9..de1f3f6e6 100644
--- a/package.json
+++ b/package.json
@@ -63,7 +63,6 @@
         "@types/cookie-parser": "^1.4.1",
         "@types/cookie-session": "^2.0.36",
         "@types/d3-format": "^1.3.1",
-        "@types/diff": "^4.0.2",
         "@types/dotenv": "^6.1.1",
         "@types/express": "^4.16.1",
         "@types/express-flash": "0.0.0",
@@ -128,7 +127,6 @@
         "cookie-session": "^2.0.0-beta.3",
         "crypto-browserify": "^3.11.0",
         "d3-format": "^1.3.2",
-        "diff": "^4.0.1",
         "dotenv": "^8.0.0",
         "express": "^4.16.4",
         "express-flash": "0.0.2",
@@ -221,4 +219,4 @@
         "xoauth2": "^1.2.0",
         "youtube": "^0.1.0"
     }
-}
+}
\ No newline at end of file
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index 56d9147ae..2cec1046b 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -5,7 +5,6 @@ import { Utils, emptyFunction } from '../Utils';
 import { SerializationHelper } from './util/SerializationHelper';
 import { RefField } from '../new_fields/RefField';
 import { Id, HandleUpdate } from '../new_fields/FieldSymbols';
-import { docs_v1 } from 'googleapis';
 
 /**
  * This class encapsulates the transfer and cross-client synchronization of
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 3deec22b5..5b50c545c 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -17,7 +17,7 @@ import { DragManager } from "../../util/DragManager";
 import { undoBatch, UndoManager } from "../../util/UndoManager";
 import { DocComponent } from "../DocComponent";
 import { FieldViewProps } from "../nodes/FieldView";
-import { FormattedTextBox, Blank } from "../nodes/FormattedTextBox";
+import { FormattedTextBox, GoogleRef } from "../nodes/FormattedTextBox";
 import { CollectionPDFView } from "./CollectionPDFView";
 import { CollectionVideoView } from "./CollectionVideoView";
 import { CollectionView } from "./CollectionView";
@@ -209,11 +209,11 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
             }
             let matches: RegExpExecArray | null;
             if ((matches = /(https:\/\/)?docs\.google\.com\/document\/d\/([^\\]+)\/edit/g.exec(text)) !== null) {
-                let newBox = Docs.Create.TextDocument({ ...options, width: 600, height: 400, title: "Fetching title from Google Docs..." });
+                let newBox = Docs.Create.TextDocument({ ...options, width: 400, height: 200, title: "Awaiting title from Google Docs..." });
                 let proto = newBox.proto!;
                 proto.autoHeight = true;
-                proto.googleDocId = matches[2];
-                proto.data = "Fetching contents from Google Docs...";
+                proto[GoogleRef] = matches[2];
+                proto.data = "Please select and then click on this document's pull button to load its contents from from Google Docs...";
                 proto.backgroundColor = "#eeeeff";
                 this.props.addDocument(newBox);
                 return;
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index d4cbb5116..2b01fb70a 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -434,6 +434,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             delete dataDoc[GoogleRef];
         }
         DocumentDecorations.Instance.startPullOutcome(pullSuccess);
+        this.tryUpdateHeight();
     }
 
     checkState = (exportState: GoogleApiClientUtils.Docs.ReadResult, dataDoc: Doc) => {
-- 
cgit v1.2.3-70-g09d2


From 73bb0f572e261850583b698dd819d35a6fe768ec Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Tue, 20 Aug 2019 21:42:37 -0400
Subject: cleanup

---
 src/client/views/MainView.tsx                      | 2 +-
 src/client/views/collections/CollectionSubView.tsx | 2 +-
 src/client/views/nodes/FormattedTextBox.tsx        | 6 +++---
 3 files changed, 5 insertions(+), 5 deletions(-)

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

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index e85374f6a..f28844009 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -15,7 +15,7 @@ import { listSpec } from '../../new_fields/Schema';
 import { BoolCast, Cast, FieldValue, StrCast } from '../../new_fields/Types';
 import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils';
 import { RouteStore } from '../../server/RouteStore';
-import { emptyFunction, returnOne, returnTrue, Utils, returnEmptyString, PostToServer } from '../../Utils';
+import { emptyFunction, returnOne, returnTrue, Utils, returnEmptyString } from '../../Utils';
 import { DocServer } from '../DocServer';
 import { Docs } from '../documents/Documents';
 import { ClientUtils } from '../util/ClientUtils';
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 5b50c545c..99e5ab7b3 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -213,7 +213,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
                 let proto = newBox.proto!;
                 proto.autoHeight = true;
                 proto[GoogleRef] = matches[2];
-                proto.data = "Please select and then click on this document's pull button to load its contents from from Google Docs...";
+                proto.data = "Please select this document and then click on its pull button to load its contents from from Google Docs...";
                 proto.backgroundColor = "#eeeeff";
                 this.props.addDocument(newBox);
                 return;
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 2b01fb70a..606e8edb0 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -389,9 +389,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 };
             }
             let redo = async () => {
-                if (this._editorView && reference) {
-                    let data = Cast(this.dataDoc.data, RichTextField);
-                    let content = data ? data[ToPlainText]() : this._editorView.state.doc.textContent;
+                let data = Cast(this.dataDoc.data, RichTextField);
+                if (this._editorView && reference && data) {
+                    let content = data[ToPlainText]();
                     let response = await GoogleApiClientUtils.Docs.write({ reference, content, mode });
                     let pushSuccess = response !== undefined && !("errors" in response);
                     dataDoc.unchanged = pushSuccess;
-- 
cgit v1.2.3-70-g09d2


From 8fa3c03f1fae853e3c626e748aef76f9b7bbc875 Mon Sep 17 00:00:00 2001
From: kimdahey <claire_kim1@brown.edu>
Date: Tue, 20 Aug 2019 22:42:25 -0400
Subject: initial commit

---
 package.json                                |  2 +-
 src/client/util/DictationManager.ts         | 12 ++++++++++--
 src/client/views/nodes/FormattedTextBox.tsx | 20 ++++++++++++++++++++
 3 files changed, 31 insertions(+), 3 deletions(-)

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

diff --git a/package.json b/package.json
index de1f3f6e6..cd60b7b55 100644
--- a/package.json
+++ b/package.json
@@ -219,4 +219,4 @@
         "xoauth2": "^1.2.0",
         "youtube": "^0.1.0"
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts
index 488a146bf..758482eae 100644
--- a/src/client/util/DictationManager.ts
+++ b/src/client/util/DictationManager.ts
@@ -301,11 +301,16 @@ export namespace DictationManager {
                 }
             }],
 
-            ["create bulleted note", {
+            ["new outline", {
                 action: (target: DocumentView) => {
                     let newBox = Docs.Create.TextDocument({ width: 400, height: 200, title: "My Outline" });
+                    newBox.autoHeight = true;
                     let proto = newBox.proto!;
-                    let proseMirrorState = '"{"doc":{"type":"doc","content":[{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"type":"text","text":""}]}]}]}]},"selection":{"type":"text","anchor":1,"head":1}}"';
+                    proto.page = -1;
+                    let prompt = "Press alt + r to start dictating here...";
+                    let head = 3;
+                    let anchor = head + prompt.length;
+                    let proseMirrorState = `{"doc":{"type":"doc","content":[{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"type":"text","text":"${prompt}"}]}]}]}]},"selection":{"type":"text","anchor":${anchor},"head":${head}}}`;
                     proto.data = new RichTextField(proseMirrorState);
                     proto.backgroundColor = "#eeffff";
                     target.props.addDocTab(newBox, proto, "onRight");
@@ -323,6 +328,9 @@ export namespace DictationManager {
                     let what = matches[2];
                     let dataDoc = Doc.GetProto(target.props.Document);
                     let fieldKey = "data";
+                    if (isNaN(count)) {
+                        return;
+                    }
                     for (let i = 0; i < count; i++) {
                         let created: Doc | undefined;
                         switch (what) {
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 606e8edb0..e460e3e50 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -35,6 +35,7 @@ import React = require("react");
 import { GoogleApiClientUtils, Pulls, Pushes } from '../../apis/google_docs/GoogleApiClientUtils';
 import { DocumentDecorations } from '../DocumentDecorations';
 import { MainOverlayTextBox } from '../MainOverlayTextBox';
+import { DictationManager } from '../../util/DictationManager';
 
 library.add(faEdit);
 library.add(faSmile, faTextHeight, faUpload);
@@ -265,7 +266,25 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         }
     }
 
+    setCurrentBulletContent = (value: string) => {
+        if (this._editorView) {
+            this._editorView.state;
+        }
+    }
+
+    considerRecordingSession = (e: KeyboardEvent) => {
+        if (e.which === 82 && e.altKey) {
+            let continuous = { indefinite: true };
+            DictationManager.Controls.listen({
+                interimHandler: this.setCurrentBulletContent,
+                continuous
+            });
+        }
+    }
+
     componentDidMount() {
+        document.removeEventListener("keypress", this.considerRecordingSession);
+        document.addEventListener("keypress", this.considerRecordingSession);
         const config = {
             schema,
             inpRules, //these currently don't do anything, but could eventually be helpful
@@ -576,6 +595,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     }
 
     componentWillUnmount() {
+        document.removeEventListener("keypress", this.considerRecordingSession);
         this._editorView && this._editorView.destroy();
         this._reactionDisposer && this._reactionDisposer();
         this._proxyReactionDisposer && this._proxyReactionDisposer();
-- 
cgit v1.2.3-70-g09d2


From d260d9abc13ae60c3206c3110f2d7f23ceeb2449 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Wed, 21 Aug 2019 05:29:07 -0400
Subject: prosemirror bulleted list dictation integration

---
 src/client/util/DictationManager.ts         | 48 ++++++++++--------
 src/client/views/DocumentDecorations.tsx    |  3 +-
 src/client/views/nodes/FormattedTextBox.tsx | 76 ++++++++++++++++++++++++-----
 3 files changed, 93 insertions(+), 34 deletions(-)

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

diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts
index 758482eae..781e5e465 100644
--- a/src/client/util/DictationManager.ts
+++ b/src/client/util/DictationManager.ts
@@ -45,7 +45,7 @@ export namespace DictationManager {
 
     export namespace Controls {
 
-        const infringe = "unable to process: dictation manager still involved in previous session";
+        export const Infringed = "unable to process: dictation manager still involved in previous session";
         const intraSession = ". ";
         const interSession = " ... ";
 
@@ -69,30 +69,32 @@ export namespace DictationManager {
             delimiters: DelimiterArgs;
             interimHandler: InterimResultHandler;
             tryExecute: boolean;
+            terminators: string[];
         }
 
         export const listen = async (options?: Partial<ListeningOptions>) => {
             let results: string | undefined;
-            let main = MainView.Instance;
+            // let main = MainView.Instance;
 
-            main.dictationOverlayVisible = true;
-            main.isListening = { interim: false };
+            // main.dictationOverlayVisible = true;
+            // main.isListening = { interim: false };
 
             try {
                 results = await listenImpl(options);
                 if (results) {
                     Utils.CopyText(results);
-                    main.isListening = false;
-                    let execute = options && options.tryExecute;
-                    main.dictatedPhrase = execute ? results.toLowerCase() : results;
-                    main.dictationSuccess = execute ? await DictationManager.Commands.execute(results) : true;
+                    // main.isListening = false;
+                    // let execute = options && options.tryExecute;
+                    // main.dictatedPhrase = execute ? results.toLowerCase() : results;
+                    // main.dictationSuccess = execute ? await DictationManager.Commands.execute(results) : true;
+                    options && options.tryExecute && await DictationManager.Commands.execute(results);
                 }
             } catch (e) {
-                main.isListening = false;
-                main.dictatedPhrase = results = `dictation error: ${"error" in e ? e.error : "unknown error"}`;
-                main.dictationSuccess = false;
+                // main.isListening = false;
+                // main.dictatedPhrase = results = `dictation error: ${"error" in e ? e.error : "unknown error"}`;
+                // main.dictationSuccess = false;
             } finally {
-                main.initiateDictationFade();
+                // main.initiateDictationFade();
             }
 
             return results;
@@ -100,7 +102,7 @@ export namespace DictationManager {
 
         const listenImpl = (options?: Partial<ListeningOptions>) => {
             if (isListening) {
-                return infringe;
+                return Infringed;
             }
             isListening = true;
 
@@ -128,6 +130,12 @@ export namespace DictationManager {
 
                 recognizer.onresult = (e: SpeechRecognitionEvent) => {
                     current = synthesize(e, intra);
+                    let matchedTerminator: string | undefined;
+                    if (options && options.terminators && (matchedTerminator = options.terminators.find(end => current ? current.trim().toLowerCase().endsWith(end.toLowerCase()) : false))) {
+                        current = matchedTerminator;
+                        recognizer.abort();
+                        return complete();
+                    }
                     handler && handler(current);
                     isManuallyStopped && complete();
                 };
@@ -163,13 +171,13 @@ export namespace DictationManager {
             }
             isManuallyStopped = true;
             salvageSession ? recognizer.stop() : recognizer.abort();
-            let main = MainView.Instance;
-            if (main.dictationOverlayVisible) {
-                main.cancelDictationFade();
-                main.dictationOverlayVisible = false;
-                main.dictationSuccess = undefined;
-                setTimeout(() => main.dictatedPhrase = placeholder, 500);
-            }
+            // let main = MainView.Instance;
+            // if (main.dictationOverlayVisible) {
+            // main.cancelDictationFade();
+            // main.dictationOverlayVisible = false;
+            // main.dictationSuccess = undefined;
+            // setTimeout(() => main.dictatedPhrase = placeholder, 500);
+            // }
         };
 
         const synthesize = (e: SpeechRecognitionEvent, delimiter?: string) => {
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 891fd7847..cc8e57af9 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -730,7 +730,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                     onPointerEnter={e => e.ctrlKey && runInAction(() => this.openHover = true)}
                     onPointerLeave={() => runInAction(() => this.openHover = false)}
                     onClick={e => {
-                        if (e.ctrlKey) {
+                        if (e.altKey) {
+                            e.preventDefault();
                             window.open(`https://docs.google.com/document/d/${dataDoc[GoogleRef]}/edit`);
                         } else {
                             this.clearPullColor();
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index e460e3e50..222ebed8f 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -6,7 +6,7 @@ import { baseKeymap } from "prosemirror-commands";
 import { history } from "prosemirror-history";
 import { keymap } from "prosemirror-keymap";
 import { Fragment, Node, Node as ProsNode, NodeType, Slice } from "prosemirror-model";
-import { EditorState, Plugin, Transaction } from "prosemirror-state";
+import { EditorState, Plugin, Transaction, TextSelection } from "prosemirror-state";
 import { EditorView } from "prosemirror-view";
 import { DateField } from '../../../new_fields/DateField';
 import { Doc, DocListCast, Opt, WidthSym } from "../../../new_fields/Doc";
@@ -36,6 +36,7 @@ import { GoogleApiClientUtils, Pulls, Pushes } from '../../apis/google_docs/Goog
 import { DocumentDecorations } from '../DocumentDecorations';
 import { MainOverlayTextBox } from '../MainOverlayTextBox';
 import { DictationManager } from '../../util/DictationManager';
+import { ReplaceStep } from 'prosemirror-transform';
 
 library.add(faEdit);
 library.add(faSmile, faTextHeight, faUpload);
@@ -181,6 +182,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 const marks = tx.storedMarks;
                 if (marks) { FormattedTextBox._toolTipTextMenu.mark_key_pressed(marks); }
             }
+
             this._applyingChange = true;
             const fieldkey = "preview";
             if (this.extensionDoc) this.extensionDoc.text = state.doc.textBetween(0, state.doc.content.size, "\n\n");
@@ -268,23 +270,70 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
 
     setCurrentBulletContent = (value: string) => {
         if (this._editorView) {
-            this._editorView.state;
+            // let next = value.endsWith("bullet");
+            // if (next) {
+            //     value = value.split("bullet")[0];
+            // }
+            let state = this._editorView.state;
+            let from = state.selection.from;
+            let to = state.selection.to;
+            this._editorView.dispatch(state.tr.insertText(value, from, to));
+            state = this._editorView.state;
+            let updated = TextSelection.create(state.doc, from, from + value.length);
+            this._editorView.dispatch(state.tr.setSelection(updated));
+            // if (next) {
+            //     this.nextBullet(this._editorView.state.selection.to);
+            // }
         }
     }
 
-    considerRecordingSession = (e: KeyboardEvent) => {
-        if (e.which === 82 && e.altKey) {
-            let continuous = { indefinite: true };
-            DictationManager.Controls.listen({
-                interimHandler: this.setCurrentBulletContent,
-                continuous
-            });
+    nextBullet = (pos: number) => {
+        if (this._editorView) {
+            // DictationManager.Controls.stop(false);
+            let frag = Fragment.fromArray(this.newListItems(2));
+            let slice = new Slice(frag, 2, 2);
+            let state = this._editorView.state;
+            this._editorView.dispatch(state.tr.step(new ReplaceStep(pos, pos, slice)));
+            pos += 4;
+            state = this._editorView.state;
+            this._editorView.dispatch(state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos, pos)));
+            // this.recordSession();
+        }
+    }
+
+    private newListItems = (count: number) => {
+        let listItems: any[] = [];
+        for (let i = 0; i < count; i++) {
+            listItems.push(schema.nodes.list_item.create(null, schema.nodes.paragraph.create(null)));
         }
+        return listItems;
+    }
+
+    recordSession = async () => {
+        let completedCue = "end session";
+        let results = await DictationManager.Controls.listen({
+            interimHandler: this.setCurrentBulletContent,
+            continuous: { indefinite: false },
+            terminators: [completedCue, "bullet", "next"]
+        });
+        if (results && [DictationManager.Controls.Infringed, completedCue].includes(results)) {
+            DictationManager.Controls.stop();
+            return;
+        }
+        this.nextBullet(this._editorView!.state.selection.to);
+        setTimeout(this.recordSession, 2000);
+    }
+
+    recordKeyHandler = (e: KeyboardEvent) => {
+        if (this.props.Document !== SelectionManager.SelectedDocuments()[0].props.Document) {
+            return;
+        }
+        e.stopPropagation();
+        e.preventDefault();
+        e.which === 174 && e.altKey && this.recordSession();
     }
 
     componentDidMount() {
-        document.removeEventListener("keypress", this.considerRecordingSession);
-        document.addEventListener("keypress", this.considerRecordingSession);
         const config = {
             schema,
             inpRules, //these currently don't do anything, but could eventually be helpful
@@ -335,7 +384,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                         updatedState.selection = { type: "text", anchor: end, head: end };
                         this._editorView.updateState(EditorState.fromJSON(config, updatedState));
                     }
-                    this.tryUpdateHeight();
                 }
             }
         );
@@ -595,7 +643,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     }
 
     componentWillUnmount() {
-        document.removeEventListener("keypress", this.considerRecordingSession);
         this._editorView && this._editorView.destroy();
         this._reactionDisposer && this._reactionDisposer();
         this._proxyReactionDisposer && this._proxyReactionDisposer();
@@ -678,6 +725,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
 
     @action
     onFocused = (e: React.FocusEvent): void => {
+        document.removeEventListener("keypress", this.recordKeyHandler);
+        document.addEventListener("keypress", this.recordKeyHandler);
         if (!this.props.isOverlay) {
             FormattedTextBox.InputBoxOverlay = this;
         } else {
@@ -727,6 +776,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         });
     }
     onBlur = (e: any) => {
+        document.removeEventListener("keypress", this.recordKeyHandler);
         if (this._undoTyping) {
             this._undoTyping.end();
             this._undoTyping = undefined;
-- 
cgit v1.2.3-70-g09d2


From 654db73e0615e77f7206128b6e5524553d653c66 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Wed, 21 Aug 2019 05:30:39 -0400
Subject: cleanup

---
 src/client/views/nodes/FormattedTextBox.tsx | 57 ++++++++++++-----------------
 1 file changed, 24 insertions(+), 33 deletions(-)

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

diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 222ebed8f..a2ea12ac5 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -268,12 +268,32 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         }
     }
 
+    recordKeyHandler = (e: KeyboardEvent) => {
+        if (this.props.Document !== SelectionManager.SelectedDocuments()[0].props.Document) {
+            return;
+        }
+        e.stopPropagation();
+        e.preventDefault();
+        e.which === 174 && e.altKey && this.recordBullet();
+    }
+
+    recordBullet = async () => {
+        let completedCue = "end session";
+        let results = await DictationManager.Controls.listen({
+            interimHandler: this.setCurrentBulletContent,
+            continuous: { indefinite: false },
+            terminators: [completedCue, "bullet", "next"]
+        });
+        if (results && [DictationManager.Controls.Infringed, completedCue].includes(results)) {
+            DictationManager.Controls.stop();
+            return;
+        }
+        this.nextBullet(this._editorView!.state.selection.to);
+        setTimeout(this.recordBullet, 2000);
+    }
+
     setCurrentBulletContent = (value: string) => {
         if (this._editorView) {
-            // let next = value.endsWith("bullet");
-            // if (next) {
-            //     value = value.split("bullet")[0];
-            // }
             let state = this._editorView.state;
             let from = state.selection.from;
             let to = state.selection.to;
@@ -281,15 +301,11 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             state = this._editorView.state;
             let updated = TextSelection.create(state.doc, from, from + value.length);
             this._editorView.dispatch(state.tr.setSelection(updated));
-            // if (next) {
-            //     this.nextBullet(this._editorView.state.selection.to);
-            // }
         }
     }
 
     nextBullet = (pos: number) => {
         if (this._editorView) {
-            // DictationManager.Controls.stop(false);
             let frag = Fragment.fromArray(this.newListItems(2));
             let slice = new Slice(frag, 2, 2);
             let state = this._editorView.state;
@@ -297,7 +313,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             pos += 4;
             state = this._editorView.state;
             this._editorView.dispatch(state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos, pos)));
-            // this.recordSession();
         }
     }
 
@@ -309,30 +324,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         return listItems;
     }
 
-    recordSession = async () => {
-        let completedCue = "end session";
-        let results = await DictationManager.Controls.listen({
-            interimHandler: this.setCurrentBulletContent,
-            continuous: { indefinite: false },
-            terminators: [completedCue, "bullet", "next"]
-        });
-        if (results && [DictationManager.Controls.Infringed, completedCue].includes(results)) {
-            DictationManager.Controls.stop();
-            return;
-        }
-        this.nextBullet(this._editorView!.state.selection.to);
-        setTimeout(this.recordSession, 2000);
-    }
-
-    recordKeyHandler = (e: KeyboardEvent) => {
-        if (this.props.Document !== SelectionManager.SelectedDocuments()[0].props.Document) {
-            return;
-        }
-        e.stopPropagation();
-        e.preventDefault();
-        e.which === 174 && e.altKey && this.recordSession();
-    }
-
     componentDidMount() {
         const config = {
             schema,
-- 
cgit v1.2.3-70-g09d2


From 186cf33322b6c96386c6e9fdb823025d09c5caeb Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Wed, 21 Aug 2019 11:35:18 -0400
Subject: extended treeview functionality - better indenting, state management,
 drag dropping.

---
 src/client/views/EditableView.tsx                  | 28 +++++---
 .../views/collections/CollectionTreeView.tsx       | 83 +++++++++++++---------
 src/client/views/nodes/FormattedTextBox.tsx        |  1 -
 .../authentication/models/current_user_utils.ts    |  5 +-
 4 files changed, 75 insertions(+), 42 deletions(-)

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

diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index c3612fee9..dd5395802 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -3,6 +3,7 @@ import { observer } from 'mobx-react';
 import { observable, action, trace } from 'mobx';
 import "./EditableView.scss";
 import * as Autosuggest from 'react-autosuggest';
+import { undoBatch } from '../util/UndoManager';
 
 export interface EditableProps {
     /**
@@ -70,14 +71,12 @@ export class EditableView extends React.Component<EditableProps> {
     onKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
         if (e.key === "Tab") {
             e.stopPropagation();
+            this.finalizeEdit(e.currentTarget.value, e.shiftKey);
             this.props.OnTab && this.props.OnTab();
         } else if (e.key === "Enter") {
             e.stopPropagation();
             if (!e.ctrlKey) {
-                if (this.props.SetValue(e.currentTarget.value, e.shiftKey)) {
-                    this._editing = false;
-                    this.props.isEditingCallback && this.props.isEditingCallback(false);
-                }
+                this.finalizeEdit(e.currentTarget.value, e.shiftKey);
             } else if (this.props.OnFillDown) {
                 this.props.OnFillDown(e.currentTarget.value);
                 this._editing = false;
@@ -100,6 +99,14 @@ export class EditableView extends React.Component<EditableProps> {
         e.stopPropagation();
     }
 
+    @action
+    private finalizeEdit(value: string, shiftDown: boolean) {
+        if (this.props.SetValue(value, shiftDown)) {
+            this._editing = false;
+            this.props.isEditingCallback && this.props.isEditingCallback(false);
+        }
+    }
+
     stopPropagation(e: React.SyntheticEvent) {
         e.stopPropagation();
     }
@@ -118,7 +125,7 @@ export class EditableView extends React.Component<EditableProps> {
                         className: "editableView-input",
                         onKeyDown: this.onKeyDown,
                         autoFocus: true,
-                        onBlur: action(() => this._editing = false),
+                        onBlur: e => this.finalizeEdit(e.currentTarget.value, false),
                         onPointerDown: this.stopPropagation,
                         onClick: this.stopPropagation,
                         onPointerUp: this.stopPropagation,
@@ -126,9 +133,14 @@ export class EditableView extends React.Component<EditableProps> {
                         onChange: this.props.autosuggestProps.onChange
                     }}
                 />
-                : <input className="editableView-input" defaultValue={this.props.GetValue()} onKeyDown={this.onKeyDown} autoFocus
-                    onBlur={action(() => { this._editing = false; this.props.isEditingCallback && this.props.isEditingCallback(false); })} onPointerDown={this.stopPropagation} onClick={this.stopPropagation} onPointerUp={this.stopPropagation}
-                    style={{ display: this.props.display, fontSize: this.props.fontSize }} />;
+                : <input className="editableView-input"
+                    defaultValue={this.props.GetValue()}
+                    onKeyDown={this.onKeyDown}
+                    autoFocus={true}
+                    onBlur={e => this.finalizeEdit(e.currentTarget.value, false)}
+                    onPointerDown={this.stopPropagation} onClick={this.stopPropagation} onPointerUp={this.stopPropagation}
+                    style={{ display: this.props.display, fontSize: this.props.fontSize }}
+                />;
         } else {
             if (this.props.autosuggestProps) this.props.autosuggestProps.resetValue();
             return (
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index ebd385743..e1eb9e1a5 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -49,6 +49,8 @@ export interface TreeViewProps {
     treeViewId: string;
     parentKey: string;
     active: () => boolean;
+    showHeaderFields: () => boolean;
+    preventTreeViewOpen: boolean;
 }
 
 library.add(faTrashAlt);
@@ -65,7 +67,12 @@ library.add(faArrowsAltH);
 library.add(faPlus, faMinus);
 @observer
 /**
- * Component that takes in a document prop and a boolean whether it's collapsed or not.
+ * Renders a treeView of a collection of documents
+ * 
+ * special fields:
+ * treeViewOpen : flag denoting whether the documents sub-tree (contents) is visible or hidden
+ * preventTreeViewOpen : ignores the treeViewOpen flag (for allowing a view to not be slaved to other views of the document)
+ * treeViewExpandedView : name of field whose contents are being displayed as the document's subtree
  */
 class TreeView extends React.Component<TreeViewProps> {
     static loadId = "";
@@ -73,7 +80,9 @@ class TreeView extends React.Component<TreeViewProps> {
     private _treedropDisposer?: DragManager.DragDropDisposer;
     private _dref = React.createRef<HTMLDivElement>();
     get defaultExpandedView() { return this.childDocs ? this.fieldKey : "fields"; }
-    @observable _collapsed: boolean = true;
+    @observable _overrideTreeViewOpen = false; // override of the treeViewOpen field allowing the display state to be independent of the document's state
+    @computed get treeViewOpen() { return (BoolCast(this.props.document.treeViewOpen) && !this.props.preventTreeViewOpen) || this._overrideTreeViewOpen; }
+    set treeViewOpen(c: boolean) { if (this.props.preventTreeViewOpen) this._overrideTreeViewOpen = c; else this.props.document.treeViewOpen = c; }
     @computed get treeViewExpandedView() { return StrCast(this.props.document.treeViewExpandedView, this.defaultExpandedView); }
     @computed get MAX_EMBED_HEIGHT() { return NumCast(this.props.document.maxEmbedHeight, 300); }
     @computed get dataDoc() { return this.resolvedDataDoc ? this.resolvedDataDoc : this.props.document; }
@@ -146,7 +155,7 @@ class TreeView extends React.Component<TreeViewProps> {
         let rect = this._header!.current!.getBoundingClientRect();
         let bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left, rect.top + rect.height / 2);
         let before = x[1] < bounds[1];
-        let inside = x[0] > bounds[0] + 75 || (!before && !this._collapsed);
+        let inside = x[0] > bounds[0] + 75 || (!before && this.treeViewOpen);
         this._header!.current!.className = "treeViewItem-header";
         if (inside) this._header!.current!.className += " treeViewItem-header-inside";
         else if (before) this._header!.current!.className += " treeViewItem-header-above";
@@ -163,15 +172,15 @@ class TreeView extends React.Component<TreeViewProps> {
         fontStyle={style}
         fontSize={12}
         GetValue={() => StrCast(this.props.document[key])}
-        SetValue={(value: string) => (Doc.GetProto(this.dataDoc)[key] = value) ? true : true}
-        OnFillDown={(value: string) => {
+        SetValue={undoBatch((value: string) => (Doc.GetProto(this.dataDoc)[key] = value) ? true : true)}
+        OnFillDown={undoBatch((value: string) => {
             Doc.GetProto(this.dataDoc)[key] = value;
             let doc = this.props.document.detailedLayout instanceof Doc ? Doc.ApplyTemplate(Doc.GetProto(this.props.document.detailedLayout)) : undefined;
             if (!doc) doc = Docs.Create.FreeformDocument([], { title: "", x: 0, y: 0, width: 100, height: 25, templates: new List<string>([Templates.Title.Layout]) });
             TreeView.loadId = doc[Id];
             return this.props.addDocument(doc);
-        }}
-        OnTab={() => this.props.indentDocument && this.props.indentDocument()}
+        })}
+        OnTab={() => { TreeView.loadId = ""; this.props.indentDocument && this.props.indentDocument(); }}
     />)
 
     onWorkspaceContextMenu = (e: React.MouseEvent): void => {
@@ -200,7 +209,7 @@ class TreeView extends React.Component<TreeViewProps> {
         let rect = this._header!.current!.getBoundingClientRect();
         let bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left, rect.top + rect.height / 2);
         let before = x[1] < bounds[1];
-        let inside = x[0] > bounds[0] + 75 || (!before && !this._collapsed);
+        let inside = x[0] > bounds[0] + 75 || (!before && this.treeViewOpen);
         if (de.data instanceof DragManager.LinkDragData) {
             let sourceDoc = de.data.linkSourceDocument;
             let destDoc = this.props.document;
@@ -256,14 +265,14 @@ class TreeView extends React.Component<TreeViewProps> {
         let rows: JSX.Element[] = [];
         for (let key of Object.keys(ids).slice().sort()) {
             let contents = doc[key];
-            let contentElement: JSX.Element[] | JSX.Element = [];
+            let contentElement: (JSX.Element | null)[] | JSX.Element = [];
 
             if (contents instanceof Doc || Cast(contents, listSpec(Doc))) {
                 let remDoc = (doc: Doc) => this.remove(doc, key);
-                let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, !BoolCast(this.props.document.stackingHeadersSortDescending));
+                let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, !BoolCast(this.props.document.stackingHeadersSortDescending, true));
                 contentElement = TreeView.GetChildElements(contents instanceof Doc ? [contents] :
                     DocListCast(contents), this.props.treeViewId, doc, undefined, key, addDoc, remDoc, this.move,
-                    this.props.dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth);
+                    this.props.dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth, this.props.showHeaderFields, this.props.preventTreeViewOpen);
             } else {
                 contentElement = <EditableView
                     key="editableView"
@@ -288,14 +297,14 @@ class TreeView extends React.Component<TreeViewProps> {
         const expandKey = this.treeViewExpandedView === this.fieldKey ? this.fieldKey : this.treeViewExpandedView === "links" ? "links" : undefined;
         if (expandKey !== undefined) {
             let remDoc = (doc: Doc) => this.remove(doc, expandKey);
-            let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, expandKey, doc, addBefore, before, !BoolCast(this.props.document.stackingHeadersSortDescending));
+            let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, expandKey, doc, addBefore, before, !BoolCast(this.props.document.stackingHeadersSortDescending, true));
             let docs = expandKey === "links" ? this.childLinks : this.childDocs;
             return <ul key={expandKey + "more"}>
                 {!docs ? (null) :
                     TreeView.GetChildElements(docs as Doc[], this.props.treeViewId, this.props.document.layout as Doc,
                         this.resolvedDataDoc, expandKey, addDoc, remDoc, this.move,
                         this.props.dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform,
-                        this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth)}
+                        this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth, this.props.showHeaderFields, this.props.preventTreeViewOpen)}
             </ul >;
         } else if (this.treeViewExpandedView === "fields") {
             return <ul><div ref={this._dref} style={{ display: "inline-block" }} key={this.props.document[Id] + this.props.document.title}>
@@ -329,8 +338,8 @@ class TreeView extends React.Component<TreeViewProps> {
 
     @computed
     get renderBullet() {
-        return <div className="bullet" onClick={action(() => this._collapsed = !this._collapsed)} style={{ color: StrCast(this.props.document.color, "black"), opacity: 0.4 }}>
-            {<FontAwesomeIcon icon={this._collapsed ? (this.childDocs ? "caret-square-right" : "caret-right") : (this.childDocs ? "caret-square-down" : "caret-down")} />}
+        return <div className="bullet" onClick={action(() => this.treeViewOpen = !this.treeViewOpen)} style={{ color: StrCast(this.props.document.color, "black"), opacity: 0.4 }}>
+            {<FontAwesomeIcon icon={!this.treeViewOpen ? (this.childDocs ? "caret-square-right" : "caret-right") : (this.childDocs ? "caret-square-down" : "caret-down")} />}
         </div>;
     }
     /**
@@ -344,13 +353,13 @@ class TreeView extends React.Component<TreeViewProps> {
         let headerElements = (
             <span className="collectionTreeView-keyHeader" key={this.treeViewExpandedView}
                 onPointerDown={action(() => {
-                    if (!this._collapsed) {
+                    if (this.treeViewOpen) {
                         this.props.document.treeViewExpandedView = this.treeViewExpandedView === this.fieldKey ? "fields" :
                             this.treeViewExpandedView === "fields" && this.props.document.layout ? "layout" :
                                 this.treeViewExpandedView === "layout" && this.props.document.links ? "links" :
                                     this.childDocs ? this.fieldKey : "fields";
                     }
-                    this._collapsed = false;
+                    this.treeViewOpen = true;
                 })}>
                 {this.treeViewExpandedView}
             </span>);
@@ -368,7 +377,7 @@ class TreeView extends React.Component<TreeViewProps> {
                 }} >
                 {this.editableView("title")}
             </div >
-            {headerElements}
+            {this.props.showHeaderFields() ? headerElements : (null)}
             {openRight}
         </>;
     }
@@ -381,13 +390,13 @@ class TreeView extends React.Component<TreeViewProps> {
                     {this.renderTitle}
                 </div>
                 <div className="treeViewItem-border">
-                    {this._collapsed ? (null) : this.renderContent}
+                    {!this.treeViewOpen ? (null) : this.renderContent}
                 </div>
             </li>
         </div>;
     }
     public static GetChildElements(
-        docs: Doc[],
+        docList: Doc[],
         treeViewId: string,
         containingCollection: Doc,
         dataDoc: Doc | undefined,
@@ -402,8 +411,11 @@ class TreeView extends React.Component<TreeViewProps> {
         outerXf: () => { translateX: number, translateY: number },
         active: () => boolean,
         panelWidth: () => number,
-        renderDepth: number
+        renderDepth: number,
+        showHeaderFields: () => boolean,
+        preventTreeViewOpen: boolean
     ) {
+        let docs = docList.filter(child => !child.excludeFromLibrary);
         let viewSpecScript = Cast(containingCollection.viewSpecScript, ScriptField);
         if (viewSpecScript) {
             let script = viewSpecScript.script;
@@ -418,7 +430,7 @@ class TreeView extends React.Component<TreeViewProps> {
             });
         }
 
-        let descending = BoolCast(containingCollection.stackingHeadersSortDescending);
+        let descending = BoolCast(containingCollection.stackingHeadersSortDescending, true);
         docs.slice().sort(function (a, b): 1 | -1 {
             let descA = descending ? b : a;
             let descB = descending ? a : b;
@@ -448,11 +460,14 @@ class TreeView extends React.Component<TreeViewProps> {
             }
 
             let indent = i === 0 ? undefined : () => {
-                if (StrCast(docs[i - 1].layout).indexOf("CollectionView") !== -1) {
+                if (StrCast(docs[i - 1].layout).indexOf("fieldKey") !== -1) {
                     let fieldKeysub = StrCast(docs[i - 1].layout).split("fieldKey")[1];
                     let fieldKey = fieldKeysub.split("\"")[1];
-                    Doc.AddDocToList(docs[i - 1], fieldKey, child);
-                    remove(child);
+                    if (fieldKey && Cast(docs[i - 1][fieldKey], listSpec(Doc)) !== undefined) {
+                        Doc.AddDocToList(docs[i - 1], fieldKey, child);
+                        docs[i - 1].treeViewOpen = true;
+                        remove(child);
+                    }
                 }
             };
             let addDocument = (doc: Doc, relativeTo?: Doc, before?: boolean) => {
@@ -481,7 +496,9 @@ class TreeView extends React.Component<TreeViewProps> {
                 ScreenToLocalTransform={screenToLocalXf}
                 outerXf={outerXf}
                 parentKey={key}
-                active={active} />;
+                active={active}
+                showHeaderFields={showHeaderFields}
+                preventTreeViewOpen={preventTreeViewOpen} />;
         });
     }
 }
@@ -561,7 +578,7 @@ export class CollectionTreeView extends CollectionSubView(Document) {
     render() {
         Doc.UpdateDocumentExtensionForField(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey);
         let dropAction = StrCast(this.props.Document.dropAction) as dropActionType;
-        let addDoc = (doc: Doc, relativeTo?: Doc, before?: boolean) => Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, relativeTo, before, !BoolCast(this.props.Document.stackingHeadersSortDescending));
+        let addDoc = (doc: Doc, relativeTo?: Doc, before?: boolean) => Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, relativeTo, before, false, false, !BoolCast(this.props.Document.stackingHeadersSortDescending, true));
         let moveDoc = (d: Doc, target: Doc, addDoc: (doc: Doc) => boolean) => this.props.moveDocument(d, target, addDoc);
         return !this.childDocs ? (null) : (
             <div id="body" className="collectionTreeView-dropTarget"
@@ -575,20 +592,22 @@ export class CollectionTreeView extends CollectionSubView(Document) {
                     display={"block"}
                     height={72}
                     GetValue={() => StrCast(this.resolvedDataDoc.title)}
-                    SetValue={(value: string) => (Doc.GetProto(this.resolvedDataDoc).title = value) ? true : true}
-                    OnFillDown={(value: string) => {
+                    SetValue={undoBatch((value: string) => (Doc.GetProto(this.resolvedDataDoc).title = value) ? true : true)}
+                    OnFillDown={undoBatch((value: string) => {
                         Doc.GetProto(this.props.Document).title = value;
                         let doc = this.props.Document.detailedLayout instanceof Doc ? Doc.ApplyTemplate(Doc.GetProto(this.props.Document.detailedLayout)) : undefined;
                         if (!doc) doc = Docs.Create.FreeformDocument([], { title: "", x: 0, y: 0, width: 100, height: 25, templates: new List<string>([Templates.Title.Layout]) });
                         TreeView.loadId = doc[Id];
-                        Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, this.childDocs.length ? this.childDocs[0] : undefined, true, !BoolCast(this.props.Document.stackingHeadersSortDescending));
-                    }} />
+                        Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, this.childDocs.length ? this.childDocs[0] : undefined, true, false, false, !BoolCast(this.props.Document.stackingHeadersSortDescending, true));
+                    })} />
                 {this.props.Document.workspaceLibrary ? this.renderNotifsButton : (null)}
                 {this.props.Document.allowClear ? this.renderClearButton : (null)}
                 <ul className="no-indent" style={{ width: "max-content" }} >
                     {
                         TreeView.GetChildElements(this.childDocs, this.props.Document[Id], this.props.Document, this.props.DataDoc, this.props.fieldKey, addDoc, this.remove,
-                            moveDoc, dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform, this.outerXf, this.props.active, this.props.PanelWidth, this.props.renderDepth)
+                            moveDoc, dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform,
+                            this.outerXf, this.props.active, this.props.PanelWidth, this.props.renderDepth, () => this.props.Document.chromeStatus !== "disabled",
+                            BoolCast(this.props.Document.preventTreeViewOpen))
                     }
                 </ul>
             </div >
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 606e8edb0..3f3360eff 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -736,7 +736,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     @action
     tryUpdateHeight() {
         if (this.props.Document.autoHeight && this._ref.current!.scrollHeight !== 0) {
-            console.log("DT = " + this.props.Document.title + " " + this._ref.current!.clientHeight + " " + this._ref.current!.scrollHeight + " " + this._ref.current!.textContent);
             let xf = this._ref.current!.getBoundingClientRect();
             let scrBounds = this.props.ScreenToLocalTransform().transformBounds(0, 0, xf.width, this._ref.current!.textContent === "" ? 35 : this._ref.current!.scrollHeight);
             let nh = this.props.Document.isTemplate ? 0 : NumCast(this.dataDoc.nativeHeight, 0);
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 508655605..aa3da0034 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -10,7 +10,7 @@ import { CollectionView } from "../../../client/views/collections/CollectionView
 import { Doc } from "../../../new_fields/Doc";
 import { List } from "../../../new_fields/List";
 import { listSpec } from "../../../new_fields/Schema";
-import { Cast, StrCast } from "../../../new_fields/Types";
+import { Cast, StrCast, PromiseValue } from "../../../new_fields/Types";
 import { Utils } from "../../../Utils";
 import { RouteStore } from "../../RouteStore";
 
@@ -49,12 +49,14 @@ export class CurrentUserUtils {
             workspaces.boxShadow = "0 0";
             doc.workspaces = workspaces;
         }
+        PromiseValue(Cast(doc.workspaces, Doc)).then(workspaces => workspaces && (workspaces.preventTreeViewOpen = true));
         if (doc.recentlyClosed === undefined) {
             const recentlyClosed = Docs.Create.TreeDocument([], { title: "Recently Closed", height: 75 });
             recentlyClosed.excludeFromLibrary = true;
             recentlyClosed.boxShadow = "0 0";
             doc.recentlyClosed = recentlyClosed;
         }
+        PromiseValue(Cast(doc.recentlyClosed, Doc)).then(recent => recent && (recent.preventTreeViewOpen = true));
         if (doc.curPresentation === undefined) {
             const curPresentation = Docs.Create.PresDocument(new List<Doc>(), { title: "Presentation" });
             curPresentation.excludeFromLibrary = true;
@@ -73,6 +75,7 @@ export class CurrentUserUtils {
         }
         StrCast(doc.title).indexOf("@") !== -1 && (doc.title = StrCast(doc.title).split("@")[0] + "'s Library");
         doc.width = 100;
+        doc.preventTreeViewOpen = true;
     }
 
     public static loadCurrentUser() {
-- 
cgit v1.2.3-70-g09d2


From 7153da56f27226cc6f92848b4548032bd5e32170 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Wed, 21 Aug 2019 12:58:34 -0400
Subject: tweaks

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

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

diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index a2ea12ac5..f2ca0715a 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -274,7 +274,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         }
         e.stopPropagation();
         e.preventDefault();
-        e.which === 174 && e.altKey && this.recordBullet();
+        e.key === "R" && this.recordBullet();
     }
 
     recordBullet = async () => {
@@ -282,7 +282,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         let results = await DictationManager.Controls.listen({
             interimHandler: this.setCurrentBulletContent,
             continuous: { indefinite: false },
-            terminators: [completedCue, "bullet", "next"]
+            // terminators: [completedCue, "bullet", "next"]
         });
         if (results && [DictationManager.Controls.Infringed, completedCue].includes(results)) {
             DictationManager.Controls.stop();
-- 
cgit v1.2.3-70-g09d2


From 3535bd703cfdb4872ff6ff0c41d17e39ad1c8e45 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Wed, 21 Aug 2019 13:17:50 -0400
Subject: fixed updating size of textboxes

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

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

diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 0c39a57ff..505257e9c 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -272,9 +272,11 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         if (this.props.Document !== SelectionManager.SelectedDocuments()[0].props.Document) {
             return;
         }
-        e.stopPropagation();
-        e.preventDefault();
-        e.key === "R" && this.recordBullet();
+        if (e.key === "R" && e.altKey) {
+            e.stopPropagation();
+            e.preventDefault();
+            this.recordBullet();
+        }
     }
 
     recordBullet = async () => {
@@ -480,8 +482,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     updateState = (exportState: GoogleApiClientUtils.Docs.ReadResult, dataDoc: Doc) => {
         let pullSuccess = false;
         if (exportState !== undefined && exportState.body !== undefined && exportState.title !== undefined) {
-            let data = Cast(dataDoc.data, RichTextField);
-            if (data) {
+            const data = Cast(dataDoc.data, RichTextField);
+            if (data instanceof RichTextField) {
                 pullSuccess = true;
                 this.isGoogleDocsUpdate = true;
                 dataDoc.data = new RichTextField(data[FromPlainText](exportState.body));
@@ -492,7 +494,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             delete dataDoc[GoogleRef];
         }
         DocumentDecorations.Instance.startPullOutcome(pullSuccess);
-        this.tryUpdateHeight();
+        setTimeout(() => this.tryUpdateHeight(), 0);
     }
 
     checkState = (exportState: GoogleApiClientUtils.Docs.ReadResult, dataDoc: Doc) => {
@@ -630,7 +632,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             if (!this.props.isOverlay) this.props.select(false);
             else this._editorView!.focus();
         }
-        this.tryUpdateHeight();
     }
 
     componentWillUnmount() {
-- 
cgit v1.2.3-70-g09d2


From 76a693012866178a3fbe037ab06cfd4482f37917 Mon Sep 17 00:00:00 2001
From: monikahedman <monika_hedman@brown.edu>
Date: Wed, 21 Aug 2019 14:22:30 -0400
Subject: djlzdkfj

---
 src/client/views/nodes/FormattedTextBox.tsx |  7 +++++++
 src/client/views/nodes/WebBox.tsx           | 26 ++++++++++++++++----------
 2 files changed, 23 insertions(+), 10 deletions(-)

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

diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 25f611f19..d299bbf72 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -162,6 +162,13 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         }
     }
 
+    public setText = (text: string) => {
+        const tx = this._editorView!.state.tr.insertText(text);
+        const state = this._editorView!.state;
+        this._editorView!.dispatch(tx);
+        return new RichTextField(JSON.stringify(state.toJSON()));
+    }
+
     dispatchTransaction = (tx: Transaction) => {
         if (this._editorView) {
             const state = this._editorView.state.apply(tx);
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index c43b90f91..e7bed3bed 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -26,6 +26,8 @@ import { SelectionManager } from "../../util/SelectionManager";
 import { CollectionView } from "../collections/CollectionView";
 import { CollectionPDFView } from "../collections/CollectionPDFView";
 import { CollectionVideoView } from "../collections/CollectionVideoView";
+import { DocumentView } from "./DocumentView";
+import { FormattedTextBox } from "./FormattedTextBox";
 
 library.add(faStickyNote)
 
@@ -85,14 +87,15 @@ export class WebBox extends React.Component<FieldViewProps> {
         let field = Cast(this.props.Document[this.props.fieldKey], WebField);
         if (field) url = field.url.href;
 
-        let parent: Opt<CollectionView | CollectionPDFView | CollectionVideoView>;
+        let docView: DocumentView;
         // let parentDoc: any;
         SelectionManager.SelectedDocuments().map(dv => {
-            parent = dv.props.ContainingCollectionView;
-            // if(parent) parentDoc = parent.props.Document;
+            // docView = dv;
             dv.props.removeDocument && dv.props.removeDocument(dv.props.Document);
         });
 
+        console.log("happening")
+
         // // let newPoint = PreviewCursor._getTransform().transformPoint(PreviewCursor._clickPoint[0], PreviewCursor._clickPoint[1]);
         let newBox = Docs.Create.TextDocument({
             width: 200, height: 100,
@@ -103,13 +106,16 @@ export class WebBox extends React.Component<FieldViewProps> {
             title: url
         });
 
-        console.log(newBox)
-        if (parent) {
-            let parentDoc: Doc = parent.props.Document;
-            if (parentDoc && parentDoc.props) {
-                parentDoc.props.addDocument();
-            }
-        }
+        console.log(typeof newBox)
+
+        // const script = KeyValueBox.CompileKVPScript(`new RichTextField("{"doc":{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"${url}"}]}]},"selection":{"type":"text","anchor":1,"head":1}}")`);
+        // const script = KeyValueBox.CompileKVPScript(newBox.setText(url))
+        // console.log(script)
+        // if (!script) return;
+        // KeyValueBox.ApplyKVPScript(this.props.Document, "data", script);
+
+        console.log(newBox);
+
 
         newBox.proto!.autoHeight = true;
         // PreviewCursor._addLiveTextDoc(newBox);
-- 
cgit v1.2.3-70-g09d2


From e50dc35f922efdeadfde70c9cf552b9ed5722810 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Wed, 21 Aug 2019 14:35:35 -0400
Subject: uncommented terminators

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

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

diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 505257e9c..1e0975b4b 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -284,7 +284,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         let results = await DictationManager.Controls.listen({
             interimHandler: this.setCurrentBulletContent,
             continuous: { indefinite: false },
-            // terminators: [completedCue, "bullet", "next"]
+            terminators: [completedCue, "bullet", "next"]
         });
         if (results && [DictationManager.Controls.Infringed, completedCue].includes(results)) {
             DictationManager.Controls.stop();
-- 
cgit v1.2.3-70-g09d2


From 88454c8163115b1396a34f4836b5f6f04817a3f0 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Wed, 21 Aug 2019 16:11:20 -0400
Subject: tryupdateheight fixes

---
 src/client/views/DocumentDecorations.tsx    |  4 ++--
 src/client/views/nodes/FormattedTextBox.tsx | 26 +++++++++++++-------------
 2 files changed, 15 insertions(+), 15 deletions(-)

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

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index cc8e57af9..a28088032 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -714,7 +714,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         let canPull = this.targetDoc.data && this.targetDoc.data instanceof RichTextField;
         let dataDoc = Doc.GetProto(this.targetDoc);
         if (!canPull || !dataDoc[GoogleRef]) return (null);
-        let icon = !dataDoc.unchanged ? (this.pullIcon as any) : fetch;
+        let icon = dataDoc.unchanged === false ? (this.pullIcon as any) : fetch;
         icon = this.openHover ? "share" : icon;
         let animation = this.isAnimatingFetch ? "spin 0.5s linear infinite" : "none";
         let title = `${!dataDoc.unchanged ? "Pull from" : "Fetch"} Google Docs`;
@@ -727,7 +727,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                         backgroundColor: this.pullColor,
                         transition: "0.2s ease all"
                     }}
-                    onPointerEnter={e => e.ctrlKey && runInAction(() => this.openHover = true)}
+                    onPointerEnter={e => e.altKey && runInAction(() => this.openHover = true)}
                     onPointerLeave={() => runInAction(() => this.openHover = false)}
                     onClick={e => {
                         if (e.altKey) {
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 1e0975b4b..c8722c6e3 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -1,6 +1,6 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
 import { faEdit, faSmile, faTextHeight, faUpload } from '@fortawesome/free-solid-svg-icons';
-import { action, computed, IReactionDisposer, Lambda, observable, reaction, runInAction } from "mobx";
+import { action, computed, IReactionDisposer, Lambda, observable, reaction, runInAction, autorun } from "mobx";
 import { observer } from "mobx-react";
 import { baseKeymap } from "prosemirror-commands";
 import { history } from "prosemirror-history";
@@ -87,7 +87,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     private pushReactionDisposer: Opt<IReactionDisposer>;
     private dropDisposer?: DragManager.DragDropDisposer;
     public get CurrentDiv(): HTMLDivElement { return this._ref.current!; }
-    private isGoogleDocsUpdate = false;
     @observable _entered = false;
 
     @observable public static InputBoxOverlay?: FormattedTextBox = undefined;
@@ -321,7 +320,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     private newListItems = (count: number) => {
         let listItems: any[] = [];
         for (let i = 0; i < count; i++) {
-            listItems.push(schema.nodes.list_item.create(null, schema.nodes.paragraph.create(null)));
+            listItems.push(schema.nodes.list_item.create(undefined, schema.nodes.paragraph.create()));
         }
         return listItems;
     }
@@ -359,7 +358,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         }
 
         this.pullFromGoogleDoc(this.checkState);
-        runInAction(() => DocumentDecorations.Instance.isAnimatingFetch = true);
+        this.dataDoc[GoogleRef] && this.dataDoc.unchanged && runInAction(() => DocumentDecorations.Instance.isAnimatingFetch = true);
 
         this._reactionDisposer = reaction(
             () => {
@@ -370,13 +369,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 if (this._editorView && !this._applyingChange) {
                     let updatedState = JSON.parse(incomingValue);
                     this._editorView.updateState(EditorState.fromJSON(config, updatedState));
-                    // manually sets cursor selection at the end of the text on focus
-                    if (this.isGoogleDocsUpdate) {
-                        this.isGoogleDocsUpdate = false;
-                        let end = this._editorView.state.doc.content.size - 1;
-                        updatedState.selection = { type: "text", anchor: end, head: end };
-                        this._editorView.updateState(EditorState.fromJSON(config, updatedState));
-                    }
+                    this.tryUpdateHeight();
                 }
             }
         );
@@ -485,16 +478,22 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             const data = Cast(dataDoc.data, RichTextField);
             if (data instanceof RichTextField) {
                 pullSuccess = true;
-                this.isGoogleDocsUpdate = true;
                 dataDoc.data = new RichTextField(data[FromPlainText](exportState.body));
+                setTimeout(() => {
+                    if (this._editorView) {
+                        let state = this._editorView.state;
+                        let end = state.doc.content.size - 1;
+                        this._editorView.dispatch(state.tr.setSelection(TextSelection.create(state.doc, end, end)));
+                    }
+                }, 0);
                 dataDoc.title = exportState.title;
+                this.Document.customTitle = true;
                 dataDoc.unchanged = true;
             }
         } else {
             delete dataDoc[GoogleRef];
         }
         DocumentDecorations.Instance.startPullOutcome(pullSuccess);
-        setTimeout(() => this.tryUpdateHeight(), 0);
     }
 
     checkState = (exportState: GoogleApiClientUtils.Docs.ReadResult, dataDoc: Doc) => {
@@ -719,6 +718,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     onFocused = (e: React.FocusEvent): void => {
         document.removeEventListener("keypress", this.recordKeyHandler);
         document.addEventListener("keypress", this.recordKeyHandler);
+        this.tryUpdateHeight();
         if (!this.props.isOverlay) {
             FormattedTextBox.InputBoxOverlay = this;
         } else {
-- 
cgit v1.2.3-70-g09d2


From f4888ec4b2862cdf890ac1b0f6670b41398266df Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Wed, 21 Aug 2019 20:01:35 -0400
Subject: can create google slides presentations

---
 .../apis/google_docs/GoogleApiClientUtils.ts       | 99 +++++++++++++---------
 src/client/views/MainView.tsx                      |  4 +
 src/client/views/nodes/FormattedTextBox.tsx        | 20 ++---
 src/server/apis/google/GoogleApiServerUtils.ts     | 65 +++++++++-----
 src/server/index.ts                                | 19 ++---
 5 files changed, 123 insertions(+), 84 deletions(-)

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

diff --git a/src/client/apis/google_docs/GoogleApiClientUtils.ts b/src/client/apis/google_docs/GoogleApiClientUtils.ts
index 821c52270..52452cd95 100644
--- a/src/client/apis/google_docs/GoogleApiClientUtils.ts
+++ b/src/client/apis/google_docs/GoogleApiClientUtils.ts
@@ -1,4 +1,4 @@
-import { docs_v1 } from "googleapis";
+import { docs_v1, slides_v1 } from "googleapis";
 import { PostToServer } from "../../../Utils";
 import { RouteStore } from "../../../server/RouteStore";
 import { Opt } from "../../../new_fields/Doc";
@@ -9,47 +9,47 @@ export const Pushes = "googleDocsPushCount";
 
 export namespace GoogleApiClientUtils {
 
-    export namespace Docs {
+    export enum Actions {
+        Create = "create",
+        Retrieve = "retrieve",
+        Update = "update"
+    }
 
-        export enum Actions {
-            Create = "create",
-            Retrieve = "retrieve",
-            Update = "update"
-        }
+    export enum WriteMode {
+        Insert,
+        Replace
+    }
 
-        export enum WriteMode {
-            Insert,
-            Replace
-        }
+    export type DocumentId = string;
+    export type Reference = DocumentId | CreateOptions;
+    export type TextContent = string | string[];
+    export type IdHandler = (id: DocumentId) => any;
+    export type CreationResult = Opt<DocumentId>;
+    export type ReadLinesResult = Opt<{ title?: string, bodyLines?: string[] }>;
+    export type ReadResult = { title?: string, body?: string };
 
-        export type DocumentId = string;
-        export type Reference = DocumentId | CreateOptions;
-        export type TextContent = string | string[];
-        export type IdHandler = (id: DocumentId) => any;
+    export interface CreateOptions {
+        title?: string; // if excluded, will use a default title annotated with the current date
+    }
 
-        export type CreationResult = Opt<DocumentId>;
-        export type RetrievalResult = Opt<docs_v1.Schema$Document>;
-        export type UpdateResult = Opt<docs_v1.Schema$BatchUpdateDocumentResponse>;
-        export type ReadLinesResult = Opt<{ title?: string, bodyLines?: string[] }>;
-        export type ReadResult = { title?: string, body?: string };
+    export interface RetrieveOptions {
+        documentId: DocumentId;
+    }
 
-        export interface CreateOptions {
-            handler: IdHandler; // callback to process the documentId of the newly created Google Doc
-            title?: string; // if excluded, will use a default title annotated with the current date
-        }
+    export type ReadOptions = RetrieveOptions & { removeNewlines?: boolean };
 
-        export interface RetrieveOptions {
-            documentId: DocumentId;
-        }
+    export interface WriteOptions {
+        mode: WriteMode;
+        content: TextContent;
+        reference: Reference;
+        index?: number; // if excluded, will compute the last index of the document and append the content there
+    }
 
-        export type ReadOptions = RetrieveOptions & { removeNewlines?: boolean };
 
-        export interface WriteOptions {
-            mode: WriteMode;
-            content: TextContent;
-            reference: Reference;
-            index?: number; // if excluded, will compute the last index of the document and append the content there
-        }
+    export namespace Docs {
+
+        export type RetrievalResult = Opt<docs_v1.Schema$Document>;
+        export type UpdateResult = Opt<docs_v1.Schema$BatchUpdateDocumentResponse>;
 
         export interface UpdateOptions {
             documentId: DocumentId;
@@ -106,7 +106,7 @@ export namespace GoogleApiClientUtils {
          * @returns the documentId of the newly generated document, or undefined if the creation process fails.
          */
         export const create = async (options: CreateOptions): Promise<CreationResult> => {
-            const path = RouteStore.googleDocs + Actions.Create;
+            const path = RouteStore.googleDocs + "Documents/" + Actions.Create;
             const parameters = {
                 requestBody: {
                     title: options.title || `Dash Export (${new Date().toDateString()})`
@@ -115,17 +115,14 @@ export namespace GoogleApiClientUtils {
             try {
                 const schema: docs_v1.Schema$Document = await PostToServer(path, parameters);
                 const generatedId = schema.documentId;
-                if (generatedId) {
-                    options.handler(generatedId);
-                    return generatedId;
-                }
+                return generatedId;
             } catch {
                 return undefined;
             }
         };
 
         export const retrieve = async (options: RetrieveOptions): Promise<RetrievalResult> => {
-            const path = RouteStore.googleDocs + Actions.Retrieve;
+            const path = RouteStore.googleDocs + "Documents/" + Actions.Retrieve;
             try {
                 const schema: RetrievalResult = await PostToServer(path, options);
                 return schema;
@@ -135,7 +132,7 @@ export namespace GoogleApiClientUtils {
         };
 
         export const update = async (options: UpdateOptions): Promise<UpdateResult> => {
-            const path = RouteStore.googleDocs + Actions.Update;
+            const path = RouteStore.googleDocs + "Documents/" + Actions.Update;
             const parameters = {
                 documentId: options.documentId,
                 requestBody: {
@@ -221,4 +218,24 @@ export namespace GoogleApiClientUtils {
 
     }
 
+    export namespace Slides {
+
+        export const create = async (options: CreateOptions): Promise<CreationResult> => {
+            const path = RouteStore.googleDocs + "Slides/" + Actions.Create;
+            const parameters = {
+                requestBody: {
+                    title: options.title || `Dash Export (${new Date().toDateString()})`
+                }
+            };
+            try {
+                const schema: slides_v1.Schema$Presentation = await PostToServer(path, parameters);
+                const generatedId = schema.presentationId;
+                return generatedId;
+            } catch {
+                return undefined;
+            }
+        };
+
+    }
+
 }
\ No newline at end of file
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index f28844009..3490a0f68 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -40,6 +40,7 @@ import { PreviewCursor } from './PreviewCursor';
 import { FilterBox } from './search/FilterBox';
 import PresModeMenu from './presentationview/PresentationModeMenu';
 import { PresBox } from './nodes/PresBox';
+import { GoogleApiClientUtils } from '../apis/google_docs/GoogleApiClientUtils';
 
 @observer
 export class MainView extends React.Component {
@@ -120,6 +121,9 @@ export class MainView extends React.Component {
     componentWillMount() {
         var tag = document.createElement('script');
 
+        let title = "THIS IS MY FIRST DASH PRESENTATION";
+        GoogleApiClientUtils.Slides.create({ title }).then(id => console.log("We received this! ", id));
+
         tag.src = "https://www.youtube.com/iframe_api";
         var firstScriptTag = document.getElementsByTagName('script')[0];
         firstScriptTag.parentNode!.insertBefore(tag, firstScriptTag);
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index c8722c6e3..408a6948e 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -64,7 +64,7 @@ export const GoogleRef = "googleDocId";
 type RichTextDocument = makeInterface<[typeof richTextSchema]>;
 const RichTextDocument = makeInterface(richTextSchema);
 
-type PullHandler = (exportState: GoogleApiClientUtils.Docs.ReadResult, dataDoc: Doc) => void;
+type PullHandler = (exportState: GoogleApiClientUtils.ReadResult, dataDoc: Doc) => void;
 
 @observer
 export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTextBoxProps), RichTextDocument>(RichTextDocument) {
@@ -430,22 +430,20 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     }
 
     pushToGoogleDoc = async () => {
-        this.pullFromGoogleDoc(async (exportState: GoogleApiClientUtils.Docs.ReadResult, dataDoc: Doc) => {
-            let modes = GoogleApiClientUtils.Docs.WriteMode;
+        this.pullFromGoogleDoc(async (exportState: GoogleApiClientUtils.ReadResult, dataDoc: Doc) => {
+            let modes = GoogleApiClientUtils.WriteMode;
             let mode = modes.Replace;
-            let reference: Opt<GoogleApiClientUtils.Docs.Reference> = Cast(this.dataDoc[GoogleRef], "string");
+            let reference: Opt<GoogleApiClientUtils.Reference> = Cast(this.dataDoc[GoogleRef], "string");
             if (!reference) {
                 mode = modes.Insert;
-                reference = {
-                    title: StrCast(this.dataDoc.title),
-                    handler: id => this.dataDoc[GoogleRef] = id
-                };
+                reference = { title: StrCast(this.dataDoc.title) };
             }
             let redo = async () => {
                 let data = Cast(this.dataDoc.data, RichTextField);
                 if (this._editorView && reference && data) {
                     let content = data[ToPlainText]();
                     let response = await GoogleApiClientUtils.Docs.write({ reference, content, mode });
+                    response && (this.dataDoc[GoogleRef] = response.documentId);
                     let pushSuccess = response !== undefined && !("errors" in response);
                     dataDoc.unchanged = pushSuccess;
                     DocumentDecorations.Instance.startPushOutcome(pushSuccess);
@@ -465,14 +463,14 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     pullFromGoogleDoc = async (handler: PullHandler) => {
         let dataDoc = this.dataDoc;
         let documentId = StrCast(dataDoc[GoogleRef]);
-        let exportState: GoogleApiClientUtils.Docs.ReadResult = {};
+        let exportState: GoogleApiClientUtils.ReadResult = {};
         if (documentId) {
             exportState = await GoogleApiClientUtils.Docs.read({ documentId });
         }
         UndoManager.RunInBatch(() => handler(exportState, dataDoc), Pulls);
     }
 
-    updateState = (exportState: GoogleApiClientUtils.Docs.ReadResult, dataDoc: Doc) => {
+    updateState = (exportState: GoogleApiClientUtils.ReadResult, dataDoc: Doc) => {
         let pullSuccess = false;
         if (exportState !== undefined && exportState.body !== undefined && exportState.title !== undefined) {
             const data = Cast(dataDoc.data, RichTextField);
@@ -496,7 +494,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         DocumentDecorations.Instance.startPullOutcome(pullSuccess);
     }
 
-    checkState = (exportState: GoogleApiClientUtils.Docs.ReadResult, dataDoc: Doc) => {
+    checkState = (exportState: GoogleApiClientUtils.ReadResult, dataDoc: Doc) => {
         if (exportState !== undefined && exportState.body !== undefined && exportState.title !== undefined) {
             let data = Cast(dataDoc.data, RichTextField);
             if (data) {
diff --git a/src/server/apis/google/GoogleApiServerUtils.ts b/src/server/apis/google/GoogleApiServerUtils.ts
index 817b2b696..ff027c501 100644
--- a/src/server/apis/google/GoogleApiServerUtils.ts
+++ b/src/server/apis/google/GoogleApiServerUtils.ts
@@ -1,7 +1,10 @@
-import { google, docs_v1 } from "googleapis";
+import { google, docs_v1, slides_v1 } from "googleapis";
 import { createInterface } from "readline";
 import { readFile, writeFile } from "fs";
 import { OAuth2Client } from "google-auth-library";
+import { Opt } from "../../../new_fields/Doc";
+import { GlobalOptions } from "googleapis-common";
+import { GaxiosResponse } from "gaxios";
 
 /**
  * Server side authentication for Google Api queries.
@@ -13,38 +16,57 @@ export namespace GoogleApiServerUtils {
     const SCOPES = [
         'documents.readonly',
         'documents',
+        'presentations',
+        'presentations.readonly',
         'drive',
         'drive.file',
     ];
-    // The file token.json stores the user's access and refresh tokens, and is
-    // created automatically when the authorization flow completes for the first
-    // time.
+
     export const parseBuffer = (data: Buffer) => JSON.parse(data.toString());
 
-    export namespace Docs {
+    export enum Sector {
+        Documents = "Documents",
+        Slides = "Slides"
+    }
+
 
-        export interface CredentialPaths {
-            credentials: string;
-            token: string;
-        }
+    export interface CredentialPaths {
+        credentials: string;
+        token: string;
+    }
 
-        export type Endpoint = docs_v1.Docs;
+    export type ApiResponse = Promise<GaxiosResponse>;
+    export type ApiRouter = (endpoint: Endpoint, paramters: any) => ApiResponse;
+    export type ApiHandler = (parameters: any) => ApiResponse;
+    export type Action = "create" | "retrieve" | "update";
 
-        export const GetEndpoint = async (paths: CredentialPaths) => {
-            return new Promise<Endpoint>((resolve, reject) => {
-                readFile(paths.credentials, (err, credentials) => {
-                    if (err) {
-                        reject(err);
-                        return console.log('Error loading client secret file:', err);
+    export type Endpoint = { get: ApiHandler, create: ApiHandler, batchUpdate: ApiHandler };
+    export type EndpointParameters = GlobalOptions & { version: "v1" };
+
+    export const GetEndpoint = async (sector: string, paths: CredentialPaths) => {
+        return new Promise<Opt<Endpoint>>((resolve, reject) => {
+            readFile(paths.credentials, (err, credentials) => {
+                if (err) {
+                    reject(err);
+                    return console.log('Error loading client secret file:', err);
+                }
+                return authorize(parseBuffer(credentials), paths.token).then(auth => {
+                    let routed: Opt<Endpoint>;
+                    let parameters: EndpointParameters = { auth, version: "v1" };
+                    switch (sector) {
+                        case Sector.Documents:
+                            routed = google.docs(parameters).documents;
+                            break;
+                        case Sector.Slides:
+                            routed = google.slides(parameters).presentations;
+                            break;
                     }
-                    return authorize(parseBuffer(credentials), paths.token).then(auth => {
-                        resolve(google.docs({ version: "v1", auth }));
-                    });
+                    resolve(routed);
                 });
             });
-        };
+        });
+    };
 
-    }
 
     /**
      * Create an OAuth2 client with the given credentials, and returns the promise resolving to the authenticated client
@@ -105,5 +127,4 @@ export namespace GoogleApiServerUtils {
             });
         });
     }
-
 }
\ No newline at end of file
diff --git a/src/server/index.ts b/src/server/index.ts
index ef1829f30..6aecb875a 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -45,6 +45,7 @@ import { GoogleApiServerUtils } from "./apis/google/GoogleApiServerUtils";
 import { GaxiosResponse } from 'gaxios';
 import { Opt } from '../new_fields/Doc';
 import { docs_v1 } from 'googleapis';
+import { Endpoint } from 'googleapis-common';
 const MongoStore = require('connect-mongo')(session);
 const mongoose = require('mongoose');
 const probe = require("probe-image-size");
@@ -799,21 +800,19 @@ function HandleYoutubeQuery([query, callback]: [YoutubeQueryInput, (result?: any
 const credentials = path.join(__dirname, "./credentials/google_docs_credentials.json");
 const token = path.join(__dirname, "./credentials/google_docs_token.json");
 
-type ApiResponse = Promise<GaxiosResponse>;
-type ApiHandler = (endpoint: docs_v1.Resource$Documents, parameters: any) => ApiResponse;
-type Action = "create" | "retrieve" | "update";
-
-const EndpointHandlerMap = new Map<Action, ApiHandler>([
+const EndpointHandlerMap = new Map<GoogleApiServerUtils.Action, GoogleApiServerUtils.ApiRouter>([
     ["create", (api, params) => api.create(params)],
     ["retrieve", (api, params) => api.get(params)],
     ["update", (api, params) => api.batchUpdate(params)],
 ]);
 
-app.post(RouteStore.googleDocs + ":action", (req, res) => {
-    GoogleApiServerUtils.Docs.GetEndpoint({ credentials, token }).then(endpoint => {
-        let handler = EndpointHandlerMap.get(req.params.action);
-        if (handler) {
-            let execute = handler(endpoint.documents, req.body).then(
+app.post(RouteStore.googleDocs + ":sector/:action", (req, res) => {
+    let sector = req.params.sector;
+    let action = req.params.action;
+    GoogleApiServerUtils.GetEndpoint(GoogleApiServerUtils.Sector[sector], { credentials, token }).then(endpoint => {
+        let handler = EndpointHandlerMap.get(action);
+        if (endpoint && handler) {
+            let execute = handler(endpoint, req.body).then(
                 response => res.send(response.data),
                 rejection => res.send(rejection)
             );
-- 
cgit v1.2.3-70-g09d2


From 30d55c5b91329d32a79b7135fff3566b2cd0e822 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Wed, 21 Aug 2019 20:33:42 -0400
Subject: create is now shared between slides and docs

---
 .../apis/google_docs/GoogleApiClientUtils.ts       | 79 +++++++++-------------
 src/client/views/MainView.tsx                      |  2 +-
 src/client/views/nodes/FormattedTextBox.tsx        |  2 +-
 src/server/RouteStore.ts                           |  2 +-
 src/server/apis/google/GoogleApiServerUtils.ts     |  6 +-
 src/server/index.ts                                |  4 +-
 6 files changed, 40 insertions(+), 55 deletions(-)

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

diff --git a/src/client/apis/google_docs/GoogleApiClientUtils.ts b/src/client/apis/google_docs/GoogleApiClientUtils.ts
index 52452cd95..ea020ed00 100644
--- a/src/client/apis/google_docs/GoogleApiClientUtils.ts
+++ b/src/client/apis/google_docs/GoogleApiClientUtils.ts
@@ -9,6 +9,11 @@ export const Pushes = "googleDocsPushCount";
 
 export namespace GoogleApiClientUtils {
 
+    export enum Service {
+        Documents = "Documents",
+        Slides = "Slides"
+    }
+
     export enum Actions {
         Create = "create",
         Retrieve = "retrieve",
@@ -29,6 +34,7 @@ export namespace GoogleApiClientUtils {
     export type ReadResult = { title?: string, body?: string };
 
     export interface CreateOptions {
+        service: Service;
         title?: string; // if excluded, will use a default title annotated with the current date
     }
 
@@ -45,6 +51,30 @@ export namespace GoogleApiClientUtils {
         index?: number; // if excluded, will compute the last index of the document and append the content there
     }
 
+    /**
+    * After following the authentication routine, which connects this API call to the current signed in account
+    * and grants the appropriate permissions, this function programmatically creates an arbitrary Google Doc which
+    * should appear in the user's Google Doc library instantaneously.
+    * 
+    * @param options the title to assign to the new document, and the information necessary
+    * to store the new documentId returned from the creation process
+    * @returns the documentId of the newly generated document, or undefined if the creation process fails.
+    */
+    export const create = async (options: CreateOptions): Promise<CreationResult> => {
+        const path = `${RouteStore.googleDocs}/${options.service}/${Actions.Create}`;
+        const parameters = {
+            requestBody: {
+                title: options.title || `Dash Export (${new Date().toDateString()})`
+            }
+        };
+        try {
+            const schema: any = await PostToServer(path, parameters);
+            let key = ["document", "presentation"].find(prefix => `${prefix}Id` in schema) + "Id";
+            return schema[key];
+        } catch {
+            return undefined;
+        }
+    };
 
     export namespace Docs {
 
@@ -96,33 +126,8 @@ export namespace GoogleApiClientUtils {
 
         }
 
-        /**
-         * After following the authentication routine, which connects this API call to the current signed in account
-         * and grants the appropriate permissions, this function programmatically creates an arbitrary Google Doc which
-         * should appear in the user's Google Doc library instantaneously.
-         * 
-         * @param options the title to assign to the new document, and the information necessary
-         * to store the new documentId returned from the creation process
-         * @returns the documentId of the newly generated document, or undefined if the creation process fails.
-         */
-        export const create = async (options: CreateOptions): Promise<CreationResult> => {
-            const path = RouteStore.googleDocs + "Documents/" + Actions.Create;
-            const parameters = {
-                requestBody: {
-                    title: options.title || `Dash Export (${new Date().toDateString()})`
-                }
-            };
-            try {
-                const schema: docs_v1.Schema$Document = await PostToServer(path, parameters);
-                const generatedId = schema.documentId;
-                return generatedId;
-            } catch {
-                return undefined;
-            }
-        };
-
         export const retrieve = async (options: RetrieveOptions): Promise<RetrievalResult> => {
-            const path = RouteStore.googleDocs + "Documents/" + Actions.Retrieve;
+            const path = `${RouteStore.googleDocs}/${Service.Documents}/${Actions.Retrieve}`;
             try {
                 const schema: RetrievalResult = await PostToServer(path, options);
                 return schema;
@@ -132,7 +137,7 @@ export namespace GoogleApiClientUtils {
         };
 
         export const update = async (options: UpdateOptions): Promise<UpdateResult> => {
-            const path = RouteStore.googleDocs + "Documents/" + Actions.Update;
+            const path = `${RouteStore.googleDocs}/${Service.Documents}/${Actions.Update}`;
             const parameters = {
                 documentId: options.documentId,
                 requestBody: {
@@ -218,24 +223,4 @@ export namespace GoogleApiClientUtils {
 
     }
 
-    export namespace Slides {
-
-        export const create = async (options: CreateOptions): Promise<CreationResult> => {
-            const path = RouteStore.googleDocs + "Slides/" + Actions.Create;
-            const parameters = {
-                requestBody: {
-                    title: options.title || `Dash Export (${new Date().toDateString()})`
-                }
-            };
-            try {
-                const schema: slides_v1.Schema$Presentation = await PostToServer(path, parameters);
-                const generatedId = schema.presentationId;
-                return generatedId;
-            } catch {
-                return undefined;
-            }
-        };
-
-    }
-
 }
\ No newline at end of file
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 3490a0f68..2a219fdd1 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -122,7 +122,7 @@ export class MainView extends React.Component {
         var tag = document.createElement('script');
 
         let title = "THIS IS MY FIRST DASH PRESENTATION";
-        GoogleApiClientUtils.Slides.create({ title }).then(id => console.log("We received this! ", id));
+        GoogleApiClientUtils.create({ service: GoogleApiClientUtils.Service.Slides, title }).then(id => console.log("We received this! ", id));
 
         tag.src = "https://www.youtube.com/iframe_api";
         var firstScriptTag = document.getElementsByTagName('script')[0];
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 408a6948e..bcec6f7c3 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -436,7 +436,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             let reference: Opt<GoogleApiClientUtils.Reference> = Cast(this.dataDoc[GoogleRef], "string");
             if (!reference) {
                 mode = modes.Insert;
-                reference = { title: StrCast(this.dataDoc.title) };
+                reference = { service: GoogleApiClientUtils.Service.Documents, title: StrCast(this.dataDoc.title) };
             }
             let redo = async () => {
                 let data = Cast(this.dataDoc.data, RichTextField);
diff --git a/src/server/RouteStore.ts b/src/server/RouteStore.ts
index 5d977006a..014906054 100644
--- a/src/server/RouteStore.ts
+++ b/src/server/RouteStore.ts
@@ -31,6 +31,6 @@ export enum RouteStore {
 
     // APIS
     cognitiveServices = "/cognitiveservices",
-    googleDocs = "/googleDocs/"
+    googleDocs = "/googleDocs"
 
 }
\ No newline at end of file
diff --git a/src/server/apis/google/GoogleApiServerUtils.ts b/src/server/apis/google/GoogleApiServerUtils.ts
index ff027c501..8785cd974 100644
--- a/src/server/apis/google/GoogleApiServerUtils.ts
+++ b/src/server/apis/google/GoogleApiServerUtils.ts
@@ -24,7 +24,7 @@ export namespace GoogleApiServerUtils {
 
     export const parseBuffer = (data: Buffer) => JSON.parse(data.toString());
 
-    export enum Sector {
+    export enum Service {
         Documents = "Documents",
         Slides = "Slides"
     }
@@ -54,10 +54,10 @@ export namespace GoogleApiServerUtils {
                     let routed: Opt<Endpoint>;
                     let parameters: EndpointParameters = { auth, version: "v1" };
                     switch (sector) {
-                        case Sector.Documents:
+                        case Service.Documents:
                             routed = google.docs(parameters).documents;
                             break;
-                        case Sector.Slides:
+                        case Service.Slides:
                             routed = google.slides(parameters).presentations;
                             break;
                     }
diff --git a/src/server/index.ts b/src/server/index.ts
index 6aecb875a..0476bf3df 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -806,10 +806,10 @@ const EndpointHandlerMap = new Map<GoogleApiServerUtils.Action, GoogleApiServerU
     ["update", (api, params) => api.batchUpdate(params)],
 ]);
 
-app.post(RouteStore.googleDocs + ":sector/:action", (req, res) => {
+app.post(RouteStore.googleDocs + "/:sector/:action", (req, res) => {
     let sector = req.params.sector;
     let action = req.params.action;
-    GoogleApiServerUtils.GetEndpoint(GoogleApiServerUtils.Sector[sector], { credentials, token }).then(endpoint => {
+    GoogleApiServerUtils.GetEndpoint(GoogleApiServerUtils.Service[sector], { credentials, token }).then(endpoint => {
         let handler = EndpointHandlerMap.get(action);
         if (endpoint && handler) {
             let execute = handler(endpoint, req.body).then(
-- 
cgit v1.2.3-70-g09d2


From e68b1cc60b2a8ffe55eea3e1d5266d6897dcdcb6 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Wed, 21 Aug 2019 21:31:17 -0400
Subject: integrated slides

---
 .../apis/google_docs/GoogleApiClientUtils.ts       |    47 +-
 src/client/views/MainView.tsx                      |     3 -
 src/client/views/nodes/FormattedTextBox.tsx        |     2 +-
 src/server/slides.json                             | 10820 +++++++++++++++++++
 4 files changed, 10851 insertions(+), 21 deletions(-)
 create mode 100644 src/server/slides.json

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

diff --git a/src/client/apis/google_docs/GoogleApiClientUtils.ts b/src/client/apis/google_docs/GoogleApiClientUtils.ts
index ea020ed00..3e90adc4d 100644
--- a/src/client/apis/google_docs/GoogleApiClientUtils.ts
+++ b/src/client/apis/google_docs/GoogleApiClientUtils.ts
@@ -25,11 +25,11 @@ export namespace GoogleApiClientUtils {
         Replace
     }
 
-    export type DocumentId = string;
-    export type Reference = DocumentId | CreateOptions;
+    export type Identifier = string;
+    export type Reference = Identifier | CreateOptions;
     export type TextContent = string | string[];
-    export type IdHandler = (id: DocumentId) => any;
-    export type CreationResult = Opt<DocumentId>;
+    export type IdHandler = (id: Identifier) => any;
+    export type CreationResult = Opt<Identifier>;
     export type ReadLinesResult = Opt<{ title?: string, bodyLines?: string[] }>;
     export type ReadResult = { title?: string, body?: string };
 
@@ -39,10 +39,14 @@ export namespace GoogleApiClientUtils {
     }
 
     export interface RetrieveOptions {
-        documentId: DocumentId;
+        service: Service;
+        identifier: Identifier;
     }
 
-    export type ReadOptions = RetrieveOptions & { removeNewlines?: boolean };
+    export interface ReadOptions {
+        identifier: Identifier;
+        removeNewlines?: boolean;
+    }
 
     export interface WriteOptions {
         mode: WriteMode;
@@ -78,11 +82,11 @@ export namespace GoogleApiClientUtils {
 
     export namespace Docs {
 
-        export type RetrievalResult = Opt<docs_v1.Schema$Document>;
+        export type RetrievalResult = Opt<docs_v1.Schema$Document | slides_v1.Schema$Presentation>;
         export type UpdateResult = Opt<docs_v1.Schema$BatchUpdateDocumentResponse>;
 
         export interface UpdateOptions {
-            documentId: DocumentId;
+            documentId: Identifier;
             requests: docs_v1.Schema$Request[];
         }
 
@@ -126,11 +130,20 @@ export namespace GoogleApiClientUtils {
 
         }
 
+        const KeyMapping = new Map<Service, string>([
+            [Service.Documents, "documentId"],
+            [Service.Slides, "presentationId"]
+        ]);
+
         export const retrieve = async (options: RetrieveOptions): Promise<RetrievalResult> => {
-            const path = `${RouteStore.googleDocs}/${Service.Documents}/${Actions.Retrieve}`;
+            const path = `${RouteStore.googleDocs}/${options.service}/${Actions.Retrieve}`;
             try {
-                const schema: RetrievalResult = await PostToServer(path, options);
-                return schema;
+                let parameters: any = {}, key: string | undefined;
+                if ((key = KeyMapping.get(options.service))) {
+                    parameters[key] = options.identifier;
+                    const schema: RetrievalResult = await PostToServer(path, parameters);
+                    return schema;
+                }
             } catch {
                 return undefined;
             }
@@ -153,7 +166,7 @@ export namespace GoogleApiClientUtils {
         };
 
         export const read = async (options: ReadOptions): Promise<ReadResult> => {
-            return retrieve(options).then(document => {
+            return retrieve({ ...options, service: Service.Documents }).then(document => {
                 let result: ReadResult = {};
                 if (document) {
                     let title = document.title;
@@ -165,7 +178,7 @@ export namespace GoogleApiClientUtils {
         };
 
         export const readLines = async (options: ReadOptions): Promise<ReadLinesResult> => {
-            return retrieve(options).then(document => {
+            return retrieve({ ...options, service: Service.Documents }).then(document => {
                 let result: ReadLinesResult = {};
                 if (document) {
                     let title = document.title;
@@ -179,14 +192,14 @@ export namespace GoogleApiClientUtils {
 
         export const write = async (options: WriteOptions): Promise<UpdateResult> => {
             const requests: docs_v1.Schema$Request[] = [];
-            const documentId = await Utils.initialize(options.reference);
-            if (!documentId) {
+            const identifier = await Utils.initialize(options.reference);
+            if (!identifier) {
                 return undefined;
             }
             let index = options.index;
             const mode = options.mode;
             if (!(index && mode === WriteMode.Insert)) {
-                let schema = await retrieve({ documentId });
+                let schema = await retrieve({ identifier, service: Service.Documents });
                 if (!schema || !(index = Utils.endOf(schema))) {
                     return undefined;
                 }
@@ -212,7 +225,7 @@ export namespace GoogleApiClientUtils {
             if (!requests.length) {
                 return undefined;
             }
-            let replies: any = await update({ documentId, requests });
+            let replies: any = await update({ documentId: identifier, requests });
             let errors = "errors";
             if (errors in replies) {
                 console.log("Write operation failed:");
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 2a219fdd1..0286a201b 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -121,9 +121,6 @@ export class MainView extends React.Component {
     componentWillMount() {
         var tag = document.createElement('script');
 
-        let title = "THIS IS MY FIRST DASH PRESENTATION";
-        GoogleApiClientUtils.create({ service: GoogleApiClientUtils.Service.Slides, title }).then(id => console.log("We received this! ", id));
-
         tag.src = "https://www.youtube.com/iframe_api";
         var firstScriptTag = document.getElementsByTagName('script')[0];
         firstScriptTag.parentNode!.insertBefore(tag, firstScriptTag);
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index bcec6f7c3..a603676bb 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -465,7 +465,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         let documentId = StrCast(dataDoc[GoogleRef]);
         let exportState: GoogleApiClientUtils.ReadResult = {};
         if (documentId) {
-            exportState = await GoogleApiClientUtils.Docs.read({ documentId });
+            exportState = await GoogleApiClientUtils.Docs.read({ identifier: documentId });
         }
         UndoManager.RunInBatch(() => handler(exportState, dataDoc), Pulls);
     }
diff --git a/src/server/slides.json b/src/server/slides.json
new file mode 100644
index 000000000..323cac3a6
--- /dev/null
+++ b/src/server/slides.json
@@ -0,0 +1,10820 @@
+{
+    "presentationId": "1gHxyT6bBhsPVeuWNnWDzI33yEviMVo8n60JtZiVy3tY",
+    "pageSize": {
+        "width": {
+            "magnitude": 9144000,
+            "unit": "EMU"
+        },
+        "height": {
+            "magnitude": 5143500,
+            "unit": "EMU"
+        }
+    },
+    "slides": [
+        {
+            "objectId": "p",
+            "pageElements": [
+                {
+                    "objectId": "i0",
+                    "size": {
+                        "width": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        },
+                        "height": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        }
+                    },
+                    "transform": {
+                        "scaleX": 2.8402,
+                        "scaleY": 0.6842,
+                        "translateX": 311708.35000000003,
+                        "translateY": 744575,
+                        "unit": "EMU"
+                    },
+                    "shape": {
+                        "shapeType": "TEXT_BOX",
+                        "text": {
+                            "textElements": [
+                                {
+                                    "endIndex": 20,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        }
+                                    }
+                                },
+                                {
+                                    "endIndex": 20,
+                                    "textRun": {
+                                        "content": "Importing into Dash\n",
+                                        "style": {}
+                                    }
+                                }
+                            ]
+                        },
+                        "shapeProperties": {
+                            "shapeBackgroundFill": {
+                                "propertyState": "INHERIT"
+                            },
+                            "outline": {
+                                "propertyState": "INHERIT"
+                            },
+                            "shadow": {
+                                "propertyState": "INHERIT"
+                            }
+                        },
+                        "placeholder": {
+                            "type": "CENTERED_TITLE",
+                            "parentObjectId": "p2_i0"
+                        }
+                    }
+                },
+                {
+                    "objectId": "i1",
+                    "size": {
+                        "width": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        },
+                        "height": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        }
+                    },
+                    "transform": {
+                        "scaleX": 2.8402,
+                        "scaleY": 0.2642,
+                        "translateX": 311700,
+                        "translateY": 2834125,
+                        "unit": "EMU"
+                    },
+                    "shape": {
+                        "shapeType": "TEXT_BOX",
+                        "text": {
+                            "textElements": [
+                                {
+                                    "endIndex": 15,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        }
+                                    }
+                                },
+                                {
+                                    "endIndex": 15,
+                                    "textRun": {
+                                        "content": "By Sam Wilkins\n",
+                                        "style": {}
+                                    }
+                                }
+                            ]
+                        },
+                        "shapeProperties": {
+                            "shapeBackgroundFill": {
+                                "propertyState": "INHERIT"
+                            },
+                            "outline": {
+                                "propertyState": "INHERIT"
+                            },
+                            "shadow": {
+                                "propertyState": "INHERIT"
+                            }
+                        },
+                        "placeholder": {
+                            "type": "SUBTITLE",
+                            "parentObjectId": "p2_i1"
+                        }
+                    }
+                }
+            ],
+            "slideProperties": {
+                "layoutObjectId": "p2",
+                "masterObjectId": "simple-light-2",
+                "notesPage": {
+                    "objectId": "p:notes",
+                    "pageType": "NOTES",
+                    "pageElements": [
+                        {
+                            "objectId": "i2",
+                            "size": {
+                                "width": {
+                                    "magnitude": 3000000,
+                                    "unit": "EMU"
+                                },
+                                "height": {
+                                    "magnitude": 3000000,
+                                    "unit": "EMU"
+                                }
+                            },
+                            "transform": {
+                                "scaleX": 2.032025,
+                                "scaleY": 1.143,
+                                "translateX": 381300,
+                                "translateY": 685800,
+                                "unit": "EMU"
+                            },
+                            "shape": {
+                                "shapeProperties": {
+                                    "outline": {
+                                        "propertyState": "INHERIT"
+                                    }
+                                },
+                                "placeholder": {
+                                    "type": "SLIDE_IMAGE",
+                                    "parentObjectId": "n:slide"
+                                }
+                            }
+                        },
+                        {
+                            "objectId": "i3",
+                            "size": {
+                                "width": {
+                                    "magnitude": 3000000,
+                                    "unit": "EMU"
+                                },
+                                "height": {
+                                    "magnitude": 3000000,
+                                    "unit": "EMU"
+                                }
+                            },
+                            "transform": {
+                                "scaleX": 1.8288,
+                                "scaleY": 1.3716,
+                                "translateX": 685800,
+                                "translateY": 4343400,
+                                "unit": "EMU"
+                            },
+                            "shape": {
+                                "shapeType": "TEXT_BOX",
+                                "shapeProperties": {
+                                    "shapeBackgroundFill": {
+                                        "propertyState": "INHERIT"
+                                    },
+                                    "outline": {
+                                        "propertyState": "INHERIT"
+                                    },
+                                    "shadow": {
+                                        "propertyState": "INHERIT"
+                                    }
+                                },
+                                "placeholder": {
+                                    "type": "BODY",
+                                    "index": 1,
+                                    "parentObjectId": "n:text"
+                                }
+                            }
+                        }
+                    ],
+                    "pageProperties": {
+                        "pageBackgroundFill": {
+                            "propertyState": "INHERIT"
+                        }
+                    },
+                    "notesProperties": {
+                        "speakerNotesObjectId": "i3"
+                    }
+                }
+            },
+            "pageProperties": {
+                "pageBackgroundFill": {
+                    "propertyState": "INHERIT"
+                }
+            }
+        },
+        {
+            "objectId": "g5f40953d50_0_0",
+            "pageElements": [
+                {
+                    "objectId": "g5f40953d50_0_1",
+                    "size": {
+                        "width": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        },
+                        "height": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        }
+                    },
+                    "transform": {
+                        "scaleX": 2.8402,
+                        "scaleY": 0.1909,
+                        "translateX": 311700,
+                        "translateY": 445025,
+                        "unit": "EMU"
+                    },
+                    "shape": {
+                        "shapeType": "TEXT_BOX",
+                        "text": {
+                            "textElements": [
+                                {
+                                    "endIndex": 10,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        }
+                                    }
+                                },
+                                {
+                                    "endIndex": 10,
+                                    "textRun": {
+                                        "content": "Dr. Seuss\n",
+                                        "style": {}
+                                    }
+                                }
+                            ]
+                        },
+                        "shapeProperties": {
+                            "shapeBackgroundFill": {
+                                "propertyState": "INHERIT"
+                            },
+                            "outline": {
+                                "propertyState": "INHERIT"
+                            },
+                            "shadow": {
+                                "propertyState": "INHERIT"
+                            }
+                        },
+                        "placeholder": {
+                            "type": "TITLE",
+                            "parentObjectId": "p4_i0"
+                        }
+                    }
+                },
+                {
+                    "objectId": "g5f40953d50_0_2",
+                    "size": {
+                        "width": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        },
+                        "height": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        }
+                    },
+                    "transform": {
+                        "scaleX": 2.8402,
+                        "scaleY": 1.1388,
+                        "translateX": 311700,
+                        "translateY": 1152475,
+                        "unit": "EMU"
+                    },
+                    "shape": {
+                        "shapeType": "TEXT_BOX",
+                        "text": {
+                            "textElements": [
+                                {
+                                    "endIndex": 25,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "indentStart": {
+                                                "unit": "PT"
+                                            },
+                                            "indentFirstLine": {
+                                                "unit": "PT"
+                                            },
+                                            "direction": "LEFT_TO_RIGHT"
+                                        }
+                                    }
+                                },
+                                {
+                                    "endIndex": 25,
+                                    "textRun": {
+                                        "content": "Here is a bulleted list!\n",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 25,
+                                    "endIndex": 34,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "indentStart": {
+                                                "magnitude": 36,
+                                                "unit": "PT"
+                                            },
+                                            "indentFirstLine": {
+                                                "magnitude": 18,
+                                                "unit": "PT"
+                                            },
+                                            "direction": "LEFT_TO_RIGHT",
+                                            "spacingMode": "COLLAPSE_LISTS"
+                                        },
+                                        "bullet": {
+                                            "listId": "kix.wifbmqnyqu4p",
+                                            "glyph": "●",
+                                            "bulletStyle": {
+                                                "underline": false
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 25,
+                                    "endIndex": 34,
+                                    "textRun": {
+                                        "content": "One fish\n",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 34,
+                                    "endIndex": 43,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "indentStart": {
+                                                "magnitude": 36,
+                                                "unit": "PT"
+                                            },
+                                            "indentFirstLine": {
+                                                "magnitude": 18,
+                                                "unit": "PT"
+                                            },
+                                            "direction": "LEFT_TO_RIGHT",
+                                            "spacingMode": "COLLAPSE_LISTS"
+                                        },
+                                        "bullet": {
+                                            "listId": "kix.wifbmqnyqu4p",
+                                            "glyph": "●",
+                                            "bulletStyle": {
+                                                "underline": false
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 34,
+                                    "endIndex": 43,
+                                    "textRun": {
+                                        "content": "Two fish\n",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 43,
+                                    "endIndex": 52,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "indentStart": {
+                                                "magnitude": 36,
+                                                "unit": "PT"
+                                            },
+                                            "indentFirstLine": {
+                                                "magnitude": 18,
+                                                "unit": "PT"
+                                            },
+                                            "direction": "LEFT_TO_RIGHT",
+                                            "spacingMode": "COLLAPSE_LISTS"
+                                        },
+                                        "bullet": {
+                                            "listId": "kix.wifbmqnyqu4p",
+                                            "glyph": "●",
+                                            "bulletStyle": {
+                                                "underline": false
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 43,
+                                    "endIndex": 52,
+                                    "textRun": {
+                                        "content": "Red fish\n",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 52,
+                                    "endIndex": 62,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "indentStart": {
+                                                "magnitude": 36,
+                                                "unit": "PT"
+                                            },
+                                            "indentFirstLine": {
+                                                "magnitude": 18,
+                                                "unit": "PT"
+                                            },
+                                            "direction": "LEFT_TO_RIGHT",
+                                            "spacingMode": "COLLAPSE_LISTS"
+                                        },
+                                        "bullet": {
+                                            "listId": "kix.wifbmqnyqu4p",
+                                            "glyph": "●",
+                                            "bulletStyle": {
+                                                "underline": false
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 52,
+                                    "endIndex": 62,
+                                    "textRun": {
+                                        "content": "Blue fish\n",
+                                        "style": {}
+                                    }
+                                }
+                            ],
+                            "lists": {
+                                "kix.wifbmqnyqu4p": {
+                                    "listId": "kix.wifbmqnyqu4p",
+                                    "nestingLevel": {
+                                        "0": {
+                                            "bulletStyle": {
+                                                "underline": false
+                                            }
+                                        },
+                                        "1": {
+                                            "bulletStyle": {
+                                                "underline": false
+                                            }
+                                        },
+                                        "2": {
+                                            "bulletStyle": {
+                                                "underline": false
+                                            }
+                                        },
+                                        "3": {
+                                            "bulletStyle": {
+                                                "underline": false
+                                            }
+                                        },
+                                        "4": {
+                                            "bulletStyle": {
+                                                "underline": false
+                                            }
+                                        },
+                                        "5": {
+                                            "bulletStyle": {
+                                                "underline": false
+                                            }
+                                        },
+                                        "6": {
+                                            "bulletStyle": {
+                                                "underline": false
+                                            }
+                                        },
+                                        "7": {
+                                            "bulletStyle": {
+                                                "underline": false
+                                            }
+                                        },
+                                        "8": {
+                                            "bulletStyle": {
+                                                "underline": false
+                                            }
+                                        }
+                                    }
+                                },
+                                "kix.yuy8atv38lqp": {
+                                    "listId": "kix.yuy8atv38lqp",
+                                    "nestingLevel": {
+                                        "0": {
+                                            "bulletStyle": {
+                                                "underline": false
+                                            }
+                                        },
+                                        "1": {
+                                            "bulletStyle": {
+                                                "underline": false
+                                            }
+                                        },
+                                        "2": {
+                                            "bulletStyle": {
+                                                "underline": false
+                                            }
+                                        },
+                                        "3": {
+                                            "bulletStyle": {
+                                                "underline": false
+                                            }
+                                        },
+                                        "4": {
+                                            "bulletStyle": {
+                                                "underline": false
+                                            }
+                                        },
+                                        "5": {
+                                            "bulletStyle": {
+                                                "underline": false
+                                            }
+                                        },
+                                        "6": {
+                                            "bulletStyle": {
+                                                "underline": false
+                                            }
+                                        },
+                                        "7": {
+                                            "bulletStyle": {
+                                                "underline": false
+                                            }
+                                        },
+                                        "8": {
+                                            "bulletStyle": {
+                                                "underline": false
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        },
+                        "shapeProperties": {
+                            "shapeBackgroundFill": {
+                                "propertyState": "INHERIT"
+                            },
+                            "outline": {
+                                "propertyState": "INHERIT"
+                            },
+                            "shadow": {
+                                "propertyState": "INHERIT"
+                            }
+                        },
+                        "placeholder": {
+                            "type": "BODY",
+                            "parentObjectId": "p4_i1"
+                        }
+                    }
+                }
+            ],
+            "slideProperties": {
+                "layoutObjectId": "p4",
+                "masterObjectId": "simple-light-2",
+                "notesPage": {
+                    "objectId": "g5f40953d50_0_0:notes",
+                    "pageType": "NOTES",
+                    "pageElements": [
+                        {
+                            "objectId": "g5f40953d50_0_3",
+                            "size": {
+                                "width": {
+                                    "magnitude": 3000000,
+                                    "unit": "EMU"
+                                },
+                                "height": {
+                                    "magnitude": 3000000,
+                                    "unit": "EMU"
+                                }
+                            },
+                            "transform": {
+                                "scaleX": 2.032,
+                                "scaleY": 1.143,
+                                "translateX": 381300,
+                                "translateY": 685800,
+                                "unit": "EMU"
+                            },
+                            "shape": {
+                                "shapeProperties": {
+                                    "outline": {
+                                        "propertyState": "INHERIT"
+                                    }
+                                },
+                                "placeholder": {
+                                    "type": "SLIDE_IMAGE",
+                                    "parentObjectId": "n:slide"
+                                }
+                            }
+                        },
+                        {
+                            "objectId": "g5f40953d50_0_4",
+                            "size": {
+                                "width": {
+                                    "magnitude": 3000000,
+                                    "unit": "EMU"
+                                },
+                                "height": {
+                                    "magnitude": 3000000,
+                                    "unit": "EMU"
+                                }
+                            },
+                            "transform": {
+                                "scaleX": 1.8288,
+                                "scaleY": 1.3716,
+                                "translateX": 685800,
+                                "translateY": 4343400,
+                                "unit": "EMU"
+                            },
+                            "shape": {
+                                "shapeType": "TEXT_BOX",
+                                "shapeProperties": {
+                                    "shapeBackgroundFill": {
+                                        "propertyState": "INHERIT"
+                                    },
+                                    "outline": {
+                                        "propertyState": "INHERIT"
+                                    },
+                                    "shadow": {
+                                        "propertyState": "INHERIT"
+                                    }
+                                },
+                                "placeholder": {
+                                    "type": "BODY",
+                                    "index": 1,
+                                    "parentObjectId": "n:text"
+                                }
+                            }
+                        }
+                    ],
+                    "pageProperties": {
+                        "pageBackgroundFill": {
+                            "propertyState": "INHERIT"
+                        }
+                    },
+                    "notesProperties": {
+                        "speakerNotesObjectId": "g5f40953d50_0_4"
+                    }
+                }
+            },
+            "pageProperties": {
+                "pageBackgroundFill": {
+                    "propertyState": "INHERIT"
+                }
+            }
+        }
+    ],
+    "title": "THIS IS MY FIRST DASH PRESENTATION",
+    "masters": [
+        {
+            "objectId": "simple-light-2",
+            "pageType": "MASTER",
+            "pageElements": [
+                {
+                    "objectId": "p1_i0",
+                    "size": {
+                        "width": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        },
+                        "height": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        }
+                    },
+                    "transform": {
+                        "scaleX": 2.8402,
+                        "scaleY": 0.1909,
+                        "translateX": 311700,
+                        "translateY": 445025,
+                        "unit": "EMU"
+                    },
+                    "shape": {
+                        "shapeType": "TEXT_BOX",
+                        "text": {
+                            "textElements": [
+                                {
+                                    "endIndex": 1,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "lineSpacing": 100,
+                                            "alignment": "START",
+                                            "indentStart": {
+                                                "unit": "PT"
+                                            },
+                                            "indentEnd": {
+                                                "unit": "PT"
+                                            },
+                                            "spaceAbove": {
+                                                "unit": "PT"
+                                            },
+                                            "spaceBelow": {
+                                                "unit": "PT"
+                                            },
+                                            "indentFirstLine": {
+                                                "unit": "PT"
+                                            },
+                                            "direction": "LEFT_TO_RIGHT",
+                                            "spacingMode": "NEVER_COLLAPSE"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "endIndex": 1,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "backgroundColor": {},
+                                            "foregroundColor": {
+                                                "opaqueColor": {
+                                                    "themeColor": "DARK1"
+                                                }
+                                            },
+                                            "bold": false,
+                                            "italic": false,
+                                            "fontFamily": "Arial",
+                                            "fontSize": {
+                                                "magnitude": 28,
+                                                "unit": "PT"
+                                            },
+                                            "baselineOffset": "NONE",
+                                            "smallCaps": false,
+                                            "strikethrough": false,
+                                            "underline": false,
+                                            "weightedFontFamily": {
+                                                "fontFamily": "Arial",
+                                                "weight": 400
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 1,
+                                    "endIndex": 2,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "lineSpacing": 100,
+                                            "alignment": "START",
+                                            "indentStart": {
+                                                "unit": "PT"
+                                            },
+                                            "indentEnd": {
+                                                "unit": "PT"
+                                            },
+                                            "spaceAbove": {
+                                                "unit": "PT"
+                                            },
+                                            "spaceBelow": {
+                                                "unit": "PT"
+                                            },
+                                            "indentFirstLine": {
+                                                "unit": "PT"
+                                            },
+                                            "direction": "LEFT_TO_RIGHT",
+                                            "spacingMode": "NEVER_COLLAPSE"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 1,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 1,
+                                    "endIndex": 2,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "backgroundColor": {},
+                                            "foregroundColor": {
+                                                "opaqueColor": {
+                                                    "themeColor": "DARK1"
+                                                }
+                                            },
+                                            "bold": false,
+                                            "italic": false,
+                                            "fontFamily": "Arial",
+                                            "fontSize": {
+                                                "magnitude": 28,
+                                                "unit": "PT"
+                                            },
+                                            "baselineOffset": "NONE",
+                                            "smallCaps": false,
+                                            "strikethrough": false,
+                                            "underline": false,
+                                            "weightedFontFamily": {
+                                                "fontFamily": "Arial",
+                                                "weight": 400
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 2,
+                                    "endIndex": 3,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "lineSpacing": 100,
+                                            "alignment": "START",
+                                            "indentStart": {
+                                                "unit": "PT"
+                                            },
+                                            "indentEnd": {
+                                                "unit": "PT"
+                                            },
+                                            "spaceAbove": {
+                                                "unit": "PT"
+                                            },
+                                            "spaceBelow": {
+                                                "unit": "PT"
+                                            },
+                                            "indentFirstLine": {
+                                                "unit": "PT"
+                                            },
+                                            "direction": "LEFT_TO_RIGHT",
+                                            "spacingMode": "NEVER_COLLAPSE"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 2,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 2,
+                                    "endIndex": 3,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "backgroundColor": {},
+                                            "foregroundColor": {
+                                                "opaqueColor": {
+                                                    "themeColor": "DARK1"
+                                                }
+                                            },
+                                            "bold": false,
+                                            "italic": false,
+                                            "fontFamily": "Arial",
+                                            "fontSize": {
+                                                "magnitude": 28,
+                                                "unit": "PT"
+                                            },
+                                            "baselineOffset": "NONE",
+                                            "smallCaps": false,
+                                            "strikethrough": false,
+                                            "underline": false,
+                                            "weightedFontFamily": {
+                                                "fontFamily": "Arial",
+                                                "weight": 400
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 3,
+                                    "endIndex": 4,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "lineSpacing": 100,
+                                            "alignment": "START",
+                                            "indentStart": {
+                                                "unit": "PT"
+                                            },
+                                            "indentEnd": {
+                                                "unit": "PT"
+                                            },
+                                            "spaceAbove": {
+                                                "unit": "PT"
+                                            },
+                                            "spaceBelow": {
+                                                "unit": "PT"
+                                            },
+                                            "indentFirstLine": {
+                                                "unit": "PT"
+                                            },
+                                            "direction": "LEFT_TO_RIGHT",
+                                            "spacingMode": "NEVER_COLLAPSE"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 3,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 3,
+                                    "endIndex": 4,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "backgroundColor": {},
+                                            "foregroundColor": {
+                                                "opaqueColor": {
+                                                    "themeColor": "DARK1"
+                                                }
+                                            },
+                                            "bold": false,
+                                            "italic": false,
+                                            "fontFamily": "Arial",
+                                            "fontSize": {
+                                                "magnitude": 28,
+                                                "unit": "PT"
+                                            },
+                                            "baselineOffset": "NONE",
+                                            "smallCaps": false,
+                                            "strikethrough": false,
+                                            "underline": false,
+                                            "weightedFontFamily": {
+                                                "fontFamily": "Arial",
+                                                "weight": 400
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 4,
+                                    "endIndex": 5,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "lineSpacing": 100,
+                                            "alignment": "START",
+                                            "indentStart": {
+                                                "unit": "PT"
+                                            },
+                                            "indentEnd": {
+                                                "unit": "PT"
+                                            },
+                                            "spaceAbove": {
+                                                "unit": "PT"
+                                            },
+                                            "spaceBelow": {
+                                                "unit": "PT"
+                                            },
+                                            "indentFirstLine": {
+                                                "unit": "PT"
+                                            },
+                                            "direction": "LEFT_TO_RIGHT",
+                                            "spacingMode": "NEVER_COLLAPSE"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 4,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 4,
+                                    "endIndex": 5,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "backgroundColor": {},
+                                            "foregroundColor": {
+                                                "opaqueColor": {
+                                                    "themeColor": "DARK1"
+                                                }
+                                            },
+                                            "bold": false,
+                                            "italic": false,
+                                            "fontFamily": "Arial",
+                                            "fontSize": {
+                                                "magnitude": 28,
+                                                "unit": "PT"
+                                            },
+                                            "baselineOffset": "NONE",
+                                            "smallCaps": false,
+                                            "strikethrough": false,
+                                            "underline": false,
+                                            "weightedFontFamily": {
+                                                "fontFamily": "Arial",
+                                                "weight": 400
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 5,
+                                    "endIndex": 6,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "lineSpacing": 100,
+                                            "alignment": "START",
+                                            "indentStart": {
+                                                "unit": "PT"
+                                            },
+                                            "indentEnd": {
+                                                "unit": "PT"
+                                            },
+                                            "spaceAbove": {
+                                                "unit": "PT"
+                                            },
+                                            "spaceBelow": {
+                                                "unit": "PT"
+                                            },
+                                            "indentFirstLine": {
+                                                "unit": "PT"
+                                            },
+                                            "direction": "LEFT_TO_RIGHT",
+                                            "spacingMode": "NEVER_COLLAPSE"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 5,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 5,
+                                    "endIndex": 6,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "backgroundColor": {},
+                                            "foregroundColor": {
+                                                "opaqueColor": {
+                                                    "themeColor": "DARK1"
+                                                }
+                                            },
+                                            "bold": false,
+                                            "italic": false,
+                                            "fontFamily": "Arial",
+                                            "fontSize": {
+                                                "magnitude": 28,
+                                                "unit": "PT"
+                                            },
+                                            "baselineOffset": "NONE",
+                                            "smallCaps": false,
+                                            "strikethrough": false,
+                                            "underline": false,
+                                            "weightedFontFamily": {
+                                                "fontFamily": "Arial",
+                                                "weight": 400
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 6,
+                                    "endIndex": 7,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "lineSpacing": 100,
+                                            "alignment": "START",
+                                            "indentStart": {
+                                                "unit": "PT"
+                                            },
+                                            "indentEnd": {
+                                                "unit": "PT"
+                                            },
+                                            "spaceAbove": {
+                                                "unit": "PT"
+                                            },
+                                            "spaceBelow": {
+                                                "unit": "PT"
+                                            },
+                                            "indentFirstLine": {
+                                                "unit": "PT"
+                                            },
+                                            "direction": "LEFT_TO_RIGHT",
+                                            "spacingMode": "NEVER_COLLAPSE"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 6,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 6,
+                                    "endIndex": 7,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "backgroundColor": {},
+                                            "foregroundColor": {
+                                                "opaqueColor": {
+                                                    "themeColor": "DARK1"
+                                                }
+                                            },
+                                            "bold": false,
+                                            "italic": false,
+                                            "fontFamily": "Arial",
+                                            "fontSize": {
+                                                "magnitude": 28,
+                                                "unit": "PT"
+                                            },
+                                            "baselineOffset": "NONE",
+                                            "smallCaps": false,
+                                            "strikethrough": false,
+                                            "underline": false,
+                                            "weightedFontFamily": {
+                                                "fontFamily": "Arial",
+                                                "weight": 400
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 7,
+                                    "endIndex": 8,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "lineSpacing": 100,
+                                            "alignment": "START",
+                                            "indentStart": {
+                                                "unit": "PT"
+                                            },
+                                            "indentEnd": {
+                                                "unit": "PT"
+                                            },
+                                            "spaceAbove": {
+                                                "unit": "PT"
+                                            },
+                                            "spaceBelow": {
+                                                "unit": "PT"
+                                            },
+                                            "indentFirstLine": {
+                                                "unit": "PT"
+                                            },
+                                            "direction": "LEFT_TO_RIGHT",
+                                            "spacingMode": "NEVER_COLLAPSE"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 7,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 7,
+                                    "endIndex": 8,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "backgroundColor": {},
+                                            "foregroundColor": {
+                                                "opaqueColor": {
+                                                    "themeColor": "DARK1"
+                                                }
+                                            },
+                                            "bold": false,
+                                            "italic": false,
+                                            "fontFamily": "Arial",
+                                            "fontSize": {
+                                                "magnitude": 28,
+                                                "unit": "PT"
+                                            },
+                                            "baselineOffset": "NONE",
+                                            "smallCaps": false,
+                                            "strikethrough": false,
+                                            "underline": false,
+                                            "weightedFontFamily": {
+                                                "fontFamily": "Arial",
+                                                "weight": 400
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 8,
+                                    "endIndex": 9,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "lineSpacing": 100,
+                                            "alignment": "START",
+                                            "indentStart": {
+                                                "unit": "PT"
+                                            },
+                                            "indentEnd": {
+                                                "unit": "PT"
+                                            },
+                                            "spaceAbove": {
+                                                "unit": "PT"
+                                            },
+                                            "spaceBelow": {
+                                                "unit": "PT"
+                                            },
+                                            "indentFirstLine": {
+                                                "unit": "PT"
+                                            },
+                                            "direction": "LEFT_TO_RIGHT",
+                                            "spacingMode": "NEVER_COLLAPSE"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 8,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 8,
+                                    "endIndex": 9,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "backgroundColor": {},
+                                            "foregroundColor": {
+                                                "opaqueColor": {
+                                                    "themeColor": "DARK1"
+                                                }
+                                            },
+                                            "bold": false,
+                                            "italic": false,
+                                            "fontFamily": "Arial",
+                                            "fontSize": {
+                                                "magnitude": 28,
+                                                "unit": "PT"
+                                            },
+                                            "baselineOffset": "NONE",
+                                            "smallCaps": false,
+                                            "strikethrough": false,
+                                            "underline": false,
+                                            "weightedFontFamily": {
+                                                "fontFamily": "Arial",
+                                                "weight": 400
+                                            }
+                                        }
+                                    }
+                                }
+                            ],
+                            "lists": {
+                                "bodyPlaceholderListEntity": {
+                                    "listId": "bodyPlaceholderListEntity",
+                                    "nestingLevel": {
+                                        "0": {
+                                            "bulletStyle": {
+                                                "backgroundColor": {},
+                                                "foregroundColor": {
+                                                    "opaqueColor": {
+                                                        "themeColor": "DARK1"
+                                                    }
+                                                },
+                                                "bold": false,
+                                                "italic": false,
+                                                "fontFamily": "Arial",
+                                                "fontSize": {
+                                                    "magnitude": 28,
+                                                    "unit": "PT"
+                                                },
+                                                "baselineOffset": "NONE",
+                                                "smallCaps": false,
+                                                "strikethrough": false,
+                                                "underline": false,
+                                                "weightedFontFamily": {
+                                                    "fontFamily": "Arial",
+                                                    "weight": 400
+                                                }
+                                            }
+                                        },
+                                        "1": {
+                                            "bulletStyle": {
+                                                "backgroundColor": {},
+                                                "foregroundColor": {
+                                                    "opaqueColor": {
+                                                        "themeColor": "DARK1"
+                                                    }
+                                                },
+                                                "bold": false,
+                                                "italic": false,
+                                                "fontFamily": "Arial",
+                                                "fontSize": {
+                                                    "magnitude": 28,
+                                                    "unit": "PT"
+                                                },
+                                                "baselineOffset": "NONE",
+                                                "smallCaps": false,
+                                                "strikethrough": false,
+                                                "underline": false,
+                                                "weightedFontFamily": {
+                                                    "fontFamily": "Arial",
+                                                    "weight": 400
+                                                }
+                                            }
+                                        },
+                                        "2": {
+                                            "bulletStyle": {
+                                                "backgroundColor": {},
+                                                "foregroundColor": {
+                                                    "opaqueColor": {
+                                                        "themeColor": "DARK1"
+                                                    }
+                                                },
+                                                "bold": false,
+                                                "italic": false,
+                                                "fontFamily": "Arial",
+                                                "fontSize": {
+                                                    "magnitude": 28,
+                                                    "unit": "PT"
+                                                },
+                                                "baselineOffset": "NONE",
+                                                "smallCaps": false,
+                                                "strikethrough": false,
+                                                "underline": false,
+                                                "weightedFontFamily": {
+                                                    "fontFamily": "Arial",
+                                                    "weight": 400
+                                                }
+                                            }
+                                        },
+                                        "3": {
+                                            "bulletStyle": {
+                                                "backgroundColor": {},
+                                                "foregroundColor": {
+                                                    "opaqueColor": {
+                                                        "themeColor": "DARK1"
+                                                    }
+                                                },
+                                                "bold": false,
+                                                "italic": false,
+                                                "fontFamily": "Arial",
+                                                "fontSize": {
+                                                    "magnitude": 28,
+                                                    "unit": "PT"
+                                                },
+                                                "baselineOffset": "NONE",
+                                                "smallCaps": false,
+                                                "strikethrough": false,
+                                                "underline": false,
+                                                "weightedFontFamily": {
+                                                    "fontFamily": "Arial",
+                                                    "weight": 400
+                                                }
+                                            }
+                                        },
+                                        "4": {
+                                            "bulletStyle": {
+                                                "backgroundColor": {},
+                                                "foregroundColor": {
+                                                    "opaqueColor": {
+                                                        "themeColor": "DARK1"
+                                                    }
+                                                },
+                                                "bold": false,
+                                                "italic": false,
+                                                "fontFamily": "Arial",
+                                                "fontSize": {
+                                                    "magnitude": 28,
+                                                    "unit": "PT"
+                                                },
+                                                "baselineOffset": "NONE",
+                                                "smallCaps": false,
+                                                "strikethrough": false,
+                                                "underline": false,
+                                                "weightedFontFamily": {
+                                                    "fontFamily": "Arial",
+                                                    "weight": 400
+                                                }
+                                            }
+                                        },
+                                        "5": {
+                                            "bulletStyle": {
+                                                "backgroundColor": {},
+                                                "foregroundColor": {
+                                                    "opaqueColor": {
+                                                        "themeColor": "DARK1"
+                                                    }
+                                                },
+                                                "bold": false,
+                                                "italic": false,
+                                                "fontFamily": "Arial",
+                                                "fontSize": {
+                                                    "magnitude": 28,
+                                                    "unit": "PT"
+                                                },
+                                                "baselineOffset": "NONE",
+                                                "smallCaps": false,
+                                                "strikethrough": false,
+                                                "underline": false,
+                                                "weightedFontFamily": {
+                                                    "fontFamily": "Arial",
+                                                    "weight": 400
+                                                }
+                                            }
+                                        },
+                                        "6": {
+                                            "bulletStyle": {
+                                                "backgroundColor": {},
+                                                "foregroundColor": {
+                                                    "opaqueColor": {
+                                                        "themeColor": "DARK1"
+                                                    }
+                                                },
+                                                "bold": false,
+                                                "italic": false,
+                                                "fontFamily": "Arial",
+                                                "fontSize": {
+                                                    "magnitude": 28,
+                                                    "unit": "PT"
+                                                },
+                                                "baselineOffset": "NONE",
+                                                "smallCaps": false,
+                                                "strikethrough": false,
+                                                "underline": false,
+                                                "weightedFontFamily": {
+                                                    "fontFamily": "Arial",
+                                                    "weight": 400
+                                                }
+                                            }
+                                        },
+                                        "7": {
+                                            "bulletStyle": {
+                                                "backgroundColor": {},
+                                                "foregroundColor": {
+                                                    "opaqueColor": {
+                                                        "themeColor": "DARK1"
+                                                    }
+                                                },
+                                                "bold": false,
+                                                "italic": false,
+                                                "fontFamily": "Arial",
+                                                "fontSize": {
+                                                    "magnitude": 28,
+                                                    "unit": "PT"
+                                                },
+                                                "baselineOffset": "NONE",
+                                                "smallCaps": false,
+                                                "strikethrough": false,
+                                                "underline": false,
+                                                "weightedFontFamily": {
+                                                    "fontFamily": "Arial",
+                                                    "weight": 400
+                                                }
+                                            }
+                                        },
+                                        "8": {
+                                            "bulletStyle": {
+                                                "backgroundColor": {},
+                                                "foregroundColor": {
+                                                    "opaqueColor": {
+                                                        "themeColor": "DARK1"
+                                                    }
+                                                },
+                                                "bold": false,
+                                                "italic": false,
+                                                "fontFamily": "Arial",
+                                                "fontSize": {
+                                                    "magnitude": 28,
+                                                    "unit": "PT"
+                                                },
+                                                "baselineOffset": "NONE",
+                                                "smallCaps": false,
+                                                "strikethrough": false,
+                                                "underline": false,
+                                                "weightedFontFamily": {
+                                                    "fontFamily": "Arial",
+                                                    "weight": 400
+                                                }
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        },
+                        "shapeProperties": {
+                            "shapeBackgroundFill": {
+                                "propertyState": "NOT_RENDERED",
+                                "solidFill": {
+                                    "color": {
+                                        "rgbColor": {
+                                            "red": 1,
+                                            "green": 1,
+                                            "blue": 1
+                                        }
+                                    },
+                                    "alpha": 1
+                                }
+                            },
+                            "outline": {
+                                "outlineFill": {
+                                    "solidFill": {
+                                        "color": {
+                                            "rgbColor": {}
+                                        },
+                                        "alpha": 1
+                                    }
+                                },
+                                "weight": {
+                                    "magnitude": 9525,
+                                    "unit": "EMU"
+                                },
+                                "dashStyle": "SOLID",
+                                "propertyState": "NOT_RENDERED"
+                            },
+                            "shadow": {
+                                "type": "OUTER",
+                                "transform": {
+                                    "scaleX": 1,
+                                    "scaleY": 1,
+                                    "unit": "EMU"
+                                },
+                                "alignment": "BOTTOM_LEFT",
+                                "blurRadius": {
+                                    "unit": "EMU"
+                                },
+                                "color": {
+                                    "rgbColor": {}
+                                },
+                                "alpha": 1,
+                                "rotateWithShape": false,
+                                "propertyState": "NOT_RENDERED"
+                            },
+                            "contentAlignment": "TOP"
+                        },
+                        "placeholder": {
+                            "type": "TITLE"
+                        }
+                    }
+                },
+                {
+                    "objectId": "p1_i1",
+                    "size": {
+                        "width": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        },
+                        "height": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        }
+                    },
+                    "transform": {
+                        "scaleX": 2.8402,
+                        "scaleY": 1.1388,
+                        "translateX": 311700,
+                        "translateY": 1152475,
+                        "unit": "EMU"
+                    },
+                    "shape": {
+                        "shapeType": "TEXT_BOX",
+                        "text": {
+                            "textElements": [
+                                {
+                                    "endIndex": 1,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "lineSpacing": 115,
+                                            "alignment": "START",
+                                            "indentStart": {
+                                                "unit": "PT"
+                                            },
+                                            "indentEnd": {
+                                                "unit": "PT"
+                                            },
+                                            "spaceAbove": {
+                                                "unit": "PT"
+                                            },
+                                            "spaceBelow": {
+                                                "magnitude": 16,
+                                                "unit": "PT"
+                                            },
+                                            "indentFirstLine": {
+                                                "unit": "PT"
+                                            },
+                                            "direction": "LEFT_TO_RIGHT",
+                                            "spacingMode": "NEVER_COLLAPSE"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "glyph": "●",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "endIndex": 1,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "backgroundColor": {},
+                                            "foregroundColor": {
+                                                "opaqueColor": {
+                                                    "themeColor": "DARK2"
+                                                }
+                                            },
+                                            "bold": false,
+                                            "italic": false,
+                                            "fontFamily": "Arial",
+                                            "fontSize": {
+                                                "magnitude": 18,
+                                                "unit": "PT"
+                                            },
+                                            "baselineOffset": "NONE",
+                                            "smallCaps": false,
+                                            "strikethrough": false,
+                                            "underline": false,
+                                            "weightedFontFamily": {
+                                                "fontFamily": "Arial",
+                                                "weight": 400
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 1,
+                                    "endIndex": 2,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "lineSpacing": 115,
+                                            "alignment": "START",
+                                            "indentStart": {
+                                                "unit": "PT"
+                                            },
+                                            "indentEnd": {
+                                                "unit": "PT"
+                                            },
+                                            "spaceAbove": {
+                                                "unit": "PT"
+                                            },
+                                            "spaceBelow": {
+                                                "magnitude": 16,
+                                                "unit": "PT"
+                                            },
+                                            "indentFirstLine": {
+                                                "unit": "PT"
+                                            },
+                                            "direction": "LEFT_TO_RIGHT",
+                                            "spacingMode": "NEVER_COLLAPSE"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 1,
+                                            "glyph": "○",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 1,
+                                    "endIndex": 2,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "backgroundColor": {},
+                                            "foregroundColor": {
+                                                "opaqueColor": {
+                                                    "themeColor": "DARK2"
+                                                }
+                                            },
+                                            "bold": false,
+                                            "italic": false,
+                                            "fontFamily": "Arial",
+                                            "fontSize": {
+                                                "magnitude": 14,
+                                                "unit": "PT"
+                                            },
+                                            "baselineOffset": "NONE",
+                                            "smallCaps": false,
+                                            "strikethrough": false,
+                                            "underline": false,
+                                            "weightedFontFamily": {
+                                                "fontFamily": "Arial",
+                                                "weight": 400
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 2,
+                                    "endIndex": 3,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "lineSpacing": 115,
+                                            "alignment": "START",
+                                            "indentStart": {
+                                                "unit": "PT"
+                                            },
+                                            "indentEnd": {
+                                                "unit": "PT"
+                                            },
+                                            "spaceAbove": {
+                                                "unit": "PT"
+                                            },
+                                            "spaceBelow": {
+                                                "magnitude": 16,
+                                                "unit": "PT"
+                                            },
+                                            "indentFirstLine": {
+                                                "unit": "PT"
+                                            },
+                                            "direction": "LEFT_TO_RIGHT",
+                                            "spacingMode": "NEVER_COLLAPSE"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 2,
+                                            "glyph": "■",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 2,
+                                    "endIndex": 3,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "backgroundColor": {},
+                                            "foregroundColor": {
+                                                "opaqueColor": {
+                                                    "themeColor": "DARK2"
+                                                }
+                                            },
+                                            "bold": false,
+                                            "italic": false,
+                                            "fontFamily": "Arial",
+                                            "fontSize": {
+                                                "magnitude": 14,
+                                                "unit": "PT"
+                                            },
+                                            "baselineOffset": "NONE",
+                                            "smallCaps": false,
+                                            "strikethrough": false,
+                                            "underline": false,
+                                            "weightedFontFamily": {
+                                                "fontFamily": "Arial",
+                                                "weight": 400
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 3,
+                                    "endIndex": 4,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "lineSpacing": 115,
+                                            "alignment": "START",
+                                            "indentStart": {
+                                                "unit": "PT"
+                                            },
+                                            "indentEnd": {
+                                                "unit": "PT"
+                                            },
+                                            "spaceAbove": {
+                                                "unit": "PT"
+                                            },
+                                            "spaceBelow": {
+                                                "magnitude": 16,
+                                                "unit": "PT"
+                                            },
+                                            "indentFirstLine": {
+                                                "unit": "PT"
+                                            },
+                                            "direction": "LEFT_TO_RIGHT",
+                                            "spacingMode": "NEVER_COLLAPSE"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 3,
+                                            "glyph": "●",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 3,
+                                    "endIndex": 4,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "backgroundColor": {},
+                                            "foregroundColor": {
+                                                "opaqueColor": {
+                                                    "themeColor": "DARK2"
+                                                }
+                                            },
+                                            "bold": false,
+                                            "italic": false,
+                                            "fontFamily": "Arial",
+                                            "fontSize": {
+                                                "magnitude": 14,
+                                                "unit": "PT"
+                                            },
+                                            "baselineOffset": "NONE",
+                                            "smallCaps": false,
+                                            "strikethrough": false,
+                                            "underline": false,
+                                            "weightedFontFamily": {
+                                                "fontFamily": "Arial",
+                                                "weight": 400
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 4,
+                                    "endIndex": 5,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "lineSpacing": 115,
+                                            "alignment": "START",
+                                            "indentStart": {
+                                                "unit": "PT"
+                                            },
+                                            "indentEnd": {
+                                                "unit": "PT"
+                                            },
+                                            "spaceAbove": {
+                                                "unit": "PT"
+                                            },
+                                            "spaceBelow": {
+                                                "magnitude": 16,
+                                                "unit": "PT"
+                                            },
+                                            "indentFirstLine": {
+                                                "unit": "PT"
+                                            },
+                                            "direction": "LEFT_TO_RIGHT",
+                                            "spacingMode": "NEVER_COLLAPSE"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 4,
+                                            "glyph": "○",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 4,
+                                    "endIndex": 5,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "backgroundColor": {},
+                                            "foregroundColor": {
+                                                "opaqueColor": {
+                                                    "themeColor": "DARK2"
+                                                }
+                                            },
+                                            "bold": false,
+                                            "italic": false,
+                                            "fontFamily": "Arial",
+                                            "fontSize": {
+                                                "magnitude": 14,
+                                                "unit": "PT"
+                                            },
+                                            "baselineOffset": "NONE",
+                                            "smallCaps": false,
+                                            "strikethrough": false,
+                                            "underline": false,
+                                            "weightedFontFamily": {
+                                                "fontFamily": "Arial",
+                                                "weight": 400
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 5,
+                                    "endIndex": 6,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "lineSpacing": 115,
+                                            "alignment": "START",
+                                            "indentStart": {
+                                                "unit": "PT"
+                                            },
+                                            "indentEnd": {
+                                                "unit": "PT"
+                                            },
+                                            "spaceAbove": {
+                                                "unit": "PT"
+                                            },
+                                            "spaceBelow": {
+                                                "magnitude": 16,
+                                                "unit": "PT"
+                                            },
+                                            "indentFirstLine": {
+                                                "unit": "PT"
+                                            },
+                                            "direction": "LEFT_TO_RIGHT",
+                                            "spacingMode": "NEVER_COLLAPSE"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 5,
+                                            "glyph": "■",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 5,
+                                    "endIndex": 6,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "backgroundColor": {},
+                                            "foregroundColor": {
+                                                "opaqueColor": {
+                                                    "themeColor": "DARK2"
+                                                }
+                                            },
+                                            "bold": false,
+                                            "italic": false,
+                                            "fontFamily": "Arial",
+                                            "fontSize": {
+                                                "magnitude": 14,
+                                                "unit": "PT"
+                                            },
+                                            "baselineOffset": "NONE",
+                                            "smallCaps": false,
+                                            "strikethrough": false,
+                                            "underline": false,
+                                            "weightedFontFamily": {
+                                                "fontFamily": "Arial",
+                                                "weight": 400
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 6,
+                                    "endIndex": 7,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "lineSpacing": 115,
+                                            "alignment": "START",
+                                            "indentStart": {
+                                                "unit": "PT"
+                                            },
+                                            "indentEnd": {
+                                                "unit": "PT"
+                                            },
+                                            "spaceAbove": {
+                                                "unit": "PT"
+                                            },
+                                            "spaceBelow": {
+                                                "magnitude": 16,
+                                                "unit": "PT"
+                                            },
+                                            "indentFirstLine": {
+                                                "unit": "PT"
+                                            },
+                                            "direction": "LEFT_TO_RIGHT",
+                                            "spacingMode": "NEVER_COLLAPSE"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 6,
+                                            "glyph": "●",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 6,
+                                    "endIndex": 7,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "backgroundColor": {},
+                                            "foregroundColor": {
+                                                "opaqueColor": {
+                                                    "themeColor": "DARK2"
+                                                }
+                                            },
+                                            "bold": false,
+                                            "italic": false,
+                                            "fontFamily": "Arial",
+                                            "fontSize": {
+                                                "magnitude": 14,
+                                                "unit": "PT"
+                                            },
+                                            "baselineOffset": "NONE",
+                                            "smallCaps": false,
+                                            "strikethrough": false,
+                                            "underline": false,
+                                            "weightedFontFamily": {
+                                                "fontFamily": "Arial",
+                                                "weight": 400
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 7,
+                                    "endIndex": 8,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "lineSpacing": 115,
+                                            "alignment": "START",
+                                            "indentStart": {
+                                                "unit": "PT"
+                                            },
+                                            "indentEnd": {
+                                                "unit": "PT"
+                                            },
+                                            "spaceAbove": {
+                                                "unit": "PT"
+                                            },
+                                            "spaceBelow": {
+                                                "magnitude": 16,
+                                                "unit": "PT"
+                                            },
+                                            "indentFirstLine": {
+                                                "unit": "PT"
+                                            },
+                                            "direction": "LEFT_TO_RIGHT",
+                                            "spacingMode": "NEVER_COLLAPSE"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 7,
+                                            "glyph": "○",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 7,
+                                    "endIndex": 8,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "backgroundColor": {},
+                                            "foregroundColor": {
+                                                "opaqueColor": {
+                                                    "themeColor": "DARK2"
+                                                }
+                                            },
+                                            "bold": false,
+                                            "italic": false,
+                                            "fontFamily": "Arial",
+                                            "fontSize": {
+                                                "magnitude": 14,
+                                                "unit": "PT"
+                                            },
+                                            "baselineOffset": "NONE",
+                                            "smallCaps": false,
+                                            "strikethrough": false,
+                                            "underline": false,
+                                            "weightedFontFamily": {
+                                                "fontFamily": "Arial",
+                                                "weight": 400
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 8,
+                                    "endIndex": 9,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "lineSpacing": 115,
+                                            "alignment": "START",
+                                            "indentStart": {
+                                                "unit": "PT"
+                                            },
+                                            "indentEnd": {
+                                                "unit": "PT"
+                                            },
+                                            "spaceAbove": {
+                                                "unit": "PT"
+                                            },
+                                            "spaceBelow": {
+                                                "magnitude": 16,
+                                                "unit": "PT"
+                                            },
+                                            "indentFirstLine": {
+                                                "unit": "PT"
+                                            },
+                                            "direction": "LEFT_TO_RIGHT",
+                                            "spacingMode": "NEVER_COLLAPSE"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 8,
+                                            "glyph": "■",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 8,
+                                    "endIndex": 9,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "backgroundColor": {},
+                                            "foregroundColor": {
+                                                "opaqueColor": {
+                                                    "themeColor": "DARK2"
+                                                }
+                                            },
+                                            "bold": false,
+                                            "italic": false,
+                                            "fontFamily": "Arial",
+                                            "fontSize": {
+                                                "magnitude": 14,
+                                                "unit": "PT"
+                                            },
+                                            "baselineOffset": "NONE",
+                                            "smallCaps": false,
+                                            "strikethrough": false,
+                                            "underline": false,
+                                            "weightedFontFamily": {
+                                                "fontFamily": "Arial",
+                                                "weight": 400
+                                            }
+                                        }
+                                    }
+                                }
+                            ],
+                            "lists": {
+                                "bodyPlaceholderListEntity": {
+                                    "listId": "bodyPlaceholderListEntity",
+                                    "nestingLevel": {
+                                        "0": {
+                                            "bulletStyle": {
+                                                "backgroundColor": {},
+                                                "foregroundColor": {
+                                                    "opaqueColor": {
+                                                        "themeColor": "DARK2"
+                                                    }
+                                                },
+                                                "bold": false,
+                                                "italic": false,
+                                                "fontFamily": "Arial",
+                                                "fontSize": {
+                                                    "magnitude": 18,
+                                                    "unit": "PT"
+                                                },
+                                                "baselineOffset": "NONE",
+                                                "smallCaps": false,
+                                                "strikethrough": false,
+                                                "underline": false,
+                                                "weightedFontFamily": {
+                                                    "fontFamily": "Arial",
+                                                    "weight": 400
+                                                }
+                                            }
+                                        },
+                                        "1": {
+                                            "bulletStyle": {
+                                                "backgroundColor": {},
+                                                "foregroundColor": {
+                                                    "opaqueColor": {
+                                                        "themeColor": "DARK2"
+                                                    }
+                                                },
+                                                "bold": false,
+                                                "italic": false,
+                                                "fontFamily": "Arial",
+                                                "fontSize": {
+                                                    "magnitude": 14,
+                                                    "unit": "PT"
+                                                },
+                                                "baselineOffset": "NONE",
+                                                "smallCaps": false,
+                                                "strikethrough": false,
+                                                "underline": false,
+                                                "weightedFontFamily": {
+                                                    "fontFamily": "Arial",
+                                                    "weight": 400
+                                                }
+                                            }
+                                        },
+                                        "2": {
+                                            "bulletStyle": {
+                                                "backgroundColor": {},
+                                                "foregroundColor": {
+                                                    "opaqueColor": {
+                                                        "themeColor": "DARK2"
+                                                    }
+                                                },
+                                                "bold": false,
+                                                "italic": false,
+                                                "fontFamily": "Arial",
+                                                "fontSize": {
+                                                    "magnitude": 14,
+                                                    "unit": "PT"
+                                                },
+                                                "baselineOffset": "NONE",
+                                                "smallCaps": false,
+                                                "strikethrough": false,
+                                                "underline": false,
+                                                "weightedFontFamily": {
+                                                    "fontFamily": "Arial",
+                                                    "weight": 400
+                                                }
+                                            }
+                                        },
+                                        "3": {
+                                            "bulletStyle": {
+                                                "backgroundColor": {},
+                                                "foregroundColor": {
+                                                    "opaqueColor": {
+                                                        "themeColor": "DARK2"
+                                                    }
+                                                },
+                                                "bold": false,
+                                                "italic": false,
+                                                "fontFamily": "Arial",
+                                                "fontSize": {
+                                                    "magnitude": 14,
+                                                    "unit": "PT"
+                                                },
+                                                "baselineOffset": "NONE",
+                                                "smallCaps": false,
+                                                "strikethrough": false,
+                                                "underline": false,
+                                                "weightedFontFamily": {
+                                                    "fontFamily": "Arial",
+                                                    "weight": 400
+                                                }
+                                            }
+                                        },
+                                        "4": {
+                                            "bulletStyle": {
+                                                "backgroundColor": {},
+                                                "foregroundColor": {
+                                                    "opaqueColor": {
+                                                        "themeColor": "DARK2"
+                                                    }
+                                                },
+                                                "bold": false,
+                                                "italic": false,
+                                                "fontFamily": "Arial",
+                                                "fontSize": {
+                                                    "magnitude": 14,
+                                                    "unit": "PT"
+                                                },
+                                                "baselineOffset": "NONE",
+                                                "smallCaps": false,
+                                                "strikethrough": false,
+                                                "underline": false,
+                                                "weightedFontFamily": {
+                                                    "fontFamily": "Arial",
+                                                    "weight": 400
+                                                }
+                                            }
+                                        },
+                                        "5": {
+                                            "bulletStyle": {
+                                                "backgroundColor": {},
+                                                "foregroundColor": {
+                                                    "opaqueColor": {
+                                                        "themeColor": "DARK2"
+                                                    }
+                                                },
+                                                "bold": false,
+                                                "italic": false,
+                                                "fontFamily": "Arial",
+                                                "fontSize": {
+                                                    "magnitude": 14,
+                                                    "unit": "PT"
+                                                },
+                                                "baselineOffset": "NONE",
+                                                "smallCaps": false,
+                                                "strikethrough": false,
+                                                "underline": false,
+                                                "weightedFontFamily": {
+                                                    "fontFamily": "Arial",
+                                                    "weight": 400
+                                                }
+                                            }
+                                        },
+                                        "6": {
+                                            "bulletStyle": {
+                                                "backgroundColor": {},
+                                                "foregroundColor": {
+                                                    "opaqueColor": {
+                                                        "themeColor": "DARK2"
+                                                    }
+                                                },
+                                                "bold": false,
+                                                "italic": false,
+                                                "fontFamily": "Arial",
+                                                "fontSize": {
+                                                    "magnitude": 14,
+                                                    "unit": "PT"
+                                                },
+                                                "baselineOffset": "NONE",
+                                                "smallCaps": false,
+                                                "strikethrough": false,
+                                                "underline": false,
+                                                "weightedFontFamily": {
+                                                    "fontFamily": "Arial",
+                                                    "weight": 400
+                                                }
+                                            }
+                                        },
+                                        "7": {
+                                            "bulletStyle": {
+                                                "backgroundColor": {},
+                                                "foregroundColor": {
+                                                    "opaqueColor": {
+                                                        "themeColor": "DARK2"
+                                                    }
+                                                },
+                                                "bold": false,
+                                                "italic": false,
+                                                "fontFamily": "Arial",
+                                                "fontSize": {
+                                                    "magnitude": 14,
+                                                    "unit": "PT"
+                                                },
+                                                "baselineOffset": "NONE",
+                                                "smallCaps": false,
+                                                "strikethrough": false,
+                                                "underline": false,
+                                                "weightedFontFamily": {
+                                                    "fontFamily": "Arial",
+                                                    "weight": 400
+                                                }
+                                            }
+                                        },
+                                        "8": {
+                                            "bulletStyle": {
+                                                "backgroundColor": {},
+                                                "foregroundColor": {
+                                                    "opaqueColor": {
+                                                        "themeColor": "DARK2"
+                                                    }
+                                                },
+                                                "bold": false,
+                                                "italic": false,
+                                                "fontFamily": "Arial",
+                                                "fontSize": {
+                                                    "magnitude": 14,
+                                                    "unit": "PT"
+                                                },
+                                                "baselineOffset": "NONE",
+                                                "smallCaps": false,
+                                                "strikethrough": false,
+                                                "underline": false,
+                                                "weightedFontFamily": {
+                                                    "fontFamily": "Arial",
+                                                    "weight": 400
+                                                }
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        },
+                        "shapeProperties": {
+                            "shapeBackgroundFill": {
+                                "propertyState": "NOT_RENDERED",
+                                "solidFill": {
+                                    "color": {
+                                        "rgbColor": {
+                                            "red": 1,
+                                            "green": 1,
+                                            "blue": 1
+                                        }
+                                    },
+                                    "alpha": 1
+                                }
+                            },
+                            "outline": {
+                                "outlineFill": {
+                                    "solidFill": {
+                                        "color": {
+                                            "rgbColor": {}
+                                        },
+                                        "alpha": 1
+                                    }
+                                },
+                                "weight": {
+                                    "magnitude": 9525,
+                                    "unit": "EMU"
+                                },
+                                "dashStyle": "SOLID",
+                                "propertyState": "NOT_RENDERED"
+                            },
+                            "shadow": {
+                                "type": "OUTER",
+                                "transform": {
+                                    "scaleX": 1,
+                                    "scaleY": 1,
+                                    "unit": "EMU"
+                                },
+                                "alignment": "BOTTOM_LEFT",
+                                "blurRadius": {
+                                    "unit": "EMU"
+                                },
+                                "color": {
+                                    "rgbColor": {}
+                                },
+                                "alpha": 1,
+                                "rotateWithShape": false,
+                                "propertyState": "NOT_RENDERED"
+                            },
+                            "contentAlignment": "TOP"
+                        },
+                        "placeholder": {
+                            "type": "BODY"
+                        }
+                    }
+                },
+                {
+                    "objectId": "p1_i2",
+                    "size": {
+                        "width": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        },
+                        "height": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        }
+                    },
+                    "transform": {
+                        "scaleX": 0.1829,
+                        "scaleY": 0.1312,
+                        "translateX": 8472457.8125,
+                        "translateY": 4663216.797499999,
+                        "unit": "EMU"
+                    },
+                    "shape": {
+                        "shapeType": "TEXT_BOX",
+                        "text": {
+                            "textElements": [
+                                {
+                                    "endIndex": 2,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "lineSpacing": 100,
+                                            "alignment": "END",
+                                            "indentStart": {
+                                                "unit": "PT"
+                                            },
+                                            "indentEnd": {
+                                                "unit": "PT"
+                                            },
+                                            "spaceAbove": {
+                                                "unit": "PT"
+                                            },
+                                            "spaceBelow": {
+                                                "unit": "PT"
+                                            },
+                                            "indentFirstLine": {
+                                                "unit": "PT"
+                                            },
+                                            "direction": "LEFT_TO_RIGHT",
+                                            "spacingMode": "NEVER_COLLAPSE"
+                                        }
+                                    }
+                                },
+                                {
+                                    "endIndex": 1,
+                                    "autoText": {
+                                        "type": "SLIDE_NUMBER",
+                                        "content": "#",
+                                        "style": {
+                                            "backgroundColor": {},
+                                            "foregroundColor": {
+                                                "opaqueColor": {
+                                                    "themeColor": "DARK2"
+                                                }
+                                            },
+                                            "bold": false,
+                                            "italic": false,
+                                            "fontFamily": "Arial",
+                                            "fontSize": {
+                                                "magnitude": 10,
+                                                "unit": "PT"
+                                            },
+                                            "baselineOffset": "NONE",
+                                            "smallCaps": false,
+                                            "strikethrough": false,
+                                            "underline": false,
+                                            "weightedFontFamily": {
+                                                "fontFamily": "Arial",
+                                                "weight": 400
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 1,
+                                    "endIndex": 2,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "backgroundColor": {},
+                                            "foregroundColor": {
+                                                "opaqueColor": {
+                                                    "themeColor": "DARK2"
+                                                }
+                                            },
+                                            "bold": false,
+                                            "italic": false,
+                                            "fontFamily": "Arial",
+                                            "fontSize": {
+                                                "magnitude": 10,
+                                                "unit": "PT"
+                                            },
+                                            "baselineOffset": "NONE",
+                                            "smallCaps": false,
+                                            "strikethrough": false,
+                                            "underline": false,
+                                            "weightedFontFamily": {
+                                                "fontFamily": "Arial",
+                                                "weight": 400
+                                            }
+                                        }
+                                    }
+                                }
+                            ]
+                        },
+                        "shapeProperties": {
+                            "shapeBackgroundFill": {
+                                "propertyState": "NOT_RENDERED",
+                                "solidFill": {
+                                    "color": {
+                                        "rgbColor": {
+                                            "red": 1,
+                                            "green": 1,
+                                            "blue": 1
+                                        }
+                                    },
+                                    "alpha": 1
+                                }
+                            },
+                            "outline": {
+                                "outlineFill": {
+                                    "solidFill": {
+                                        "color": {
+                                            "rgbColor": {}
+                                        },
+                                        "alpha": 1
+                                    }
+                                },
+                                "weight": {
+                                    "magnitude": 9525,
+                                    "unit": "EMU"
+                                },
+                                "dashStyle": "SOLID",
+                                "propertyState": "NOT_RENDERED"
+                            },
+                            "shadow": {
+                                "type": "OUTER",
+                                "transform": {
+                                    "scaleX": 1,
+                                    "scaleY": 1,
+                                    "unit": "EMU"
+                                },
+                                "alignment": "BOTTOM_LEFT",
+                                "blurRadius": {
+                                    "unit": "EMU"
+                                },
+                                "color": {
+                                    "rgbColor": {}
+                                },
+                                "alpha": 1,
+                                "rotateWithShape": false,
+                                "propertyState": "NOT_RENDERED"
+                            },
+                            "contentAlignment": "MIDDLE"
+                        },
+                        "placeholder": {
+                            "type": "SLIDE_NUMBER"
+                        }
+                    }
+                }
+            ],
+            "pageProperties": {
+                "pageBackgroundFill": {
+                    "solidFill": {
+                        "color": {
+                            "themeColor": "LIGHT1"
+                        },
+                        "alpha": 1
+                    }
+                },
+                "colorScheme": {
+                    "colors": [
+                        {
+                            "type": "DARK1",
+                            "color": {}
+                        },
+                        {
+                            "type": "LIGHT1",
+                            "color": {
+                                "red": 1,
+                                "green": 1,
+                                "blue": 1
+                            }
+                        },
+                        {
+                            "type": "DARK2",
+                            "color": {
+                                "red": 0.34901962,
+                                "green": 0.34901962,
+                                "blue": 0.34901962
+                            }
+                        },
+                        {
+                            "type": "LIGHT2",
+                            "color": {
+                                "red": 0.93333334,
+                                "green": 0.93333334,
+                                "blue": 0.93333334
+                            }
+                        },
+                        {
+                            "type": "ACCENT1",
+                            "color": {
+                                "red": 1,
+                                "green": 0.67058825,
+                                "blue": 0.2509804
+                            }
+                        },
+                        {
+                            "type": "ACCENT2",
+                            "color": {
+                                "red": 0.12941177,
+                                "green": 0.12941177,
+                                "blue": 0.12941177
+                            }
+                        },
+                        {
+                            "type": "ACCENT3",
+                            "color": {
+                                "red": 0.47058824,
+                                "green": 0.5647059,
+                                "blue": 0.6117647
+                            }
+                        },
+                        {
+                            "type": "ACCENT4",
+                            "color": {
+                                "red": 1,
+                                "green": 0.67058825,
+                                "blue": 0.2509804
+                            }
+                        },
+                        {
+                            "type": "ACCENT5",
+                            "color": {
+                                "green": 0.5921569,
+                                "blue": 0.654902
+                            }
+                        },
+                        {
+                            "type": "ACCENT6",
+                            "color": {
+                                "red": 0.93333334,
+                                "green": 1,
+                                "blue": 0.25490198
+                            }
+                        },
+                        {
+                            "type": "HYPERLINK",
+                            "color": {
+                                "green": 0.5921569,
+                                "blue": 0.654902
+                            }
+                        },
+                        {
+                            "type": "FOLLOWED_HYPERLINK",
+                            "color": {
+                                "green": 0.5921569,
+                                "blue": 0.654902
+                            }
+                        },
+                        {
+                            "type": "TEXT1",
+                            "color": {}
+                        },
+                        {
+                            "type": "BACKGROUND1",
+                            "color": {
+                                "red": 1,
+                                "green": 1,
+                                "blue": 1
+                            }
+                        },
+                        {
+                            "type": "TEXT2",
+                            "color": {
+                                "red": 0.93333334,
+                                "green": 0.93333334,
+                                "blue": 0.93333334
+                            }
+                        },
+                        {
+                            "type": "BACKGROUND2",
+                            "color": {
+                                "red": 0.34901962,
+                                "green": 0.34901962,
+                                "blue": 0.34901962
+                            }
+                        }
+                    ]
+                }
+            },
+            "masterProperties": {
+                "displayName": "Simple Light"
+            }
+        }
+    ],
+    "layouts": [
+        {
+            "objectId": "p2",
+            "pageType": "LAYOUT",
+            "pageElements": [
+                {
+                    "objectId": "p2_i0",
+                    "size": {
+                        "width": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        },
+                        "height": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        }
+                    },
+                    "transform": {
+                        "scaleX": 2.8402,
+                        "scaleY": 0.6842,
+                        "translateX": 311708.35000000003,
+                        "translateY": 744575,
+                        "unit": "EMU"
+                    },
+                    "shape": {
+                        "shapeType": "TEXT_BOX",
+                        "text": {
+                            "textElements": [
+                                {
+                                    "endIndex": 1,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "alignment": "CENTER",
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "endIndex": 1,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 52,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 1,
+                                    "endIndex": 2,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "alignment": "CENTER",
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 1,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 1,
+                                    "endIndex": 2,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 52,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 2,
+                                    "endIndex": 3,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "alignment": "CENTER",
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 2,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 2,
+                                    "endIndex": 3,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 52,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 3,
+                                    "endIndex": 4,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "alignment": "CENTER",
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 3,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 3,
+                                    "endIndex": 4,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 52,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 4,
+                                    "endIndex": 5,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "alignment": "CENTER",
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 4,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 4,
+                                    "endIndex": 5,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 52,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 5,
+                                    "endIndex": 6,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "alignment": "CENTER",
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 5,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 5,
+                                    "endIndex": 6,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 52,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 6,
+                                    "endIndex": 7,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "alignment": "CENTER",
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 6,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 6,
+                                    "endIndex": 7,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 52,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 7,
+                                    "endIndex": 8,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "alignment": "CENTER",
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 7,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 7,
+                                    "endIndex": 8,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 52,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 8,
+                                    "endIndex": 9,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "alignment": "CENTER",
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 8,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 8,
+                                    "endIndex": 9,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 52,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                }
+                            ],
+                            "lists": {
+                                "bodyPlaceholderListEntity": {
+                                    "listId": "bodyPlaceholderListEntity",
+                                    "nestingLevel": {
+                                        "0": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 52,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "1": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 52,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "2": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 52,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "3": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 52,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "4": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 52,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "5": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 52,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "6": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 52,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "7": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 52,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "8": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 52,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        },
+                        "shapeProperties": {
+                            "shapeBackgroundFill": {
+                                "propertyState": "INHERIT"
+                            },
+                            "outline": {
+                                "propertyState": "INHERIT"
+                            },
+                            "shadow": {
+                                "propertyState": "INHERIT"
+                            },
+                            "contentAlignment": "BOTTOM"
+                        },
+                        "placeholder": {
+                            "type": "CENTERED_TITLE",
+                            "parentObjectId": "p1_i0"
+                        }
+                    }
+                },
+                {
+                    "objectId": "p2_i1",
+                    "size": {
+                        "width": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        },
+                        "height": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        }
+                    },
+                    "transform": {
+                        "scaleX": 2.8402,
+                        "scaleY": 0.2642,
+                        "translateX": 311700,
+                        "translateY": 2834125,
+                        "unit": "EMU"
+                    },
+                    "shape": {
+                        "shapeType": "TEXT_BOX",
+                        "text": {
+                            "textElements": [
+                                {
+                                    "endIndex": 1,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "lineSpacing": 100,
+                                            "alignment": "CENTER",
+                                            "spaceBelow": {
+                                                "unit": "PT"
+                                            },
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "endIndex": 1,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 28,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 1,
+                                    "endIndex": 2,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "lineSpacing": 100,
+                                            "alignment": "CENTER",
+                                            "spaceBelow": {
+                                                "unit": "PT"
+                                            },
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 1,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 1,
+                                    "endIndex": 2,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 28,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 2,
+                                    "endIndex": 3,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "lineSpacing": 100,
+                                            "alignment": "CENTER",
+                                            "spaceBelow": {
+                                                "unit": "PT"
+                                            },
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 2,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 2,
+                                    "endIndex": 3,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 28,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 3,
+                                    "endIndex": 4,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "lineSpacing": 100,
+                                            "alignment": "CENTER",
+                                            "spaceBelow": {
+                                                "unit": "PT"
+                                            },
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 3,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 3,
+                                    "endIndex": 4,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 28,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 4,
+                                    "endIndex": 5,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "lineSpacing": 100,
+                                            "alignment": "CENTER",
+                                            "spaceBelow": {
+                                                "unit": "PT"
+                                            },
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 4,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 4,
+                                    "endIndex": 5,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 28,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 5,
+                                    "endIndex": 6,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "lineSpacing": 100,
+                                            "alignment": "CENTER",
+                                            "spaceBelow": {
+                                                "unit": "PT"
+                                            },
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 5,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 5,
+                                    "endIndex": 6,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 28,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 6,
+                                    "endIndex": 7,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "lineSpacing": 100,
+                                            "alignment": "CENTER",
+                                            "spaceBelow": {
+                                                "unit": "PT"
+                                            },
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 6,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 6,
+                                    "endIndex": 7,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 28,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 7,
+                                    "endIndex": 8,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "lineSpacing": 100,
+                                            "alignment": "CENTER",
+                                            "spaceBelow": {
+                                                "unit": "PT"
+                                            },
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 7,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 7,
+                                    "endIndex": 8,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 28,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 8,
+                                    "endIndex": 9,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "lineSpacing": 100,
+                                            "alignment": "CENTER",
+                                            "spaceBelow": {
+                                                "unit": "PT"
+                                            },
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 8,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 8,
+                                    "endIndex": 9,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 28,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                }
+                            ],
+                            "lists": {
+                                "bodyPlaceholderListEntity": {
+                                    "listId": "bodyPlaceholderListEntity",
+                                    "nestingLevel": {
+                                        "0": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 28,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "1": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 28,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "2": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 28,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "3": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 28,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "4": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 28,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "5": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 28,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "6": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 28,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "7": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 28,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "8": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 28,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        },
+                        "shapeProperties": {
+                            "shapeBackgroundFill": {
+                                "propertyState": "INHERIT"
+                            },
+                            "outline": {
+                                "propertyState": "INHERIT"
+                            },
+                            "shadow": {
+                                "propertyState": "INHERIT"
+                            }
+                        },
+                        "placeholder": {
+                            "type": "SUBTITLE",
+                            "parentObjectId": "p1_i1"
+                        }
+                    }
+                },
+                {
+                    "objectId": "p2_i2",
+                    "size": {
+                        "width": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        },
+                        "height": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        }
+                    },
+                    "transform": {
+                        "scaleX": 0.1829,
+                        "scaleY": 0.1312,
+                        "translateX": 8472457.8125,
+                        "translateY": 4663216.797499999,
+                        "unit": "EMU"
+                    },
+                    "shape": {
+                        "shapeType": "TEXT_BOX",
+                        "text": {
+                            "textElements": [
+                                {
+                                    "endIndex": 2,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        }
+                                    }
+                                },
+                                {
+                                    "endIndex": 1,
+                                    "autoText": {
+                                        "type": "SLIDE_NUMBER",
+                                        "content": "#",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 1,
+                                    "endIndex": 2,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                }
+                            ]
+                        },
+                        "shapeProperties": {
+                            "shapeBackgroundFill": {
+                                "propertyState": "INHERIT"
+                            },
+                            "outline": {
+                                "propertyState": "INHERIT"
+                            },
+                            "shadow": {
+                                "propertyState": "INHERIT"
+                            }
+                        },
+                        "placeholder": {
+                            "type": "SLIDE_NUMBER",
+                            "parentObjectId": "p1_i2"
+                        }
+                    }
+                }
+            ],
+            "layoutProperties": {
+                "masterObjectId": "simple-light-2",
+                "name": "TITLE",
+                "displayName": "Title slide"
+            },
+            "pageProperties": {
+                "pageBackgroundFill": {
+                    "propertyState": "INHERIT"
+                }
+            }
+        },
+        {
+            "objectId": "p3",
+            "pageType": "LAYOUT",
+            "pageElements": [
+                {
+                    "objectId": "p3_i0",
+                    "size": {
+                        "width": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        },
+                        "height": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        }
+                    },
+                    "transform": {
+                        "scaleX": 2.8402,
+                        "scaleY": 0.2806,
+                        "translateX": 311700,
+                        "translateY": 2150850,
+                        "unit": "EMU"
+                    },
+                    "shape": {
+                        "shapeType": "TEXT_BOX",
+                        "text": {
+                            "textElements": [
+                                {
+                                    "endIndex": 1,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "alignment": "CENTER",
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "endIndex": 1,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 36,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 1,
+                                    "endIndex": 2,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "alignment": "CENTER",
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 1,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 1,
+                                    "endIndex": 2,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 36,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 2,
+                                    "endIndex": 3,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "alignment": "CENTER",
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 2,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 2,
+                                    "endIndex": 3,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 36,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 3,
+                                    "endIndex": 4,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "alignment": "CENTER",
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 3,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 3,
+                                    "endIndex": 4,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 36,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 4,
+                                    "endIndex": 5,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "alignment": "CENTER",
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 4,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 4,
+                                    "endIndex": 5,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 36,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 5,
+                                    "endIndex": 6,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "alignment": "CENTER",
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 5,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 5,
+                                    "endIndex": 6,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 36,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 6,
+                                    "endIndex": 7,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "alignment": "CENTER",
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 6,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 6,
+                                    "endIndex": 7,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 36,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 7,
+                                    "endIndex": 8,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "alignment": "CENTER",
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 7,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 7,
+                                    "endIndex": 8,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 36,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 8,
+                                    "endIndex": 9,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "alignment": "CENTER",
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 8,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 8,
+                                    "endIndex": 9,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 36,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                }
+                            ],
+                            "lists": {
+                                "bodyPlaceholderListEntity": {
+                                    "listId": "bodyPlaceholderListEntity",
+                                    "nestingLevel": {
+                                        "0": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 36,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "1": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 36,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "2": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 36,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "3": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 36,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "4": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 36,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "5": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 36,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "6": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 36,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "7": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 36,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "8": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 36,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        },
+                        "shapeProperties": {
+                            "shapeBackgroundFill": {
+                                "propertyState": "INHERIT"
+                            },
+                            "outline": {
+                                "propertyState": "INHERIT"
+                            },
+                            "shadow": {
+                                "propertyState": "INHERIT"
+                            },
+                            "contentAlignment": "MIDDLE"
+                        },
+                        "placeholder": {
+                            "type": "TITLE",
+                            "parentObjectId": "p1_i0"
+                        }
+                    }
+                },
+                {
+                    "objectId": "p3_i1",
+                    "size": {
+                        "width": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        },
+                        "height": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        }
+                    },
+                    "transform": {
+                        "scaleX": 0.1829,
+                        "scaleY": 0.1312,
+                        "translateX": 8472457.8125,
+                        "translateY": 4663216.797499999,
+                        "unit": "EMU"
+                    },
+                    "shape": {
+                        "shapeType": "TEXT_BOX",
+                        "text": {
+                            "textElements": [
+                                {
+                                    "endIndex": 2,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        }
+                                    }
+                                },
+                                {
+                                    "endIndex": 1,
+                                    "autoText": {
+                                        "type": "SLIDE_NUMBER",
+                                        "content": "#",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 1,
+                                    "endIndex": 2,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                }
+                            ]
+                        },
+                        "shapeProperties": {
+                            "shapeBackgroundFill": {
+                                "propertyState": "INHERIT"
+                            },
+                            "outline": {
+                                "propertyState": "INHERIT"
+                            },
+                            "shadow": {
+                                "propertyState": "INHERIT"
+                            }
+                        },
+                        "placeholder": {
+                            "type": "SLIDE_NUMBER",
+                            "parentObjectId": "p1_i2"
+                        }
+                    }
+                }
+            ],
+            "layoutProperties": {
+                "masterObjectId": "simple-light-2",
+                "name": "SECTION_HEADER",
+                "displayName": "Section header"
+            },
+            "pageProperties": {
+                "pageBackgroundFill": {
+                    "propertyState": "INHERIT"
+                }
+            }
+        },
+        {
+            "objectId": "p4",
+            "pageType": "LAYOUT",
+            "pageElements": [
+                {
+                    "objectId": "p4_i0",
+                    "size": {
+                        "width": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        },
+                        "height": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        }
+                    },
+                    "transform": {
+                        "scaleX": 2.8402,
+                        "scaleY": 0.1909,
+                        "translateX": 311700,
+                        "translateY": 445025,
+                        "unit": "EMU"
+                    },
+                    "shape": {
+                        "shapeType": "TEXT_BOX",
+                        "text": {
+                            "textElements": [
+                                {
+                                    "endIndex": 1,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "endIndex": 1,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 1,
+                                    "endIndex": 2,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 1,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 1,
+                                    "endIndex": 2,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 2,
+                                    "endIndex": 3,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 2,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 2,
+                                    "endIndex": 3,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 3,
+                                    "endIndex": 4,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 3,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 3,
+                                    "endIndex": 4,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 4,
+                                    "endIndex": 5,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 4,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 4,
+                                    "endIndex": 5,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 5,
+                                    "endIndex": 6,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 5,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 5,
+                                    "endIndex": 6,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 6,
+                                    "endIndex": 7,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 6,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 6,
+                                    "endIndex": 7,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 7,
+                                    "endIndex": 8,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 7,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 7,
+                                    "endIndex": 8,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 8,
+                                    "endIndex": 9,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 8,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 8,
+                                    "endIndex": 9,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                }
+                            ],
+                            "lists": {
+                                "bodyPlaceholderListEntity": {
+                                    "listId": "bodyPlaceholderListEntity",
+                                    "nestingLevel": {
+                                        "0": {
+                                            "bulletStyle": {}
+                                        },
+                                        "1": {
+                                            "bulletStyle": {}
+                                        },
+                                        "2": {
+                                            "bulletStyle": {}
+                                        },
+                                        "3": {
+                                            "bulletStyle": {}
+                                        },
+                                        "4": {
+                                            "bulletStyle": {}
+                                        },
+                                        "5": {
+                                            "bulletStyle": {}
+                                        },
+                                        "6": {
+                                            "bulletStyle": {}
+                                        },
+                                        "7": {
+                                            "bulletStyle": {}
+                                        },
+                                        "8": {
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                }
+                            }
+                        },
+                        "shapeProperties": {
+                            "shapeBackgroundFill": {
+                                "propertyState": "INHERIT"
+                            },
+                            "outline": {
+                                "propertyState": "INHERIT"
+                            },
+                            "shadow": {
+                                "propertyState": "INHERIT"
+                            }
+                        },
+                        "placeholder": {
+                            "type": "TITLE",
+                            "parentObjectId": "p1_i0"
+                        }
+                    }
+                },
+                {
+                    "objectId": "p4_i1",
+                    "size": {
+                        "width": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        },
+                        "height": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        }
+                    },
+                    "transform": {
+                        "scaleX": 2.8402,
+                        "scaleY": 1.1388,
+                        "translateX": 311700,
+                        "translateY": 1152475,
+                        "unit": "EMU"
+                    },
+                    "shape": {
+                        "shapeType": "TEXT_BOX",
+                        "text": {
+                            "textElements": [
+                                {
+                                    "endIndex": 1,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "glyph": "●",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "endIndex": 1,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 1,
+                                    "endIndex": 2,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 1,
+                                            "glyph": "○",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 1,
+                                    "endIndex": 2,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 2,
+                                    "endIndex": 3,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 2,
+                                            "glyph": "■",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 2,
+                                    "endIndex": 3,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 3,
+                                    "endIndex": 4,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 3,
+                                            "glyph": "●",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 3,
+                                    "endIndex": 4,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 4,
+                                    "endIndex": 5,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 4,
+                                            "glyph": "○",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 4,
+                                    "endIndex": 5,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 5,
+                                    "endIndex": 6,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 5,
+                                            "glyph": "■",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 5,
+                                    "endIndex": 6,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 6,
+                                    "endIndex": 7,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 6,
+                                            "glyph": "●",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 6,
+                                    "endIndex": 7,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 7,
+                                    "endIndex": 8,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 7,
+                                            "glyph": "○",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 7,
+                                    "endIndex": 8,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 8,
+                                    "endIndex": 9,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 8,
+                                            "glyph": "■",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 8,
+                                    "endIndex": 9,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                }
+                            ],
+                            "lists": {
+                                "bodyPlaceholderListEntity": {
+                                    "listId": "bodyPlaceholderListEntity",
+                                    "nestingLevel": {
+                                        "0": {
+                                            "bulletStyle": {}
+                                        },
+                                        "1": {
+                                            "bulletStyle": {}
+                                        },
+                                        "2": {
+                                            "bulletStyle": {}
+                                        },
+                                        "3": {
+                                            "bulletStyle": {}
+                                        },
+                                        "4": {
+                                            "bulletStyle": {}
+                                        },
+                                        "5": {
+                                            "bulletStyle": {}
+                                        },
+                                        "6": {
+                                            "bulletStyle": {}
+                                        },
+                                        "7": {
+                                            "bulletStyle": {}
+                                        },
+                                        "8": {
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                }
+                            }
+                        },
+                        "shapeProperties": {
+                            "shapeBackgroundFill": {
+                                "propertyState": "INHERIT"
+                            },
+                            "outline": {
+                                "propertyState": "INHERIT"
+                            },
+                            "shadow": {
+                                "propertyState": "INHERIT"
+                            }
+                        },
+                        "placeholder": {
+                            "type": "BODY",
+                            "parentObjectId": "p1_i1"
+                        }
+                    }
+                },
+                {
+                    "objectId": "p4_i2",
+                    "size": {
+                        "width": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        },
+                        "height": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        }
+                    },
+                    "transform": {
+                        "scaleX": 0.1829,
+                        "scaleY": 0.1312,
+                        "translateX": 8472457.8125,
+                        "translateY": 4663216.797499999,
+                        "unit": "EMU"
+                    },
+                    "shape": {
+                        "shapeType": "TEXT_BOX",
+                        "text": {
+                            "textElements": [
+                                {
+                                    "endIndex": 2,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        }
+                                    }
+                                },
+                                {
+                                    "endIndex": 1,
+                                    "autoText": {
+                                        "type": "SLIDE_NUMBER",
+                                        "content": "#",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 1,
+                                    "endIndex": 2,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                }
+                            ]
+                        },
+                        "shapeProperties": {
+                            "shapeBackgroundFill": {
+                                "propertyState": "INHERIT"
+                            },
+                            "outline": {
+                                "propertyState": "INHERIT"
+                            },
+                            "shadow": {
+                                "propertyState": "INHERIT"
+                            }
+                        },
+                        "placeholder": {
+                            "type": "SLIDE_NUMBER",
+                            "parentObjectId": "p1_i2"
+                        }
+                    }
+                }
+            ],
+            "layoutProperties": {
+                "masterObjectId": "simple-light-2",
+                "name": "TITLE_AND_BODY",
+                "displayName": "Title and body"
+            },
+            "pageProperties": {
+                "pageBackgroundFill": {
+                    "propertyState": "INHERIT"
+                }
+            }
+        },
+        {
+            "objectId": "p5",
+            "pageType": "LAYOUT",
+            "pageElements": [
+                {
+                    "objectId": "p5_i0",
+                    "size": {
+                        "width": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        },
+                        "height": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        }
+                    },
+                    "transform": {
+                        "scaleX": 2.8402,
+                        "scaleY": 0.1909,
+                        "translateX": 311700,
+                        "translateY": 445025,
+                        "unit": "EMU"
+                    },
+                    "shape": {
+                        "shapeType": "TEXT_BOX",
+                        "text": {
+                            "textElements": [
+                                {
+                                    "endIndex": 1,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "endIndex": 1,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 1,
+                                    "endIndex": 2,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 1,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 1,
+                                    "endIndex": 2,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 2,
+                                    "endIndex": 3,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 2,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 2,
+                                    "endIndex": 3,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 3,
+                                    "endIndex": 4,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 3,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 3,
+                                    "endIndex": 4,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 4,
+                                    "endIndex": 5,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 4,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 4,
+                                    "endIndex": 5,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 5,
+                                    "endIndex": 6,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 5,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 5,
+                                    "endIndex": 6,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 6,
+                                    "endIndex": 7,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 6,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 6,
+                                    "endIndex": 7,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 7,
+                                    "endIndex": 8,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 7,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 7,
+                                    "endIndex": 8,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 8,
+                                    "endIndex": 9,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 8,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 8,
+                                    "endIndex": 9,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                }
+                            ],
+                            "lists": {
+                                "bodyPlaceholderListEntity": {
+                                    "listId": "bodyPlaceholderListEntity",
+                                    "nestingLevel": {
+                                        "0": {
+                                            "bulletStyle": {}
+                                        },
+                                        "1": {
+                                            "bulletStyle": {}
+                                        },
+                                        "2": {
+                                            "bulletStyle": {}
+                                        },
+                                        "3": {
+                                            "bulletStyle": {}
+                                        },
+                                        "4": {
+                                            "bulletStyle": {}
+                                        },
+                                        "5": {
+                                            "bulletStyle": {}
+                                        },
+                                        "6": {
+                                            "bulletStyle": {}
+                                        },
+                                        "7": {
+                                            "bulletStyle": {}
+                                        },
+                                        "8": {
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                }
+                            }
+                        },
+                        "shapeProperties": {
+                            "shapeBackgroundFill": {
+                                "propertyState": "INHERIT"
+                            },
+                            "outline": {
+                                "propertyState": "INHERIT"
+                            },
+                            "shadow": {
+                                "propertyState": "INHERIT"
+                            }
+                        },
+                        "placeholder": {
+                            "type": "TITLE",
+                            "parentObjectId": "p1_i0"
+                        }
+                    }
+                },
+                {
+                    "objectId": "p5_i1",
+                    "size": {
+                        "width": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        },
+                        "height": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        }
+                    },
+                    "transform": {
+                        "scaleX": 1.3333,
+                        "scaleY": 1.1388,
+                        "translateX": 311700,
+                        "translateY": 1152475,
+                        "unit": "EMU"
+                    },
+                    "shape": {
+                        "shapeType": "TEXT_BOX",
+                        "text": {
+                            "textElements": [
+                                {
+                                    "endIndex": 1,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "glyph": "●",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "endIndex": 1,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 14,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 1,
+                                    "endIndex": 2,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 1,
+                                            "glyph": "○",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 1,
+                                    "endIndex": 2,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 12,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 2,
+                                    "endIndex": 3,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 2,
+                                            "glyph": "■",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 2,
+                                    "endIndex": 3,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 12,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 3,
+                                    "endIndex": 4,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 3,
+                                            "glyph": "●",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 3,
+                                    "endIndex": 4,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 12,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 4,
+                                    "endIndex": 5,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 4,
+                                            "glyph": "○",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 4,
+                                    "endIndex": 5,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 12,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 5,
+                                    "endIndex": 6,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 5,
+                                            "glyph": "■",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 5,
+                                    "endIndex": 6,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 12,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 6,
+                                    "endIndex": 7,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 6,
+                                            "glyph": "●",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 6,
+                                    "endIndex": 7,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 12,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 7,
+                                    "endIndex": 8,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 7,
+                                            "glyph": "○",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 7,
+                                    "endIndex": 8,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 12,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 8,
+                                    "endIndex": 9,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 8,
+                                            "glyph": "■",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 8,
+                                    "endIndex": 9,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 12,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                }
+                            ],
+                            "lists": {
+                                "bodyPlaceholderListEntity": {
+                                    "listId": "bodyPlaceholderListEntity",
+                                    "nestingLevel": {
+                                        "0": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 14,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "1": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 12,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "2": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 12,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "3": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 12,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "4": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 12,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "5": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 12,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "6": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 12,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "7": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 12,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "8": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 12,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        },
+                        "shapeProperties": {
+                            "shapeBackgroundFill": {
+                                "propertyState": "INHERIT"
+                            },
+                            "outline": {
+                                "propertyState": "INHERIT"
+                            },
+                            "shadow": {
+                                "propertyState": "INHERIT"
+                            }
+                        },
+                        "placeholder": {
+                            "type": "BODY",
+                            "parentObjectId": "p1_i1"
+                        }
+                    }
+                },
+                {
+                    "objectId": "p5_i2",
+                    "size": {
+                        "width": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        },
+                        "height": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        }
+                    },
+                    "transform": {
+                        "scaleX": 1.3333,
+                        "scaleY": 1.1388,
+                        "translateX": 4832400,
+                        "translateY": 1152475,
+                        "unit": "EMU"
+                    },
+                    "shape": {
+                        "shapeType": "TEXT_BOX",
+                        "text": {
+                            "textElements": [
+                                {
+                                    "endIndex": 1,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "glyph": "●",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "endIndex": 1,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 14,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 1,
+                                    "endIndex": 2,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 1,
+                                            "glyph": "○",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 1,
+                                    "endIndex": 2,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 12,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 2,
+                                    "endIndex": 3,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 2,
+                                            "glyph": "■",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 2,
+                                    "endIndex": 3,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 12,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 3,
+                                    "endIndex": 4,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 3,
+                                            "glyph": "●",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 3,
+                                    "endIndex": 4,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 12,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 4,
+                                    "endIndex": 5,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 4,
+                                            "glyph": "○",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 4,
+                                    "endIndex": 5,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 12,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 5,
+                                    "endIndex": 6,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 5,
+                                            "glyph": "■",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 5,
+                                    "endIndex": 6,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 12,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 6,
+                                    "endIndex": 7,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 6,
+                                            "glyph": "●",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 6,
+                                    "endIndex": 7,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 12,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 7,
+                                    "endIndex": 8,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 7,
+                                            "glyph": "○",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 7,
+                                    "endIndex": 8,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 12,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 8,
+                                    "endIndex": 9,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 8,
+                                            "glyph": "■",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 8,
+                                    "endIndex": 9,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 12,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                }
+                            ],
+                            "lists": {
+                                "bodyPlaceholderListEntity": {
+                                    "listId": "bodyPlaceholderListEntity",
+                                    "nestingLevel": {
+                                        "0": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 14,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "1": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 12,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "2": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 12,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "3": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 12,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "4": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 12,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "5": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 12,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "6": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 12,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "7": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 12,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "8": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 12,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        },
+                        "shapeProperties": {
+                            "shapeBackgroundFill": {
+                                "propertyState": "INHERIT"
+                            },
+                            "outline": {
+                                "propertyState": "INHERIT"
+                            },
+                            "shadow": {
+                                "propertyState": "INHERIT"
+                            }
+                        },
+                        "placeholder": {
+                            "type": "BODY",
+                            "index": 1,
+                            "parentObjectId": "p1_i1"
+                        }
+                    }
+                },
+                {
+                    "objectId": "p5_i3",
+                    "size": {
+                        "width": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        },
+                        "height": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        }
+                    },
+                    "transform": {
+                        "scaleX": 0.1829,
+                        "scaleY": 0.1312,
+                        "translateX": 8472457.8125,
+                        "translateY": 4663216.797499999,
+                        "unit": "EMU"
+                    },
+                    "shape": {
+                        "shapeType": "TEXT_BOX",
+                        "text": {
+                            "textElements": [
+                                {
+                                    "endIndex": 2,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        }
+                                    }
+                                },
+                                {
+                                    "endIndex": 1,
+                                    "autoText": {
+                                        "type": "SLIDE_NUMBER",
+                                        "content": "#",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 1,
+                                    "endIndex": 2,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                }
+                            ]
+                        },
+                        "shapeProperties": {
+                            "shapeBackgroundFill": {
+                                "propertyState": "INHERIT"
+                            },
+                            "outline": {
+                                "propertyState": "INHERIT"
+                            },
+                            "shadow": {
+                                "propertyState": "INHERIT"
+                            }
+                        },
+                        "placeholder": {
+                            "type": "SLIDE_NUMBER",
+                            "parentObjectId": "p1_i2"
+                        }
+                    }
+                }
+            ],
+            "layoutProperties": {
+                "masterObjectId": "simple-light-2",
+                "name": "TITLE_AND_TWO_COLUMNS",
+                "displayName": "Title and two columns"
+            },
+            "pageProperties": {
+                "pageBackgroundFill": {
+                    "propertyState": "INHERIT"
+                }
+            }
+        },
+        {
+            "objectId": "p6",
+            "pageType": "LAYOUT",
+            "pageElements": [
+                {
+                    "objectId": "p6_i0",
+                    "size": {
+                        "width": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        },
+                        "height": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        }
+                    },
+                    "transform": {
+                        "scaleX": 2.8402,
+                        "scaleY": 0.1909,
+                        "translateX": 311700,
+                        "translateY": 445025,
+                        "unit": "EMU"
+                    },
+                    "shape": {
+                        "shapeType": "TEXT_BOX",
+                        "text": {
+                            "textElements": [
+                                {
+                                    "endIndex": 1,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "endIndex": 1,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 1,
+                                    "endIndex": 2,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 1,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 1,
+                                    "endIndex": 2,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 2,
+                                    "endIndex": 3,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 2,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 2,
+                                    "endIndex": 3,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 3,
+                                    "endIndex": 4,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 3,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 3,
+                                    "endIndex": 4,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 4,
+                                    "endIndex": 5,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 4,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 4,
+                                    "endIndex": 5,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 5,
+                                    "endIndex": 6,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 5,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 5,
+                                    "endIndex": 6,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 6,
+                                    "endIndex": 7,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 6,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 6,
+                                    "endIndex": 7,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 7,
+                                    "endIndex": 8,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 7,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 7,
+                                    "endIndex": 8,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 8,
+                                    "endIndex": 9,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 8,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 8,
+                                    "endIndex": 9,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                }
+                            ],
+                            "lists": {
+                                "bodyPlaceholderListEntity": {
+                                    "listId": "bodyPlaceholderListEntity",
+                                    "nestingLevel": {
+                                        "0": {
+                                            "bulletStyle": {}
+                                        },
+                                        "1": {
+                                            "bulletStyle": {}
+                                        },
+                                        "2": {
+                                            "bulletStyle": {}
+                                        },
+                                        "3": {
+                                            "bulletStyle": {}
+                                        },
+                                        "4": {
+                                            "bulletStyle": {}
+                                        },
+                                        "5": {
+                                            "bulletStyle": {}
+                                        },
+                                        "6": {
+                                            "bulletStyle": {}
+                                        },
+                                        "7": {
+                                            "bulletStyle": {}
+                                        },
+                                        "8": {
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                }
+                            }
+                        },
+                        "shapeProperties": {
+                            "shapeBackgroundFill": {
+                                "propertyState": "INHERIT"
+                            },
+                            "outline": {
+                                "propertyState": "INHERIT"
+                            },
+                            "shadow": {
+                                "propertyState": "INHERIT"
+                            }
+                        },
+                        "placeholder": {
+                            "type": "TITLE",
+                            "parentObjectId": "p1_i0"
+                        }
+                    }
+                },
+                {
+                    "objectId": "p6_i1",
+                    "size": {
+                        "width": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        },
+                        "height": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        }
+                    },
+                    "transform": {
+                        "scaleX": 0.1829,
+                        "scaleY": 0.1312,
+                        "translateX": 8472457.8125,
+                        "translateY": 4663216.797499999,
+                        "unit": "EMU"
+                    },
+                    "shape": {
+                        "shapeType": "TEXT_BOX",
+                        "text": {
+                            "textElements": [
+                                {
+                                    "endIndex": 2,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        }
+                                    }
+                                },
+                                {
+                                    "endIndex": 1,
+                                    "autoText": {
+                                        "type": "SLIDE_NUMBER",
+                                        "content": "#",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 1,
+                                    "endIndex": 2,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                }
+                            ]
+                        },
+                        "shapeProperties": {
+                            "shapeBackgroundFill": {
+                                "propertyState": "INHERIT"
+                            },
+                            "outline": {
+                                "propertyState": "INHERIT"
+                            },
+                            "shadow": {
+                                "propertyState": "INHERIT"
+                            }
+                        },
+                        "placeholder": {
+                            "type": "SLIDE_NUMBER",
+                            "parentObjectId": "p1_i2"
+                        }
+                    }
+                }
+            ],
+            "layoutProperties": {
+                "masterObjectId": "simple-light-2",
+                "name": "TITLE_ONLY",
+                "displayName": "Title only"
+            },
+            "pageProperties": {
+                "pageBackgroundFill": {
+                    "propertyState": "INHERIT"
+                }
+            }
+        },
+        {
+            "objectId": "p7",
+            "pageType": "LAYOUT",
+            "pageElements": [
+                {
+                    "objectId": "p7_i0",
+                    "size": {
+                        "width": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        },
+                        "height": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        }
+                    },
+                    "transform": {
+                        "scaleX": 0.936,
+                        "scaleY": 0.2519,
+                        "translateX": 311700,
+                        "translateY": 555600,
+                        "unit": "EMU"
+                    },
+                    "shape": {
+                        "shapeType": "TEXT_BOX",
+                        "text": {
+                            "textElements": [
+                                {
+                                    "endIndex": 1,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "endIndex": 1,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 24,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 1,
+                                    "endIndex": 2,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 1,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 1,
+                                    "endIndex": 2,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 24,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 2,
+                                    "endIndex": 3,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 2,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 2,
+                                    "endIndex": 3,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 24,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 3,
+                                    "endIndex": 4,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 3,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 3,
+                                    "endIndex": 4,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 24,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 4,
+                                    "endIndex": 5,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 4,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 4,
+                                    "endIndex": 5,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 24,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 5,
+                                    "endIndex": 6,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 5,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 5,
+                                    "endIndex": 6,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 24,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 6,
+                                    "endIndex": 7,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 6,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 6,
+                                    "endIndex": 7,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 24,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 7,
+                                    "endIndex": 8,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 7,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 7,
+                                    "endIndex": 8,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 24,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 8,
+                                    "endIndex": 9,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 8,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 8,
+                                    "endIndex": 9,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 24,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                }
+                            ],
+                            "lists": {
+                                "bodyPlaceholderListEntity": {
+                                    "listId": "bodyPlaceholderListEntity",
+                                    "nestingLevel": {
+                                        "0": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 24,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "1": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 24,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "2": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 24,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "3": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 24,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "4": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 24,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "5": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 24,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "6": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 24,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "7": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 24,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "8": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 24,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        },
+                        "shapeProperties": {
+                            "shapeBackgroundFill": {
+                                "propertyState": "INHERIT"
+                            },
+                            "outline": {
+                                "propertyState": "INHERIT"
+                            },
+                            "shadow": {
+                                "propertyState": "INHERIT"
+                            },
+                            "contentAlignment": "BOTTOM"
+                        },
+                        "placeholder": {
+                            "type": "TITLE",
+                            "parentObjectId": "p1_i0"
+                        }
+                    }
+                },
+                {
+                    "objectId": "p7_i1",
+                    "size": {
+                        "width": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        },
+                        "height": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        }
+                    },
+                    "transform": {
+                        "scaleX": 0.936,
+                        "scaleY": 1.0598,
+                        "translateX": 311700,
+                        "translateY": 1389600,
+                        "unit": "EMU"
+                    },
+                    "shape": {
+                        "shapeType": "TEXT_BOX",
+                        "text": {
+                            "textElements": [
+                                {
+                                    "endIndex": 1,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "glyph": "●",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "endIndex": 1,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 12,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 1,
+                                    "endIndex": 2,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 1,
+                                            "glyph": "○",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 1,
+                                    "endIndex": 2,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 12,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 2,
+                                    "endIndex": 3,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 2,
+                                            "glyph": "■",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 2,
+                                    "endIndex": 3,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 12,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 3,
+                                    "endIndex": 4,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 3,
+                                            "glyph": "●",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 3,
+                                    "endIndex": 4,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 12,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 4,
+                                    "endIndex": 5,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 4,
+                                            "glyph": "○",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 4,
+                                    "endIndex": 5,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 12,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 5,
+                                    "endIndex": 6,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 5,
+                                            "glyph": "■",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 5,
+                                    "endIndex": 6,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 12,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 6,
+                                    "endIndex": 7,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 6,
+                                            "glyph": "●",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 6,
+                                    "endIndex": 7,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 12,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 7,
+                                    "endIndex": 8,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 7,
+                                            "glyph": "○",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 7,
+                                    "endIndex": 8,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 12,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 8,
+                                    "endIndex": 9,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 8,
+                                            "glyph": "■",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 8,
+                                    "endIndex": 9,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 12,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                }
+                            ],
+                            "lists": {
+                                "bodyPlaceholderListEntity": {
+                                    "listId": "bodyPlaceholderListEntity",
+                                    "nestingLevel": {
+                                        "0": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 12,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "1": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 12,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "2": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 12,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "3": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 12,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "4": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 12,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "5": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 12,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "6": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 12,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "7": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 12,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "8": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 12,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        },
+                        "shapeProperties": {
+                            "shapeBackgroundFill": {
+                                "propertyState": "INHERIT"
+                            },
+                            "outline": {
+                                "propertyState": "INHERIT"
+                            },
+                            "shadow": {
+                                "propertyState": "INHERIT"
+                            }
+                        },
+                        "placeholder": {
+                            "type": "BODY",
+                            "parentObjectId": "p1_i1"
+                        }
+                    }
+                },
+                {
+                    "objectId": "p7_i2",
+                    "size": {
+                        "width": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        },
+                        "height": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        }
+                    },
+                    "transform": {
+                        "scaleX": 0.1829,
+                        "scaleY": 0.1312,
+                        "translateX": 8472457.8125,
+                        "translateY": 4663216.797499999,
+                        "unit": "EMU"
+                    },
+                    "shape": {
+                        "shapeType": "TEXT_BOX",
+                        "text": {
+                            "textElements": [
+                                {
+                                    "endIndex": 2,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        }
+                                    }
+                                },
+                                {
+                                    "endIndex": 1,
+                                    "autoText": {
+                                        "type": "SLIDE_NUMBER",
+                                        "content": "#",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 1,
+                                    "endIndex": 2,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                }
+                            ]
+                        },
+                        "shapeProperties": {
+                            "shapeBackgroundFill": {
+                                "propertyState": "INHERIT"
+                            },
+                            "outline": {
+                                "propertyState": "INHERIT"
+                            },
+                            "shadow": {
+                                "propertyState": "INHERIT"
+                            }
+                        },
+                        "placeholder": {
+                            "type": "SLIDE_NUMBER",
+                            "parentObjectId": "p1_i2"
+                        }
+                    }
+                }
+            ],
+            "layoutProperties": {
+                "masterObjectId": "simple-light-2",
+                "name": "ONE_COLUMN_TEXT",
+                "displayName": "One column text"
+            },
+            "pageProperties": {
+                "pageBackgroundFill": {
+                    "propertyState": "INHERIT"
+                }
+            }
+        },
+        {
+            "objectId": "p8",
+            "pageType": "LAYOUT",
+            "pageElements": [
+                {
+                    "objectId": "p8_i0",
+                    "size": {
+                        "width": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        },
+                        "height": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        }
+                    },
+                    "transform": {
+                        "scaleX": 2.1226,
+                        "scaleY": 1.3636,
+                        "translateX": 490250,
+                        "translateY": 450150,
+                        "unit": "EMU"
+                    },
+                    "shape": {
+                        "shapeType": "TEXT_BOX",
+                        "text": {
+                            "textElements": [
+                                {
+                                    "endIndex": 1,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "endIndex": 1,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 48,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 1,
+                                    "endIndex": 2,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 1,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 1,
+                                    "endIndex": 2,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 48,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 2,
+                                    "endIndex": 3,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 2,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 2,
+                                    "endIndex": 3,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 48,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 3,
+                                    "endIndex": 4,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 3,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 3,
+                                    "endIndex": 4,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 48,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 4,
+                                    "endIndex": 5,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 4,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 4,
+                                    "endIndex": 5,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 48,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 5,
+                                    "endIndex": 6,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 5,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 5,
+                                    "endIndex": 6,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 48,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 6,
+                                    "endIndex": 7,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 6,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 6,
+                                    "endIndex": 7,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 48,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 7,
+                                    "endIndex": 8,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 7,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 7,
+                                    "endIndex": 8,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 48,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 8,
+                                    "endIndex": 9,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 8,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 8,
+                                    "endIndex": 9,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 48,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                }
+                            ],
+                            "lists": {
+                                "bodyPlaceholderListEntity": {
+                                    "listId": "bodyPlaceholderListEntity",
+                                    "nestingLevel": {
+                                        "0": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 48,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "1": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 48,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "2": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 48,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "3": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 48,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "4": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 48,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "5": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 48,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "6": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 48,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "7": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 48,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "8": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 48,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        },
+                        "shapeProperties": {
+                            "shapeBackgroundFill": {
+                                "propertyState": "INHERIT"
+                            },
+                            "outline": {
+                                "propertyState": "INHERIT"
+                            },
+                            "shadow": {
+                                "propertyState": "INHERIT"
+                            },
+                            "contentAlignment": "MIDDLE"
+                        },
+                        "placeholder": {
+                            "type": "TITLE",
+                            "parentObjectId": "p1_i0"
+                        }
+                    }
+                },
+                {
+                    "objectId": "p8_i1",
+                    "size": {
+                        "width": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        },
+                        "height": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        }
+                    },
+                    "transform": {
+                        "scaleX": 0.1829,
+                        "scaleY": 0.1312,
+                        "translateX": 8472457.8125,
+                        "translateY": 4663216.797499999,
+                        "unit": "EMU"
+                    },
+                    "shape": {
+                        "shapeType": "TEXT_BOX",
+                        "text": {
+                            "textElements": [
+                                {
+                                    "endIndex": 2,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        }
+                                    }
+                                },
+                                {
+                                    "endIndex": 1,
+                                    "autoText": {
+                                        "type": "SLIDE_NUMBER",
+                                        "content": "#",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 1,
+                                    "endIndex": 2,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                }
+                            ]
+                        },
+                        "shapeProperties": {
+                            "shapeBackgroundFill": {
+                                "propertyState": "INHERIT"
+                            },
+                            "outline": {
+                                "propertyState": "INHERIT"
+                            },
+                            "shadow": {
+                                "propertyState": "INHERIT"
+                            }
+                        },
+                        "placeholder": {
+                            "type": "SLIDE_NUMBER",
+                            "parentObjectId": "p1_i2"
+                        }
+                    }
+                }
+            ],
+            "layoutProperties": {
+                "masterObjectId": "simple-light-2",
+                "name": "MAIN_POINT",
+                "displayName": "Main point"
+            },
+            "pageProperties": {
+                "pageBackgroundFill": {
+                    "propertyState": "INHERIT"
+                }
+            }
+        },
+        {
+            "objectId": "p9",
+            "pageType": "LAYOUT",
+            "pageElements": [
+                {
+                    "objectId": "p9_i0",
+                    "size": {
+                        "width": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        },
+                        "height": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        }
+                    },
+                    "transform": {
+                        "scaleX": 1.524,
+                        "scaleY": 1.7145,
+                        "translateX": 4572000,
+                        "translateY": -125,
+                        "unit": "EMU"
+                    },
+                    "shape": {
+                        "shapeType": "RECTANGLE",
+                        "shapeProperties": {
+                            "shapeBackgroundFill": {
+                                "solidFill": {
+                                    "color": {
+                                        "themeColor": "LIGHT2"
+                                    },
+                                    "alpha": 1
+                                }
+                            },
+                            "outline": {
+                                "outlineFill": {
+                                    "solidFill": {
+                                        "color": {
+                                            "rgbColor": {}
+                                        },
+                                        "alpha": 1
+                                    }
+                                },
+                                "weight": {
+                                    "magnitude": 9525,
+                                    "unit": "EMU"
+                                },
+                                "dashStyle": "SOLID",
+                                "propertyState": "NOT_RENDERED"
+                            },
+                            "shadow": {
+                                "type": "OUTER",
+                                "transform": {
+                                    "scaleX": 1,
+                                    "scaleY": 1,
+                                    "unit": "EMU"
+                                },
+                                "alignment": "BOTTOM_LEFT",
+                                "blurRadius": {
+                                    "unit": "EMU"
+                                },
+                                "color": {
+                                    "rgbColor": {}
+                                },
+                                "alpha": 1,
+                                "rotateWithShape": false,
+                                "propertyState": "NOT_RENDERED"
+                            },
+                            "contentAlignment": "MIDDLE"
+                        }
+                    }
+                },
+                {
+                    "objectId": "p9_i1",
+                    "size": {
+                        "width": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        },
+                        "height": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        }
+                    },
+                    "transform": {
+                        "scaleX": 1.3484,
+                        "scaleY": 0.4941,
+                        "translateX": 265500,
+                        "translateY": 1233175,
+                        "unit": "EMU"
+                    },
+                    "shape": {
+                        "shapeType": "TEXT_BOX",
+                        "text": {
+                            "textElements": [
+                                {
+                                    "endIndex": 1,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "alignment": "CENTER",
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "endIndex": 1,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 42,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 1,
+                                    "endIndex": 2,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "alignment": "CENTER",
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 1,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 1,
+                                    "endIndex": 2,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 42,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 2,
+                                    "endIndex": 3,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "alignment": "CENTER",
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 2,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 2,
+                                    "endIndex": 3,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 42,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 3,
+                                    "endIndex": 4,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "alignment": "CENTER",
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 3,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 3,
+                                    "endIndex": 4,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 42,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 4,
+                                    "endIndex": 5,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "alignment": "CENTER",
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 4,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 4,
+                                    "endIndex": 5,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 42,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 5,
+                                    "endIndex": 6,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "alignment": "CENTER",
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 5,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 5,
+                                    "endIndex": 6,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 42,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 6,
+                                    "endIndex": 7,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "alignment": "CENTER",
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 6,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 6,
+                                    "endIndex": 7,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 42,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 7,
+                                    "endIndex": 8,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "alignment": "CENTER",
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 7,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 7,
+                                    "endIndex": 8,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 42,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 8,
+                                    "endIndex": 9,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "alignment": "CENTER",
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 8,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 8,
+                                    "endIndex": 9,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 42,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                }
+                            ],
+                            "lists": {
+                                "bodyPlaceholderListEntity": {
+                                    "listId": "bodyPlaceholderListEntity",
+                                    "nestingLevel": {
+                                        "0": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 42,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "1": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 42,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "2": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 42,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "3": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 42,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "4": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 42,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "5": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 42,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "6": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 42,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "7": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 42,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "8": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 42,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        },
+                        "shapeProperties": {
+                            "shapeBackgroundFill": {
+                                "propertyState": "INHERIT"
+                            },
+                            "outline": {
+                                "propertyState": "INHERIT"
+                            },
+                            "shadow": {
+                                "propertyState": "INHERIT"
+                            },
+                            "contentAlignment": "BOTTOM"
+                        },
+                        "placeholder": {
+                            "type": "TITLE",
+                            "parentObjectId": "p1_i0"
+                        }
+                    }
+                },
+                {
+                    "objectId": "p9_i2",
+                    "size": {
+                        "width": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        },
+                        "height": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        }
+                    },
+                    "transform": {
+                        "scaleX": 1.3484,
+                        "scaleY": 0.4117,
+                        "translateX": 265500,
+                        "translateY": 2803075,
+                        "unit": "EMU"
+                    },
+                    "shape": {
+                        "shapeType": "TEXT_BOX",
+                        "text": {
+                            "textElements": [
+                                {
+                                    "endIndex": 1,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "lineSpacing": 100,
+                                            "alignment": "CENTER",
+                                            "spaceBelow": {
+                                                "unit": "PT"
+                                            },
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "endIndex": 1,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 21,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 1,
+                                    "endIndex": 2,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "lineSpacing": 100,
+                                            "alignment": "CENTER",
+                                            "spaceBelow": {
+                                                "unit": "PT"
+                                            },
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 1,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 1,
+                                    "endIndex": 2,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 21,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 2,
+                                    "endIndex": 3,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "lineSpacing": 100,
+                                            "alignment": "CENTER",
+                                            "spaceBelow": {
+                                                "unit": "PT"
+                                            },
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 2,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 2,
+                                    "endIndex": 3,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 21,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 3,
+                                    "endIndex": 4,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "lineSpacing": 100,
+                                            "alignment": "CENTER",
+                                            "spaceBelow": {
+                                                "unit": "PT"
+                                            },
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 3,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 3,
+                                    "endIndex": 4,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 21,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 4,
+                                    "endIndex": 5,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "lineSpacing": 100,
+                                            "alignment": "CENTER",
+                                            "spaceBelow": {
+                                                "unit": "PT"
+                                            },
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 4,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 4,
+                                    "endIndex": 5,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 21,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 5,
+                                    "endIndex": 6,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "lineSpacing": 100,
+                                            "alignment": "CENTER",
+                                            "spaceBelow": {
+                                                "unit": "PT"
+                                            },
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 5,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 5,
+                                    "endIndex": 6,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 21,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 6,
+                                    "endIndex": 7,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "lineSpacing": 100,
+                                            "alignment": "CENTER",
+                                            "spaceBelow": {
+                                                "unit": "PT"
+                                            },
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 6,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 6,
+                                    "endIndex": 7,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 21,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 7,
+                                    "endIndex": 8,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "lineSpacing": 100,
+                                            "alignment": "CENTER",
+                                            "spaceBelow": {
+                                                "unit": "PT"
+                                            },
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 7,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 7,
+                                    "endIndex": 8,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 21,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 8,
+                                    "endIndex": 9,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "lineSpacing": 100,
+                                            "alignment": "CENTER",
+                                            "spaceBelow": {
+                                                "unit": "PT"
+                                            },
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 8,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 8,
+                                    "endIndex": 9,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 21,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                }
+                            ],
+                            "lists": {
+                                "bodyPlaceholderListEntity": {
+                                    "listId": "bodyPlaceholderListEntity",
+                                    "nestingLevel": {
+                                        "0": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 21,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "1": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 21,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "2": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 21,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "3": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 21,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "4": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 21,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "5": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 21,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "6": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 21,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "7": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 21,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "8": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 21,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        },
+                        "shapeProperties": {
+                            "shapeBackgroundFill": {
+                                "propertyState": "INHERIT"
+                            },
+                            "outline": {
+                                "propertyState": "INHERIT"
+                            },
+                            "shadow": {
+                                "propertyState": "INHERIT"
+                            }
+                        },
+                        "placeholder": {
+                            "type": "SUBTITLE",
+                            "parentObjectId": "p1_i1"
+                        }
+                    }
+                },
+                {
+                    "objectId": "p9_i3",
+                    "size": {
+                        "width": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        },
+                        "height": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        }
+                    },
+                    "transform": {
+                        "scaleX": 1.279,
+                        "scaleY": 1.2317,
+                        "translateX": 4939500,
+                        "translateY": 724075,
+                        "unit": "EMU"
+                    },
+                    "shape": {
+                        "shapeType": "TEXT_BOX",
+                        "text": {
+                            "textElements": [
+                                {
+                                    "endIndex": 1,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "glyph": "●",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "endIndex": 1,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 1,
+                                    "endIndex": 2,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 1,
+                                            "glyph": "○",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 1,
+                                    "endIndex": 2,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 2,
+                                    "endIndex": 3,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 2,
+                                            "glyph": "■",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 2,
+                                    "endIndex": 3,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 3,
+                                    "endIndex": 4,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 3,
+                                            "glyph": "●",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 3,
+                                    "endIndex": 4,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 4,
+                                    "endIndex": 5,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 4,
+                                            "glyph": "○",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 4,
+                                    "endIndex": 5,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 5,
+                                    "endIndex": 6,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 5,
+                                            "glyph": "■",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 5,
+                                    "endIndex": 6,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 6,
+                                    "endIndex": 7,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 6,
+                                            "glyph": "●",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 6,
+                                    "endIndex": 7,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 7,
+                                    "endIndex": 8,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 7,
+                                            "glyph": "○",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 7,
+                                    "endIndex": 8,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 8,
+                                    "endIndex": 9,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 8,
+                                            "glyph": "■",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 8,
+                                    "endIndex": 9,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                }
+                            ],
+                            "lists": {
+                                "bodyPlaceholderListEntity": {
+                                    "listId": "bodyPlaceholderListEntity",
+                                    "nestingLevel": {
+                                        "0": {
+                                            "bulletStyle": {}
+                                        },
+                                        "1": {
+                                            "bulletStyle": {}
+                                        },
+                                        "2": {
+                                            "bulletStyle": {}
+                                        },
+                                        "3": {
+                                            "bulletStyle": {}
+                                        },
+                                        "4": {
+                                            "bulletStyle": {}
+                                        },
+                                        "5": {
+                                            "bulletStyle": {}
+                                        },
+                                        "6": {
+                                            "bulletStyle": {}
+                                        },
+                                        "7": {
+                                            "bulletStyle": {}
+                                        },
+                                        "8": {
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                }
+                            }
+                        },
+                        "shapeProperties": {
+                            "shapeBackgroundFill": {
+                                "propertyState": "INHERIT"
+                            },
+                            "outline": {
+                                "propertyState": "INHERIT"
+                            },
+                            "shadow": {
+                                "propertyState": "INHERIT"
+                            },
+                            "contentAlignment": "MIDDLE"
+                        },
+                        "placeholder": {
+                            "type": "BODY",
+                            "parentObjectId": "p1_i1"
+                        }
+                    }
+                },
+                {
+                    "objectId": "p9_i4",
+                    "size": {
+                        "width": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        },
+                        "height": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        }
+                    },
+                    "transform": {
+                        "scaleX": 0.1829,
+                        "scaleY": 0.1312,
+                        "translateX": 8472457.8125,
+                        "translateY": 4663216.797499999,
+                        "unit": "EMU"
+                    },
+                    "shape": {
+                        "shapeType": "TEXT_BOX",
+                        "text": {
+                            "textElements": [
+                                {
+                                    "endIndex": 2,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        }
+                                    }
+                                },
+                                {
+                                    "endIndex": 1,
+                                    "autoText": {
+                                        "type": "SLIDE_NUMBER",
+                                        "content": "#",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 1,
+                                    "endIndex": 2,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                }
+                            ]
+                        },
+                        "shapeProperties": {
+                            "shapeBackgroundFill": {
+                                "propertyState": "INHERIT"
+                            },
+                            "outline": {
+                                "propertyState": "INHERIT"
+                            },
+                            "shadow": {
+                                "propertyState": "INHERIT"
+                            }
+                        },
+                        "placeholder": {
+                            "type": "SLIDE_NUMBER",
+                            "parentObjectId": "p1_i2"
+                        }
+                    }
+                }
+            ],
+            "layoutProperties": {
+                "masterObjectId": "simple-light-2",
+                "name": "SECTION_TITLE_AND_DESCRIPTION",
+                "displayName": "Section title and description"
+            },
+            "pageProperties": {
+                "pageBackgroundFill": {
+                    "propertyState": "INHERIT"
+                }
+            }
+        },
+        {
+            "objectId": "p10",
+            "pageType": "LAYOUT",
+            "pageElements": [
+                {
+                    "objectId": "p10_i0",
+                    "size": {
+                        "width": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        },
+                        "height": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        }
+                    },
+                    "transform": {
+                        "scaleX": 1.9996,
+                        "scaleY": 0.2017,
+                        "translateX": 311700,
+                        "translateY": 4230575,
+                        "unit": "EMU"
+                    },
+                    "shape": {
+                        "shapeType": "TEXT_BOX",
+                        "text": {
+                            "textElements": [
+                                {
+                                    "endIndex": 1,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "lineSpacing": 100,
+                                            "spaceBelow": {
+                                                "unit": "PT"
+                                            },
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "endIndex": 1,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                }
+                            ],
+                            "lists": {
+                                "bodyPlaceholderListEntity": {
+                                    "listId": "bodyPlaceholderListEntity",
+                                    "nestingLevel": {
+                                        "0": {
+                                            "bulletStyle": {}
+                                        },
+                                        "1": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 18,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "2": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 18,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "3": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 18,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "4": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 18,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "5": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 18,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "6": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 18,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "7": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 18,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "8": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 18,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        },
+                        "shapeProperties": {
+                            "shapeBackgroundFill": {
+                                "propertyState": "INHERIT"
+                            },
+                            "outline": {
+                                "propertyState": "INHERIT"
+                            },
+                            "shadow": {
+                                "propertyState": "INHERIT"
+                            },
+                            "contentAlignment": "MIDDLE"
+                        },
+                        "placeholder": {
+                            "type": "BODY",
+                            "parentObjectId": "p1_i1"
+                        }
+                    }
+                },
+                {
+                    "objectId": "p10_i1",
+                    "size": {
+                        "width": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        },
+                        "height": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        }
+                    },
+                    "transform": {
+                        "scaleX": 0.1829,
+                        "scaleY": 0.1312,
+                        "translateX": 8472457.8125,
+                        "translateY": 4663216.797499999,
+                        "unit": "EMU"
+                    },
+                    "shape": {
+                        "shapeType": "TEXT_BOX",
+                        "text": {
+                            "textElements": [
+                                {
+                                    "endIndex": 2,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        }
+                                    }
+                                },
+                                {
+                                    "endIndex": 1,
+                                    "autoText": {
+                                        "type": "SLIDE_NUMBER",
+                                        "content": "#",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 1,
+                                    "endIndex": 2,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                }
+                            ]
+                        },
+                        "shapeProperties": {
+                            "shapeBackgroundFill": {
+                                "propertyState": "INHERIT"
+                            },
+                            "outline": {
+                                "propertyState": "INHERIT"
+                            },
+                            "shadow": {
+                                "propertyState": "INHERIT"
+                            }
+                        },
+                        "placeholder": {
+                            "type": "SLIDE_NUMBER",
+                            "parentObjectId": "p1_i2"
+                        }
+                    }
+                }
+            ],
+            "layoutProperties": {
+                "masterObjectId": "simple-light-2",
+                "name": "CAPTION_ONLY",
+                "displayName": "Caption"
+            },
+            "pageProperties": {
+                "pageBackgroundFill": {
+                    "propertyState": "INHERIT"
+                }
+            }
+        },
+        {
+            "objectId": "p11",
+            "pageType": "LAYOUT",
+            "pageElements": [
+                {
+                    "objectId": "p11_i0",
+                    "size": {
+                        "width": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        },
+                        "height": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        }
+                    },
+                    "transform": {
+                        "scaleX": 2.8402,
+                        "scaleY": 0.6545,
+                        "translateX": 311700,
+                        "translateY": 1106125,
+                        "unit": "EMU"
+                    },
+                    "shape": {
+                        "shapeType": "TEXT_BOX",
+                        "text": {
+                            "textElements": [
+                                {
+                                    "endIndex": 1,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "alignment": "CENTER",
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "endIndex": 1,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 120,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 1,
+                                    "endIndex": 2,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "alignment": "CENTER",
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 1,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 1,
+                                    "endIndex": 2,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 120,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 2,
+                                    "endIndex": 3,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "alignment": "CENTER",
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 2,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 2,
+                                    "endIndex": 3,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 120,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 3,
+                                    "endIndex": 4,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "alignment": "CENTER",
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 3,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 3,
+                                    "endIndex": 4,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 120,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 4,
+                                    "endIndex": 5,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "alignment": "CENTER",
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 4,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 4,
+                                    "endIndex": 5,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 120,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 5,
+                                    "endIndex": 6,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "alignment": "CENTER",
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 5,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 5,
+                                    "endIndex": 6,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 120,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 6,
+                                    "endIndex": 7,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "alignment": "CENTER",
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 6,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 6,
+                                    "endIndex": 7,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 120,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 7,
+                                    "endIndex": 8,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "alignment": "CENTER",
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 7,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 7,
+                                    "endIndex": 8,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 120,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 8,
+                                    "endIndex": 9,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "alignment": "CENTER",
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 8,
+                                            "glyph": " ",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 8,
+                                    "endIndex": 9,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {
+                                            "fontSize": {
+                                                "magnitude": 120,
+                                                "unit": "PT"
+                                            }
+                                        }
+                                    }
+                                }
+                            ],
+                            "lists": {
+                                "bodyPlaceholderListEntity": {
+                                    "listId": "bodyPlaceholderListEntity",
+                                    "nestingLevel": {
+                                        "0": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 120,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "1": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 120,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "2": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 120,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "3": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 120,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "4": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 120,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "5": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 120,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "6": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 120,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "7": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 120,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        },
+                                        "8": {
+                                            "bulletStyle": {
+                                                "fontSize": {
+                                                    "magnitude": 120,
+                                                    "unit": "PT"
+                                                }
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        },
+                        "shapeProperties": {
+                            "shapeBackgroundFill": {
+                                "propertyState": "INHERIT"
+                            },
+                            "outline": {
+                                "propertyState": "INHERIT"
+                            },
+                            "shadow": {
+                                "propertyState": "INHERIT"
+                            },
+                            "contentAlignment": "BOTTOM"
+                        },
+                        "placeholder": {
+                            "type": "TITLE",
+                            "parentObjectId": "p1_i0"
+                        }
+                    }
+                },
+                {
+                    "objectId": "p11_i1",
+                    "size": {
+                        "width": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        },
+                        "height": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        }
+                    },
+                    "transform": {
+                        "scaleX": 2.8402,
+                        "scaleY": 0.4336,
+                        "translateX": 311700,
+                        "translateY": 3152225,
+                        "unit": "EMU"
+                    },
+                    "shape": {
+                        "shapeType": "TEXT_BOX",
+                        "text": {
+                            "textElements": [
+                                {
+                                    "endIndex": 1,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "alignment": "CENTER",
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "glyph": "●",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "endIndex": 1,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 1,
+                                    "endIndex": 2,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "alignment": "CENTER",
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 1,
+                                            "glyph": "○",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 1,
+                                    "endIndex": 2,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 2,
+                                    "endIndex": 3,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "alignment": "CENTER",
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 2,
+                                            "glyph": "■",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 2,
+                                    "endIndex": 3,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 3,
+                                    "endIndex": 4,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "alignment": "CENTER",
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 3,
+                                            "glyph": "●",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 3,
+                                    "endIndex": 4,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 4,
+                                    "endIndex": 5,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "alignment": "CENTER",
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 4,
+                                            "glyph": "○",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 4,
+                                    "endIndex": 5,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 5,
+                                    "endIndex": 6,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "alignment": "CENTER",
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 5,
+                                            "glyph": "■",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 5,
+                                    "endIndex": 6,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 6,
+                                    "endIndex": 7,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "alignment": "CENTER",
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 6,
+                                            "glyph": "●",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 6,
+                                    "endIndex": 7,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 7,
+                                    "endIndex": 8,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "alignment": "CENTER",
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 7,
+                                            "glyph": "○",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 7,
+                                    "endIndex": 8,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 8,
+                                    "endIndex": 9,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "alignment": "CENTER",
+                                            "direction": "LEFT_TO_RIGHT"
+                                        },
+                                        "bullet": {
+                                            "listId": "bodyPlaceholderListEntity",
+                                            "nestingLevel": 8,
+                                            "glyph": "■",
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                },
+                                {
+                                    "startIndex": 8,
+                                    "endIndex": 9,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                }
+                            ],
+                            "lists": {
+                                "bodyPlaceholderListEntity": {
+                                    "listId": "bodyPlaceholderListEntity",
+                                    "nestingLevel": {
+                                        "0": {
+                                            "bulletStyle": {}
+                                        },
+                                        "1": {
+                                            "bulletStyle": {}
+                                        },
+                                        "2": {
+                                            "bulletStyle": {}
+                                        },
+                                        "3": {
+                                            "bulletStyle": {}
+                                        },
+                                        "4": {
+                                            "bulletStyle": {}
+                                        },
+                                        "5": {
+                                            "bulletStyle": {}
+                                        },
+                                        "6": {
+                                            "bulletStyle": {}
+                                        },
+                                        "7": {
+                                            "bulletStyle": {}
+                                        },
+                                        "8": {
+                                            "bulletStyle": {}
+                                        }
+                                    }
+                                }
+                            }
+                        },
+                        "shapeProperties": {
+                            "shapeBackgroundFill": {
+                                "propertyState": "INHERIT"
+                            },
+                            "outline": {
+                                "propertyState": "INHERIT"
+                            },
+                            "shadow": {
+                                "propertyState": "INHERIT"
+                            }
+                        },
+                        "placeholder": {
+                            "type": "BODY",
+                            "parentObjectId": "p1_i1"
+                        }
+                    }
+                },
+                {
+                    "objectId": "p11_i2",
+                    "size": {
+                        "width": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        },
+                        "height": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        }
+                    },
+                    "transform": {
+                        "scaleX": 0.1829,
+                        "scaleY": 0.1312,
+                        "translateX": 8472457.8125,
+                        "translateY": 4663216.797499999,
+                        "unit": "EMU"
+                    },
+                    "shape": {
+                        "shapeType": "TEXT_BOX",
+                        "text": {
+                            "textElements": [
+                                {
+                                    "endIndex": 2,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        }
+                                    }
+                                },
+                                {
+                                    "endIndex": 1,
+                                    "autoText": {
+                                        "type": "SLIDE_NUMBER",
+                                        "content": "#",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 1,
+                                    "endIndex": 2,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                }
+                            ]
+                        },
+                        "shapeProperties": {
+                            "shapeBackgroundFill": {
+                                "propertyState": "INHERIT"
+                            },
+                            "outline": {
+                                "propertyState": "INHERIT"
+                            },
+                            "shadow": {
+                                "propertyState": "INHERIT"
+                            }
+                        },
+                        "placeholder": {
+                            "type": "SLIDE_NUMBER",
+                            "parentObjectId": "p1_i2"
+                        }
+                    }
+                }
+            ],
+            "layoutProperties": {
+                "masterObjectId": "simple-light-2",
+                "name": "BIG_NUMBER",
+                "displayName": "Big number"
+            },
+            "pageProperties": {
+                "pageBackgroundFill": {
+                    "propertyState": "INHERIT"
+                }
+            }
+        },
+        {
+            "objectId": "p12",
+            "pageType": "LAYOUT",
+            "pageElements": [
+                {
+                    "objectId": "p12_i0",
+                    "size": {
+                        "width": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        },
+                        "height": {
+                            "magnitude": 3000000,
+                            "unit": "EMU"
+                        }
+                    },
+                    "transform": {
+                        "scaleX": 0.1829,
+                        "scaleY": 0.1312,
+                        "translateX": 8472457.8125,
+                        "translateY": 4663216.797499999,
+                        "unit": "EMU"
+                    },
+                    "shape": {
+                        "shapeType": "TEXT_BOX",
+                        "text": {
+                            "textElements": [
+                                {
+                                    "endIndex": 2,
+                                    "paragraphMarker": {
+                                        "style": {
+                                            "direction": "LEFT_TO_RIGHT"
+                                        }
+                                    }
+                                },
+                                {
+                                    "endIndex": 1,
+                                    "autoText": {
+                                        "type": "SLIDE_NUMBER",
+                                        "content": "#",
+                                        "style": {}
+                                    }
+                                },
+                                {
+                                    "startIndex": 1,
+                                    "endIndex": 2,
+                                    "textRun": {
+                                        "content": "\n",
+                                        "style": {}
+                                    }
+                                }
+                            ]
+                        },
+                        "shapeProperties": {
+                            "shapeBackgroundFill": {
+                                "propertyState": "INHERIT"
+                            },
+                            "outline": {
+                                "propertyState": "INHERIT"
+                            },
+                            "shadow": {
+                                "propertyState": "INHERIT"
+                            }
+                        },
+                        "placeholder": {
+                            "type": "SLIDE_NUMBER",
+                            "parentObjectId": "p1_i2"
+                        }
+                    }
+                }
+            ],
+            "layoutProperties": {
+                "masterObjectId": "simple-light-2",
+                "name": "BLANK",
+                "displayName": "Blank"
+            },
+            "pageProperties": {
+                "pageBackgroundFill": {
+                    "propertyState": "INHERIT"
+                }
+            }
+        }
+    ],
+    "locale": "en",
+    "revisionId": "kaHql7SEgvqFcw",
+    "notesMaster": {
+        "objectId": "n",
+        "pageType": "NOTES_MASTER",
+        "pageElements": [
+            {
+                "objectId": "n:slide",
+                "size": {
+                    "width": {
+                        "magnitude": 3000000,
+                        "unit": "EMU"
+                    },
+                    "height": {
+                        "magnitude": 3000000,
+                        "unit": "EMU"
+                    }
+                },
+                "transform": {
+                    "scaleX": 2.032025,
+                    "scaleY": 1.143,
+                    "translateX": 381300,
+                    "translateY": 685800,
+                    "unit": "EMU"
+                },
+                "shape": {
+                    "shapeProperties": {
+                        "outline": {
+                            "outlineFill": {
+                                "solidFill": {
+                                    "color": {
+                                        "rgbColor": {}
+                                    },
+                                    "alpha": 1
+                                }
+                            },
+                            "weight": {
+                                "magnitude": 9525,
+                                "unit": "EMU"
+                            },
+                            "dashStyle": "SOLID"
+                        },
+                        "contentAlignment": "MIDDLE"
+                    },
+                    "placeholder": {
+                        "type": "SLIDE_IMAGE"
+                    }
+                }
+            },
+            {
+                "objectId": "n:text",
+                "size": {
+                    "width": {
+                        "magnitude": 3000000,
+                        "unit": "EMU"
+                    },
+                    "height": {
+                        "magnitude": 3000000,
+                        "unit": "EMU"
+                    }
+                },
+                "transform": {
+                    "scaleX": 1.8288,
+                    "scaleY": 1.3716,
+                    "translateX": 685800,
+                    "translateY": 4343400,
+                    "unit": "EMU"
+                },
+                "shape": {
+                    "shapeType": "TEXT_BOX",
+                    "text": {
+                        "textElements": [
+                            {
+                                "endIndex": 1,
+                                "paragraphMarker": {
+                                    "style": {
+                                        "lineSpacing": 100,
+                                        "alignment": "START",
+                                        "indentStart": {
+                                            "unit": "PT"
+                                        },
+                                        "indentEnd": {
+                                            "unit": "PT"
+                                        },
+                                        "spaceAbove": {
+                                            "unit": "PT"
+                                        },
+                                        "spaceBelow": {
+                                            "unit": "PT"
+                                        },
+                                        "indentFirstLine": {
+                                            "unit": "PT"
+                                        },
+                                        "direction": "LEFT_TO_RIGHT",
+                                        "spacingMode": "COLLAPSE_LISTS"
+                                    },
+                                    "bullet": {
+                                        "listId": "bodyPlaceholderListEntity",
+                                        "glyph": "●",
+                                        "bulletStyle": {}
+                                    }
+                                }
+                            },
+                            {
+                                "endIndex": 1,
+                                "textRun": {
+                                    "content": "\n",
+                                    "style": {
+                                        "backgroundColor": {},
+                                        "foregroundColor": {
+                                            "opaqueColor": {
+                                                "rgbColor": {}
+                                            }
+                                        },
+                                        "bold": false,
+                                        "italic": false,
+                                        "fontFamily": "Arial",
+                                        "fontSize": {
+                                            "magnitude": 11,
+                                            "unit": "PT"
+                                        },
+                                        "baselineOffset": "NONE",
+                                        "smallCaps": false,
+                                        "strikethrough": false,
+                                        "underline": false,
+                                        "weightedFontFamily": {
+                                            "fontFamily": "Arial",
+                                            "weight": 400
+                                        }
+                                    }
+                                }
+                            },
+                            {
+                                "startIndex": 1,
+                                "endIndex": 2,
+                                "paragraphMarker": {
+                                    "style": {
+                                        "lineSpacing": 100,
+                                        "alignment": "START",
+                                        "indentStart": {
+                                            "unit": "PT"
+                                        },
+                                        "indentEnd": {
+                                            "unit": "PT"
+                                        },
+                                        "spaceAbove": {
+                                            "unit": "PT"
+                                        },
+                                        "spaceBelow": {
+                                            "unit": "PT"
+                                        },
+                                        "indentFirstLine": {
+                                            "unit": "PT"
+                                        },
+                                        "direction": "LEFT_TO_RIGHT",
+                                        "spacingMode": "COLLAPSE_LISTS"
+                                    },
+                                    "bullet": {
+                                        "listId": "bodyPlaceholderListEntity",
+                                        "nestingLevel": 1,
+                                        "glyph": "○",
+                                        "bulletStyle": {}
+                                    }
+                                }
+                            },
+                            {
+                                "startIndex": 1,
+                                "endIndex": 2,
+                                "textRun": {
+                                    "content": "\n",
+                                    "style": {
+                                        "backgroundColor": {},
+                                        "foregroundColor": {
+                                            "opaqueColor": {
+                                                "rgbColor": {}
+                                            }
+                                        },
+                                        "bold": false,
+                                        "italic": false,
+                                        "fontFamily": "Arial",
+                                        "fontSize": {
+                                            "magnitude": 11,
+                                            "unit": "PT"
+                                        },
+                                        "baselineOffset": "NONE",
+                                        "smallCaps": false,
+                                        "strikethrough": false,
+                                        "underline": false,
+                                        "weightedFontFamily": {
+                                            "fontFamily": "Arial",
+                                            "weight": 400
+                                        }
+                                    }
+                                }
+                            },
+                            {
+                                "startIndex": 2,
+                                "endIndex": 3,
+                                "paragraphMarker": {
+                                    "style": {
+                                        "lineSpacing": 100,
+                                        "alignment": "START",
+                                        "indentStart": {
+                                            "unit": "PT"
+                                        },
+                                        "indentEnd": {
+                                            "unit": "PT"
+                                        },
+                                        "spaceAbove": {
+                                            "unit": "PT"
+                                        },
+                                        "spaceBelow": {
+                                            "unit": "PT"
+                                        },
+                                        "indentFirstLine": {
+                                            "unit": "PT"
+                                        },
+                                        "direction": "LEFT_TO_RIGHT",
+                                        "spacingMode": "COLLAPSE_LISTS"
+                                    },
+                                    "bullet": {
+                                        "listId": "bodyPlaceholderListEntity",
+                                        "nestingLevel": 2,
+                                        "glyph": "■",
+                                        "bulletStyle": {}
+                                    }
+                                }
+                            },
+                            {
+                                "startIndex": 2,
+                                "endIndex": 3,
+                                "textRun": {
+                                    "content": "\n",
+                                    "style": {
+                                        "backgroundColor": {},
+                                        "foregroundColor": {
+                                            "opaqueColor": {
+                                                "rgbColor": {}
+                                            }
+                                        },
+                                        "bold": false,
+                                        "italic": false,
+                                        "fontFamily": "Arial",
+                                        "fontSize": {
+                                            "magnitude": 11,
+                                            "unit": "PT"
+                                        },
+                                        "baselineOffset": "NONE",
+                                        "smallCaps": false,
+                                        "strikethrough": false,
+                                        "underline": false,
+                                        "weightedFontFamily": {
+                                            "fontFamily": "Arial",
+                                            "weight": 400
+                                        }
+                                    }
+                                }
+                            },
+                            {
+                                "startIndex": 3,
+                                "endIndex": 4,
+                                "paragraphMarker": {
+                                    "style": {
+                                        "lineSpacing": 100,
+                                        "alignment": "START",
+                                        "indentStart": {
+                                            "unit": "PT"
+                                        },
+                                        "indentEnd": {
+                                            "unit": "PT"
+                                        },
+                                        "spaceAbove": {
+                                            "unit": "PT"
+                                        },
+                                        "spaceBelow": {
+                                            "unit": "PT"
+                                        },
+                                        "indentFirstLine": {
+                                            "unit": "PT"
+                                        },
+                                        "direction": "LEFT_TO_RIGHT",
+                                        "spacingMode": "COLLAPSE_LISTS"
+                                    },
+                                    "bullet": {
+                                        "listId": "bodyPlaceholderListEntity",
+                                        "nestingLevel": 3,
+                                        "glyph": "●",
+                                        "bulletStyle": {}
+                                    }
+                                }
+                            },
+                            {
+                                "startIndex": 3,
+                                "endIndex": 4,
+                                "textRun": {
+                                    "content": "\n",
+                                    "style": {
+                                        "backgroundColor": {},
+                                        "foregroundColor": {
+                                            "opaqueColor": {
+                                                "rgbColor": {}
+                                            }
+                                        },
+                                        "bold": false,
+                                        "italic": false,
+                                        "fontFamily": "Arial",
+                                        "fontSize": {
+                                            "magnitude": 11,
+                                            "unit": "PT"
+                                        },
+                                        "baselineOffset": "NONE",
+                                        "smallCaps": false,
+                                        "strikethrough": false,
+                                        "underline": false,
+                                        "weightedFontFamily": {
+                                            "fontFamily": "Arial",
+                                            "weight": 400
+                                        }
+                                    }
+                                }
+                            },
+                            {
+                                "startIndex": 4,
+                                "endIndex": 5,
+                                "paragraphMarker": {
+                                    "style": {
+                                        "lineSpacing": 100,
+                                        "alignment": "START",
+                                        "indentStart": {
+                                            "unit": "PT"
+                                        },
+                                        "indentEnd": {
+                                            "unit": "PT"
+                                        },
+                                        "spaceAbove": {
+                                            "unit": "PT"
+                                        },
+                                        "spaceBelow": {
+                                            "unit": "PT"
+                                        },
+                                        "indentFirstLine": {
+                                            "unit": "PT"
+                                        },
+                                        "direction": "LEFT_TO_RIGHT",
+                                        "spacingMode": "COLLAPSE_LISTS"
+                                    },
+                                    "bullet": {
+                                        "listId": "bodyPlaceholderListEntity",
+                                        "nestingLevel": 4,
+                                        "glyph": "○",
+                                        "bulletStyle": {}
+                                    }
+                                }
+                            },
+                            {
+                                "startIndex": 4,
+                                "endIndex": 5,
+                                "textRun": {
+                                    "content": "\n",
+                                    "style": {
+                                        "backgroundColor": {},
+                                        "foregroundColor": {
+                                            "opaqueColor": {
+                                                "rgbColor": {}
+                                            }
+                                        },
+                                        "bold": false,
+                                        "italic": false,
+                                        "fontFamily": "Arial",
+                                        "fontSize": {
+                                            "magnitude": 11,
+                                            "unit": "PT"
+                                        },
+                                        "baselineOffset": "NONE",
+                                        "smallCaps": false,
+                                        "strikethrough": false,
+                                        "underline": false,
+                                        "weightedFontFamily": {
+                                            "fontFamily": "Arial",
+                                            "weight": 400
+                                        }
+                                    }
+                                }
+                            },
+                            {
+                                "startIndex": 5,
+                                "endIndex": 6,
+                                "paragraphMarker": {
+                                    "style": {
+                                        "lineSpacing": 100,
+                                        "alignment": "START",
+                                        "indentStart": {
+                                            "unit": "PT"
+                                        },
+                                        "indentEnd": {
+                                            "unit": "PT"
+                                        },
+                                        "spaceAbove": {
+                                            "unit": "PT"
+                                        },
+                                        "spaceBelow": {
+                                            "unit": "PT"
+                                        },
+                                        "indentFirstLine": {
+                                            "unit": "PT"
+                                        },
+                                        "direction": "LEFT_TO_RIGHT",
+                                        "spacingMode": "COLLAPSE_LISTS"
+                                    },
+                                    "bullet": {
+                                        "listId": "bodyPlaceholderListEntity",
+                                        "nestingLevel": 5,
+                                        "glyph": "■",
+                                        "bulletStyle": {}
+                                    }
+                                }
+                            },
+                            {
+                                "startIndex": 5,
+                                "endIndex": 6,
+                                "textRun": {
+                                    "content": "\n",
+                                    "style": {
+                                        "backgroundColor": {},
+                                        "foregroundColor": {
+                                            "opaqueColor": {
+                                                "rgbColor": {}
+                                            }
+                                        },
+                                        "bold": false,
+                                        "italic": false,
+                                        "fontFamily": "Arial",
+                                        "fontSize": {
+                                            "magnitude": 11,
+                                            "unit": "PT"
+                                        },
+                                        "baselineOffset": "NONE",
+                                        "smallCaps": false,
+                                        "strikethrough": false,
+                                        "underline": false,
+                                        "weightedFontFamily": {
+                                            "fontFamily": "Arial",
+                                            "weight": 400
+                                        }
+                                    }
+                                }
+                            },
+                            {
+                                "startIndex": 6,
+                                "endIndex": 7,
+                                "paragraphMarker": {
+                                    "style": {
+                                        "lineSpacing": 100,
+                                        "alignment": "START",
+                                        "indentStart": {
+                                            "unit": "PT"
+                                        },
+                                        "indentEnd": {
+                                            "unit": "PT"
+                                        },
+                                        "spaceAbove": {
+                                            "unit": "PT"
+                                        },
+                                        "spaceBelow": {
+                                            "unit": "PT"
+                                        },
+                                        "indentFirstLine": {
+                                            "unit": "PT"
+                                        },
+                                        "direction": "LEFT_TO_RIGHT",
+                                        "spacingMode": "COLLAPSE_LISTS"
+                                    },
+                                    "bullet": {
+                                        "listId": "bodyPlaceholderListEntity",
+                                        "nestingLevel": 6,
+                                        "glyph": "●",
+                                        "bulletStyle": {}
+                                    }
+                                }
+                            },
+                            {
+                                "startIndex": 6,
+                                "endIndex": 7,
+                                "textRun": {
+                                    "content": "\n",
+                                    "style": {
+                                        "backgroundColor": {},
+                                        "foregroundColor": {
+                                            "opaqueColor": {
+                                                "rgbColor": {}
+                                            }
+                                        },
+                                        "bold": false,
+                                        "italic": false,
+                                        "fontFamily": "Arial",
+                                        "fontSize": {
+                                            "magnitude": 11,
+                                            "unit": "PT"
+                                        },
+                                        "baselineOffset": "NONE",
+                                        "smallCaps": false,
+                                        "strikethrough": false,
+                                        "underline": false,
+                                        "weightedFontFamily": {
+                                            "fontFamily": "Arial",
+                                            "weight": 400
+                                        }
+                                    }
+                                }
+                            },
+                            {
+                                "startIndex": 7,
+                                "endIndex": 8,
+                                "paragraphMarker": {
+                                    "style": {
+                                        "lineSpacing": 100,
+                                        "alignment": "START",
+                                        "indentStart": {
+                                            "unit": "PT"
+                                        },
+                                        "indentEnd": {
+                                            "unit": "PT"
+                                        },
+                                        "spaceAbove": {
+                                            "unit": "PT"
+                                        },
+                                        "spaceBelow": {
+                                            "unit": "PT"
+                                        },
+                                        "indentFirstLine": {
+                                            "unit": "PT"
+                                        },
+                                        "direction": "LEFT_TO_RIGHT",
+                                        "spacingMode": "COLLAPSE_LISTS"
+                                    },
+                                    "bullet": {
+                                        "listId": "bodyPlaceholderListEntity",
+                                        "nestingLevel": 7,
+                                        "glyph": "○",
+                                        "bulletStyle": {}
+                                    }
+                                }
+                            },
+                            {
+                                "startIndex": 7,
+                                "endIndex": 8,
+                                "textRun": {
+                                    "content": "\n",
+                                    "style": {
+                                        "backgroundColor": {},
+                                        "foregroundColor": {
+                                            "opaqueColor": {
+                                                "rgbColor": {}
+                                            }
+                                        },
+                                        "bold": false,
+                                        "italic": false,
+                                        "fontFamily": "Arial",
+                                        "fontSize": {
+                                            "magnitude": 11,
+                                            "unit": "PT"
+                                        },
+                                        "baselineOffset": "NONE",
+                                        "smallCaps": false,
+                                        "strikethrough": false,
+                                        "underline": false,
+                                        "weightedFontFamily": {
+                                            "fontFamily": "Arial",
+                                            "weight": 400
+                                        }
+                                    }
+                                }
+                            },
+                            {
+                                "startIndex": 8,
+                                "endIndex": 9,
+                                "paragraphMarker": {
+                                    "style": {
+                                        "lineSpacing": 100,
+                                        "alignment": "START",
+                                        "indentStart": {
+                                            "unit": "PT"
+                                        },
+                                        "indentEnd": {
+                                            "unit": "PT"
+                                        },
+                                        "spaceAbove": {
+                                            "unit": "PT"
+                                        },
+                                        "spaceBelow": {
+                                            "unit": "PT"
+                                        },
+                                        "indentFirstLine": {
+                                            "unit": "PT"
+                                        },
+                                        "direction": "LEFT_TO_RIGHT",
+                                        "spacingMode": "COLLAPSE_LISTS"
+                                    },
+                                    "bullet": {
+                                        "listId": "bodyPlaceholderListEntity",
+                                        "nestingLevel": 8,
+                                        "glyph": "■",
+                                        "bulletStyle": {}
+                                    }
+                                }
+                            },
+                            {
+                                "startIndex": 8,
+                                "endIndex": 9,
+                                "textRun": {
+                                    "content": "\n",
+                                    "style": {
+                                        "backgroundColor": {},
+                                        "foregroundColor": {
+                                            "opaqueColor": {
+                                                "rgbColor": {}
+                                            }
+                                        },
+                                        "bold": false,
+                                        "italic": false,
+                                        "fontFamily": "Arial",
+                                        "fontSize": {
+                                            "magnitude": 11,
+                                            "unit": "PT"
+                                        },
+                                        "baselineOffset": "NONE",
+                                        "smallCaps": false,
+                                        "strikethrough": false,
+                                        "underline": false,
+                                        "weightedFontFamily": {
+                                            "fontFamily": "Arial",
+                                            "weight": 400
+                                        }
+                                    }
+                                }
+                            }
+                        ],
+                        "lists": {
+                            "bodyPlaceholderListEntity": {
+                                "listId": "bodyPlaceholderListEntity",
+                                "nestingLevel": {
+                                    "0": {
+                                        "bulletStyle": {
+                                            "backgroundColor": {},
+                                            "foregroundColor": {
+                                                "opaqueColor": {
+                                                    "rgbColor": {}
+                                                }
+                                            },
+                                            "bold": false,
+                                            "italic": false,
+                                            "fontFamily": "Arial",
+                                            "fontSize": {
+                                                "magnitude": 11,
+                                                "unit": "PT"
+                                            },
+                                            "baselineOffset": "NONE",
+                                            "smallCaps": false,
+                                            "strikethrough": false,
+                                            "underline": false,
+                                            "weightedFontFamily": {
+                                                "fontFamily": "Arial",
+                                                "weight": 400
+                                            }
+                                        }
+                                    },
+                                    "1": {
+                                        "bulletStyle": {
+                                            "backgroundColor": {},
+                                            "foregroundColor": {
+                                                "opaqueColor": {
+                                                    "rgbColor": {}
+                                                }
+                                            },
+                                            "bold": false,
+                                            "italic": false,
+                                            "fontFamily": "Arial",
+                                            "fontSize": {
+                                                "magnitude": 11,
+                                                "unit": "PT"
+                                            },
+                                            "baselineOffset": "NONE",
+                                            "smallCaps": false,
+                                            "strikethrough": false,
+                                            "underline": false,
+                                            "weightedFontFamily": {
+                                                "fontFamily": "Arial",
+                                                "weight": 400
+                                            }
+                                        }
+                                    },
+                                    "2": {
+                                        "bulletStyle": {
+                                            "backgroundColor": {},
+                                            "foregroundColor": {
+                                                "opaqueColor": {
+                                                    "rgbColor": {}
+                                                }
+                                            },
+                                            "bold": false,
+                                            "italic": false,
+                                            "fontFamily": "Arial",
+                                            "fontSize": {
+                                                "magnitude": 11,
+                                                "unit": "PT"
+                                            },
+                                            "baselineOffset": "NONE",
+                                            "smallCaps": false,
+                                            "strikethrough": false,
+                                            "underline": false,
+                                            "weightedFontFamily": {
+                                                "fontFamily": "Arial",
+                                                "weight": 400
+                                            }
+                                        }
+                                    },
+                                    "3": {
+                                        "bulletStyle": {
+                                            "backgroundColor": {},
+                                            "foregroundColor": {
+                                                "opaqueColor": {
+                                                    "rgbColor": {}
+                                                }
+                                            },
+                                            "bold": false,
+                                            "italic": false,
+                                            "fontFamily": "Arial",
+                                            "fontSize": {
+                                                "magnitude": 11,
+                                                "unit": "PT"
+                                            },
+                                            "baselineOffset": "NONE",
+                                            "smallCaps": false,
+                                            "strikethrough": false,
+                                            "underline": false,
+                                            "weightedFontFamily": {
+                                                "fontFamily": "Arial",
+                                                "weight": 400
+                                            }
+                                        }
+                                    },
+                                    "4": {
+                                        "bulletStyle": {
+                                            "backgroundColor": {},
+                                            "foregroundColor": {
+                                                "opaqueColor": {
+                                                    "rgbColor": {}
+                                                }
+                                            },
+                                            "bold": false,
+                                            "italic": false,
+                                            "fontFamily": "Arial",
+                                            "fontSize": {
+                                                "magnitude": 11,
+                                                "unit": "PT"
+                                            },
+                                            "baselineOffset": "NONE",
+                                            "smallCaps": false,
+                                            "strikethrough": false,
+                                            "underline": false,
+                                            "weightedFontFamily": {
+                                                "fontFamily": "Arial",
+                                                "weight": 400
+                                            }
+                                        }
+                                    },
+                                    "5": {
+                                        "bulletStyle": {
+                                            "backgroundColor": {},
+                                            "foregroundColor": {
+                                                "opaqueColor": {
+                                                    "rgbColor": {}
+                                                }
+                                            },
+                                            "bold": false,
+                                            "italic": false,
+                                            "fontFamily": "Arial",
+                                            "fontSize": {
+                                                "magnitude": 11,
+                                                "unit": "PT"
+                                            },
+                                            "baselineOffset": "NONE",
+                                            "smallCaps": false,
+                                            "strikethrough": false,
+                                            "underline": false,
+                                            "weightedFontFamily": {
+                                                "fontFamily": "Arial",
+                                                "weight": 400
+                                            }
+                                        }
+                                    },
+                                    "6": {
+                                        "bulletStyle": {
+                                            "backgroundColor": {},
+                                            "foregroundColor": {
+                                                "opaqueColor": {
+                                                    "rgbColor": {}
+                                                }
+                                            },
+                                            "bold": false,
+                                            "italic": false,
+                                            "fontFamily": "Arial",
+                                            "fontSize": {
+                                                "magnitude": 11,
+                                                "unit": "PT"
+                                            },
+                                            "baselineOffset": "NONE",
+                                            "smallCaps": false,
+                                            "strikethrough": false,
+                                            "underline": false,
+                                            "weightedFontFamily": {
+                                                "fontFamily": "Arial",
+                                                "weight": 400
+                                            }
+                                        }
+                                    },
+                                    "7": {
+                                        "bulletStyle": {
+                                            "backgroundColor": {},
+                                            "foregroundColor": {
+                                                "opaqueColor": {
+                                                    "rgbColor": {}
+                                                }
+                                            },
+                                            "bold": false,
+                                            "italic": false,
+                                            "fontFamily": "Arial",
+                                            "fontSize": {
+                                                "magnitude": 11,
+                                                "unit": "PT"
+                                            },
+                                            "baselineOffset": "NONE",
+                                            "smallCaps": false,
+                                            "strikethrough": false,
+                                            "underline": false,
+                                            "weightedFontFamily": {
+                                                "fontFamily": "Arial",
+                                                "weight": 400
+                                            }
+                                        }
+                                    },
+                                    "8": {
+                                        "bulletStyle": {
+                                            "backgroundColor": {},
+                                            "foregroundColor": {
+                                                "opaqueColor": {
+                                                    "rgbColor": {}
+                                                }
+                                            },
+                                            "bold": false,
+                                            "italic": false,
+                                            "fontFamily": "Arial",
+                                            "fontSize": {
+                                                "magnitude": 11,
+                                                "unit": "PT"
+                                            },
+                                            "baselineOffset": "NONE",
+                                            "smallCaps": false,
+                                            "strikethrough": false,
+                                            "underline": false,
+                                            "weightedFontFamily": {
+                                                "fontFamily": "Arial",
+                                                "weight": 400
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    },
+                    "shapeProperties": {
+                        "shapeBackgroundFill": {
+                            "propertyState": "NOT_RENDERED",
+                            "solidFill": {
+                                "color": {
+                                    "rgbColor": {
+                                        "red": 1,
+                                        "green": 1,
+                                        "blue": 1
+                                    }
+                                },
+                                "alpha": 1
+                            }
+                        },
+                        "outline": {
+                            "outlineFill": {
+                                "solidFill": {
+                                    "color": {
+                                        "rgbColor": {}
+                                    },
+                                    "alpha": 1
+                                }
+                            },
+                            "weight": {
+                                "magnitude": 9525,
+                                "unit": "EMU"
+                            },
+                            "dashStyle": "SOLID",
+                            "propertyState": "NOT_RENDERED"
+                        },
+                        "shadow": {
+                            "type": "OUTER",
+                            "transform": {
+                                "scaleX": 1,
+                                "scaleY": 1,
+                                "unit": "EMU"
+                            },
+                            "alignment": "BOTTOM_LEFT",
+                            "blurRadius": {
+                                "unit": "EMU"
+                            },
+                            "color": {
+                                "rgbColor": {}
+                            },
+                            "alpha": 1,
+                            "rotateWithShape": false,
+                            "propertyState": "NOT_RENDERED"
+                        },
+                        "contentAlignment": "TOP"
+                    },
+                    "placeholder": {
+                        "type": "BODY",
+                        "index": 1
+                    }
+                }
+            }
+        ],
+        "pageProperties": {
+            "pageBackgroundFill": {
+                "propertyState": "NOT_RENDERED",
+                "solidFill": {
+                    "color": {
+                        "rgbColor": {
+                            "red": 1,
+                            "green": 1,
+                            "blue": 1
+                        }
+                    },
+                    "alpha": 1
+                }
+            },
+            "colorScheme": {
+                "colors": [
+                    {
+                        "type": "DARK1",
+                        "color": {}
+                    },
+                    {
+                        "type": "LIGHT1",
+                        "color": {
+                            "red": 1,
+                            "green": 1,
+                            "blue": 1
+                        }
+                    },
+                    {
+                        "type": "DARK2",
+                        "color": {
+                            "red": 0.08235294,
+                            "green": 0.5058824,
+                            "blue": 0.34509805
+                        }
+                    },
+                    {
+                        "type": "LIGHT2",
+                        "color": {
+                            "red": 0.9529412,
+                            "green": 0.9529412,
+                            "blue": 0.9529412
+                        }
+                    },
+                    {
+                        "type": "ACCENT1",
+                        "color": {
+                            "red": 0.019607844,
+                            "green": 0.5529412,
+                            "blue": 0.78039217
+                        }
+                    },
+                    {
+                        "type": "ACCENT2",
+                        "color": {
+                            "red": 0.3137255,
+                            "green": 0.7058824,
+                            "blue": 0.19607843
+                        }
+                    },
+                    {
+                        "type": "ACCENT3",
+                        "color": {
+                            "red": 0.92941177,
+                            "green": 0.3372549,
+                            "blue": 0.105882354
+                        }
+                    },
+                    {
+                        "type": "ACCENT4",
+                        "color": {
+                            "red": 0.92941177,
+                            "green": 0.9372549
+                        }
+                    },
+                    {
+                        "type": "ACCENT5",
+                        "color": {
+                            "red": 0.14117648,
+                            "green": 0.79607844,
+                            "blue": 0.8980392
+                        }
+                    },
+                    {
+                        "type": "ACCENT6",
+                        "color": {
+                            "red": 0.39215687,
+                            "green": 0.8980392,
+                            "blue": 0.44705883
+                        }
+                    },
+                    {
+                        "type": "HYPERLINK",
+                        "color": {
+                            "red": 0.13333334,
+                            "blue": 0.8
+                        }
+                    },
+                    {
+                        "type": "FOLLOWED_HYPERLINK",
+                        "color": {
+                            "red": 0.33333334,
+                            "green": 0.101960786,
+                            "blue": 0.54509807
+                        }
+                    },
+                    {
+                        "type": "TEXT1",
+                        "color": {}
+                    },
+                    {
+                        "type": "BACKGROUND1",
+                        "color": {
+                            "red": 1,
+                            "green": 1,
+                            "blue": 1
+                        }
+                    },
+                    {
+                        "type": "TEXT2",
+                        "color": {
+                            "red": 0.9529412,
+                            "green": 0.9529412,
+                            "blue": 0.9529412
+                        }
+                    },
+                    {
+                        "type": "BACKGROUND2",
+                        "color": {
+                            "red": 0.08235294,
+                            "green": 0.5058824,
+                            "blue": 0.34509805
+                        }
+                    }
+                ]
+            }
+        }
+    }
+}
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From 6927418364d58c2aaba1b25f1d11537dd510535c Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 22 Aug 2019 08:29:43 -0400
Subject: fixed autoheight for text

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

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

diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 3f3360eff..849f942b6 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -735,12 +735,10 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
 
     @action
     tryUpdateHeight() {
-        if (this.props.Document.autoHeight && this._ref.current!.scrollHeight !== 0) {
-            let xf = this._ref.current!.getBoundingClientRect();
-            let scrBounds = this.props.ScreenToLocalTransform().transformBounds(0, 0, xf.width, this._ref.current!.textContent === "" ? 35 : this._ref.current!.scrollHeight);
+        let sh = this._ref.current ? this._ref.current.scrollHeight : 0;
+        if (this.props.Document.autoHeight && sh !== 0) {
             let nh = this.props.Document.isTemplate ? 0 : NumCast(this.dataDoc.nativeHeight, 0);
             let dh = NumCast(this.props.Document.height, 0);
-            let sh = scrBounds.height;
             const ChromeHeight = MainOverlayTextBox.Instance.ChromeHeight;
             this.props.Document.height = Math.max(10, (nh ? dh / nh * sh : sh) + (ChromeHeight ? ChromeHeight() : 0));
             this.dataDoc.nativeHeight = nh ? sh : undefined;
-- 
cgit v1.2.3-70-g09d2


From 8cffe031fe364e19897cfb638cffa9e4056c4343 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 22 Aug 2019 11:07:11 -0400
Subject: fixed textheight with titles. fixed marquee with floating docs. other
 tweaks.

---
 src/client/util/DocumentManager.ts                   |  4 +++-
 src/client/views/MainOverlayTextBox.tsx              |  1 +
 .../views/collections/CollectionStackingView.tsx     | 13 ++++++++-----
 .../views/collections/CollectionViewChromes.tsx      | 14 +++++++++-----
 .../collections/collectionFreeForm/MarqueeView.tsx   | 20 ++++++++++++++++++--
 src/client/views/nodes/DocumentView.tsx              | 10 ++++++++--
 src/client/views/nodes/FormattedTextBox.tsx          |  6 ++++--
 7 files changed, 51 insertions(+), 17 deletions(-)

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

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 7f526b247..124faf266 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -9,6 +9,7 @@ import { CollectionView } from '../views/collections/CollectionView';
 import { DocumentView } from '../views/nodes/DocumentView';
 import { LinkManager } from './LinkManager';
 import { undoBatch, UndoManager } from './UndoManager';
+import { Scripting } from './Scripting';
 
 
 export class DocumentManager {
@@ -202,4 +203,5 @@ export class DocumentManager {
             return 1;
         }
     }
-}
\ No newline at end of file
+}
+Scripting.addGlobal(function focus(doc: any) { DocumentManager.Instance.getDocumentViews(Doc.GetProto(doc)).map(view => view.props.focus(doc, true)) })
\ No newline at end of file
diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx
index 9fe435bc5..0839e1114 100644
--- a/src/client/views/MainOverlayTextBox.tsx
+++ b/src/client/views/MainOverlayTextBox.tsx
@@ -144,6 +144,7 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
                                 Document={FormattedTextBox.InputBoxOverlay.props.Document}
                                 DataDoc={FormattedTextBox.InputBoxOverlay.props.DataDoc}
                                 onClick={undefined}
+                                ChromeHeight={this.ChromeHeight}
                                 isSelected={returnTrue} select={emptyFunction} renderDepth={0} selectOnLoad={true}
                                 ContainingCollectionView={undefined} whenActiveChanged={emptyFunction} active={returnTrue} ContentScaling={returnOne}
                                 ScreenToLocalTransform={this._textXf} PanelWidth={returnZero} PanelHeight={returnZero} focus={emptyFunction}
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index be6ee1756..4ab656744 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -287,15 +287,18 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     masonryChildren(docs: Doc[]) {
         this._docXfs.length = 0;
         return docs.map((d, i) => {
+            const pair = Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, d);
+            if (!pair.layout || pair.data instanceof Promise) {
+                return (null);
+            }
             let dref = React.createRef<HTMLDivElement>();
-            let layoutDoc = Doc.expandTemplateLayout(d, this.props.DataDoc);
             let width = () => (d.nativeWidth && !d.ignoreAspect && !this.props.Document.fillColumn ? Math.min(d[WidthSym](), this.columnWidth) : this.columnWidth);/// (uniqueHeadings.length + 1);
-            let height = () => this.getDocHeight(layoutDoc);
-            let dxf = () => this.getDocTransform(layoutDoc!, dref.current!);
+            let height = () => this.getDocHeight(pair.layout);
+            let dxf = () => this.getDocTransform(pair.layout!, dref.current!);
             let rowSpan = Math.ceil((height() + this.gridGap) / this.gridGap);
             this._docXfs.push({ dxf: dxf, width: width, height: height });
-            return !layoutDoc ? (null) : <div className="collectionStackingView-masonryDoc" key={d[Id]} ref={dref} style={{ gridRowEnd: `span ${rowSpan}` }} >
-                {this.getDisplayDoc(layoutDoc, d, dxf, width)}
+            return !pair.layout ? (null) : <div className="collectionStackingView-masonryDoc" key={d[Id]} ref={dref} style={{ gridRowEnd: `span ${rowSpan}` }} >
+                {this.getDisplayDoc(pair.layout, pair.data, dxf, width)}
             </div>;
         });
     }
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index 25b152d4e..74e57611d 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -297,11 +297,15 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
 
     datePickerRef = (node: HTMLInputElement) => {
         if (node) {
-            this._picker = datepicker("#" + node.id, {
-                disabler: (date: Date) => date > new Date(),
-                onSelect: (instance: any, date: Date) => runInAction(() => this._dateValue = date),
-                dateSelected: new Date()
-            });
+            try {
+                this._picker = datepicker("#" + node.id, {
+                    disabler: (date: Date) => date > new Date(),
+                    onSelect: (instance: any, date: Date) => runInAction(() => this._dateValue = date),
+                    dateSelected: new Date()
+                });
+            } catch (e) {
+                console.log("date picker exception:" + e);
+            }
         }
     }
     render() {
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index aad26efa0..221237365 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -373,7 +373,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
     marqueeSelect(selectBackgrounds: boolean = true) {
         let selRect = this.Bounds;
         let selection: Doc[] = [];
-        this.props.activeDocuments().filter(doc => !doc.isBackground).map(doc => {
+        this.props.activeDocuments().filter(doc => !doc.isBackground && doc.z === undefined).map(doc => {
             var x = NumCast(doc.x);
             var y = NumCast(doc.y);
             var w = NumCast(doc.width);
@@ -383,7 +383,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
             }
         });
         if (!selection.length && selectBackgrounds) {
-            this.props.activeDocuments().map(doc => {
+            this.props.activeDocuments().filter(doc => doc.z === undefined).map(doc => {
                 var x = NumCast(doc.x);
                 var y = NumCast(doc.y);
                 var w = NumCast(doc.width);
@@ -393,6 +393,22 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                 }
             });
         }
+        if (!selection.length) {
+            let left = this._downX < this._lastX ? this._downX : this._lastX;
+            let top = this._downY < this._lastY ? this._downY : this._lastY;
+            let topLeft = this.props.getContainerTransform().transformPoint(left, top);
+            let size = this.props.getContainerTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY);
+            let otherBounds = { left: topLeft[0], top: topLeft[1], width: Math.abs(size[0]), height: Math.abs(size[1]) };
+            this.props.activeDocuments().filter(doc => doc.z !== undefined).map(doc => {
+                var x = NumCast(doc.x);
+                var y = NumCast(doc.y);
+                var w = NumCast(doc.width);
+                var h = NumCast(doc.height);
+                if (this.intersectRect({ left: x, top: y, width: w, height: h }, otherBounds)) {
+                    selection.push(doc);
+                }
+            });
+        }
         return selection;
     }
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 8dadf2bef..0c577af86 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -715,7 +715,13 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     chromeHeight = () => {
         let showOverlays = this.props.showOverlays ? this.props.showOverlays(this.layoutDoc) : undefined;
         let showTitle = showOverlays && "title" in showOverlays ? showOverlays.title : StrCast(this.layoutDoc.showTitle);
-        return showTitle ? 25 : 0;
+        let templates = Cast(this.layoutDoc.templates, listSpec("string"));
+        if (!showOverlays && templates instanceof List) {
+            templates.map(str => {
+                if (!showTitle && str.indexOf("{props.Document.title}") !== -1) showTitle = "title";
+            });
+        }
+        return (showTitle ? 25 : 0) + 1;// bcz: why 8??
     }
 
     get layoutDoc() {
@@ -769,7 +775,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 {!showTitle && !showCaption ? this.contents :
                     <div style={{ position: "absolute", display: "inline-block", width: "100%", height: "100%", pointerEvents: "none" }}>
 
-                        <div style={{ width: "100%", height: showTextTitle ? "calc(100% - 33px)" : "100%", display: "inline-block", position: "absolute", top: showTextTitle ? "29px" : undefined }}>
+                        <div style={{ width: "100%", height: showTextTitle ? "calc(100% - 29px)" : "100%", display: "inline-block", position: "absolute", top: showTextTitle ? "29px" : undefined }}>
                             {this.contents}
                         </div>
                         {!showTitle ? (null) :
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 849f942b6..94d9f2f66 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -374,6 +374,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 this.unhighlightSearchTerms();
             }
         }, { fireImmediately: true });
+        this.tryUpdateHeight();
     }
 
     pushToGoogleDoc = async () => {
@@ -735,11 +736,12 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
 
     @action
     tryUpdateHeight() {
+        const ChromeHeight = this.props.ChromeHeight;
         let sh = this._ref.current ? this._ref.current.scrollHeight : 0;
+        console.log(this.props.Document.title + " " + sh + " " + (ChromeHeight && ChromeHeight()));
         if (this.props.Document.autoHeight && sh !== 0) {
             let nh = this.props.Document.isTemplate ? 0 : NumCast(this.dataDoc.nativeHeight, 0);
             let dh = NumCast(this.props.Document.height, 0);
-            const ChromeHeight = MainOverlayTextBox.Instance.ChromeHeight;
             this.props.Document.height = Math.max(10, (nh ? dh / nh * sh : sh) + (ChromeHeight ? ChromeHeight() : 0));
             this.dataDoc.nativeHeight = nh ? sh : undefined;
         }
@@ -775,7 +777,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             <div className={`formattedTextBox-cont-${style}`} ref={this._ref}
                 style={{
                     overflowY: this.props.Document.autoHeight ? "hidden" : "auto",
-                    height: this.props.height ? this.props.height : undefined,
+                    height: this.props.Document.autoHeight ? "max-content" : this.props.height ? this.props.height : undefined,
                     background: this.props.hideOnLeave ? "rgba(0,0,0 ,0.4)" : undefined,
                     opacity: this.props.hideOnLeave ? (this._entered || this.props.isSelected() || Doc.IsBrushed(this.props.Document) ? 1 : 0.1) : 1,
                     color: this.props.color ? this.props.color : this.props.hideOnLeave ? "white" : "inherit",
-- 
cgit v1.2.3-70-g09d2


From 7812945012c320743a4271787ad614b2011c74a3 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 22 Aug 2019 11:18:19 -0400
Subject: from last

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

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

diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 94d9f2f66..1cc3e821f 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -374,7 +374,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 this.unhighlightSearchTerms();
             }
         }, { fireImmediately: true });
-        this.tryUpdateHeight();
+        setTimeout(() => this.tryUpdateHeight(), 0);
     }
 
     pushToGoogleDoc = async () => {
-- 
cgit v1.2.3-70-g09d2


From 0ee435f6bd686c667a067fa750b4589cedfb0070 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 22 Aug 2019 11:18:41 -0400
Subject: again

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

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

diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 1cc3e821f..44a481953 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -738,7 +738,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     tryUpdateHeight() {
         const ChromeHeight = this.props.ChromeHeight;
         let sh = this._ref.current ? this._ref.current.scrollHeight : 0;
-        console.log(this.props.Document.title + " " + sh + " " + (ChromeHeight && ChromeHeight()));
         if (this.props.Document.autoHeight && sh !== 0) {
             let nh = this.props.Document.isTemplate ? 0 : NumCast(this.dataDoc.nativeHeight, 0);
             let dh = NumCast(this.props.Document.height, 0);
-- 
cgit v1.2.3-70-g09d2


From 65aec7b22e63de15e0d911971fa4e5a32d09d9b5 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Thu, 22 Aug 2019 12:00:47 -0400
Subject: cleanup

---
 src/client/util/DictationManager.ts         | 30 ++++++++++++++++++-----------
 src/client/views/GlobalKeyHandler.ts        |  2 +-
 src/client/views/MainView.tsx               |  1 -
 src/client/views/nodes/FormattedTextBox.tsx |  2 +-
 src/new_fields/RichTextField.ts             |  6 ++++++
 5 files changed, 27 insertions(+), 14 deletions(-)

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

diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts
index 781e5e465..fb3c15cea 100644
--- a/src/client/util/DictationManager.ts
+++ b/src/client/util/DictationManager.ts
@@ -64,6 +64,7 @@ export namespace DictationManager {
         export type ListeningUIStatus = { interim: boolean } | false;
 
         export interface ListeningOptions {
+            useOverlay: boolean;
             language: string;
             continuous: ContinuityArgs;
             delimiters: DelimiterArgs;
@@ -74,27 +75,34 @@ export namespace DictationManager {
 
         export const listen = async (options?: Partial<ListeningOptions>) => {
             let results: string | undefined;
-            // let main = MainView.Instance;
+            let main = MainView.Instance;
 
-            // main.dictationOverlayVisible = true;
-            // main.isListening = { interim: false };
+            let overlay = options !== undefined && options.useOverlay;
+            if (overlay) {
+                main.dictationOverlayVisible = true;
+                main.isListening = { interim: false };
+            }
 
             try {
                 results = await listenImpl(options);
                 if (results) {
                     Utils.CopyText(results);
-                    // main.isListening = false;
-                    // let execute = options && options.tryExecute;
-                    // main.dictatedPhrase = execute ? results.toLowerCase() : results;
-                    // main.dictationSuccess = execute ? await DictationManager.Commands.execute(results) : true;
+                    if (overlay) {
+                        main.isListening = false;
+                        let execute = options && options.tryExecute;
+                        main.dictatedPhrase = execute ? results.toLowerCase() : results;
+                        main.dictationSuccess = execute ? await DictationManager.Commands.execute(results) : true;
+                    }
                     options && options.tryExecute && await DictationManager.Commands.execute(results);
                 }
             } catch (e) {
-                // main.isListening = false;
-                // main.dictatedPhrase = results = `dictation error: ${"error" in e ? e.error : "unknown error"}`;
-                // main.dictationSuccess = false;
+                if (overlay) {
+                    main.isListening = false;
+                    main.dictatedPhrase = results = `dictation error: ${"error" in e ? e.error : "unknown error"}`;
+                    main.dictationSuccess = false;
+                }
             } finally {
-                // main.initiateDictationFade();
+                overlay && main.initiateDictationFade();
             }
 
             return results;
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 8a7295c65..d0464bd5f 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -105,7 +105,7 @@ export default class KeyManager {
 
         switch (keyname) {
             case " ":
-                DictationManager.Controls.listen({ tryExecute: true });
+                DictationManager.Controls.listen({ useOverlay: true, tryExecute: true });
                 stopPropagation = true;
                 preventDefault = true;
         }
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 0286a201b..f28844009 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -40,7 +40,6 @@ import { PreviewCursor } from './PreviewCursor';
 import { FilterBox } from './search/FilterBox';
 import PresModeMenu from './presentationview/PresentationModeMenu';
 import { PresBox } from './nodes/PresBox';
-import { GoogleApiClientUtils } from '../apis/google_docs/GoogleApiClientUtils';
 
 @observer
 export class MainView extends React.Component {
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index d9043c739..5492a457d 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -1,6 +1,6 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
 import { faEdit, faSmile, faTextHeight, faUpload } from '@fortawesome/free-solid-svg-icons';
-import { action, computed, IReactionDisposer, Lambda, observable, reaction, runInAction, autorun } from "mobx";
+import { action, computed, IReactionDisposer, Lambda, observable, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import { baseKeymap } from "prosemirror-commands";
 import { history } from "prosemirror-history";
diff --git a/src/new_fields/RichTextField.ts b/src/new_fields/RichTextField.ts
index cae5623e6..1b52e6f82 100644
--- a/src/new_fields/RichTextField.ts
+++ b/src/new_fields/RichTextField.ts
@@ -28,6 +28,12 @@ export class RichTextField extends ObjectField {
         return `new RichTextField("${this.Data}")`;
     }
 
+    public static Initialize = (initial: string) => {
+        !initial.length && (initial = " ");
+        let pos = initial.length + 1;
+        return `{"doc":{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"${initial}"}]}]},"selection":{"type":"text","anchor":${pos},"head":${pos}}}`;
+    }
+
     [ToPlainText]() {
         // Because we're working with plain text, just concatenate all paragraphs
         let content = JSON.parse(this.Data).doc.content;
-- 
cgit v1.2.3-70-g09d2


From f0f0cc36654183921076db5a341fe7cac2bfdd3c Mon Sep 17 00:00:00 2001
From: monikahedman <monika_hedman@brown.edu>
Date: Thu, 22 Aug 2019 12:08:12 -0400
Subject: cleaned up

---
 src/client/views/collections/collectionFreeForm/MarqueeView.tsx | 1 -
 src/client/views/nodes/FormattedTextBox.tsx                     | 9 +--------
 2 files changed, 1 insertion(+), 9 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index e70d526c5..27eafd769 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -203,7 +203,6 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
     onClick = (e: React.MouseEvent): void => {
         if (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD &&
             Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
-            //this is probably the wrong transform
             PreviewCursor.Show(e.clientX, e.clientY, this.onKeyPress, this.props.addLiveTextDocument, this.props.getTransform, this.props.addDocument);
             // let the DocumentView stopPropagation of this event when it selects this document
         } else {  // why do we get a click event when the cursor have moved a big distance?
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 0c0ab4d87..1db66d4a0 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -145,7 +145,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
 
 
     paste = (e: ClipboardEvent) => {
-        //this is throwing a ton of erros so i had to comment it out
+        //this is throwing a ton of errors so commented it out
         if (e.clipboardData && this._editorView) {
             // let pdfPasteText = `${Utils.GenerateDeterministicGuid("pdf paste")}`;
             // for (let i = 0; i < e.clipboardData.items.length; i++) {
@@ -173,13 +173,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         }
     }
 
-    public setText = (text: string) => {
-        const tx = this._editorView!.state.tr.insertText(text);
-        const state = this._editorView!.state;
-        this._editorView!.dispatch(tx);
-        return new RichTextField(JSON.stringify(state.toJSON()));
-    }
-
     dispatchTransaction = (tx: Transaction) => {
         if (this._editorView) {
             const state = this._editorView.state.apply(tx);
-- 
cgit v1.2.3-70-g09d2


From e680f97f9d50ce5759d4533bae38af341956ddce Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Fri, 23 Aug 2019 11:26:43 -0400
Subject: cleanup

---
 src/client/views/nodes/FormattedTextBox.tsx | 5 -----
 src/server/index.ts                         | 2 +-
 2 files changed, 1 insertion(+), 6 deletions(-)

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

diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 191a24664..0e04bacf7 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -34,16 +34,12 @@ import "./FormattedTextBox.scss";
 import React = require("react");
 import { GoogleApiClientUtils, Pulls, Pushes } from '../../apis/google_docs/GoogleApiClientUtils';
 import { DocumentDecorations } from '../DocumentDecorations';
-import { MainOverlayTextBox } from '../MainOverlayTextBox';
 import { DictationManager } from '../../util/DictationManager';
 import { ReplaceStep } from 'prosemirror-transform';
 
 library.add(faEdit);
 library.add(faSmile, faTextHeight, faUpload);
 
-// FormattedTextBox: Displays an editable plain text node that maps to a specified Key of a Document
-//
-
 export const Blank = `{"doc":{"type":"doc","content":[]},"selection":{"type":"text","anchor":0,"head":0}}`;
 
 export interface FormattedTextBoxProps {
@@ -146,7 +142,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
 
 
     paste = (e: ClipboardEvent) => {
-        //this is throwing a ton of errors so commented it out
         if (e.clipboardData && this._editorView) {
             // let pdfPasteText = `${Utils.GenerateDeterministicGuid("pdf paste")}`;
             // for (let i = 0; i < e.clipboardData.items.length; i++) {
diff --git a/src/server/index.ts b/src/server/index.ts
index eeadef239..34a0a19f1 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(1 as any);
+                let viewport = page.getViewport(1);
                 let canvasAndContext = factory.create(viewport.width, viewport.height);
                 let renderContext = {
                     canvasContext: canvasAndContext.context,
-- 
cgit v1.2.3-70-g09d2


From 538187907421057212eca1eadbe07c8b79705d1d Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Fri, 23 Aug 2019 16:47:49 -0400
Subject: fixed text scrolling issues.

---
 src/client/views/DocumentDecorations.tsx    | 2 +-
 src/client/views/MainOverlayTextBox.tsx     | 5 +++--
 src/client/views/nodes/FormattedTextBox.tsx | 2 +-
 3 files changed, 5 insertions(+), 4 deletions(-)

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

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index a28088032..2c5940467 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -695,7 +695,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         if (!canPush) return (null);
         let published = Doc.GetProto(this.targetDoc)[GoogleRef] !== undefined;
         if (!published) {
-            this.targetDoc.autoHeight = true;
+            // this.targetDoc.autoHeight = true;
         }
         let icon: IconProp = published ? (this.pushIcon as any) : cloud;
         return (
diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx
index 0839e1114..e95e5b777 100644
--- a/src/client/views/MainOverlayTextBox.tsx
+++ b/src/client/views/MainOverlayTextBox.tsx
@@ -59,7 +59,7 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
                         let sxf = Utils.GetScreenTransform(box ? box.CurrentDiv : undefined);
                         return new Transform(-sxf.translateX, -sxf.translateY, 1 / sxf.scale);
                     };
-                    this.setTextDoc(box.props.fieldKey, box.CurrentDiv, xf, BoolCast(box.props.Document.autoHeight, false) || box.props.height === "min-content");
+                    this.setTextDoc(box.props.fieldKey, box.CurrentDiv, xf, BoolCast(box.props.Document.autoHeight) || box.props.height === "min-content");
                 }
                 else {
                     this.TextDoc = undefined;
@@ -131,10 +131,11 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
         this.TextDoc; this.TextDataDoc;
         if (FormattedTextBox.InputBoxOverlay && this._textTargetDiv) {
             let wid = FormattedTextBox.InputBoxOverlay.props.Document.width; // need to force overlay to render when underlying text box is resized (eg, w/ DocDecorations)
+            let hgtx = FormattedTextBox.InputBoxOverlay.props.Document.height; // need to force overlay to render when underlying text box is resized (eg, w/ DocDecorations)
             let textRect = this._textTargetDiv.getBoundingClientRect();
             let s = this._textXf().Scale;
             let location = this._textBottom ? textRect.bottom : textRect.top;
-            let hgt = this._textAutoHeight || this._textBottom ? "auto" : this._textTargetDiv.clientHeight;
+            let hgt = (this._textBox && this._textBox.props.Document.autoHeight) || this._textBottom ? "auto" : this._textTargetDiv.clientHeight;
             return <div ref={this._setouterdiv} className="mainOverlayTextBox-unscaled_div" style={{ transform: `translate(${textRect.left}px, ${location}px)` }} >
                 <div className="mainOverlayTextBox-textInput" style={{ transform: `scale(${1 / s})`, width: "auto", height: "0px" }} >
                     <div className="mainOverlayTextBox-textInput" onPointerDown={this.textBoxDown} ref={this._textProxyDiv} onScroll={this.textScroll}
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 0e04bacf7..91596e825 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -796,7 +796,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     tryUpdateHeight() {
         const ChromeHeight = this.props.ChromeHeight;
         let sh = this._ref.current ? this._ref.current.scrollHeight : 0;
-        if (this.props.Document.autoHeight && sh !== 0) {
+        if (!this.props.isOverlay && this.props.Document.autoHeight && sh !== 0) {
             let nh = this.props.Document.isTemplate ? 0 : NumCast(this.dataDoc.nativeHeight, 0);
             let dh = NumCast(this.props.Document.height, 0);
             this.props.Document.height = Math.max(10, (nh ? dh / nh * sh : sh) + (ChromeHeight ? ChromeHeight() : 0));
-- 
cgit v1.2.3-70-g09d2


From 9fb7008b5cce6fd8973ec48cf6b067f3c4cfd363 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Fri, 23 Aug 2019 17:41:11 -0400
Subject: fixed documents disappearing without an x/y, fixed dropping something
 onto textbox making it a template, fixed treeview to show minimized
 documents.

---
 src/client/views/DocumentDecorations.tsx                       |  2 +-
 src/client/views/collections/CollectionTreeView.tsx            |  1 +
 .../collections/collectionFreeForm/CollectionFreeFormView.tsx  |  2 +-
 src/client/views/nodes/FormattedTextBox.tsx                    | 10 ++++++----
 4 files changed, 9 insertions(+), 6 deletions(-)

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

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 2c5940467..ee2a57bd3 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -1,7 +1,7 @@
 import { library, IconProp } from '@fortawesome/fontawesome-svg-core';
 import { faLink, faTag, faTimes, faArrowAltCircleDown, faArrowAltCircleUp, faCheckCircle, faStopCircle, faCloudUploadAlt, faSyncAlt, faShare } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed, observable, reaction, runInAction } from "mobx";
+import { action, computed, observable, reaction, runInAction, trace } from "mobx";
 import { observer } from "mobx-react";
 import { Doc } from "../../new_fields/Doc";
 import { List } from "../../new_fields/List";
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 7e1aacd5d..6e284a76f 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -373,6 +373,7 @@ class TreeView extends React.Component<TreeViewProps> {
         return <>
             <div className="docContainer" id={`docContainer-${this.props.parentKey}`} ref={reference} onPointerDown={onItemDown}
                 style={{
+                    color: this.props.document.isMinimized ? "red" : "black",
                     background: Doc.IsBrushed(this.props.document) ? "#06121212" : "0",
                     outline: BoolCast(this.props.document.workspaceBrush) ? "dashed 1px #06123232" : undefined,
                     pointerEvents: this.props.active() || SelectionManager.GetIsDragging() ? "all" : "none"
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 0a2dcbe3b..f185222ce 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -760,7 +760,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                             ele: <CollectionFreeFormDocumentView key={doc[Id]}
                                 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: (pos.x !== undefined && pos.y !== undefined) ? { x: pos.x, y: pos.y, z: pos.z, width: NumCast(pos.width), height: NumCast(pos.height) } : undefined
+                            bounds: { x: pos.x || 0, y: pos.y || 0, z: pos.z, width: NumCast(pos.width), height: NumCast(pos.height) }
                         });
                     }
                 }
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 91596e825..876d4c428 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -36,6 +36,7 @@ import { GoogleApiClientUtils, Pulls, Pushes } from '../../apis/google_docs/Goog
 import { DocumentDecorations } from '../DocumentDecorations';
 import { DictationManager } from '../../util/DictationManager';
 import { ReplaceStep } from 'prosemirror-transform';
+import { DocumentType } from '../../documents/DocumentTypes';
 
 library.add(faEdit);
 library.add(faSmile, faTextHeight, faUpload);
@@ -256,10 +257,11 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             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 })));
             e.stopPropagation();
-        } else {
-            if (de.data instanceof DragManager.DocumentDragData) {
-                this.props.Document.layout = de.data.draggedDocuments[0];
-                de.data.draggedDocuments[0].isTemplate = true;
+        } else if (de.data instanceof DragManager.DocumentDragData) {
+            const draggedDoc = de.data.draggedDocuments.length && de.data.draggedDocuments[0];
+            if (draggedDoc && draggedDoc.type === DocumentType.TEXT && StrCast(draggedDoc.layout) != "") {
+                this.props.Document.layout = draggedDoc;
+                draggedDoc.isTemplate = true;
                 e.stopPropagation();
             }
         }
-- 
cgit v1.2.3-70-g09d2


From d237c67b236ce66e9bc711931e4c283a8c1e95f2 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Fri, 23 Aug 2019 18:46:35 -0400
Subject: linter

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

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

diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 876d4c428..d6ba1700a 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -259,7 +259,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             e.stopPropagation();
         } else if (de.data instanceof DragManager.DocumentDragData) {
             const draggedDoc = de.data.draggedDocuments.length && de.data.draggedDocuments[0];
-            if (draggedDoc && draggedDoc.type === DocumentType.TEXT && StrCast(draggedDoc.layout) != "") {
+            if (draggedDoc && draggedDoc.type === DocumentType.TEXT && StrCast(draggedDoc.layout) !== "") {
                 this.props.Document.layout = draggedDoc;
                 draggedDoc.isTemplate = true;
                 e.stopPropagation();
-- 
cgit v1.2.3-70-g09d2


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

---
 src/client/util/ProsemirrorExampleTransfer.ts | 70 +++++++++++++++++++++------
 src/client/util/RichTextRules.ts              |  3 +-
 src/client/util/RichTextSchema.tsx            | 10 +++-
 src/client/util/TooltipTextMenu.tsx           | 10 +++-
 src/client/views/nodes/FormattedTextBox.tsx   | 12 ++---
 5 files changed, 78 insertions(+), 27 deletions(-)

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

diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts
index b928532d6..3cdfba59a 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, splitListItem, wrapInList } from "prosemirror-schema-list";
-import { EditorState, Transaction } from "prosemirror-state";
+import { liftListItem, splitListItem, wrapInList, sinkListItem } from "prosemirror-schema-list";
+import { EditorState, Transaction, TextSelection, NodeSelection } from "prosemirror-state";
 import { TooltipTextMenu } from "./TooltipTextMenu";
 
 const mac = typeof navigator !== "undefined" ? /Mac/.test(navigator.platform) : false;
@@ -79,32 +79,72 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?:
 
     bind("Mod-s", TooltipTextMenu.insertStar);
 
-    bind("Shift-Tab", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
-        var marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
-        liftListItem(schema.nodes.list_item)(state, (tx2: Transaction) => {
-            marks && tx2.ensureMarks(marks);
-            marks && tx2.setStoredMarks(marks);
-            dispatch(tx2);
-        });
-    });
 
+    let levelMark = (depth: number) => {
+        let p10 = schema.marks.pFontSize.create({ fontSize: 10 });
+        let p14 = schema.marks.pFontSize.create({ fontSize: 14 });
+        let p18 = schema.marks.pFontSize.create({ fontSize: 18 });
+        let p24 = schema.marks.pFontSize.create({ fontSize: 24 });
+        return depth == 0 ? p24 : depth == 2 ? p18 : depth == 4 ? p14 : p10;
+    }
     let bulletFunc = (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
         var ref = state.selection;
         var range = ref.$from.blockRange(ref.$to);
         var marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
         let depth = range && range.depth ? range.depth : 0;
         let nodeType = depth == 2 ? schema.nodes.cap_alphabet_list : depth == 4 ? schema.nodes.roman_list : depth == 6 ? schema.nodes.alphabet_list : schema.nodes.ordered_list;
+        let nodeTypeMark = depth == 2 ? schema.marks.mcap_alphabet_list : depth == 4 ? schema.marks.mroman_list : depth == 6 ? schema.marks.malphabet_list : schema.marks.mordered_list;
+        let created = levelMark(depth);
+        if (!sinkListItem(nodeType /*schema.nodes.list_item */)(state, (tx2: Transaction) => {
+            const resolvedPos = tx2.doc.resolve(range!.start);
+
+            let ns = new NodeSelection(resolvedPos);
+            let tx3 = tx2.addMark(ns.from - 1, ns.to, created).addMark(ns.from - 1, ns.to, nodeTypeMark as any).setSelection(TextSelection.create(tx2.doc, ns.to - (depth == 0 ? 3 : 1)));
+            marks && tx3.ensureMarks([...marks, created]);
+            marks && tx3.setStoredMarks([...marks, created]);
 
-        if (!wrapInList(nodeType)(state, (tx2: Transaction) => {
-            marks && tx2.ensureMarks(marks);
-            marks && tx2.setStoredMarks(marks);
-            dispatch(tx2);
+            dispatch(tx3);
         })) {
-            console.log("bullet fail");
+            let sxf = state.tr.setSelection(TextSelection.create(state.doc, range!.start, range!.end));
+            let newstate = state.applyTransaction(sxf);
+            if (!wrapInList(nodeType)(newstate.state, (tx2: Transaction) => {
+                const resolvedPos = tx2.doc.resolve(range!.start);
+                let ns = new TextSelection(resolvedPos, tx2.doc.resolve(range!.end + 1)); // new NodeSelection(resolvedPos);
+                let tx3 = tx2.setSelection(ns).removeMark(ns.from, ns.to, created).addMark(ns.from, ns.to, created).setSelection(TextSelection.create(tx2.doc, ns.to));
+                let tx4 = depth > 0 ? tx3.insertText(" ").setSelection(TextSelection.create(tx2.doc, ns.to - 2, ns.to + 2)).deleteSelection() : tx3;
+                marks && tx4.ensureMarks([...marks, created]);
+                marks && tx4.setStoredMarks([...marks, created]);
+
+                dispatch(tx4);
+            })) {
+                console.log("bullet fail");
+            }
         }
     }
     bind("Tab", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => bulletFunc(state, dispatch));
 
+    bind("Shift-Tab", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
+        var ref = state.selection;
+        var range = ref.$from.blockRange(ref.$to);
+        var marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
+        let created = levelMark(range && range.depth ? range.depth - 4 : 0);
+        liftListItem(schema.nodes.list_item)(state, (tx2: Transaction) => {
+            try {
+                const resolvedPos = tx2.doc.resolve(Math.round((range!.start + range!.end) / 2));
+                let nodeIndex = resolvedPos.pos - (resolvedPos.nodeBefore && resolvedPos.nodeBefore.type.name === "text" ? resolvedPos.nodeBefore!.nodeSize : 0);
+                let ns = new NodeSelection(tx2.doc.resolve(nodeIndex));
+                if (resolvedPos.nodeAfter && resolvedPos.nodeAfter.type.name === "list_item")
+                    ns = new NodeSelection(tx2.doc.resolve(nodeIndex + 1));
+                let tx3 = tx2.setSelection(ns).removeMark(ns.from, ns.to, created).addMark(ns.from, ns.to, created).setSelection(TextSelection.create(tx2.doc, ns.to));
+                marks && tx3.ensureMarks([...marks, created]);
+                marks && tx3.setStoredMarks([...marks, created]);
+                dispatch(tx3);
+            } catch (e) {
+                dispatch(tx2);
+            }
+        });
+    });
+
     bind("Enter", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
         var marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
         if (!splitListItem(schema.nodes.list_item)(state, (tx3: Transaction) => {
diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts
index 89933650b..8c4c76027 100644
--- a/src/client/util/RichTextRules.ts
+++ b/src/client/util/RichTextRules.ts
@@ -26,7 +26,7 @@ export const inpRules = {
             match => ({ order: +match[1] }),
             (match, node) => node.childCount + node.attrs.order === +match[1]
         ),
-        // 1. ordered list
+        // a. alphabbetical list
         wrappingInputRule(
             /^([a-z]+)\.\s$/,
             schema.nodes.alphabet_list,
@@ -34,7 +34,6 @@ export const inpRules = {
             (match, node) => node.childCount + node.attrs.order === +match[1]
         ),
 
-
         // * bullet list
         wrappingInputRule(/^\s*([-+*])\s$/, schema.nodes.bullet_list),
 
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index a8ce4731c..f128162c2 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -319,6 +319,15 @@ export const marks: { [index: string]: MarkSpec } = {
         toDOM: () => ['sup']
     },
 
+    malphabet_list: {
+    },
+    mcap_alphabet_list: {
+    },
+    mroman_list: {
+    },
+    mo_list: {
+    },
+
     highlight: {
         parseDOM: [{ style: 'color: blue' }],
         toDOM() {
@@ -412,7 +421,6 @@ export const marks: { [index: string]: MarkSpec } = {
         attrs: {
             fontSize: { default: 10 }
         },
-        inclusive: false,
         parseDOM: [{ style: 'font-size: 10px;' }],
         toDOM: (node) => ['span', {
             style: `font-size: ${node.attrs.fontSize}px;`
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 7a0c6f8c5..77396c829 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -165,7 +165,15 @@ export class TooltipTextMenu {
         this.fontSizeToNum.set(schema.marks.p48, 48);
         this.fontSizeToNum.set(schema.marks.p72, 72);
         this.fontSizeToNum.set(schema.marks.pFontSize, 10);
-        this.fontSizeToNum.set(schema.marks.pFontSize, 10);
+        // this.fontSizeToNum.set(schema.marks.pFontSize, 12);
+        // this.fontSizeToNum.set(schema.marks.pFontSize, 14);
+        // this.fontSizeToNum.set(schema.marks.pFontSize, 16);
+        // this.fontSizeToNum.set(schema.marks.pFontSize, 18);
+        // this.fontSizeToNum.set(schema.marks.pFontSize, 20);
+        // this.fontSizeToNum.set(schema.marks.pFontSize, 24);
+        // this.fontSizeToNum.set(schema.marks.pFontSize, 32);
+        // this.fontSizeToNum.set(schema.marks.pFontSize, 48);
+        // this.fontSizeToNum.set(schema.marks.pFontSize, 72);
         this.fontSizes = Array.from(this.fontSizeToNum.keys());
 
         //list types
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index d6ba1700a..acfd2a3b8 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -115,7 +115,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
 
     @undoBatch
     public setFontColor(color: string) {
-        let self = this;
         if (this._editorView!.state.selection.from === this._editorView!.state.selection.to) return false;
         if (this._editorView!.state.selection.to - this._editorView!.state.selection.from > this._editorView!.state.doc.nodeSize - 3) {
             this.props.Document.color = color;
@@ -176,15 +175,13 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             FormattedTextBox._toolTipTextMenu && (FormattedTextBox._toolTipTextMenu.HackToFixTextSelectionGlitch = true);
             this._editorView.updateState(state);
             FormattedTextBox._toolTipTextMenu && (FormattedTextBox._toolTipTextMenu.HackToFixTextSelectionGlitch = false);
-            if (state.selection.empty && FormattedTextBox._toolTipTextMenu) {
-                const marks = tx.storedMarks;
-                if (marks) { FormattedTextBox._toolTipTextMenu.mark_key_pressed(marks); }
+            if (state.selection.empty && FormattedTextBox._toolTipTextMenu && tx.storedMarks) {
+                FormattedTextBox._toolTipTextMenu.mark_key_pressed(tx.storedMarks);
             }
 
             this._applyingChange = true;
-            const fieldkey = "preview";
-            if (this.extensionDoc) this.extensionDoc.text = state.doc.textBetween(0, state.doc.content.size, "\n\n");
-            if (this.extensionDoc) this.extensionDoc.lastModified = new DateField(new Date(Date.now()));
+            this.extensionDoc && (this.extensionDoc.text = state.doc.textBetween(0, state.doc.content.size, "\n\n"));
+            this.extensionDoc && (this.extensionDoc.lastModified = new DateField(new Date(Date.now())));
             this.dataDoc[this.props.fieldKey] = new RichTextField(JSON.stringify(state.toJSON()));
             this._applyingChange = false;
             let title = StrCast(this.dataDoc.title);
@@ -198,7 +195,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
 
     public highlightSearchTerms = (terms: String[]) => {
         if (this._editorView && (this._editorView as any).docView) {
-            const fieldkey = "preview";
             const doc = this._editorView.state.doc;
             const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight);
             doc.nodesBetween(0, doc.content.size, (node: ProsNode, pos: number, parent: ProsNode, index: number) => {
-- 
cgit v1.2.3-70-g09d2


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

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

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

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


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

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

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

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


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

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

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

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


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

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

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

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


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

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

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

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


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

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

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

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


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

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

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

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


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

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

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

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


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

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

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

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


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

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

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

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


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

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

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

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


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

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

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

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


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

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

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

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


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

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

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

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


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

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

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

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


From 2d3de245e31e0c45c1e64720816c3d6d219efcd3 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Tue, 3 Sep 2019 18:53:08 -0400
Subject: Update FormattedTextBox.tsx

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

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

diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index bb44c5ac6..b46502c1d 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -811,8 +811,12 @@ 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.
+        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();
             e.preventDefault();
+            setTimeout(() => { // force re-rendering of bullet numbers that may have had their bullet labels change.  (Our prosemirrior code re-"marks" the changed bullets, but nothing causes the Dom to be re-rendered which is where the nubering takes place)
+                SelectionManager.DeselectAll();
+                SelectionManager.SelectDoc(DocumentManager.Instance.getDocumentView(this.props.Document, this.props.ContainingCollectionView)!, false);
+            }, 0);
         }
         function timenow() {
             var now = new Date();
-- 
cgit v1.2.3-70-g09d2


From 4921ef0121818381e4139137c0d204d41cb64a20 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Tue, 3 Sep 2019 18:57:23 -0400
Subject: reverted back to semi-working version of outline mode

---
 src/client/views/nodes/FormattedTextBox.tsx | 9 +++++----
 src/server/index.ts                         | 2 +-
 2 files changed, 6 insertions(+), 5 deletions(-)

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

diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index b46502c1d..4cb52ec78 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -813,10 +813,11 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         e.stopPropagation();
         if (e.key === "Tab" || e.key === "Enter") { // bullets typically change "levels" when tab or enter is used.  sometimes backspcae, so maybe that should be added. e.preventDefault();
             e.preventDefault();
-            setTimeout(() => { // force re-rendering of bullet numbers that may have had their bullet labels change.  (Our prosemirrior code re-"marks" the changed bullets, but nothing causes the Dom to be re-rendered which is where the nubering takes place)
-                SelectionManager.DeselectAll();
-                SelectionManager.SelectDoc(DocumentManager.Instance.getDocumentView(this.props.Document, this.props.ContainingCollectionView)!, false);
-            }, 0);
+            // 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);
         }
         function timenow() {
             var now = new Date();
diff --git a/src/server/index.ts b/src/server/index.ts
index fca90a585..17cd59ec7 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);
                 let canvasAndContext = factory.create(viewport.width, viewport.height);
                 let renderContext = {
                     canvasContext: canvasAndContext.context,
-- 
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/views/nodes/FormattedTextBox.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/views/nodes/FormattedTextBox.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 a936f08e3c18ad34bbe74133eaa768c7bda832dc Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 4 Sep 2019 21:00:30 -0400
Subject: cleanup

---
 src/client/views/nodes/FormattedTextBox.tsx | 22 +++++++++++++---------
 1 file changed, 13 insertions(+), 9 deletions(-)

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

diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 2dbfeeba7..d811273e8 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -75,6 +75,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     private _editorView: Opt<EditorView>;
     private _applyingChange: boolean = false;
     private _linkClicked = "";
+    private _nodeClicked: any;
     private _undoTyping?: UndoManager.Batch;
     private _reactionDisposer: Opt<IReactionDisposer>;
     private _searchReactionDisposer?: Lambda;
@@ -671,17 +672,10 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         this._editorView && this._editorView.destroy();
     }
 
+
     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 }));
-                }
-            }
-        }
+        pos && (this._nodeClicked = this._editorView!.state.doc.nodeAt(pos.pos));
         if (this.props.onClick && e.button === 0) {
             e.preventDefault();
         }
@@ -778,6 +772,16 @@ 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) {
+                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)) {
+                    this._editorView!.dispatch(this._editorView!.state.tr.setNodeMarkup(pos.pos - 1, node2.type, { ...node2.attrs, visibility: !node2.attrs.visibility }));
+                }
+            }
+        }
         this._proseRef!.focus();
         if (this._linkClicked) {
             this._linkClicked = "";
-- 
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/views/nodes/FormattedTextBox.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/views/nodes/FormattedTextBox.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/views/nodes/FormattedTextBox.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/views/nodes/FormattedTextBox.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/views/nodes/FormattedTextBox.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/nodes/FormattedTextBox.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/views/nodes/FormattedTextBox.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 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/views/nodes/FormattedTextBox.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/views/nodes/FormattedTextBox.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 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/nodes/FormattedTextBox.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 a8be7ef95403a1f4559aeff9695e67ad2030e3b9 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 9 Sep 2019 21:34:34 -0400
Subject: restored input parsing rules back to prosemirror and added some news

---
 src/client/util/RichTextRules.ts            | 52 ++++++++++++++++++-----------
 src/client/util/prosemirrorPatches.js       | 47 ++++++++++++++++++++++++++
 src/client/views/nodes/FormattedTextBox.tsx |  3 +-
 src/server/index.ts                         |  4 +--
 4 files changed, 83 insertions(+), 23 deletions(-)

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

diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts
index 8c4c76027..979b76988 100644
--- a/src/client/util/RichTextRules.ts
+++ b/src/client/util/RichTextRules.ts
@@ -1,14 +1,6 @@
-import {
-    inputRules,
-    wrappingInputRule,
-    textblockTypeInputRule,
-    smartQuotes,
-    emDash,
-    ellipsis
-} from "prosemirror-inputrules";
-import { Schema, NodeSpec, MarkSpec, DOMOutputSpecArray, NodeType } from "prosemirror-model";
-
+import { textblockTypeInputRule, smartQuotes, emDash, ellipsis, InputRule } from "prosemirror-inputrules";
 import { schema } from "./RichTextSchema";
+import { wrappingInputRule } from "./prosemirrorPatches";
 
 export const inpRules = {
     rules: [
@@ -21,17 +13,29 @@ export const inpRules = {
 
         // 1. ordered list
         wrappingInputRule(
-            /^(\d+)\.\s$/,
+            /^1\.\s$/,
             schema.nodes.ordered_list,
-            match => ({ order: +match[1] }),
-            (match, node) => node.childCount + node.attrs.order === +match[1]
+            () => {
+                return ({ mapStyle: "decimal", bulletStyle: 1 })
+            },
+            (match: any, node: any) => {
+                return node.childCount + node.attrs.order === +match[1];
+            },
+            (type: any) => ({ type: type, attrs: { mapStyle: "decimal", bulletStyle: 1 } })
         ),
         // a. alphabbetical list
         wrappingInputRule(
-            /^([a-z]+)\.\s$/,
-            schema.nodes.alphabet_list,
-            match => ({ order: +match[1] }),
-            (match, node) => node.childCount + node.attrs.order === +match[1]
+            /^a\.\s$/,
+            schema.nodes.ordered_list,
+            // match => {
+            () => {
+                return ({ mapStyle: "alpha", bulletStyle: 1 })
+                // return ({ order: +match[1] })
+            },
+            (match: any, node: any) => {
+                return node.childCount + node.attrs.order === +match[1];
+            },
+            (type: any) => ({ type: type, attrs: { mapStyle: "alpha", bulletStyle: 1 } })
         ),
 
         // * bullet list
@@ -42,9 +46,17 @@ export const inpRules = {
 
         // # heading
         textblockTypeInputRule(
-            new RegExp("^(#{1,6})\\s$"),
+            new RegExp(/^(#{1,6})\s$/),
             schema.nodes.heading,
-            match => ({ level: match[1].length })
-        )
+            match => {
+                return ({ level: match[1].length });
+            }
+        ),
+
+        new InputRule(
+            new RegExp(/^#([0-9]+)\s$/),
+            (state, match, start, end) => {
+                return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontSize.create({ fontSize: Number(match[1]) }))
+            }),
     ]
 };
diff --git a/src/client/util/prosemirrorPatches.js b/src/client/util/prosemirrorPatches.js
index 6bf4395ad..188e3e1c5 100644
--- a/src/client/util/prosemirrorPatches.js
+++ b/src/client/util/prosemirrorPatches.js
@@ -2,11 +2,13 @@
 
 Object.defineProperty(exports, '__esModule', { value: true });
 
+var prosemirrorInputRules = require('prosemirror-inputrules');
 var prosemirrorTransform = require('prosemirror-transform');
 var prosemirrorModel = require('prosemirror-model');
 
 exports.liftListItem = liftListItem;
 exports.sinkListItem = sinkListItem;
+exports.wrappingInputRule = wrappingInputRule;
 // :: (NodeType) → (state: EditorState, dispatch: ?(tr: Transaction)) → bool
 // Create a command to lift the list item around the selection up into
 // a wrapping list.
@@ -89,4 +91,49 @@ function sinkListItem(itemType) {
         }
         return true
     }
+}
+
+function findWrappingOutside(range, type) {
+    var parent = range.parent;
+    var startIndex = range.startIndex;
+    var endIndex = range.endIndex;
+    var around = parent.contentMatchAt(startIndex).findWrapping(type);
+    if (!around) { return null }
+    var outer = around.length ? around[0] : type;
+    return parent.canReplaceWith(startIndex, endIndex, outer) ? around : null
+}
+
+function findWrappingInside(range, type) {
+    var parent = range.parent;
+    var startIndex = range.startIndex;
+    var endIndex = range.endIndex;
+    var inner = parent.child(startIndex);
+    var inside = type.contentMatch.findWrapping(inner.type);
+    if (!inside) { return null }
+    var lastType = inside.length ? inside[inside.length - 1] : type;
+    var innerMatch = lastType.contentMatch;
+    for (var i = startIndex; innerMatch && i < endIndex; i++) { innerMatch = innerMatch.matchType(parent.child(i).type); }
+    if (!innerMatch || !innerMatch.validEnd) { return null }
+    return inside
+}
+function findWrapping(range, nodeType, attrs, innerRange, customWithAttrs = null) {
+    if (innerRange === void 0) innerRange = range;
+    let withAttrs = (type) => ({ type: type, attrs: null });
+    var around = findWrappingOutside(range, nodeType);
+    var inner = around && findWrappingInside(innerRange, nodeType);
+    if (!inner) { return null }
+    return around.map(withAttrs).concat({ type: nodeType, attrs: attrs }).concat(inner.map(customWithAttrs ? customWithAttrs : withAttrs))
+}
+function wrappingInputRule(regexp, nodeType, getAttrs, joinPredicate, customWithAttrs = null) {
+    return new prosemirrorInputRules.InputRule(regexp, function (state, match, start, end) {
+        var attrs = getAttrs instanceof Function ? getAttrs(match) : getAttrs;
+        var tr = state.tr.delete(start, end);
+        var $start = tr.doc.resolve(start), range = $start.blockRange(), wrapping = range && findWrapping(range, nodeType, attrs, undefined, customWithAttrs);
+        if (!wrapping) { return null }
+        tr.wrap(range, wrapping);
+        var before = tr.doc.resolve(start - 1).nodeBefore;
+        if (before && before.type == nodeType && prosemirrorTransform.canJoin(tr.doc, start - 1) &&
+            (!joinPredicate || joinPredicate(match, before))) { tr.join(start - 1); }
+        return tr
+    })
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 93c97fa23..6a6000dc5 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -38,6 +38,7 @@ import { DictationManager } from '../../util/DictationManager';
 import { ReplaceStep } from 'prosemirror-transform';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { formattedTextBoxCommentPlugin, FormattedTextBoxComment } from './FormattedTextBoxComment';
+import { inputRules } from 'prosemirror-inputrules';
 
 library.add(faEdit);
 library.add(faSmile, faTextHeight, faUpload);
@@ -355,8 +356,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         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
             plugins: this.props.isOverlay ? [
+                inputRules(inpRules),
                 this.tooltipTextMenuPlugin(),
                 history(),
                 keymap(this._keymap),
diff --git a/src/server/index.ts b/src/server/index.ts
index 082e9422d..50ce2b14e 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -811,8 +811,8 @@ const EndpointHandlerMap = new Map<GoogleApiServerUtils.Action, GoogleApiServerU
 ]);
 
 app.post(RouteStore.googleDocs + "/:sector/:action", (req, res) => {
-    let sector: GoogleApiServerUtils.Service = req.params.sector;
-    let action: GoogleApiServerUtils.Action = req.params.action;
+    let sector: GoogleApiServerUtils.Service = req.params.sector as GoogleApiServerUtils.Service;
+    let action: GoogleApiServerUtils.Action = req.params.action as GoogleApiServerUtils.Action;
     GoogleApiServerUtils.GetEndpoint(GoogleApiServerUtils.Service[sector], { credentials, token }).then(endpoint => {
         let handler = EndpointHandlerMap.get(action);
         if (endpoint && handler) {
-- 
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/nodes/FormattedTextBox.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 68e554cafb6107bfde9526773b3e0e667d582c88 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Wed, 11 Sep 2019 17:24:48 -0400
Subject: added default note type collection

---
 src/client/documents/Documents.ts                        |  1 +
 src/client/views/Main.tsx                                |  1 -
 src/client/views/PreviewCursor.tsx                       |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx        | 16 +++++++++-------
 .../views/collections/collectionFreeForm/MarqueeView.tsx |  6 ++++++
 src/client/views/nodes/FormattedTextBox.tsx              |  3 ++-
 src/server/authentication/models/current_user_utils.ts   | 12 +++++++++++-
 7 files changed, 30 insertions(+), 11 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index ae65fde1e..602a7f9ad 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -66,6 +66,7 @@ export interface DocumentOptions {
     page?: number;
     scale?: number;
     layout?: string;
+    isTemplate?: boolean;
     templates?: List<string>;
     viewType?: number;
     backgroundColor?: string;
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 0e687737d..11ec6f0c9 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -37,7 +37,6 @@ let swapDocs = async () => {
     (await Cast(CurrentUserUtils.UserDocument.workspaces, Doc))!.chromeStatus = "disabled";
     (await Cast(CurrentUserUtils.UserDocument.recentlyClosed, Doc))!.chromeStatus = "disabled";
     (await Cast(CurrentUserUtils.UserDocument.sidebar, Doc))!.chromeStatus = "disabled";
-    CurrentUserUtils.UserDocument.chromeStatus = "disabled";
     await swapDocs();
     document.getElementById('root')!.addEventListener('wheel', event => {
         if (event.ctrlKey) {
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index 45a8556bf..1aed51e64 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -102,7 +102,7 @@ export class PreviewCursor extends React.Component<{}> {
             (e.keyCode < 112 || e.keyCode > 123) && // F1 thru F12 keys
             !e.key.startsWith("Arrow") &&
             !e.defaultPrevented) {
-            if (!e.ctrlKey && !e.metaKey) {//  /^[a-zA-Z0-9$*^%#@+-=_|}{[]"':;?/><.,}]$/.test(e.key)) {
+            if ((!e.ctrlKey || (e.keyCode >= 48 && e.keyCode <= 57)) && !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/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 21f119d57..2591bdd8d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -39,6 +39,7 @@ import { MarqueeView } from "./MarqueeView";
 import React = require("react");
 import { DocServer } from "../../../DocServer";
 import { FormattedTextBox } from "../../nodes/FormattedTextBox";
+import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils";
 
 library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard);
 
@@ -272,7 +273,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             // 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);
+            (newBox.backgroundColor === newBox.defaultBackgroundColor) && col && (Doc.GetProto(newBox).backgroundColor = col);
 
             let round = StrCast(ruleProvider["ruleRounding_" + NumCast(newBox.heading)]);
             round && (Doc.GetProto(newBox).borderRounding = round);
@@ -945,17 +946,18 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         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" });
+        let notes = DocListCast((CurrentUserUtils.UserDocument.noteTypes as Doc).data);
+        notes.map((node, i) => noteItems.push({ description: (i + 1) + ": " + StrCast(node.title), event: () => this.createText(i), icon: "eye" }));
         layoutItems.push({ description: "Add Note ...", subitems: noteItems, icon: "eye" })
         ContextMenu.Instance.addItem({ description: "Freeform Options ...", subitems: layoutItems, icon: "eye" });
     }
 
-    createText = (noteStyle: string, color: string) => {
+    createText = (noteStyle: number) => {
         let pt = this.getTransform().transformPoint(ContextMenu.Instance.pageX, ContextMenu.Instance.pageY);
-        this.addLiveTextBox(Docs.Create.TextDocument({ title: noteStyle, x: pt[0], y: pt[1], autoHeight: true, backgroundColor: color }))
+        let notes = DocListCast((CurrentUserUtils.UserDocument.noteTypes as Doc).data);
+        let text = Docs.Create.TextDocument({ width: 200, height: 100, x: pt[0], y: pt[1], autoHeight: true, title: StrCast(notes[noteStyle % notes.length].title) });
+        text.layout = notes[noteStyle % notes.length];
+        this.addLiveTextBox(text);
     }
 
     private childViews = () => [
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 0c4860be1..fe48a3485 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -22,6 +22,7 @@ import React = require("react");
 import { SchemaHeaderField, RandomPastel } from "../../../../new_fields/SchemaHeaderField";
 import { string } from "prop-types";
 import { listSpec } from "../../../../new_fields/Schema";
+import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils";
 
 interface MarqueeViewProps {
     getContainerTransform: () => Transform;
@@ -97,6 +98,11 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
         } else if (!e.ctrlKey) {
             this.props.addLiveTextDocument(
                 Docs.Create.TextDocument({ width: 200, height: 100, x: x, y: y, autoHeight: true, title: "-typed text-" }));
+        } else if (e.keyCode > 48 && e.keyCode <= 57) {
+            let notes = DocListCast((CurrentUserUtils.UserDocument.noteTypes as Doc).data);
+            let text = Docs.Create.TextDocument({ width: 200, height: 100, x: x, y: y, autoHeight: true, title: "-typed text-" });
+            text.layout = notes[(e.keyCode - 49) % notes.length];
+            this.props.addLiveTextDocument(text);
         }
         e.stopPropagation();
     }
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 0ea36cdc2..194026a08 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -289,7 +289,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         } else if (de.data instanceof DragManager.DocumentDragData) {
             const draggedDoc = de.data.draggedDocuments.length && de.data.draggedDocuments[0];
             if (draggedDoc && draggedDoc.type === DocumentType.TEXT && StrCast(draggedDoc.layout) !== "") {
-                this.props.Document.layout = draggedDoc;
+                if (this.props.DataDoc) this.props.DataDoc.layout = draggedDoc;
+                else this.props.Document.layout = draggedDoc;
                 draggedDoc.isTemplate = true;
                 e.stopPropagation();
             }
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 9d35d36d3..af5774ebe 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -7,7 +7,7 @@ import { Attribute, AttributeGroup, Catalog, Schema } from "../../../client/nort
 import { ArrayUtil } from "../../../client/northstar/utils/ArrayUtil";
 import { CollectionViewType } from "../../../client/views/collections/CollectionBaseView";
 import { CollectionView } from "../../../client/views/collections/CollectionView";
-import { Doc } from "../../../new_fields/Doc";
+import { Doc, DocListCast } from "../../../new_fields/Doc";
 import { List } from "../../../new_fields/List";
 import { listSpec } from "../../../new_fields/Schema";
 import { Cast, StrCast, PromiseValue } from "../../../new_fields/Types";
@@ -50,6 +50,16 @@ export class CurrentUserUtils {
             doc.workspaces = workspaces;
         }
         PromiseValue(Cast(doc.workspaces, Doc)).then(workspaces => workspaces && (workspaces.preventTreeViewOpen = true));
+        if (doc.noteTypes === undefined) {
+            let notes = [Docs.Create.TextDocument({ title: "Note", backgroundColor: "yellow", isTemplate: true }),
+            Docs.Create.TextDocument({ title: "Idea", backgroundColor: "pink", isTemplate: true }),
+            Docs.Create.TextDocument({ title: "Topic", backgroundColor: "lightBlue", isTemplate: true }),
+            Docs.Create.TextDocument({ title: "Person", backgroundColor: "lightGreen", isTemplate: true })];
+            const noteTypes = Docs.Create.TreeDocument(notes, { title: "Note Types", height: 75 });
+            noteTypes.excludeFromLibrary = true;
+            doc.noteTypes = noteTypes;
+        }
+        PromiseValue(Cast(doc.noteTypes, Doc)).then(noteTypes => noteTypes && PromiseValue(noteTypes.data).then(vals => DocListCast(vals)));
         if (doc.recentlyClosed === undefined) {
             const recentlyClosed = Docs.Create.TreeDocument([], { title: "Recently Closed", height: 75 });
             recentlyClosed.excludeFromLibrary = true;
-- 
cgit v1.2.3-70-g09d2


From 186d7aed7b99b1373e99b51cfe0c88c8167c8290 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 11 Sep 2019 22:52:07 -0400
Subject: fixed some template issues specifically for self-templates.

---
 src/client/views/DocumentDecorations.tsx           | 22 +++++++++++++-------
 .../views/collections/CollectionBaseView.tsx       |  2 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx | 24 ++++++++++++++--------
 src/client/views/nodes/DocumentView.tsx            |  6 +++---
 src/client/views/nodes/FormattedTextBox.tsx        |  3 +--
 src/new_fields/Doc.ts                              |  5 ++---
 6 files changed, 37 insertions(+), 25 deletions(-)

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

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index fe409d9a6..814d718be 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -30,6 +30,7 @@ import { MetadataEntryMenu } from './MetadataEntryMenu';
 import { ImageBox } from './nodes/ImageBox';
 import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils';
 import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils';
+import { ObjectField } from '../../new_fields/ObjectField';
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -145,13 +146,20 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 let fieldTemplateView = SelectionManager.SelectedDocuments()[0];
                 SelectionManager.DeselectAll();
                 let fieldTemplate = fieldTemplateView.props.Document;
-                let docTemplate = fieldTemplateView.props.ContainingCollectionView!.props.Document;
-                let metaKey = text.startsWith(">>") ? text.slice(2, text.length) : text.slice(1, text.length);
-                let proto = Doc.GetProto(docTemplate);
-                Doc.MakeTemplate(fieldTemplate, metaKey, proto);
-                if (text.startsWith(">>")) {
-                    proto.detailedLayout = proto.layout;
-                    proto.miniLayout = ImageBox.LayoutString(metaKey);
+                let containerView = fieldTemplateView.props.ContainingCollectionView;
+                if (containerView) {
+                    let docTemplate = containerView.props.Document;
+                    let metaKey = text.startsWith(">>") ? text.slice(2, text.length) : text.slice(1, text.length);
+                    let proto = Doc.GetProto(docTemplate);
+                    if (metaKey !== containerView.props.fieldKey && containerView.props.DataDoc) {
+                        const fd = fieldTemplate.data;
+                        fd instanceof ObjectField && (Doc.GetProto(containerView.props.DataDoc)[metaKey] = ObjectField.MakeCopy(fd));
+                    }
+                    Doc.MakeTemplate(fieldTemplate, metaKey, proto);
+                    if (text.startsWith(">>")) {
+                        proto.detailedLayout = proto.layout;
+                        proto.miniLayout = ImageBox.LayoutString(metaKey);
+                    }
                 }
             }
             else {
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index 5829f0626..b7036b3ff 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -127,7 +127,7 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
         let targetDataDoc = this.props.fieldExt || this.props.Document.isTemplate ? this.extensionDoc : this.props.Document;
         let targetField = (this.props.fieldExt || this.props.Document.isTemplate) && this.props.fieldExt ? this.props.fieldExt : this.props.fieldKey;
         let value = Cast(targetDataDoc[targetField], listSpec(Doc), []);
-        let index = value.reduce((p, v, i) => (v instanceof Doc && v[Id] === doc[Id]) ? i : p, -1);
+        let index = value.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, doc)) ? i : p, -1);
         PromiseValue(Cast(doc.annotationOn, Doc)).then(annotationOn =>
             annotationOn === this.dataDoc.Document && (doc.annotationOn = undefined));
 
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index eb7ab64f8..07dd1cae7 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -74,10 +74,10 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     }
 
     borderRounding = () => {
-        let br = StrCast(this.props.Document.layout instanceof Doc ? this.props.Document.layout.borderRounding : this.props.Document.borderRounding);
+        let br = StrCast(this.layoutDoc.layout instanceof Doc ? this.layoutDoc.layout.borderRounding : this.props.Document.borderRounding);
         if (br.endsWith("%")) {
             let percent = Number(br.substr(0, br.length - 1)) / 100;
-            let nativeDim = Math.min(NumCast(this.props.Document.nativeWidth), NumCast(this.props.Document.nativeHeight));
+            let nativeDim = Math.min(NumCast(this.layoutDoc.nativeWidth), NumCast(this.layoutDoc.nativeHeight));
             let minDim = percent * (nativeDim ? nativeDim : Math.min(this.props.PanelWidth(), this.props.PanelHeight()));
             return minDim;
         }
@@ -89,6 +89,12 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
 
     clusterColorFunc = (doc: Doc) => this.clusterColor;
 
+    get layoutDoc() {
+        // if this document's layout field contains a document (ie, a rendering template), then we will use that
+        // to determine the render JSX string, otherwise the layout field should directly contain a JSX layout string.
+        return this.props.Document.layout instanceof Doc ? this.props.Document.layout : this.props.Document;
+    }
+
     render() {
         const hasPosition = this.props.x !== undefined || this.props.y !== undefined;
         return (
@@ -98,15 +104,15 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                     position: "absolute",
                     backgroundColor: "transparent",
                     boxShadow:
-                        this.props.Document.opacity === 0 ? undefined :  // if it's not visible, then no shadow
-                            this.props.Document.z ? `#9c9396  ${StrCast(this.props.Document.boxShadow, "10px 10px 0.9vw")}` :  // if it's a floating doc, give it a big shadow
-                                this.clusterColor ? (
-                                    this.props.Document.isBackground ? `0px 0px 50px 50px ${this.clusterColor}` :  // if it's a background & has a cluster color, make the shadow spread really big
-                                        `${this.clusterColor} ${StrCast(this.props.Document.boxShadow, `0vw 0vw ${50 / this.props.ContentScaling()}px`)}`) :  // if it's just in a cluster, make the shadown roughly match the cluster border extent
-                                    undefined,
+                        this.layoutDoc.opacity === 0 ? undefined :  // if it's not visible, then no shadow
+                            this.layoutDoc.z ? `#9c9396  ${StrCast(this.layoutDoc.boxShadow, "10px 10px 0.9vw")}` :  // if it's a floating doc, give it a big shadow
+                                this.layoutDoc.isBackground ? `0px 0px 50px 50px ${this.clusterColor}` :  // if it's a background & has a cluster color, make the shadow spread really big
+                                    this.clusterColor ? (
+                                        `${this.clusterColor} ${StrCast(this.layoutDoc.boxShadow, `0vw 0vw ${50 / this.props.ContentScaling()}px`)}`) :  // if it's just in a cluster, make the shadown roughly match the cluster border extent
+                                        StrCast(this.layoutDoc.boxShadow, ""),
                     borderRadius: this.borderRounding(),
                     transform: this.transform,
-                    transition: hasPosition ? "transform 1s" : StrCast(this.props.Document.transition),
+                    transition: hasPosition ? "transform 1s" : StrCast(this.layoutDoc.transition),
                     width: this.width,
                     height: this.height,
                     zIndex: this.Document.zIndex || 0,
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 940a66b36..c2143bb82 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -452,9 +452,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         let docTemplate = Docs.Create.FreeformDocument([fieldTemplate], { title: StrCast(this.Document.title) + "layout", width: NumCast(this.props.Document.width) + 20, height: Math.max(100, NumCast(this.props.Document.height) + 45) });
         let metaKey = "data";
         let proto = Doc.GetProto(docTemplate);
-        Doc.MakeTemplate(fieldTemplate, metaKey, proto, true);
+        Doc.MakeTemplate(fieldTemplate, metaKey, proto);
 
-        Doc.ApplyTemplateTo(docTemplate, this.props.Document, undefined, true);
+        Doc.ApplyTemplateTo(docTemplate, this.props.Document, undefined, false);
     }
 
     @undoBatch
@@ -634,7 +634,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         let makes: ContextMenuProps[] = existingMake && "subitems" in existingMake ? existingMake.subitems : [];
         makes.push({ description: this.props.Document.isBackground ? "Remove Background" : "Into Background", event: this.makeBackground, icon: this.props.Document.lockedPosition ? "unlock" : "lock" });
         makes.push({ description: "Custom Document View", event: this.makeCustomViewClicked, icon: "concierge-bell" });
-        makes.push({ description: "Metadata Field View", event: () => this.props.ContainingCollectionView && Doc.MakeTemplate(this.props.Document, StrCast(this.props.Document.title), this.props.ContainingCollectionView.props.Document, true), icon: "concierge-bell" })
+        makes.push({ description: "Metadata Field View", event: () => this.props.ContainingCollectionView && Doc.MakeTemplate(this.props.Document, StrCast(this.props.Document.title), this.props.ContainingCollectionView.props.Document), icon: "concierge-bell" })
         makes.push({ description: "Into Portal", event: this.makeIntoPortal, icon: "window-restore" });
         makes.push({ description: this.layoutDoc.ignoreClick ? "Selectable" : "Unselectable", event: () => this.layoutDoc.ignoreClick = !this.layoutDoc.ignoreClick, icon: this.layoutDoc.ignoreClick ? "unlock" : "lock" });
         !existingMake && cm.addItem({ description: "Make...", subitems: makes, icon: "hand-point-right" });
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 194026a08..0ea36cdc2 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -289,8 +289,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         } else if (de.data instanceof DragManager.DocumentDragData) {
             const draggedDoc = de.data.draggedDocuments.length && de.data.draggedDocuments[0];
             if (draggedDoc && draggedDoc.type === DocumentType.TEXT && StrCast(draggedDoc.layout) !== "") {
-                if (this.props.DataDoc) this.props.DataDoc.layout = draggedDoc;
-                else this.props.Document.layout = draggedDoc;
+                this.props.Document.layout = draggedDoc;
                 draggedDoc.isTemplate = true;
                 e.stopPropagation();
             }
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index d4b784cac..e94b9f1eb 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -459,7 +459,7 @@ export namespace Doc {
         }
         if (expandedTemplateLayout === undefined) {
             setTimeout(() => dataDoc[expandedLayoutFieldKey] === undefined &&
-                (dataDoc[expandedLayoutFieldKey] = !BoolCast(templateLayoutDoc.suppressTemplateInstance) ? Doc.MakeDelegate(templateLayoutDoc, undefined, "[" + templateLayoutDoc.title + "]") : templateLayoutDoc), 0);
+                (dataDoc[expandedLayoutFieldKey] = Doc.MakeDelegate(templateLayoutDoc, undefined, "[" + templateLayoutDoc.title + "]")), 0);
         }
         return undefined; // use the templateLayout when it's not a template or the expandedTemplate is pending.
     }
@@ -558,7 +558,7 @@ export namespace Doc {
         }
     }
 
-    export function MakeTemplate(fieldTemplate: Doc, metaKey: string, templateDataDoc: Doc, suppressTemplateFlag?: boolean) {
+    export function MakeTemplate(fieldTemplate: Doc, metaKey: string, templateDataDoc: Doc) {
         // move data doc fields to layout doc as needed (nativeWidth/nativeHeight, data, ??)
         let backgroundLayout = StrCast(fieldTemplate.backgroundLayout);
         let fieldLayoutDoc = fieldTemplate;
@@ -576,7 +576,6 @@ export namespace Doc {
         fieldTemplate.templateField = metaKey;
         fieldTemplate.title = metaKey;
         fieldTemplate.isTemplate = true;
-        fieldTemplate.suppressTemplateInstance = suppressTemplateFlag;
         fieldTemplate.layout = layoutDelegate !== fieldTemplate ? layoutDelegate : layout;
         fieldTemplate.backgroundLayout = backgroundLayout;
         /* move certain layout properties from the original data doc to the template layout to avoid
-- 
cgit v1.2.3-70-g09d2


From 147f1a6bed7f273b6248d55eee670713bfbf5e7d Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 12 Sep 2019 17:07:59 -0400
Subject: better template inferencing support

---
 src/client/util/RichTextRules.ts                   |  9 +--
 src/client/views/GlobalKeyHandler.ts               |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 15 ++--
 src/client/views/nodes/FormattedTextBox.scss       |  1 -
 src/client/views/nodes/FormattedTextBox.tsx        | 86 ++++++++++++----------
 5 files changed, 61 insertions(+), 52 deletions(-)

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

diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts
index 00e671db9..7e3d435a7 100644
--- a/src/client/util/RichTextRules.ts
+++ b/src/client/util/RichTextRules.ts
@@ -78,8 +78,7 @@ export const inpRules = {
                 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;
+                return node ? state.tr.deleteRange(start, end).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
             }),
         new InputRule(
             new RegExp(/^\[\[\s$/),
@@ -91,8 +90,7 @@ export const inpRules = {
                 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;
+                return node ? state.tr.deleteRange(start, end).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
             }),
         new InputRule(
             new RegExp(/^\]\]\s$/),
@@ -104,8 +102,7 @@ export const inpRules = {
                 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;
+                return node ? state.tr.deleteRange(start, end).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
             }),
         new InputRule(
             new RegExp(/\^f\s$/),
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index d0464bd5f..f9ee22f61 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -30,7 +30,7 @@ export default class KeyManager {
     }
 
     public handle = async (e: KeyboardEvent) => {
-        let keyname = e.key.toLowerCase();
+        let keyname = e.key && e.key.toLowerCase();
         this.handleGreedy(keyname);
 
         if (modifiers.includes(keyname)) {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 24be5963f..24f2a60a9 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, PromiseValue } from "../../../../new_fields/Types";
+import { BoolCast, Cast, FieldValue, NumCast, StrCast, PromiseValue, DateCast } from "../../../../new_fields/Types";
 import { emptyFunction, returnEmptyString, returnOne, Utils } from "../../../../Utils";
 import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
 import { Docs } from "../../../documents/Documents";
@@ -257,12 +257,15 @@ 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 heading = this.childDocs.reduce((maxHeading, doc) => NumCast(doc.heading) > maxHeading ? NumCast(doc.heading) : maxHeading, 0);
+        heading = heading === 0 || this.childDocs.length === 0 ? 1 : heading === 1 ? 2 : 0;
+        if (heading === 0) {
+            let sorted = this.childDocs.filter(d => d.type === DocumentType.TEXT && d.data_ext instanceof Doc && d.data_ext.lastModified).sort((a, b) => DateCast((Cast(a.data_ext, Doc) as Doc).lastModified).date > DateCast((Cast(b.data_ext, Doc) as Doc).lastModified).date ? 1 :
+                DateCast((Cast(a.data_ext, Doc) as Doc).lastModified).date < DateCast((Cast(b.data_ext, Doc) as Doc).lastModified).date ? -1 : 0);
+            heading = NumCast(sorted[sorted.length - 1].heading) === 1 ? 2 : NumCast(sorted[sorted.length - 1].heading);
         }
+        newBox.heading = heading;
+
         PromiseValue(Cast(this.props.Document.ruleProvider, Doc)).then(ruleProvider => {
             if (!ruleProvider) ruleProvider = this.props.Document;
             // saturation shift
diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss
index d7ac7a9c5..0d7277cbe 100644
--- a/src/client/views/nodes/FormattedTextBox.scss
+++ b/src/client/views/nodes/FormattedTextBox.scss
@@ -4,7 +4,6 @@
     width: 100%;
     height: 100%;
     min-height: 100%;
-    font-family: $serif;
 }
 
 .ProseMirror:focus {
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 0ea36cdc2..444b91b28 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -39,7 +39,6 @@ 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);
@@ -79,15 +78,19 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     private _linkClicked = "";
     private _nodeClicked: any;
     private _undoTyping?: UndoManager.Batch;
-    private _reactionDisposer: Opt<IReactionDisposer>;
     private _searchReactionDisposer?: Lambda;
+    private _reactionDisposer: Opt<IReactionDisposer>;
     private _textReactionDisposer: Opt<IReactionDisposer>;
     private _heightReactionDisposer: Opt<IReactionDisposer>;
+    private _rulesReactionDisposer: Opt<IReactionDisposer>;
     private _proxyReactionDisposer: Opt<IReactionDisposer>;
     private _pullReactionDisposer: Opt<IReactionDisposer>;
     private _pushReactionDisposer: Opt<IReactionDisposer>;
     private dropDisposer?: DragManager.DragDropDisposer;
 
+    @observable private _fontSize = 13;
+    @observable private _fontFamily = "Arial";
+    @observable private _fontAlign = "";
     @observable private _entered = false;
     @observable public static InputBoxOverlay?: FormattedTextBox = undefined;
     public static SelectOnLoad = "";
@@ -119,14 +122,13 @@ 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) {
+        let view = this._editorView!;
+        if (view.state.selection.from === view.state.selection.to) return false;
+        if (view.state.selection.to - view.state.selection.from > view.state.doc.nodeSize - 3) {
             this.props.Document.color = color;
         }
-        let colorMark = this._editorView!.state.schema.mark(this._editorView!.state.schema.marks.pFontColor, { color: color });
-        this._editorView!.dispatch(this._editorView!.state.tr.addMark(this._editorView!.state.selection.from,
-            this._editorView!.state.selection.to, colorMark));
+        let colorMark = view.state.schema.mark(view.state.schema.marks.pFontColor, { color: color });
+        view.dispatch(view.state.tr.addMark(view.state.selection.from, view.state.selection.to, colorMark));
         return true;
     }
 
@@ -253,12 +255,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                     }
                 }
             });
-            //     const fieldkey = 'search_string';
-            //     if (Object.keys(this.props.Document).indexOf(fieldkey) !== -1) {
-            //         this.props.Document[fieldkey] = undefined;
-            //     }
-            //     else this.props.Document.proto![fieldkey] = undefined;
-            // }
         }
     }
     setAnnotation = (start: number, end: number, mark: Mark, opened: boolean, keep: boolean = false) => {
@@ -376,6 +372,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     }
 
     componentDidMount() {
+
         if (!this.props.isOverlay) {
             this._proxyReactionDisposer = reaction(() => this.props.isSelected(),
                 () => {
@@ -458,6 +455,39 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 this.unhighlightSearchTerms();
             }
         }, { fireImmediately: true });
+
+
+        this._rulesReactionDisposer = reaction(() => {
+            let ruleProvider = Cast(this.props.Document.ruleProvider, Doc);
+            let heading = NumCast(this.props.Document.heading);
+            if (ruleProvider instanceof Doc) {
+                return {
+                    align: StrCast(ruleProvider["ruleAlign_" + heading], ""),
+                    font: StrCast(ruleProvider["ruleFont_" + heading], "Arial"),
+                    size: NumCast(ruleProvider["ruleSize_" + heading], 13)
+                };
+            }
+            return { align: "", font: "Arial", size: 13 };
+        },
+            action((rules: any) => {
+                this._fontFamily = rules.font;
+                this._fontSize = rules.size;
+                setTimeout(() => {
+                    let tr = this._editorView!.state.tr;
+                    let n = new NodeSelection(this._editorView!.state.doc.resolve(0));
+                    if (this._editorView!.state.doc.textContent === "") {
+                        tr = tr.setSelection(new TextSelection(tr.doc.resolve(0), tr.doc.resolve(2))).
+                            replaceSelectionWith(this._editorView!.state.schema.nodes.paragraph.create({ align: rules.align }), true).
+                            setSelection(new TextSelection(tr.doc.resolve(0), tr.doc.resolve(0)));
+                    } else if (n.node && n.node.type === this._editorView!.state.schema.nodes.paragraph) {
+                        tr = tr.setNodeMarkup(0, n.node.type, { ...n.node.attrs, align: rules.align });
+                    }
+                    this._editorView!.dispatch(tr);
+                    this.tryUpdateHeight();
+                }, 0);
+            }), { fireImmediately: true }
+        );
+
         setTimeout(() => this.tryUpdateHeight(), 0);
     }
 
@@ -665,27 +695,6 @@ 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 && 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) {
@@ -701,6 +710,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     }
 
     componentWillUnmount() {
+        this._rulesReactionDisposer && this._rulesReactionDisposer();
         this._reactionDisposer && this._reactionDisposer();
         this._proxyReactionDisposer && this._proxyReactionDisposer();
         this._textReactionDisposer && this._textReactionDisposer();
@@ -866,7 +876,7 @@ 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.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() }));
 
         if (!this._undoTyping) {
             this._undoTyping = UndoManager.StartBatch("undoTyping");
@@ -885,7 +895,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         }
     }
 
-
     render() {
         let style = this.props.isOverlay ? "scroll" : "hidden";
         let rounded = StrCast(this.props.Document.borderRounding) === "100%" ? "-rounded" : "";
@@ -901,7 +910,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                     opacity: this.props.hideOnLeave ? (this._entered || this.props.isSelected() || Doc.IsBrushed(this.props.Document) ? 1 : 0.1) : 1,
                     color: this.props.color ? this.props.color : this.props.hideOnLeave ? "white" : "inherit",
                     pointerEvents: interactive,
-                    fontSize: "13px"
+                    fontSize: this._fontSize,
+                    fontFamily: this._fontFamily,
                 }}
                 onKeyDown={this.onKeyPress}
                 onFocus={this.onFocused}
-- 
cgit v1.2.3-70-g09d2


From 47ca25c49d7d9f1fee22b256f86e296dac42b47b Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 12 Sep 2019 21:42:07 -0400
Subject: cleaned up rulerProvider a bit and added menu item to turn it on.

---
 src/client/views/DocumentDecorations.tsx           | 13 +++++----
 src/client/views/InkingControl.tsx                 |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 31 +++++++---------------
 .../collections/collectionFreeForm/MarqueeView.tsx |  2 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  3 +++
 src/client/views/nodes/DocumentView.tsx            | 12 ++++++---
 src/client/views/nodes/FormattedTextBox.tsx        |  4 +--
 7 files changed, 31 insertions(+), 36 deletions(-)

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

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 6451fdf5e..4ab2ade8e 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -424,23 +424,22 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
             document.addEventListener("pointermove", this.onRadiusMove);
             document.addEventListener("pointerup", this.onRadiusUp);
         }
-        if (!this._isMoving) {
-            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 = "0%");
-        }
     }
 
     onRadiusMove = (e: PointerEvent): void => {
         this._isMoving = true;
         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)}%`);
+        dist = dist < 3 ? 0 : dist;
+        let usingRule = false;
         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));
+            ruleProvider && heading && (Doc.GetProto(ruleProvider)["ruleRounding_" + heading] = `${Math.min(100, dist)}%`);
+            usingRule = usingRule || (ruleProvider && heading ? true : false);
         })
+        !usingRule && 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)}%`);
         e.stopPropagation();
         e.preventDefault();
     }
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index aa573f16b..867735c0b 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -73,7 +73,7 @@ export class InkingControl extends React.Component {
                     let cv = view.props.ContainingCollectionView;
                     let ruleProvider = cv && (Cast(cv.props.Document.ruleProvider, Doc) as Doc);
                     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));
+                    cv && parback && (Doc.GetProto(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);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 24f2a60a9..9a8ae3535 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -262,27 +262,14 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         if (heading === 0) {
             let sorted = this.childDocs.filter(d => d.type === DocumentType.TEXT && d.data_ext instanceof Doc && d.data_ext.lastModified).sort((a, b) => DateCast((Cast(a.data_ext, Doc) as Doc).lastModified).date > DateCast((Cast(b.data_ext, Doc) as Doc).lastModified).date ? 1 :
                 DateCast((Cast(a.data_ext, Doc) as Doc).lastModified).date < DateCast((Cast(b.data_ext, Doc) as Doc).lastModified).date ? -1 : 0);
-            heading = NumCast(sorted[sorted.length - 1].heading) === 1 ? 2 : NumCast(sorted[sorted.length - 1].heading);
+            heading = !sorted.length ? 1 : NumCast(sorted[sorted.length - 1].heading) === 1 ? 2 : NumCast(sorted[sorted.length - 1].heading);
         }
         newBox.heading = heading;
 
-        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)]);
-            (newBox.backgroundColor === newBox.defaultBackgroundColor) && col && (Doc.GetProto(newBox).backgroundColor = col);
-
-            let round = StrCast(ruleProvider["ruleRounding_" + NumCast(newBox.heading)]);
-            round && (Doc.GetProto(newBox).borderRounding = round);
-            newBox.ruleProvider = ruleProvider;
-            this.addDocument(newBox, false);
-        });
+        if (Cast(this.props.Document.ruleProvider, Doc) as Doc) {
+            newBox.ruleProvider = Doc.GetProto(Cast(this.props.Document.ruleProvider, Doc) as Doc);
+        }
+        this.addDocument(newBox, false);
     }
     private addDocument = (newBox: Doc, allowDuplicates: boolean) => {
         this.props.addDocument(newBox, false);
@@ -898,7 +885,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     onContextMenu = (e: React.MouseEvent) => {
         let layoutItems: ContextMenuProps[] = [];
 
-        if (this.childDocs.some(d => d.isTemplate)) {
+        if (this.childDocs.some(d => BoolCast(d.isTemplate))) {
             layoutItems.push({ description: "Template Layout Instance", event: () => this.props.addDocTab && this.props.addDocTab(Doc.ApplyTemplate(this.props.Document)!, undefined, "onRight"), icon: "project-diagram" });
         }
         layoutItems.push({ description: "reset view", event: () => { this.props.Document.panX = this.props.Document.panY = 0; this.props.Document.scale = 1; }, icon: "compress-arrows-alt" });
@@ -913,9 +900,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             },
             icon: !this.props.Document.useClusters ? "braille" : "braille"
         });
-        this.props.Document.useClusters && layoutItems.push({
-            description: `${this.props.Document.clusterOverridesDefaultBackground ? "Use Default Backgrounds" : "Clusters Override Defaults"}`,
-            event: async () => this.props.Document.clusterOverridesDefaultBackground = !this.props.Document.clusterOverridesDefaultBackground,
+        layoutItems.push({
+            description: `${this.props.Document.isRuleProvider ? "Stop Auto Format" : "Auto Format"}`,
+            event: () => this.props.Document.isRuleProvider = !this.props.Document.isRuleProvider,
             icon: !this.props.Document.useClusters ? "chalkboard" : "chalkboard"
         });
         layoutItems.push({ description: "Arrange contents in grid", event: this.arrangeContents, icon: "table" });
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index fe48a3485..4308497a1 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -356,7 +356,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                 this.props.addLiveTextDocument(summary);
             }
             else {
-                newCollection.ruleProvider = this.props.container.props.Document;
+                newCollection.ruleProvider = this.props.container.props.Document.isRuleProvider ? this.props.container.props.Document : this.props.container.props.Document.ruleProvider;
                 this.props.addDocument(newCollection, false);
                 this.props.selectDocuments([newCollection]);
             }
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 07dd1cae7..082e5c5e3 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -74,7 +74,10 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     }
 
     borderRounding = () => {
+        let ruleProvider = this.props.Document.ruleProvider as Doc;
+        let ruleRounding = ruleProvider ? StrCast(Doc.GetProto(ruleProvider)["ruleRounding_" + NumCast(this.props.Document.heading)]) : undefined;
         let br = StrCast(this.layoutDoc.layout instanceof Doc ? this.layoutDoc.layout.borderRounding : this.props.Document.borderRounding);
+        br = !br && ruleRounding ? ruleRounding : br;
         if (br.endsWith("%")) {
             let percent = Number(br.substr(0, br.length - 1)) / 100;
             let nativeDim = Math.min(NumCast(this.layoutDoc.nativeWidth), NumCast(this.layoutDoc.nativeHeight));
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 3f0b62511..7b9ed12a7 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -793,9 +793,15 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
 
     render() {
-        let backgroundColor = this.layoutDoc.isBackground || (this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.Document.clusterOverridesDefaultBackground && this.layoutDoc.backgroundColor === this.layoutDoc.defaultBackgroundColor) ?
+        let ruleProvider = this.props.Document.ruleProvider as Doc;
+        let ruleColor = ruleProvider ? StrCast(Doc.GetProto(ruleProvider)["ruleColor_" + NumCast(this.props.Document.heading)]) : undefined;
+        let ruleRounding = ruleProvider ? StrCast(Doc.GetProto(ruleProvider)["ruleRounding_" + NumCast(this.props.Document.heading)]) : undefined;
+        let colorSet = this.layoutDoc.backgroundColor !== this.layoutDoc.defaultBackgroundColor;
+        let clusterCol = this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.Document.clusterOverridesDefaultBackground;
+
+        let backgroundColor = this.layoutDoc.isBackground || (clusterCol && !colorSet) ?
             this.props.backgroundColor(this.layoutDoc) || StrCast(this.layoutDoc.backgroundColor) :
-            StrCast(this.layoutDoc.backgroundColor) || this.props.backgroundColor(this.layoutDoc);
+            ruleColor && !colorSet ? ruleColor : StrCast(this.layoutDoc.backgroundColor) || this.props.backgroundColor(this.layoutDoc);
         let foregroundColor = StrCast(this.layoutDoc.color);
         var nativeWidth = this.nativeWidth > 0 && !BoolCast(this.props.Document.ignoreAspect) ? `${this.nativeWidth}px` : "100%";
         var nativeHeight = BoolCast(this.props.Document.ignoreAspect) ? this.props.PanelHeight() / this.props.ContentScaling() : this.nativeHeight > 0 ? `${this.nativeHeight}px` : "100%";
@@ -811,7 +817,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         }
         let showTextTitle = showTitle && StrCast(this.layoutDoc.layout).startsWith("<FormattedTextBox") ? showTitle : undefined;
         let fullDegree = Doc.isBrushedHighlightedDegree(this.props.Document);
-        let borderRounding = StrCast(Doc.GetProto(this.props.Document).borderRounding);
+        let borderRounding = StrCast(Doc.GetProto(this.props.Document).borderRounding, ruleRounding);
         let localScale = this.props.ScreenToLocalTransform().Scale * fullDegree;
         let searchHighlight = (!this.props.Document.search_fields ? (null) :
             <div key="search" style={{ position: "absolute", background: "yellow", bottom: "-20px", borderRadius: "5px", transformOrigin: "bottom left", width: `${100 * this.props.ContentScaling()}%`, transform: `scale(${1 / this.props.ContentScaling()})` }}>
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 444b91b28..658e2a04d 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -284,8 +284,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             e.stopPropagation();
         } else if (de.data instanceof DragManager.DocumentDragData) {
             const draggedDoc = de.data.draggedDocuments.length && de.data.draggedDocuments[0];
-            if (draggedDoc && draggedDoc.type === DocumentType.TEXT && StrCast(draggedDoc.layout) !== "") {
-                this.props.Document.layout = draggedDoc;
+            if (draggedDoc && draggedDoc.type === DocumentType.TEXT) {
+                this.props.Document.layout = draggedDoc.layout instanceof Doc ? draggedDoc.layout : draggedDoc;
                 draggedDoc.isTemplate = true;
                 e.stopPropagation();
             }
-- 
cgit v1.2.3-70-g09d2


From 26eed39d2fe140e6bfc3d572bd1aa4717ab52926 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 12 Sep 2019 22:04:03 -0400
Subject: fixed title/caption menu for templates.

---
 src/client/views/TemplateMenu.tsx           |  7 ++++---
 src/client/views/nodes/FormattedTextBox.tsx | 22 ++++++++++++----------
 2 files changed, 16 insertions(+), 13 deletions(-)

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

diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 0586b31e4..0ef1a137d 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -9,6 +9,7 @@ import './DocumentDecorations.scss';
 import { DocumentView } from "./nodes/DocumentView";
 import { Template, Templates } from "./Templates";
 import React = require("react");
+import { Doc } from "../../new_fields/Doc";
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -99,16 +100,16 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
     @action
     toggleTemplate = (event: React.ChangeEvent<HTMLInputElement>, template: Template): void => {
         if (event.target.checked) {
-            this.props.docs.map(d => d.props.Document["show" + template.Name] = template.Name.toLowerCase());
+            this.props.docs.map(d => Doc.GetProto(d.layoutDoc)["show" + template.Name] = template.Name.toLowerCase());
         } else {
-            this.props.docs.map(d => d.props.Document["show" + template.Name] = undefined);
+            this.props.docs.map(d => Doc.GetProto(d.layoutDoc)["show" + template.Name] = undefined);
         }
     }
 
     @undoBatch
     @action
     clearTemplates = (event: React.MouseEvent) => {
-        Templates.TemplateList.map(template => this.props.docs.map(d => d.props.Document["show" + template.Name] = false));
+        Templates.TemplateList.map(template => this.props.docs.map(d => d.layoutDoc["show" + template.Name] = false));
     }
 
     @action
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 658e2a04d..c07461e13 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -473,17 +473,19 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 this._fontFamily = rules.font;
                 this._fontSize = rules.size;
                 setTimeout(() => {
-                    let tr = this._editorView!.state.tr;
-                    let n = new NodeSelection(this._editorView!.state.doc.resolve(0));
-                    if (this._editorView!.state.doc.textContent === "") {
-                        tr = tr.setSelection(new TextSelection(tr.doc.resolve(0), tr.doc.resolve(2))).
-                            replaceSelectionWith(this._editorView!.state.schema.nodes.paragraph.create({ align: rules.align }), true).
-                            setSelection(new TextSelection(tr.doc.resolve(0), tr.doc.resolve(0)));
-                    } else if (n.node && n.node.type === this._editorView!.state.schema.nodes.paragraph) {
-                        tr = tr.setNodeMarkup(0, n.node.type, { ...n.node.attrs, align: rules.align });
+                    if (this._editorView!.state.doc.childCount) {
+                        let tr = this._editorView!.state.tr;
+                        let n = new NodeSelection(this._editorView!.state.doc.resolve(0));
+                        if (this._editorView!.state.doc.textContent === "") {
+                            tr = tr.setSelection(new TextSelection(tr.doc.resolve(0), tr.doc.resolve(2))).
+                                replaceSelectionWith(this._editorView!.state.schema.nodes.paragraph.create({ align: rules.align }), true).
+                                setSelection(new TextSelection(tr.doc.resolve(0), tr.doc.resolve(0)));
+                        } else if (n.node && n.node.type === this._editorView!.state.schema.nodes.paragraph) {
+                            tr = tr.setNodeMarkup(0, n.node.type, { ...n.node.attrs, align: rules.align });
+                        }
+                        this._editorView!.dispatch(tr);
+                        this.tryUpdateHeight();
                     }
-                    this._editorView!.dispatch(tr);
-                    this.tryUpdateHeight();
                 }, 0);
             }), { fireImmediately: true }
         );
-- 
cgit v1.2.3-70-g09d2


From e241e61d6521ff5d63de1292f2b4269493f5d7cc Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 13 Sep 2019 00:55:39 -0400
Subject: added "publish" option to convert a document to a recognizable id

---
 src/client/documents/Documents.ts           | 28 +++++++++++++++++++++++++++-
 src/client/views/DocumentDecorations.tsx    |  9 +++++++--
 src/client/views/nodes/DocumentView.tsx     |  3 ++-
 src/client/views/nodes/FormattedTextBox.tsx |  4 ++--
 src/new_fields/Doc.ts                       |  8 ++++----
 5 files changed, 42 insertions(+), 10 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 602a7f9ad..28e5e5f40 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -20,7 +20,7 @@ import { AttributeTransformationModel } from "../northstar/core/attribute/Attrib
 import { AggregateFunction } from "../northstar/model/idea/idea";
 import { MINIMIZED_ICON_SIZE } from "../views/globalCssVariables.scss";
 import { IconBox } from "../views/nodes/IconBox";
-import { Field, Doc, Opt } from "../../new_fields/Doc";
+import { Field, Doc, Opt, DocListCastAsync } from "../../new_fields/Doc";
 import { OmitKeys, JSONUtils } from "../../Utils";
 import { ImageField, VideoField, AudioField, PdfField, WebField, YoutubeField } from "../../new_fields/URLField";
 import { HtmlField } from "../../new_fields/HtmlField";
@@ -607,6 +607,32 @@ export namespace Docs {
 
 export namespace DocUtils {
 
+    export function Publish(promoteDoc: Doc, targetID: string, addDoc: any, remDoc: any) {
+        if (targetID.startsWith("-")) {
+            targetID = targetID.substr(1, targetID.length - 1);
+            Doc.GetProto(promoteDoc).title = targetID;
+        }
+        DocServer.GetRefField(targetID).then(doc => {
+            let copy = doc instanceof Doc ? doc : Doc.MakeCopy(promoteDoc, true, targetID);
+            !doc && (Doc.GetProto(copy).title = targetID);
+            addDoc && addDoc(copy);
+            !doc && remDoc && remDoc(promoteDoc);
+            if (!doc) {
+                DocListCastAsync(promoteDoc.links).then(links => {
+                    links && links.map(async link => {
+                        if (link) {
+                            let a1 = await Cast(link.anchor1, Doc);
+                            if (a1 && Doc.AreProtosEqual(a1, promoteDoc)) link.anchor1 = copy;
+                            let a2 = await Cast(link.anchor2, Doc);
+                            if (a2 && Doc.AreProtosEqual(a2, promoteDoc)) link.anchor2 = copy;
+                            LinkManager.Instance.deleteLink(link);
+                            LinkManager.Instance.addLink(link);
+                        }
+                    })
+                })
+            }
+        });
+    }
     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);
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 4ab2ade8e..589d69264 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -3,12 +3,12 @@ import { faLink, faTag, faTimes, faArrowAltCircleDown, faArrowAltCircleUp, faChe
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { action, computed, observable, reaction, runInAction, trace } from "mobx";
 import { observer } from "mobx-react";
-import { Doc } from "../../new_fields/Doc";
+import { Doc, DocListCastAsync } from "../../new_fields/Doc";
 import { List } from "../../new_fields/List";
 import { BoolCast, Cast, NumCast, StrCast } from "../../new_fields/Types";
 import { URLField } from '../../new_fields/URLField';
 import { emptyFunction, Utils } from "../../Utils";
-import { Docs } from "../documents/Documents";
+import { Docs, DocUtils } from "../documents/Documents";
 import { DocumentManager } from "../util/DocumentManager";
 import { DragLinksAsDocuments, DragManager } from "../util/DragManager";
 import { SelectionManager } from "../util/SelectionManager";
@@ -31,6 +31,7 @@ import { ImageBox } from './nodes/ImageBox';
 import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils';
 import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils';
 import { ObjectField } from '../../new_fields/ObjectField';
+import { DocServer } from '../DocServer';
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -142,6 +143,10 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
             if (text[0] === '#') {
                 this._fieldKey = text.slice(1, text.length);
                 this._title = this.selectionTitle;
+            } else if (text.startsWith("::")) {
+                let targetID = text.slice(2, text.length);
+                let promoteDoc = SelectionManager.SelectedDocuments()[0];
+                DocUtils.Publish(promoteDoc.props.Document, targetID, promoteDoc.props.addDocument, promoteDoc.props.removeDocument);
             } else if (text.startsWith(">")) {
                 let fieldTemplateView = SelectionManager.SelectedDocuments()[0];
                 SelectionManager.DeselectAll();
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 7b9ed12a7..44e9b3180 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -438,7 +438,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
     @undoBatch
     makeNativeViewClicked = (): void => {
-        (this.dataDoc || Doc.GetProto(this.props.Document)).customLayout = this.props.Document.layout;
         this.props.Document.layout = this.props.Document.nativeLayout;
         this.props.Document.type = this.props.Document.nativeType;
         this.props.Document.nativeWidth = this.props.Document.nativeNativeWidth;
@@ -473,6 +472,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 Doc.MakeTemplate(fieldTemplate, metaKey, proto);
 
                 Doc.ApplyTemplateTo(docTemplate, this.props.Document, undefined, false);
+                Doc.GetProto(this.dataDoc || this.props.Document).customLayout = this.props.Document.layout;
             }
         });
     }
@@ -690,6 +690,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             }
         });
 
+        cm.addItem({ description: "Publish", event: () => DocUtils.Publish(this.props.Document, StrCast(this.props.Document.title), this.props.addDocument, this.props.removeDocument), icon: "file" });
         cm.addItem({ description: "Delete", event: this.deleteClicked, icon: "trash" });
         type User = { email: string, userDocumentId: string };
         let usersMenu: ContextMenuProps[] = [];
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index c07461e13..04d24fe8c 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -293,7 +293,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     }
 
     recordKeyHandler = (e: KeyboardEvent) => {
-        if (this.props.Document === SelectionManager.SelectedDocuments()[0].props.Document) {
+        if (SelectionManager.SelectedDocuments().length && this.props.Document === SelectionManager.SelectedDocuments()[0].props.Document) {
             if (e.key === "R" && e.altKey) {
                 e.stopPropagation();
                 e.preventDefault();
@@ -473,7 +473,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 this._fontFamily = rules.font;
                 this._fontSize = rules.size;
                 setTimeout(() => {
-                    if (this._editorView!.state.doc.childCount) {
+                    if (this._editorView!.state.doc.childCount && this._proseRef) {
                         let tr = this._editorView!.state.tr;
                         let n = new NodeSelection(this._editorView!.state.doc.resolve(0));
                         if (this._editorView!.state.doc.textContent === "") {
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index e94b9f1eb..29925feb8 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -475,13 +475,13 @@ export namespace Doc {
         return { layout: layoutDoc, data: resolvedDataDoc };
     }
 
-    export function MakeCopy(doc: Doc, copyProto: boolean = false): Doc {
-        const copy = new Doc;
+    export function MakeCopy(doc: Doc, copyProto: boolean = false, copyProtoId?: string): Doc {
+        const copy = new Doc(copyProtoId, true);
         Object.keys(doc).forEach(key => {
             const field = ProxyField.WithoutProxy(() => doc[key]);
             if (key === "proto" && copyProto) {
-                if (field instanceof Doc) {
-                    copy[key] = Doc.MakeCopy(field);
+                if (doc[key] instanceof Doc) {
+                    copy[key] = Doc.MakeCopy(doc[key]!, false);
                 }
             } else {
                 if (field instanceof RefField) {
-- 
cgit v1.2.3-70-g09d2


From b3d9c4e3d8c7c425df41b2d8555a7d242771a823 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Fri, 13 Sep 2019 09:06:51 -0400
Subject: small fixes to text editing

---
 src/client/util/RichTextRules.ts            |  4 +++-
 src/client/views/nodes/FormattedTextBox.tsx | 28 ++++++++++++++--------------
 2 files changed, 17 insertions(+), 15 deletions(-)

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

diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts
index 7e3d435a7..8ceb56f2f 100644
--- a/src/client/util/RichTextRules.ts
+++ b/src/client/util/RichTextRules.ts
@@ -77,8 +77,10 @@ export const inpRules = {
                 let heading = NumCast(FormattedTextBox.InputBoxOverlay!.props.Document.heading);
                 if (ruleProvider && heading) {
                     ruleProvider["ruleAlign_" + heading] = "center";
+                    return node ? state.tr.deleteRange(start, end).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
                 }
-                return node ? state.tr.deleteRange(start, end).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
+                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$/),
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 444b91b28..6020ad583 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -467,23 +467,23 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                     size: NumCast(ruleProvider["ruleSize_" + heading], 13)
                 };
             }
-            return { align: "", font: "Arial", size: 13 };
+            return undefined;
         },
             action((rules: any) => {
-                this._fontFamily = rules.font;
-                this._fontSize = rules.size;
-                setTimeout(() => {
-                    let tr = this._editorView!.state.tr;
-                    let n = new NodeSelection(this._editorView!.state.doc.resolve(0));
-                    if (this._editorView!.state.doc.textContent === "") {
-                        tr = tr.setSelection(new TextSelection(tr.doc.resolve(0), tr.doc.resolve(2))).
-                            replaceSelectionWith(this._editorView!.state.schema.nodes.paragraph.create({ align: rules.align }), true).
-                            setSelection(new TextSelection(tr.doc.resolve(0), tr.doc.resolve(0)));
-                    } else if (n.node && n.node.type === this._editorView!.state.schema.nodes.paragraph) {
-                        tr = tr.setNodeMarkup(0, n.node.type, { ...n.node.attrs, align: rules.align });
+                this._fontFamily = rules ? rules.font : "Arial";
+                this._fontSize = rules ? rules.size : 13;
+                rules && setTimeout(() => {
+                    const view = this._editorView!;
+                    if (this._proseRef) {
+                        let n = new NodeSelection(view.state.doc.resolve(0));
+                        if (this._editorView!.state.doc.textContent === "") {
+                            view.dispatch(view.state.tr.setSelection(new TextSelection(view.state.doc.resolve(0), view.state.doc.resolve(2))).
+                                replaceSelectionWith(this._editorView!.state.schema.nodes.paragraph.create({ align: rules.align }), true));
+                        } else if (n.node && n.node.type === view.state.schema.nodes.paragraph) {
+                            view.dispatch(view.state.tr.setNodeMarkup(0, n.node.type, { ...n.node.attrs, align: rules.align }));
+                        }
+                        this.tryUpdateHeight();
                     }
-                    this._editorView!.dispatch(tr);
-                    this.tryUpdateHeight();
                 }, 0);
             }), { fireImmediately: true }
         );
-- 
cgit v1.2.3-70-g09d2


From f508d5987e91e8297258905d8e8c9dfc405c50e9 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Fri, 13 Sep 2019 12:30:20 -0400
Subject: changed link following to follow links that aren't shown that don't
 have an anchor first.  changed text pointerevents when its a button.

---
 src/client/documents/Documents.ts           | 3 ++-
 src/client/views/nodes/DocumentView.tsx     | 4 +++-
 src/client/views/nodes/FormattedTextBox.tsx | 9 +++++----
 3 files changed, 10 insertions(+), 6 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 9db2ac558..2eff73b87 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -630,7 +630,7 @@ export namespace DocUtils {
             }
         });
     }
-    export function MakeLink(source: Doc, target: Doc, targetContext?: Doc, title: string = "", description: string = "", sourceContext?: Doc, id?: string) {
+    export function MakeLink(source: Doc, target: Doc, targetContext?: Doc, title: string = "", description: string = "", sourceContext?: Doc, id?: string, anchored1?: boolean) {
         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;
@@ -649,6 +649,7 @@ export namespace DocUtils {
             linkDocProto.anchor1 = source;
             linkDocProto.anchor1Page = source.curPage;
             linkDocProto.anchor1Groups = new List<Doc>([]);
+            linkDocProto.anchor1anchored = anchored1;
             linkDocProto.anchor2 = target;
             linkDocProto.anchor2Page = target.curPage;
             linkDocProto.anchor2Groups = new List<Doc>([]);
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 0a1367b56..0816cb813 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -354,7 +354,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 }
                 else if (linkedDocs.length) {
                     SelectionManager.DeselectAll();
-                    let first = linkedDocs.filter(d => Doc.AreProtosEqual(d.anchor1 as Doc, this.props.Document));
+                    let first = linkedDocs.filter(d => Doc.AreProtosEqual(d.anchor1 as Doc, this.props.Document) && !d.anchor1anchored);
+                    let firstUnshown = first.filter(d => DocumentManager.Instance.getDocumentViews(d.anchor2 as Doc).length === 0);
+                    if (firstUnshown.length) first = [firstUnshown[0]];
                     let linkedFwdDocs = first.length ? [first[0].anchor2 as Doc, first[0].anchor1 as Doc] : [expandedDocs[0], expandedDocs[0]];
 
                     // @TODO: shouldn't always follow target context
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index b2d44f14b..d39291743 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -189,7 +189,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                         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);
+                            else DocUtils.MakeLink(this.dataDoc, this.dataDoc[key] as Doc, undefined, "Ref:" + value, undefined, undefined, id, true);
                         })
                     });
                     const link = this._editorView!.state.schema.marks.link.create({ href: `http://localhost:1050/doc/${id}`, location: "onRight", title: value });
@@ -898,8 +898,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     render() {
         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 ||
-            (this.props.Document.isButton && !this.props.isSelected()) ? "none" : "all";
+        let interactive: "all" | "none" = InkingControl.Instance.selectedTool || this.props.Document.isBackground
+            //||  (this.props.Document.isButton && !this.props.isSelected()) 
+            ? "none" : "all";
         Doc.UpdateDocumentExtensionForField(this.dataDoc, this.props.fieldKey);
         return (
             <div className={`formattedTextBox-cont-${style}`} ref={this._ref}
@@ -924,7 +925,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 onPointerEnter={action(() => this._entered = true)}
                 onPointerLeave={action(() => this._entered = false)}
             >
-                <div className={`formattedTextBox-inner${rounded}`} ref={this.createDropTarget} style={{ whiteSpace: "pre-wrap" }} />
+                <div className={`formattedTextBox-inner${rounded}`} style={{ whiteSpace: "pre-wrap", pointerEvents: (this.props.Document.isButton && !this.props.isSelected()) ? "none" : undefined }} ref={this.createDropTarget} />
             </div>
         );
     }
-- 
cgit v1.2.3-70-g09d2


From 233893698083cbcfcf39ddad8b57049aeb1ba842 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Fri, 13 Sep 2019 14:18:55 -0400
Subject: refactored how ruleProvider's work.   overloaded custom template  for
 creating metadata fields

---
 src/client/util/RichTextRules.ts                   |  8 +++----
 src/client/util/TooltipTextMenu.tsx                |  4 ++--
 src/client/views/DocumentDecorations.tsx           | 11 ++-------
 src/client/views/InkingControl.tsx                 |  5 ++--
 src/client/views/MainOverlayTextBox.tsx            |  2 +-
 src/client/views/MainView.tsx                      |  2 ++
 src/client/views/TemplateMenu.tsx                  | 16 ++++++-------
 .../views/collections/CollectionDockingView.tsx    |  1 +
 .../views/collections/CollectionSchemaView.tsx     |  1 +
 src/client/views/collections/CollectionSubView.tsx |  1 +
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 27 +++++++++++-----------
 .../collections/collectionFreeForm/MarqueeView.tsx |  2 --
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  2 +-
 src/client/views/nodes/DocumentView.tsx            | 13 +++++++----
 src/client/views/nodes/FieldView.tsx               |  1 +
 src/client/views/nodes/FormattedTextBox.tsx        |  2 +-
 .../views/presentationview/PresentationElement.tsx |  1 +
 src/client/views/search/SearchItem.tsx             |  1 +
 src/new_fields/Doc.ts                              |  2 +-
 19 files changed, 51 insertions(+), 51 deletions(-)

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

diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts
index c0c62463a..c727eec73 100644
--- a/src/client/util/RichTextRules.ts
+++ b/src/client/util/RichTextRules.ts
@@ -61,7 +61,7 @@ export const inpRules = {
             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 ruleProvider = FormattedTextBox.InputBoxOverlay!.props.ruleProvider;
                 let heading = NumCast(FormattedTextBox.InputBoxOverlay!.props.Document.heading);
                 if (ruleProvider && heading) {
                     (Cast(FormattedTextBox.InputBoxOverlay!.props.Document, Doc) as Doc).heading = Number(match[1]);
@@ -74,7 +74,7 @@ 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 ruleProvider = FormattedTextBox.InputBoxOverlay!.props.ruleProvider;
                 let heading = NumCast(FormattedTextBox.InputBoxOverlay!.props.Document.heading);
                 if (ruleProvider && heading) {
                     ruleProvider["ruleAlign_" + heading] = "center";
@@ -88,7 +88,7 @@ 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 ruleProvider = FormattedTextBox.InputBoxOverlay!.props.ruleProvider;
                 let heading = NumCast(FormattedTextBox.InputBoxOverlay!.props.Document.heading);
                 if (ruleProvider && heading) {
                     ruleProvider["ruleAlign_" + heading] = "left";
@@ -100,7 +100,7 @@ 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 ruleProvider = FormattedTextBox.InputBoxOverlay!.props.ruleProvider;
                 let heading = NumCast(FormattedTextBox.InputBoxOverlay!.props.Document.heading);
                 if (ruleProvider && heading) {
                     ruleProvider["ruleAlign_" + heading] = "right";
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index c376b6f86..84d045e6f 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -496,7 +496,7 @@ 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 ruleProvider = this.editorProps.ruleProvider;
                 let heading = NumCast(this.editorProps.Document.heading);
                 if (ruleProvider && heading) {
                     ruleProvider["ruleSize_" + heading] = size;
@@ -505,7 +505,7 @@ export class TooltipTextMenu {
             else {
                 let fontName = this.fontStylesToName.get(markType);
                 if (fontName) { this.updateFontStyleDropdown(fontName); }
-                let ruleProvider = Cast(this.editorProps.Document.ruleProvider, Doc) as Doc;
+                let ruleProvider = this.editorProps.ruleProvider;
                 let heading = NumCast(this.editorProps.Document.heading);
                 if (ruleProvider && heading) {
                     ruleProvider["ruleFont_" + heading] = fontName;
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 6d63e8f73..ebdf2a749 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -365,14 +365,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                     Math.abs(e.pageY - this._downY) < Utils.DRAG_THRESHOLD) {
                     let docViews = SelectionManager.ViewsSortedVertically();
                     let topDocView = docViews[0];
-                    let ind = topDocView.templates.indexOf(Templates.Bullet.Layout);
-                    if (ind !== -1) {
-                        topDocView.templates.splice(ind, 1);
-                        topDocView.props.Document.subBulletDocs = undefined;
-                    } else {
-                        topDocView.addTemplate(Templates.Bullet);
-                        topDocView.props.Document.subBulletDocs = new List<Doc>(docViews.filter(v => v !== topDocView).map(v => v.props.Document.proto!));
-                    }
+                    topDocView.props.Document.subBulletDocs = new List<Doc>(docViews.filter(v => v !== topDocView).map(v => v.props.Document.proto!));
                 }
             }
             this._removeIcon = false;
@@ -439,7 +432,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         let usingRule = false;
         SelectionManager.SelectedDocuments().map(dv => {
             let cv = dv.props.ContainingCollectionView;
-            let ruleProvider = cv && (Cast(cv.props.Document.ruleProvider, Doc) as Doc);
+            let ruleProvider = cv && cv.props.ruleProvider;
             let heading = NumCast(dv.props.Document.heading);
             ruleProvider && heading && (Doc.GetProto(ruleProvider)["ruleRounding_" + heading] = `${Math.min(100, dist)}%`);
             usingRule = usingRule || (ruleProvider && heading ? true : false);
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index 867735c0b..86d0fc0be 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -71,9 +71,8 @@ export class InkingControl extends React.Component {
                     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);
-                    let parback = cv && StrCast(cv.props.Document.backgroundColor);
-                    cv && parback && (Doc.GetProto(ruleProvider ? ruleProvider : cv.props.Document)["ruleColor_" + NumCast(view.props.Document.heading)] = Utils.toRGBAstr(color.rgb));
+                    let ruleProvider = cv && (Cast(cv.props.ruleProvider, Doc) as Doc);
+                    cv && (Doc.GetProto(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);
diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx
index c3a2cb214..71fb2707d 100644
--- a/src/client/views/MainOverlayTextBox.tsx
+++ b/src/client/views/MainOverlayTextBox.tsx
@@ -72,7 +72,6 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
         if (this._textTargetDiv) {
             this._textTargetDiv.style.color = this._textColor;
         }
-        this._textAutoHeight = autoHeight;
         this.TextFieldKey = textFieldKey!;
         let txf = tx ? tx : () => Transform.Identity();
         this._textXf = txf;
@@ -143,6 +142,7 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
                                 Document={FormattedTextBox.InputBoxOverlay.props.Document}
                                 DataDoc={FormattedTextBox.InputBoxOverlay.props.DataDoc}
                                 onClick={undefined}
+                                ruleProvider={this._textBox ? this._textBox.props.ruleProvider : undefined}
                                 ChromeHeight={this.ChromeHeight}
                                 isSelected={returnTrue} select={emptyFunction} renderDepth={0}
                                 ContainingCollectionView={undefined} whenActiveChanged={emptyFunction} active={returnTrue} ContentScaling={returnOne}
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index b64986084..2cec1c052 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -322,6 +322,7 @@ export class MainView extends React.Component {
                             addDocTab={emptyFunction}
                             pinToPres={emptyFunction}
                             onClick={undefined}
+                            ruleProvider={undefined}
                             removeDocument={undefined}
                             ScreenToLocalTransform={Transform.Identity}
                             ContentScaling={returnOne}
@@ -385,6 +386,7 @@ export class MainView extends React.Component {
             addDocTab={this.addDocTabFunc}
             pinToPres={emptyFunction}
             removeDocument={undefined}
+            ruleProvider={undefined}
             onClick={undefined}
             ScreenToLocalTransform={Transform.Identity}
             ContentScaling={returnOne}
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 0ef1a137d..060191e29 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -51,16 +51,16 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
     @observable private _hidden: boolean = true;
     dragRef = React.createRef<HTMLUListElement>();
 
-    constructor(props: TemplateMenuProps) {
-        super(props);
-    }
-
     toggleCustom = (e: React.MouseEvent): void => {
         this.props.docs.map(dv => {
-            if (dv.Document.type !== DocumentType.COL && dv.Document.type !== DocumentType.TEMPLATE) {
-                dv.makeCustomViewClicked();
-            } else if (dv.Document.nativeLayout) {
-                dv.makeNativeViewClicked();
+            if (dv.props.ContainingCollectionView && dv.props.ContainingCollectionView.props.DataDoc) {
+                Doc.MakeMetadataFieldTemplate(dv.props.Document, dv.props.ContainingCollectionView.props.DataDoc)
+            } else {
+                if (dv.Document.type !== DocumentType.COL && dv.Document.type !== DocumentType.TEMPLATE) {
+                    dv.makeCustomViewClicked();
+                } else if (dv.Document.nativeLayout) {
+                    dv.makeNativeViewClicked();
+                }
             }
         });
     }
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index fb8b0c41b..166fa0811 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -631,6 +631,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
             bringToFront={emptyFunction}
             addDocument={undefined}
             removeDocument={undefined}
+            ruleProvider={undefined}
             ContentScaling={this.contentScaling}
             PanelWidth={this.panelWidth}
             PanelHeight={this.panelHeight}
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 9d83aa6c1..dca1d7c1d 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -995,6 +995,7 @@ export class CollectionSchemaPreview extends React.Component<CollectionSchemaPre
                         Document={this.props.Document}
                         fitToBox={this.props.fitToBox}
                         onClick={this.props.onClick}
+                        ruleProvider={undefined}
                         showOverlays={this.props.showOverlays}
                         addDocument={this.props.addDocument}
                         removeDocument={this.props.removeDocument}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 99e5ab7b3..270916522 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -34,6 +34,7 @@ export interface CollectionViewProps extends FieldViewProps {
 
 export interface SubCollectionViewProps extends CollectionViewProps {
     CollectionView: CollectionView | CollectionPDFView | CollectionVideoView;
+    ruleProvider: Doc | undefined;
 }
 
 export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 9a8ae3535..4a3e5039a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -257,18 +257,14 @@ 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
-        let heading = this.childDocs.reduce((maxHeading, doc) => NumCast(doc.heading) > maxHeading ? NumCast(doc.heading) : maxHeading, 0);
-        heading = heading === 0 || this.childDocs.length === 0 ? 1 : heading === 1 ? 2 : 0;
+        let maxHeading = this.childDocs.reduce((maxHeading, doc) => NumCast(doc.heading) > maxHeading ? NumCast(doc.heading) : maxHeading, 0);
+        let heading = maxHeading === 0 || this.childDocs.length === 0 ? 1 : maxHeading === 1 ? 2 : 0;
         if (heading === 0) {
             let sorted = this.childDocs.filter(d => d.type === DocumentType.TEXT && d.data_ext instanceof Doc && d.data_ext.lastModified).sort((a, b) => DateCast((Cast(a.data_ext, Doc) as Doc).lastModified).date > DateCast((Cast(b.data_ext, Doc) as Doc).lastModified).date ? 1 :
                 DateCast((Cast(a.data_ext, Doc) as Doc).lastModified).date < DateCast((Cast(b.data_ext, Doc) as Doc).lastModified).date ? -1 : 0);
-            heading = !sorted.length ? 1 : NumCast(sorted[sorted.length - 1].heading) === 1 ? 2 : NumCast(sorted[sorted.length - 1].heading);
-        }
-        newBox.heading = heading;
-
-        if (Cast(this.props.Document.ruleProvider, Doc) as Doc) {
-            newBox.ruleProvider = Doc.GetProto(Cast(this.props.Document.ruleProvider, Doc) as Doc);
+            heading = !sorted.length ? Math.max(1, maxHeading) : NumCast(sorted[sorted.length - 1].heading) === 1 ? 2 : NumCast(sorted[sorted.length - 1].heading);
         }
+        !this.props.Document.isRuleProvider && (newBox.heading = heading);
         this.addDocument(newBox, false);
     }
     private addDocument = (newBox: Doc, allowDuplicates: boolean) => {
@@ -698,6 +694,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             addDocument: this.props.addDocument,
             removeDocument: this.props.removeDocument,
             moveDocument: this.props.moveDocument,
+            ruleProvider: this.props.Document.isRuleProvider && childLayout.type !== DocumentType.TEXT ? this.props.Document : this.props.ruleProvider,
             onClick: this.props.onClick,
             ScreenToLocalTransform: childLayout.z ? this.getTransformOverlay : this.getTransform,
             renderDepth: this.props.renderDepth + 1,
@@ -723,6 +720,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             addDocument: this.props.addDocument,
             removeDocument: this.props.removeDocument,
             moveDocument: this.props.moveDocument,
+            ruleProvider: this.props.ruleProvider,
             onClick: this.props.onClick,
             ScreenToLocalTransform: this.getTransform,
             renderDepth: this.props.renderDepth,
@@ -817,6 +815,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                     if (pair.layout && !(pair.data instanceof Promise)) {
                         prev.push({
                             ele: <CollectionFreeFormDocumentView key={doc[Id]}
+                                ruleProvider={this.props.Document.isRuleProvider ? this.props.Document : this.props.ruleProvider}
                                 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)} />,
@@ -873,6 +872,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         }, "arrange contents");
     }
 
+    autoFormat = () => {
+        this.props.Document.isRuleProvider = !this.props.Document.isRuleProvider;
+        this.childDocs.map(child => child.heading = undefined);
+    }
+
     analyzeStrokes = async () => {
         let data = Cast(this.fieldExtensionDoc[this.inkKey], InkField);
         if (!data) {
@@ -900,11 +904,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             },
             icon: !this.props.Document.useClusters ? "braille" : "braille"
         });
-        layoutItems.push({
-            description: `${this.props.Document.isRuleProvider ? "Stop Auto Format" : "Auto Format"}`,
-            event: () => this.props.Document.isRuleProvider = !this.props.Document.isRuleProvider,
-            icon: !this.props.Document.useClusters ? "chalkboard" : "chalkboard"
-        });
+        layoutItems.push({ description: `${this.props.Document.isRuleProvider ? "Stop Auto Format" : "Auto Format"}`, event: this.autoFormat, icon: !this.props.Document.isRuleProvider ? "chalkboard" : "chalkboard" });
         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: "Jitter Rotation", event: action(() => this.props.Document.jitterRotation = 10), icon: "paint-brush" });
@@ -1034,7 +1034,6 @@ class CollectionFreeFormOverlayView extends React.Component<DocumentViewProps &
 @observer
 class CollectionFreeFormBackgroundView extends React.Component<DocumentViewProps & { isSelected: () => boolean }> {
     @computed get backgroundView() {
-        let props = this.props;
         return (<DocumentContentsView {...this.props} layoutKey={"backgroundLayout"}
             renderDepth={this.props.renderDepth} isSelected={this.props.isSelected} select={emptyFunction} />);
     }
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 4308497a1..e46e8cb88 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -329,7 +329,6 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                 selected = [newCollection];
                 newCollection.x = bounds.left + bounds.width;
                 summary.proto!.subBulletDocs = new List<Doc>(selected);
-                summary.templates = new List<string>([Templates.Bullet.Layout]);
                 let container = Docs.Create.FreeformDocument([summary, newCollection], { x: bounds.left, y: bounds.top, width: 300, height: 200, chromeStatus: "disabled", title: "-summary-" });
                 container.viewType = CollectionViewType.Stacking;
                 container.autoHeight = true;
@@ -356,7 +355,6 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                 this.props.addLiveTextDocument(summary);
             }
             else {
-                newCollection.ruleProvider = this.props.container.props.Document.isRuleProvider ? this.props.container.props.Document : this.props.container.props.Document.ruleProvider;
                 this.props.addDocument(newCollection, false);
                 this.props.selectDocuments([newCollection]);
             }
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 082e5c5e3..4872a7aa1 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -74,7 +74,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     }
 
     borderRounding = () => {
-        let ruleProvider = this.props.Document.ruleProvider as Doc;
+        let ruleProvider = this.props.ruleProvider;
         let ruleRounding = ruleProvider ? StrCast(Doc.GetProto(ruleProvider)["ruleRounding_" + NumCast(this.props.Document.heading)]) : undefined;
         let br = StrCast(this.layoutDoc.layout instanceof Doc ? this.layoutDoc.layout.borderRounding : this.props.Document.borderRounding);
         br = !br && ruleRounding ? ruleRounding : br;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 0816cb813..cc04c5a9f 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,6 +1,6 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
 import * as fa from '@fortawesome/free-solid-svg-icons';
-import { action, computed, IReactionDisposer, reaction, runInAction, trace, observable } from "mobx";
+import { action, computed, IReactionDisposer, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import * as rp from "request-promise";
 import { Doc, DocListCast, DocListCastAsync, HeightSym, Opt, WidthSym } from "../../../new_fields/Doc";
@@ -9,12 +9,13 @@ import { List } from "../../../new_fields/List";
 import { ObjectField } from "../../../new_fields/ObjectField";
 import { createSchema, listSpec, makeInterface } from "../../../new_fields/Schema";
 import { ScriptField } from '../../../new_fields/ScriptField';
-import { BoolCast, Cast, FieldValue, NumCast, StrCast, PromiseValue } from "../../../new_fields/Types";
+import { BoolCast, Cast, FieldValue, NumCast, PromiseValue, StrCast } from "../../../new_fields/Types";
 import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
 import { RouteStore } from '../../../server/RouteStore';
 import { emptyFunction, returnTrue, Utils } from "../../../Utils";
 import { DocServer } from "../../DocServer";
 import { Docs, DocUtils } from "../../documents/Documents";
+import { DocumentType } from '../../documents/DocumentTypes';
 import { ClientUtils } from '../../util/ClientUtils';
 import { DictationManager } from '../../util/DictationManager';
 import { DocumentManager } from "../../util/DocumentManager";
@@ -35,12 +36,10 @@ import { MainView } from '../MainView';
 import { OverlayView } from '../OverlayView';
 import { ScriptBox } from '../ScriptBox';
 import { ScriptingRepl } from '../ScriptingRepl';
-import { Template } from "./../Templates";
 import { DocumentContentsView } from "./DocumentContentsView";
 import "./DocumentView.scss";
 import { FormattedTextBox } from './FormattedTextBox';
 import React = require("react");
-import { DocumentType } from '../../documents/DocumentTypes';
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
 library.add(fa.faTrash);
@@ -89,6 +88,7 @@ export interface DocumentViewProps {
     renderDepth: number;
     showOverlays?: (doc: Doc) => { title?: string, caption?: string };
     ContentScaling: () => number;
+    ruleProvider: Doc | undefined;
     PanelWidth: () => number;
     PanelHeight: () => number;
     focus: (doc: Doc, willZoom: boolean, scale?: number) => void;
@@ -470,6 +470,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     this.props.Document.type === DocumentType.VID ? Docs.Create.VideoDocument("http://www.cs.brown.edu", options) :
                         Docs.Create.ImageDocument("http://www.cs.brown.edu", options);
 
+                fieldTemplate.backgroundColor = StrCast(this.props.Document.backgroundColor);
+                fieldTemplate.heading = 1;
+
                 let docTemplate = Docs.Create.FreeformDocument([fieldTemplate], { title: StrCast(this.Document.title) + "layout", width: NumCast(this.props.Document.width) + 20, height: Math.max(100, NumCast(this.props.Document.height) + 45) });
                 let proto = Doc.GetProto(docTemplate);
                 Doc.MakeMetadataFieldTemplate(fieldTemplate, proto);
@@ -800,7 +803,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
 
     render() {
-        let ruleProvider = this.props.Document.ruleProvider as Doc;
+        let ruleProvider = this.props.ruleProvider;
         let ruleColor = ruleProvider ? StrCast(Doc.GetProto(ruleProvider)["ruleColor_" + NumCast(this.props.Document.heading)]) : undefined;
         let ruleRounding = ruleProvider ? StrCast(Doc.GetProto(ruleProvider)["ruleRounding_" + NumCast(this.props.Document.heading)]) : undefined;
         let colorSet = this.layoutDoc.backgroundColor !== this.layoutDoc.defaultBackgroundColor;
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index d9774303b..943d181d6 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -30,6 +30,7 @@ export interface FieldViewProps {
     leaveNativeSize?: boolean;
     fitToBox?: boolean;
     ContainingCollectionView: Opt<CollectionView | CollectionPDFView | CollectionVideoView>;
+    ruleProvider: Doc | undefined;
     Document: Doc;
     DataDoc?: Doc;
     onClick?: ScriptField;
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index d39291743..a0dc054cf 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -458,7 +458,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
 
 
         this._rulesReactionDisposer = reaction(() => {
-            let ruleProvider = Cast(this.props.Document.ruleProvider, Doc);
+            let ruleProvider = this.props.ruleProvider;
             let heading = NumCast(this.props.Document.heading);
             if (ruleProvider instanceof Doc) {
                 return {
diff --git a/src/client/views/presentationview/PresentationElement.tsx b/src/client/views/presentationview/PresentationElement.tsx
index 80aa25f48..7be44faf6 100644
--- a/src/client/views/presentationview/PresentationElement.tsx
+++ b/src/client/views/presentationview/PresentationElement.tsx
@@ -351,6 +351,7 @@ export default class PresentationElement extends React.Component<PresentationEle
                     Document={this.props.document}
                     addDocument={returnFalse}
                     removeDocument={returnFalse}
+                    ruleProvider={undefined}
                     ScreenToLocalTransform={Transform.Identity}
                     addDocTab={returnFalse}
                     pinToPres={returnFalse}
diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx
index c56d093fa..cd3dd912c 100644
--- a/src/client/views/search/SearchItem.tsx
+++ b/src/client/views/search/SearchItem.tsx
@@ -165,6 +165,7 @@ export class SearchItem extends React.Component<SearchItemProps> {
                     Document={this.props.doc}
                     addDocument={returnFalse}
                     removeDocument={returnFalse}
+                    ruleProvider={undefined}
                     ScreenToLocalTransform={Transform.Identity}
                     addDocTab={returnFalse}
                     pinToPres={returnFalse}
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 1a3d689bb..5b22a62a1 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -560,7 +560,7 @@ export namespace Doc {
 
     export function MakeMetadataFieldTemplate(fieldTemplate: Doc, templateDataDoc: Doc) {
         // move data doc fields to layout doc as needed (nativeWidth/nativeHeight, data, ??)
-        let metadataFieldName = StrCast(fieldTemplate.title);
+        let metadataFieldName = StrCast(fieldTemplate.title).replace(/^-/, "");
         let backgroundLayout = StrCast(fieldTemplate.backgroundLayout);
         let fieldLayoutDoc = fieldTemplate;
         if (fieldTemplate.layout instanceof Doc) {
-- 
cgit v1.2.3-70-g09d2


From dd7679295b84ceba49b8d581bb64f97cc1a86fbb Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Fri, 13 Sep 2019 17:30:02 -0400
Subject: more rule provider fixes

---
 src/client/views/DocumentDecorations.tsx                        | 2 +-
 src/client/views/InkingControl.tsx                              | 8 +++++++-
 src/client/views/collections/CollectionSchemaCells.tsx          | 1 +
 src/client/views/collections/CollectionSchemaView.tsx           | 5 ++++-
 src/client/views/collections/CollectionStackingView.tsx         | 1 +
 src/client/views/collections/CollectionTreeView.tsx             | 3 +++
 src/client/views/collections/collectionFreeForm/MarqueeView.tsx | 2 +-
 src/client/views/nodes/DocumentView.tsx                         | 2 +-
 src/client/views/nodes/FormattedTextBox.tsx                     | 2 ++
 src/client/views/nodes/KeyValuePair.tsx                         | 1 +
 10 files changed, 22 insertions(+), 5 deletions(-)

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

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index ebdf2a749..ac103b2ea 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -848,7 +848,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         let templates: Map<Template, boolean> = new Map();
         Array.from(Object.values(Templates.TemplateList)).map(template => {
             let checked = false;
-            SelectionManager.SelectedDocuments().map(doc => checked = checked || (doc.props.Document["show" + template.Name] !== undefined));
+            SelectionManager.SelectedDocuments().map(doc => checked = checked || (doc.layoutDoc["show" + template.Name] !== undefined));
             templates.set(template, checked);
         });
 
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index 86d0fc0be..94cc1f06c 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -51,7 +51,13 @@ 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);
-                if (view.props.ContainingCollectionView && view.props.ContainingCollectionView.props.Document.colorPalette) {
+                if (view.props.ContainingCollectionView) {
+                    if (!view.props.ContainingCollectionView.props.Document.colorPalette) {
+                        let defaultPalette = ["rg14,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(view.props.ContainingCollectionView.props.Document.colorPalette, listSpec("string"));
+                        if (!colorPalette) view.props.ContainingCollectionView.props.Document.colorPalette = new List<string>(defaultPalette);
+                    }
                     let cp = Cast(view.props.ContainingCollectionView.props.Document.colorPalette, listSpec("string")) as string[];
                     let closest = 0;
                     let dist = 10000000;
diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx
index c59107b53..17a3f4f7c 100644
--- a/src/client/views/collections/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/CollectionSchemaCells.tsx
@@ -149,6 +149,7 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
             DataDoc: this.props.rowProps.original,
             fieldKey: this.props.rowProps.column.id as string,
             fieldExt: "",
+            ruleProvider: undefined,
             ContainingCollectionView: this.props.CollectionView,
             isSelected: returnFalse,
             select: emptyFunction,
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index dca1d7c1d..1a84f94c8 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -32,6 +32,7 @@ import { CellProps, CollectionSchemaCell, CollectionSchemaNumberCell, Collection
 import { MovableColumn, MovableRow } from "./CollectionSchemaMovableTableHOC";
 import { ComputedField, ScriptField } from "../../../new_fields/ScriptField";
 import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
+import { DocumentType } from "../../documents/DocumentTypes";
 
 
 library.add(faCog, faPlus, faSortUp, faSortDown);
@@ -161,6 +162,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
                 DataDocument={this.previewDocument !== this.props.DataDoc ? this.props.DataDoc : undefined}
                 childDocs={this.childDocs}
                 renderDepth={this.props.renderDepth}
+                ruleProvider={this.props.Document.isRuleProvider && layoutDoc && layoutDoc.type !== DocumentType.TEXT ? this.props.Document : this.props.ruleProvider}
                 width={this.previewWidth}
                 height={this.previewHeight}
                 getTransform={this.getPreviewTransform}
@@ -901,6 +903,7 @@ interface CollectionSchemaPreviewProps {
     fitToBox?: boolean;
     width: () => number;
     height: () => number;
+    ruleProvider: Doc | undefined;
     showOverlays?: (doc: Doc) => { title?: string, caption?: string };
     CollectionView?: CollectionView | CollectionPDFView | CollectionVideoView;
     onClick?: ScriptField;
@@ -995,7 +998,7 @@ export class CollectionSchemaPreview extends React.Component<CollectionSchemaPre
                         Document={this.props.Document}
                         fitToBox={this.props.fitToBox}
                         onClick={this.props.onClick}
-                        ruleProvider={undefined}
+                        ruleProvider={this.props.ruleProvider}
                         showOverlays={this.props.showOverlays}
                         addDocument={this.props.addDocument}
                         removeDocument={this.props.removeDocument}
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 91e10b0ac..89cccedff 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -144,6 +144,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
             DataDocument={dataDoc}
             showOverlays={this.overlays}
             renderDepth={this.props.renderDepth}
+            ruleProvider={this.props.Document.isRuleProvider && layoutDoc.type !== DocumentType.TEXT ? this.props.Document : this.props.ruleProvider}
             fitToBox={this.props.fitToBox}
             onClick={layoutDoc.isTemplate ? this.onClickHandler : this.onChildClickHandler}
             width={width}
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index f5bb76966..b1e063997 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -37,6 +37,7 @@ export interface TreeViewProps {
     containingCollection: Doc;
     renderDepth: number;
     deleteDoc: (doc: Doc) => boolean;
+    ruleProvider: Doc | undefined;
     moveDocument: DragManager.MoveFunction;
     dropAction: "alias" | "copy" | undefined;
     addDocTab: (doc: Doc, dataDoc: Doc | undefined, where: string) => void;
@@ -324,6 +325,7 @@ class TreeView extends React.Component<TreeViewProps> {
                     DataDocument={this.resolvedDataDoc}
                     renderDepth={this.props.renderDepth}
                     showOverlays={this.noOverlays}
+                    ruleProvider={this.props.document.isRuleProvider && layoutDoc.type !== DocumentType.TEXT ? this.props.document : this.props.ruleProvider}
                     fitToBox={this.boundsOfCollectionDocument !== undefined}
                     width={this.docWidth}
                     height={this.docHeight}
@@ -491,6 +493,7 @@ class TreeView extends React.Component<TreeViewProps> {
                 dataDoc={pair.data}
                 containingCollection={containingCollection}
                 treeViewId={treeViewId}
+                ruleProvider={containingCollection.isRuleProvider && pair.layout.type !== DocumentType.TEXT ? containingCollection : containingCollection.ruleProvider as Doc}
                 key={child[Id]}
                 indentDocument={indent}
                 renderDepth={renderDepth}
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index e46e8cb88..cc5e887b2 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -287,7 +287,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
             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);
+                let bg = StrCast(child.layout instanceof Doc ? child.layout.backgroundColor : child.backgroundColor);
                 if (palette.indexOf(bg) !== -1) {
                     palette.splice(palette.indexOf(bg), 1);
                     if (usedPaletted.get(bg)) usedPaletted.set(bg, usedPaletted.get(bg)! + 1);
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index cc04c5a9f..591a507eb 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -811,7 +811,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
         let backgroundColor = this.layoutDoc.isBackground || (clusterCol && !colorSet) ?
             this.props.backgroundColor(this.layoutDoc) || StrCast(this.layoutDoc.backgroundColor) :
-            ruleColor && !colorSet ? ruleColor : StrCast(this.layoutDoc.backgroundColor) || this.props.backgroundColor(this.layoutDoc);
+            ruleColor ? ruleColor : StrCast(this.layoutDoc.backgroundColor) || this.props.backgroundColor(this.layoutDoc);
         let foregroundColor = StrCast(this.layoutDoc.color);
         var nativeWidth = this.nativeWidth > 0 && !BoolCast(this.props.Document.ignoreAspect) ? `${this.nativeWidth}px` : "100%";
         var nativeHeight = BoolCast(this.props.Document.ignoreAspect) ? this.props.PanelHeight() / this.props.ContentScaling() : this.nativeHeight > 0 ? `${this.nativeHeight}px` : "100%";
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index a0dc054cf..ffb829825 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -285,6 +285,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         } else if (de.data instanceof DragManager.DocumentDragData) {
             const draggedDoc = de.data.draggedDocuments.length && de.data.draggedDocuments[0];
             if (draggedDoc && draggedDoc.type === DocumentType.TEXT) {
+                // let m = Doc.MakeDelegate(draggedDoc);  // under construction
+                // m.layout = m.layout.replace(/fieldKey={})
                 this.props.Document.layout = draggedDoc.layout instanceof Doc ? draggedDoc.layout : draggedDoc;
                 draggedDoc.isTemplate = true;
                 e.stopPropagation();
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index a27dbd83d..7e0f3735d 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -55,6 +55,7 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
             Document: this.props.doc,
             DataDoc: this.props.doc,
             ContainingCollectionView: undefined,
+            ruleProvider: undefined,
             fieldKey: this.props.keyName,
             fieldExt: "",
             isSelected: returnFalse,
-- 
cgit v1.2.3-70-g09d2


From 8a4163eedcfd37a5e245c710ffc674c1d16088f8 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Fri, 13 Sep 2019 18:51:13 -0400
Subject: fixed applying template to template

---
 src/client/views/nodes/DocumentView.tsx     |  2 +-
 src/client/views/nodes/FormattedTextBox.tsx | 10 +++++++---
 src/new_fields/Doc.ts                       | 17 +++++++++--------
 3 files changed, 17 insertions(+), 12 deletions(-)

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

diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 591a507eb..3dd384c80 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -285,7 +285,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     }
 
     onClick = async (e: React.MouseEvent) => {
-        if (e.nativeEvent.cancelBubble) return; // needed because EditableView may stopPropagation which won't apparently stop this event from firing.
+        if (e.nativeEvent.cancelBubble || SelectionManager.IsSelected(this)) return; // needed because EditableView may stopPropagation which won't apparently stop this event from firing.
         if (this.onClickHandler && this.onClickHandler.script) {
             e.stopPropagation();
             this.onClickHandler.script.run({ this: this.props.Document.isTemplate && this.props.DataDoc ? this.props.DataDoc : this.props.Document });
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index ffb829825..3e8b01dfd 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -285,10 +285,14 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         } else if (de.data instanceof DragManager.DocumentDragData) {
             const draggedDoc = de.data.draggedDocuments.length && de.data.draggedDocuments[0];
             if (draggedDoc && draggedDoc.type === DocumentType.TEXT) {
-                // let m = Doc.MakeDelegate(draggedDoc);  // under construction
-                // m.layout = m.layout.replace(/fieldKey={})
-                this.props.Document.layout = draggedDoc.layout instanceof Doc ? draggedDoc.layout : draggedDoc;
                 draggedDoc.isTemplate = true;
+                if (typeof (draggedDoc.layout) === "string") {
+                    let layoutDelegateToOverrideFieldKey = Doc.MakeDelegate(draggedDoc);
+                    layoutDelegateToOverrideFieldKey.layout = StrCast(layoutDelegateToOverrideFieldKey.layout).replace(/fieldKey={"[^"]*"}/, `fieldKey={"${this.props.fieldKey}"}`);
+                    this.props.Document.layout = layoutDelegateToOverrideFieldKey;
+                } else {
+                    this.props.Document.layout = draggedDoc.layout instanceof Doc ? draggedDoc.layout : draggedDoc;
+                }
                 e.stopPropagation();
             }
         }
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 5b22a62a1..474644dba 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -1,4 +1,4 @@
-import { observable, ObservableMap, runInAction } from "mobx";
+import { observable, ObservableMap, runInAction, action } from "mobx";
 import { alias, map, serializable } from "serializr";
 import { DocServer } from "../client/DocServer";
 import { DocumentType } from "../client/documents/DocumentTypes";
@@ -566,18 +566,13 @@ export namespace Doc {
         if (fieldTemplate.layout instanceof Doc) {
             fieldLayoutDoc = Doc.MakeDelegate(fieldTemplate.layout);
         }
-        let layout = StrCast(fieldLayoutDoc.layout).replace(/fieldKey={"[^"]*"}/, `fieldKey={"${metadataFieldName}"}`);
         if (backgroundLayout) {
             backgroundLayout = backgroundLayout.replace(/fieldKey={"[^"]*"}/, `fieldKey={"${metadataFieldName}"}`);
         }
 
-        let layoutDelegate = fieldTemplate.layout instanceof Doc ? fieldLayoutDoc : fieldTemplate;
-        layoutDelegate.layout = layout;
-
         fieldTemplate.templateField = metadataFieldName;
         fieldTemplate.title = metadataFieldName;
         fieldTemplate.isTemplate = true;
-        fieldTemplate.layout = layoutDelegate !== fieldTemplate ? layoutDelegate : layout;
         fieldTemplate.backgroundLayout = backgroundLayout;
         /* 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.
@@ -591,8 +586,14 @@ export namespace Doc {
         fieldTemplate.scale = 1;
         fieldTemplate.showTitle = "title";
         let data = fieldTemplate.data;
-        !templateDataDoc[metadataFieldName] && data instanceof ObjectField && (templateDataDoc[metadataFieldName] = ObjectField.MakeCopy(data));
-        setTimeout(() => fieldTemplate.proto = templateDataDoc);
+        setTimeout(action(() => {
+            !templateDataDoc[metadataFieldName] && data instanceof ObjectField && (Doc.GetProto(templateDataDoc)[metadataFieldName] = ObjectField.MakeCopy(data));
+            let layout = StrCast(fieldLayoutDoc.layout).replace(/fieldKey={"[^"]*"}/, `fieldKey={"${metadataFieldName}"}`);
+            let layoutDelegate = fieldTemplate.layout instanceof Doc ? fieldLayoutDoc : fieldTemplate;
+            layoutDelegate.layout = layout;
+            fieldTemplate.layout = layoutDelegate !== fieldTemplate ? layoutDelegate : layout;
+            fieldTemplate.proto = templateDataDoc;
+        }), 0);
     }
 
     export function ToggleDetailLayout(d: Doc) {
-- 
cgit v1.2.3-70-g09d2


From d9fa64c229b13f9c8121a40b76d180775be5f6c6 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 14 Sep 2019 00:33:18 -0400
Subject: fixed color assignments for rule providers.  no titles are shown when
 promoting to a custom layout

---
 src/client/views/InkingControl.tsx                 | 32 ++++++++++------------
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 16 +++++++++++
 src/client/views/nodes/DocumentView.tsx            |  4 +--
 src/client/views/nodes/FormattedTextBox.tsx        | 16 ++++++-----
 src/new_fields/Doc.ts                              |  5 ++--
 5 files changed, 44 insertions(+), 29 deletions(-)

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

diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index 94cc1f06c..57dad5e6b 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -51,14 +51,17 @@ 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);
-                if (view.props.ContainingCollectionView) {
-                    if (!view.props.ContainingCollectionView.props.Document.colorPalette) {
+                let matchedColor = this._selectedColor;
+                const cv = view.props.ContainingCollectionView;
+                let ruleProvider: Doc | undefined;
+                if (cv) {
+                    if (!cv.props.Document.colorPalette) {
                         let defaultPalette = ["rg14,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(view.props.ContainingCollectionView.props.Document.colorPalette, listSpec("string"));
-                        if (!colorPalette) view.props.ContainingCollectionView.props.Document.colorPalette = new List<string>(defaultPalette);
+                        let colorPalette = Cast(cv.props.Document.colorPalette, listSpec("string"));
+                        if (!colorPalette) cv.props.Document.colorPalette = new List<string>(defaultPalette);
                     }
-                    let cp = Cast(view.props.ContainingCollectionView.props.Document.colorPalette, listSpec("string")) as string[];
+                    let cp = Cast(cv.props.Document.colorPalette, listSpec("string")) as string[];
                     let closest = 0;
                     let dist = 10000000;
                     let ccol = Utils.fromRGBAstr(StrCast(targetDoc.backgroundColor));
@@ -71,20 +74,13 @@ export class InkingControl extends React.Component {
                         }
                     }
                     cp[closest] = "rgba(" + color.rgb.r + "," + color.rgb.g + "," + color.rgb.b + "," + color.rgb.a + ")";
-                    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.ruleProvider, Doc) as Doc);
-                    cv && (Doc.GetProto(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);
-                    // }
+                    cv.props.Document.colorPalette = new List(cp);
+                    matchedColor = cp[closest];
+                    ruleProvider = (view.props.Document.heading && cv && cv.props.ruleProvider) ? cv.props.ruleProvider : undefined;
+                    ruleProvider && ((Doc.GetProto(ruleProvider)["ruleColor_" + NumCast(view.props.Document.heading)] = Utils.toRGBAstr(color.rgb)));
                 }
+                !ruleProvider && (targetDoc.backgroundColor = matchedColor);
+
                 return {
                     target: targetDoc,
                     previous: oldColor
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 4a3e5039a..ad91eb007 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -875,6 +875,22 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     autoFormat = () => {
         this.props.Document.isRuleProvider = !this.props.Document.isRuleProvider;
         this.childDocs.map(child => child.heading = undefined);
+        this.childDocs.map(child => {
+            DocListCast(child.layout instanceof Doc ? child.layout.data : child.data).map(heading => {
+                let pair = Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, heading);
+                let disp = (child.data_ext instanceof Doc) && pair.layout && (child.data_ext[`Layout[${pair.layout[Id]}]`] as Doc);
+                if (disp && NumCast(disp.heading) > 0) {
+                    if (disp.backgroundColor !== disp.defaultBackgroundColor) {
+                        Doc.GetProto(this.props.Document)["ruleColor_" + NumCast(disp.heading)] = disp.backgroundColor;
+                    }
+                }
+                if (pair.layout && NumCast(pair.layout.heading) > 0) {
+                    if (pair.layout.backgroundColor !== pair.layout.defaultBackgroundColor) {
+                        Doc.GetProto(this.props.Document)["ruleColor_" + NumCast(pair.layout.heading)] = pair.layout.backgroundColor;
+                    }
+                }
+            })
+        })
     }
 
     analyzeStrokes = async () => {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 3dd384c80..d37a0ee59 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -475,7 +475,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
                 let docTemplate = Docs.Create.FreeformDocument([fieldTemplate], { title: StrCast(this.Document.title) + "layout", width: NumCast(this.props.Document.width) + 20, height: Math.max(100, NumCast(this.props.Document.height) + 45) });
                 let proto = Doc.GetProto(docTemplate);
-                Doc.MakeMetadataFieldTemplate(fieldTemplate, proto);
+                Doc.MakeMetadataFieldTemplate(fieldTemplate, proto, true);
 
                 Doc.ApplyTemplateTo(docTemplate, this.props.Document, undefined, false);
                 Doc.GetProto(this.dataDoc || this.props.Document).customLayout = this.props.Document.layout;
@@ -811,7 +811,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
         let backgroundColor = this.layoutDoc.isBackground || (clusterCol && !colorSet) ?
             this.props.backgroundColor(this.layoutDoc) || StrCast(this.layoutDoc.backgroundColor) :
-            ruleColor ? ruleColor : StrCast(this.layoutDoc.backgroundColor) || this.props.backgroundColor(this.layoutDoc);
+            ruleColor && !colorSet ? ruleColor : StrCast(this.layoutDoc.backgroundColor) || this.props.backgroundColor(this.layoutDoc);
         let foregroundColor = StrCast(this.layoutDoc.color);
         var nativeWidth = this.nativeWidth > 0 && !BoolCast(this.props.Document.ignoreAspect) ? `${this.nativeWidth}px` : "100%";
         var nativeHeight = BoolCast(this.props.Document.ignoreAspect) ? this.props.PanelHeight() / this.props.ContentScaling() : this.nativeHeight > 0 ? `${this.nativeHeight}px` : "100%";
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 3e8b01dfd..2e05268a6 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -285,13 +285,15 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         } else if (de.data instanceof DragManager.DocumentDragData) {
             const draggedDoc = de.data.draggedDocuments.length && de.data.draggedDocuments[0];
             if (draggedDoc && draggedDoc.type === DocumentType.TEXT) {
-                draggedDoc.isTemplate = true;
-                if (typeof (draggedDoc.layout) === "string") {
-                    let layoutDelegateToOverrideFieldKey = Doc.MakeDelegate(draggedDoc);
-                    layoutDelegateToOverrideFieldKey.layout = StrCast(layoutDelegateToOverrideFieldKey.layout).replace(/fieldKey={"[^"]*"}/, `fieldKey={"${this.props.fieldKey}"}`);
-                    this.props.Document.layout = layoutDelegateToOverrideFieldKey;
-                } else {
-                    this.props.Document.layout = draggedDoc.layout instanceof Doc ? draggedDoc.layout : draggedDoc;
+                if (!Doc.AreProtosEqual(draggedDoc, this.props.Document)) {
+                    draggedDoc.isTemplate = true;
+                    if (typeof (draggedDoc.layout) === "string") {
+                        let layoutDelegateToOverrideFieldKey = Doc.MakeDelegate(draggedDoc);
+                        layoutDelegateToOverrideFieldKey.layout = StrCast(layoutDelegateToOverrideFieldKey.layout).replace(/fieldKey={"[^"]*"}/, `fieldKey={"${this.props.fieldKey}"}`);
+                        this.props.Document.layout = layoutDelegateToOverrideFieldKey;
+                    } else {
+                        this.props.Document.layout = draggedDoc.layout instanceof Doc ? draggedDoc.layout : draggedDoc;
+                    }
                 }
                 e.stopPropagation();
             }
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 474644dba..b6b3bf73e 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -558,7 +558,7 @@ export namespace Doc {
         }
     }
 
-    export function MakeMetadataFieldTemplate(fieldTemplate: Doc, templateDataDoc: Doc) {
+    export function MakeMetadataFieldTemplate(fieldTemplate: Doc, templateDataDoc: Doc, suppressTitle: boolean = false) {
         // move data doc fields to layout doc as needed (nativeWidth/nativeHeight, data, ??)
         let metadataFieldName = StrCast(fieldTemplate.title).replace(/^-/, "");
         let backgroundLayout = StrCast(fieldTemplate.backgroundLayout);
@@ -584,7 +584,7 @@ export namespace Doc {
         fieldTemplate.panX = 0;
         fieldTemplate.panY = 0;
         fieldTemplate.scale = 1;
-        fieldTemplate.showTitle = "title";
+        fieldTemplate.showTitle = suppressTitle ? undefined : "title";
         let data = fieldTemplate.data;
         setTimeout(action(() => {
             !templateDataDoc[metadataFieldName] && data instanceof ObjectField && (Doc.GetProto(templateDataDoc)[metadataFieldName] = ObjectField.MakeCopy(data));
@@ -592,6 +592,7 @@ export namespace Doc {
             let layoutDelegate = fieldTemplate.layout instanceof Doc ? fieldLayoutDoc : fieldTemplate;
             layoutDelegate.layout = layout;
             fieldTemplate.layout = layoutDelegate !== fieldTemplate ? layoutDelegate : layout;
+            if (fieldTemplate.backgroundColor !== templateDataDoc.defaultBackgroundColor) fieldTemplate.defaultBackgroundColor = fieldTemplate.backgroundColor;
             fieldTemplate.proto = templateDataDoc;
         }), 0);
     }
-- 
cgit v1.2.3-70-g09d2


From 4ec15a9576a27b8290fb37b6959cb13ae76feeaa Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Sat, 14 Sep 2019 11:18:51 -0400
Subject: various fixes for templating and publishing document ids

---
 src/client/documents/Documents.ts                  | 39 ++++++++++-------
 .../CollectionStackingViewFieldColumn.tsx          |  3 ++
 .../views/collections/CollectionTreeView.tsx       |  1 +
 src/client/views/nodes/DocumentView.tsx            | 49 +++++++++++++++++-----
 src/client/views/nodes/FormattedTextBox.tsx        | 28 +++++++++----
 src/new_fields/Doc.ts                              | 23 ++++++++++
 6 files changed, 110 insertions(+), 33 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 2eff73b87..e7ac1e321 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -610,23 +610,30 @@ export namespace DocUtils {
     export function Publish(promoteDoc: Doc, targetID: string, addDoc: any, remDoc: any) {
         targetID = targetID.replace(/^-/, "").replace(/\([0-9]*\)$/, "");
         DocServer.GetRefField(targetID).then(doc => {
-            let copy = doc instanceof Doc ? doc : Doc.MakeCopy(promoteDoc, true, targetID);
-            !doc && (copy.title = undefined) && (Doc.GetProto(copy).title = targetID);
-            addDoc && addDoc(copy);
-            !doc && remDoc && remDoc(promoteDoc);
-            if (!doc) {
-                DocListCastAsync(promoteDoc.links).then(links => {
-                    links && links.map(async link => {
-                        if (link) {
-                            let a1 = await Cast(link.anchor1, Doc);
-                            if (a1 && Doc.AreProtosEqual(a1, promoteDoc)) link.anchor1 = copy;
-                            let a2 = await Cast(link.anchor2, Doc);
-                            if (a2 && Doc.AreProtosEqual(a2, promoteDoc)) link.anchor2 = copy;
-                            LinkManager.Instance.deleteLink(link);
-                            LinkManager.Instance.addLink(link);
-                        }
+            if (promoteDoc !== doc) {
+                let copy = doc as Doc;
+                if (copy) {
+                    Doc.Overwrite(promoteDoc, copy, true);
+                } else {
+                    copy = Doc.MakeCopy(promoteDoc, true, targetID);
+                }
+                !doc && (copy.title = undefined) && (Doc.GetProto(copy).title = targetID);
+                addDoc && addDoc(copy);
+                remDoc && remDoc(promoteDoc);
+                if (!doc) {
+                    DocListCastAsync(promoteDoc.links).then(links => {
+                        links && links.map(async link => {
+                            if (link) {
+                                let a1 = await Cast(link.anchor1, Doc);
+                                if (a1 && Doc.AreProtosEqual(a1, promoteDoc)) link.anchor1 = copy;
+                                let a2 = await Cast(link.anchor2, Doc);
+                                if (a2 && Doc.AreProtosEqual(a2, promoteDoc)) link.anchor2 = copy;
+                                LinkManager.Instance.deleteLink(link);
+                                LinkManager.Instance.addLink(link);
+                            }
+                        })
                     })
-                })
+                }
             }
         });
     }
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index bc4fe7dd7..185bec7a2 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -155,6 +155,9 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
         let key = StrCast(this.props.parent.props.Document.sectionFilter);
         let newDoc = Docs.Create.TextDocument({ height: 18, width: 200, documentText: "@@@" + value, title: value, autoHeight: true });
         newDoc[key] = this.getValue(this.props.heading);
+        let maxHeading = this.props.docList.reduce((maxHeading, doc) => NumCast(doc.heading) > maxHeading ? NumCast(doc.heading) : maxHeading, 0);
+        let heading = maxHeading === 0 || this.props.docList.length === 0 ? 1 : maxHeading === 1 ? 2 : 3;
+        newDoc.heading = heading;
         return this.props.parent.props.addDocument(newDoc);
     }
 
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index b1e063997..6217ef859 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -201,6 +201,7 @@ class TreeView extends React.Component<TreeViewProps> {
                 ContextMenu.Instance.addItem({ description: "Delete Workspace", event: () => this.props.deleteDoc(this.props.document), icon: "trash-alt" });
             }
             ContextMenu.Instance.addItem({ description: "Open Fields", event: () => { let kvp = Docs.Create.KVPDocument(this.props.document, { width: 300, height: 300 }); this.props.addDocTab(kvp, this.props.dataDoc ? this.props.dataDoc : kvp, "onRight"); }, icon: "layer-group" });
+            ContextMenu.Instance.addItem({ description: "Publish", event: () => DocUtils.Publish(this.props.document, StrCast(this.props.document.title), () => { }, () => { }), icon: "file" });
             ContextMenu.Instance.displayMenu(e.pageX > 156 ? e.pageX - 156 : 0, e.pageY - 15);
             e.stopPropagation();
             e.preventDefault();
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index d37a0ee59..d90224eae 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -441,22 +441,39 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     @undoBatch
     makeNativeViewClicked = (): void => {
         this.props.Document.layout = this.props.Document.nativeLayout;
-        this.props.Document.type = this.props.Document.nativeType;
-        this.props.Document.nativeWidth = this.props.Document.nativeNativeWidth;
-        this.props.Document.nativeHeight = this.props.Document.nativeNativeHeight;
-        this.props.Document.ignoreAspect = this.props.Document.nativeIgnoreAspect;
         this.props.Document.nativeLayout = undefined;
-        this.props.Document.nativeNativeWidth = undefined;
-        this.props.Document.nativeNativeHeight = undefined;
-        this.props.Document.nativeIgnoreAspect = undefined;
+        this.props.Document.type = this.props.Document.nativeType;
+
+        this.props.Document.customAutoHeight = this.props.Document.autoHeight;
+        this.props.Document.customWidth = this.props.Document.nativeWidth;
+        this.props.Document.customHeight = this.props.Document.nativeHeight;
+        this.props.Document.customNativeWidth = this.props.Document.nativeWidth;
+        this.props.Document.customNativeHeight = this.props.Document.nativeHeight;
+        this.props.Document.customIgnoreAspect = this.props.Document.ignoreAspect;
+
+        this.props.Document.autoHeight = this.props.Document.nonCustomAutoHeight;
+        this.props.Document.width = this.props.Document.nonCustomWidth;
+        this.props.Document.height = this.props.Document.nonCustomHeight;
+        this.props.Document.nativeWidth = this.props.Document.nonCustomNativeWidth;
+        this.props.Document.nativeHeight = this.props.Document.nonCustomNativeHeight;
+        this.props.Document.ignoreAspect = this.props.Document.nonCustomIgnoreAspect;
+        this.props.Document.nonCustomAutoHeight = undefined;
+        this.props.Document.nonCustomWidth = undefined;
+        this.props.Document.nonCustomHeight = undefined;
+        this.props.Document.nonCustomNativeWidth = undefined;
+        this.props.Document.nonCustomNativeHeight = undefined;
+        this.props.Document.nonCustomIgnoreAspect = undefined;
     }
     @undoBatch
     makeCustomViewClicked = (): void => {
         this.props.Document.nativeLayout = this.props.Document.layout;
         this.props.Document.nativeType = this.props.Document.type;
-        this.props.Document.nativeNativeWidth = this.props.Document.nativeWidth;
-        this.props.Document.nativeNativeHeight = this.props.Document.nativeHeight;
-        this.props.Document.nativeIgnoreAspect = this.props.Document.ignoreAspect;
+        this.props.Document.nonCustomAutoHeight = this.props.Document.autoHeight;
+        this.props.Document.nonCustomWidth = this.props.Document.nativeWidth;
+        this.props.Document.nonCustomHeight = this.props.Document.nativeHeight;
+        this.props.Document.nonCustomNativeWidth = this.props.Document.nativeWidth;
+        this.props.Document.nonCustomNativeHeight = this.props.Document.nativeHeight;
+        this.props.Document.nonCustomIgnoreAspect = this.props.Document.ignoreAspect;
         PromiseValue(Cast(this.props.Document.customLayout, Doc)).then(custom => {
             if (custom) {
                 this.props.Document.type = DocumentType.TEMPLATE;
@@ -464,6 +481,18 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 !custom.nativeWidth && (this.props.Document.nativeWidth = 0);
                 !custom.nativeHeight && (this.props.Document.nativeHeight = 0);
                 !custom.nativeWidth && (this.props.Document.ignoreAspect = true);
+                this.props.Document.autoHeight = this.props.Document.autoHeight;
+                this.props.Document.width = this.props.Document.customWidth;
+                this.props.Document.height = this.props.Document.customHeight;
+                this.props.Document.nativeWidth = this.props.Document.customNativeWidth;
+                this.props.Document.nativeHeight = this.props.Document.customNativeHeight;
+                this.props.Document.ignoreAspect = this.props.Document.ignoreAspect;
+                this.props.Document.customAutoHeight = undefined;
+                this.props.Document.customWidth = undefined;
+                this.props.Document.customHeight = undefined;
+                this.props.Document.customNativeWidth = undefined;
+                this.props.Document.customNativeHeight = undefined;
+                this.props.Document.customIgnoreAspect = undefined;
             } else {
                 let options = { title: "data", width: NumCast(this.props.Document.width), height: NumCast(this.props.Document.height) + 25, x: -NumCast(this.props.Document.width) / 2, y: -NumCast(this.props.Document.height) / 2, };
                 let fieldTemplate = this.props.Document.type === DocumentType.TEXT ? Docs.Create.TextDocument(options) :
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 2e05268a6..77e29632e 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -169,6 +169,25 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         }
     }
 
+    linkOnDeselect: Map<string, string> = new Map();
+
+    doLinkOnDeselect() {
+        Array.from(this.linkOnDeselect.entries()).map(entry => {
+            let key = entry[0];
+            let value = entry[1];
+            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);
+                    DocUtils.Publish(this.dataDoc[key] as Doc, value, this.props.addDocument, this.props.removeDocument);
+                    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, true);
+                })
+            });
+        })
+        this.linkOnDeselect.clear();
+    }
+
     dispatchTransaction = (tx: Transaction) => {
         if (this._editorView) {
             let metadata = tx.selection.$from.marks().find((m: Mark) => m.type === schema.marks.metadata);
@@ -183,15 +202,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 if (split.length > 1 && split[1]) {
                     let key = split[0];
                     let value = split[split.length - 1];
+                    this.linkOnDeselect.set(key, 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, true);
-                        })
-                    });
                     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);
@@ -875,6 +888,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             this._undoTyping.end();
             this._undoTyping = undefined;
         }
+        this.doLinkOnDeselect();
     }
     onKeyPress = (e: React.KeyboardEvent) => {
         if (e.key === "Escape") {
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index b6b3bf73e..eef14ad25 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -475,6 +475,29 @@ export namespace Doc {
         return { layout: layoutDoc, data: resolvedDataDoc };
     }
 
+    export function Overwrite(doc: Doc, overwrite: Doc, copyProto: boolean = false): Doc {
+        Object.keys(doc).forEach(key => {
+            const field = ProxyField.WithoutProxy(() => doc[key]);
+            if (key === "proto" && copyProto) {
+                if (doc.proto instanceof Doc && overwrite.proto instanceof Doc) {
+                    overwrite[key] = Doc.Overwrite(doc[key]!, overwrite.proto);
+                }
+            } else {
+                if (field instanceof RefField) {
+                    overwrite[key] = field;
+                } else if (field instanceof ObjectField) {
+                    overwrite[key] = ObjectField.MakeCopy(field);
+                } else if (field instanceof Promise) {
+                    debugger; //This shouldn't happend...
+                } else {
+                    overwrite[key] = field;
+                }
+            }
+        });
+
+        return overwrite;
+    }
+
     export function MakeCopy(doc: Doc, copyProto: boolean = false, copyProtoId?: string): Doc {
         const copy = new Doc(copyProtoId, true);
         Object.keys(doc).forEach(key => {
-- 
cgit v1.2.3-70-g09d2

