From ed1698dc1b225ebc79c1dbe5b831c3e164a312e7 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 5 Dec 2019 14:25:02 -0500
Subject: added style shortcut for text

---
 src/client/util/TooltipTextMenu.tsx | 31 +++++++++++++++++++++++++++++--
 1 file changed, 29 insertions(+), 2 deletions(-)

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

diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 35a099c48..89037dcb2 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -20,6 +20,7 @@ import { updateBullets } from './ProsemirrorExampleTransfer';
 import { DocumentDecorations } from '../views/DocumentDecorations';
 import { SelectionManager } from './SelectionManager';
 import { PastelSchemaPalette, DarkPastelSchemaPalette } from '../../new_fields/SchemaHeaderField';
+import { Keys } from "../views/search/FilterBox";
 const { toggleMark, setBlockType } = require("prosemirror-commands");
 const { openPrompt, TextField } = require("./ProsemirrorCopy/prompt.js");
 
@@ -840,7 +841,7 @@ export class TooltipTextMenu {
     public static insertColor(color: String, state: EditorState<any>, dispatch: any) {
         if (state.selection.empty) return false;
 
-        const colorMark = state.schema.mark(state.schema.marks.color, { color: color });
+        const colorMark = state.schema.mark(state.schema.marks.pFontColor, { color: color });
         dispatch(state.tr.addMark(state.selection.from, state.selection.to, colorMark));
     }
 
@@ -998,6 +999,7 @@ export class TooltipTextMenu {
         });
 
         const self = this;
+        const input = document.createElement("input");
         const clearBrush = new MenuItem({
             title: "Clear brush",
             execEvent: "",
@@ -1007,7 +1009,31 @@ export class TooltipTextMenu {
                 const button = document.createElement("button");
                 button.textContent = "Clear brush";
 
+                input.textContent = "editme";
+                input.style.width = "75px";
+                input.style.height = "30px";
+                input.style.background = "white";
+                input.setAttribute("contenteditable", "true");
+                input.style.whiteSpace = "nowrap";
+                input.type = "text";
+                input.placeholder = "Enter URL";
+                input.onpointerdown = (e: PointerEvent) => {
+                    e.stopPropagation();
+                    e.preventDefault();
+                };
+                input.onclick = (e: MouseEvent) => {
+                    input.select();
+                    input.focus();
+                };
+                input.onkeypress = (e: KeyboardEvent) => {
+                    if (e.key === "Enter") {
+                        TooltipTextMenuManager.Instance._brushMarks && TooltipTextMenuManager.Instance._brushMap.set(input.value, TooltipTextMenuManager.Instance._brushMarks);
+                        input.style.background = "lightGray";
+                    }
+                }
+
                 const wrapper = document.createElement("div");
+                wrapper.appendChild(input);
                 wrapper.appendChild(button);
                 return wrapper;
             },
@@ -1488,7 +1514,7 @@ export class TooltipTextMenu {
 }
 
 
-class TooltipTextMenuManager {
+export class TooltipTextMenuManager {
     private static _instance: TooltipTextMenuManager;
 
     public pinnedX: number = 0;
@@ -1498,6 +1524,7 @@ class TooltipTextMenuManager {
     private _isPinned: boolean = false;
 
     public _brushMarks: Set<Mark> | undefined;
+    public _brushMap: Map<string, Set<Mark>> = new Map();
     public _brushIsEmpty: boolean = true;
 
     public color: String = "#000";
-- 
cgit v1.2.3-70-g09d2


From 304d7f239ae6e7b854a8da1124a919b7ba093b48 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Sat, 7 Dec 2019 18:35:32 -0500
Subject: initial commit

---
 src/client/DocServer.ts                     |  1 +
 src/client/util/ClientDiagnostics.ts        | 34 --------------------
 src/client/util/TooltipTextMenu.tsx         |  2 +-
 src/client/views/Main.tsx                   |  2 --
 src/server/ActionUtilities.ts               |  8 ++---
 src/server/ApiManagers/DiagnosticManager.ts | 30 ------------------
 src/server/ApiManagers/SearchManager.ts     | 49 ++++++++++++++++++++++++++++-
 src/server/Message.ts                       |  1 +
 src/server/Websocket/Websocket.ts           |  4 ++-
 src/server/index.ts                         | 41 ++++++++++++++++++++++--
 10 files changed, 96 insertions(+), 76 deletions(-)
 delete mode 100644 src/client/util/ClientDiagnostics.ts
 delete mode 100644 src/server/ApiManagers/DiagnosticManager.ts

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

diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index e4b183715..befe9ea5c 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -82,6 +82,7 @@ export namespace DocServer {
         Utils.AddServerHandler(_socket, MessageStore.UpdateField, respondToUpdate);
         Utils.AddServerHandler(_socket, MessageStore.DeleteField, respondToDelete);
         Utils.AddServerHandler(_socket, MessageStore.DeleteFields, respondToDelete);
+        _socket.on("connection_terminated", () => alert("Your connection to the server has been terminated."));
     }
 
     function errorFunc(): never {
diff --git a/src/client/util/ClientDiagnostics.ts b/src/client/util/ClientDiagnostics.ts
deleted file mode 100644
index 0a213aa1c..000000000
--- a/src/client/util/ClientDiagnostics.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-export namespace ClientDiagnostics {
-
-    export async function start() {
-
-        let serverPolls = 0;
-        const serverHandle = setInterval(async () => {
-            if (++serverPolls === 20) {
-                alert("Your connection to the server has been terminated.");
-                clearInterval(serverHandle);
-            }
-            await fetch("/serverHeartbeat");
-            serverPolls--;
-        }, 1000 * 15);
-
-        let executed = false;
-        let solrHandle: NodeJS.Timeout | undefined;
-        const handler = async () => {
-            const response = await fetch("/solrHeartbeat");
-            if (!(await response.json()).running) {
-                if (!executed) {
-                    alert("Looks like SOLR is not running on your machine.");
-                    executed = true;
-                    solrHandle && clearInterval(solrHandle);
-                }
-            }
-        };
-        await handler();
-        if (!executed) {
-            solrHandle = setInterval(handler, 1000 * 15);
-        }
-
-    }
-
-}
\ No newline at end of file
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 89037dcb2..01d566831 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -1030,7 +1030,7 @@ export class TooltipTextMenu {
                         TooltipTextMenuManager.Instance._brushMarks && TooltipTextMenuManager.Instance._brushMap.set(input.value, TooltipTextMenuManager.Instance._brushMarks);
                         input.style.background = "lightGray";
                     }
-                }
+                };
 
                 const wrapper = document.createElement("div");
                 wrapper.appendChild(input);
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 9e699978f..b21eb9c8f 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -5,12 +5,10 @@ import * as ReactDOM from 'react-dom';
 import * as React from 'react';
 import { DocServer } from "../DocServer";
 import { AssignAllExtensions } from "../../extensions/General/Extensions";
-import { ClientDiagnostics } from "../util/ClientDiagnostics";
 
 AssignAllExtensions();
 
 (async () => {
-    await ClientDiagnostics.start();
     const info = await CurrentUserUtils.loadCurrentUser();
     DocServer.init(window.location.protocol, window.location.hostname, 4321, info.email);
     await Docs.Prototypes.initialize();
diff --git a/src/server/ActionUtilities.ts b/src/server/ActionUtilities.ts
index 4fe7374d1..1ebeab203 100644
--- a/src/server/ActionUtilities.ts
+++ b/src/server/ActionUtilities.ts
@@ -9,21 +9,21 @@ export const command_line = (command: string, fromDirectory?: string) => {
     return new Promise<string>((resolve, reject) => {
         const options: ExecOptions = {};
         if (fromDirectory) {
-            options.cwd = path.join(__dirname, fromDirectory);
+            options.cwd = path.resolve(__dirname, fromDirectory);
         }
         exec(command, options, (err, stdout) => err ? reject(err) : resolve(stdout));
     });
 };
 
 export const read_text_file = (relativePath: string) => {
-    const target = path.join(__dirname, relativePath);
+    const target = path.resolve(__dirname, relativePath);
     return new Promise<string>((resolve, reject) => {
         fs.readFile(target, (err, data) => err ? reject(err) : resolve(data.toString()));
     });
 };
 
 export const write_text_file = (relativePath: string, contents: any) => {
-    const target = path.join(__dirname, relativePath);
+    const target = path.resolve(__dirname, relativePath);
     return new Promise<void>((resolve, reject) => {
         fs.writeFile(target, contents, (err) => err ? reject(err) : resolve());
     });
@@ -75,5 +75,5 @@ export async function Prune(rootDirectory: string): Promise<boolean> {
 export const Destroy = (mediaPath: string) => new Promise<boolean>(resolve => fs.unlink(mediaPath, error => resolve(error === null)));
 
 export function addBeforeExitHandler(handler: NodeJS.BeforeExitListener) {
-    process.on("beforeExit", handler);
+    // process.on("beforeExit", handler);
 }
diff --git a/src/server/ApiManagers/DiagnosticManager.ts b/src/server/ApiManagers/DiagnosticManager.ts
deleted file mode 100644
index 104985481..000000000
--- a/src/server/ApiManagers/DiagnosticManager.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import ApiManager, { Registration } from "./ApiManager";
-import { Method } from "../RouteManager";
-import request = require('request-promise');
-
-export default class DiagnosticManager extends ApiManager {
-
-    protected initialize(register: Registration): void {
-
-        register({
-            method: Method.GET,
-            subscription: "/serverHeartbeat",
-            onValidation: ({ res }) => res.send(true)
-        });
-
-        register({
-            method: Method.GET,
-            subscription: "/solrHeartbeat",
-            onValidation: async ({ res }) => {
-                try {
-                    await request("http://localhost:8983");
-                    res.send({ running: true });
-                } catch (e) {
-                    res.send({ running: false });
-                }
-            }
-        });
-
-    }
-
-}
\ No newline at end of file
diff --git a/src/server/ApiManagers/SearchManager.ts b/src/server/ApiManagers/SearchManager.ts
index 7afecbb18..0e794fed6 100644
--- a/src/server/ApiManagers/SearchManager.ts
+++ b/src/server/ApiManagers/SearchManager.ts
@@ -4,11 +4,26 @@ import { Search } from "../Search";
 const findInFiles = require('find-in-files');
 import * as path from 'path';
 import { pathToDirectory, Directory } from "./UploadManager";
+import { command_line, addBeforeExitHandler } from "../ActionUtilities";
+import request = require('request-promise');
+import { red, green, yellow, cyan } from "colors";
 
-export default class SearchManager extends ApiManager {
+export class SearchManager extends ApiManager {
 
     protected initialize(register: Registration): void {
 
+        register({
+            method: Method.GET,
+            subscription: "/startSolr",
+            onValidation: async ({ res }) => res.send((await SolrManager.SetRunning(true)) ? "Successfully started Solr!" : "Uh oh! Check the console for the error that occurred while starting Solr")
+        });
+
+        register({
+            method: Method.GET,
+            subscription: "/stopSolr",
+            onValidation: async ({ res }) => res.send((await SolrManager.SetRunning(false)) ? "Successfully stopped Solr!" : "Uh oh! Check the console for the error that occurred while stopping Solr")
+        });
+
         register({
             method: Method.GET,
             subscription: "/textsearch",
@@ -46,4 +61,36 @@ export default class SearchManager extends ApiManager {
 
     }
 
+}
+
+export namespace SolrManager {
+
+    export async function initializeSolr() {
+        console.log(cyan("\nInspecting Solr status..."));
+        try {
+            await request("http://localhost:8983");
+            console.log(green('Solr already running\n'));
+        } catch (e) {
+            console.log(cyan('Initializing Solr...'));
+            await SolrManager.SetRunning(true);
+        } finally {
+            addBeforeExitHandler(async () => SolrManager.SetRunning(false));
+        }
+    }
+
+    export async function SetRunning(status: boolean): Promise<boolean> {
+        const args = status ? "start" : "stop -p 8983";
+        console.log(`Solr management: trying to ${args}`);
+        try {
+            console.log(await command_line(`solr.cmd ${args}`, "../../solr-8.1.1/bin"));
+            return true;
+        } catch (e) {
+            console.log(red(`Solr management error: unable to ${args}`));
+            if (status) {
+                process.exit(0);
+            }
+            return false;
+        }
+    }
+
 }
\ No newline at end of file
diff --git a/src/server/Message.ts b/src/server/Message.ts
index aaee143e8..621abfd1e 100644
--- a/src/server/Message.ts
+++ b/src/server/Message.ts
@@ -50,6 +50,7 @@ export namespace MessageStore {
     export const GetFields = new Message<string[]>("Get Fields"); // send string[] of 'id' get Transferable[] back
     export const GetDocument = new Message<string>("Get Document");
     export const DeleteAll = new Message<any>("Delete All");
+    export const ConnectionTerminated = new Message<string>("Connection Terminated");
 
     export const GetRefField = new Message<string>("Get Ref Field");
     export const GetRefFields = new Message<string[]>("Get Ref Fields");
diff --git a/src/server/Websocket/Websocket.ts b/src/server/Websocket/Websocket.ts
index 60c34aa44..de1d5eb25 100644
--- a/src/server/Websocket/Websocket.ts
+++ b/src/server/Websocket/Websocket.ts
@@ -10,6 +10,7 @@ import { GoogleCredentialsLoader } from "../credentials/CredentialsLoader";
 import { logPort, addBeforeExitHandler } from "../ActionUtilities";
 import { timeMap } from "../ApiManagers/UserManager";
 import { green } from "colors";
+import { ExitHandlers } from "..";
 
 export namespace WebSocket {
 
@@ -52,8 +53,9 @@ export namespace WebSocket {
             Utils.AddServerHandler(socket, MessageStore.DeleteFields, ids => DeleteFields(socket, ids));
             Utils.AddServerHandlerCallback(socket, MessageStore.GetRefField, GetRefField);
             Utils.AddServerHandlerCallback(socket, MessageStore.GetRefFields, GetRefFields);
+
+            ExitHandlers.push(() => socket.broadcast.emit("connection_terminated", Date.now()));
         });
-        addBeforeExitHandler(async () => { await new Promise<void>(resolve => endpoint.close(resolve)); });
         endpoint.listen(socketPort);
         logPort("websocket", socketPort);
     }
diff --git a/src/server/index.ts b/src/server/index.ts
index 551ce3898..42b4f7ff2 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -10,7 +10,7 @@ import initializeServer from './Initialization';
 import RouteManager, { Method, _success, _permission_denied, _error, _invalid, OnUnauthenticated } from './RouteManager';
 import * as qs from 'query-string';
 import UtilManager from './ApiManagers/UtilManager';
-import SearchManager from './ApiManagers/SearchManager';
+import { SearchManager, SolrManager } from './ApiManagers/SearchManager';
 import UserManager from './ApiManagers/UserManager';
 import { WebSocket } from './Websocket/Websocket';
 import DownloadManager from './ApiManagers/DownloadManager';
@@ -21,18 +21,48 @@ import UploadManager from "./ApiManagers/UploadManager";
 import { log_execution } from "./ActionUtilities";
 import GeneralGoogleManager from "./ApiManagers/GeneralGoogleManager";
 import GooglePhotosManager from "./ApiManagers/GooglePhotosManager";
-import DiagnosticManager from "./ApiManagers/DiagnosticManager";
 import { yellow } from "colors";
 
 export const publicDirectory = path.resolve(__dirname, "public");
 export const filesDirectory = path.resolve(publicDirectory, "files");
 
+export const ExitHandlers = new Array<() => void>();
+
 /**
  * These are the functions run before the server starts
  * listening. Anything that must be complete
  * before clients can access the server should be run or awaited here.
  */
 async function preliminaryFunctions() {
+    process.on('SIGINT', () => {
+        const { stdin, stdout, stderr } = process;
+        stdin.resume();
+        stdout.resume();
+        stderr.resume();
+        ExitHandlers.forEach(handler => handler());
+        console.log("Okay, now we're done...");
+        // process.exit(0);
+    });
+
+    (process as any).on('cleanup', () => {
+        console.log("CLEANING UP!");
+    });
+
+    process.on('exit', function () {
+        (process.emit as Function)('cleanup');
+    });
+
+    //catch uncaught exceptions, trace, then exit normally
+    process.on('uncaughtException', function (e) {
+        console.log('Uncaught Exception...');
+        process.exit(99);
+    });
+    process.on('unhandledRejection', function (e) {
+        console.log('Unhandled Rejection...');
+        process.exit(99);
+    });
+
+    await SolrManager.initializeSolr();
     await GoogleCredentialsLoader.loadCredentials();
     GoogleApiServerUtils.processProjectCredentials();
     await DashUploadUtils.buildFileDirectories();
@@ -57,7 +87,6 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }:
         new UserManager(),
         new UploadManager(),
         new DownloadManager(),
-        new DiagnosticManager(),
         new SearchManager(),
         new PDFManager(),
         new DeleteManager(),
@@ -79,6 +108,12 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }:
         onValidation: ({ res }) => res.redirect("/home")
     });
 
+    addSupervisedRoute({
+        method: Method.GET,
+        subscription: "/serverHeartbeat",
+        onValidation: ({ res }) => res.send(true)
+    });
+
     const serve: OnUnauthenticated = ({ req, res }) => {
         const detector = new mobileDetect(req.headers['user-agent'] || "");
         const filename = detector.mobile() !== null ? 'mobile/image.html' : 'index.html';
-- 
cgit v1.2.3-70-g09d2


From 4ab742c54d600fb62b02268f48e711258558924b Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 10 Dec 2019 18:47:27 -0500
Subject: fixes to color marks

---
 src/client/util/RichTextSchema.tsx  | 2 +-
 src/client/util/TooltipTextMenu.tsx | 6 ++++--
 2 files changed, 5 insertions(+), 3 deletions(-)

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

diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 7cb8448ca..4369be1ee 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -313,7 +313,7 @@ export const marks: { [index: string]: MarkSpec } = {
         attrs: {
             color: { default: "#000" }
         },
-        inclusive: false,
+        inclusive: true,
         parseDOM: [{
             tag: "span", getAttrs(dom: any) {
                 return { color: dom.getAttribute("color") };
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 01d566831..f29dbf2e4 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -839,9 +839,11 @@ export class TooltipTextMenu {
     }
 
     public static insertColor(color: String, state: EditorState<any>, dispatch: any) {
-        if (state.selection.empty) return false;
-
         const colorMark = state.schema.mark(state.schema.marks.pFontColor, { color: color });
+        if (state.selection.empty) {
+            dispatch(state.tr.addStoredMark(colorMark));
+            return false;
+        }
         dispatch(state.tr.addMark(state.selection.from, state.selection.to, colorMark));
     }
 
-- 
cgit v1.2.3-70-g09d2


From 9d8845fb64c08729b446f12206aa5ed215228f4e Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 11 Dec 2019 17:00:58 -0500
Subject: cleaned up toottipmenu and richtextschema a bit.  fixed some problems
 with text styles.  fixed warnings.

---
 logs/npm-2-Wed, 11 Dec 2019 08:44:28 GMT.log    | 274 ----------------
 logs/server_pids.txt                            |   1 -
 src/client/util/ProsemirrorExampleTransfer.ts   |   2 +-
 src/client/util/RichTextRules.ts                | 162 +++++-----
 src/client/util/RichTextSchema.tsx              |  68 ++--
 src/client/util/TooltipTextMenu.tsx             | 414 +++++-------------------
 src/client/views/DocumentDecorations.tsx        |   4 +-
 src/client/views/EditableView.tsx               |   2 +-
 src/client/views/MainView.tsx                   |   2 +-
 src/client/views/collections/CollectionView.tsx |   6 +-
 src/client/views/nodes/DocumentView.tsx         |   2 +-
 src/client/views/nodes/FormattedTextBox.tsx     |  32 +-
 12 files changed, 228 insertions(+), 741 deletions(-)
 delete mode 100644 logs/npm-2-Wed, 11 Dec 2019 08:44:28 GMT.log
 delete mode 100644 logs/server_pids.txt

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

diff --git a/logs/npm-2-Wed, 11 Dec 2019 08:44:28 GMT.log b/logs/npm-2-Wed, 11 Dec 2019 08:44:28 GMT.log
deleted file mode 100644
index 37e232d48..000000000
--- a/logs/npm-2-Wed, 11 Dec 2019 08:44:28 GMT.log	
+++ /dev/null
@@ -1,274 +0,0 @@
-
-> dash@1.0.0 start-spawn /Users/swilkinss2012/Documents/GitHub/Dash-Web
-> cross-env SPAWNED=true NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev -- src/server/index.ts
-
-Using ts-node version 7.0.1, typescript version 3.7.2
-objc[9678]: Class GNotificationCenterDelegate is implemented in both /Users/swilkinss2012/Documents/GitHub/Dash-Web/node_modules/sharp/vendor/lib/libgio-2.0.0.dylib (0x10838d578) and /Users/swilkinss2012/Documents/GitHub/Dash-Web/node_modules/canvas/build/Release/libgio-2.0.0.dylib (0x10afd9578). One of the two will be used. Which one is undefined.
-[34m
-starting execution of preliminary functions...[0m
-(node:9678) DeprecationWarning: current URL string parser is deprecated, and will be removed in a future version. To use the new parser, pass option { useNewUrlParser: true } to MongoClient.connect.
-[34mcompleted preliminary functions
-.[0m
-Starting type checking and linting service...
-Using 1 worker with 2048MB memory limit
-
-running server in [33mdevelopment[39m mode
-[33m[39m
-[33mregistering server routes...[39m
-[32mall server routes have been successfully registered:[39m
-[36m/[39m
-[36m/activity[39m
-[36m/buxton[39m
-[36m/delete[39m
-[36m/deleteAll[39m
-[36m/deleteWithAux[39m
-[36m/deleteWithGoogleCredentials[39m
-[36m/doc/:docId[39m
-[36m/downloadId/:docId[39m
-[36m/environment/:key[39m
-[36m/getCurrentUser[39m
-[36m/getUserDocumentId[39m
-[36m/getUsers[39m
-[36m/googleDocs/:sector/:action[39m
-[36m/googlePhotosMediaDownload[39m
-[36m/googlePhotosMediaUpload[39m
-[36m/home[39m
-[36m/imageHierarchyExport/:docId[39m
-[36m/inspectImage[39m
-[36m/persist[39m
-[36m/pull[39m
-[36m/readGoogleAccessToken[39m
-[36m/search[39m
-[36m/serializeDoc/:docId[39m
-[36m/serverHeartbeat[39m
-[36m/shutdown[39m
-[36m/solr/:action[39m
-[36m/textsearch[39m
-[36m/thumbnail/:filename[39m
-[36m/upload[39m
-[36m/uploadDoc[39m
-[36m/uploadURI[39m
-[36m/version[39m
-[36m/writeGoogleAccessToken[39m
-
-websocket listening on port [33m4321[39m
-server listening on port [33m1050[39m
-
-[34mℹ[39m [90m｢wdm｣[39m: wait until bundle finished: /serverHeartbeat
-[32muser samuel_wilkins@brown.edu has connected to the web socket[39m
-[32muser samuel_wilkins@brown.edu has connected to the web socket[39m
-[34mℹ[39m [90m｢wdm｣[39m: wait until bundle finished: /serverHeartbeat
-[34mℹ[39m [90m｢wdm｣[39m: wait until bundle finished: /serverHeartbeat
-[34mℹ[39m [90m｢wdm｣[39m: wait until bundle finished: /serverHeartbeat
-[34mℹ[39m [90m｢wdm｣[39m: wait until bundle finished: /serverHeartbeat
-[32muser samuel_wilkins@brown.edu has connected to the web socket[39m
-[32muser samuel_wilkins@brown.edu has connected to the web socket[39m
-Type checking and linting in progress...
-webpack built 8f6b743d91fd3862683b in 47419ms
-[33m⚠[39m [90m｢wdm｣[39m: Hash: [1m8f6b743d91fd3862683b[39m[22m
-Version: webpack [1m4.36.1[39m[22m
-Time: [1m47419[39m[22mms
-Built at: 12/11/2019 [1m3:45:31 AM[39m[22m
-                               [1mAsset[39m[22m       [1mSize[39m[22m               [1mChunks[39m[22m  [1m[39m[22m           [1m[39m[22m[1mChunk Names[39m[22m
-[1m[32m275711e56bd1bc79fdff544a3d7dbfae.png[39m[22m  289 bytes                     [1m[39m[22m  [1m[32m[emitted][39m[22m  
-[1m[32m32f1593298e6e7bee5673bf647328d72.png[39m[22m  429 bytes                     [1m[39m[22m  [1m[32m[emitted][39m[22m  
-[1m[32m718c914a99a2136c01c84e01f63e505a.png[39m[22m  829 bytes                     [1m[39m[22m  [1m[32m[emitted][39m[22m  
-[1m[32m906f1a1816c2a03b5c7612f6aa2ceece.png[39m[22m  281 bytes                     [1m[39m[22m  [1m[32m[emitted][39m[22m  
-                [1m[32massets/downarrow.png[39m[22m   3.28 KiB                     [1m[39m[22m  [1m[32m[emitted][39m[22m  
-                     [1m[32massets/env.json[39m[22m  360 bytes                     [1m[39m[22m  [1m[32m[emitted][39m[22m  
-            [1m[32massets/google_photos.png[39m[22m    114 KiB                     [1m[39m[22m  [1m[32m[emitted][39m[22m  
-              [1m[32massets/google_tags.png[39m[22m    7.9 KiB                     [1m[39m[22m  [1m[32m[emitted][39m[22m  
-                  [1m[32massets/loading.gif[39m[22m    112 KiB                     [1m[39m[22m  [1m[32m[emitted][39m[22m  
-                [1m[32massets/pdf.worker.js[39m[22m   1.55 MiB                     [1m[39m[22m  [1m[32m[emitted][39m[22m  
-                           [1m[32mbundle.js[39m[22m   20.8 MiB               [1mbundle[39m[22m  [1m[32m[emitted][39m[22m  bundle
-                       [1m[32mbundle.js.map[39m[22m   23.3 MiB               [1mbundle[39m[22m  [1m[32m[emitted][39m[22m  bundle
-                     [1m[32mdebug/repl.html[39m[22m  348 bytes                     [1m[39m[22m  [1m[32m[emitted][39m[22m  
-                     [1m[32mdebug/test.html[39m[22m  245 bytes                     [1m[39m[22m  [1m[32m[emitted][39m[22m  
-                   [1m[32mdebug/viewer.html[39m[22m  357 bytes                     [1m[39m[22m  [1m[32m[emitted][39m[22m  
-[1m[32me7a34b49f3c49ca0c25c76b30cd09e12.png[39m[22m  445 bytes                     [1m[39m[22m  [1m[32m[emitted][39m[22m  
-                      [1m[32mimageUpload.js[39m[22m   20.8 MiB          [1mimageUpload[39m[22m  [1m[32m[emitted][39m[22m  imageUpload
-                  [1m[32mimageUpload.js.map[39m[22m   23.3 MiB          [1mimageUpload[39m[22m  [1m[32m[emitted][39m[22m  imageUpload
-                          [1m[32mindex.html[39m[22m  593 bytes                     [1m[39m[22m  [1m[32m[emitted][39m[22m  
-                      [1m[32minkControls.js[39m[22m    116 KiB          [1minkControls[39m[22m  [1m[32m[emitted][39m[22m  inkControls
-                  [1m[32minkControls.js.map[39m[22m    122 KiB          [1minkControls[39m[22m  [1m[32m[emitted][39m[22m  inkControls
-                   [1m[32mmobile/image.html[39m[22m  333 bytes                     [1m[39m[22m  [1m[32m[emitted][39m[22m  
-                     [1m[32mmobile/ink.html[39m[22m  252 bytes                     [1m[39m[22m  [1m[32m[emitted][39m[22m  
-                             [1m[32mrepl.js[39m[22m   9.31 MiB                 [1mrepl[39m[22m  [1m[32m[emitted][39m[22m  repl
-                         [1m[32mrepl.js.map[39m[22m   10.5 MiB                 [1mrepl[39m[22m  [1m[32m[emitted][39m[22m  repl
-                             [1m[32mtest.js[39m[22m    1.2 MiB                 [1mtest[39m[22m  [1m[32m[emitted][39m[22m  test
-                         [1m[32mtest.js.map[39m[22m   1.42 MiB                 [1mtest[39m[22m  [1m[32m[emitted][39m[22m  test
-                            [1m[32mtest.pdf[39m[22m   53.6 KiB                     [1m[39m[22m  [1m[32m[emitted][39m[22m  
-              [1m[32mvendors~pdfjsWorker.js[39m[22m   1.55 MiB  [1mvendors~pdfjsWorker[39m[22m  [1m[32m[emitted][39m[22m  vendors~pdfjsWorker
-          [1m[32mvendors~pdfjsWorker.js.map[39m[22m   1.87 MiB  [1mvendors~pdfjsWorker[39m[22m  [1m[32m[emitted][39m[22m  vendors~pdfjsWorker
-                           [1m[32mviewer.js[39m[22m   9.47 MiB               [1mviewer[39m[22m  [1m[32m[emitted][39m[22m  viewer
-                       [1m[32mviewer.js.map[39m[22m   10.7 MiB               [1mviewer[39m[22m  [1m[32m[emitted][39m[22m  viewer
-Entrypoint [1mbundle[39m[22m = [1m[32mbundle.js[39m[22m [1m[32mbundle.js.map[39m[22m
-Entrypoint [1mviewer[39m[22m = [1m[32mviewer.js[39m[22m [1m[32mviewer.js.map[39m[22m
-Entrypoint [1mrepl[39m[22m = [1m[32mrepl.js[39m[22m [1m[32mrepl.js.map[39m[22m
-Entrypoint [1mtest[39m[22m = [1m[32mtest.js[39m[22m [1m[32mtest.js.map[39m[22m
-Entrypoint [1minkControls[39m[22m = [1m[32minkControls.js[39m[22m [1m[32minkControls.js.map[39m[22m
-Entrypoint [1mimageUpload[39m[22m = [1m[32mimageUpload.js[39m[22m [1m[32mimageUpload.js.map[39m[22m
-[19] [1mmulti ./src/client/views/Main.tsx webpack-hot-middleware/client?reload=true[39m[22m 40 bytes {[1m[33mbundle[39m[22m}[1m[32m [built][39m[22m
-[20] [1mmulti ./src/debug/Viewer.tsx webpack-hot-middleware/client?reload=true[39m[22m 40 bytes {[1m[33mviewer[39m[22m}[1m[32m [built][39m[22m
-[21] [1mmulti ./src/debug/Repl.tsx webpack-hot-middleware/client?reload=true[39m[22m 40 bytes {[1m[33mrepl[39m[22m}[1m[32m [built][39m[22m
-[22] [1mmulti ./src/debug/Test.tsx webpack-hot-middleware/client?reload=true[39m[22m 40 bytes {[1m[33mtest[39m[22m}[1m[32m [built][39m[22m
-[23] [1mmulti ./src/mobile/InkControls.tsx webpack-hot-middleware/client?reload=true[39m[22m 40 bytes {[1m[33minkControls[39m[22m}[1m[32m [built][39m[22m
-[24] [1mmulti ./src/mobile/ImageUpload.tsx webpack-hot-middleware/client?reload=true[39m[22m 40 bytes {[1m[33mimageUpload[39m[22m}[1m[32m [built][39m[22m
- [[1m./node_modules/mobx-react/index.module.js[39m[22m] 48.8 KiB {[1m[33mbundle[39m[22m} {[1m[33mviewer[39m[22m} {[1m[33mrepl[39m[22m} {[1m[33mimageUpload[39m[22m}[1m[32m [built][39m[22m
- [[1m./node_modules/mobx/lib/mobx.module.js[39m[22m] 175 KiB {[1m[33mbundle[39m[22m} {[1m[33mviewer[39m[22m} {[1m[33mrepl[39m[22m} {[1m[33mimageUpload[39m[22m}[1m[32m [built][39m[22m
- [./node_modules/webpack-hot-middleware/client.js?reload=true] [1m(webpack)-hot-middleware/client.js?reload=true[39m[22m 7.68 KiB {[1m[33mbundle[39m[22m} {[1m[33mviewer[39m[22m} {[1m[33mrepl[39m[22m} {[1m[33mtest[39m[22m} {[1m[33minkControls[39m[22m} {[1m[33mimageUpload[39m[22m}[1m[32m [built][39m[22m
- [[1m./src/client/views/Main.tsx[39m[22m] 4.03 KiB {[1m[33mbundle[39m[22m}[1m[32m [built][39m[22m
- [[1m./src/debug/Repl.tsx[39m[22m] 6.87 KiB {[1m[33mrepl[39m[22m}[1m[32m [built][39m[22m
- [[1m./src/debug/Test.tsx[39m[22m] 1.02 KiB {[1m[33mtest[39m[22m}[1m[32m [built][39m[22m
- [[1m./src/debug/Viewer.tsx[39m[22m] 12.1 KiB {[1m[33mviewer[39m[22m}[1m[32m [built][39m[22m
- [[1m./src/mobile/ImageUpload.tsx[39m[22m] 9.97 KiB {[1m[33mimageUpload[39m[22m}[1m[32m [built][39m[22m
- [[1m./src/mobile/InkControls.tsx[39m[22m] 14 bytes {[1m[33minkControls[39m[22m}[1m[32m [built][39m[22m
-    + 1494 hidden modules
-
-[1m[33mWARNING in ./node_modules/typescript/lib/typescript.js 5121:41-60
-Critical dependency: the request of a dependency is an expression
- @ ./src/client/util/Scripting.ts
- @ ./src/debug/Viewer.tsx
- @ multi ./src/debug/Viewer.tsx webpack-hot-middleware/client?reload=true[39m[22m
-[34mℹ[39m [90m｢wdm｣[39m: Compiled with warnings.
-[34mℹ[39m [90m｢wdm｣[39m: Compiling...
-webpack building...
-[34mℹ[39m [90m｢wdm｣[39m: wait until bundle finished: /login
-[34mℹ[39m [90m｢wdm｣[39m: wait until bundle finished: /login
-[34mℹ[39m [90m｢wdm｣[39m: wait until bundle finished: /login
-[34mℹ[39m [90m｢wdm｣[39m: wait until bundle finished: /login
-[34mℹ[39m [90m｢wdm｣[39m: wait until bundle finished: /login
-Type checking and linting in progress...
-webpack built 8f6b743d91fd3862683b in 615ms
-[33m⚠[39m [90m｢wdm｣[39m: Hash: [1m8f6b743d91fd3862683b[39m[22m
-Version: webpack [1m4.36.1[39m[22m
-Time: [1m615[39m[22mms
-Built at: 12/11/2019 [1m3:45:33 AM[39m[22m
-                               [1mAsset[39m[22m       [1mSize[39m[22m               [1mChunks[39m[22m  [1m[39m[22m[1m[39m[22m[1mChunk Names[39m[22m
-[1m[32m275711e56bd1bc79fdff544a3d7dbfae.png[39m[22m  289 bytes                     [1m[39m[22m  [1m[32m[39m[22m
-[1m[32m32f1593298e6e7bee5673bf647328d72.png[39m[22m  429 bytes                     [1m[39m[22m  [1m[32m[39m[22m
-[1m[32m718c914a99a2136c01c84e01f63e505a.png[39m[22m  829 bytes                     [1m[39m[22m  [1m[32m[39m[22m
-[1m[32m906f1a1816c2a03b5c7612f6aa2ceece.png[39m[22m  281 bytes                     [1m[39m[22m  [1m[32m[39m[22m
-                           [1m[32mbundle.js[39m[22m   20.8 MiB               [1mbundle[39m[22m  [1m[32m[39m[22mbundle
-                       [1m[32mbundle.js.map[39m[22m   23.3 MiB               [1mbundle[39m[22m  [1m[32m[39m[22mbundle
-[1m[32me7a34b49f3c49ca0c25c76b30cd09e12.png[39m[22m  445 bytes                     [1m[39m[22m  [1m[32m[39m[22m
-                      [1m[32mimageUpload.js[39m[22m   20.8 MiB          [1mimageUpload[39m[22m  [1m[32m[39m[22mimageUpload
-                  [1m[32mimageUpload.js.map[39m[22m   23.3 MiB          [1mimageUpload[39m[22m  [1m[32m[39m[22mimageUpload
-                      [1m[32minkControls.js[39m[22m    116 KiB          [1minkControls[39m[22m  [1m[32m[39m[22minkControls
-                  [1m[32minkControls.js.map[39m[22m    122 KiB          [1minkControls[39m[22m  [1m[32m[39m[22minkControls
-                             [1m[32mrepl.js[39m[22m   9.31 MiB                 [1mrepl[39m[22m  [1m[32m[39m[22mrepl
-                         [1m[32mrepl.js.map[39m[22m   10.5 MiB                 [1mrepl[39m[22m  [1m[32m[39m[22mrepl
-                             [1m[32mtest.js[39m[22m    1.2 MiB                 [1mtest[39m[22m  [1m[32m[39m[22mtest
-                         [1m[32mtest.js.map[39m[22m   1.42 MiB                 [1mtest[39m[22m  [1m[32m[39m[22mtest
-              [1m[32mvendors~pdfjsWorker.js[39m[22m   1.55 MiB  [1mvendors~pdfjsWorker[39m[22m  [1m[32m[39m[22mvendors~pdfjsWorker
-          [1m[32mvendors~pdfjsWorker.js.map[39m[22m   1.87 MiB  [1mvendors~pdfjsWorker[39m[22m  [1m[32m[39m[22mvendors~pdfjsWorker
-                           [1m[32mviewer.js[39m[22m   9.47 MiB               [1mviewer[39m[22m  [1m[32m[39m[22mviewer
-                       [1m[32mviewer.js.map[39m[22m   10.7 MiB               [1mviewer[39m[22m  [1m[32m[39m[22mviewer
-Entrypoint [1mbundle[39m[22m = [1m[32mbundle.js[39m[22m [1m[32mbundle.js.map[39m[22m
-Entrypoint [1mviewer[39m[22m = [1m[32mviewer.js[39m[22m [1m[32mviewer.js.map[39m[22m
-Entrypoint [1mrepl[39m[22m = [1m[32mrepl.js[39m[22m [1m[32mrepl.js.map[39m[22m
-Entrypoint [1mtest[39m[22m = [1m[32mtest.js[39m[22m [1m[32mtest.js.map[39m[22m
-Entrypoint [1minkControls[39m[22m = [1m[32minkControls.js[39m[22m [1m[32minkControls.js.map[39m[22m
-Entrypoint [1mimageUpload[39m[22m = [1m[32mimageUpload.js[39m[22m [1m[32mimageUpload.js.map[39m[22m
-[19] [1mmulti ./src/client/views/Main.tsx webpack-hot-middleware/client?reload=true[39m[22m 40 bytes {[1m[33mbundle[39m[22m}
-[20] [1mmulti ./src/debug/Viewer.tsx webpack-hot-middleware/client?reload=true[39m[22m 40 bytes {[1m[33mviewer[39m[22m}
-[21] [1mmulti ./src/debug/Repl.tsx webpack-hot-middleware/client?reload=true[39m[22m 40 bytes {[1m[33mrepl[39m[22m}
-[22] [1mmulti ./src/debug/Test.tsx webpack-hot-middleware/client?reload=true[39m[22m 40 bytes {[1m[33mtest[39m[22m}
-[23] [1mmulti ./src/mobile/InkControls.tsx webpack-hot-middleware/client?reload=true[39m[22m 40 bytes {[1m[33minkControls[39m[22m}
-[24] [1mmulti ./src/mobile/ImageUpload.tsx webpack-hot-middleware/client?reload=true[39m[22m 40 bytes {[1m[33mimageUpload[39m[22m}
- [[1m./node_modules/mobx-react/index.module.js[39m[22m] 48.8 KiB {[1m[33mbundle[39m[22m} {[1m[33mviewer[39m[22m} {[1m[33mrepl[39m[22m} {[1m[33mimageUpload[39m[22m}
- [[1m./node_modules/mobx/lib/mobx.module.js[39m[22m] 175 KiB {[1m[33mbundle[39m[22m} {[1m[33mviewer[39m[22m} {[1m[33mrepl[39m[22m} {[1m[33mimageUpload[39m[22m}
- [./node_modules/webpack-hot-middleware/client.js?reload=true] [1m(webpack)-hot-middleware/client.js?reload=true[39m[22m 7.68 KiB {[1m[33mbundle[39m[22m} {[1m[33mviewer[39m[22m} {[1m[33mrepl[39m[22m} {[1m[33mtest[39m[22m} {[1m[33minkControls[39m[22m} {[1m[33mimageUpload[39m[22m}
- [[1m./src/client/views/Main.tsx[39m[22m] 4.03 KiB {[1m[33mbundle[39m[22m}
- [[1m./src/debug/Repl.tsx[39m[22m] 6.87 KiB {[1m[33mrepl[39m[22m}
- [[1m./src/debug/Test.tsx[39m[22m] 1.02 KiB {[1m[33mtest[39m[22m}
- [[1m./src/debug/Viewer.tsx[39m[22m] 12.1 KiB {[1m[33mviewer[39m[22m}
- [[1m./src/mobile/ImageUpload.tsx[39m[22m] 9.97 KiB {[1m[33mimageUpload[39m[22m}
- [[1m./src/mobile/InkControls.tsx[39m[22m] 14 bytes {[1m[33minkControls[39m[22m}
-    + 1494 hidden modules
-
-[1m[33mWARNING in ./node_modules/typescript/lib/typescript.js 5121:41-60
-Critical dependency: the request of a dependency is an expression
- @ ./src/client/util/Scripting.ts
- @ ./src/debug/Viewer.tsx
- @ multi ./src/debug/Viewer.tsx webpack-hot-middleware/client?reload=true[39m[22m
-[34mℹ[39m [90m｢wdm｣[39m: Compiled with warnings.
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(81,21):
-prefer-const: Identifier 'marks' is never reassigned; use 'const' instead of 'let'.
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(83,25):
-prefer-const: Identifier 'tr' is never reassigned; use 'const' instead of 'let'.
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(86,21):
-prefer-const: Identifier 'isValidColor' is never reassigned; use 'const' instead of 'let'.
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(87,25):
-prefer-const: Identifier 's' is never reassigned; use 'const' instead of 'var'.
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(89,36):
-triple-equals: == should be ===
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(90,18):
-semicolon: Missing semicolon
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(99,21):
-prefer-const: Identifier 'tr' is never reassigned; use 'const' instead of 'let'.
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(100,21):
-prefer-const: Identifier 'marks' is never reassigned; use 'const' instead of 'let'.
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(126,25):
-prefer-const: Identifier 'node' is never reassigned; use 'const' instead of 'let'.
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(141,25):
-prefer-const: Identifier 'node' is never reassigned; use 'const' instead of 'let'.
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(154,66):
-no-unnecessary-type-assertion: This assertion is unnecessary since it does not change the type of the expression.
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(155,25):
-prefer-const: Identifier 'node' is never reassigned; use 'const' instead of 'let'.
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(155,33):
-no-unnecessary-type-assertion: This assertion is unnecessary since it does not change the type of the expression.
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(160,25):
-prefer-const: Identifier 'node' is never reassigned; use 'const' instead of 'let'.
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(192,29):
-prefer-const: Identifier 'doc' is never reassigned; use 'const' instead of 'let'.
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextSchema.tsx(640,134):
-semicolon: Missing semicolon
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextSchema.tsx(649,21):
-prefer-const: Identifier 'expand' is never reassigned; use 'const' instead of 'let'.
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextSchema.tsx(650,21):
-prefer-const: Identifier 'tr' is never reassigned; use 'const' instead of 'let'.
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextSchema.tsx(654,138):
-semicolon: Missing semicolon
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextSchema.tsx(658,10):
-semicolon: Missing semicolon
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionView.tsx(243,13):
-prefer-const: Identifier 'main' is never reassigned; use 'const' instead of 'let'.
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionView.tsx(244,13):
-prefer-const: Identifier 'next' is never reassigned; use 'const' instead of 'let'.
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionView.tsx(245,13):
-prefer-const: Identifier 'prev' is never reassigned; use 'const' instead of 'let'.
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/DocumentDecorations.tsx(88,21):
-prefer-const: Identifier 'selectionTitleFieldKey' is never reassigned; use 'const' instead of 'let'.
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/DocumentDecorations.tsx(95,8):
-semicolon: Missing semicolon
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/EditableView.tsx(123,13):
-prefer-const: Identifier 'wasFocused' is never reassigned; use 'const' instead of 'let'.
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/MainView.tsx(289,11):
-semicolon: Missing semicolon
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/DocumentView.tsx(151,25):
-prefer-const: Identifier 'any' is never reassigned; use 'const' instead of 'let'.
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(910,13):
-prefer-const: Identifier 'prosediv' is never reassigned; use 'const' instead of 'let'.
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(911,13):
-prefer-const: Identifier 'keeplocation' is never reassigned; use 'const' instead of 'let'.
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(913,13):
-prefer-const: Identifier 'pos' is never reassigned; use 'const' instead of 'let'.
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(928,17):
-prefer-const: Identifier 'pcords' is never reassigned; use 'const' instead of 'let'.
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(929,17):
-prefer-const: Identifier 'node' is never reassigned; use 'const' instead of 'let'.
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(935,21):
-prefer-const: Identifier 'lastNode' is never reassigned; use 'const' instead of 'let'.
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(976,71):
-semicolon: Missing semicolon
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(987,17):
-prefer-const: Identifier '$pos' is never reassigned; use 'const' instead of 'let'.
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(1001,25):
-prefer-const: Identifier '$olist_pos' is never reassigned; use 'const' instead of 'let'.
-WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(1103,17):
-prefer-const: Identifier 'newHeight' is never reassigned; use 'const' instead of 'let'.
-No type errors found
-Version: typescript 3.7.2, tslint 5.18.0
-Time: 5473ms
diff --git a/logs/server_pids.txt b/logs/server_pids.txt
deleted file mode 100644
index 2aa143f24..000000000
--- a/logs/server_pids.txt
+++ /dev/null
@@ -1 +0,0 @@
-9675 created at Wed, 11 Dec 2019 08:44:28 GMT
diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts
index f1fa6f11d..3324d8abe 100644
--- a/src/client/util/ProsemirrorExampleTransfer.ts
+++ b/src/client/util/ProsemirrorExampleTransfer.ts
@@ -105,7 +105,7 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?:
         return true;
     });
 
-    bind("Mod-s", TooltipTextMenu.insertStar);
+    bind("Mod-s", TooltipTextMenu.insertSummarizer);
 
     bind("Tab", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
         const ref = state.selection;
diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts
index 22b2a8204..364c85165 100644
--- a/src/client/util/RichTextRules.ts
+++ b/src/client/util/RichTextRules.ts
@@ -62,8 +62,9 @@ export const inpRules = {
             }
         ),
 
+        // set the font size using #<font-size> 
         new InputRule(
-            new RegExp(/^#([0-9]+)\s$/),
+            new RegExp(/^%([0-9]+)\s$/),
             (state, match, start, end) => {
                 const size = Number(match[1]);
                 const ruleProvider = FormattedTextBox.FocusedBox!.props.ruleProvider;
@@ -74,131 +75,123 @@ export const inpRules = {
                 }
                 return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontSize.create({ fontSize: size }));
             }),
+
+        // make current selection a hyperlink portal (assumes % was used to initiate an EnteringStyle mode)
+        new InputRule(
+            new RegExp(/@$/),
+            (state, match, start, end) => {
+                if (state.selection.to === state.selection.from || !(schema as any).EnteringStyle) return null;
+
+                const value = state.doc.textBetween(start, end);
+                if (value) {
+                    DocServer.GetRefField(value).then(docx => {
+                        const doc = ((docx instanceof Doc) && docx) || Docs.Create.FreeformDocument([], { title: value, width: 500, height: 500 }, value);
+                        DocUtils.Publish(doc, value, returnFalse, returnFalse);
+                    });
+                    const link = state.schema.marks.link.create({ href: Utils.prepend("/doc/" + value), location: "onRight", title: value });
+                    return state.tr.addMark(start, end, link);
+                }
+                return state.tr;
+            }),
+
+        // activate a style by name using prefix '%'
         new InputRule(
             new RegExp(/%[a-z]+$/),
             (state, match, start, end) => {
                 const color = match[0].substring(1, match[0].length);
-                let marks = TooltipTextMenuManager.Instance._brushMap.get(color);
+                const marks = TooltipTextMenuManager.Instance._brushMap.get(color);
                 if (marks) {
-                    let tr = state.tr.deleteRange(start, end);
+                    const tr = state.tr.deleteRange(start, end);
                     return marks ? Array.from(marks).reduce((tr, m) => tr.addStoredMark(m), tr) : tr;
                 }
-                let isValidColor = (strColor: string) => {
-                    var s = new Option().style;
+                const isValidColor = (strColor: string) => {
+                    const s = new Option().style;
                     s.color = strColor;
-                    return s.color == strColor.toLowerCase(); // 'false' if color wasn't assigned
-                }
+                    return s.color === strColor.toLowerCase(); // 'false' if color wasn't assigned
+                };
                 if (isValidColor(color)) {
                     return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontColor.create({ color: color }));
                 }
                 return null;
             }),
+        // stop using active style
         new InputRule(
             new RegExp(/%%$/),
             (state, match, start, end) => {
-                let tr = state.tr.deleteRange(start, end);
-                let marks = state.tr.selection.$anchor.nodeBefore?.marks;
+                const tr = state.tr.deleteRange(start, end);
+                const marks = state.tr.selection.$anchor.nodeBefore?.marks;
                 return marks ? Array.from(marks).filter(m => m !== state.schema.marks.user_mark).reduce((tr, m) => tr.removeStoredMark(m), tr) : tr;
             }),
+
+        // set the Todo user-tag on the current selection (assumes % was used to initiate an EnteringStyle mode)
         new InputRule(
-            new RegExp(/t$/),
-            (state, match, start, end) => {
-                if (state.selection.to === state.selection.from && !(state as any).EnteringStyle) return null;
-                const node = (state.doc.resolve(start) as any).nodeAfter;
-                if (node?.marks.findIndex((m: any) => m.type === schema.marks.user_tag) !== -1) return state.tr.removeMark(start, end, schema.marks.user_tag);
-                return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: "todo", modified: Math.round(Date.now() / 1000 / 60) })) : state.tr;
-            }),
-        new InputRule(
-            new RegExp(/i$/),
+            new RegExp(/[ti!x]$/),
             (state, match, start, end) => {
-                if (state.selection.to === state.selection.from && !(state as any).EnteringStyle) return null;
+                if (state.selection.to === state.selection.from || !(schema as any).EnteringStyle) return null;
+                const tag = match[0] === "t" ? "todo" : match[0] === "i" ? "ignore" : match[0] === "x" ? "disagree" : match[0] === "!" ? "important" : "??";
                 const node = (state.doc.resolve(start) as any).nodeAfter;
                 if (node?.marks.findIndex((m: any) => m.type === schema.marks.user_tag) !== -1) return state.tr.removeMark(start, end, schema.marks.user_tag);
-                return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: "ignore", modified: Math.round(Date.now() / 1000 / 60) })) : state.tr;
+                return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: tag, modified: Math.round(Date.now() / 1000 / 60) })) : state.tr;
             }),
+
+        // set the First-line indent node type for the selection's paragraph (assumes % was used to initiate an EnteringStyle mode)
         new InputRule(
-            new RegExp(/d$/),
+            new RegExp(/(%d|d)$/),
             (state, match, start, end) => {
-                if (state.selection.to === state.selection.from) return null;
+                if (!match[0].startsWith("%") && !(schema as any).EnteringStyle) return null;
                 const pos = (state.doc.resolve(start) as any);
-                let depth = pos.path.length / 3 - 1;
-                for (; depth >= 0; depth--) {
-                    let node = pos.node(depth);
+                for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) {
+                    const node = pos.node(depth);
                     if (node.type === schema.nodes.paragraph) {
                         const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, node.type, { ...node.attrs, indent: node.attrs.indent === 25 ? undefined : 25 });
-                        return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
+                        const result = replaced.setSelection(new TextSelection(replaced.doc.resolve(start)));
+                        return match[0].startsWith("%") ? result.deleteRange(start, end) : result;
                     }
                 }
                 return null;
             }),
+
+        // set the Hanging indent node type for the current selection's paragraph (assumes % was used to initiate an EnteringStyle mode)
         new InputRule(
-            new RegExp(/h$/),
+            new RegExp(/(%h|h)$/),
             (state, match, start, end) => {
-                if (state.selection.to === state.selection.from) return null;
+                if (!match[0].startsWith("%") && !(schema as any).EnteringStyle) return null;
                 const pos = (state.doc.resolve(start) as any);
-                let depth = pos.path.length / 3 - 1;
-                for (; depth >= 0; depth--) {
-                    let node = pos.node(depth);
+                for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) {
+                    const node = pos.node(depth);
                     if (node.type === schema.nodes.paragraph) {
                         const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, node.type, { ...node.attrs, indent: node.attrs.indent === -25 ? undefined : -25 });
-                        return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
+                        const result = replaced.setSelection(new TextSelection(replaced.doc.resolve(start)));
+                        return match[0].startsWith("%") ? result.deleteRange(start, end) : result;
                     }
                 }
                 return null;
             }),
+        // set the Quoted indent node type for the current selection's paragraph (assumes % was used to initiate an EnteringStyle mode)
         new InputRule(
-            new RegExp(/q$/),
+            new RegExp(/(%q|q)$/),
             (state, match, start, end) => {
-                if (state.selection.to === state.selection.from) return null;
+                if (!match[0].startsWith("%") && !(schema as any).EnteringStyle) return null;
                 const pos = (state.doc.resolve(start) as any);
-                if (state.selection instanceof NodeSelection && (state.selection as NodeSelection).node.type === schema.nodes.ordered_list) {
-                    let node = (state.selection as NodeSelection).node;
+                if (state.selection instanceof NodeSelection && state.selection.node.type === schema.nodes.ordered_list) {
+                    const node = state.selection.node;
                     return state.tr.setNodeMarkup(pos.pos, node.type, { ...node.attrs, indent: node.attrs.indent === 30 ? undefined : 30 });
                 }
-                let depth = pos.path.length / 3 - 1;
-                for (; depth >= 0; depth--) {
-                    let node = pos.node(depth);
+                for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) {
+                    const node = pos.node(depth);
                     if (node.type === schema.nodes.paragraph) {
                         const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, node.type, { ...node.attrs, inset: node.attrs.inset === 30 ? undefined : 30 });
-                        return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
+                        const result = replaced.setSelection(new TextSelection(replaced.doc.resolve(start)));
+                        return match[0].startsWith("%") ? result.deleteRange(start, end) : result;
                     }
                 }
                 return null;
             }),
-        new InputRule(
-            new RegExp(/!$/),
-            (state, match, start, end) => {
-                if (state.selection.to === state.selection.from && !(state as any).EnteringStyle) return null;
-                const node = (state.doc.resolve(start) as any).nodeAfter;
-                if (node?.marks.findIndex((m: any) => m.type === schema.marks.user_tag) !== -1) return state.tr.removeMark(start, end, schema.marks.user_tag);
-                return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: "important", modified: Math.round(Date.now() / 1000 / 60) })) : state.tr;
-            }),
-        new InputRule(
-            new RegExp(/x$/),
-            (state, match, start, end) => {
-                if (state.selection.to === state.selection.from && !(state as any).EnteringStyle) return null;
-                const node = (state.doc.resolve(start) as any).nodeAfter;
-                if (node?.marks.findIndex((m: any) => m.type === schema.marks.user_tag) !== -1) return state.tr.removeMark(start, end, schema.marks.user_tag);
-                return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: "disagree", modified: Math.round(Date.now() / 1000 / 60) })) : state.tr;
-            }),
-        new InputRule(
-            new RegExp(/@$/),
-            (state, match, start, end) => {
-                if (state.selection.to === state.selection.from) return null;
 
-                const value = state.doc.textBetween(start, end);
-                if (value) {
-                    DocServer.GetRefField(value).then(docx => {
-                        let doc = ((docx instanceof Doc) && docx) || Docs.Create.FreeformDocument([], { title: value, width: 500, height: 500 }, value);
-                        DocUtils.Publish(doc, value, returnFalse, returnFalse);
-                    });
-                    const link = state.schema.marks.link.create({ href: Utils.prepend("/doc/" + value), location: "onRight", title: value });
-                    return state.tr.addMark(start, end, link);
-                }
-                return state.tr;
-            }),
+
+        // center justify text
         new InputRule(
-            new RegExp(/^\^\^\s$/),
+            new RegExp(/%\^$/),
             (state, match, start, end) => {
                 const node = (state.doc.resolve(start) as any).nodeAfter;
                 const sm = state.storedMarks || undefined;
@@ -212,8 +205,9 @@ export const inpRules = {
                     state.tr;
                 return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
             }),
+        // left justify text
         new InputRule(
-            new RegExp(/^\[\[\s$/),
+            new RegExp(/%\[$/),
             (state, match, start, end) => {
                 const node = (state.doc.resolve(start) as any).nodeAfter;
                 const sm = state.storedMarks || undefined;
@@ -227,8 +221,9 @@ export const inpRules = {
                     state.tr;
                 return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
             }),
+        // right justify text
         new InputRule(
-            new RegExp(/^\]\]\s$/),
+            new RegExp(/%\]$/),
             (state, match, start, end) => {
                 const node = (state.doc.resolve(start) as any).nodeAfter;
                 const sm = state.storedMarks || undefined;
@@ -243,7 +238,7 @@ export const inpRules = {
                 return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
             }),
         new InputRule(
-            new RegExp(/##\s$/),
+            new RegExp(/%#$/),
             (state, match, start, end) => {
                 const target = Docs.Create.TextDocument({ width: 75, height: 35, backgroundColor: "yellow", autoHeight: true, fontSize: 9, title: "inline comment" });
                 const node = (state.doc.resolve(start) as any).nodeAfter;
@@ -255,26 +250,25 @@ export const inpRules = {
                 return replaced;//.setSelection(new NodeSelection(replaced.doc.resolve(end)));
             }),
         new InputRule(
-            new RegExp(/\(\(/),
+            new RegExp(/%\(/),
             (state, match, start, end) => {
                 const node = (state.doc.resolve(start) as any).nodeAfter;
                 const sm = state.storedMarks || undefined;
-                const mark = state.schema.marks.highlight.create();
+                const mark = state.schema.marks.summarizeInclusive.create();
                 const selected = state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).addMark(start, end, mark);
                 const content = selected.selection.content();
                 const replaced = node ? selected.replaceRangeWith(start, start,
-                    schema.nodes.star.create({ visibility: true, text: content, textslice: content.toJSON() })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
+                    schema.nodes.summary.create({ visibility: true, text: content, textslice: content.toJSON() })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
                     state.tr;
                 return replaced.setSelection(new TextSelection(replaced.doc.resolve(end + 1)));
             }),
         new InputRule(
-            new RegExp(/\)\)/),
+            new RegExp(/%\)/),
             (state, match, start, end) => {
-                const mark = state.schema.marks.highlight.create();
-                return state.tr.removeStoredMark(mark);
+                return state.tr.removeStoredMark(state.schema.marks.summarizeInclusive.create());
             }),
         new InputRule(
-            new RegExp(/\^f\s$/),
+            new RegExp(/%f\$/),
             (state, match, start, end) => {
                 const newNode = schema.nodes.footnote.create({});
                 const tr = state.tr;
@@ -283,9 +277,5 @@ export const inpRules = {
                     tr.doc.resolve(  // get the location of the footnote node by subtracting the nodesize of the footnote from the current insertion point anchor (which will be immediately after the footnote node)
                         tr.selection.anchor - tr.selection.$anchor.nodeBefore!.nodeSize)));
             }),
-        // let newNode = schema.nodes.footnote.create({});
-        // if (dispatch && state.selection.from === state.selection.to) {
-        //     return true;
-        // }
     ]
 };
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index f9251fb7e..543f45731 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -109,7 +109,7 @@ export const nodes: { [index: string]: NodeSpec } = {
         },
     },
 
-    star: {
+    summary: {
         inline: true,
         attrs: {
             visibility: { default: false },
@@ -121,15 +121,6 @@ export const nodes: { [index: string]: NodeSpec } = {
             const attrs = { style: `width: 40px` };
             return ["span", { ...node.attrs, ...attrs }];
         },
-        // parseDOM: [{
-        //     tag: "star", getAttrs(dom: any) {
-        //         return {
-        //             visibility: dom.getAttribute("visibility"),
-        //             oldtext: dom.getAttribute("oldtext"),
-        //             oldtextlen: dom.getAttribute("oldtextlen"),
-        //         }
-        //     }
-        // }]
     },
 
     // :: NodeSpec An inline image (`<img>`) node. Supports `src`,
@@ -228,6 +219,7 @@ export const nodes: { [index: string]: NodeSpec } = {
             mapStyle: { default: "decimal" },
             setFontSize: { default: undefined },
             setFontFamily: { default: "inherit" },
+            setFontColor: { default: "inherit" },
             inheritedFontSize: { default: undefined },
             visibility: { default: true },
             indent: { default: undefined }
@@ -237,8 +229,10 @@ export const nodes: { [index: string]: NodeSpec } = {
             const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : "";
             const fsize = node.attrs.setFontSize ? node.attrs.setFontSize : node.attrs.inheritedFontSize;
             const ffam = node.attrs.setFontFamily;
-            return node.attrs.visibility ? ['ol', { class: `${map}-ol`, style: `list-style: none; font-size: ${fsize}; font-family: ${ffam}; margin-left: ${node.attrs.indent}` }, 0] :
-                ['ol', { class: `${map}-ol`, style: `list-style: none; font-size: ${fsize}; font-family: ${ffam}` }];
+            const color = node.attrs.setFontColor;
+            return node.attrs.visibility ?
+                ['ol', { class: `${map}-ol`, style: `list-style: none; font-size: ${fsize}; font-family: ${ffam}; color:${color}; margin-left: ${node.attrs.indent}` }, 0] :
+                ['ol', { class: `${map}-ol`, style: `list-style: none;` }];
         }
     },
 
@@ -318,7 +312,7 @@ export const marks: { [index: string]: MarkSpec } = {
         attrs: {
             highlight: { default: "transparent" }
         },
-        inclusive: false,
+        inclusive: true,
         parseDOM: [{
             tag: "span", getAttrs(dom: any) {
                 return { highlight: dom.getAttribute("backgroundColor") };
@@ -401,7 +395,7 @@ export const marks: { [index: string]: MarkSpec } = {
         }
     },
 
-    highlight: {
+    summarizeInclusive: {
         parseDOM: [
             {
                 tag: "span",
@@ -410,7 +404,7 @@ export const marks: { [index: string]: MarkSpec } = {
                         const 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) {
+                            p.parentElement.outerHTML.indexOf("text-decoration-style: solid") !== -1) {
                             return null;
                         }
                     }
@@ -419,6 +413,31 @@ export const marks: { [index: string]: MarkSpec } = {
             },
         ],
         inclusive: true,
+        toDOM() {
+            return ['span', {
+                style: 'text-decoration: underline; text-decoration-style: solid; text-decoration-color: rgba(204, 206, 210, 0.92)'
+            }];
+        }
+    },
+
+    summarize: {
+        inclusive: false,
+        parseDOM: [
+            {
+                tag: "span",
+                getAttrs: (p: any) => {
+                    if (typeof (p) !== "string") {
+                        const 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;
+                }
+            },
+        ],
         toDOM() {
             return ['span', {
                 style: 'text-decoration: underline; text-decoration-style: dotted; text-decoration-color: rgba(204, 206, 210, 0.92)'
@@ -637,7 +656,7 @@ export class DashDocCommentView {
             }
             const dashDoc = view.state.schema.nodes.dashDoc.create({ width: 75, height: 35, title: "dashDoc", docid: node.attrs.docid, float: "right" });
             view.dispatch(view.state.tr.insert(getPos() + 1, dashDoc));
-            setTimeout(() => { try { view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.tr.doc, getPos() + 2))) } catch (e) { } }, 0);
+            setTimeout(() => { try { view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.tr.doc, getPos() + 2))); } catch (e) { } }, 0);
             return undefined;
         };
         this._collapsed.onpointerdown = (e: any) => {
@@ -646,16 +665,16 @@ export class DashDocCommentView {
         this._collapsed.onpointerup = (e: any) => {
             const target = targetNode();
             if (target) {
-                let expand = target.hidden;
-                let tr = view.state.tr.setNodeMarkup(target.pos, undefined, { ...target.node.attrs, hidden: target.node.attrs.hidden ? false : true });
+                const expand = target.hidden;
+                const tr = view.state.tr.setNodeMarkup(target.pos, undefined, { ...target.node.attrs, hidden: target.node.attrs.hidden ? false : true });
                 view.dispatch(tr.setSelection(TextSelection.create(tr.doc, getPos() + (expand ? 2 : 1)))); // update the attrs 
                 setTimeout(() => {
                     expand && DocServer.GetRefField(node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc));
-                    try { view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.tr.doc, getPos() + (expand ? 2 : 1)))) } catch (e) { }
+                    try { view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.tr.doc, getPos() + (expand ? 2 : 1)))); } catch (e) { }
                 }, 0);
             }
             e.stopPropagation();
-        }
+        };
         this._collapsed.onpointerenter = (e: any) => {
             DocServer.GetRefField(node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc));
             e.preventDefault();
@@ -908,7 +927,7 @@ export class FootnoteView {
     ignoreMutation() { return true; }
 }
 
-export class SummarizedView {
+export class SummaryView {
     _collapsed: HTMLElement;
     _view: any;
     constructor(node: any, view: any, getPos: any) {
@@ -946,7 +965,8 @@ export class SummarizedView {
     className = (visible: boolean) => "formattedTextBox-summarizer" + (visible ? "" : "-collapsed");
 
     updateSummarizedText(start?: any) {
-        const mark = this._view.state.schema.marks.highlight.create();
+        const mtype = this._view.state.schema.marks.summarize;
+        const mtypeInc = this._view.state.schema.marks.summarizeInclusive;
         let endPos = start;
 
         const visited = new Set();
@@ -954,7 +974,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.find((m: any) => m.type === mark.type)) {
+                    if (node.marks.find((m: any) => m.type === mtype || m.type === mtypeInc)) {
                         visited.add(node);
                         endPos = i + node.nodeSize - 1;
                     }
@@ -979,7 +999,7 @@ const fromJson = schema.nodeFromJSON;
 
 schema.nodeFromJSON = (json: any) => {
     const node = fromJson(json);
-    if (json.type === "star") {
+    if (json.type === schema.marks.summarize.name) {
         node.attrs.text = Slice.fromJSON(schema, node.attrs.textslice);
     }
     return node;
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index f29dbf2e4..483ab40a7 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -1,4 +1,3 @@
-import { action } 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';
@@ -10,8 +9,6 @@ import { Utils } from "../../Utils";
 import { DocServer } from "../DocServer";
 import { FieldViewProps } from "../views/nodes/FieldView";
 import { FormattedTextBoxProps } from "../views/nodes/FormattedTextBox";
-import { DocumentManager } from "./DocumentManager";
-import { DragManager } from "./DragManager";
 import { LinkManager } from "./LinkManager";
 import { schema } from "./RichTextSchema";
 import "./TooltipTextMenu.scss";
@@ -20,13 +17,11 @@ import { updateBullets } from './ProsemirrorExampleTransfer';
 import { DocumentDecorations } from '../views/DocumentDecorations';
 import { SelectionManager } from './SelectionManager';
 import { PastelSchemaPalette, DarkPastelSchemaPalette } from '../../new_fields/SchemaHeaderField';
-import { Keys } from "../views/search/FilterBox";
 const { toggleMark, setBlockType } = require("prosemirror-commands");
 const { openPrompt, TextField } = require("./ProsemirrorCopy/prompt.js");
 
 //appears above a selection of text in a RichTextBox to give user options such as Bold, Italics, etc.
 export class TooltipTextMenu {
-
     public static Toolbar: HTMLDivElement | undefined;
 
     // editor state properties
@@ -48,10 +43,9 @@ export class TooltipTextMenu {
     // editor button doms
     private colorDom?: Node;
     private colorDropdownDom?: Node;
-    private highlightDom?: Node;
-    private highlightDropdownDom?: Node;
+    private highighterDom?: Node;
+    private highlighterDropdownDom?: Node;
     private linkEditor?: HTMLDivElement;
-    private linkText?: HTMLDivElement;
     private linkDrag?: HTMLImageElement;
     private _linkDropdownDom?: Node;
     private _brushdom?: Node;
@@ -94,7 +88,6 @@ export class TooltipTextMenu {
             { command: toggleMark(schema.marks.strikethrough), dom: this.svgIcon("strikethrough", "Strikethrough", "M496 224H293.9l-87.17-26.83A43.55 43.55 0 0 1 219.55 112h66.79A49.89 49.89 0 0 1 331 139.58a16 16 0 0 0 21.46 7.15l42.94-21.47a16 16 0 0 0 7.16-21.46l-.53-1A128 128 0 0 0 287.51 32h-68a123.68 123.68 0 0 0-123 135.64c2 20.89 10.1 39.83 21.78 56.36H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h480a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm-180.24 96A43 43 0 0 1 336 356.45 43.59 43.59 0 0 1 292.45 400h-66.79A49.89 49.89 0 0 1 181 372.42a16 16 0 0 0-21.46-7.15l-42.94 21.47a16 16 0 0 0-7.16 21.46l.53 1A128 128 0 0 0 224.49 480h68a123.68 123.68 0 0 0 123-135.64 114.25 114.25 0 0 0-5.34-24.36z") },
             { command: toggleMark(schema.marks.superscript), dom: this.svgIcon("superscript", "Superscript", "M496 160h-16V16a16 16 0 0 0-16-16h-48a16 16 0 0 0-14.29 8.83l-16 32A16 16 0 0 0 400 64h16v96h-16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h96a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM336 64h-67a16 16 0 0 0-13.14 6.87l-79.9 115-79.9-115A16 16 0 0 0 83 64H16A16 16 0 0 0 0 80v48a16 16 0 0 0 16 16h33.48l77.81 112-77.81 112H16a16 16 0 0 0-16 16v48a16 16 0 0 0 16 16h67a16 16 0 0 0 13.14-6.87l79.9-115 79.9 115A16 16 0 0 0 269 448h67a16 16 0 0 0 16-16v-48a16 16 0 0 0-16-16h-33.48l-77.81-112 77.81-112H336a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16z") },
             { command: toggleMark(schema.marks.subscript), dom: this.svgIcon("subscript", "Subscript", "M496 448h-16V304a16 16 0 0 0-16-16h-48a16 16 0 0 0-14.29 8.83l-16 32A16 16 0 0 0 400 352h16v96h-16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h96a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM336 64h-67a16 16 0 0 0-13.14 6.87l-79.9 115-79.9-115A16 16 0 0 0 83 64H16A16 16 0 0 0 0 80v48a16 16 0 0 0 16 16h33.48l77.81 112-77.81 112H16a16 16 0 0 0-16 16v48a16 16 0 0 0 16 16h67a16 16 0 0 0 13.14-6.87l79.9-115 79.9 115A16 16 0 0 0 269 448h67a16 16 0 0 0 16-16v-48a16 16 0 0 0-16-16h-33.48l-77.81-112 77.81-112H336a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16z") },
-            // { command: toggleMark(schema.marks.highlight), dom: this.icon("H", 'blue', 'Blue') }
         ];
 
         // add menu items
@@ -123,20 +116,15 @@ export class TooltipTextMenu {
                 if (dom.contains(e.target as Node)) {
                     e.stopPropagation();
                     command(this.view.state, this.view.dispatch, this.view);
-                    // if (this.view.state.selection.empty) {
-                    //     if (dom.style.color === "white") { dom.style.color = "greenyellow"; }
-                    //     else { dom.style.color = "white"; }
-                    // }
                 }
             });
-
         });
 
-        // highlight menu
-        this.highlightDom = this.createHighlightTool().render(this.view).dom;
-        this.highlightDropdownDom = this.createHighlightDropdown().render(this.view).dom;
-        this.tooltip.appendChild(this.highlightDom);
-        this.tooltip.appendChild(this.highlightDropdownDom);
+        // summarize menu
+        this.highighterDom = this.createHighlightTool().render(this.view).dom;
+        this.highlighterDropdownDom = this.createHighlightDropdown().render(this.view).dom;
+        this.tooltip.appendChild(this.highighterDom);
+        this.tooltip.appendChild(this.highlighterDropdownDom);
 
         // color menu
         this.colorDom = this.createColorTool().render(this.view).dom;
@@ -166,7 +154,7 @@ export class TooltipTextMenu {
         this.tooltip.appendChild(this._brushDropdownDom);
 
         // star
-        this.tooltip.appendChild(this.createStar().render(this.view).dom);
+        this.tooltip.appendChild(this.createSummarizer().render(this.view).dom);
 
         // list types dropdown
         this.updateListItemDropdown(":", this.listTypeBtnDom);
@@ -289,8 +277,6 @@ export class TooltipTextMenu {
             // stop moving when mouse button is released:
             document.onpointerup = null;
             document.onpointermove = null;
-            //self.highlightSearchTerms(self.state, ["hello"]);
-            //FormattedTextBox.Instance.unhighlightSearchTerms();
         }
     }
 
@@ -302,11 +288,10 @@ export class TooltipTextMenu {
             fontSizeBtns.push(this.dropdownFontSizeBtn(String(mark.attrs.fontSize), "color: black; width: 50px;", mark, this.view, this.changeToFontSize));
         });
 
-        const newfontSizeDom = (new Dropdown(fontSizeBtns, {
-            label: label,
-            css: "color:black; min-width: 60px;"
-        }) as MenuItem).render(this.view).dom;
-        if (this.fontSizeDom) { this.tooltip.replaceChild(newfontSizeDom, this.fontSizeDom); }
+        const newfontSizeDom = (new Dropdown(fontSizeBtns, { label: label, css: "color:black; min-width: 60px;" }) as MenuItem).render(this.view).dom;
+        if (this.fontSizeDom) {
+            this.tooltip.replaceChild(newfontSizeDom, this.fontSizeDom);
+        }
         else {
             this.tooltip.appendChild(newfontSizeDom);
         }
@@ -321,11 +306,10 @@ export class TooltipTextMenu {
             fontBtns.push(this.dropdownFontFamilyBtn(mark.attrs.family, "color: black; font-family: " + mark.attrs.family + ", sans-serif; width: 125px;", mark, this.view, this.changeToFontFamily));
         });
 
-        const newfontStyleDom = (new Dropdown(fontBtns, {
-            label: label,
-            css: "color:black; width: 125px;"
-        }) as MenuItem).render(this.view).dom;
-        if (this.fontStyleDom) { this.tooltip.replaceChild(newfontStyleDom, this.fontStyleDom); }
+        const newfontStyleDom = (new Dropdown(fontBtns, { label: label, css: "color:black; width: 125px;" }) as MenuItem).render(this.view).dom;
+        if (this.fontStyleDom) {
+            this.tooltip.replaceChild(newfontStyleDom, this.fontStyleDom);
+        }
         else {
             this.tooltip.appendChild(newfontStyleDom);
         }
@@ -333,94 +317,16 @@ export class TooltipTextMenu {
     }
 
     updateLinkMenu() {
-        if (!this.linkEditor || !this.linkText) {
-            this.linkEditor = document.createElement("div");
-            this.linkEditor.className = "ProseMirror-icon menuicon";
-            this.linkText = document.createElement("div");
-            this.linkText.setAttribute("contenteditable", "true");
-            this.linkText.style.whiteSpace = "nowrap";
-            this.linkText.style.width = "150px";
-            this.linkText.style.overflow = "hidden";
-            this.linkText.style.color = "white";
-            this.linkText.onpointerdown = (e: PointerEvent) => { e.stopPropagation(); };
-            const linkBtn = document.createElement("div");
-            linkBtn.textContent = ">>";
-            linkBtn.style.width = "10px";
-            linkBtn.style.height = "10px";
-            linkBtn.style.color = "white";
-            linkBtn.style.cssFloat = "left";
-            linkBtn.onpointerdown = (e: PointerEvent) => {
-                const node = this.view.state.selection.$from.nodeAfter;
-                const link = node && node.marks.find(m => m.type.name === "link");
-                if (link) {
-                    const href: string = link.attrs.href;
-                    if (href.indexOf(Utils.prepend("/doc/")) === 0) {
-                        const docid = href.replace(Utils.prepend("/doc/"), "");
-                        DocServer.GetRefField(docid).then(action((f: Opt<Field>) => {
-                            if (f instanceof Doc) {
-                                if (DocumentManager.Instance.getDocumentView(f)) {
-                                    DocumentManager.Instance.getDocumentView(f)!.props.focus(f, false);
-                                }
-                                else this.editorProps && this.editorProps.addDocTab(f, undefined, "onRight");
-                            }
-                        }));
-                    }
-                    // TODO This should have an else to handle external links
-                    e.stopPropagation();
-                    e.preventDefault();
-                }
-            };
-            this.linkDrag = document.createElement("img");
-            this.linkDrag.src = "https://seogurusnyc.com/wp-content/uploads/2016/12/link-1.png";
-            this.linkDrag.style.width = "15px";
-            this.linkDrag.style.height = "15px";
-            this.linkDrag.title = "Drag to create link";
-            this.linkDrag.id = "link-drag";
-            this.linkDrag.onpointerdown = (e: PointerEvent) => {
-                if (!this.editorProps) return;
-                const dragData = new DragManager.LinkDragData(this.editorProps.Document);
-                dragData.dontClearTextBox = true;
-                // hack to get source context -sy
-                const docView = DocumentManager.Instance.getDocumentView(this.editorProps.Document);
-                e.stopPropagation();
-                const ctrlKey = e.ctrlKey;
-                DragManager.StartLinkDrag(this.linkDrag!, dragData, e.clientX, e.clientY,
-                    {
-                        handlers: {
-                            dragComplete: action(() => {
-                                if (dragData.linkDocument) {
-                                    const linkDoc = dragData.linkDocument;
-                                    const proto = Doc.GetProto(linkDoc);
-                                    if (proto && docView) {
-                                        proto.sourceContext = docView.props.ContainingCollectionDoc;
-                                    }
-                                    const text = this.makeLink(linkDoc, StrCast(linkDoc.anchor2.title), ctrlKey ? "onRight" : "inTab");
-                                    if (linkDoc instanceof Doc && linkDoc.anchor2 instanceof Doc) {
-                                        proto.title = text === "" ? proto.title : text + " to " + linkDoc.anchor2.title; // TODODO open to more descriptive descriptions of following in text link
-                                    }
-                                }
-                            }),
-                        },
-                        hideSource: false
-                    });
-                e.stopPropagation();
-                e.preventDefault();
-            };
-            this.linkEditor.appendChild(this.linkDrag);
-            this.tooltip.appendChild(this.linkEditor);
-        }
-
-        const node = this.view.state.selection.$from.nodeAfter;
-        const link = node && node.marks.find(m => m.type.name === "link");
-        this.linkText.textContent = link ? link.attrs.href : "-empty-";
-
-        this.linkText.onkeydown = (e: KeyboardEvent) => {
-            if (e.key === "Enter") {
-                // this.makeLink(this.linkText!.textContent!);
-                e.stopPropagation();
-                e.preventDefault();
-            }
-        };
+        this.linkEditor = document.createElement("div");
+        this.linkEditor.className = "ProseMirror-icon menuicon";
+        this.linkDrag = document.createElement("img");
+        this.linkDrag.src = "https://seogurusnyc.com/wp-content/uploads/2016/12/link-1.png";
+        this.linkDrag.style.width = "15px";
+        this.linkDrag.style.height = "15px";
+        this.linkDrag.title = "Click to set link target";
+        this.linkDrag.id = "link-btn";
+        this.linkEditor.appendChild(this.linkDrag);
+        this.tooltip.appendChild(this.linkEditor);
     }
 
     async getTextLinkTargetTitle() {
@@ -485,9 +391,7 @@ export class TooltipTextMenu {
                 return div;
             },
             enable() { return false; },
-            run(p1, p2, p3, event) {
-                event.stopPropagation();
-            }
+            run(p1, p2, p3, event) { event.stopPropagation(); }
         });
 
         // menu item to update/apply the hyperlink to the selected text
@@ -584,24 +488,6 @@ export class TooltipTextMenu {
         }
     }
 
-    deleteLinkItem() {
-        const icon = {
-            height: 16, width: 16,
-            path: "M15.898,4.045c-0.271-0.272-0.713-0.272-0.986,0l-4.71,4.711L5.493,4.045c-0.272-0.272-0.714-0.272-0.986,0s-0.272,0.714,0,0.986l4.709,4.711l-4.71,4.711c-0.272,0.271-0.272,0.713,0,0.986c0.136,0.136,0.314,0.203,0.492,0.203c0.179,0,0.357-0.067,0.493-0.203l4.711-4.711l4.71,4.711c0.137,0.136,0.314,0.203,0.494,0.203c0.178,0,0.355-0.067,0.492-0.203c0.273-0.273,0.273-0.715,0-0.986l-4.711-4.711l4.711-4.711C16.172,4.759,16.172,4.317,15.898,4.045z"
-        };
-        return new MenuItem({
-            title: "Delete Link",
-            label: "X",
-            icon: icon,
-            css: "color: red",
-            class: "summarize",
-            execEvent: "",
-            run: (state, dispatch) => {
-                this.deleteLink();
-            }
-        });
-    }
-
     createLink() {
         const markType = schema.marks.link;
         return new MenuItem({
@@ -655,22 +541,19 @@ export class TooltipTextMenu {
         //Make a dropdown of all list types
         const toAdd: MenuItem[] = [];
         this.listTypeToIcon.forEach((icon, type) => {
-            toAdd.push(this.dropdownNodeBtn(icon, "color: black; width: 40px;", type, this.view, this.listTypes, this.changeToNodeType));
+            toAdd.push(this.dropdownBulletBtn(icon, "color: black; width: 40px;", type, this.view, this.listTypes, this.changeBulletType));
         });
         //option to remove the list formatting
-        toAdd.push(this.dropdownNodeBtn("X", "color: black; width: 40px;", undefined, this.view, this.listTypes, this.changeToNodeType));
+        toAdd.push(this.dropdownBulletBtn("X", "color: black; width: 40px;", undefined, this.view, this.listTypes, this.changeBulletType));
 
-        listTypeBtn = (new Dropdown(toAdd, {
-            label: label,
-            css: "color:black; width: 40px;"
-        }) as MenuItem).render(this.view).dom;
+        listTypeBtn = (new Dropdown(toAdd, { label: label, css: "color:black; width: 40px;" }) as MenuItem).render(this.view).dom;
 
         //add this new button and return it
         this.tooltip.appendChild(listTypeBtn);
         return listTypeBtn;
     }
 
-    createStar() {
+    createSummarizer() {
         return new MenuItem({
             title: "Summarize",
             label: "Summarize",
@@ -678,31 +561,17 @@ export class TooltipTextMenu {
             css: "color:white;",
             class: "menuicon",
             execEvent: "",
-            run: (state, dispatch) => {
-                TooltipTextMenu.insertStar(this.view.state, this.view.dispatch);
-            }
-
+            run: (state, dispatch) => TooltipTextMenu.insertSummarizer(state, dispatch)
         });
     }
 
-    public static insertStar(state: EditorState<any>, dispatch: any) {
+    public static insertSummarizer(state: EditorState<any>, dispatch: any) {
         if (state.selection.empty) return false;
-        const mark = state.schema.marks.highlight.create();
+        const mark = state.schema.marks.summarize.create();
         const tr = state.tr;
         tr.addMark(state.selection.from, state.selection.to, mark);
         const content = tr.selection.content();
-        const newNode = state.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;
-    }
-
-    public static insertComment(state: EditorState<any>, dispatch: any) {
-        if (state.selection.empty) return false;
-        const mark = state.schema.marks.highlight.create();
-        const tr = state.tr;
-        tr.addMark(state.selection.from, state.selection.to, mark);
-        const content = tr.selection.content();
-        const newNode = state.schema.nodes.star.create({ visibility: false, text: content, textslice: content.toJSON() });
+        const newNode = state.schema.nodes.summary.create({ visibility: false, text: content, textslice: content.toJSON() });
         dispatch && dispatch(tr.replaceSelectionWith(newNode).removeMark(tr.selection.from - 1, tr.selection.from, mark));
         return true;
     }
@@ -722,7 +591,7 @@ export class TooltipTextMenu {
 
                 const color = document.createElement("div");
                 color.className = "buttonColor";
-                color.style.backgroundColor = TooltipTextMenuManager.Instance.highlight.toString();
+                color.style.backgroundColor = TooltipTextMenuManager.Instance.highlighter.toString();
 
                 const wrapper = document.createElement("div");
                 wrapper.id = "colorPicker";
@@ -730,17 +599,14 @@ export class TooltipTextMenu {
                 wrapper.appendChild(color);
                 return wrapper;
             },
-            run: (state, dispatch) => {
-                TooltipTextMenu.insertHighlight(TooltipTextMenuManager.Instance.highlight, this.view.state, this.view.dispatch);
-            }
+            run: (state, dispatch) => TooltipTextMenu.insertHighlight(TooltipTextMenuManager.Instance.highlighter, state, dispatch)
         });
     }
 
     public static insertHighlight(color: String, state: EditorState<any>, dispatch: any) {
         if (state.selection.empty) return false;
 
-        const highlightMark = state.schema.mark(state.schema.marks.marker, { highlight: color });
-        dispatch(state.tr.addMark(state.selection.from, state.selection.to, highlightMark));
+        toggleMark(state.schema.marks.marker, { highlight: color })(state, dispatch);
     }
 
     createHighlightDropdown() {
@@ -773,22 +639,22 @@ export class TooltipTextMenu {
 
                 colors.forEach(color => {
                     const button = document.createElement("button");
-                    button.className = color === TooltipTextMenuManager.Instance.highlight ? "colorPicker active" : "colorPicker";
+                    button.className = color === TooltipTextMenuManager.Instance.highlighter ? "colorPicker active" : "colorPicker";
                     if (color) {
                         button.style.backgroundColor = color;
                         button.textContent = color === "transparent" ? "X" : "";
                         button.onclick = e => {
-                            TooltipTextMenuManager.Instance.highlight = color;
+                            TooltipTextMenuManager.Instance.highlighter = color;
 
-                            TooltipTextMenu.insertHighlight(TooltipTextMenuManager.Instance.highlight, self.view.state, self.view.dispatch);
+                            TooltipTextMenu.insertHighlight(TooltipTextMenuManager.Instance.highlighter, self.view.state, self.view.dispatch);
 
                             // update color menu
                             const highlightDom = self.createHighlightTool().render(self.view).dom;
                             const highlightDropdownDom = self.createHighlightDropdown().render(self.view).dom;
-                            self.highlightDom && self.tooltip.replaceChild(highlightDom, self.highlightDom);
-                            self.highlightDropdownDom && self.tooltip.replaceChild(highlightDropdownDom, self.highlightDropdownDom);
-                            self.highlightDom = highlightDom;
-                            self.highlightDropdownDom = highlightDropdownDom;
+                            self.highighterDom && self.tooltip.replaceChild(highlightDom, self.highighterDom);
+                            self.highlighterDropdownDom && self.tooltip.replaceChild(highlightDropdownDom, self.highlighterDropdownDom);
+                            self.highighterDom = highlightDom;
+                            self.highlighterDropdownDom = highlightDropdownDom;
                         };
                     }
                     colorsWrapper.appendChild(button);
@@ -832,19 +698,18 @@ export class TooltipTextMenu {
                 wrapper.appendChild(color);
                 return wrapper;
             },
-            run: (state, dispatch) => {
-                TooltipTextMenu.insertColor(TooltipTextMenuManager.Instance.color, this.view.state, this.view.dispatch);
-            }
+            run: (state, dispatch) => this.insertColor(TooltipTextMenuManager.Instance.color, state, dispatch)
         });
     }
 
-    public static insertColor(color: String, state: EditorState<any>, dispatch: any) {
+    public insertColor(color: String, state: EditorState<any>, dispatch: any) {
         const colorMark = state.schema.mark(state.schema.marks.pFontColor, { color: color });
         if (state.selection.empty) {
             dispatch(state.tr.addStoredMark(colorMark));
             return false;
         }
-        dispatch(state.tr.addMark(state.selection.from, state.selection.to, colorMark));
+        this.setMark(colorMark, state, dispatch);
+        toggleMark(colorMark.type, { color: color })(state, dispatch);
     }
 
     createColorDropdown() {
@@ -883,7 +748,7 @@ export class TooltipTextMenu {
                         button.onclick = e => {
                             TooltipTextMenuManager.Instance.color = color;
 
-                            TooltipTextMenu.insertColor(TooltipTextMenuManager.Instance.color, self.view.state, self.view.dispatch);
+                            self.insertColor(TooltipTextMenuManager.Instance.color, self.view.state, self.view.dispatch);
 
                             // update color menu
                             const colorDom = self.createColorTool().render(self.view).dom;
@@ -903,13 +768,10 @@ export class TooltipTextMenu {
                 return div;
             },
             enable() { return false; },
-            run(p1, p2, p3, event) {
-                event.stopPropagation();
-            }
+            run(p1, p2, p3, event) { event.stopPropagation(); }
         });
 
-        const colorDropdown = new Dropdown([colors], { class: "buttonSettings-dropdown" }) as MenuItem;
-        return colorDropdown;
+        return new Dropdown([colors], { class: "buttonSettings-dropdown" }) as MenuItem;
     }
 
     createBrush(active: boolean = false) {
@@ -933,9 +795,7 @@ export class TooltipTextMenu {
                 self._brushDropdownDom && self.tooltip.replaceChild(newBrushDropdowndom, self._brushDropdownDom);
                 self._brushDropdownDom = newBrushDropdowndom;
             },
-            active: (state) => {
-                return true;
-            }
+            active: (state) => true
         });
     }
 
@@ -959,8 +819,7 @@ export class TooltipTextMenu {
                     if (TooltipTextMenuManager.Instance._brushMarks && to - from > 0) {
                         this.view.dispatch(this.view.state.tr.removeMark(from, to));
                         Array.from(TooltipTextMenuManager.Instance._brushMarks).filter(m => m.type !== schema.marks.user_mark).forEach((mark: Mark) => {
-                            const markType = mark.type;
-                            this.changeToMarkInGroup(markType, this.view, []);
+                            this.setMark(mark, this.view.state, this.view.dispatch);
                         });
                     }
                 }
@@ -995,9 +854,7 @@ export class TooltipTextMenu {
             class: "button-setting-disabled",
             css: "",
             enable() { return false; },
-            run(p1, p2, p3, event) {
-                event.stopPropagation();
-            }
+            run(p1, p2, p3, event) { event.stopPropagation(); }
         });
 
         const self = this;
@@ -1056,71 +913,26 @@ export class TooltipTextMenu {
         });
 
         const hasMarks = TooltipTextMenuManager.Instance._brushMarks && TooltipTextMenuManager.Instance._brushMarks.size > 0;
-        const brushDom = new Dropdown(hasMarks ? [brushInfo, clearBrush] : [brushInfo], { class: "buttonSettings-dropdown" }) as MenuItem;
-        return brushDom;
+        return new Dropdown(hasMarks ? [brushInfo, clearBrush] : [brushInfo], { class: "buttonSettings-dropdown" }) as MenuItem;
     }
-    //for a specific grouping of marks (passed in), remove all and apply the passed-in one to the selected textchangeToMarkInGroup = (markType: MarkType | undefined, view: EditorView, fontMarks: MarkType[]) => {
-    changeToMarkInGroup = (markType: MarkType | undefined, view: EditorView, fontMarks: MarkType[]) => {
-        const { $cursor, ranges } = view.state.selection as TextSelection;
-        const state = view.state;
-        const dispatch = view.dispatch;
-
-        //remove all other active font marks
-        fontMarks.forEach((type) => {
-            if (dispatch) {
-                if ($cursor) {
-                    if (type.isInSet(state.storedMarks || $cursor.marks())) {
-                        dispatch(state.tr.removeStoredMark(type));
-                    }
-                } else {
-                    let has = false;
-                    for (let i = 0; !has && i < ranges.length; i++) {
-                        const { $from, $to } = ranges[i];
-                        has = state.doc.rangeHasMark($from.pos, $to.pos, type);
-                    }
-                    for (const i of ranges) {
-                        if (has) {
-                            toggleMark(type)(view.state, view.dispatch, view);
-                        }
-                    }
-                }
-            }
-        });
 
-        if (markType) {
-            //actually apply font
-            if ((view.state.selection as any).node && (view.state.selection as any).node.type === view.state.schema.nodes.ordered_list) {
-                const status = updateBullets(view.state.tr.setNodeMarkup(view.state.selection.from, (view.state.selection as any).node.type,
-                    { ...(view.state.selection as NodeSelection).node.attrs, setFontFamily: markType.name, setFontSize: Number(markType.name.replace(/p/, "")) }), view.state.schema);
-                view.dispatch(status.setSelection(new NodeSelection(status.doc.resolve(view.state.selection.from))));
+    setMark = (mark: Mark, state: EditorState<any>, dispatch: any) => {
+        if (mark) {
+            const node = (state.selection as NodeSelection).node;
+            if (node?.type === schema.nodes.ordered_list) {
+                let attrs = node.attrs;
+                if (mark.type === schema.marks.pFontFamily) attrs = { ...attrs, setFontFamily: mark.attrs.family };
+                if (mark.type === schema.marks.pFontSize) attrs = { ...attrs, setFontSize: mark.attrs.fontSize };
+                if (mark.type === schema.marks.pFontColor) attrs = { ...attrs, setFontColor: mark.attrs.color };
+                const tr = updateBullets(state.tr.setNodeMarkup(state.selection.from, node.type, attrs), state.schema);
+                dispatch(tr.setSelection(new NodeSelection(tr.doc.resolve(state.selection.from))));
+            } else {
+                toggleMark(mark.type, mark.attrs)(state, dispatch);
             }
-            else toggleMark(markType)(view.state, view.dispatch, view);
         }
     }
 
     changeToFontFamily = (mark: Mark, view: EditorView) => {
-        const { $cursor, ranges } = view.state.selection as TextSelection;
-        const state = view.state;
-        const dispatch = view.dispatch;
-
-        //remove all other active font marks
-        if ($cursor) {
-            if (view.state.schema.marks.pFontFamily.isInSet(state.storedMarks || $cursor.marks())) {
-                dispatch(state.tr.removeStoredMark(view.state.schema.marks.pFontFamily));
-            }
-        } else {
-            let has = false;
-            for (let i = 0; !has && i < ranges.length; i++) {
-                const { $from, $to } = ranges[i];
-                has = state.doc.rangeHasMark($from.pos, $to.pos, view.state.schema.marks.pFontFamily);
-            }
-            for (const i of ranges) {
-                if (has) {
-                    toggleMark(view.state.schema.marks.pFontFamily)(view.state, view.dispatch, view);
-                }
-            }
-        }
-
         const fontName = mark.attrs.family;
         if (fontName) { this.updateFontStyleDropdown(fontName); }
         if (this.editorProps) {
@@ -1130,39 +942,10 @@ export class TooltipTextMenu {
                 ruleProvider["ruleFont_" + heading] = fontName;
             }
         }
-        //actually apply font
-        if ((view.state.selection as any).node && (view.state.selection as any).node.type === view.state.schema.nodes.ordered_list) {
-            const status = updateBullets(view.state.tr.setNodeMarkup(view.state.selection.from, (view.state.selection as any).node.type,
-                { ...(view.state.selection as NodeSelection).node.attrs, setFontFamily: fontName }), view.state.schema);
-            view.dispatch(status.setSelection(new NodeSelection(status.doc.resolve(view.state.selection.from))));
-        }
-        else view.dispatch(view.state.tr.addMark(view.state.selection.from, view.state.selection.to, view.state.schema.marks.pFontFamily.create({ family: fontName })));
-        view.state.storedMarks = [...(view.state.storedMarks || []), view.state.schema.marks.pFontFamily.create({ family: fontName })];
+        this.setMark(view.state.schema.marks.pFontFamily.create({ family: fontName }), view.state, view.dispatch);
     }
 
     changeToFontSize = (mark: Mark, view: EditorView) => {
-        const { $cursor, ranges } = view.state.selection as TextSelection;
-        const state = view.state;
-        const dispatch = view.dispatch;
-
-        //remove all other active font marks
-        if ($cursor) {
-            if (view.state.schema.marks.pFontSize.isInSet(state.storedMarks || $cursor.marks())) {
-                dispatch(state.tr.removeStoredMark(view.state.schema.marks.pFontSize));
-            }
-        } else {
-            let has = false;
-            for (let i = 0; !has && i < ranges.length; i++) {
-                const { $from, $to } = ranges[i];
-                has = state.doc.rangeHasMark($from.pos, $to.pos, view.state.schema.marks.pFontSize);
-            }
-            for (const i of ranges) {
-                if (has) {
-                    toggleMark(view.state.schema.marks.pFontSize)(view.state, view.dispatch, view);
-                }
-            }
-        }
-
         const size = mark.attrs.fontSize;
         if (size) { this.updateFontSizeDropdown(String(size) + " pt"); }
         if (this.editorProps) {
@@ -1172,18 +955,11 @@ export class TooltipTextMenu {
                 ruleProvider["ruleSize_" + heading] = size;
             }
         }
-        //actually apply font
-        if ((view.state.selection as any).node && (view.state.selection as any).node.type === view.state.schema.nodes.ordered_list) {
-            const status = updateBullets(view.state.tr.setNodeMarkup(view.state.selection.from, (view.state.selection as any).node.type,
-                { ...(view.state.selection as NodeSelection).node.attrs, setFontSize: size }), view.state.schema);
-            view.dispatch(status.setSelection(new NodeSelection(status.doc.resolve(view.state.selection.from))));
-        }
-        else view.dispatch(view.state.tr.addMark(view.state.selection.from, view.state.selection.to, view.state.schema.marks.pFontSize.create({ fontSize: size })));
-        view.state.storedMarks = [...(view.state.storedMarks || []), view.state.schema.marks.pFontSize.create({ fontSize: size })];
+        this.setMark(view.state.schema.marks.pFontSize.create({ fontSize: size }), view.state, view.dispatch);
     }
 
     //remove all node typeand apply the passed-in one to the selected text
-    changeToNodeType = (nodeType: NodeType | undefined) => {
+    changeBulletType = (nodeType: NodeType | undefined) => {
         //remove oldif (nodeType) { //add new
         const view = this.view;
         if (nodeType === schema.nodes.bullet_list) {
@@ -1211,46 +987,40 @@ export class TooltipTextMenu {
     //css is the style you want applied to the button
     dropdownFontFamilyBtn(label: string, css: string, mark: Mark, view: EditorView, changeFontFamily: (mark: Mark<any>, view: EditorView) => any) {
         return new MenuItem({
-            title: "",
+            title: "Set Font Family",
             label: label,
             execEvent: "",
             class: "dropdown-item",
             css: css,
             enable() { return true; },
-            run() {
-                changeFontFamily(mark, view);
-            }
+            run() { changeFontFamily(mark, view); }
         });
     }
     //makes a button for the drop down FOR MARKS
     //css is the style you want applied to the button
     dropdownFontSizeBtn(label: string, css: string, mark: Mark, view: EditorView, changeFontSize: (markType: Mark<any>, view: EditorView) => any) {
         return new MenuItem({
-            title: "",
+            title: "Set Font Size",
             label: label,
             execEvent: "",
             class: "dropdown-item",
             css: css,
             enable() { return true; },
-            run() {
-                changeFontSize(mark, view);
-            }
+            run() { changeFontSize(mark, view); }
         });
     }
 
     //makes a button for the drop down FOR NODE TYPES
     //css is the style you want applied to the button
-    dropdownNodeBtn(label: string, css: string, nodeType: NodeType | undefined, view: EditorView, groupNodes: NodeType[], changeToNodeInGroup: (nodeType: NodeType<any> | undefined, view: EditorView, groupNodes: NodeType[]) => any) {
+    dropdownBulletBtn(label: string, css: string, nodeType: NodeType | undefined, view: EditorView, groupNodes: NodeType[], changeToNodeInGroup: (nodeType: NodeType<any> | undefined, view: EditorView, groupNodes: NodeType[]) => any) {
         return new MenuItem({
-            title: "",
+            title: "Set Bullet Style",
             label: label,
             execEvent: "",
             class: "dropdown-item",
             css: css,
             enable() { return true; },
-            run() {
-                changeToNodeInGroup(nodeType, view, groupNodes);
-            }
+            run() { changeToNodeInGroup(nodeType, view, groupNodes); }
         });
     }
 
@@ -1325,14 +1095,7 @@ export class TooltipTextMenu {
     getMarksInSelection(state: EditorState<any>) {
         const found = new Set<Mark>();
         const { from, to } = state.selection as TextSelection;
-        state.doc.nodesBetween(from, to, (node) => {
-            const marks = node.marks;
-            if (marks) {
-                marks.forEach(m => {
-                    found.add(m);
-                });
-            }
-        });
+        state.doc.nodesBetween(from, to, (node) => node.marks?.forEach(m => found.add(m)));
         return found;
     }
 
@@ -1362,12 +1125,6 @@ export class TooltipTextMenu {
 
         this.reset_mark_doms();
 
-        // Hide the tooltip if the selection is empty
-        if (state.selection.empty) {
-            //this.tooltip.style.display = "none";
-            //return;
-        }
-
         // update link dropdown
         const linkDropdown = await this.createLinkDropdown();
         const newLinkDropdowndom = linkDropdown.render(this.view).dom;
@@ -1473,7 +1230,6 @@ export class TooltipTextMenu {
                     }
                     const mark = state.schema.mark(mark_type);
                     return ref_node.marks.includes(mark);
-                    return false;
                 });
             }
             else {
@@ -1518,19 +1274,19 @@ export class TooltipTextMenu {
 
 export class TooltipTextMenuManager {
     private static _instance: TooltipTextMenuManager;
+    private _isPinned: boolean = false;
 
     public pinnedX: number = 0;
     public pinnedY: number = 0;
     public unpinnedX: number = 0;
     public unpinnedY: number = 0;
-    private _isPinned: boolean = false;
 
     public _brushMarks: Set<Mark> | undefined;
     public _brushMap: Map<string, Set<Mark>> = new Map();
     public _brushIsEmpty: boolean = true;
 
     public color: String = "#000";
-    public highlight: String = "transparent";
+    public highlighter: String = "transparent";
 
     public activeMenu: TooltipTextMenu | undefined;
 
@@ -1541,11 +1297,7 @@ export class TooltipTextMenuManager {
         return TooltipTextMenuManager._instance;
     }
 
-    public get isPinned() {
-        return this._isPinned;
-    }
+    public get isPinned() { return this._isPinned; }
 
-    public toggleIsPinned() {
-        this._isPinned = !this._isPinned;
-    }
+    public toggleIsPinned() { this._isPinned = !this._isPinned; }
 }
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index dd3b740fb..f366a3677 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -85,14 +85,14 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
             if (this._accumulatedTitle.startsWith("#") || this._accumulatedTitle.startsWith("=")) {
                 this._titleControlString = this._accumulatedTitle;
             } else if (this._titleControlString.startsWith("#")) {
-                let selectionTitleFieldKey = this._titleControlString.substring(1);
+                const selectionTitleFieldKey = this._titleControlString.substring(1);
                 selectionTitleFieldKey === "title" && (SelectionManager.SelectedDocuments()[0].props.Document.customTitle = !this._accumulatedTitle.startsWith("-"));
                 selectionTitleFieldKey && SelectionManager.SelectedDocuments().forEach(d =>
                     Doc.SetInPlace(d.props.Document, selectionTitleFieldKey, typeof d.props.Document[selectionTitleFieldKey] === "number" ? +this._accumulatedTitle : this._accumulatedTitle, true)
                 );
             }
         }
-    }))
+    }));
 
     @action titleEntered = (e: any) => {
         const key = e.keyCode || e.which;
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index d0cecf03d..54def38b5 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -120,7 +120,7 @@ export class EditableView extends React.Component<EditableProps> {
 
     @action
     setIsFocused = (value: boolean) => {
-        let wasFocused = this._editing;
+        const wasFocused = this._editing;
         this._editing = value;
         return wasFocused !== this._editing;
     }
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 01cd7957c..25b7dc5ec 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -286,7 +286,7 @@ export class MainView extends React.Component {
             ContainingCollectionDoc={undefined}
             zoomToScale={emptyFunction}
             getScale={returnOne}
-        />
+        />;
     }
     @computed get dockingContent() {
         TraceMobx();
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 411040332..15eec37de 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -240,9 +240,9 @@ export class CollectionView extends Touchable<FieldViewProps> {
         const mainPath = path.extname(images[this._curLightboxImg]);
         const nextPath = path.extname(images[(this._curLightboxImg + 1) % images.length]);
         const prevPath = path.extname(images[(this._curLightboxImg + images.length - 1) % images.length]);
-        let main = images[this._curLightboxImg].replace(mainPath, "_o" + mainPath);
-        let next = images[(this._curLightboxImg + 1) % images.length].replace(nextPath, "_o" + nextPath);
-        let prev = images[(this._curLightboxImg + images.length - 1) % images.length].replace(prevPath, "_o" + prevPath);
+        const main = images[this._curLightboxImg].replace(mainPath, "_o" + mainPath);
+        const next = images[(this._curLightboxImg + 1) % images.length].replace(nextPath, "_o" + nextPath);
+        const prev = images[(this._curLightboxImg + images.length - 1) % images.length].replace(prevPath, "_o" + prevPath);
         return !this._isLightboxOpen ? (null) : (<Lightbox key="lightbox"
             mainSrc={main}
             nextSrc={next}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 24cc0b5ea..3620ccb34 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -148,7 +148,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             else if (!this._titleRef.current.setIsFocused(true)) { // if focus didn't change, focus on interior text...
                 {
                     this._titleRef.current?.setIsFocused(false);
-                    let any = (this._mainCont.current?.getElementsByClassName("ProseMirror")?.[0] as any);
+                    const any = (this._mainCont.current?.getElementsByClassName("ProseMirror")?.[0] as any);
                     any.keeplocation = true;
                     any?.focus();
                 }
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 9318142e2..ec9e0ce5b 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -27,7 +27,7 @@ import { DictationManager } from '../../util/DictationManager';
 import { DragManager } from "../../util/DragManager";
 import buildKeymap from "../../util/ProsemirrorExampleTransfer";
 import { inpRules } from "../../util/RichTextRules";
-import { DashDocCommentView, FootnoteView, ImageResizeView, DashDocView, OrderedListView, schema, SummarizedView } from "../../util/RichTextSchema";
+import { DashDocCommentView, FootnoteView, ImageResizeView, DashDocView, OrderedListView, schema, SummaryView } from "../../util/RichTextSchema";
 import { SelectionManager } from "../../util/SelectionManager";
 import { TooltipLinkingMenu } from "../../util/TooltipLinkingMenu";
 import { TooltipTextMenu } from "../../util/TooltipTextMenu";
@@ -822,7 +822,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
                     dashComment(node, view, getPos) { return new DashDocCommentView(node, view, getPos); },
                     dashDoc(node, view, getPos) { return new DashDocView(node, view, getPos, self); },
                     image(node, view, getPos) { return new ImageResizeView(node, view, getPos, self.props.addDocTab); },
-                    star(node, view, getPos) { return new SummarizedView(node, view, getPos); },
+                    summary(node, view, getPos) { return new SummaryView(node, view, getPos); },
                     ordered_list(node, view, getPos) { return new OrderedListView(); },
                     footnote(node, view, getPos) { return new FootnoteView(node, view, getPos); }
                 },
@@ -907,10 +907,10 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
         this.tryUpdateHeight();
 
         // see if we need to preserve the insertion point
-        let prosediv = this._proseRef?.children?.[0] as any;
-        let keeplocation = prosediv?.keeplocation;
+        const prosediv = this._proseRef?.children?.[0] as any;
+        const keeplocation = prosediv?.keeplocation;
         prosediv && (prosediv.keeplocation = undefined);
-        let pos = this._editorView?.state.selection.$from.pos || 1;
+        const pos = this._editorView?.state.selection.$from.pos || 1;
         keeplocation && setTimeout(() => this._editorView?.dispatch(this._editorView?.state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos))));
     }
     onPointerWheel = (e: React.WheelEvent): void => {
@@ -925,14 +925,14 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
 
     onClick = (e: React.MouseEvent): void => {
         if ((this._editorView!.root as any).getSelection().isCollapsed) { // this is a hack to allow the cursor to be placed at the end of a document when the document ends in an inline dash comment.  Apparently Chrome on Windows has a bug/feature which breaks this when clicking after the end of the text.
-            let pcords = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY });
-            let node = pcords && this._editorView!.state.doc.nodeAt(pcords.pos); // get what prosemirror thinks the clicked node is (if it's null, then we didn't click on any text)
+            const pcords = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY });
+            const node = pcords && this._editorView!.state.doc.nodeAt(pcords.pos); // get what prosemirror thinks the clicked node is (if it's null, then we didn't click on any text)
             if (pcords && node?.type === this._editorView!.state.schema.nodes.dashComment) {
                 this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, pcords.pos + 2)));
                 e.preventDefault();
             }
             if (!node && this._proseRef) {
-                let lastNode = this._proseRef.children[this._proseRef.children.length - 1].children[this._proseRef.children[this._proseRef.children.length - 1].children.length - 1]; // get the last prosemirror div
+                const lastNode = this._proseRef.children[this._proseRef.children.length - 1].children[this._proseRef.children[this._proseRef.children.length - 1].children.length - 1]; // get the last prosemirror div
                 if (e.clientY > lastNode.getBoundingClientRect().bottom) { // if we clicked below the last prosemirror div, then set the selection to be the end of the document
                     this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, this._editorView!.state.doc.content.size)));
                 }
@@ -973,7 +973,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
         //     }
         // }
 
-        this.hitBulletTargets(e.clientX, e.clientY, e.shiftKey, false)
+        this.hitBulletTargets(e.clientX, e.clientY, e.shiftKey, false);
         if (this._recording) setTimeout(() => { this.stopDictation(true); setTimeout(() => this.recordDictation(), 500); }, 500);
     }
 
@@ -984,7 +984,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
         if (pos && this.props.isSelected(true)) {
             // let beforeEle = document.querySelector("." + hit.className) as Element; // const before = hit ? window.getComputedStyle(hit, ':before') : undefined;
             //const node = this._editorView!.state.doc.nodeAt(pos.pos);
-            let $pos = this._editorView!.state.doc.resolve(pos.pos);
+            const $pos = this._editorView!.state.doc.resolve(pos.pos);
             let list_node = $pos.node().type === schema.nodes.list_item ? $pos.node() : undefined;
             if ($pos.node().type === schema.nodes.ordered_list) {
                 for (let off = 1; off < 100; off++) {
@@ -998,7 +998,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
             }
             if (list_node && pos.inside >= 0 && this._editorView!.state.doc.nodeAt(pos.inside)!.attrs.bulletStyle === list_node.attrs.bulletStyle) {
                 if (select) {
-                    let $olist_pos = this._editorView!.state.doc.resolve($pos.pos - $pos.parentOffset - 1);
+                    const $olist_pos = this._editorView!.state.doc.resolve($pos.pos - $pos.parentOffset - 1);
                     if (!highlightOnly) {
                         this._editorView!.dispatch(this._editorView!.state.tr.setSelection(new NodeSelection($olist_pos)));
                     }
@@ -1064,14 +1064,14 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
             return;
         }
         if (!this._editorView!.state.selection.empty && e.key === "%") {
-            (this._editorView!.state as any).EnteringStyle = true;
+            this._editorView!.state.schema.EnteringStyle = true;
             e.preventDefault();
             e.stopPropagation();
             return;
         }
 
-        if (this._editorView!.state.selection.empty || !(this._editorView!.state as any).EnteringStyle) {
-            (this._editorView!.state as any).EnteringStyle = false;
+        if (this._editorView!.state.selection.empty || !this._editorView!.state.schema.EnteringStyle) {
+            this._editorView!.state.schema.EnteringStyle = false;
         }
         if (e.key === "Escape") {
             SelectionManager.DeselectAll();
@@ -1080,7 +1080,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
         if (e.key === "Tab" || e.key === "Enter") {
             e.preventDefault();
         }
-        const mark = e.key !== " " && this._lastTimedMark ? this._lastTimedMark! : schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.round(Date.now() / 1000 / 5) });
+        const mark = e.key !== " " && this._lastTimedMark ? this._lastTimedMark : schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.round(Date.now() / 1000 / 5) });
         this._lastTimedMark = mark;
         this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(mark));
 
@@ -1100,7 +1100,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
             getComputedStyle(this._ref.current!.parentElement!).top === "0px") {  // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
             const nh = this.Document.isTemplateField ? 0 : NumCast(this.dataDoc.nativeHeight, 0);
             const dh = NumCast(this.layoutDoc.height, 0);
-            let newHeight = Math.max(10, (nh ? dh / nh * scrollHeight : scrollHeight) + (this.props.ChromeHeight ? this.props.ChromeHeight() : 0));
+            const newHeight = Math.max(10, (nh ? dh / nh * scrollHeight : scrollHeight) + (this.props.ChromeHeight ? this.props.ChromeHeight() : 0));
             if (Math.abs(newHeight - dh) > 1) { // bcz: Argh!  without this, we get into a React crash if the same document is opened in a freeform view and in the treeview.  no idea why, but after dragging the freeform document, selecting it, and selecting text, it will compute to 1 pixel higher than the treeview which causes a cycle
                 this.layoutDoc.height = newHeight;
                 this.dataDoc.nativeHeight = nh ? scrollHeight : undefined;
-- 
cgit v1.2.3-70-g09d2


From f92f51236f10fca1d21b787a46dffbf6e565876a Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 16 Dec 2019 18:20:45 -0500
Subject: fixed setMark() made DocumentDocument default to being a previewer of
 selections

---
 src/client/util/TooltipTextMenu.tsx                    | 8 ++++++--
 src/server/authentication/models/current_user_utils.ts | 2 +-
 2 files changed, 7 insertions(+), 3 deletions(-)

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

diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 483ab40a7..fbdb9e377 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -709,7 +709,6 @@ export class TooltipTextMenu {
             return false;
         }
         this.setMark(colorMark, state, dispatch);
-        toggleMark(colorMark.type, { color: color })(state, dispatch);
     }
 
     createColorDropdown() {
@@ -927,7 +926,12 @@ export class TooltipTextMenu {
                 const tr = updateBullets(state.tr.setNodeMarkup(state.selection.from, node.type, attrs), state.schema);
                 dispatch(tr.setSelection(new NodeSelection(tr.doc.resolve(state.selection.from))));
             } else {
-                toggleMark(mark.type, mark.attrs)(state, dispatch);
+                toggleMark(mark.type, mark.attrs)(state, (tx: any) => {
+                    const { from, $from, to, empty } = tx.selection;
+                    if (!tx.doc.rangeHasMark(from, to, mark.type)) {
+                        toggleMark(mark.type, mark.attrs)({ tr: tx, doc: tx.doc, selection: tx.selection, storedMarks: tx.storedMarks }, dispatch);
+                    } else dispatch(tx);
+                });
             }
         }
     }
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index fd01a291d..e2906c19a 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -47,7 +47,7 @@ export class CurrentUserUtils {
         doc.activePen = doc;
         const docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, click?: string, ischecked?: string, activePen?: Doc, backgroundColor?: string, dragFactory?: Doc }[] = [
             { title: "collection", icon: "folder", ignoreClick: true, drag: 'Docs.Create.FreeformDocument([], { nativeWidth: undefined, nativeHeight: undefined, width: 150, height: 100, title: "freeform" })' },
-            { title: "preview", icon: "expand", ignoreClick: true, drag: 'Docs.Create.DocumentDocument(undefined, { width: 250, height: 250, title: "container" })' },
+            { title: "preview", icon: "expand", ignoreClick: true, drag: 'Docs.Create.DocumentDocument(ComputedField.MakeFunction("selectedDocs(this,true,[_last_])?.[0]"), { width: 250, height: 250, title: "container" })' },
             { title: "todo item", icon: "check", ignoreClick: true, drag: 'getCopy(this.dragFactory, true)', dragFactory: notes[notes.length - 1] },
             { title: "web page", icon: "globe-asia", ignoreClick: true, drag: 'Docs.Create.WebDocument("https://en.wikipedia.org/wiki/Hedgehog", { width: 300, height: 300, title: "New Webpage" })' },
             { title: "cat image", icon: "cat", ignoreClick: true, drag: 'Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { width: 200, title: "an image of a cat" })' },
-- 
cgit v1.2.3-70-g09d2


From ccd39c9a53ebf9aea84fcdcba6050145add4526f Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Mon, 6 Jan 2020 15:42:16 -0500
Subject: made doculinkbox show up at top right of text hyperlink.  it's a hack
 that should be cleaned up if I can figure out how.

---
 src/client/util/RichTextRules.ts         |  4 ++--
 src/client/util/RichTextSchema.tsx       |  3 ++-
 src/client/util/TooltipTextMenu.tsx      |  4 ++--
 src/client/views/DocumentButtonBar.tsx   |  3 ++-
 src/client/views/nodes/DocuLinkBox.tsx   | 25 ++++++++++++++++++++-----
 src/client/views/nodes/DocumentView.scss |  1 +
 6 files changed, 29 insertions(+), 11 deletions(-)

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

diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts
index 5148af889..c980a8003 100644
--- a/src/client/util/RichTextRules.ts
+++ b/src/client/util/RichTextRules.ts
@@ -85,11 +85,11 @@ export const inpRules = {
                 const value = state.doc.textBetween(start, end);
                 if (value) {
                     DocServer.GetRefField(value).then(docx => {
-                        const target = ((docx instanceof Doc) && docx) || Docs.Create.FreeformDocument([], { title: value, width: 500, height: 500 }, value);
+                        const target = ((docx instanceof Doc) && docx) || Docs.Create.FreeformDocument([], { title: value, width: 500, height: 500, }, value);
                         DocUtils.Publish(target, value, returnFalse, returnFalse);
                         DocUtils.MakeLink({ doc: (schema as any).Document }, { doc: target }, "portal link", "");
                     });
-                    const link = state.schema.marks.link.create({ href: Utils.prepend("/doc/" + value), location: "onRight", title: value });
+                    const link = state.schema.marks.link.create({ href: Utils.prepend("/doc/" + value), location: "onRight", title: value, targetId: value });
                     return state.tr.addMark(start, end, link);
                 }
                 return state.tr;
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 4fead71e5..2a5b348d2 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -274,6 +274,7 @@ export const marks: { [index: string]: MarkSpec } = {
     link: {
         attrs: {
             href: {},
+            targetId: { default: "" },
             showPreview: { default: true },
             location: { default: null },
             title: { default: null },
@@ -288,7 +289,7 @@ export const marks: { [index: string]: MarkSpec } = {
         toDOM(node: any) {
             return node.attrs.docref && node.attrs.title ?
                 ["div", ["span", `"`], ["span", 0], ["span", `"`], ["br"], ["a", { ...node.attrs, class: "prosemirror-attribution", title: `${node.attrs.title}` }, node.attrs.title], ["br"]] :
-                ["a", { ...node.attrs, title: `${node.attrs.title}` }, 0];
+                ["a", { ...node.attrs, id: node.attrs.targetId, title: `${node.attrs.title}` }, 0];
         }
     },
 
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index fbdb9e377..33257b658 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -449,8 +449,8 @@ export class TooltipTextMenu {
     //     let link = state.schema.mark(state.schema.marks.link, { href: target, location: location });
     // }
 
-    makeLink = (targetDoc: Doc, title: string, location: string): string => {
-        const link = this.view.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + targetDoc[Id]), title: title, location: location });
+    makeLink = (linkDocId: string, title: string, location: string, targetDocId: string): string => {
+        const link = this.view.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + linkDocId), title: title, location: location, targetId: targetDocId });
         this.view.dispatch(this.view.state.tr.removeMark(this.view.state.selection.from, this.view.state.selection.to, this.view.state.schema.marks.link).
             addMark(this.view.state.selection.from, this.view.state.selection.to, link));
         const node = this.view.state.selection.$from.nodeAfter;
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 37b5ef3ec..0ef842275 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -20,6 +20,7 @@ import React = require("react");
 import { DocumentView } from './nodes/DocumentView';
 import { ParentDocSelector } from './collections/ParentDocumentSelector';
 import { CollectionDockingView } from './collections/CollectionDockingView';
+import { Id } from '../../new_fields/FieldSymbols';
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -117,8 +118,8 @@ export class DocumentButtonBar extends React.Component<{ views: (DocumentView |
                         proto.sourceContext = this.view0.props.ContainingCollectionDoc;
 
                         const anchor2Title = linkDoc.anchor2 instanceof Doc ? StrCast(linkDoc.anchor2.title) : "-untitled-";
-                        const text = FormattedTextBox.ToolTipTextMenu.makeLink(linkDoc, anchor2Title, e.ctrlKey ? "onRight" : "inTab");
                         if (linkDoc.anchor2 instanceof Doc) {
+                            const text = FormattedTextBox.ToolTipTextMenu.makeLink(linkDoc[Id], anchor2Title, e.ctrlKey ? "onRight" : "inTab", linkDoc.anchor2[Id]);
                             proto.title = text === "" ? proto.title : text + " to " + linkDoc.anchor2.title; // TODO open to more descriptive descriptions of following in text link
                         }
                     }
diff --git a/src/client/views/nodes/DocuLinkBox.tsx b/src/client/views/nodes/DocuLinkBox.tsx
index 3e2e74c67..d17b2e498 100644
--- a/src/client/views/nodes/DocuLinkBox.tsx
+++ b/src/client/views/nodes/DocuLinkBox.tsx
@@ -1,6 +1,6 @@
 import { action, observable } from "mobx";
 import { observer } from "mobx-react";
-import { Doc } from "../../../new_fields/Doc";
+import { Doc, WidthSym, HeightSym } from "../../../new_fields/Doc";
 import { makeInterface } from "../../../new_fields/Schema";
 import { NumCast, StrCast, Cast } from "../../../new_fields/Types";
 import { Utils } from '../../../Utils';
@@ -12,6 +12,7 @@ import { FieldView, FieldViewProps } from "./FieldView";
 import React = require("react");
 import { DocumentType } from "../../documents/DocumentTypes";
 import { documentSchema } from "../../../new_fields/documentSchemas";
+import { Id } from "../../../new_fields/FieldSymbols";
 
 type DocLinkSchema = makeInterface<[typeof documentSchema]>;
 const DocLinkDocument = makeInterface(documentSchema);
@@ -68,17 +69,31 @@ export class DocuLinkBox extends DocComponent<FieldViewProps, DocLinkSchema>(Doc
 
     render() {
         const anchorDoc = Cast(this.props.Document[this.props.fieldKey], Doc);
-        const hasAnchor = anchorDoc instanceof Doc && anchorDoc.type === DocumentType.PDFANNO;
-        const y = NumCast(this.props.Document[this.props.fieldKey + "_y"], 100);
-        const x = NumCast(this.props.Document[this.props.fieldKey + "_x"], 100);
+        let anchorScale = anchorDoc instanceof Doc && anchorDoc.type === DocumentType.PDFANNO ? 0.33 : 1;
+        let y = NumCast(this.props.Document[this.props.fieldKey + "_y"], 100);
+        let x = NumCast(this.props.Document[this.props.fieldKey + "_x"], 100);
         const c = StrCast(this.props.Document.backgroundColor, "lightblue");
         const anchor = this.props.fieldKey === "anchor1" ? "anchor2" : "anchor1";
+
+        // really hacky stuff to make the link box display at the top right of hypertext link in a formatted text box.  somehow, this should get moved into the hyperlink itself...
+        const other = window.document.getElementById((this.props.Document[anchor] as Doc)[Id]);
+        if (other) {
+            (this.props.Document[this.props.fieldKey] as Doc)?.data; // ugh .. assumes that 'data' is the field used to store the text
+            setTimeout(() => {
+                let m = other.getBoundingClientRect();
+                let mp = this.props.ScreenToLocalTransform().transformPoint(m.right - 5, m.top + 5);
+                this.props.Document[this.props.fieldKey + "_x"] = mp[0] / this.props.PanelWidth() * 100;
+                this.props.Document[this.props.fieldKey + "_y"] = mp[1] / this.props.PanelHeight() * 100;
+            }, 0);
+            anchorScale = 0.15;
+        }
+
         const timecode = this.props.Document[anchor + "Timecode"];
         const targetTitle = StrCast((this.props.Document[anchor]! as Doc).title) + (timecode !== undefined ? ":" + timecode : "");
         return <div className="docuLinkBox-cont" onPointerDown={this.onPointerDown} onClick={this.onClick} title={targetTitle}
             ref={this._ref} style={{
                 background: c, left: `calc(${x}% - 12.5px)`, top: `calc(${y}% - 12.5px)`,
-                transform: `scale(${hasAnchor ? 0.333 : 1 / this.props.ContentScaling()})`
+                transform: `scale(${anchorScale / this.props.ContentScaling()})`
             }} />;
     }
 }
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index dfb84ed5c..f44c6dd3b 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -39,6 +39,7 @@
         transform-origin: top left;
         width: 100%;
         height: 100%;
+        z-index: 1;
     }
 
     .documentView-styleWrapper {
-- 
cgit v1.2.3-70-g09d2


From ced3532239c582938271635802e0d2d62703e148 Mon Sep 17 00:00:00 2001
From: vellichora <fangrui_tong@brown.edu>
Date: Tue, 7 Jan 2020 14:20:36 -0500
Subject: richtextmenu appears whenever formattedtextbox is focused

---
 src/client/util/RichTextMenu.scss           | 64 +++++++++++++++++++++++++++++
 src/client/util/RichTextMenu.tsx            | 58 +++++++++++++++++---------
 src/client/util/SelectionManager.ts         |  3 +-
 src/client/util/TooltipTextMenu.tsx         |  2 +
 src/client/views/DocumentDecorations.tsx    |  8 ----
 src/client/views/nodes/FormattedTextBox.tsx |  7 ++++
 6 files changed, 113 insertions(+), 29 deletions(-)

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

diff --git a/src/client/util/RichTextMenu.scss b/src/client/util/RichTextMenu.scss
index f7414cc7f..85d2765e3 100644
--- a/src/client/util/RichTextMenu.scss
+++ b/src/client/util/RichTextMenu.scss
@@ -20,9 +20,73 @@
         box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25);
         min-width: 150px;
         padding: 5px;
+
+        button {
+            background-color: #323232;
+            border: 1px solid black;
+            border-radius: 1px;
+            padding: 6px;
+            margin: 5px 0;
+
+            &:hover {
+                background-color: black;
+            }
+        }
     }
 
     input {
         color: black;
     }
+}
+
+.link-menu {
+    .divider {
+        background-color: white;
+        height: 1px;
+        width: 100%;
+    }
+}
+
+.color-preview-button {
+    .color-preview {
+        width: 100%;
+        height: 3px;
+        margin-top: 3px;
+    }
+}
+
+.color-wrapper {
+    display: flex;
+    flex-wrap: wrap;
+    justify-content: space-between;
+
+    button.color-button {
+        width: 20px;
+        height: 20px;
+        border-radius: 15px !important;
+        margin: 3px;
+        border: 2px solid transparent !important;
+        padding: 3px;
+
+        &.active {
+            border: 2px solid white !important;
+        }
+    }
+}
+
+select {
+    background-color: #323232;
+    color: white;
+    border: 1px solid black;
+    border-top: none;
+    border-bottom: none;
+
+    &:focus,
+    &:hover {
+        background-color: black;
+    }
+
+    &::-ms-expand {
+        color: white;
+    }
 }
\ No newline at end of file
diff --git a/src/client/util/RichTextMenu.tsx b/src/client/util/RichTextMenu.tsx
index 111822307..8c373b818 100644
--- a/src/client/util/RichTextMenu.tsx
+++ b/src/client/util/RichTextMenu.tsx
@@ -29,6 +29,7 @@ library.add(faBold, faItalic, faUnderline, faStrikethrough, faSuperscript, faSub
 @observer
 export default class RichTextMenu extends AntimodeMenu {
     static Instance: RichTextMenu;
+    @observable private isVisible: boolean = false;
 
     private view?: EditorView;
     private editorProps: FieldViewProps & FormattedTextBoxProps | undefined;
@@ -53,7 +54,6 @@ export default class RichTextMenu extends AntimodeMenu {
     constructor(props: Readonly<{}>) {
         super(props);
         RichTextMenu.Instance = this;
-        this.Pinned = true;
     }
 
     @action
@@ -73,6 +73,7 @@ export default class RichTextMenu extends AntimodeMenu {
         }
         this.view = view;
         const state = view.state;
+        this.isVisible = true;
         // DocumentDecorations.Instance.showTextBar();
         props && (this.editorProps = props);
         // Don't do anything if the document/selection didn't change
@@ -146,7 +147,6 @@ export default class RichTextMenu extends AntimodeMenu {
     }
 
     destroy() {
-        console.log("destroy");
     }
 
     createButton(faIcon: string, title: string, command?: any, onclick?: any) {
@@ -213,7 +213,6 @@ export default class RichTextMenu extends AntimodeMenu {
     }
 
     changeFontSize = (mark: Mark, view: EditorView) => {
-        console.log("change font size!!");
         const size = mark.attrs.fontSize;
         if (this.editorProps) {
             const ruleProvider = this.editorProps.ruleProvider;
@@ -241,9 +240,7 @@ export default class RichTextMenu extends AntimodeMenu {
     // TODO: remove doesn't work
     //remove all node type and apply the passed-in one to the selected text
     changeListType = (nodeType: NodeType | undefined) => {
-        console.log("change the list type ");
         if (!this.view) return;
-        console.log("change the list type has view");
 
         if (nodeType === schema.nodes.bullet_list) {
             wrapInList(nodeType)(this.view.state, this.view.dispatch);
@@ -354,7 +351,6 @@ export default class RichTextMenu extends AntimodeMenu {
                 this.brushIsEmpty = !this.brushIsEmpty;
             }
         }
-        console.log("brush marks are ", this.brushMarks);
     }
 
     @action toggleColorDropdown() { this.showColorDropdown = !this.showColorDropdown; }
@@ -397,13 +393,23 @@ export default class RichTextMenu extends AntimodeMenu {
 
         return (
             <div className="button-dropdown-wrapper">
-                <button className="antimodeMenu-button" title="" onPointerDown={onColorClick}><FontAwesomeIcon icon="palette" size="lg" /></button>
+                <button className="antimodeMenu-button color-preview-button" title="" onPointerDown={onColorClick}>
+                    <FontAwesomeIcon icon="palette" size="lg" />
+                    <div className="color-preview" style={{ backgroundColor: this.activeFontColor }}></div>
+                </button>
                 <button className="dropdown-button antimodeMenu-button" onPointerDown={onDropdownClick}><FontAwesomeIcon icon="caret-down" size="sm" /></button>
                 {this.showColorDropdown ?
                     (<div className="dropdown">
-                        {colors.map(color => {
-                            if (color) return <button style={{ backgroundColor: color }} onPointerDown={e => changeColor(e, color)}></button>;
-                        })}
+                        <p>Change font color:</p>
+                        <div className="color-wrapper">
+                            {colors.map(color => {
+                                if (color) {
+                                    return this.activeFontColor === color ?
+                                        <button className="color-button active" style={{ backgroundColor: color }} onPointerDown={e => changeColor(e, color)}></button> :
+                                        <button className="color-button" style={{ backgroundColor: color }} onPointerDown={e => changeColor(e, color)}></button>;
+                                }
+                            })}
+                        </div>
                     </div>)
                     : <></>}
             </div>
@@ -459,13 +465,23 @@ export default class RichTextMenu extends AntimodeMenu {
 
         return (
             <div className="button-dropdown-wrapper">
-                <button className="antimodeMenu-button" title="" onPointerDown={onHighlightClick}><FontAwesomeIcon icon="highlighter" size="lg" /></button>
+                <button className="antimodeMenu-button color-preview-button" title="" onPointerDown={onHighlightClick}>
+                    <FontAwesomeIcon icon="highlighter" size="lg" />
+                    <div className="color-preview" style={{ backgroundColor: this.activeHighlightColor }}></div>
+                </button>
                 <button className="dropdown-button antimodeMenu-button" onPointerDown={onDropdownClick}><FontAwesomeIcon icon="caret-down" size="sm" /></button>
                 {this.showHighlightDropdown ?
                     (<div className="dropdown">
-                        {colors.map(color => {
-                            if (color) return <button style={{ backgroundColor: color }} onPointerDown={e => changeHighlight(e, color)}></button>;
-                        })}
+                        <p>Change highlight color:</p>
+                        <div className="color-wrapper">
+                            {colors.map(color => {
+                                if (color) {
+                                    return this.activeHighlightColor === color ?
+                                        <button className="color-button active" style={{ backgroundColor: color }} onPointerDown={e => changeHighlight(e, color)}>{color === "transparent" ? "X" : ""}</button> :
+                                        <button className="color-button" style={{ backgroundColor: color }} onPointerDown={e => changeHighlight(e, color)}>{color === "transparent" ? "X" : ""}</button>;
+                                }
+                            })}
+                        </div>
                     </div>)
                     : <></>}
             </div>
@@ -492,9 +508,6 @@ export default class RichTextMenu extends AntimodeMenu {
             self.setCurrentLink(e.target.value);
         }
 
-        // const targetTitle = this.getTextLinkTargetTitle();
-        console.log("link curr is ", this.currentLink);
-        // // this.setCurrentLink(targetTitle);
         const link = this.currentLink ? this.currentLink : "";
 
         return (
@@ -502,11 +515,12 @@ export default class RichTextMenu extends AntimodeMenu {
                 <button className="antimodeMenu-button" title="" onPointerDown={onDropdownClick}><FontAwesomeIcon icon="link" size="lg" /></button>
                 <button className="dropdown-button antimodeMenu-button" onPointerDown={onDropdownClick}><FontAwesomeIcon icon="caret-down" size="sm" /></button>
                 {this.showLinkDropdown ?
-                    (<div className="dropdown">
+                    (<div className="dropdown link-menu">
                         <p>Linked to:</p>
                         <input value={link} placeholder="Enter URL" onChange={onLinkChange} />
-                        <button onPointerDown={e => this.makeLinkToURL(link, "onRight")}>Apply hyperlink</button>
-                        <button onPointerDown={e => this.deleteLink()}>Remove link</button>
+                        <button className="make-button" onPointerDown={e => this.makeLinkToURL(link, "onRight")}>Apply hyperlink</button>
+                        <div className="divider"></div>
+                        <button className="remove-button" onPointerDown={e => this.deleteLink()}>Remove link</button>
                     </div>)
                     : <></>}
             </div>
@@ -634,6 +648,10 @@ export default class RichTextMenu extends AntimodeMenu {
     }
 
     render() {
+        // if (!this.isVisible) return <></>;
+        SelectionManager.SelectedDocuments()
+        // if (this.Pinned || )
+
         const fontSizeOptions = [
             { mark: schema.marks.pFontSize.create({ fontSize: 7 }), title: "Set font size", label: "7pt", command: this.changeFontSize },
             { mark: schema.marks.pFontSize.create({ fontSize: 8 }), title: "Set font size", label: "8pt", command: this.changeFontSize },
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 0c733ac47..86a7a620e 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -3,6 +3,8 @@ import { Doc } from "../../new_fields/Doc";
 import { DocumentView } from "../views/nodes/DocumentView";
 import { computedFn } from "mobx-utils";
 import { List } from "../../new_fields/List";
+import { DocumentDecorations } from "../views/DocumentDecorations";
+import RichTextMenu from "./RichTextMenu";
 
 export namespace SelectionManager {
 
@@ -14,7 +16,6 @@ export namespace SelectionManager {
 
         @action
         SelectDoc(docView: DocumentView, ctrlPressed: boolean): void {
-            console.log("select doc!!!");
             // if doc is not in SelectedDocuments, add it
             if (!manager.SelectedDocuments.get(docView)) {
                 if (!ctrlPressed) {
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 33257b658..2c4d08b82 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -20,6 +20,8 @@ import { PastelSchemaPalette, DarkPastelSchemaPalette } from '../../new_fields/S
 const { toggleMark, setBlockType } = require("prosemirror-commands");
 const { openPrompt, TextField } = require("./ProsemirrorCopy/prompt.js");
 
+// deprecated in favor of richtextmenu
+
 //appears above a selection of text in a RichTextBox to give user options such as Bold, Italics, etc.
 export class TooltipTextMenu {
     public static Toolbar: HTMLDivElement | undefined;
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 093761f1f..799b3695c 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -77,11 +77,6 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         DocumentDecorations.Instance = this;
         this._keyinput = React.createRef();
         reaction(() => SelectionManager.SelectedDocuments().slice(), docs => this.titleBlur(false));
-        console.log("constructing document decorations!!!");
-    }
-
-    componentDidMount() {
-        console.log("mounting deocument deoctirioeon!!!!");
     }
 
     @action titleChanged = (event: any) => this._accumulatedTitle = event.target.value;
@@ -579,9 +574,6 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         if (bounds.y > bounds.b) {
             bounds.y = bounds.b - (this._resizeBorderWidth + this._linkBoxHeight + this._titleHeight);
         }
-        // // RichTextMenu.Instance.jumpTo(this._lastX, this._lastY - 50);
-        // console.log("jump to ");
-        // RichTextMenu.Instance.jumpTo(500, 300);
         return (<div className="documentDecorations">
             <div className="documentDecorations-background" style={{
                 width: (bounds.r - bounds.x + this._resizeBorderWidth) + "px",
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index f00a24d41..7d48a0859 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -48,6 +48,7 @@ import { CollectionFreeFormView } from '../collections/collectionFreeForm/Collec
 import { InkTool } from '../../../new_fields/InkField';
 import { TraceMobx } from '../../../new_fields/util';
 import RichTextMenu from '../../util/RichTextMenu';
+import { DocumentDecorations } from '../DocumentDecorations';
 
 library.add(faEdit);
 library.add(faSmile, faTextHeight, faUpload);
@@ -910,6 +911,9 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
         prosediv && (prosediv.keeplocation = undefined);
         const pos = this._editorView?.state.selection.$from.pos || 1;
         keeplocation && setTimeout(() => this._editorView?.dispatch(this._editorView?.state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos))));
+
+        // jump rich text menu to this textbox
+        this._ref.current && RichTextMenu.Instance.jumpTo(this._ref.current.getBoundingClientRect().x, this._ref.current?.getBoundingClientRect().y - 70);
     }
     onPointerWheel = (e: React.WheelEvent): void => {
         // if a text note is not selected and scrollable, this prevents us from being able to scroll and zoom out at the same time
@@ -1055,6 +1059,9 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
             this._undoTyping = undefined;
         }
         this.doLinkOnDeselect();
+
+        // move the richtextmenu offscreen
+        if (!RichTextMenu.Instance.Pinned) RichTextMenu.Instance.jumpTo(-300, -300);
     }
 
     _lastTimedMark: Mark | undefined = undefined;
-- 
cgit v1.2.3-70-g09d2


From 8ab1e09906399232aa9bc7017b3195f4cd0ff746 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Tue, 7 Jan 2020 15:18:54 -0500
Subject: fixed up text toolbar a little.  fixed doculink anchors on hyperlinks

---
 src/client/util/ProsemirrorExampleTransfer.ts      |   2 -
 src/client/util/RichTextRules.ts                   |  11 +-
 src/client/util/RichTextSchema.tsx                 |   2 +-
 src/client/util/TooltipTextMenu.scss               |   4 +-
 src/client/util/TooltipTextMenu.tsx                | 843 +++++++--------------
 src/client/views/DocumentButtonBar.tsx             |   2 +-
 .../CollectionFreeFormLinkView.tsx                 |  35 +-
 src/client/views/nodes/DocuLinkBox.tsx             |  20 +-
 8 files changed, 333 insertions(+), 586 deletions(-)

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

diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts
index b879ca7aa..c028dbf8b 100644
--- a/src/client/util/ProsemirrorExampleTransfer.ts
+++ b/src/client/util/ProsemirrorExampleTransfer.ts
@@ -111,8 +111,6 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?:
         return true;
     });
 
-    bind("Mod-s", TooltipTextMenu.insertSummarizer);
-
     bind("Tab", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
         const ref = state.selection;
         const range = ref.$from.blockRange(ref.$to);
diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts
index c980a8003..29b378299 100644
--- a/src/client/util/RichTextRules.ts
+++ b/src/client/util/RichTextRules.ts
@@ -233,19 +233,20 @@ export const inpRules = {
             new RegExp(/%\(/),
             (state, match, start, end) => {
                 const node = (state.doc.resolve(start) as any).nodeAfter;
-                const sm = state.storedMarks || undefined;
+                const sm = state.storedMarks || [];
                 const mark = state.schema.marks.summarizeInclusive.create();
+                sm.push(mark);
                 const selected = state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).addMark(start, end, mark);
                 const content = selected.selection.content();
-                const replaced = node ? selected.replaceRangeWith(start, start,
-                    schema.nodes.summary.create({ visibility: true, text: content, textslice: content.toJSON() })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
+                const replaced = node ? selected.replaceRangeWith(start, end,
+                    schema.nodes.summary.create({ visibility: true, text: content, textslice: content.toJSON() })) :
                     state.tr;
-                return replaced.setSelection(new TextSelection(replaced.doc.resolve(end + 1)));
+                return replaced.setSelection(new TextSelection(replaced.doc.resolve(end + 1))).setStoredMarks([...node.marks, ...sm]);
             }),
         new InputRule(
             new RegExp(/%\)/),
             (state, match, start, end) => {
-                return state.tr.removeStoredMark(state.schema.marks.summarizeInclusive.create());
+                return state.tr.deleteRange(start, end).removeStoredMark(state.schema.marks.summarizeInclusive.create());
             }),
         new InputRule(
             new RegExp(/%f$/),
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 2a5b348d2..ef90a7294 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -283,7 +283,7 @@ export const marks: { [index: string]: MarkSpec } = {
         inclusive: false,
         parseDOM: [{
             tag: "a[href]", getAttrs(dom: any) {
-                return { href: dom.getAttribute("href"), location: dom.getAttribute("location"), title: dom.getAttribute("title") };
+                return { href: dom.getAttribute("href"), location: dom.getAttribute("location"), title: dom.getAttribute("title"), targetId: dom.getAttribute("id") };
             }
         }],
         toDOM(node: any) {
diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss
index 8310a0da6..2a38fe118 100644
--- a/src/client/util/TooltipTextMenu.scss
+++ b/src/client/util/TooltipTextMenu.scss
@@ -149,7 +149,7 @@
     }
 
     svg {
-        fill: white;
+        fill: inherit;
         width: 18px;
         height: 18px;
     }
@@ -181,7 +181,7 @@
     }
 }
 
-#colorPicker {
+.colorPicker {
     position: relative;
 
     svg {
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 33257b658..8aa304fad 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -4,7 +4,6 @@ import { wrapInList } from 'prosemirror-schema-list';
 import { EditorState, NodeSelection, TextSelection } from "prosemirror-state";
 import { EditorView } from "prosemirror-view";
 import { Doc, Field, Opt } from "../../new_fields/Doc";
-import { Id } from "../../new_fields/FieldSymbols";
 import { Utils } from "../../Utils";
 import { DocServer } from "../DocServer";
 import { FieldViewProps } from "../views/nodes/FieldView";
@@ -17,8 +16,7 @@ import { updateBullets } from './ProsemirrorExampleTransfer';
 import { DocumentDecorations } from '../views/DocumentDecorations';
 import { SelectionManager } from './SelectionManager';
 import { PastelSchemaPalette, DarkPastelSchemaPalette } from '../../new_fields/SchemaHeaderField';
-const { toggleMark, setBlockType } = require("prosemirror-commands");
-const { openPrompt, TextField } = require("./ProsemirrorCopy/prompt.js");
+const { toggleMark } = require("prosemirror-commands");
 
 //appears above a selection of text in a RichTextBox to give user options such as Bold, Italics, etc.
 export class TooltipTextMenu {
@@ -30,10 +28,7 @@ export class TooltipTextMenu {
 
     private fontStyles: Mark[] = [];
     private fontSizes: Mark[] = [];
-    private listTypes: (NodeType | any)[] = [];
-    private listTypeToIcon: Map<NodeType | any, string> = new Map();
-    private _activeMarks: Mark[] = [];
-    private _marksToDoms: Map<Mark, HTMLSpanElement> = new Map();
+    private _marksToDoms: Map<MarkType, HTMLSpanElement> = new Map();
     private _collapsed: boolean = false;
 
     // editor doms
@@ -43,19 +38,18 @@ export class TooltipTextMenu {
     // editor button doms
     private colorDom?: Node;
     private colorDropdownDom?: Node;
+    private linkDom?: Node;
     private highighterDom?: Node;
     private highlighterDropdownDom?: Node;
-    private linkEditor?: HTMLDivElement;
-    private linkDrag?: HTMLImageElement;
-    private _linkDropdownDom?: Node;
+    private linkDropdownDom?: Node;
     private _brushdom?: Node;
     private _brushDropdownDom?: Node;
     private fontSizeDom?: Node;
     private fontStyleDom?: Node;
-    private listTypeBtnDom?: Node;
     private basicTools?: HTMLElement;
 
-
+    static createDiv(className: string) { const div = document.createElement("div"); div.className = className; return div; }
+    static createSpan(className: string) { const div = document.createElement("span"); div.className = className; return div; }
     constructor(view: EditorView) {
         this.view = view;
 
@@ -63,59 +57,55 @@ export class TooltipTextMenu {
         this.initTooltip(view);
 
         // initialize the wrapper
-        this.wrapper = document.createElement("div");
-        this.wrapper.className = "wrapper";
+        this.wrapper = TooltipTextMenu.createDiv("wrapper");
         this.wrapper.appendChild(this.tooltip);
 
-        // initialize the dragger -- appends it to the wrapper
-        this.createDragger();
-
         TooltipTextMenu.Toolbar = this.wrapper;
     }
 
     private async initTooltip(view: EditorView) {
-        // initialize tooltip dom
-        this.tooltip = document.createElement("div");
-        this.tooltip.className = "tooltipMenu";
-        this.basicTools = document.createElement("div");
-        this.basicTools.className = "basic-tools";
-
-        // init buttons to the tooltip -- paths to svgs are obtained from fontawesome
-        const items = [
-            { command: toggleMark(schema.marks.strong), dom: this.svgIcon("strong", "Bold", "M333.49 238a122 122 0 0 0 27-65.21C367.87 96.49 308 32 233.42 32H34a16 16 0 0 0-16 16v48a16 16 0 0 0 16 16h31.87v288H34a16 16 0 0 0-16 16v48a16 16 0 0 0 16 16h209.32c70.8 0 134.14-51.75 141-122.4 4.74-48.45-16.39-92.06-50.83-119.6zM145.66 112h87.76a48 48 0 0 1 0 96h-87.76zm87.76 288h-87.76V288h87.76a56 56 0 0 1 0 112z") },
-            { command: toggleMark(schema.marks.em), dom: this.svgIcon("em", "Italic", "M320 48v32a16 16 0 0 1-16 16h-62.76l-80 320H208a16 16 0 0 1 16 16v32a16 16 0 0 1-16 16H16a16 16 0 0 1-16-16v-32a16 16 0 0 1 16-16h62.76l80-320H112a16 16 0 0 1-16-16V48a16 16 0 0 1 16-16h192a16 16 0 0 1 16 16z") },
-            { command: toggleMark(schema.marks.underline), dom: this.svgIcon("underline", "Underline", "M32 64h32v160c0 88.22 71.78 160 160 160s160-71.78 160-160V64h32a16 16 0 0 0 16-16V16a16 16 0 0 0-16-16H272a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h32v160a80 80 0 0 1-160 0V64h32a16 16 0 0 0 16-16V16a16 16 0 0 0-16-16H32a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16zm400 384H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16z") },
-            { command: toggleMark(schema.marks.strikethrough), dom: this.svgIcon("strikethrough", "Strikethrough", "M496 224H293.9l-87.17-26.83A43.55 43.55 0 0 1 219.55 112h66.79A49.89 49.89 0 0 1 331 139.58a16 16 0 0 0 21.46 7.15l42.94-21.47a16 16 0 0 0 7.16-21.46l-.53-1A128 128 0 0 0 287.51 32h-68a123.68 123.68 0 0 0-123 135.64c2 20.89 10.1 39.83 21.78 56.36H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h480a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm-180.24 96A43 43 0 0 1 336 356.45 43.59 43.59 0 0 1 292.45 400h-66.79A49.89 49.89 0 0 1 181 372.42a16 16 0 0 0-21.46-7.15l-42.94 21.47a16 16 0 0 0-7.16 21.46l.53 1A128 128 0 0 0 224.49 480h68a123.68 123.68 0 0 0 123-135.64 114.25 114.25 0 0 0-5.34-24.36z") },
-            { command: toggleMark(schema.marks.superscript), dom: this.svgIcon("superscript", "Superscript", "M496 160h-16V16a16 16 0 0 0-16-16h-48a16 16 0 0 0-14.29 8.83l-16 32A16 16 0 0 0 400 64h16v96h-16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h96a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM336 64h-67a16 16 0 0 0-13.14 6.87l-79.9 115-79.9-115A16 16 0 0 0 83 64H16A16 16 0 0 0 0 80v48a16 16 0 0 0 16 16h33.48l77.81 112-77.81 112H16a16 16 0 0 0-16 16v48a16 16 0 0 0 16 16h67a16 16 0 0 0 13.14-6.87l79.9-115 79.9 115A16 16 0 0 0 269 448h67a16 16 0 0 0 16-16v-48a16 16 0 0 0-16-16h-33.48l-77.81-112 77.81-112H336a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16z") },
-            { command: toggleMark(schema.marks.subscript), dom: this.svgIcon("subscript", "Subscript", "M496 448h-16V304a16 16 0 0 0-16-16h-48a16 16 0 0 0-14.29 8.83l-16 32A16 16 0 0 0 400 352h16v96h-16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h96a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM336 64h-67a16 16 0 0 0-13.14 6.87l-79.9 115-79.9-115A16 16 0 0 0 83 64H16A16 16 0 0 0 0 80v48a16 16 0 0 0 16 16h33.48l77.81 112-77.81 112H16a16 16 0 0 0-16 16v48a16 16 0 0 0 16 16h67a16 16 0 0 0 13.14-6.87l79.9-115 79.9 115A16 16 0 0 0 269 448h67a16 16 0 0 0 16-16v-48a16 16 0 0 0-16-16h-33.48l-77.81-112 77.81-112H336a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16z") },
+        const self = this;
+        this.tooltip = TooltipTextMenu.createDiv("tooltipMenu");
+        this.basicTools = TooltipTextMenu.createDiv("basic-tools");
+
+        const svgIcon = (name: string, title: string = name, dpath: string) => {
+            const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
+            svg.setAttribute("viewBox", "-100 -100 650 650");
+            const path = document.createElementNS('http://www.w3.org/2000/svg', "path");
+            path.setAttributeNS(null, "d", dpath);
+            svg.appendChild(path);
+
+            const span = TooltipTextMenu.createSpan(name + " menuicon");
+            span.title = title;
+            span.appendChild(svg);
+
+            return span;
+        }
+
+        const basicItems = [ // init basicItems in minimized toolbar -- paths to svgs are obtained from fontawesome
+            { mark: schema.marks.strong, dom: svgIcon("strong", "Bold", "M333.49 238a122 122 0 0 0 27-65.21C367.87 96.49 308 32 233.42 32H34a16 16 0 0 0-16 16v48a16 16 0 0 0 16 16h31.87v288H34a16 16 0 0 0-16 16v48a16 16 0 0 0 16 16h209.32c70.8 0 134.14-51.75 141-122.4 4.74-48.45-16.39-92.06-50.83-119.6zM145.66 112h87.76a48 48 0 0 1 0 96h-87.76zm87.76 288h-87.76V288h87.76a56 56 0 0 1 0 112z") },
+            { mark: schema.marks.em, dom: svgIcon("em", "Italic", "M320 48v32a16 16 0 0 1-16 16h-62.76l-80 320H208a16 16 0 0 1 16 16v32a16 16 0 0 1-16 16H16a16 16 0 0 1-16-16v-32a16 16 0 0 1 16-16h62.76l80-320H112a16 16 0 0 1-16-16V48a16 16 0 0 1 16-16h192a16 16 0 0 1 16 16z") },
+            { mark: schema.marks.underline, dom: svgIcon("underline", "Underline", "M32 64h32v160c0 88.22 71.78 160 160 160s160-71.78 160-160V64h32a16 16 0 0 0 16-16V16a16 16 0 0 0-16-16H272a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h32v160a80 80 0 0 1-160 0V64h32a16 16 0 0 0 16-16V16a16 16 0 0 0-16-16H32a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16zm400 384H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16z") },
+        ];
+        const items = [ // init items in full size toolbar
+            { mark: schema.marks.strikethrough, dom: svgIcon("strikethrough", "Strikethrough", "M496 224H293.9l-87.17-26.83A43.55 43.55 0 0 1 219.55 112h66.79A49.89 49.89 0 0 1 331 139.58a16 16 0 0 0 21.46 7.15l42.94-21.47a16 16 0 0 0 7.16-21.46l-.53-1A128 128 0 0 0 287.51 32h-68a123.68 123.68 0 0 0-123 135.64c2 20.89 10.1 39.83 21.78 56.36H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h480a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm-180.24 96A43 43 0 0 1 336 356.45 43.59 43.59 0 0 1 292.45 400h-66.79A49.89 49.89 0 0 1 181 372.42a16 16 0 0 0-21.46-7.15l-42.94 21.47a16 16 0 0 0-7.16 21.46l.53 1A128 128 0 0 0 224.49 480h68a123.68 123.68 0 0 0 123-135.64 114.25 114.25 0 0 0-5.34-24.36z") },
+            { mark: schema.marks.superscript, dom: svgIcon("superscript", "Superscript", "M496 160h-16V16a16 16 0 0 0-16-16h-48a16 16 0 0 0-14.29 8.83l-16 32A16 16 0 0 0 400 64h16v96h-16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h96a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM336 64h-67a16 16 0 0 0-13.14 6.87l-79.9 115-79.9-115A16 16 0 0 0 83 64H16A16 16 0 0 0 0 80v48a16 16 0 0 0 16 16h33.48l77.81 112-77.81 112H16a16 16 0 0 0-16 16v48a16 16 0 0 0 16 16h67a16 16 0 0 0 13.14-6.87l79.9-115 79.9 115A16 16 0 0 0 269 448h67a16 16 0 0 0 16-16v-48a16 16 0 0 0-16-16h-33.48l-77.81-112 77.81-112H336a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16z") },
+            { mark: schema.marks.subscript, dom: svgIcon("subscript", "Subscript", "M496 448h-16V304a16 16 0 0 0-16-16h-48a16 16 0 0 0-14.29 8.83l-16 32A16 16 0 0 0 400 352h16v96h-16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h96a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM336 64h-67a16 16 0 0 0-13.14 6.87l-79.9 115-79.9-115A16 16 0 0 0 83 64H16A16 16 0 0 0 0 80v48a16 16 0 0 0 16 16h33.48l77.81 112-77.81 112H16a16 16 0 0 0-16 16v48a16 16 0 0 0 16 16h67a16 16 0 0 0 13.14-6.87l79.9-115 79.9 115A16 16 0 0 0 269 448h67a16 16 0 0 0 16-16v-48a16 16 0 0 0-16-16h-33.48l-77.81-112 77.81-112H336a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16z") },
         ];
 
-        // add menu items
-        this._marksToDoms = new Map();
-        items.forEach(({ dom, command }) => {
+        basicItems.map(({ dom, mark }) => this.basicTools?.appendChild(dom.cloneNode(true)));
+        basicItems.concat(items).forEach(({ dom, mark }) => {
             this.tooltip.appendChild(dom);
-            switch (dom.title) {
-                case "Bold":
-                    this._marksToDoms.set(schema.mark(schema.marks.strong), dom);
-                    this.basicTools && this.basicTools.appendChild(dom.cloneNode(true));
-                    break;
-                case "Italic":
-                    this._marksToDoms.set(schema.mark(schema.marks.em), dom);
-                    this.basicTools && this.basicTools.appendChild(dom.cloneNode(true));
-                    break;
-                case "Underline":
-                    this._marksToDoms.set(schema.mark(schema.marks.underline), dom);
-                    this.basicTools && this.basicTools.appendChild(dom.cloneNode(true));
-                    break;
-            }
+            this._marksToDoms.set(mark, dom);
 
             //pointer down handler to activate button effects
             dom.addEventListener("pointerdown", e => {
-                e.preventDefault();
                 this.view.focus();
                 if (dom.contains(e.target as Node)) {
+                    e.preventDefault();
                     e.stopPropagation();
-                    command(this.view.state, this.view.dispatch, this.view);
+                    toggleMark(mark)(this.view.state, this.view.dispatch, this.view);
+                    this.updateHighlightStateOfButtons();
                 }
             });
         });
@@ -133,46 +123,12 @@ export class TooltipTextMenu {
         this.tooltip.appendChild(this.colorDropdownDom);
 
         // link menu
-        this.updateLinkMenu();
-        const dropdown = await this.createLinkDropdown();
-        this._linkDropdownDom = dropdown.render(this.view).dom;
-        this.tooltip.appendChild(this._linkDropdownDom);
+        this.linkDom = this.createLinkTool().render(this.view).dom;
+        this.linkDropdownDom = this.createLinkDropdown("").render(this.view).dom;
+        this.tooltip.appendChild(this.linkDom);
+        this.tooltip.appendChild(this.linkDropdownDom);
 
         // list of font styles
-        this.initFontStyles();
-
-        // font sizes
-        this.initFontSizes();
-
-        // list types
-        this.initListTypes();
-
-        // init brush tool
-        this._brushdom = this.createBrush().render(this.view).dom;
-        this.tooltip.appendChild(this._brushdom);
-        this._brushDropdownDom = this.createBrushDropdown().render(this.view).dom;
-        this.tooltip.appendChild(this._brushDropdownDom);
-
-        // star
-        this.tooltip.appendChild(this.createSummarizer().render(this.view).dom);
-
-        // list types dropdown
-        this.updateListItemDropdown(":", this.listTypeBtnDom);
-
-        await this.updateFromDash(view, undefined, undefined);
-    }
-
-    initFontStyles() {
-        this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Times New Roman" }));
-        this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Arial" }));
-        this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Georgia" }));
-        this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Comic Sans MS" }));
-        this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Tahoma" }));
-        this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Impact" }));
-        this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Crimson Text" }));
-    }
-
-    initFontSizes() {
         this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 7 }));
         this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 8 }));
         this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 9 }));
@@ -186,56 +142,89 @@ export class TooltipTextMenu {
         this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 32 }));
         this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 48 }));
         this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 72 }));
-    }
 
-    initListTypes() {
-        this.listTypeToIcon = new Map();
-        //this.listTypeToIcon.set(schema.nodes.bullet_list, ":");
-        this.listTypeToIcon.set(schema.nodes.ordered_list.create({ mapStyle: "bullet" }), ":");
-        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());
-    }
+        // font sizes
+        this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Times New Roman" }));
+        this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Arial" }));
+        this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Georgia" }));
+        this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Comic Sans MS" }));
+        this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Tahoma" }));
+        this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Impact" }));
+        this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Crimson Text" }));
 
-    // creates dragger element that allows dragging and collapsing (on double click) 
-    // of editor and appends it to the wrapper
-    createDragger() {
-        const draggerWrapper = document.createElement("div");
-        draggerWrapper.className = "dragger-wrapper";
 
-        const dragger = document.createElement("div");
-        dragger.className = "dragger";
+        // init brush tool
+        this._brushdom = this.createBrushTool().render(this.view).dom;
+        this.tooltip.appendChild(this._brushdom);
+        this._brushDropdownDom = this.createBrushDropdown().render(this.view).dom;
+        this.tooltip.appendChild(this._brushDropdownDom);
+
+        // summarizer tool
+        const summarizer = new MenuItem({
+            title: "Summarize",
+            label: "Summarize",
+            icon: icons.join,
+            css: "fill:white;",
+            class: "menuicon",
+            execEvent: "",
+            run: (state, dispatch) => TooltipTextMenu.insertSummarizer(state, dispatch)
+        });
+        this.tooltip.appendChild(summarizer.render(this.view).dom);
 
-        const line1 = document.createElement("span");
-        line1.className = "dragger-line";
-        const line2 = document.createElement("span");
-        line2.className = "dragger-line";
-        const line3 = document.createElement("span");
-        line3.className = "dragger-line";
+        // list types dropdown
+        const listDropdownTypes = [{ mapStyle: "bullet", label: ":" }, { mapStyle: "decimal", label: "1.1" }, { mapStyle: "multi", label: "A.1" }, { label: "X" }];
+        const listTypes = new Dropdown(listDropdownTypes.map(({ mapStyle, label }) =>
+            new MenuItem({
+                title: "Set Bullet Style",
+                label: label,
+                execEvent: "",
+                class: "dropdown-item",
+                css: "color: black; width: 40px;",
+                enable() { return true; },
+                run() {
+                    const marks = self.view.state.storedMarks || (view.state.selection.$to.parentOffset && view.state.selection.$from.marks());
+                    if (!wrapInList(schema.nodes.ordered_list)(view.state, (tx2: any) => {
+                        const tx3 = updateBullets(tx2, schema, mapStyle);
+                        marks && tx3.ensureMarks([...marks]);
+                        marks && tx3.setStoredMarks([...marks]);
+
+                        view.dispatch(tx2);
+                    })) {
+                        const tx2 = view.state.tr;
+                        const tx3 = updateBullets(tx2, schema, mapStyle);
+                        marks && tx3.ensureMarks([...marks]);
+                        marks && tx3.setStoredMarks([...marks]);
+
+                        view.dispatch(tx3);
+                    }
+                }
+            })), { label: ":", css: "color:black; width: 40px;" });
+        this.tooltip.appendChild(listTypes.render(this.view).dom);
 
-        dragger.appendChild(line1);
-        dragger.appendChild(line2);
-        dragger.appendChild(line3);
+        await this.updateFromDash(view, undefined, undefined);
 
+        const draggerWrapper = TooltipTextMenu.createDiv("dragger-wrapper");
+        const dragger = TooltipTextMenu.createDiv("dragger");
+        dragger.appendChild(TooltipTextMenu.createSpan("dragger-line"));
+        dragger.appendChild(TooltipTextMenu.createSpan("dragger-line"));
+        dragger.appendChild(TooltipTextMenu.createSpan("dragger-line"));
         draggerWrapper.appendChild(dragger);
-
         this.wrapper.appendChild(draggerWrapper);
-        this.dragElement(draggerWrapper);
+        this.setupDragElementInteractions(draggerWrapper);
     }
 
-    dragElement(elmnt: HTMLElement) {
+    setupDragElementInteractions(elmnt: HTMLElement) {
         var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
         if (elmnt) {
             // if present, the header is where you move the DIV from:
-            elmnt.onpointerdown = dragMouseDown;
+            elmnt.onpointerdown = dragPointerDown;
             elmnt.ondblclick = onClick;
         }
         const self = this;
 
-        function dragMouseDown(e: PointerEvent) {
+        function dragPointerDown(e: PointerEvent) {
             e = e || window.event;
-            //e.preventDefault();
+            e.preventDefault();
             // get the mouse cursor position at startup:
             pos3 = e.clientX;
             pos4 = e.clientY;
@@ -284,9 +273,28 @@ export class TooltipTextMenu {
     updateFontSizeDropdown(label: string) {
         //font SIZES
         const fontSizeBtns: MenuItem[] = [];
-        this.fontSizes.forEach(mark => {
-            fontSizeBtns.push(this.dropdownFontSizeBtn(String(mark.attrs.fontSize), "color: black; width: 50px;", mark, this.view, this.changeToFontSize));
-        });
+        const self = this;
+        this.fontSizes.forEach(mark =>
+            fontSizeBtns.push(new MenuItem({
+                title: "Set Font Size",
+                label: String(mark.attrs.fontSize),
+                execEvent: "",
+                class: "dropdown-item",
+                css: "color: black; width: 50px;",
+                enable() { return true; },
+                run() {
+                    const size = mark.attrs.fontSize;
+                    if (size) { self.updateFontSizeDropdown(String(size) + " pt"); }
+                    if (self.editorProps) {
+                        const ruleProvider = self.editorProps.ruleProvider;
+                        const heading = NumCast(self.editorProps.Document.heading);
+                        if (ruleProvider && heading) {
+                            ruleProvider["ruleSize_" + heading] = size;
+                        }
+                    }
+                    TooltipTextMenu.setMark(self.view.state.schema.marks.pFontSize.create({ fontSize: size }), self.view.state, self.view.dispatch);
+                }
+            })));
 
         const newfontSizeDom = (new Dropdown(fontSizeBtns, { label: label, css: "color:black; min-width: 60px;" }) as MenuItem).render(this.view).dom;
         if (this.fontSizeDom) {
@@ -302,9 +310,28 @@ export class TooltipTextMenu {
     updateFontStyleDropdown(label: string) {
         //font STYLES
         const fontBtns: MenuItem[] = [];
-        this.fontStyles.forEach((mark) => {
-            fontBtns.push(this.dropdownFontFamilyBtn(mark.attrs.family, "color: black; font-family: " + mark.attrs.family + ", sans-serif; width: 125px;", mark, this.view, this.changeToFontFamily));
-        });
+        const self = this;
+        this.fontStyles.forEach(mark =>
+            fontBtns.push(new MenuItem({
+                title: "Set Font Family",
+                label: mark.attrs.family,
+                execEvent: "",
+                class: "dropdown-item",
+                css: "color: black; font-family: " + mark.attrs.family + ", sans-serif; width: 125px;",
+                enable() { return true; },
+                run() {
+                    const fontName = mark.attrs.family;
+                    if (fontName) { self.updateFontStyleDropdown(fontName); }
+                    if (self.editorProps) {
+                        const ruleProvider = self.editorProps.ruleProvider;
+                        const heading = NumCast(self.editorProps.Document.heading);
+                        if (ruleProvider && heading) {
+                            ruleProvider["ruleFont_" + heading] = fontName;
+                        }
+                    }
+                    TooltipTextMenu.setMark(self.view.state.schema.marks.pFontFamily.create({ family: fontName }), self.view.state, self.view.dispatch);
+                }
+            })));
 
         const newfontStyleDom = (new Dropdown(fontBtns, { label: label, css: "color:black; width: 125px;" }) as MenuItem).render(this.view).dom;
         if (this.fontStyleDom) {
@@ -315,20 +342,6 @@ export class TooltipTextMenu {
         }
         this.fontStyleDom = newfontStyleDom;
     }
-
-    updateLinkMenu() {
-        this.linkEditor = document.createElement("div");
-        this.linkEditor.className = "ProseMirror-icon menuicon";
-        this.linkDrag = document.createElement("img");
-        this.linkDrag.src = "https://seogurusnyc.com/wp-content/uploads/2016/12/link-1.png";
-        this.linkDrag.style.width = "15px";
-        this.linkDrag.style.height = "15px";
-        this.linkDrag.title = "Click to set link target";
-        this.linkDrag.id = "link-btn";
-        this.linkEditor.appendChild(this.linkDrag);
-        this.tooltip.appendChild(this.linkEditor);
-    }
-
     async getTextLinkTargetTitle() {
         const node = this.view.state.selection.$from.nodeAfter;
         const link = node && node.marks.find(m => m.type.name === "link");
@@ -362,8 +375,21 @@ export class TooltipTextMenu {
         }
     }
 
-    async createLinkDropdown() {
-        const targetTitle = await this.getTextLinkTargetTitle();
+    // LINK TOOL
+    createLinkTool(active: boolean = false) {
+        return new MenuItem({
+            title: "Link tool",
+            label: "Link tool",
+            icon: icons.link,
+            css: "fill:white;",
+            class: active ? "menuicon-active" : "menuicon",
+            execEvent: "",
+            run: async (state, dispatch) => { },
+            active: (state) => true
+        });
+    }
+
+    createLinkDropdown(targetTitle: string) {
         const input = document.createElement("input");
 
         // menu item for input for hyperlink url 
@@ -407,9 +433,22 @@ export class TooltipTextMenu {
                 return button;
             },
             enable() { return false; },
-            run: (state, dispatch, view, event) => {
+            run: async (state, dispatch, view, event) => {
                 event.stopPropagation();
-                this.makeLinkToURL(input.value, "onRight");
+                let node = this.view.state.selection.$from.nodeAfter;
+                let link = this.view.state.schema.mark(this.view.state.schema.marks.link, { href: input.value, location: "onRight" });
+                this.view.dispatch(this.view.state.tr.removeMark(this.view.state.selection.from, this.view.state.selection.to, this.view.state.schema.marks.link));
+                this.view.dispatch(this.view.state.tr.addMark(this.view.state.selection.from, this.view.state.selection.to, link));
+                node = this.view.state.selection.$from.nodeAfter;
+                link = node && node.marks.find(m => m.type.name === "link");
+
+                // update link menu
+                const linkDom = self.createLinkTool(true).render(self.view).dom;
+                const linkDropdownDom = self.createLinkDropdown(await self.getTextLinkTargetTitle()).render(self.view).dom;
+                self.linkDom && self.tooltip.replaceChild(linkDom, self.linkDom);
+                self.linkDropdownDom && self.tooltip.replaceChild(linkDropdownDom, self.linkDropdownDom);
+                self.linkDom = linkDom;
+                self.linkDropdownDom = linkDropdownDom;
             }
         });
 
@@ -431,155 +470,55 @@ export class TooltipTextMenu {
             },
             enable() { return true; },
             async run() {
-                self.deleteLink();
-                // update link dropdown
-                const dropdown = await self.createLinkDropdown();
-                const newLinkDropdowndom = dropdown.render(self.view).dom;
-                self._linkDropdownDom && self.tooltip.replaceChild(newLinkDropdowndom, self._linkDropdownDom);
-                self._linkDropdownDom = newLinkDropdowndom;
-            }
-        });
-
-
-        const linkDropdown = new Dropdown(targetTitle ? [linkInfo, linkApply, deleteLink] : [linkInfo, linkApply], { class: "buttonSettings-dropdown" }) as MenuItem;
-        return linkDropdown;
-    }
-
-    // makeLinkWithState = (state: EditorState, target: string, location: string) => {
-    //     let link = state.schema.mark(state.schema.marks.link, { href: target, location: location });
-    // }
-
-    makeLink = (linkDocId: string, title: string, location: string, targetDocId: string): string => {
-        const link = this.view.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + linkDocId), title: title, location: location, targetId: targetDocId });
-        this.view.dispatch(this.view.state.tr.removeMark(this.view.state.selection.from, this.view.state.selection.to, this.view.state.schema.marks.link).
-            addMark(this.view.state.selection.from, this.view.state.selection.to, link));
-        const node = this.view.state.selection.$from.nodeAfter;
-        if (node && node.text) {
-            return node.text;
-        }
-        return "";
-    }
-
-    makeLinkToURL = (target: String, lcoation: string) => {
-        let node = this.view.state.selection.$from.nodeAfter;
-        let link = this.view.state.schema.mark(this.view.state.schema.marks.link, { href: target, location: location });
-        this.view.dispatch(this.view.state.tr.removeMark(this.view.state.selection.from, this.view.state.selection.to, this.view.state.schema.marks.link));
-        this.view.dispatch(this.view.state.tr.addMark(this.view.state.selection.from, this.view.state.selection.to, link));
-        node = this.view.state.selection.$from.nodeAfter;
-        link = node && node.marks.find(m => m.type.name === "link");
-    }
-
-    deleteLink = () => {
-        const node = this.view.state.selection.$from.nodeAfter;
-        const link = node && node.marks.find(m => m.type === this.view.state.schema.marks.link);
-        const href = link!.attrs.href;
-        if (href) {
-            if (href.indexOf(Utils.prepend("/doc/")) === 0) {
-                const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
-                if (linkclicked) {
-                    DocServer.GetRefField(linkclicked).then(async linkDoc => {
+                // delete the link
+                const node = self.view.state.selection.$from.nodeAfter;
+                const link = node && node.marks.find(m => m.type === self.view.state.schema.marks.link);
+                const href = link!.attrs.href;
+                if (href?.indexOf(Utils.prepend("/doc/")) === 0) {
+                    const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
+                    linkclicked && DocServer.GetRefField(linkclicked).then(async linkDoc => {
                         if (linkDoc instanceof Doc) {
                             LinkManager.Instance.deleteLink(linkDoc);
-                            this.view.dispatch(this.view.state.tr.removeMark(this.view.state.selection.from, this.view.state.selection.to, this.view.state.schema.marks.link));
+                            self.view.dispatch(self.view.state.tr.removeMark(self.view.state.selection.from, self.view.state.selection.to, self.view.state.schema.marks.link));
                         }
                     });
                 }
+                // update link menu
+                const linkDom = self.createLinkTool(false).render(self.view).dom;
+                const linkDropdownDom = self.createLinkDropdown("").render(self.view).dom;
+                self.linkDom && self.tooltip.replaceChild(linkDom, self.linkDom);
+                self.linkDropdownDom && self.tooltip.replaceChild(linkDropdownDom, self.linkDropdownDom);
+                self.linkDom = linkDom;
+                self.linkDropdownDom = linkDropdownDom;
             }
-        }
-    }
-
-    createLink() {
-        const markType = schema.marks.link;
-        return new MenuItem({
-            title: "Add or remove link",
-            label: "Add or remove link",
-            execEvent: "",
-            icon: icons.link,
-            css: "color:white;",
-            class: "menuicon",
-            enable(state) { return !state.selection.empty; },
-            run: (state, dispatch, view) => {
-                // to remove link
-                let curLink = "";
-                if (this.markActive(state, markType)) {
-
-                    const { from, $from, to, empty } = state.selection;
-                    const node = state.doc.nodeAt(from);
-                    node && node.marks.map(m => {
-                        m.type === markType && (curLink = m.attrs.href);
-                    });
-                    //toggleMark(markType)(state, dispatch);
-                    //return true;
-                }
-                // to create link
-                openPrompt({
-                    title: "Create a link",
-                    fields: {
-                        href: new TextField({
-                            value: curLink,
-                            label: "Link Target",
-                            required: true
-                        }),
-                        title: new TextField({ label: "Title" })
-                    },
-                    callback(attrs: any) {
-                        toggleMark(markType, attrs)(view.state, view.dispatch);
-                        view.focus();
-                    },
-                    flyout_top: 0,
-                    flyout_left: 0
-                });
-            }
-        });
-    }
-
-    //will display a remove-list-type button if selection is in list, otherwise will show list type dropdown
-    updateListItemDropdown(label: string, listTypeBtn: any) {
-        //remove old btn
-        if (listTypeBtn) { this.tooltip.removeChild(listTypeBtn); }
-
-        //Make a dropdown of all list types
-        const toAdd: MenuItem[] = [];
-        this.listTypeToIcon.forEach((icon, type) => {
-            toAdd.push(this.dropdownBulletBtn(icon, "color: black; width: 40px;", type, this.view, this.listTypes, this.changeBulletType));
         });
-        //option to remove the list formatting
-        toAdd.push(this.dropdownBulletBtn("X", "color: black; width: 40px;", undefined, this.view, this.listTypes, this.changeBulletType));
 
-        listTypeBtn = (new Dropdown(toAdd, { label: label, css: "color:black; width: 40px;" }) as MenuItem).render(this.view).dom;
-
-        //add this new button and return it
-        this.tooltip.appendChild(listTypeBtn);
-        return listTypeBtn;
+        return new Dropdown(targetTitle ? [linkInfo, linkApply, deleteLink] : [linkInfo, linkApply], { class: "buttonSettings-dropdown" }) as MenuItem;
     }
 
-    createSummarizer() {
-        return new MenuItem({
-            title: "Summarize",
-            label: "Summarize",
-            icon: icons.join,
-            css: "color:white;",
-            class: "menuicon",
-            execEvent: "",
-            run: (state, dispatch) => TooltipTextMenu.insertSummarizer(state, dispatch)
-        });
+    public MakeLinkToSelection = (linkDocId: string, title: string, location: string, targetDocId: string): string => {
+        const link = this.view.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + linkDocId), title: title, location: location, targetId: targetDocId });
+        this.view.dispatch(this.view.state.tr.removeMark(this.view.state.selection.from, this.view.state.selection.to, this.view.state.schema.marks.link).
+            addMark(this.view.state.selection.from, this.view.state.selection.to, link));
+        return this.view.state.selection.$from.nodeAfter?.text || "";
     }
 
-    public static insertSummarizer(state: EditorState<any>, dispatch: any) {
-        if (state.selection.empty) return false;
-        const mark = state.schema.marks.summarize.create();
-        const tr = state.tr;
-        tr.addMark(state.selection.from, state.selection.to, mark);
-        const content = tr.selection.content();
-        const newNode = state.schema.nodes.summary.create({ visibility: false, text: content, textslice: content.toJSON() });
-        dispatch && dispatch(tr.replaceSelectionWith(newNode).removeMark(tr.selection.from - 1, tr.selection.from, mark));
-        return true;
+    // SUMMARIZER TOOL
+    static insertSummarizer(state: EditorState<any>, dispatch: any) {
+        if (!state.selection.empty) {
+            const mark = state.schema.marks.summarize.create();
+            const tr = state.tr.addMark(state.selection.from, state.selection.to, mark);
+            const content = tr.selection.content();
+            const newNode = state.schema.nodes.summary.create({ visibility: false, text: content, textslice: content.toJSON() });
+            dispatch?.(tr.replaceSelectionWith(newNode).removeMark(tr.selection.from - 1, tr.selection.from, mark));
+        }
     }
 
+    // HIGHLIGHTER TOOL
     createHighlightTool() {
         return new MenuItem({
             title: "Highlight",
-            css: "color:white;",
+            css: "fill:white;",
             class: "menuicon",
             execEvent: "",
             render() {
@@ -589,12 +528,10 @@ export class TooltipTextMenu {
                 path.setAttributeNS(null, "d", "M0 479.98L99.92 512l35.45-35.45-67.04-67.04L0 479.98zm124.61-240.01a36.592 36.592 0 0 0-10.79 38.1l13.05 42.83-50.93 50.94 96.23 96.23 50.86-50.86 42.74 13.08c13.73 4.2 28.65-.01 38.15-10.78l35.55-41.64-173.34-173.34-41.52 35.44zm403.31-160.7l-63.2-63.2c-20.49-20.49-53.38-21.52-75.12-2.35L190.55 183.68l169.77 169.78L530.27 154.4c19.18-21.74 18.15-54.63-2.35-75.13z");
                 svg.appendChild(path);
 
-                const color = document.createElement("div");
-                color.className = "buttonColor";
+                const color = TooltipTextMenu.createDiv("buttonColor");
                 color.style.backgroundColor = TooltipTextMenuManager.Instance.highlighter.toString();
 
-                const wrapper = document.createElement("div");
-                wrapper.id = "colorPicker";
+                const wrapper = TooltipTextMenu.createDiv("colorPicker");
                 wrapper.appendChild(svg);
                 wrapper.appendChild(color);
                 return wrapper;
@@ -603,10 +540,10 @@ export class TooltipTextMenu {
         });
     }
 
-    public static insertHighlight(color: String, state: EditorState<any>, dispatch: any) {
-        if (state.selection.empty) return false;
-
-        toggleMark(state.schema.marks.marker, { highlight: color })(state, dispatch);
+    static insertHighlight(color: String, state: EditorState<any>, dispatch: any) {
+        if (!state.selection.empty) {
+            toggleMark(state.schema.marks.marker, { highlight: color })(state, dispatch);
+        }
     }
 
     createHighlightDropdown() {
@@ -621,8 +558,7 @@ export class TooltipTextMenu {
                 const p = document.createElement("p");
                 p.textContent = "Change highlight:";
 
-                const colorsWrapper = document.createElement("div");
-                colorsWrapper.className = "colorPicker-wrapper";
+                const colorsWrapper = TooltipTextMenu.createDiv("colorPicker-wrapper");
 
                 const colors = [
                     PastelSchemaPalette.get("pink2"),
@@ -671,14 +607,14 @@ export class TooltipTextMenu {
             }
         });
 
-        const colorDropdown = new Dropdown([colors], { class: "buttonSettings-dropdown" }) as MenuItem;
-        return colorDropdown;
+        return new Dropdown([colors], { class: "buttonSettings-dropdown" }) as MenuItem;
     }
 
+    // COLOR TOOL
     createColorTool() {
         return new MenuItem({
             title: "Color",
-            css: "color:white;",
+            css: "fill:white;",
             class: "menuicon",
             execEvent: "",
             render() {
@@ -688,27 +624,25 @@ export class TooltipTextMenu {
                 path.setAttributeNS(null, "d", "M204.3 5C104.9 24.4 24.8 104.3 5.2 203.4c-37 187 131.7 326.4 258.8 306.7 41.2-6.4 61.4-54.6 42.5-91.7-23.1-45.4 9.9-98.4 60.9-98.4h79.7c35.8 0 64.8-29.6 64.9-65.3C511.5 97.1 368.1-26.9 204.3 5zM96 320c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32zm32-128c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32zm128-64c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32zm128 64c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32z");
                 svg.appendChild(path);
 
-                const color = document.createElement("div");
-                color.className = "buttonColor";
+                const color = TooltipTextMenu.createDiv("buttonColor");
                 color.style.backgroundColor = TooltipTextMenuManager.Instance.color.toString();
 
-                const wrapper = document.createElement("div");
-                wrapper.id = "colorPicker";
+                const wrapper = TooltipTextMenu.createDiv("colorPicker");
                 wrapper.appendChild(svg);
                 wrapper.appendChild(color);
                 return wrapper;
             },
-            run: (state, dispatch) => this.insertColor(TooltipTextMenuManager.Instance.color, state, dispatch)
+            run: (state, dispatch) => TooltipTextMenu.insertColor(TooltipTextMenuManager.Instance.color, state, dispatch)
         });
     }
 
-    public insertColor(color: String, state: EditorState<any>, dispatch: any) {
+    static insertColor(color: String, state: EditorState<any>, dispatch: any) {
         const colorMark = state.schema.mark(state.schema.marks.pFontColor, { color: color });
         if (state.selection.empty) {
             dispatch(state.tr.addStoredMark(colorMark));
-            return false;
+        } else {
+            this.setMark(colorMark, state, dispatch);
         }
-        this.setMark(colorMark, state, dispatch);
     }
 
     createColorDropdown() {
@@ -723,8 +657,7 @@ export class TooltipTextMenu {
                 const p = document.createElement("p");
                 p.textContent = "Change color:";
 
-                const colorsWrapper = document.createElement("div");
-                colorsWrapper.className = "colorPicker-wrapper";
+                const colorsWrapper = TooltipTextMenu.createDiv("colorPicker-wrapper");
 
                 const colors = [
                     DarkPastelSchemaPalette.get("pink2"),
@@ -747,7 +680,7 @@ export class TooltipTextMenu {
                         button.onclick = e => {
                             TooltipTextMenuManager.Instance.color = color;
 
-                            self.insertColor(TooltipTextMenuManager.Instance.color, self.view.state, self.view.dispatch);
+                            TooltipTextMenu.insertColor(TooltipTextMenuManager.Instance.color, self.view.state, self.view.dispatch);
 
                             // update color menu
                             const colorDom = self.createColorTool().render(self.view).dom;
@@ -773,7 +706,8 @@ export class TooltipTextMenu {
         return new Dropdown([colors], { class: "buttonSettings-dropdown" }) as MenuItem;
     }
 
-    createBrush(active: boolean = false) {
+    // BRUSH TOOL
+    createBrushTool(active: boolean = false) {
         const icon = {
             height: 32, width: 32,
             path: "M30.828 1.172c-1.562-1.562-4.095-1.562-5.657 0l-5.379 5.379-3.793-3.793-4.243 4.243 3.326 3.326-14.754 14.754c-0.252 0.252-0.358 0.592-0.322 0.921h-0.008v5c0 0.552 0.448 1 1 1h5c0 0 0.083 0 0.125 0 0.288 0 0.576-0.11 0.795-0.329l14.754-14.754 3.326 3.326 4.243-4.243-3.793-3.793 5.379-5.379c1.562-1.562 1.562-4.095 0-5.657zM5.409 30h-3.409v-3.409l14.674-14.674 3.409 3.409-14.674 14.674z"
@@ -783,7 +717,7 @@ export class TooltipTextMenu {
             title: "Brush tool",
             label: "Brush tool",
             icon: icon,
-            css: "color:white;",
+            css: "fill:white;",
             class: active ? "menuicon-active" : "menuicon",
             execEvent: "",
             run: (state, dispatch) => {
@@ -800,15 +734,17 @@ export class TooltipTextMenu {
 
     brush_function(state: EditorState<any>, dispatch: any) {
         if (TooltipTextMenuManager.Instance._brushIsEmpty) {
-            const selected_marks = this.getMarksInSelection(this.view.state);
-            if (this._brushdom) {
-                if (selected_marks.size >= 0) {
-                    TooltipTextMenuManager.Instance._brushMarks = selected_marks;
-                    const newbrush = this.createBrush(true).render(this.view).dom;
-                    this.tooltip.replaceChild(newbrush, this._brushdom);
-                    this._brushdom = newbrush;
-                    TooltipTextMenuManager.Instance._brushIsEmpty = !TooltipTextMenuManager.Instance._brushIsEmpty;
-                }
+            // get marks in the selection
+            const selected_marks = new Set<Mark>();
+            const { from, to } = state.selection as TextSelection;
+            state.doc.nodesBetween(from, to, (node) => node.marks?.forEach(m => selected_marks.add(m)));
+
+            if (this._brushdom && selected_marks.size >= 0) {
+                TooltipTextMenuManager.Instance._brushMarks = selected_marks;
+                const newbrush = this.createBrushTool(true).render(this.view).dom;
+                this.tooltip.replaceChild(newbrush, this._brushdom);
+                this._brushdom = newbrush;
+                TooltipTextMenuManager.Instance._brushIsEmpty = !TooltipTextMenuManager.Instance._brushIsEmpty;
             }
         }
         else {
@@ -818,12 +754,12 @@ export class TooltipTextMenu {
                     if (TooltipTextMenuManager.Instance._brushMarks && to - from > 0) {
                         this.view.dispatch(this.view.state.tr.removeMark(from, to));
                         Array.from(TooltipTextMenuManager.Instance._brushMarks).filter(m => m.type !== schema.marks.user_mark).forEach((mark: Mark) => {
-                            this.setMark(mark, this.view.state, this.view.dispatch);
+                            TooltipTextMenu.setMark(mark, this.view.state, this.view.dispatch);
                         });
                     }
                 }
                 else {
-                    const newbrush = this.createBrush(false).render(this.view).dom;
+                    const newbrush = this.createBrushTool(false).render(this.view).dom;
                     this.tooltip.replaceChild(newbrush, this._brushdom);
                     this._brushdom = newbrush;
                     TooltipTextMenuManager.Instance._brushIsEmpty = !TooltipTextMenuManager.Instance._brushIsEmpty;
@@ -835,17 +771,12 @@ export class TooltipTextMenu {
     createBrushDropdown(active: boolean = false) {
         let label = "Stored marks: ";
         if (TooltipTextMenuManager.Instance._brushMarks && TooltipTextMenuManager.Instance._brushMarks.size > 0) {
-            TooltipTextMenuManager.Instance._brushMarks.forEach((mark: Mark) => {
-                const markType = mark.type;
-                label += markType.name;
-                label += ", ";
-            });
+            TooltipTextMenuManager.Instance._brushMarks.forEach((mark: Mark) => label += mark.type.name + ", ");
             label = label.substring(0, label.length - 2);
         } else {
             label = "No marks are currently stored";
         }
 
-
         const brushInfo = new MenuItem({
             title: "",
             label: label,
@@ -902,7 +833,7 @@ export class TooltipTextMenu {
 
                 // update brush tool
                 // TODO: this probably isn't very clean
-                const newBrushdom = self.createBrush().render(self.view).dom;
+                const newBrushdom = self.createBrushTool().render(self.view).dom;
                 self._brushdom && self.tooltip.replaceChild(newBrushdom, self._brushdom);
                 self._brushdom = newBrushdom;
                 const newBrushDropdowndom = self.createBrushDropdown().render(self.view).dom;
@@ -915,7 +846,7 @@ export class TooltipTextMenu {
         return new Dropdown(hasMarks ? [brushInfo, clearBrush] : [brushInfo], { class: "buttonSettings-dropdown" }) as MenuItem;
     }
 
-    setMark = (mark: Mark, state: EditorState<any>, dispatch: any) => {
+    static setMark = (mark: Mark, state: EditorState<any>, dispatch: any) => {
         if (mark) {
             const node = (state.selection as NodeSelection).node;
             if (node?.type === schema.nodes.ordered_list) {
@@ -936,182 +867,7 @@ export class TooltipTextMenu {
         }
     }
 
-    changeToFontFamily = (mark: Mark, view: EditorView) => {
-        const fontName = mark.attrs.family;
-        if (fontName) { this.updateFontStyleDropdown(fontName); }
-        if (this.editorProps) {
-            const ruleProvider = this.editorProps.ruleProvider;
-            const heading = NumCast(this.editorProps.Document.heading);
-            if (ruleProvider && heading) {
-                ruleProvider["ruleFont_" + heading] = fontName;
-            }
-        }
-        this.setMark(view.state.schema.marks.pFontFamily.create({ family: fontName }), view.state, view.dispatch);
-    }
-
-    changeToFontSize = (mark: Mark, view: EditorView) => {
-        const size = mark.attrs.fontSize;
-        if (size) { this.updateFontSizeDropdown(String(size) + " pt"); }
-        if (this.editorProps) {
-            const ruleProvider = this.editorProps.ruleProvider;
-            const heading = NumCast(this.editorProps.Document.heading);
-            if (ruleProvider && heading) {
-                ruleProvider["ruleSize_" + heading] = size;
-            }
-        }
-        this.setMark(view.state.schema.marks.pFontSize.create({ fontSize: size }), view.state, view.dispatch);
-    }
-
-    //remove all node typeand apply the passed-in one to the selected text
-    changeBulletType = (nodeType: NodeType | undefined) => {
-        //remove oldif (nodeType) { //add new
-        const view = this.view;
-        if (nodeType === schema.nodes.bullet_list) {
-            wrapInList(nodeType)(view.state, view.dispatch);
-        } else {
-            const marks = view.state.storedMarks || (view.state.selection.$to.parentOffset && view.state.selection.$from.marks());
-            if (!wrapInList(schema.nodes.ordered_list)(view.state, (tx2: any) => {
-                const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle);
-                marks && tx3.ensureMarks([...marks]);
-                marks && tx3.setStoredMarks([...marks]);
-
-                view.dispatch(tx2);
-            })) {
-                const tx2 = view.state.tr;
-                const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle);
-                marks && tx3.ensureMarks([...marks]);
-                marks && tx3.setStoredMarks([...marks]);
-
-                view.dispatch(tx3);
-            }
-        }
-    }
-
-    //makes a button for the drop down FOR MARKS
-    //css is the style you want applied to the button
-    dropdownFontFamilyBtn(label: string, css: string, mark: Mark, view: EditorView, changeFontFamily: (mark: Mark<any>, view: EditorView) => any) {
-        return new MenuItem({
-            title: "Set Font Family",
-            label: label,
-            execEvent: "",
-            class: "dropdown-item",
-            css: css,
-            enable() { return true; },
-            run() { changeFontFamily(mark, view); }
-        });
-    }
-    //makes a button for the drop down FOR MARKS
-    //css is the style you want applied to the button
-    dropdownFontSizeBtn(label: string, css: string, mark: Mark, view: EditorView, changeFontSize: (markType: Mark<any>, view: EditorView) => any) {
-        return new MenuItem({
-            title: "Set Font Size",
-            label: label,
-            execEvent: "",
-            class: "dropdown-item",
-            css: css,
-            enable() { return true; },
-            run() { changeFontSize(mark, view); }
-        });
-    }
-
-    //makes a button for the drop down FOR NODE TYPES
-    //css is the style you want applied to the button
-    dropdownBulletBtn(label: string, css: string, nodeType: NodeType | undefined, view: EditorView, groupNodes: NodeType[], changeToNodeInGroup: (nodeType: NodeType<any> | undefined, view: EditorView, groupNodes: NodeType[]) => any) {
-        return new MenuItem({
-            title: "Set Bullet Style",
-            label: label,
-            execEvent: "",
-            class: "dropdown-item",
-            css: css,
-            enable() { return true; },
-            run() { changeToNodeInGroup(nodeType, view, groupNodes); }
-        });
-    }
-
-    markActive = function (state: EditorState<any>, type: MarkType<Schema<string, string>>) {
-        const { from, $from, to, empty } = state.selection;
-        if (empty) return type.isInSet(state.storedMarks || $from.marks());
-        else return state.doc.rangeHasMark(from, to, type);
-    };
-
-    // Helper function to create menu icons
-    icon(text: string, name: string, title: string = name) {
-        const span = document.createElement("span");
-        span.className = name + " menuicon";
-        span.title = title;
-        span.textContent = text;
-        span.style.color = "white";
-        return span;
-    }
-
-    svgIcon(name: string, title: string = name, dpath: string) {
-        const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
-        svg.setAttribute("viewBox", "-100 -100 650 650");
-        const path = document.createElementNS('http://www.w3.org/2000/svg', "path");
-        path.setAttributeNS(null, "d", dpath);
-        svg.appendChild(path);
-
-        const span = document.createElement("span");
-        span.className = name + " menuicon";
-        span.title = title;
-        span.appendChild(svg);
-
-        return span;
-    }
-
-    //method for checking whether node can be inserted
-    canInsert(state: EditorState, nodeType: NodeType<Schema<string, string>>) {
-        const $from = state.selection.$from;
-        for (let d = $from.depth; d >= 0; d--) {
-            const index = $from.index(d);
-            if ($from.node(d).canReplaceWith(index, index, nodeType)) return true;
-        }
-        return false;
-    }
-
-
-    //adapted this method - use it to check if block has a tag (ie bulleting)
-    blockActive(type: NodeType<Schema<string, string>>, state: EditorState) {
-        const attrs = {};
-
-        if (state.selection instanceof NodeSelection) {
-            const sel: NodeSelection = state.selection;
-            const $from = sel.$from;
-            const to = sel.to;
-            const node = sel.node;
-
-            if (node) {
-                return node.hasMarkup(type, attrs);
-            }
-
-            return to <= $from.end() && $from.parent.hasMarkup(type, attrs);
-        }
-    }
-
-    // Create an icon for a heading at the given level
-    heading(level: number) {
-        return {
-            command: setBlockType(schema.nodes.heading, { level }),
-            dom: this.icon("H" + level, "heading")
-        };
-    }
-
-    getMarksInSelection(state: EditorState<any>) {
-        const found = new Set<Mark>();
-        const { from, to } = state.selection as TextSelection;
-        state.doc.nodesBetween(from, to, (node) => node.marks?.forEach(m => found.add(m)));
-        return found;
-    }
-
-    reset_mark_doms() {
-        const iterator = this._marksToDoms.values();
-        let next = iterator.next();
-        while (!next.done) {
-            next.value.style.color = "white";
-            next = iterator.next();
-        }
-    }
-
+    // called by Prosemirror
     update(view: EditorView, lastState: EditorState | undefined) { this.updateFromDash(view, lastState, this.editorProps); }
     //updates the tooltip menu when the selection changes
     public async updateFromDash(view: EditorView, lastState: EditorState | undefined, props: any) {
@@ -1120,58 +876,45 @@ export class TooltipTextMenu {
             return;
         }
         this.view = view;
-        const state = view.state;
         DocumentDecorations.Instance.showTextBar();
         props && (this.editorProps = props);
-        // Don't do anything if the document/selection didn't change
-        if (lastState && lastState.doc.eq(state.doc) &&
-            lastState.selection.eq(state.selection)) return;
-
-        this.reset_mark_doms();
-
-        // update link dropdown
-        const linkDropdown = await this.createLinkDropdown();
-        const newLinkDropdowndom = linkDropdown.render(this.view).dom;
-        this._linkDropdownDom && this.tooltip.replaceChild(newLinkDropdowndom, this._linkDropdownDom);
-        this._linkDropdownDom = newLinkDropdowndom;
-
-        //UPDATE FONT STYLE DROPDOWN
-        const activeStyles = this.activeFontFamilyOnSelection();
-        if (activeStyles !== undefined) {
-            if (activeStyles.length === 1) {
-                console.log("updating font style dropdown", activeStyles[0]);
-                activeStyles[0] && this.updateFontStyleDropdown(activeStyles[0]);
-            } else this.updateFontStyleDropdown(activeStyles.length ? "various" : "default");
-        }
 
-        //UPDATE FONT SIZE DROPDOWN
-        const activeSizes = this.activeFontSizeOnSelection();
-        if (activeSizes !== undefined) {
-            if (activeSizes.length === 1) { //if there's only one active font size
-                activeSizes[0] && this.updateFontSizeDropdown(String(activeSizes[0]) + " pt");
-            } else this.updateFontSizeDropdown(activeSizes.length ? "various" : "default");
+        // Don't do anything if the document/selection didn't change
+        if (!lastState || !lastState.doc.eq(view.state.doc) || !lastState.selection.eq(view.state.selection)) {
+
+            // UPDATE LINK DROPDOWN
+            const linkTarget = await this.getTextLinkTargetTitle()
+            const linkDom = this.createLinkTool(linkTarget ? true : false).render(this.view).dom;
+            const linkDropdownDom = this.createLinkDropdown(linkTarget).render(this.view).dom;
+            this.linkDom && this.tooltip.replaceChild(linkDom, this.linkDom);
+            this.linkDropdownDom && this.tooltip.replaceChild(linkDropdownDom, this.linkDropdownDom);
+            this.linkDom = linkDom;
+            this.linkDropdownDom = linkDropdownDom;
+
+            //UPDATE FONT STYLE DROPDOWN
+            const activeStyles = this.activeFontFamilyOnSelection();
+            this.updateFontStyleDropdown(activeStyles.length === 1 ? activeStyles[0] : activeStyles.length ? "various" : "default");
+
+            //UPDATE FONT SIZE DROPDOWN
+            const activeSizes = this.activeFontSizeOnSelection();
+            this.updateFontSizeDropdown(activeSizes.length === 1 ? String(activeSizes[0]) + " pt" : activeSizes.length ? "various" : "default");
+
+            //UPDATE ALL OTHER BUTTONS
+            this.updateHighlightStateOfButtons();
         }
-
-        this.update_mark_doms();
     }
-    update_mark_doms() {
-        this.reset_mark_doms();
-        this._activeMarks.forEach((mark) => {
-            if (this._marksToDoms.has(mark)) {
-                const dom = this._marksToDoms.get(mark);
-                if (dom) dom.style.color = "greenyellow";
-            }
-        });
+
+    updateHighlightStateOfButtons() {
+        Array.from(this._marksToDoms.values()).forEach(val => val.style.fill = "white");
+        this.activeMarksOnSelection().filter(mark => this._marksToDoms.has(mark)).forEach(mark =>
+            this._marksToDoms.get(mark)!.style.fill = "greenyellow");
 
         // keeps brush tool highlighted if active when switching between textboxes
-        if (!TooltipTextMenuManager.Instance._brushIsEmpty) {
-            if (this._brushdom) {
-                const newbrush = this.createBrush(true).render(this.view).dom;
-                this.tooltip.replaceChild(newbrush, this._brushdom);
-                this._brushdom = newbrush;
-            }
+        if (!TooltipTextMenuManager.Instance._brushIsEmpty && this._brushdom) {
+            const newbrush = this.createBrushTool(true).render(this.view).dom;
+            this.tooltip.replaceChild(newbrush, this._brushdom);
+            this._brushdom = newbrush;
         }
-
     }
 
     //finds fontSize at start of selection
@@ -1199,24 +942,21 @@ export class TooltipTextMenu {
         return activeFamilies;
     }
     //finds all active marks on selection in given group
-    activeMarksOnSelection(markGroup: MarkType[]) {
+    activeMarksOnSelection() {
+        const markGroup = Array.from(this._marksToDoms.keys());
+        if (this.view.state.storedMarks) return this.view.state.storedMarks.map(mark => mark.type);
         //current selection
         const { empty, ranges, $to } = this.view.state.selection as TextSelection;
         const state = this.view.state;
-        const dispatch = this.view.dispatch;
-        let activeMarks: MarkType[];
+        let activeMarks: MarkType[] = [];
         if (!empty) {
             activeMarks = markGroup.filter(mark => {
                 const has = false;
                 for (let i = 0; !has && i < ranges.length; i++) {
-                    const { $from, $to } = ranges[i];
-                    return state.doc.rangeHasMark($from.pos, $to.pos, mark);
+                    return state.doc.rangeHasMark(ranges[i].$from.pos, ranges[i].$to.pos, mark);
                 }
                 return false;
             });
-
-            const refnode = this.reference_node($to);
-            this._activeMarks = refnode.marks;
         }
         else {
             const pos = this.view.state.selection.$from;
@@ -1227,7 +967,6 @@ export class TooltipTextMenu {
                 else {
                     return [];
                 }
-                this._activeMarks = ref_node.marks;
                 activeMarks = markGroup.filter(mark_type => {
                     if (mark_type === state.schema.marks.pFontSize) {
                         return ref_node.marks.some(m => m.type.name === state.schema.marks.pFontSize.name);
@@ -1236,10 +975,6 @@ export class TooltipTextMenu {
                     return ref_node.marks.includes(mark);
                 });
             }
-            else {
-                return [];
-            }
-
         }
         return activeMarks;
     }
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 0ef842275..202bfe400 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -119,7 +119,7 @@ export class DocumentButtonBar extends React.Component<{ views: (DocumentView |
 
                         const anchor2Title = linkDoc.anchor2 instanceof Doc ? StrCast(linkDoc.anchor2.title) : "-untitled-";
                         if (linkDoc.anchor2 instanceof Doc) {
-                            const text = FormattedTextBox.ToolTipTextMenu.makeLink(linkDoc[Id], anchor2Title, e.ctrlKey ? "onRight" : "inTab", linkDoc.anchor2[Id]);
+                            const text = FormattedTextBox.ToolTipTextMenu.MakeLinkToSelection(linkDoc[Id], anchor2Title, e.ctrlKey ? "onRight" : "inTab", linkDoc.anchor2[Id]);
                             proto.title = text === "" ? proto.title : text + " to " + linkDoc.anchor2.title; // TODO open to more descriptive descriptions of following in text link
                         }
                     }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index 059393142..1382b01d3 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -8,6 +8,7 @@ import v5 = require("uuid/v5");
 import { DocumentType } from "../../../documents/DocumentTypes";
 import { observable, action, reaction, IReactionDisposer } from "mobx";
 import { StrCast } from "../../../../new_fields/Types";
+import { Id } from "../../../../new_fields/FieldSymbols";
 
 export interface CollectionFreeFormLinkViewProps {
     A: DocumentView;
@@ -41,10 +42,36 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
                     apt.point.x, apt.point.y);
                 const afield = StrCast(this.props.A.props.Document[StrCast(this.props.A.props.layoutKey, "layout")]).indexOf("anchor1") === -1 ? "anchor2" : "anchor1";
                 const bfield = afield === "anchor1" ? "anchor2" : "anchor1";
-                this.props.A.props.Document[afield + "_x"] = (apt.point.x - abounds.left) / abounds.width * 100;
-                this.props.A.props.Document[afield + "_y"] = (apt.point.y - abounds.top) / abounds.height * 100;
-                this.props.A.props.Document[bfield + "_x"] = (bpt.point.x - bbounds.left) / bbounds.width * 100;
-                this.props.A.props.Document[bfield + "_y"] = (bpt.point.y - bbounds.top) / bbounds.height * 100;
+
+                // really hacky stuff to make the DocuLinkBox display where we want it to:
+                //   if there's an element in the DOM with the id of the opposite anchor, then that DOM element is a hyperlink source for the current anchor and we want to place our link box at it's top right
+                //   otherwise, we just use the computed nearest point on the document boundary to the target Document
+                const targetAhyperlink = window.document.getElementById((this.props.LinkDocs[0][afield] as Doc)[Id]);
+                const targetBhyperlink = window.document.getElementById((this.props.LinkDocs[0][bfield] as Doc)[Id]);
+                if (!targetBhyperlink) {
+                    this.props.A.props.Document[afield + "_x"] = (apt.point.x - abounds.left) / abounds.width * 100;
+                    this.props.A.props.Document[afield + "_y"] = (apt.point.y - abounds.top) / abounds.height * 100;
+                } else {
+                    setTimeout(() => {
+                        (this.props.A.props.Document[(this.props.A.props as any).fieldKey] as Doc);
+                        let m = targetBhyperlink.getBoundingClientRect();
+                        let mp = this.props.A.props.ScreenToLocalTransform().transformPoint(m.right, m.top + 5);
+                        this.props.A.props.Document[afield + "_x"] = mp[0] / this.props.A.props.PanelWidth() * 100;
+                        this.props.A.props.Document[afield + "_y"] = mp[1] / this.props.A.props.PanelHeight() * 100;
+                    }, 0);
+                }
+                if (!targetAhyperlink) {
+                    this.props.A.props.Document[bfield + "_x"] = (bpt.point.x - bbounds.left) / bbounds.width * 100;
+                    this.props.A.props.Document[bfield + "_y"] = (bpt.point.y - bbounds.top) / bbounds.height * 100;
+                } else {
+                    setTimeout(() => {
+                        (this.props.B.props.Document[(this.props.B.props as any).fieldKey] as Doc);
+                        let m = targetAhyperlink.getBoundingClientRect();
+                        let mp = this.props.B.props.ScreenToLocalTransform().transformPoint(m.right, m.top + 5);
+                        this.props.B.props.Document[afield + "_x"] = mp[0] / this.props.B.props.PanelWidth() * 100;
+                        this.props.B.props.Document[afield + "_y"] = mp[1] / this.props.B.props.PanelHeight() * 100;
+                    }, 0);
+                }
             })
             , { fireImmediately: true });
     }
diff --git a/src/client/views/nodes/DocuLinkBox.tsx b/src/client/views/nodes/DocuLinkBox.tsx
index d17b2e498..0d4d50c59 100644
--- a/src/client/views/nodes/DocuLinkBox.tsx
+++ b/src/client/views/nodes/DocuLinkBox.tsx
@@ -68,25 +68,11 @@ export class DocuLinkBox extends DocComponent<FieldViewProps, DocLinkSchema>(Doc
     }
 
     render() {
-        const anchorDoc = Cast(this.props.Document[this.props.fieldKey], Doc);
-        let anchorScale = anchorDoc instanceof Doc && anchorDoc.type === DocumentType.PDFANNO ? 0.33 : 1;
-        let y = NumCast(this.props.Document[this.props.fieldKey + "_y"], 100);
-        let x = NumCast(this.props.Document[this.props.fieldKey + "_x"], 100);
+        const x = NumCast(this.props.Document[this.props.fieldKey + "_x"], 100);
+        const y = NumCast(this.props.Document[this.props.fieldKey + "_y"], 100);
         const c = StrCast(this.props.Document.backgroundColor, "lightblue");
         const anchor = this.props.fieldKey === "anchor1" ? "anchor2" : "anchor1";
-
-        // really hacky stuff to make the link box display at the top right of hypertext link in a formatted text box.  somehow, this should get moved into the hyperlink itself...
-        const other = window.document.getElementById((this.props.Document[anchor] as Doc)[Id]);
-        if (other) {
-            (this.props.Document[this.props.fieldKey] as Doc)?.data; // ugh .. assumes that 'data' is the field used to store the text
-            setTimeout(() => {
-                let m = other.getBoundingClientRect();
-                let mp = this.props.ScreenToLocalTransform().transformPoint(m.right - 5, m.top + 5);
-                this.props.Document[this.props.fieldKey + "_x"] = mp[0] / this.props.PanelWidth() * 100;
-                this.props.Document[this.props.fieldKey + "_y"] = mp[1] / this.props.PanelHeight() * 100;
-            }, 0);
-            anchorScale = 0.15;
-        }
+        const anchorScale = (x === 0 || x === 100 || y === 0 || y === 100) ? 1 : .15;
 
         const timecode = this.props.Document[anchor + "Timecode"];
         const targetTitle = StrCast((this.props.Document[anchor]! as Doc).title) + (timecode !== undefined ? ":" + timecode : "");
-- 
cgit v1.2.3-70-g09d2


From 540bda7295f6ee7c2eed848598de6f5df74b2723 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 9 Jan 2020 10:04:24 -0500
Subject: fixed many warnings

---
 src/client/util/TooltipTextMenu.tsx                |   4 +-
 .../CollectionFreeFormLinkView.tsx                 |  12 +-
 src/client/views/nodes/DocumentView.tsx            |   1 -
 src/pen-gestures/ndollar.ts                        | 122 +++++++++++----------
 4 files changed, 71 insertions(+), 68 deletions(-)

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

diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 8aa304fad..e56930624 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -80,7 +80,7 @@ export class TooltipTextMenu {
             span.appendChild(svg);
 
             return span;
-        }
+        };
 
         const basicItems = [ // init basicItems in minimized toolbar -- paths to svgs are obtained from fontawesome
             { mark: schema.marks.strong, dom: svgIcon("strong", "Bold", "M333.49 238a122 122 0 0 0 27-65.21C367.87 96.49 308 32 233.42 32H34a16 16 0 0 0-16 16v48a16 16 0 0 0 16 16h31.87v288H34a16 16 0 0 0-16 16v48a16 16 0 0 0 16 16h209.32c70.8 0 134.14-51.75 141-122.4 4.74-48.45-16.39-92.06-50.83-119.6zM145.66 112h87.76a48 48 0 0 1 0 96h-87.76zm87.76 288h-87.76V288h87.76a56 56 0 0 1 0 112z") },
@@ -883,7 +883,7 @@ export class TooltipTextMenu {
         if (!lastState || !lastState.doc.eq(view.state.doc) || !lastState.selection.eq(view.state.selection)) {
 
             // UPDATE LINK DROPDOWN
-            const linkTarget = await this.getTextLinkTargetTitle()
+            const linkTarget = await this.getTextLinkTargetTitle();
             const linkDom = this.createLinkTool(linkTarget ? true : false).render(this.view).dom;
             const linkDropdownDom = this.createLinkDropdown(linkTarget).render(this.view).dom;
             this.linkDom && this.tooltip.replaceChild(linkDom, this.linkDom);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index 178a5bcdc..b8fbaef5c 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -54,8 +54,8 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
                 } else {
                     setTimeout(() => {
                         (this.props.A.props.Document[(this.props.A.props as any).fieldKey] as Doc);
-                        let m = targetBhyperlink.getBoundingClientRect();
-                        let mp = this.props.A.props.ScreenToLocalTransform().transformPoint(m.right, m.top + 5);
+                        const m = targetBhyperlink.getBoundingClientRect();
+                        const mp = this.props.A.props.ScreenToLocalTransform().transformPoint(m.right, m.top + 5);
                         this.props.A.props.Document[afield + "_x"] = mp[0] / this.props.A.props.PanelWidth() * 100;
                         this.props.A.props.Document[afield + "_y"] = mp[1] / this.props.A.props.PanelHeight() * 100;
                     }, 0);
@@ -66,8 +66,8 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
                 } else {
                     setTimeout(() => {
                         (this.props.B.props.Document[(this.props.B.props as any).fieldKey] as Doc);
-                        let m = targetAhyperlink.getBoundingClientRect();
-                        let mp = this.props.B.props.ScreenToLocalTransform().transformPoint(m.right, m.top + 5);
+                        const m = targetAhyperlink.getBoundingClientRect();
+                        const mp = this.props.B.props.ScreenToLocalTransform().transformPoint(m.right, m.top + 5);
                         this.props.B.props.Document[afield + "_x"] = mp[0] / this.props.B.props.PanelWidth() * 100;
                         this.props.B.props.Document[afield + "_y"] = mp[1] / this.props.B.props.PanelHeight() * 100;
                     }, 0);
@@ -93,8 +93,8 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
             apt.point.x, apt.point.y);
         const pt1 = [apt.point.x, apt.point.y];
         const pt2 = [bpt.point.x, bpt.point.y];
-        let aActive = this.props.A.isSelected() || Doc.IsBrushed(this.props.A.props.Document);
-        let bActive = this.props.A.isSelected() || Doc.IsBrushed(this.props.A.props.Document);
+        const aActive = this.props.A.isSelected() || Doc.IsBrushed(this.props.A.props.Document);
+        const bActive = this.props.A.isSelected() || Doc.IsBrushed(this.props.A.props.Document);
         return !aActive && !bActive ? (null) :
             <line key="linkLine" className="collectionfreeformlinkview-linkLine"
                 style={{ opacity: this._opacity, strokeDasharray: "2 2" }}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index a14f69f71..10d2e2b3e 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -253,7 +253,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             document.removeEventListener("touchend", this.onTouchEnd);
             document.addEventListener("touchend", this.onTouchEnd);
             if ((e.nativeEvent as any).formattedHandled) e.stopPropagation();
-            console.log("down")
         }
     }
 
diff --git a/src/pen-gestures/ndollar.ts b/src/pen-gestures/ndollar.ts
index 12c2b25bb..872c524d6 100644
--- a/src/pen-gestures/ndollar.ts
+++ b/src/pen-gestures/ndollar.ts
@@ -95,7 +95,7 @@ export class Unistroke {
 
     constructor(public Name: string, useBoundedRotationInvariance: boolean, points: Point[]) {
         this.Points = Resample(points, NumPoints);
-        var radians = IndicativeAngle(this.Points);
+        const radians = IndicativeAngle(this.Points);
         this.Points = RotateBy(this.Points, -radians);
         this.Points = ScaleDimTo(this.Points, SquareSize, OneDThreshold);
         if (useBoundedRotationInvariance) {
@@ -117,14 +117,14 @@ export class Multistroke {
     {
         this.NumStrokes = strokes.length; // number of individual strokes
 
-        var order = new Array(strokes.length); // array of integer indices
+        const order = new Array(strokes.length); // array of integer indices
         for (var i = 0; i < strokes.length; i++) {
             order[i] = i; // initialize
         }
-        var orders = new Array(); // array of integer arrays
+        const orders = new Array(); // array of integer arrays
         HeapPermute(strokes.length, order, /*out*/ orders);
 
-        var unistrokes = MakeUnistrokes(strokes, orders); // returns array of point arrays
+        const unistrokes = MakeUnistrokes(strokes, orders); // returns array of point arrays
         this.Unistrokes = new Array(unistrokes.length); // unistrokes for this multistroke
         for (var j = 0; j < unistrokes.length; j++) {
             this.Unistrokes[j] = new Unistroke(this.Name, useBoundedRotationInvariance, unistrokes[j]);
@@ -247,15 +247,15 @@ export class NDollarRecognizer {
     }
 
     Recognize = (strokes: any[], useBoundedRotationInvariance: boolean = false, requireSameNoOfStrokes: boolean = false, useProtractor: boolean = true) => {
-        var t0 = Date.now();
-        var points = CombineStrokes(strokes); // make one connected unistroke from the given strokes
-        var candidate = new Unistroke("", useBoundedRotationInvariance, points);
+        const t0 = Date.now();
+        const points = CombineStrokes(strokes); // make one connected unistroke from the given strokes
+        const candidate = new Unistroke("", useBoundedRotationInvariance, points);
 
         var u = -1;
         var b = +Infinity;
         for (var i = 0; i < this.Multistrokes.length; i++) // for each multistroke template
         {
-            if (!requireSameNoOfStrokes || strokes.length == this.Multistrokes[i].NumStrokes) // optional -- only attempt match when same # of component strokes
+            if (!requireSameNoOfStrokes || strokes.length === this.Multistrokes[i].NumStrokes) // optional -- only attempt match when same # of component strokes
             {
                 for (var j = 0; j < this.Multistrokes[i].Unistrokes.length; j++) // for each unistroke within this multistroke
                 {
@@ -276,15 +276,15 @@ export class NDollarRecognizer {
                 }
             }
         }
-        var t1 = Date.now();
-        return (u == -1) ? null : new Result(this.Multistrokes[u].Name, useProtractor ? (1.0 - b) : (1.0 - b / HalfDiagonal), t1 - t0);
+        const t1 = Date.now();
+        return (u === -1) ? null : new Result(this.Multistrokes[u].Name, useProtractor ? (1.0 - b) : (1.0 - b / HalfDiagonal), t1 - t0);
     }
 
     AddGesture = (name: string, useBoundedRotationInvariance: boolean, strokes: any[]) => {
         this.Multistrokes[this.Multistrokes.length] = new Multistroke(name, useBoundedRotationInvariance, strokes);
         var num = 0;
         for (var i = 0; i < this.Multistrokes.length; i++) {
-            if (this.Multistrokes[i].Name == name) {
+            if (this.Multistrokes[i].Name === name) {
                 num++;
             }
         }
@@ -302,17 +302,17 @@ export class NDollarRecognizer {
 // Private helper functions from here on down
 //
 function HeapPermute(n: number, order: any[], /*out*/ orders: any[]) {
-    if (n == 1) {
+    if (n === 1) {
         orders[orders.length] = order.slice(); // append copy
     } else {
         for (var i = 0; i < n; i++) {
             HeapPermute(n - 1, order, orders);
-            if (n % 2 == 1) { // swap 0, n-1
-                var tmp = order[0];
+            if (n % 2 === 1) { // swap 0, n-1
+                const tmp = order[0];
                 order[0] = order[n - 1];
                 order[n - 1] = tmp;
             } else { // swap i, n-1
-                var tmp = order[i];
+                const tmp = order[i];
                 order[i] = order[n - 1];
                 order[n - 1] = tmp;
             }
@@ -321,14 +321,14 @@ function HeapPermute(n: number, order: any[], /*out*/ orders: any[]) {
 }
 
 function MakeUnistrokes(strokes: any, orders: any) {
-    var unistrokes = new Array(); // array of point arrays
+    const unistrokes = new Array(); // array of point arrays
     for (var r = 0; r < orders.length; r++) {
         for (var b = 0; b < Math.pow(2, orders[r].length); b++) // use b's bits for directions
         {
-            var unistroke = new Array(); // array of points
+            const unistroke = new Array(); // array of points
             for (var i = 0; i < orders[r].length; i++) {
                 var pts;
-                if (((b >> i) & 1) == 1) {// is b's bit at index i on?
+                if (((b >> i) & 1) === 1) {// is b's bit at index i on?
                     pts = strokes[orders[r][i]].slice().reverse(); // copy and reverse
                 }
                 else {
@@ -345,69 +345,71 @@ function MakeUnistrokes(strokes: any, orders: any) {
 }
 
 function CombineStrokes(strokes: any) {
-    var points = new Array();
+    const points = new Array();
     for (var s = 0; s < strokes.length; s++) {
-        for (var p = 0; p < strokes[s].length; p++)
+        for (var p = 0; p < strokes[s].length; p++) {
             points[points.length] = new Point(strokes[s][p].X, strokes[s][p].Y);
+        }
     }
     return points;
 }
 function Resample(points: any, n: any) {
-    var I = PathLength(points) / (n - 1); // interval length
+    const I = PathLength(points) / (n - 1); // interval length
     var D = 0.0;
-    var newpoints = new Array(points[0]);
+    const newpoints = new Array(points[0]);
     for (var i = 1; i < points.length; i++) {
-        var d = Distance(points[i - 1], points[i]);
+        const d = Distance(points[i - 1], points[i]);
         if ((D + d) >= I) {
-            var qx = points[i - 1].X + ((I - D) / d) * (points[i].X - points[i - 1].X);
-            var qy = points[i - 1].Y + ((I - D) / d) * (points[i].Y - points[i - 1].Y);
-            var q = new Point(qx, qy);
+            const qx = points[i - 1].X + ((I - D) / d) * (points[i].X - points[i - 1].X);
+            const qy = points[i - 1].Y + ((I - D) / d) * (points[i].Y - points[i - 1].Y);
+            const q = new Point(qx, qy);
             newpoints[newpoints.length] = q; // append new point 'q'
             points.splice(i, 0, q); // insert 'q' at position i in points s.t. 'q' will be the next i
             D = 0.0;
         }
         else D += d;
     }
-    if (newpoints.length == n - 1) // somtimes we fall a rounding-error short of adding the last point, so add it if so
+    if (newpoints.length === n - 1) {// sometimes we fall a rounding-error short of adding the last point, so add it if so
         newpoints[newpoints.length] = new Point(points[points.length - 1].X, points[points.length - 1].Y);
+    }
     return newpoints;
 }
 function IndicativeAngle(points: any) {
-    var c = Centroid(points);
+    const c = Centroid(points);
     return Math.atan2(c.Y - points[0].Y, c.X - points[0].X);
 }
 function RotateBy(points: any, radians: any) // rotates points around centroid
 {
-    var c = Centroid(points);
-    var cos = Math.cos(radians);
-    var sin = Math.sin(radians);
-    var newpoints = new Array();
+    const c = Centroid(points);
+    const cos = Math.cos(radians);
+    const sin = Math.sin(radians);
+    const newpoints = new Array();
     for (var i = 0; i < points.length; i++) {
-        var qx = (points[i].X - c.X) * cos - (points[i].Y - c.Y) * sin + c.X
-        var qy = (points[i].X - c.X) * sin + (points[i].Y - c.Y) * cos + c.Y;
+        const qx = (points[i].X - c.X) * cos - (points[i].Y - c.Y) * sin + c.X;
+        const qy = (points[i].X - c.X) * sin + (points[i].Y - c.Y) * cos + c.Y;
         newpoints[newpoints.length] = new Point(qx, qy);
     }
     return newpoints;
 }
 function ScaleDimTo(points: any, size: any, ratio1D: any) // scales bbox uniformly for 1D, non-uniformly for 2D
 {
-    var B = BoundingBox(points);
-    var uniformly = Math.min(B.Width / B.Height, B.Height / B.Width) <= ratio1D; // 1D or 2D gesture test
-    var newpoints = new Array();
+    const B = BoundingBox(points);
+    const uniformly = Math.min(B.Width / B.Height, B.Height / B.Width) <= ratio1D; // 1D or 2D gesture test
+    const newpoints = new Array();
     for (var i = 0; i < points.length; i++) {
-        var qx = uniformly ? points[i].X * (size / Math.max(B.Width, B.Height)) : points[i].X * (size / B.Width);
-        var qy = uniformly ? points[i].Y * (size / Math.max(B.Width, B.Height)) : points[i].Y * (size / B.Height);
+        const qx = uniformly ? points[i].X * (size / Math.max(B.Width, B.Height)) : points[i].X * (size / B.Width);
+        const qy = uniformly ? points[i].Y * (size / Math.max(B.Width, B.Height)) : points[i].Y * (size / B.Height);
         newpoints[newpoints.length] = new Point(qx, qy);
     }
     return newpoints;
 }
 function TranslateTo(points: any, pt: any) // translates points' centroid
 {
-    var c = Centroid(points);
-    var newpoints = new Array();
+    const c = Centroid(points);
+    const newpoints = new Array();
     for (var i = 0; i < points.length; i++) {
-        var qx = points[i].X + pt.X - c.X;
-        var qy = points[i].Y + pt.Y - c.Y;
+        const qx = points[i].X + pt.X - c.X;
+        const qy = points[i].Y + pt.Y - c.Y;
         newpoints[newpoints.length] = new Point(qx, qy);
     }
     return newpoints;
@@ -417,21 +419,21 @@ function Vectorize(points: any, useBoundedRotationInvariance: any) // for Protra
     var cos = 1.0;
     var sin = 0.0;
     if (useBoundedRotationInvariance) {
-        var iAngle = Math.atan2(points[0].Y, points[0].X);
-        var baseOrientation = (Math.PI / 4.0) * Math.floor((iAngle + Math.PI / 8.0) / (Math.PI / 4.0));
+        const iAngle = Math.atan2(points[0].Y, points[0].X);
+        const baseOrientation = (Math.PI / 4.0) * Math.floor((iAngle + Math.PI / 8.0) / (Math.PI / 4.0));
         cos = Math.cos(baseOrientation - iAngle);
         sin = Math.sin(baseOrientation - iAngle);
     }
     var sum = 0.0;
-    var vector = new Array<number>();
+    const vector = new Array<number>();
     for (var i = 0; i < points.length; i++) {
-        var newX = points[i].X * cos - points[i].Y * sin;
-        var newY = points[i].Y * cos + points[i].X * sin;
+        const newX = points[i].X * cos - points[i].Y * sin;
+        const newY = points[i].Y * cos + points[i].X * sin;
         vector[vector.length] = newX;
         vector[vector.length] = newY;
         sum += newX * newX + newY * newY;
     }
-    var magnitude = Math.sqrt(sum);
+    const magnitude = Math.sqrt(sum);
     for (var i = 0; i < vector.length; i++) {
         vector[i] /= magnitude;
     }
@@ -445,7 +447,7 @@ function OptimalCosineDistance(v1: any, v2: any) // for Protractor
         a += v1[i] * v2[i] + v1[i + 1] * v2[i + 1];
         b += v1[i] * v2[i + 1] - v1[i + 1] * v2[i];
     }
-    var angle = Math.atan(b / a);
+    const angle = Math.atan(b / a);
     return Math.acos(a * Math.cos(angle) + b * Math.sin(angle));
 }
 function DistanceAtBestAngle(points: any, T: any, a: any, b: any, threshold: any) {
@@ -471,7 +473,7 @@ function DistanceAtBestAngle(points: any, T: any, a: any, b: any, threshold: any
     return Math.min(f1, f2);
 }
 function DistanceAtAngle(points: any, T: any, radians: any) {
-    var newpoints = RotateBy(points, radians);
+    const newpoints = RotateBy(points, radians);
     return PathDistance(newpoints, T.Points);
 }
 function Centroid(points: any) {
@@ -497,33 +499,35 @@ function BoundingBox(points: any) {
 function PathDistance(pts1: any, pts2: any) // average distance between corresponding points in two paths
 {
     var d = 0.0;
-    for (var i = 0; i < pts1.length; i++) // assumes pts1.length == pts2.length
+    for (var i = 0; i < pts1.length; i++) {// assumes pts1.length == pts2.length
         d += Distance(pts1[i], pts2[i]);
+    }
     return d / pts1.length;
 }
 function PathLength(points: any) // length traversed by a point path
 {
     var d = 0.0;
-    for (var i = 1; i < points.length; i++)
+    for (var i = 1; i < points.length; i++) {
         d += Distance(points[i - 1], points[i]);
+    }
     return d;
 }
 function Distance(p1: any, p2: any) // distance between two points
 {
-    var dx = p2.X - p1.X;
-    var dy = p2.Y - p1.Y;
+    const dx = p2.X - p1.X;
+    const dy = p2.Y - p1.Y;
     return Math.sqrt(dx * dx + dy * dy);
 }
 function CalcStartUnitVector(points: any, index: any) // start angle from points[0] to points[index] normalized as a unit vector
 {
-    var v = new Point(points[index].X - points[0].X, points[index].Y - points[0].Y);
-    var len = Math.sqrt(v.X * v.X + v.Y * v.Y);
+    const v = new Point(points[index].X - points[0].X, points[index].Y - points[0].Y);
+    const len = Math.sqrt(v.X * v.X + v.Y * v.Y);
     return new Point(v.X / len, v.Y / len);
 }
 function AngleBetweenUnitVectors(v1: any, v2: any) // gives acute angle between unit vectors from (0,0) to v1, and (0,0) to v2
 {
-    var n = (v1.X * v2.X + v1.Y * v2.Y);
-    var c = Math.max(-1.0, Math.min(1.0, n)); // ensure [-1,+1]
+    const n = (v1.X * v2.X + v1.Y * v2.Y);
+    const c = Math.max(-1.0, Math.min(1.0, n)); // ensure [-1,+1]
     return Math.acos(c); // arc cosine of the vector dot product
 }
 function Deg2Rad(d: any) { return (d * Math.PI / 180.0); }
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From 5abc4de3c3495db71b0a9bcbe2468828f674eff3 Mon Sep 17 00:00:00 2001
From: Fawn <fangrui_tong@brown.edu>
Date: Thu, 9 Jan 2020 16:41:52 -0500
Subject: minor merge

---
 package-lock.json                   |  6 +-----
 src/client/util/RichTextMenu.tsx    |  4 ++--
 src/client/util/TooltipTextMenu.tsx | 14 ++++++--------
 src/client/views/AntimodeMenu.tsx   |  2 +-
 4 files changed, 10 insertions(+), 16 deletions(-)

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

diff --git a/package-lock.json b/package-lock.json
index cad3edd82..6aa0ffc02 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -7073,11 +7073,7 @@
                 "babel-runtime": "^6.22.0",
                 "bluebird": "^3.4.7",
                 "boolify-string": "^2.0.2",
-<<<<<<< HEAD
-                "emit-logger": "github:chocolateboy/emit-logger#b9d25a2d939e42f29c940861e9648bd0fb810070",
-=======
                 "emit-logger": "github:chocolateboy/emit-logger#better-emitter-name",
->>>>>>> 540bda7295f6ee7c2eed848598de6f5df74b2723
                 "lodash": "^4.17.4",
                 "semver": "^5.0.3",
                 "source-map-support": "^0.5.9"
@@ -18091,4 +18087,4 @@
             }
         }
     }
-}
+}
\ No newline at end of file
diff --git a/src/client/util/RichTextMenu.tsx b/src/client/util/RichTextMenu.tsx
index e367cf350..375c10da3 100644
--- a/src/client/util/RichTextMenu.tsx
+++ b/src/client/util/RichTextMenu.tsx
@@ -208,7 +208,7 @@ export default class RichTextMenu extends AntimodeMenu {
     getMarksInSelection(state: EditorState<any>) {
         const found = new Set<Mark>();
         const { from, to } = state.selection as TextSelection;
-        state.doc.nodesBetween(from, to, (node) => node.marks ?.forEach(m => found.add(m)));
+        state.doc.nodesBetween(from, to, (node) => node.marks.forEach(m => found.add(m)));
         return found;
     }
 
@@ -768,6 +768,6 @@ class ButtonDropdown extends React.Component<ButtonDropdownProps> {
                 <button className="dropdown-button antimodeMenu-button" onPointerDown={this.onDropdownClick}><FontAwesomeIcon icon="caret-down" size="sm" /></button>
                 {this.showDropdown ? this.props.dropdownContent : <></>}
             </div>
-        )
+        );
     }
 }
\ No newline at end of file
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 8177593c2..1c15dca7f 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -18,8 +18,6 @@ import { SelectionManager } from './SelectionManager';
 import { PastelSchemaPalette, DarkPastelSchemaPalette } from '../../new_fields/SchemaHeaderField';
 const { toggleMark } = require("prosemirror-commands");
 
-// deprecated in favor of richtextmenu
-
 //appears above a selection of text in a RichTextBox to give user options such as Bold, Italics, etc.
 export class TooltipTextMenu {
     public static Toolbar: HTMLDivElement | undefined;
@@ -95,7 +93,7 @@ export class TooltipTextMenu {
             { mark: schema.marks.subscript, dom: svgIcon("subscript", "Subscript", "M496 448h-16V304a16 16 0 0 0-16-16h-48a16 16 0 0 0-14.29 8.83l-16 32A16 16 0 0 0 400 352h16v96h-16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h96a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM336 64h-67a16 16 0 0 0-13.14 6.87l-79.9 115-79.9-115A16 16 0 0 0 83 64H16A16 16 0 0 0 0 80v48a16 16 0 0 0 16 16h33.48l77.81 112-77.81 112H16a16 16 0 0 0-16 16v48a16 16 0 0 0 16 16h67a16 16 0 0 0 13.14-6.87l79.9-115 79.9 115A16 16 0 0 0 269 448h67a16 16 0 0 0 16-16v-48a16 16 0 0 0-16-16h-33.48l-77.81-112 77.81-112H336a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16z") },
         ];
 
-        basicItems.map(({ dom, mark }) => this.basicTools?.appendChild(dom.cloneNode(true)));
+        basicItems.map(({ dom, mark }) => this.basicTools ?.appendChild(dom.cloneNode(true)));
         basicItems.concat(items).forEach(({ dom, mark }) => {
             this.tooltip.appendChild(dom);
             this._marksToDoms.set(mark, dom);
@@ -476,7 +474,7 @@ export class TooltipTextMenu {
                 const node = self.view.state.selection.$from.nodeAfter;
                 const link = node && node.marks.find(m => m.type === self.view.state.schema.marks.link);
                 const href = link!.attrs.href;
-                if (href?.indexOf(Utils.prepend("/doc/")) === 0) {
+                if (href ?.indexOf(Utils.prepend("/doc/")) === 0) {
                     const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
                     linkclicked && DocServer.GetRefField(linkclicked).then(async linkDoc => {
                         if (linkDoc instanceof Doc) {
@@ -502,7 +500,7 @@ export class TooltipTextMenu {
         const link = this.view.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + linkDocId), title: title, location: location, targetId: targetDocId });
         this.view.dispatch(this.view.state.tr.removeMark(this.view.state.selection.from, this.view.state.selection.to, this.view.state.schema.marks.link).
             addMark(this.view.state.selection.from, this.view.state.selection.to, link));
-        return this.view.state.selection.$from.nodeAfter?.text || "";
+        return this.view.state.selection.$from.nodeAfter ?.text || "";
     }
 
     // SUMMARIZER TOOL
@@ -512,7 +510,7 @@ export class TooltipTextMenu {
             const tr = state.tr.addMark(state.selection.from, state.selection.to, mark);
             const content = tr.selection.content();
             const newNode = state.schema.nodes.summary.create({ visibility: false, text: content, textslice: content.toJSON() });
-            dispatch?.(tr.replaceSelectionWith(newNode).removeMark(tr.selection.from - 1, tr.selection.from, mark));
+            dispatch ?.(tr.replaceSelectionWith(newNode).removeMark(tr.selection.from - 1, tr.selection.from, mark));
         }
     }
 
@@ -739,7 +737,7 @@ export class TooltipTextMenu {
             // get marks in the selection
             const selected_marks = new Set<Mark>();
             const { from, to } = state.selection as TextSelection;
-            state.doc.nodesBetween(from, to, (node) => node.marks?.forEach(m => selected_marks.add(m)));
+            state.doc.nodesBetween(from, to, (node) => node.marks ?.forEach(m => selected_marks.add(m)));
 
             if (this._brushdom && selected_marks.size >= 0) {
                 TooltipTextMenuManager.Instance._brushMarks = selected_marks;
@@ -851,7 +849,7 @@ export class TooltipTextMenu {
     static setMark = (mark: Mark, state: EditorState<any>, dispatch: any) => {
         if (mark) {
             const node = (state.selection as NodeSelection).node;
-            if (node?.type === schema.nodes.ordered_list) {
+            if (node ?.type === schema.nodes.ordered_list) {
                 let attrs = node.attrs;
                 if (mark.type === schema.marks.pFontFamily) attrs = { ...attrs, setFontFamily: mark.attrs.family };
                 if (mark.type === schema.marks.pFontSize) attrs = { ...attrs, setFontSize: mark.attrs.fontSize };
diff --git a/src/client/views/AntimodeMenu.tsx b/src/client/views/AntimodeMenu.tsx
index e358d88cf..4625eb92f 100644
--- a/src/client/views/AntimodeMenu.tsx
+++ b/src/client/views/AntimodeMenu.tsx
@@ -127,7 +127,7 @@ export default abstract class AntimodeMenu extends React.Component {
     }
 
     protected getDragger = () => {
-        return <div className="antimodeMenu-dragger" onPointerDown={this.dragStart} style={{ width: this.Pinned ? "20px" : "0px" }} />
+        return <div className="antimodeMenu-dragger" onPointerDown={this.dragStart} style={{ width: this.Pinned ? "20px" : "0px" }} />;
     }
 
     protected getElement(buttons: JSX.Element[]) {
-- 
cgit v1.2.3-70-g09d2

