From 7a5c3d931381cc698d01b710a2576117449cf302 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 10 Dec 2019 22:39:43 -0500
Subject: cleaned up userMarks a bit (got rid of 'opened' attr).  added @
 markup to create collection from text selection

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

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

diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 70fa4974d..9318142e2 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -184,7 +184,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
                     this.linkOnDeselect.set(key, value);
 
                     const id = Utils.GenerateDeterministicGuid(this.dataDoc[Id] + key);
-                    const link = this._editorView.state.schema.marks.link.create({ href: `http://localhost:1050/doc/${id}`, location: "onRight", title: value });
+                    const link = this._editorView.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + id), location: "onRight", title: value });
                     const mval = this._editorView.state.schema.marks.metadataVal.create();
                     const offset = (tx.selection.to === range!.end - 1 ? -1 : 0);
                     tx = tx.addMark(textEndSelection - value.length + offset, textEndSelection, link).addMark(textEndSelection - value.length + offset, textEndSelection, mval);
@@ -238,9 +238,9 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
             this._editorView.dispatch(this._editorView.state.tr.removeMark(0, end, mark).removeMark(0, end, activeMark));
         }
     }
-    setAnnotation = (start: number, end: number, mark: Mark, opened: boolean, keep: boolean = false) => {
+    adoptAnnotation = (start: number, end: number, mark: Mark) => {
         const view = this._editorView!;
-        const nmark = view.state.schema.marks.user_mark.create({ ...mark.attrs, userid: keep ? Doc.CurrentUserEmail : mark.attrs.userid, opened: opened });
+        const nmark = view.state.schema.marks.user_mark.create({ ...mark.attrs, userid: Doc.CurrentUserEmail });
         view.dispatch(view.state.tr.removeMark(start, end, nmark).addMark(start, end, nmark));
     }
     protected createDropTarget = (ele: HTMLDivElement) => {
@@ -873,6 +873,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
 
     static _downEvent: any;
     onPointerDown = (e: React.PointerEvent): void => {
+        this.doLinkOnDeselect();
         FormattedTextBox._downEvent = true;
         FormattedTextBoxComment.textBox = this;
         if (this.props.onClick && e.button === 0) {
@@ -1053,9 +1054,10 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
             this._undoTyping.end();
             this._undoTyping = undefined;
         }
-        this.doLinkOnDeselect(); 6
+        this.doLinkOnDeselect();
     }
 
+    _lastTimedMark: Mark | undefined = undefined;
     onKeyPress = (e: React.KeyboardEvent) => {
         if (e.altKey) {
             e.preventDefault();
@@ -1078,7 +1080,8 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
         if (e.key === "Tab" || e.key === "Enter") {
             e.preventDefault();
         }
-        const mark = 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));
 
         if (!this._undoTyping) {
-- 
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/views/nodes/FormattedTextBox.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 ac3fc59b4574719c22d95910bbbf0430be7de220 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 11 Dec 2019 20:28:11 -0500
Subject: got rid of search_match&search_fields.  Now have Doc.SearchQuery()
 and document.searchMatch

---
 src/client/views/collections/CollectionTreeView.tsx |  2 +-
 src/client/views/nodes/FormattedTextBox.tsx         | 15 ++++-----------
 src/client/views/pdf/PDFViewer.tsx                  | 10 +++++-----
 src/client/views/search/SearchBox.tsx               | 10 +++++++---
 src/client/views/search/SearchItem.tsx              | 15 +++++++--------
 src/new_fields/Doc.ts                               |  3 +++
 6 files changed, 27 insertions(+), 28 deletions(-)

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

diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index a0ddc8884..ec1e7409f 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -401,7 +401,7 @@ class TreeView extends React.Component<TreeViewProps> {
                 style={{
                     color: this.props.document.isMinimized ? "red" : "black",
                     background: Doc.IsHighlighted(this.props.document) ? "orange" : Doc.IsBrushed(this.props.document) ? "#06121212" : "0",
-                    fontWeight: this.props.document.search_string ? "bold" : undefined,
+                    fontWeight: this.props.document.searchMatch ? "bold" : undefined,
                     outline: BoolCast(this.props.document.workspaceBrush) ? "dashed 1px #06123232" : undefined,
                     pointerEvents: this.props.active() || SelectionManager.GetIsDragging() ? "all" : "none"
                 }} >
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index ec9e0ce5b..7d404b28f 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -219,7 +219,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
         if (this._editorView && (this._editorView as any).docView) {
             const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight);
             const activeMark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight, { selected: true });
-            const res = terms.map(term => this.findInNode(this._editorView!, this._editorView!.state.doc, term));
+            const res = terms.filter(t => t).map(term => this.findInNode(this._editorView!, this._editorView!.state.doc, term));
             let tr = this._editorView.state.tr;
             const flattened: TextSelection[] = [];
             res.map(r => r.map(h => flattened.push(h)));
@@ -550,16 +550,9 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
 
         this.setupEditor(this.config, this.dataDoc, this.props.fieldKey);
 
-        this._searchReactionDisposer = reaction(() => {
-            return StrCast(this.layoutDoc.search_string);
-        }, searchString => {
-            if (searchString) {
-                this.highlightSearchTerms([searchString]);
-            }
-            else {
-                this.unhighlightSearchTerms();
-            }
-        }, { fireImmediately: true });
+        this._searchReactionDisposer = reaction(() => this.layoutDoc.searchMatch,
+            search => search ? this.highlightSearchTerms([Doc.SearchQuery()]) : this.unhighlightSearchTerms(),
+            { fireImmediately: true });
 
         this._rulesReactionDisposer = reaction(() => {
             const ruleProvider = this.props.ruleProvider;
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 3aa5d1d2c..b114cd7ed 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -39,7 +39,7 @@ export const pageSchema = createSchema({
     rotation: "number",
     scrollY: "number",
     scrollHeight: "number",
-    search_string: "string"
+    serachMatch: "boolean"
 });
 
 pdfjsLib.GlobalWorkerOptions.workerSrc = `/assets/pdf.worker.js`;
@@ -129,10 +129,10 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
         this._coverPath = JSON.parse(await rp.get(path));
         runInAction(() => this._showWaiting = this._showCover = true);
         this.props.startupLive && this.setupPdfJsViewer();
-        this._searchReactionDisposer = reaction(() => this.Document.search_string, searchString => {
-            if (searchString) {
-                this.search(searchString, true);
-                this._lastSearch = searchString;
+        this._searchReactionDisposer = reaction(() => this.Document.searchMatch, search => {
+            if (search) {
+                this.search(Doc.SearchQuery(), true);
+                this._lastSearch = Doc.SearchQuery();
             }
             else {
                 setTimeout(() => this._lastSearch === "mxytzlaf" && this.search("mxytzlaf", true), 200); // bcz: how do we clear search highlights?
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index c45fbe210..fe21c29c3 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -85,7 +85,11 @@ export class SearchBox extends React.Component {
         this._maxSearchIndex = 0;
     }
 
-    enter = (e: React.KeyboardEvent) => { if (e.key === "Enter") { this.submitSearch(); } };
+    enter = (e: React.KeyboardEvent) => {
+        if (e.key === "Enter") {
+            this.submitSearch();
+        }
+    }
 
     public static async convertDataUri(imageUri: string, returnedFilename: string) {
         try {
@@ -307,7 +311,7 @@ export class SearchBox extends React.Component {
                         this.getResults(this._searchString);
                         if (i < this._results.length) result = this._results[i];
                         if (result) {
-                            const highlights = Array.from([...Array.from(new Set(result[1]).values())]).filter(v => v !== "search_string");
+                            const highlights = Array.from([...Array.from(new Set(result[1]).values())]);
                             this._visibleElements[i] = <SearchItem doc={result[0]} query={this._searchString} key={result[0][Id]} lines={result[2]} highlighting={highlights} />;
                             this._isSearch[i] = "search";
                         }
@@ -315,7 +319,7 @@ export class SearchBox extends React.Component {
                     else {
                         result = this._results[i];
                         if (result) {
-                            const highlights = Array.from([...Array.from(new Set(result[1]).values())]).filter(v => v !== "search_string");
+                            const highlights = Array.from([...Array.from(new Set(result[1]).values())]);
                             this._visibleElements[i] = <SearchItem doc={result[0]} query={this._searchString} key={result[0][Id]} lines={result[2]} highlighting={highlights} />;
                             this._isSearch[i] = "search";
                         }
diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx
index 1007102f6..cdb6dcf7c 100644
--- a/src/client/views/search/SearchItem.tsx
+++ b/src/client/views/search/SearchItem.tsx
@@ -135,12 +135,11 @@ export class SearchItem extends React.Component<SearchItemProps> {
     @observable _displayDim = 50;
 
     componentDidMount() {
-        this.props.doc.search_string = this.props.query;
-        this.props.doc.search_fields = this.props.highlighting.join(", ");
+        Doc.SetSearchQuery(this.props.query);
+        this.props.doc.searchMatch = true;
     }
     componentWillUnmount() {
-        this.props.doc.search_string = undefined;
-        this.props.doc.search_fields = undefined;
+        this.props.doc.searchMatch = undefined;
     }
 
     //@computed
@@ -218,10 +217,10 @@ export class SearchItem extends React.Component<SearchItemProps> {
     pointerDown = (e: React.PointerEvent) => { e.preventDefault(); e.button === 0 && SearchBox.Instance.openSearch(e); }
 
     nextHighlight = (e: React.PointerEvent) => {
-        e.preventDefault(); e.button === 0 && SearchBox.Instance.openSearch(e);
-        const sstring = StrCast(this.props.doc.search_string);
-        this.props.doc.search_string = "";
-        setTimeout(() => this.props.doc.search_string = sstring, 0);
+        e.preventDefault();
+        e.button === 0 && SearchBox.Instance.openSearch(e);
+        this.props.doc.searchMatch = false;
+        setTimeout(() => this.props.doc.searchMatch = true, 0);
     }
     highlightDoc = (e: React.PointerEvent) => {
         if (this.props.doc.type === DocumentType.LINK) {
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 6f55775fe..17c1b7e16 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -644,6 +644,7 @@ export namespace Doc {
 
     export class DocData {
         @observable _user_doc: Doc = undefined!;
+        @observable _searchQuery: string = "";
     }
 
     // the document containing the view layout information - will be the Document itself unless the Document has
@@ -651,6 +652,8 @@ export namespace Doc {
     export function Layout(doc: Doc) { return Doc.LayoutField(doc) instanceof Doc ? doc[StrCast(doc.layoutKey, "layout")] as Doc : doc; }
     export function LayoutField(doc: Doc) { return doc[StrCast(doc.layoutKey, "layout")]; }
     const manager = new DocData();
+    export function SearchQuery(): string { return manager._searchQuery; }
+    export function SetSearchQuery(query: string) { runInAction(() => manager._searchQuery = query); }
     export function UserDoc(): Doc { return manager._user_doc; }
     export function SetUserDoc(doc: Doc) { manager._user_doc = doc; }
     export function IsBrushed(doc: Doc) {
-- 
cgit v1.2.3-70-g09d2


From 860c7907268874f1754dd86bc3a984066552db12 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 12 Dec 2019 15:02:22 -0500
Subject: major changes to drag and drop

---
 src/client/northstar/dash-nodes/HistogramBox.tsx   |  12 +-
 src/client/util/DocumentManager.ts                 |   4 +-
 src/client/util/DragManager.ts                     | 386 ++++++++-------------
 src/client/util/LinkManager.ts                     |   6 +-
 src/client/views/CollectionLinearView.tsx          |   2 +-
 src/client/views/DocComponent.tsx                  |   2 +-
 src/client/views/DocumentButtonBar.tsx             |  37 +-
 src/client/views/DocumentDecorations.tsx           |   2 +-
 src/client/views/MainView.tsx                      |   2 +-
 src/client/views/OverlayView.tsx                   |   2 -
 src/client/views/TemplateMenu.tsx                  |  11 +-
 .../views/collections/CollectionDockingView.tsx    |  20 +-
 .../collections/CollectionMasonryViewFieldRow.tsx  |  14 +-
 .../views/collections/CollectionSchemaCells.tsx    |  21 +-
 .../CollectionSchemaMovableTableHOC.tsx            |  33 +-
 .../views/collections/CollectionSchemaView.tsx     |   2 +-
 .../views/collections/CollectionStackingView.tsx   |  24 +-
 .../CollectionStackingViewFieldColumn.tsx          |   8 +-
 src/client/views/collections/CollectionSubView.tsx |  31 +-
 .../views/collections/CollectionTreeView.tsx       |  28 +-
 src/client/views/collections/CollectionView.tsx    |   4 +-
 .../views/collections/CollectionViewChromes.tsx    |   6 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  33 +-
 src/client/views/linking/LinkMenu.scss             |  85 -----
 src/client/views/linking/LinkMenuGroup.tsx         |  28 +-
 src/client/views/linking/LinkMenuItem.scss         |  87 +++++
 src/client/views/linking/LinkMenuItem.tsx          |  24 +-
 src/client/views/nodes/ButtonBox.tsx               |   9 +-
 .../views/nodes/ContentFittingDocumentView.tsx     |   7 +-
 src/client/views/nodes/DocuLinkBox.tsx             |   4 +-
 src/client/views/nodes/DocumentView.tsx            |  33 +-
 src/client/views/nodes/FieldView.tsx               |   2 +-
 src/client/views/nodes/FormattedTextBox.tsx        |  14 +-
 src/client/views/nodes/ImageBox.tsx                |  10 +-
 src/client/views/pdf/PDFViewer.tsx                 |  12 +-
 src/client/views/search/SearchBox.tsx              |   2 -
 src/client/views/search/SearchItem.tsx             |  10 +-
 37 files changed, 446 insertions(+), 571 deletions(-)
 create mode 100644 src/client/views/linking/LinkMenuItem.scss

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

diff --git a/src/client/northstar/dash-nodes/HistogramBox.tsx b/src/client/northstar/dash-nodes/HistogramBox.tsx
index 854135648..8fee53fb9 100644
--- a/src/client/northstar/dash-nodes/HistogramBox.tsx
+++ b/src/client/northstar/dash-nodes/HistogramBox.tsx
@@ -46,8 +46,8 @@ export class HistogramBox extends React.Component<FieldViewProps> {
 
     @action
     dropX = (e: Event, de: DragManager.DropEvent) => {
-        if (de.data instanceof DragManager.DocumentDragData) {
-            let h = Cast(de.data.draggedDocuments[0].data, HistogramField);
+        if (de.complete.docDragData) {
+            let h = Cast(de.complete.docDragData.draggedDocuments[0].data, HistogramField);
             if (h) {
                 this.HistoOp.X = h.HistoOp.X;
             }
@@ -57,8 +57,8 @@ export class HistogramBox extends React.Component<FieldViewProps> {
     }
     @action
     dropY = (e: Event, de: DragManager.DropEvent) => {
-        if (de.data instanceof DragManager.DocumentDragData) {
-            let h = Cast(de.data.draggedDocuments[0].data, HistogramField);
+        if (de.complete.docDragData) {
+            let h = Cast(de.complete.docDragData.draggedDocuments[0].data, HistogramField);
             if (h) {
                 this.HistoOp.Y = h.HistoOp.X;
             }
@@ -78,10 +78,10 @@ export class HistogramBox extends React.Component<FieldViewProps> {
 
     componentDidMount() {
         if (this._dropXRef.current) {
-            this._dropXDisposer = DragManager.MakeDropTarget(this._dropXRef.current, { handlers: { drop: this.dropX.bind(this) } });
+            this._dropXDisposer = DragManager.MakeDropTarget(this._dropXRef.current, this.dropX.bind(this));
         }
         if (this._dropYRef.current) {
-            this._dropYDisposer = DragManager.MakeDropTarget(this._dropYRef.current, { handlers: { drop: this.dropY.bind(this) } });
+            this._dropYDisposer = DragManager.MakeDropTarget(this._dropYRef.current, this.dropY.bind(this));
         }
         reaction(() => CurrentUserUtils.NorthstarDBCatalog, (catalog?: Catalog) => this.activateHistogramOperation(catalog), { fireImmediately: true });
         reaction(() => [this.VisualBinRanges && this.VisualBinRanges.slice()], () => this.SizeConverter.SetVisualBinRanges(this.VisualBinRanges));
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 07247b7e9..6f65e6c68 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -108,7 +108,7 @@ export class DocumentManager {
                 return init && rest;
             })
         ).reduce((pairs, dv) => {
-            const linksList = LinkManager.Instance.getAllRelatedLinks(dv.props.Document);
+            const linksList = DocListCast(dv.props.Document.links);
             pairs.push(...linksList.reduce((pairs, link) => {
                 const linkToDoc = link && LinkManager.Instance.getOppositeAnchor(link, dv.props.Document);
                 linkToDoc && DocumentManager.Instance.getDocumentViews(linkToDoc).map(docView1 => {
@@ -176,7 +176,7 @@ export class DocumentManager {
     }
 
     public async FollowLink(link: Doc | undefined, doc: Doc, focus: (doc: Doc, maxLocation: string) => void, zoom: boolean = false, reverse: boolean = false, currentContext?: Doc) {
-        const linkDocs = link ? [link] : LinkManager.Instance.getAllRelatedLinks(doc);
+        const linkDocs = link ? [link] : DocListCast(doc.links);
         SelectionManager.DeselectAll();
         const firstDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor1 as Doc, doc));
         const secondDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor2 as Doc, doc));
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 9e6224d97..95529311a 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -1,5 +1,4 @@
-import { action, runInAction } from "mobx";
-import { Doc, Field } from "../../new_fields/Doc";
+import { Doc, Field, DocListCast } from "../../new_fields/Doc";
 import { Cast, ScriptCast } from "../../new_fields/Types";
 import { emptyFunction } from "../../Utils";
 import { CollectionDockingView } from "../views/collections/CollectionDockingView";
@@ -22,7 +21,7 @@ export function SetupDrag(
     docFunc: () => Doc | Promise<Doc>,
     moveFunc?: DragManager.MoveFunction,
     dropAction?: dropActionType,
-    options?: any,
+    treeViewId?: string,
     dontHideOnDrop?: boolean,
     dragStarted?: () => void
 ) {
@@ -36,7 +35,7 @@ export function SetupDrag(
         const dragData = new DragManager.DocumentDragData([doc]);
         dragData.dropAction = dropAction;
         dragData.moveDocument = moveFunc;
-        dragData.options = options;
+        dragData.treeViewId = treeViewId;
         dragData.dontHideOnDrop = dontHideOnDrop;
         DragManager.StartDocumentDrag([_reference.current!], dragData, e.x, e.y);
         dragStarted && dragStarted();
@@ -65,62 +64,9 @@ export function SetupDrag(
     return onItemDown;
 }
 
-function moveLinkedDocument(doc: Doc, targetCollection: Doc, addDocument: (doc: Doc) => boolean): boolean {
-    const document = SelectionManager.SelectedDocuments()[0];
-    document && document.props.removeDocument && document.props.removeDocument(doc);
-    addDocument(doc);
-    return true;
-}
-
-export async function DragLinkAsDocument(dragEle: HTMLElement, x: number, y: number, linkDoc: Doc, sourceDoc: Doc) {
-    const draggeddoc = LinkManager.Instance.getOppositeAnchor(linkDoc, sourceDoc);
-    if (draggeddoc) {
-        const moddrag = await Cast(draggeddoc.annotationOn, Doc);
-        const dragdocs = moddrag ? [moddrag] : [draggeddoc];
-        const dragData = new DragManager.DocumentDragData(dragdocs);
-        dragData.moveDocument = moveLinkedDocument;
-        DragManager.StartLinkedDocumentDrag([dragEle], dragData, x, y, {
-            handlers: {
-                dragComplete: action(emptyFunction),
-            },
-            hideSource: false
-        });
-    }
-}
-
-export async function DragLinksAsDocuments(dragEle: HTMLElement, x: number, y: number, sourceDoc: Doc, singleLink?: Doc) {
-    const srcTarg = sourceDoc.proto;
-    let draggedDocs: Doc[] = [];
-
-    if (srcTarg) {
-        const linkDocs = singleLink ? [singleLink] : LinkManager.Instance.getAllRelatedLinks(srcTarg);
-        if (linkDocs) {
-            draggedDocs = linkDocs.map(link => {
-                const opp = LinkManager.Instance.getOppositeAnchor(link, sourceDoc);
-                if (opp) return opp;
-            }) as Doc[];
-        }
-    }
-    if (draggedDocs.length) {
-        const moddrag: Doc[] = [];
-        for (const draggedDoc of draggedDocs) {
-            const doc = await Cast(draggedDoc.annotationOn, Doc);
-            if (doc) moddrag.push(doc);
-        }
-        const dragdocs = moddrag.length ? moddrag : draggedDocs;
-        const dragData = new DragManager.DocumentDragData(dragdocs);
-        dragData.moveDocument = moveLinkedDocument;
-        DragManager.StartLinkedDocumentDrag([dragEle], dragData, x, y, {
-            handlers: {
-                dragComplete: action(emptyFunction),
-            },
-            hideSource: false
-        });
-    }
-}
-
-
 export namespace DragManager {
+    let dragDiv: HTMLDivElement;
+
     export function Root() {
         const root = document.getElementById("root");
         if (!root) {
@@ -128,79 +74,45 @@ export namespace DragManager {
         }
         return root;
     }
+    export let AbortDrag: () => void = emptyFunction;
+    export type MoveFunction = (document: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => boolean;
 
-    let dragDiv: HTMLDivElement;
-
-    export enum DragButtons {
-        Left = 1,
-        Right = 2,
-        Both = Left | Right
-    }
-
-    interface DragOptions {
-        handlers: DragHandlers;
-
-        hideSource: boolean | (() => boolean);
-
-        dragHasStarted?: () => void;
-
-        withoutShiftDrag?: boolean;
-
-        finishDrag?: (dropData: { [id: string]: any }) => void;
-
-        offsetX?: number;
-
+    export interface DragDropDisposer { (): void; }
+    export interface DragOptions {
+        dragComplete?: (e: DragCompleteEvent) => void; // function to invoke when drag has completed
+        hideSource?: boolean;  // hide source document during drag
+        offsetX?: number;      // offset of top left of source drag visual from cursor
         offsetY?: number;
     }
 
-    export interface DragDropDisposer {
-        (): void;
-    }
-
-    export class DragCompleteEvent { }
-
-    export interface DragHandlers {
-        dragComplete: (e: DragCompleteEvent) => void;
-    }
-
-    export interface DropOptions {
-        handlers: DropHandlers;
-    }
+    // event called when the drag operation results in a drop action
     export class DropEvent {
         constructor(
             readonly x: number,
             readonly y: number,
-            readonly data: { [id: string]: any },
-            readonly mods: string
+            readonly complete: DragCompleteEvent,
+            readonly altKey: boolean,
+            readonly metaKey: boolean,
+            readonly ctrlKey: boolean
         ) { }
     }
 
-    export interface DropHandlers {
-        drop: (e: Event, de: DropEvent) => void;
-    }
-
-    export function MakeDropTarget(
-        element: HTMLElement,
-        options: DropOptions
-    ): DragDropDisposer {
-        if ("canDrop" in element.dataset) {
-            throw new Error(
-                "Element is already droppable, can't make it droppable again"
-            );
+    // event called when the drag operation has completed (aborted or completed a drop) -- this will be after any drop event has been generated
+    export class DragCompleteEvent {
+        constructor(aborted: boolean, dragData: { [id: string]: any }) {
+            this.aborted = aborted;
+            this.docDragData = dragData instanceof DocumentDragData ? dragData as DocumentDragData : undefined;
+            this.annoDragData = dragData instanceof PdfAnnoDragData ? dragData as PdfAnnoDragData : undefined;
+            this.linkDragData = dragData instanceof LinkDragData ? dragData as LinkDragData : undefined;
+            this.columnDragData = dragData instanceof ColumnDragData ? dragData as ColumnDragData : undefined;
         }
-        element.dataset.canDrop = "true";
-        const handler = (e: Event) => {
-            const ce = e as CustomEvent<DropEvent>;
-            options.handlers.drop(e, ce.detail);
-        };
-        element.addEventListener("dashOnDrop", handler);
-        return () => {
-            element.removeEventListener("dashOnDrop", handler);
-            delete element.dataset.canDrop;
-        };
+        aborted: boolean;
+        docDragData?: DocumentDragData;
+        annoDragData?: PdfAnnoDragData;
+        linkDragData?: LinkDragData;
+        columnDragData?: ColumnDragData;
     }
 
-    export type MoveFunction = (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
     export class DocumentDragData {
         constructor(dragDoc: Doc[]) {
             this.draggedDocuments = dragDoc;
@@ -210,6 +122,8 @@ export namespace DragManager {
         draggedDocuments: Doc[];
         droppedDocuments: Doc[];
         dragDivName?: string;
+        treeViewId?: string;
+        dontHideOnDrop?: boolean;
         offset: number[];
         dropAction: dropActionType;
         userDropAction: dropActionType;
@@ -217,16 +131,34 @@ export namespace DragManager {
         moveDocument?: MoveFunction;
         isSelectionMove?: boolean; // indicates that an explicitly selected Document is being dragged.  this will suppress onDragStart scripts
         applyAsTemplate?: boolean;
+    }
+    export class LinkDragData {
+        constructor(linkSourceDoc: Doc) {
+            this.linkSourceDocument = linkSourceDoc;
+        }
+        droppedDocuments: Doc[] = [];
+        linkSourceDocument: Doc;
+        dontClearTextBox?: boolean;
+        linkDocument?: Doc;
         [id: string]: any;
     }
-
-    export class AnnotationDragData {
+    export class ColumnDragData {
+        constructor(colKey: SchemaHeaderField) {
+            this.colKey = colKey;
+        }
+        colKey: SchemaHeaderField;
+        [id: string]: any;
+    }
+    // used by PDFs to conditionally (if the drop completes) create a text annotation when dragging from the PDF toolbar when a text region has been selected.
+    // this is pretty clunky and should be rethought out using linkDrag or DocumentDrag
+    export class PdfAnnoDragData {
         constructor(dragDoc: Doc, annotationDoc: Doc, dropDoc: Doc) {
             this.dragDocument = dragDoc;
             this.dropDocument = dropDoc;
             this.annotationDocument = annotationDoc;
             this.offset = [0, 0];
         }
+        linkedToDoc?: boolean;
         targetContext: Doc | undefined;
         dragDocument: Doc;
         annotationDocument: Doc;
@@ -236,98 +168,103 @@ export namespace DragManager {
         userDropAction: dropActionType;
     }
 
-    export let StartDragFunctions: (() => void)[] = [];
+    export function MakeDropTarget(
+        element: HTMLElement,
+        dropFunc: (e: Event, de: DropEvent) => void
+    ): DragDropDisposer {
+        if ("canDrop" in element.dataset) {
+            throw new Error(
+                "Element is already droppable, can't make it droppable again"
+            );
+        }
+        element.dataset.canDrop = "true";
+        const handler = (e: Event) => dropFunc(e, (e as CustomEvent<DropEvent>).detail);
+        element.addEventListener("dashOnDrop", handler);
+        return () => {
+            element.removeEventListener("dashOnDrop", handler);
+            delete element.dataset.canDrop;
+        };
+    }
 
+    // drag a document and drop it (or make an alias/copy on drop)
     export function StartDocumentDrag(eles: HTMLElement[], dragData: DocumentDragData, downX: number, downY: number, options?: DragOptions) {
-        runInAction(() => StartDragFunctions.map(func => func()));
+        const finishDrag = (e: DragCompleteEvent) => {
+            e.docDragData && (e.docDragData.droppedDocuments =
+                dragData.draggedDocuments.map(d => !dragData.isSelectionMove && !dragData.userDropAction && ScriptCast(d.onDragStart) ? ScriptCast(d.onDragStart).script.run({ this: d }).result :
+                    dragData.userDropAction === "alias" || (!dragData.userDropAction && dragData.dropAction === "alias") ? Doc.MakeAlias(d) :
+                        dragData.userDropAction === "copy" || (!dragData.userDropAction && dragData.dropAction === "copy") ? Doc.MakeCopy(d, true) : d)
+            );
+            e.docDragData?.droppedDocuments.forEach((drop: Doc, i: number) =>
+                Cast(dragData.draggedDocuments[i].removeDropProperties, listSpec("string"), []).map(prop => drop[prop] = undefined));
+        };
         dragData.draggedDocuments.map(d => d.dragFactory); // does this help?  trying to make sure the dragFactory Doc is loaded
-        StartDrag(eles, dragData, downX, downY, options, options && options.finishDrag ? options.finishDrag :
-            (dropData: { [id: string]: any }) => {
-                (dropData.droppedDocuments =
-                    dragData.draggedDocuments.map(d => !dragData.isSelectionMove && !dragData.userDropAction && ScriptCast(d.onDragStart) ? ScriptCast(d.onDragStart).script.run({ this: d }).result :
-                        dragData.userDropAction === "alias" || (!dragData.userDropAction && dragData.dropAction === "alias") ? Doc.MakeAlias(d) :
-                            dragData.userDropAction === "copy" || (!dragData.userDropAction && dragData.dropAction === "copy") ? Doc.MakeCopy(d, true) : d)
-                );
-                dropData.droppedDocuments.forEach((drop: Doc, i: number) =>
-                    Cast(dragData.draggedDocuments[i].removeDropProperties, listSpec("string"), []).map(prop => drop[prop] = undefined));
-            });
+        StartDrag(eles, dragData, downX, downY, options, finishDrag);
     }
 
+    // drag a button template and drop a new button 
     export function StartButtonDrag(eles: HTMLElement[], script: string, title: string, vars: { [name: string]: Field }, params: string[], initialize: (button: Doc) => void, downX: number, downY: number, options?: DragOptions) {
-        const dragData = new DragManager.DocumentDragData([]);
-        runInAction(() => StartDragFunctions.map(func => func()));
-        StartDrag(eles, dragData, downX, downY, options, options && options.finishDrag ? options.finishDrag :
-            (dropData: { [id: string]: any }) => {
-                const bd = Docs.Create.ButtonDocument({ width: 150, height: 50, title: title });
-                bd.onClick = ScriptField.MakeScript(script);
-                params.map(p => Object.keys(vars).indexOf(p) !== -1 && (Doc.GetProto(bd)[p] = new PrefetchProxy(vars[p] as Doc)));
-                initialize && initialize(bd);
-                bd.buttonParams = new List<string>(params);
-                dropData.droppedDocuments = [bd];
-            });
+        const finishDrag = (e: DragCompleteEvent) => {
+            const bd = Docs.Create.ButtonDocument({ width: 150, height: 50, title: title });
+            bd.onClick = ScriptField.MakeScript(script);
+            params.map(p => Object.keys(vars).indexOf(p) !== -1 && (Doc.GetProto(bd)[p] = new PrefetchProxy(vars[p] as Doc)));
+            initialize && initialize(bd);
+            bd.buttonParams = new List<string>(params);
+            e.docDragData && (e.docDragData.droppedDocuments = [bd]);
+        };
+        StartDrag(eles, new DragManager.DocumentDragData([]), downX, downY, options, finishDrag);
     }
 
-    export function StartLinkedDocumentDrag(eles: HTMLElement[], dragData: DocumentDragData, downX: number, downY: number, options?: DragOptions) {
-        dragData.moveDocument = moveLinkedDocument;
+    // drag links and drop link targets (aliasing them if needed)
+    export async function StartLinkTargetsDrag(dragEle: HTMLElement, downX: number, downY: number, sourceDoc: Doc, specificLinks?: Doc[]) {
+        let draggedDocs = (specificLinks ? specificLinks : DocListCast(sourceDoc.links)).map(link => LinkManager.Instance.getOppositeAnchor(link, sourceDoc)).filter(l => l) as Doc[];
 
-        runInAction(() => StartDragFunctions.map(func => func()));
-        StartDrag(eles, dragData, downX, downY, options,
-            (dropData: { [id: string]: any }) => {
-                const droppedDocuments: Doc[] = dragData.draggedDocuments.reduce((droppedDocs: Doc[], d) => {
-                    const dvs = DocumentManager.Instance.getDocumentViews(d);
-                    if (dvs.length) {
-                        const containingView = SelectionManager.SelectedDocuments()[0] ? SelectionManager.SelectedDocuments()[0].props.ContainingCollectionView : undefined;
-                        const inContext = dvs.filter(dv => dv.props.ContainingCollectionView === containingView);
-                        if (inContext.length) {
-                            inContext.forEach(dv => droppedDocs.push(dv.props.Document));
+        if (draggedDocs.length) {
+            const moddrag: Doc[] = [];
+            for (const draggedDoc of draggedDocs) {
+                const doc = await Cast(draggedDoc.annotationOn, Doc);
+                if (doc) moddrag.push(doc);
+            }
+
+            let dragData = new DragManager.DocumentDragData(moddrag.length ? moddrag : draggedDocs);
+            dragData.moveDocument = (doc: Doc, targetCollection: Doc | undefined, addDocument: (doc: Doc) => boolean): boolean => {
+                const document = SelectionManager.SelectedDocuments()[0];
+                document && document.props.removeDocument && document.props.removeDocument(doc);
+                addDocument(doc);
+                return true;
+            };
+            const containingView = SelectionManager.SelectedDocuments()[0] ? SelectionManager.SelectedDocuments()[0].props.ContainingCollectionView : undefined;
+            const finishDrag = (e: DragCompleteEvent) =>
+                e.docDragData && (e.docDragData.droppedDocuments =
+                    dragData.draggedDocuments.reduce((droppedDocs, d) => {
+                        const dvs = DocumentManager.Instance.getDocumentViews(d).filter(dv => dv.props.ContainingCollectionView === containingView);
+                        if (dvs.length) {
+                            dvs.forEach(dv => droppedDocs.push(dv.props.Document));
                         } else {
                             droppedDocs.push(Doc.MakeAlias(d));
                         }
-                    } else {
-                        droppedDocs.push(Doc.MakeAlias(d));
-                    }
-                    return droppedDocs;
-                }, []);
-                dropData.droppedDocuments = droppedDocuments;
-            });
-    }
-
-    export function StartAnnotationDrag(eles: HTMLElement[], dragData: AnnotationDragData, downX: number, downY: number, options?: DragOptions) {
-        StartDrag(eles, dragData, downX, downY, options);
-    }
+                        return droppedDocs;
+                    }, [] as Doc[]));
 
-    export class LinkDragData {
-        constructor(linkSourceDoc: Doc, blacklist: Doc[] = []) {
-            this.linkSourceDocument = linkSourceDoc;
-            this.blacklist = blacklist;
+            StartDrag([dragEle], dragData, downX, downY, undefined, finishDrag);
         }
-        droppedDocuments: Doc[] = [];
-        linkSourceDocument: Doc;
-        blacklist: Doc[];
-        dontClearTextBox?: boolean;
-        [id: string]: any;
     }
 
-    // for column dragging in schema view
-    export class ColumnDragData {
-        constructor(colKey: SchemaHeaderField) {
-            this.colKey = colKey;
-        }
-        colKey: SchemaHeaderField;
-        [id: string]: any;
+    // drag&drop the pdf annotation anchor which will create a text note  on drop via a dropCompleted() DragOption 
+    export function StartPdfAnnoDrag(eles: HTMLElement[], dragData: PdfAnnoDragData, downX: number, downY: number, options?: DragOptions) {
+        StartDrag(eles, dragData, downX, downY, options);
     }
 
-    export function StartLinkDrag(ele: HTMLElement, dragData: LinkDragData, downX: number, downY: number, options?: DragOptions) {
-        StartDrag([ele], dragData, downX, downY, options);
+    // drags a linker button and creates a link on drop
+    export function StartLinkDrag(ele: HTMLElement, sourceDoc: Doc, downX: number, downY: number, options?: DragOptions) {
+        StartDrag([ele], new DragManager.LinkDragData(sourceDoc), downX, downY, options);
     }
 
+    // drags a column from a schema view
     export function StartColumnDrag(ele: HTMLElement, dragData: ColumnDragData, downX: number, downY: number, options?: DragOptions) {
         StartDrag([ele], dragData, downX, downY, options);
     }
 
-    export let AbortDrag: () => void = emptyFunction;
-
-    function StartDrag(eles: HTMLElement[], dragData: { [id: string]: any }, downX: number, downY: number, options?: DragOptions, finishDrag?: (dropData: { [id: string]: any }) => void) {
+    function StartDrag(eles: HTMLElement[], dragData: { [id: string]: any }, downX: number, downY: number, options?: DragOptions, finishDrag?: (dropData: DragCompleteEvent) => void) {
         eles = eles.filter(e => e);
         if (!dragDiv) {
             dragDiv = document.createElement("div");
@@ -341,33 +278,29 @@ export namespace DragManager {
         const xs: number[] = [];
         const ys: number[] = [];
 
-        const docs = dragData instanceof DocumentDragData ? dragData.draggedDocuments :
-            dragData instanceof AnnotationDragData ? [dragData.dragDocument] : [];
+        const docs = dragData instanceof DocumentDragData ? dragData.draggedDocuments : dragData instanceof PdfAnnoDragData ? [dragData.dragDocument] : [];
         const dragElements = eles.map(ele => {
-            const w = ele.offsetWidth,
-                h = ele.offsetHeight;
+            if (!ele.parentNode) dragDiv.appendChild(ele);
+            const dragElement = ele.parentNode === dragDiv ? ele : ele.cloneNode(true) as HTMLElement;
             const rect = ele.getBoundingClientRect();
-            const scaleX = rect.width / w,
-                scaleY = rect.height / h;
-            const x = rect.left,
-                y = rect.top;
-            xs.push(x);
-            ys.push(y);
+            const scaleX = rect.width / ele.offsetWidth,
+                scaleY = rect.height / ele.offsetHeight;
+            xs.push(rect.left);
+            ys.push(rect.top);
             scaleXs.push(scaleX);
             scaleYs.push(scaleY);
-            const dragElement = ele.cloneNode(true) as HTMLElement;
             dragElement.style.opacity = "0.7";
-            dragElement.style.borderRadius = getComputedStyle(ele).borderRadius;
             dragElement.style.position = "absolute";
             dragElement.style.margin = "0";
             dragElement.style.top = "0";
             dragElement.style.bottom = "";
             dragElement.style.left = "0";
-            dragElement.style.transition = "none";
             dragElement.style.color = "black";
+            dragElement.style.transition = "none";
             dragElement.style.transformOrigin = "0 0";
+            dragElement.style.borderRadius = getComputedStyle(ele).borderRadius;
             dragElement.style.zIndex = globalCssVariables.contextMenuZindex;// "1000";
-            dragElement.style.transform = `translate(${x + (options?.offsetX || 0)}px, ${y + (options?.offsetY || 0)}px) scale(${scaleX}, ${scaleY})`;
+            dragElement.style.transform = `translate(${rect.left + (options?.offsetX || 0)}px, ${rect.top + (options?.offsetY || 0)}px) scale(${scaleX}, ${scaleY})`;
             dragElement.style.width = `${rect.width / scaleX}px`;
             dragElement.style.height = `${rect.height / scaleY}px`;
 
@@ -385,30 +318,18 @@ export namespace DragManager {
                     Array.from(pdfView).map((v, i) => v.scrollTo({ top: tops[i] }));
                 }, 0);
             }
-            const set = dragElement.getElementsByTagName('*');
             if (dragElement.hasAttribute("style")) (dragElement as any).style.pointerEvents = "none";
+            const set = dragElement.getElementsByTagName('*');
             // tslint:disable-next-line: prefer-for-of
             for (let i = 0; i < set.length; i++) {
-                if (set[i].hasAttribute("style")) {
-                    const s = set[i];
-                    (s as any).style.pointerEvents = "none";
-                }
+                set[i].hasAttribute("style") && ((set[i] as any).style.pointerEvents = "none");
             }
 
-
             dragDiv.appendChild(dragElement);
             return dragElement;
         });
 
-        let hideSource = false;
-        if (options) {
-            if (typeof options.hideSource === "boolean") {
-                hideSource = options.hideSource;
-            } else {
-                hideSource = options.hideSource();
-            }
-        }
-
+        const hideSource = options?.hideSource ? true : false;
         eles.map(ele => ele.parentElement && ele.parentElement?.className === dragData.dragDivName ? (ele.parentElement.hidden = hideSource) : (ele.hidden = hideSource));
 
         let lastX = downX;
@@ -418,9 +339,9 @@ export namespace DragManager {
             if (dragData instanceof DocumentDragData) {
                 dragData.userDropAction = e.ctrlKey ? "alias" : undefined;
             }
-            if (((options && !options.withoutShiftDrag) || !options) && e.shiftKey && CollectionDockingView.Instance) {
+            if (e.shiftKey && CollectionDockingView.Instance) {
                 AbortDrag();
-                finishDrag && finishDrag(dragData);
+                finishDrag?.(new DragCompleteEvent(true, dragData));
                 CollectionDockingView.Instance.StartOtherDrag({
                     pageX: e.pageX,
                     pageY: e.pageY,
@@ -445,45 +366,40 @@ export namespace DragManager {
         const endDrag = () => {
             document.removeEventListener("pointermove", moveHandler, true);
             document.removeEventListener("pointerup", upHandler);
-            if (options) {
-                options.handlers.dragComplete({});
-            }
         };
 
         AbortDrag = () => {
             hideDragShowOriginalElements();
             SelectionManager.SetIsDragging(false);
+            options?.dragComplete?.(new DragCompleteEvent(true, dragData));
             endDrag();
         };
         const upHandler = (e: PointerEvent) => {
             hideDragShowOriginalElements();
             dispatchDrag(eles, e, dragData, options, finishDrag);
             SelectionManager.SetIsDragging(false);
+            options?.dragComplete?.(new DragCompleteEvent(false, dragData));
             endDrag();
         };
         document.addEventListener("pointermove", moveHandler, true);
         document.addEventListener("pointerup", upHandler);
     }
 
-    function dispatchDrag(dragEles: HTMLElement[], e: PointerEvent, dragData: { [index: string]: any }, options?: DragOptions, finishDrag?: (dragData: { [index: string]: any }) => void) {
+    function dispatchDrag(dragEles: HTMLElement[], e: PointerEvent, dragData: { [index: string]: any }, options?: DragOptions, finishDrag?: (e: DragCompleteEvent) => void) {
         const removed = dragData.dontHideOnDrop ? [] : dragEles.map(dragEle => {
-            // let parent = dragEle.parentElement;
-            // if (parent) parent.removeChild(dragEle);
-            const ret = [dragEle, dragEle.style.width, dragEle.style.height];
+            const ret = { ele: dragEle, w: dragEle.style.width, h: dragEle.style.height };
             dragEle.style.width = "0";
             dragEle.style.height = "0";
             return ret;
         });
         const target = document.elementFromPoint(e.x, e.y);
         removed.map(r => {
-            const dragEle = r[0] as HTMLElement;
-            dragEle.style.width = r[1] as string;
-            dragEle.style.height = r[2] as string;
-            // let parent = r[1];
-            // if (parent && dragEle) parent.appendChild(dragEle);
+            r.ele.style.width = r.w;
+            r.ele.style.height = r.h;
         });
         if (target) {
-            finishDrag && finishDrag(dragData);
+            const complete = new DragCompleteEvent(false, dragData);
+            finishDrag?.(complete);
 
             target.dispatchEvent(
                 new CustomEvent<DropEvent>("dashOnDrop", {
@@ -491,8 +407,10 @@ export namespace DragManager {
                     detail: {
                         x: e.x,
                         y: e.y,
-                        data: dragData,
-                        mods: e.altKey ? "AltKey" : e.ctrlKey ? "CtrlKey" : e.metaKey ? "MetaKey" : ""
+                        complete: complete,
+                        altKey: e.altKey,
+                        metaKey: e.metaKey,
+                        ctrlKey: e.ctrlKey
                     }
                 })
             );
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index fb6f27478..5f3667acc 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -70,7 +70,7 @@ export class LinkManager {
     }
 
     // finds all links that contain the given anchor
-    public getAllRelatedLinks(anchor: Doc): Doc[] {//List<Doc> {
+    public getAllRelatedLinks(anchor: Doc): Doc[] {
         const related = LinkManager.Instance.getAllLinks().filter(link => {
             const protomatch1 = Doc.AreProtosEqual(anchor, Cast(link.anchor1, Doc, null));
             const protomatch2 = Doc.AreProtosEqual(anchor, Cast(link.anchor2, Doc, null));
@@ -244,7 +244,5 @@ export class LinkManager {
         if (Doc.AreProtosEqual(anchor, linkDoc)) return linkDoc;
     }
 }
-Scripting.addGlobal(function links(doc: any) {
-    return new List(LinkManager.Instance.getAllRelatedLinks(doc));
-});
 
+Scripting.addGlobal(function links(doc: any) { return new List(LinkManager.Instance.getAllRelatedLinks(doc)); });
\ No newline at end of file
diff --git a/src/client/views/CollectionLinearView.tsx b/src/client/views/CollectionLinearView.tsx
index 293a4feea..5ca861f71 100644
--- a/src/client/views/CollectionLinearView.tsx
+++ b/src/client/views/CollectionLinearView.tsx
@@ -39,7 +39,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
     protected createDropTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
         this._dropDisposer && this._dropDisposer();
         if (ele) {
-            this._dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } });
+            this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
         }
     }
 
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 27ee9f122..c7ddee7ea 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -71,7 +71,7 @@ export function DocAnnotatableComponent<P extends DocAnnotatableProps, T>(schema
         // if the moved document is already in this overlay collection nothing needs to be done.
         // otherwise, if the document can be removed from where it was, it will then be added to this document's overlay collection. 
         @action.bound
-        moveDocument(doc: Doc, targetCollection: Doc, addDocument: (doc: Doc) => boolean): boolean {
+        moveDocument(doc: Doc, targetCollection: Doc | undefined, addDocument: (doc: Doc) => boolean): boolean {
             return Doc.AreProtosEqual(this.props.Document, targetCollection) ? true : this.removeDocument(doc) ? addDocument(doc) : false;
         }
         @action.bound
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 7f125dd34..f014c1329 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -3,13 +3,12 @@ import { faArrowAltCircleDown, faArrowAltCircleUp, faCheckCircle, faCloudUploadA
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { action, observable, runInAction, computed } from "mobx";
 import { observer } from "mobx-react";
-import { Doc } from "../../new_fields/Doc";
+import { Doc, DocListCast } from "../../new_fields/Doc";
 import { RichTextField } from '../../new_fields/RichTextField';
-import { NumCast, StrCast } from "../../new_fields/Types";
+import { NumCast, StrCast, Cast } from "../../new_fields/Types";
 import { emptyFunction } from "../../Utils";
 import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils';
 import { DragManager } from "../util/DragManager";
-import { LinkManager } from '../util/LinkManager';
 import { UndoManager } from "../util/UndoManager";
 import './DocumentButtonBar.scss';
 import './collections/ParentDocumentSelector.scss';
@@ -107,27 +106,21 @@ export class DocumentButtonBar extends React.Component<{ views: DocumentView[],
         if (this._linkButton.current !== null && (Math.abs(e.clientX - this._downX) > 3 || Math.abs(e.clientY - this._downY) > 3)) {
             document.removeEventListener("pointermove", this.onLinkButtonMoved);
             document.removeEventListener("pointerup", this.onLinkButtonUp);
-            const docView = this.props.views[0];
-            const container = docView.props.ContainingCollectionDoc?.proto;
-            const dragData = new DragManager.LinkDragData(docView.props.Document, container ? [container] : []);
             const linkDrag = UndoManager.StartBatch("Drag Link");
-            DragManager.StartLinkDrag(this._linkButton.current, dragData, e.pageX, e.pageY, {
-                handlers: {
-                    dragComplete: () => {
-                        const tooltipmenu = FormattedTextBox.ToolTipTextMenu;
-                        const linkDoc = dragData.linkDocument;
-                        if (linkDoc && tooltipmenu) {
-                            const proto = Doc.GetProto(linkDoc);
-                            if (proto && docView) {
-                                proto.sourceContext = docView.props.ContainingCollectionDoc;
-                            }
-                            const text = tooltipmenu.makeLink(linkDoc, StrCast(linkDoc.anchor2.title), e.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
-                            }
+            DragManager.StartLinkDrag(this._linkButton.current, this.props.views[0].props.Document, e.pageX, e.pageY, {
+                dragComplete: dropEv => {
+                    const linkDoc = dropEv.linkDragData?.linkDocument; // equivalent to !dropEve.aborted since linkDocument is only assigned on a completed drop
+                    if (linkDoc && FormattedTextBox.ToolTipTextMenu) {
+                        const proto = Doc.GetProto(linkDoc);
+                        proto.sourceContext = this.props.views[0].props.ContainingCollectionDoc;
+
+                        const anchor2Title = linkDoc.anchor2 instanceof Doc ? StrCast((linkDoc.anchor2 as Doc).title) : "-untitled-";
+                        const text = FormattedTextBox.ToolTipTextMenu.makeLink(linkDoc, anchor2Title, e.ctrlKey ? "onRight" : "inTab");
+                        if (linkDoc.anchor2 instanceof Doc) {
+                            proto.title = text === "" ? proto.title : text + " to " + linkDoc.anchor2.title; // TODO open to more descriptive descriptions of following in text link
                         }
-                        linkDrag && linkDrag.end();
                     }
+                    linkDrag?.end();
                 },
                 hideSource: false
             });
@@ -200,7 +193,7 @@ export class DocumentButtonBar extends React.Component<{ views: DocumentView[],
 
     @computed
     get linkButton() {
-        const linkCount = LinkManager.Instance.getAllRelatedLinks(this.props.views[0].props.Document).length;
+        const linkCount = DocListCast(this.props.views[0].props.Document.links).length;
         return <div title="Drag(create link) Tap(view links)" className="documentButtonBar-linkFlyout" ref={this._linkButton}>
             <Flyout anchorPoint={anchorPoints.RIGHT_TOP}
                 content={<LinkMenu docView={this.props.views[0]} addDocTab={this.props.views[0].props.addDocTab} changeFlyout={emptyFunction} />}>
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index f366a3677..fdaca87a9 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -199,7 +199,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         document.removeEventListener("pointermove", this.onTitleMove);
         document.removeEventListener("pointerup", this.onTitleUp);
         DragManager.StartDocumentDrag(SelectionManager.SelectedDocuments().map(documentView => documentView.ContentDiv!), dragData, e.x, e.y, {
-            handlers: { dragComplete: action(() => this._hidden = this.Interacting = false) },
+            dragComplete: action(e => this._hidden = this.Interacting = false),
             hideSource: true
         });
         e.stopPropagation();
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 25b7dc5ec..db2a3c298 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -460,7 +460,7 @@ export class MainView extends React.Component {
 
     addButtonDoc = (doc: Doc) => Doc.AddDocToList(CurrentUserUtils.UserDocument.expandingButtons as Doc, "data", doc);
     remButtonDoc = (doc: Doc) => Doc.RemoveDocFromList(CurrentUserUtils.UserDocument.expandingButtons as Doc, "data", doc);
-    moveButtonDoc = (doc: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => this.remButtonDoc(doc) && addDocument(doc);
+    moveButtonDoc = (doc: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => this.remButtonDoc(doc) && addDocument(doc);
 
     buttonBarXf = () => {
         if (!this._docBtnRef.current) return Transform.Identity();
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index cd330d492..350a75d29 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -9,8 +9,6 @@ import { DocListCast, Doc } from "../../new_fields/Doc";
 import { Id } from "../../new_fields/FieldSymbols";
 import { DocumentView } from "./nodes/DocumentView";
 import { Transform } from "../util/Transform";
-import { CollectionFreeFormDocumentView } from "./nodes/CollectionFreeFormDocumentView";
-import { DocumentContentsView } from "./nodes/DocumentContentsView";
 import { NumCast } from "../../new_fields/Types";
 import { CollectionFreeFormLinksView } from "./collections/collectionFreeForm/CollectionFreeFormLinksView";
 
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 8d9dc4cf7..f7cfc4022 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -66,7 +66,7 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
         const ex = e.target.getBoundingClientRect().left;
         const ey = e.target.getBoundingClientRect().top;
         const de = new DragManager.DocumentDragData([topDoc]);
-        de.dragDivName =  topDocView.props.dragDivName;
+        de.dragDivName = topDocView.props.dragDivName;
         de.moveDocument = topDocView.props.moveDocument;
         undoBatch(action(() => topDoc.z = topDoc.z ? 0 : 1))();
         setTimeout(() => {
@@ -74,11 +74,7 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
             if (newDocView) {
                 const contentDiv = newDocView.ContentDiv!;
                 const xf = contentDiv.getBoundingClientRect();
-                DragManager.StartDocumentDrag([contentDiv], de, ex, ey, {
-                    offsetX: ex - xf.left, offsetY: ey - xf.top,
-                    handlers: { dragComplete: () => { }, },
-                    hideSource: true
-                });
+                DragManager.StartDocumentDrag([contentDiv], de, ex, ey, { offsetX: ex - xf.left, offsetY: ey - xf.top, hideSource: true });
             }
         }, 0);
     }
@@ -148,9 +144,6 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
             DragManager.StartDocumentDrag([dragDocView.ContentDiv!], dragData, left, top, {
                 offsetX: dragData.offset[0],
                 offsetY: dragData.offset[1],
-                handlers: {
-                    dragComplete: action(emptyFunction),
-                },
                 hideSource: false
             });
         }
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index ffcb3e9ce..08b9fd216 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -20,7 +20,7 @@ import { emptyFunction, returnEmptyString, returnFalse, returnOne, returnTrue, U
 import { DocServer } from "../../DocServer";
 import { Docs } from '../../documents/Documents';
 import { DocumentManager } from '../../util/DocumentManager';
-import { DragLinksAsDocuments, DragManager } from "../../util/DragManager";
+import { DragManager } from "../../util/DragManager";
 import { SelectionManager } from '../../util/SelectionManager';
 import { Transform } from '../../util/Transform';
 import { undoBatch } from "../../util/UndoManager";
@@ -346,7 +346,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
             const docid = (e.target as any).DashDocId;
             const tab = (e.target as any).parentElement as HTMLElement;
             DocServer.GetRefField(docid).then(action(async (sourceDoc: Opt<Field>) =>
-                (sourceDoc instanceof Doc) && DragLinksAsDocuments(tab, x, y, sourceDoc)));
+                (sourceDoc instanceof Doc) && DragManager.StartLinkTargetsDrag(tab, x, y, sourceDoc)));
         }
         if (className === "lm_drag_handle" || className === "lm_close" || className === "lm_maximise" || className === "lm_minimise" || className === "lm_close_tab") {
             this._flush = true;
@@ -419,15 +419,13 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                 };
                 ReactDOM.render(<span title="Drag as document"
                     className="collectionDockingView-dragAsDocument"
-                    onPointerDown={
-                        e => {
-                            e.preventDefault();
-                            e.stopPropagation();
-                            DragManager.StartDocumentDrag([dragSpan], new DragManager.DocumentDragData([doc]), e.clientX, e.clientY, {
-                                handlers: { dragComplete: emptyFunction },
-                                hideSource: false
-                            });
-                        }}><FontAwesomeIcon icon="file" size="lg" /></span>, dragSpan);
+                    onPointerDown={e => {
+                        e.preventDefault();
+                        e.stopPropagation();
+                        DragManager.StartDocumentDrag([dragSpan], new DragManager.DocumentDragData([doc]), e.clientX, e.clientY);
+                    }}>
+                    <FontAwesomeIcon icon="file" size="lg" />
+                </span>, dragSpan);
                 ReactDOM.render(<ButtonSelector Document={doc} Stack={stack} />, gearSpan);
                 tab.reactComponents = [dragSpan, gearSpan, upDiv];
                 tab.element.append(dragSpan);
diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
index d697e721b..80752303c 100644
--- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
+++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
@@ -56,7 +56,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
     createRowDropRef = (ele: HTMLDivElement | null) => {
         this._dropDisposer && this._dropDisposer();
         if (ele) {
-            this._dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.rowDrop.bind(this) } });
+            this._dropDisposer = DragManager.MakeDropTarget(ele, this.rowDrop.bind(this));
         }
     }
 
@@ -74,12 +74,12 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
     @undoBatch
     rowDrop = action((e: Event, de: DragManager.DropEvent) => {
         this._createAliasSelected = false;
-        if (de.data instanceof DragManager.DocumentDragData) {
+        if (de.complete.docDragData) {
             (this.props.parent.Document.dropConverter instanceof ScriptField) &&
-                this.props.parent.Document.dropConverter.script.run({ dragData: de.data });
+                this.props.parent.Document.dropConverter.script.run({ dragData: de.complete.docDragData });
             const key = StrCast(this.props.parent.props.Document.sectionFilter);
             const castedValue = this.getValue(this._heading);
-            de.data.droppedDocuments.forEach(d => d[key] = castedValue);
+            de.complete.docDragData.droppedDocuments.forEach(d => d[key] = castedValue);
             this.props.parent.drop(e, de);
             e.stopPropagation();
         }
@@ -171,10 +171,8 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
             const script = `return doc.${key} === ${value}`;
             const compiled = CompileScript(script, { params: { doc: Doc.name } });
             if (compiled.compiled) {
-                const scriptField = new ScriptField(compiled);
-                alias.viewSpecScript = scriptField;
-                const dragData = new DragManager.DocumentDragData([alias]);
-                DragManager.StartDocumentDrag([this._headerRef.current!], dragData, e.clientX, e.clientY);
+                alias.viewSpecScript = new ScriptField(compiled);
+                DragManager.StartDocumentDrag([this._headerRef.current!], new DragManager.DocumentDragData([alias]), e.clientX, e.clientY);
             }
 
             e.stopPropagation();
diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx
index 171dc4606..79a34bc00 100644
--- a/src/client/views/collections/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/CollectionSchemaCells.tsx
@@ -37,7 +37,7 @@ export interface CellProps {
     renderDepth: number;
     addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => boolean;
     pinToPres: (document: Doc) => void;
-    moveDocument: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
+    moveDocument: (document: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => boolean;
     isFocused: boolean;
     changeFocusedCellByIndex: (row: number, col: number) => void;
     setIsEditing: (isEditing: boolean) => void;
@@ -105,13 +105,13 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
     }
 
     private drop = (e: Event, de: DragManager.DropEvent) => {
-        if (de.data instanceof DragManager.DocumentDragData) {
+        if (de.complete.docDragData) {
             const fieldKey = this.props.rowProps.column.id as string;
-            if (de.data.draggedDocuments.length === 1) {
-                this._document[fieldKey] = de.data.draggedDocuments[0];
+            if (de.complete.docDragData.draggedDocuments.length === 1) {
+                this._document[fieldKey] = de.complete.docDragData.draggedDocuments[0];
             }
             else {
-                const coll = Docs.Create.SchemaDocument([new SchemaHeaderField("title", "#f1efeb")], de.data.draggedDocuments, {});
+                const coll = Docs.Create.SchemaDocument([new SchemaHeaderField("title", "#f1efeb")], de.complete.docDragData.draggedDocuments, {});
                 this._document[fieldKey] = coll;
             }
             e.stopPropagation();
@@ -121,7 +121,7 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
     private dropRef = (ele: HTMLElement | null) => {
         this._dropDisposer && this._dropDisposer();
         if (ele) {
-            this._dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } });
+            this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
         }
     }
 
@@ -167,11 +167,10 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
         const fieldIsDoc = (type === "document" && typeof field === "object") || (typeof field === "object" && doc);
 
         const onItemDown = (e: React.PointerEvent) => {
-            if (fieldIsDoc) {
-                SetupDrag(this._focusRef, () => this._document[props.fieldKey] instanceof Doc ? this._document[props.fieldKey] : this._document,
-                    this._document[props.fieldKey] instanceof Doc ? (doc: Doc, target: Doc, addDoc: (newDoc: Doc) => any) => addDoc(doc) : this.props.moveDocument,
-                    this._document[props.fieldKey] instanceof Doc ? "alias" : this.props.Document.schemaDoc ? "copy" : undefined)(e);
-            }
+            fieldIsDoc && SetupDrag(this._focusRef,
+                () => this._document[props.fieldKey] instanceof Doc ? this._document[props.fieldKey] : this._document,
+                this._document[props.fieldKey] instanceof Doc ? (doc: Doc, target: Doc | undefined, addDoc: (newDoc: Doc) => any) => addDoc(doc) : this.props.moveDocument,
+                this._document[props.fieldKey] instanceof Doc ? "alias" : this.props.Document.schemaDoc ? "copy" : undefined)(e);
         };
         const onPointerEnter = (e: React.PointerEvent): void => {
             if (e.buttons === 1 && SelectionManager.GetIsDragging() && (type === "document" || type === undefined)) {
diff --git a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx
index 90320df82..153bbd410 100644
--- a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx
+++ b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx
@@ -56,7 +56,7 @@ export class MovableColumn extends React.Component<MovableColumnProps> {
     createColDropTarget = (ele: HTMLDivElement) => {
         this._colDropDisposer && this._colDropDisposer();
         if (ele) {
-            this._colDropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.colDrop.bind(this) } });
+            this._colDropDisposer = DragManager.MakeDropTarget(ele, this.colDrop.bind(this));
         }
     }
 
@@ -66,8 +66,8 @@ export class MovableColumn extends React.Component<MovableColumnProps> {
         const rect = this._header!.current!.getBoundingClientRect();
         const bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left + ((rect.right - rect.left) / 2), rect.top);
         const before = x[0] < bounds[0];
-        if (de.data instanceof DragManager.ColumnDragData) {
-            this.props.reorderColumns(de.data.colKey, this.props.columnValue, before, this.props.allColumns);
+        if (de.complete.columnDragData) {
+            this.props.reorderColumns(de.complete.columnDragData.colKey, this.props.columnValue, before, this.props.allColumns);
             return true;
         }
         return false;
@@ -165,7 +165,7 @@ export class MovableRow extends React.Component<MovableRowProps> {
     createRowDropTarget = (ele: HTMLDivElement) => {
         this._rowDropDisposer && this._rowDropDisposer();
         if (ele) {
-            this._rowDropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.rowDrop.bind(this) } });
+            this._rowDropDisposer = DragManager.MakeDropTarget(ele, this.rowDrop.bind(this));
         }
     }
 
@@ -178,16 +178,17 @@ export class MovableRow extends React.Component<MovableRowProps> {
         const bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left, rect.top + rect.height / 2);
         const before = x[1] < bounds[1];
 
-        if (de.data instanceof DragManager.DocumentDragData) {
+        const docDragData = de.complete.docDragData;
+        if (docDragData) {
             e.stopPropagation();
-            if (de.data.draggedDocuments[0] === rowDoc) return true;
+            if (docDragData.draggedDocuments[0] === rowDoc) return true;
             const addDocument = (doc: Doc) => this.props.addDoc(doc, rowDoc, before);
-            const movedDocs = de.data.draggedDocuments;
-            return (de.data.dropAction || de.data.userDropAction) ?
-                de.data.droppedDocuments.reduce((added: boolean, d) => this.props.addDoc(d, rowDoc, before) || added, false)
-                : (de.data.moveDocument) ?
-                    movedDocs.reduce((added: boolean, d) => de.data.moveDocument(d, rowDoc, addDocument) || added, false)
-                    : de.data.droppedDocuments.reduce((added: boolean, d) => this.props.addDoc(d, rowDoc, before), false);
+            const movedDocs = docDragData.draggedDocuments;
+            return (docDragData.dropAction || docDragData.userDropAction) ?
+                docDragData.droppedDocuments.reduce((added: boolean, d) => this.props.addDoc(d, rowDoc, before) || added, false)
+                : (docDragData.moveDocument) ?
+                    movedDocs.reduce((added: boolean, d) => docDragData.moveDocument?.(d, rowDoc, addDocument) || added, false)
+                    : docDragData.droppedDocuments.reduce((added: boolean, d) => this.props.addDoc(d, rowDoc, before), false);
         }
         return false;
     }
@@ -199,12 +200,12 @@ export class MovableRow extends React.Component<MovableRowProps> {
 
     @undoBatch
     @action
-    move: DragManager.MoveFunction = (doc: Doc, target: Doc, addDoc) => {
-        const targetView = DocumentManager.Instance.getDocumentView(target);
+    move: DragManager.MoveFunction = (doc: Doc, targetCollection: Doc | undefined, addDoc) => {
+        const targetView = targetCollection && DocumentManager.Instance.getDocumentView(targetCollection);
         if (targetView && targetView.props.ContainingCollectionDoc) {
-            return doc !== target && doc !== targetView.props.ContainingCollectionDoc && this.props.removeDoc(doc) && addDoc(doc);
+            return doc !== targetCollection && doc !== targetView.props.ContainingCollectionDoc && this.props.removeDoc(doc) && addDoc(doc);
         }
-        return doc !== target && this.props.removeDoc(doc) && addDoc(doc);
+        return doc !== targetCollection && this.props.removeDoc(doc) && addDoc(doc);
     }
 
     render() {
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 8169e9e2b..bb706e528 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -224,7 +224,7 @@ export interface SchemaTableProps {
     renderDepth: number;
     deleteDocument: (document: Doc) => boolean;
     addDocument: (document: Doc) => boolean;
-    moveDocument: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
+    moveDocument: (document: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => boolean;
     ScreenToLocalTransform: () => Transform;
     active: (outsideReaction: boolean) => boolean;
     onDrop: (e: React.DragEvent<Element>, options: DocumentOptions, completed?: (() => void) | undefined) => void;
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index ff3417b77..6a23920f3 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -147,7 +147,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     }
 
     @action
-    moveDocument = (doc: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean): boolean => {
+    moveDocument = (doc: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean): boolean => {
         return this.props.removeDocument(doc) && addDocument(doc);
     }
     createRef = (ele: HTMLDivElement | null) => {
@@ -243,7 +243,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         const where = [de.x, de.y];
         let targInd = -1;
         let plusOne = false;
-        if (de.data instanceof DragManager.DocumentDragData) {
+        if (de.complete.docDragData) {
             this._docXfs.map((cd, i) => {
                 const pos = cd.dxf().inverse().transformPoint(-2 * this.gridGap, -2 * this.gridGap);
                 const pos1 = cd.dxf().inverse().transformPoint(cd.width(), cd.height());
@@ -252,16 +252,16 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
                     plusOne = (where[1] > (pos[1] + pos1[1]) / 2 ? 1 : 0) ? true : false;
                 }
             });
-        }
-        if (super.drop(e, de)) {
-            const newDoc = de.data.droppedDocuments[0];
-            const docs = this.childDocList;
-            if (docs) {
-                if (targInd === -1) targInd = docs.length;
-                else targInd = docs.indexOf(this.filteredChildren[targInd]);
-                const srcInd = docs.indexOf(newDoc);
-                docs.splice(srcInd, 1);
-                docs.splice((targInd > srcInd ? targInd - 1 : targInd) + (plusOne ? 1 : 0), 0, newDoc);
+            if (super.drop(e, de)) {
+                const newDoc = de.complete.docDragData.droppedDocuments[0];
+                const docs = this.childDocList;
+                if (docs) {
+                    if (targInd === -1) targInd = docs.length;
+                    else targInd = docs.indexOf(this.filteredChildren[targInd]);
+                    const srcInd = docs.indexOf(newDoc);
+                    docs.splice(srcInd, 1);
+                    docs.splice((targInd > srcInd ? targInd - 1 : targInd) + (plusOne ? 1 : 0), 0, newDoc);
+                }
             }
         }
         return false;
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index ca3b93bf8..39b4e4e1d 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -51,21 +51,21 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
         this._dropRef = ele;
         this.dropDisposer && this.dropDisposer();
         if (ele) {
-            this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.columnDrop.bind(this) } });
+            this.dropDisposer = DragManager.MakeDropTarget(ele, this.columnDrop.bind(this));
         }
     }
 
     @undoBatch
     columnDrop = action((e: Event, de: DragManager.DropEvent) => {
         this._createAliasSelected = false;
-        if (de.data instanceof DragManager.DocumentDragData) {
+        if (de.complete.docDragData) {
             const key = StrCast(this.props.parent.props.Document.sectionFilter);
             const castedValue = this.getValue(this._heading);
             if (castedValue) {
-                de.data.droppedDocuments.forEach(d => d[key] = castedValue);
+                de.complete.docDragData.droppedDocuments.forEach(d => d[key] = castedValue);
             }
             else {
-                de.data.droppedDocuments.forEach(d => d[key] = undefined);
+                de.complete.docDragData.droppedDocuments.forEach(d => d[key] = undefined);
             }
             this.props.parent.drop(e, de);
             e.stopPropagation();
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 9fa244546..5c7794cc0 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -27,7 +27,7 @@ import { Networking } from "../../Network";
 export interface CollectionViewProps extends FieldViewProps {
     addDocument: (document: Doc) => boolean;
     removeDocument: (document: Doc) => boolean;
-    moveDocument: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
+    moveDocument: (document: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => boolean;
     PanelWidth: () => number;
     PanelHeight: () => number;
     VisibleHeight?: () => number;
@@ -51,7 +51,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
         protected createDropTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
             this.dropDisposer && this.dropDisposer();
             if (ele) {
-                this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } });
+                this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
             }
         }
         protected CreateDropTarget(ele: HTMLDivElement) { //used in schema view
@@ -132,32 +132,33 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
         @undoBatch
         @action
         protected drop(e: Event, de: DragManager.DropEvent): boolean {
+            const docDragData = de.complete.docDragData;
             (this.props.Document.dropConverter instanceof ScriptField) &&
-                this.props.Document.dropConverter.script.run({ dragData: de.data });
-            if (de.data instanceof DragManager.DocumentDragData && !de.data.applyAsTemplate) {
-                if (de.mods === "AltKey" && de.data.draggedDocuments.length) {
+                this.props.Document.dropConverter.script.run({ dragData: docDragData }); /// bcz: check this 
+            if (docDragData && !docDragData.applyAsTemplate) {
+                if (de.altKey && docDragData.draggedDocuments.length) {
                     this.childDocs.map(doc =>
-                        Doc.ApplyTemplateTo(de.data.draggedDocuments[0], doc, "layoutFromParent"));
+                        Doc.ApplyTemplateTo(docDragData.draggedDocuments[0], doc, "layoutFromParent"));
                     e.stopPropagation();
                     return true;
                 }
                 let added = false;
-                if (de.data.dropAction || de.data.userDropAction) {
-                    added = de.data.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d) || added, false);
-                } else if (de.data.moveDocument) {
-                    const movedDocs = de.data.draggedDocuments;
+                if (docDragData.dropAction || docDragData.userDropAction) {
+                    added = docDragData.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d) || added, false);
+                } else if (docDragData.moveDocument) {
+                    const movedDocs = docDragData.draggedDocuments;
                     added = movedDocs.reduce((added: boolean, d, i) =>
-                        de.data.droppedDocuments[i] !== d ? this.props.addDocument(de.data.droppedDocuments[i]) :
-                            de.data.moveDocument(d, this.props.Document, this.props.addDocument) || added, false);
+                        docDragData.droppedDocuments[i] !== d ? this.props.addDocument(docDragData.droppedDocuments[i]) :
+                            docDragData.moveDocument?.(d, this.props.Document, this.props.addDocument) || added, false);
                 } else {
-                    added = de.data.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d) || added, false);
+                    added = docDragData.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d) || added, false);
                 }
                 e.stopPropagation();
                 return added;
             }
-            else if (de.data instanceof DragManager.AnnotationDragData) {
+            else if (de.complete.annoDragData) {
                 e.stopPropagation();
-                return this.props.addDocument(de.data.dropDocument);
+                return this.props.addDocument(de.complete.annoDragData.dropDocument);
             }
             return false;
         }
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index ec1e7409f..0a22a897c 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -123,7 +123,7 @@ class TreeView extends React.Component<TreeViewProps> {
     @undoBatch delete = () => this.props.deleteDoc(this.props.document);
     @undoBatch openRight = () => this.props.addDocTab(this.props.document, this.templateDataDoc, "onRight", this.props.libraryPath);
     @undoBatch indent = () => this.props.addDocument(this.props.document) && this.delete();
-    @undoBatch move = (doc: Doc, target: Doc, addDoc: (doc: Doc) => boolean) => {
+    @undoBatch move = (doc: Doc, target: Doc | undefined, addDoc: (doc: Doc) => boolean) => {
         return this.props.document !== target && this.props.deleteDoc(doc) && addDoc(doc);
     }
     @undoBatch @action remove = (document: Document, key: string) => {
@@ -132,7 +132,7 @@ class TreeView extends React.Component<TreeViewProps> {
 
     protected createTreeDropTarget = (ele: HTMLDivElement) => {
         this._treedropDisposer && this._treedropDisposer();
-        ele && (this._treedropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.treeDrop.bind(this) } }));
+        ele && (this._treedropDisposer = DragManager.MakeDropTarget(ele, this.treeDrop.bind(this)));
     }
 
     onPointerDown = (e: React.PointerEvent) => e.stopPropagation();
@@ -220,25 +220,25 @@ class TreeView extends React.Component<TreeViewProps> {
         const rect = this._header!.current!.getBoundingClientRect();
         const before = pt[1] < rect.top + rect.height / 2;
         const inside = pt[0] > Math.min(rect.left + 75, rect.left + rect.width * .75) || (!before && this.treeViewOpen && DocListCast(this.dataDoc[this.fieldKey]).length);
-        if (de.data instanceof DragManager.LinkDragData) {
-            const sourceDoc = de.data.linkSourceDocument;
+        if (de.complete.linkDragData) {
+            const sourceDoc = de.complete.linkDragData.linkSourceDocument;
             const destDoc = this.props.document;
             DocUtils.MakeLink({ doc: sourceDoc }, { doc: destDoc });
             e.stopPropagation();
         }
-        if (de.data instanceof DragManager.DocumentDragData) {
+        if (de.complete.docDragData) {
             e.stopPropagation();
-            if (de.data.draggedDocuments[0] === this.props.document) return true;
+            if (de.complete.docDragData.draggedDocuments[0] === this.props.document) return true;
             let addDoc = (doc: Doc) => this.props.addDocument(doc, undefined, before);
             if (inside) {
                 addDoc = (doc: Doc) => Doc.AddDocToList(this.dataDoc, this.fieldKey, doc) || addDoc(doc);
             }
-            const movedDocs = (de.data.options === this.props.treeViewId ? de.data.draggedDocuments : de.data.droppedDocuments);
-            return ((de.data.dropAction && (de.data.options !== this.props.treeViewId)) || de.data.userDropAction) ?
-                de.data.droppedDocuments.reduce((added, d) => addDoc(d) || added, false)
-                : de.data.moveDocument ?
-                    movedDocs.reduce((added, d) => de.data.moveDocument(d, undefined, addDoc) || added, false)
-                    : de.data.droppedDocuments.reduce((added, d) => addDoc(d), false);
+            const movedDocs = (de.complete.docDragData.treeViewId === this.props.treeViewId ? de.complete.docDragData.draggedDocuments : de.complete.docDragData.droppedDocuments);
+            return ((de.complete.docDragData.dropAction && (de.complete.docDragData.treeViewId !== this.props.treeViewId)) || de.complete.docDragData.userDropAction) ?
+                de.complete.docDragData.droppedDocuments.reduce((added, d) => addDoc(d) || added, false)
+                : de.complete.docDragData.moveDocument ?
+                    movedDocs.reduce((added, d) => de.complete.docDragData?.moveDocument?.(d, undefined, addDoc) || added, false)
+                    : de.complete.docDragData.droppedDocuments.reduce((added, d) => addDoc(d), false);
         }
         return false;
     }
@@ -571,7 +571,7 @@ export class CollectionTreeView extends CollectionSubView(Document) {
     protected createTreeDropTarget = (ele: HTMLDivElement) => {
         this.treedropDisposer && this.treedropDisposer();
         if (this._mainEle = ele) {
-            this.treedropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } });
+            this.treedropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
         }
     }
 
@@ -624,7 +624,7 @@ export class CollectionTreeView extends CollectionSubView(Document) {
     render() {
         const dropAction = StrCast(this.props.Document.dropAction) as dropActionType;
         const addDoc = (doc: Doc, relativeTo?: Doc, before?: boolean) => Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, relativeTo, before, false, false, false);
-        const moveDoc = (d: Doc, target: Doc, addDoc: (doc: Doc) => boolean) => this.props.moveDocument(d, target, addDoc);
+        const moveDoc = (d: Doc, target: Doc | undefined, addDoc: (doc: Doc) => boolean) => this.props.moveDocument(d, target, addDoc);
         return !this.childDocs ? (null) : (
             <div className="collectionTreeView-dropTarget" id="body"
                 style={{ background: StrCast(this.props.Document.backgroundColor, "lightgray"), paddingTop: `${NumCast(this.props.Document.yMargin, 20)}px` }}
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 15eec37de..9bd07966c 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -68,7 +68,7 @@ export namespace CollectionViewType {
 export interface CollectionRenderProps {
     addDocument: (document: Doc) => boolean;
     removeDocument: (document: Doc) => boolean;
-    moveDocument: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
+    moveDocument: (document: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => boolean;
     active: () => boolean;
     whenActiveChanged: (isActive: boolean) => void;
 }
@@ -150,7 +150,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
     // otherwise, the document being moved must be able to be removed from its container before
     // moving it into the target.  
     @action.bound
-    moveDocument(doc: Doc, targetCollection: Doc, addDocument: (doc: Doc) => boolean): boolean {
+    moveDocument(doc: Doc, targetCollection: Doc | undefined, addDocument: (doc: Doc) => boolean): boolean {
         if (Doc.AreProtosEqual(this.props.Document, targetCollection)) {
             return true;
         }
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index 4161e5d6e..a870b6043 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -288,15 +288,15 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
     protected createDropTarget = (ele: HTMLDivElement) => {
         this.dropDisposer && this.dropDisposer();
         if (ele) {
-            this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } });
+            this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
         }
     }
 
     @undoBatch
     @action
     protected drop(e: Event, de: DragManager.DropEvent): boolean {
-        if (de.data instanceof DragManager.DocumentDragData && de.data.draggedDocuments.length) {
-            this._buttonizableCommands.filter(c => c.title === this._currentKey).map(c => c.immediate(de.data.draggedDocuments));
+        if (de.complete.docDragData && de.complete.docDragData.draggedDocuments.length) {
+            this._buttonizableCommands.filter(c => c.title === this._currentKey).map(c => c.immediate(de.complete.docDragData?.draggedDocuments || []));
             e.stopPropagation();
         }
         return true;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index ddfb52ba9..89c1df598 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -139,15 +139,15 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         const [xp, yp] = xf.transformPoint(de.x, de.y);
         const [xpo, ypo] = xfo.transformPoint(de.x, de.y);
         if (super.drop(e, de)) {
-            if (de.data instanceof DragManager.DocumentDragData) {
-                if (de.data.droppedDocuments.length) {
-                    const firstDoc = de.data.droppedDocuments[0];
+            if (de.complete.docDragData) {
+                if (de.complete.docDragData.droppedDocuments.length) {
+                    const firstDoc = de.complete.docDragData.droppedDocuments[0];
                     const z = NumCast(firstDoc.z);
-                    const x = (z ? xpo : xp) - de.data.offset[0];
-                    const y = (z ? ypo : yp) - de.data.offset[1];
+                    const x = (z ? xpo : xp) - de.complete.docDragData.offset[0];
+                    const y = (z ? ypo : yp) - de.complete.docDragData.offset[1];
                     const dropX = NumCast(firstDoc.x);
                     const dropY = NumCast(firstDoc.y);
-                    de.data.droppedDocuments.forEach(action((d: Doc) => {
+                    de.complete.docDragData.droppedDocuments.forEach(action((d: Doc) => {
                         const layoutDoc = Doc.Layout(d);
                         d.x = x + NumCast(d.x) - dropX;
                         d.y = y + NumCast(d.y) - dropY;
@@ -162,19 +162,19 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                         this.bringToFront(d);
                     }));
 
-                    de.data.droppedDocuments.length === 1 && this.updateCluster(de.data.droppedDocuments[0]);
+                    de.complete.docDragData.droppedDocuments.length === 1 && this.updateCluster(de.complete.docDragData.droppedDocuments[0]);
                 }
             }
-            else if (de.data instanceof DragManager.AnnotationDragData) {
-                if (de.data.dropDocument) {
-                    const dragDoc = de.data.dropDocument;
-                    const x = xp - de.data.offset[0];
-                    const y = yp - de.data.offset[1];
+            else if (de.complete.annoDragData) {
+                if (de.complete.annoDragData.dropDocument) {
+                    const dragDoc = de.complete.annoDragData.dropDocument;
+                    const x = xp - de.complete.annoDragData.offset[0];
+                    const y = yp - de.complete.annoDragData.offset[1];
                     const dropX = NumCast(dragDoc.x);
                     const dropY = NumCast(dragDoc.y);
                     dragDoc.x = x + NumCast(dragDoc.x) - dropX;
                     dragDoc.y = y + NumCast(dragDoc.y) - dropY;
-                    de.data.targetContext = this.props.Document; // dropped a PDF annotation, so we need to set the targetContext on the dragData which the PDF view uses at the end of the drop operation
+                    de.complete.annoDragData.targetContext = this.props.Document; // dropped a PDF annotation, so we need to set the targetContext on the dragData which the PDF view uses at the end of the drop operation
                     this.bringToFront(dragDoc);
                 }
             }
@@ -205,10 +205,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 const [left, top] = clusterDocs[0].props.ScreenToLocalTransform().scale(clusterDocs[0].props.ContentScaling()).inverse().transformPoint(0, 0);
                 de.offset = this.getTransform().transformDirection(ptsParent.clientX - left, ptsParent.clientY - top);
                 de.dropAction = e.ctrlKey || e.altKey ? "alias" : undefined;
-                DragManager.StartDocumentDrag(clusterDocs.map(v => v.ContentDiv!), de, ptsParent.clientX, ptsParent.clientY, {
-                    handlers: { dragComplete: action(emptyFunction) },
-                    hideSource: !de.dropAction
-                });
+                DragManager.StartDocumentDrag(clusterDocs.map(v => v.ContentDiv!), de, ptsParent.clientX, ptsParent.clientY, { hideSource: !de.dropAction });
                 return true;
             }
         }
@@ -896,7 +893,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         // otherwise, they are stored in fieldKey.  All annotations to this document are stored in the extension document
         return !this.extensionDoc ? (null) :
             <div className={"collectionfreeformview-container"} ref={this.createDropTarget} onWheel={this.onPointerWheel}//pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined,
-                style={{ height: this.isAnnotationOverlay ? (this.props.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight() }}
+                style={{ pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined, height: this.isAnnotationOverlay ? (this.props.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight() }}
                 onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onContextMenu={this.onContextMenu} onTouchStart={this.onTouchStart}>
                 <MarqueeView {...this.props} extensionDoc={this.extensionDoc} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments} addDocument={this.addDocument}
                     addLiveTextDocument={this.addLiveTextBox} getContainerTransform={this.getContainerTransform} getTransform={this.getTransform} isAnnotationOverlay={this.isAnnotationOverlay}>
diff --git a/src/client/views/linking/LinkMenu.scss b/src/client/views/linking/LinkMenu.scss
index a4018bd2d..7dee22f66 100644
--- a/src/client/views/linking/LinkMenu.scss
+++ b/src/client/views/linking/LinkMenu.scss
@@ -48,90 +48,5 @@
     }
 }
 
-.linkMenu-item {
-    // border-top: 0.5px solid $main-accent; 
-    position: relative;
-    display: flex;
-    font-size: 12px;
-    
-
-    .link-name {
-        position: relative;
-
-        p {
-            padding: 4px 6px;
-            line-height: 12px;
-            border-radius: 5px;
-            overflow-wrap: break-word;
-        }
-    }
-
-    .linkMenu-item-content {
-        width: 100%;
-    }
-    
-    .link-metadata {
-        padding: 0 10px 0 16px;
-        margin-bottom: 4px;
-        color: $main-accent;
-        font-style: italic;
-        font-size: 10.5px;
-    }
-
-    &:hover {
-        .linkMenu-item-buttons {
-            display: flex;
-        }
-        .linkMenu-item-content {
-            &.expand-two p {
-                width: calc(100% - 52px);
-                background-color: lightgray;
-            }
-            &.expand-three p {
-                width: calc(100% - 84px);
-                background-color: lightgray;
-            }
-        }
-    }
-}
-
-.linkMenu-item-buttons {
-    display: none;
-    position: absolute;
-    top: 50%;
-    right: 0;
-    transform: translateY(-50%);
-
-    .button {
-        width: 20px;
-        height: 20px;
-        margin: 0;
-        margin-right: 6px;
-        border-radius: 50%;
-        cursor: pointer;
-        pointer-events: auto;
-        background-color: $dark-color;
-        color: $light-color;
-        font-size: 65%;
-        transition: transform 0.2s;
-        text-align: center;
-        position: relative;
-
-        .fa-icon {
-            position: absolute;
-            top: 50%;
-            left: 50%;
-            transform: translate(-50%, -50%);
-        }
-
-        &:last-child {
-            margin-right: 0;
-        }
-        &:hover {
-            background: $main-accent;
-        }
-    }
-}
-
 
 
diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx
index 15aacbbc9..da56435f2 100644
--- a/src/client/views/linking/LinkMenuGroup.tsx
+++ b/src/client/views/linking/LinkMenuGroup.tsx
@@ -4,11 +4,9 @@ import { observer } from "mobx-react";
 import { Doc } from "../../../new_fields/Doc";
 import { Id } from "../../../new_fields/FieldSymbols";
 import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
-import { emptyFunction } from "../../../Utils";
 import { Docs } from "../../documents/Documents";
 import { DragManager, SetupDrag } from "../../util/DragManager";
 import { LinkManager } from "../../util/LinkManager";
-import { UndoManager } from "../../util/UndoManager";
 import { DocumentView } from "../nodes/DocumentView";
 import './LinkMenu.scss';
 import { LinkMenuItem } from "./LinkMenuItem";
@@ -21,7 +19,6 @@ interface LinkMenuGroupProps {
     showEditor: (linkDoc: Doc) => void;
     addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => boolean;
     docView: DocumentView;
-
 }
 
 @observer
@@ -44,27 +41,14 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
         e.stopPropagation();
     }
 
-
     onLinkButtonMoved = async (e: PointerEvent) => {
-        UndoManager.RunInBatch(() => {
-            if (this._drag.current !== null && (e.movementX > 1 || e.movementY > 1)) {
-                document.removeEventListener("pointermove", this.onLinkButtonMoved);
-                document.removeEventListener("pointerup", this.onLinkButtonUp);
+        if (this._drag.current && (e.movementX > 1 || e.movementY > 1)) {
+            document.removeEventListener("pointermove", this.onLinkButtonMoved);
+            document.removeEventListener("pointerup", this.onLinkButtonUp);
 
-                const draggedDocs = this.props.group.map(linkDoc => {
-                    const opp = LinkManager.Instance.getOppositeAnchor(linkDoc, this.props.sourceDoc);
-                    if (opp) return opp;
-                }) as Doc[];
-                const dragData = new DragManager.DocumentDragData(draggedDocs);
-
-                DragManager.StartLinkedDocumentDrag([this._drag.current], dragData, e.x, e.y, {
-                    handlers: {
-                        dragComplete: action(emptyFunction),
-                    },
-                    hideSource: false
-                });
-            }
-        }, "drag links");
+            let targets = this.props.group.map(l => LinkManager.Instance.getOppositeAnchor(l, this.props.sourceDoc)).filter(d => d) as Doc[];
+            DragManager.StartLinkTargetsDrag(this._drag.current!, e.x, e.y, this.props.sourceDoc, targets);
+        }
         e.stopPropagation();
     }
 
diff --git a/src/client/views/linking/LinkMenuItem.scss b/src/client/views/linking/LinkMenuItem.scss
new file mode 100644
index 000000000..fd0954f65
--- /dev/null
+++ b/src/client/views/linking/LinkMenuItem.scss
@@ -0,0 +1,87 @@
+@import "../globalCssVariables";
+
+.linkMenu-item {
+    // border-top: 0.5px solid $main-accent; 
+    position: relative;
+    display: flex;
+    font-size: 12px;
+    
+
+    .linkMenu-name {
+        position: relative;
+
+        p {
+            padding: 4px 6px;
+            line-height: 12px;
+            border-radius: 5px;
+            overflow-wrap: break-word;
+            user-select: none;
+        }
+    }
+
+    .linkMenu-item-content {
+        width: 100%;
+    }
+    
+    .link-metadata {
+        padding: 0 10px 0 16px;
+        margin-bottom: 4px;
+        color: $main-accent;
+        font-style: italic;
+        font-size: 10.5px;
+    }
+
+    &:hover {
+        .linkMenu-item-buttons {
+            display: flex;
+        }
+        .linkMenu-item-content {
+            &.expand-two p {
+                width: calc(100% - 52px);
+                background-color: lightgray;
+            }
+            &.expand-three p {
+                width: calc(100% - 84px);
+                background-color: lightgray;
+            }
+        }
+    }
+}
+
+.linkMenu-item-buttons {
+    display: none;
+    position: absolute;
+    top: 50%;
+    right: 0;
+    transform: translateY(-50%);
+
+    .button {
+        width: 20px;
+        height: 20px;
+        margin: 0;
+        margin-right: 6px;
+        border-radius: 50%;
+        cursor: pointer;
+        pointer-events: auto;
+        background-color: $dark-color;
+        color: $light-color;
+        font-size: 65%;
+        transition: transform 0.2s;
+        text-align: center;
+        position: relative;
+
+        .fa-icon {
+            position: absolute;
+            top: 50%;
+            left: 50%;
+            transform: translate(-50%, -50%);
+        }
+
+        &:last-child {
+            margin-right: 0;
+        }
+        &:hover {
+            background: $main-accent;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index edf5e9c26..b7d27ee30 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -5,11 +5,11 @@ import { action, observable } from 'mobx';
 import { observer } from "mobx-react";
 import { Doc } from '../../../new_fields/Doc';
 import { Cast, StrCast } from '../../../new_fields/Types';
-import { DragLinkAsDocument } from '../../util/DragManager';
+import { DragManager } from '../../util/DragManager';
 import { LinkManager } from '../../util/LinkManager';
 import { ContextMenu } from '../ContextMenu';
 import { LinkFollowBox } from './LinkFollowBox';
-import './LinkMenu.scss';
+import './LinkMenuItem.scss';
 import React = require("react");
 library.add(faEye, faEdit, faTimes, faArrowRight, faChevronDown, faChevronUp);
 
@@ -26,6 +26,9 @@ interface LinkMenuItemProps {
 @observer
 export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
     private _drag = React.createRef<HTMLDivElement>();
+    private _downX = 0;
+    private _downY = 0;
+    private _eleClone: any;
     @observable private _showMore: boolean = false;
     @action toggleShowMore() { this._showMore = !this._showMore; }
 
@@ -55,6 +58,9 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
     }
 
     onLinkButtonDown = (e: React.PointerEvent): void => {
+        this._downX = e.clientX;
+        this._downY = e.clientY;
+        this._eleClone = this._drag.current!.cloneNode(true);
         e.stopPropagation();
         document.removeEventListener("pointermove", this.onLinkButtonMoved);
         document.addEventListener("pointermove", this.onLinkButtonMoved);
@@ -75,11 +81,12 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
     }
 
     onLinkButtonMoved = async (e: PointerEvent) => {
-        if (this._drag.current !== null && (e.movementX > 1 || e.movementY > 1)) {
+        if (this._drag.current !== null && Math.abs((e.clientX - this._downX) * (e.clientX - this._downX) + (e.clientY - this._downY) * (e.clientY - this._downY)) > 5) {
             document.removeEventListener("pointermove", this.onLinkButtonMoved);
             document.removeEventListener("pointerup", this.onLinkButtonUp);
 
-            DragLinkAsDocument(this._drag.current, e.x, e.y, this.props.linkDoc, this.props.sourceDoc);
+            this._eleClone.style.transform = `translate(${e.x}px, ${e.y}px)`;
+            DragManager.StartLinkTargetsDrag(this._eleClone, e.x, e.y, this.props.sourceDoc, [this.props.linkDoc]);
         }
         e.stopPropagation();
     }
@@ -109,20 +116,21 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
     }
 
     render() {
-
         const keys = LinkManager.Instance.getMetadataKeysInGroup(this.props.groupType);//groupMetadataKeys.get(this.props.groupType);
         const canExpand = keys ? keys.length > 0 : false;
 
         return (
             <div className="linkMenu-item">
                 <div className={canExpand ? "linkMenu-item-content expand-three" : "linkMenu-item-content expand-two"}>
-                    <div className="link-name">
-                        <p ref={this._drag} onPointerDown={this.onLinkButtonDown}>{StrCast(this.props.destinationDoc.title)}</p>
+                    <div ref={this._drag} className="linkMenu-name" title="drag to view target. click to customize." onPointerDown={this.onLinkButtonDown}>
+                        <p >{StrCast(this.props.destinationDoc.title)}</p>
                         <div className="linkMenu-item-buttons">
                             {canExpand ? <div title="Show more" className="button" onPointerDown={() => this.toggleShowMore()}>
                                 <FontAwesomeIcon className="fa-icon" icon={this._showMore ? "chevron-up" : "chevron-down"} size="sm" /></div> : <></>}
                             <div title="Edit link" className="button" onPointerDown={this.onEdit}><FontAwesomeIcon className="fa-icon" icon="edit" size="sm" /></div>
-                            <div title="Follow link" className="button" onClick={this.followDefault} onContextMenu={this.onContextMenu}><FontAwesomeIcon className="fa-icon" icon="arrow-right" size="sm" /></div>
+                            <div title="Follow link" className="button" onClick={this.followDefault} onContextMenu={this.onContextMenu}>
+                                <FontAwesomeIcon className="fa-icon" icon="arrow-right" size="sm" />
+                            </div>
                         </div>
                     </div>
                     {this._showMore ? this.renderMetadata() : <></>}
diff --git a/src/client/views/nodes/ButtonBox.tsx b/src/client/views/nodes/ButtonBox.tsx
index 34151a311..d1272c266 100644
--- a/src/client/views/nodes/ButtonBox.tsx
+++ b/src/client/views/nodes/ButtonBox.tsx
@@ -46,7 +46,7 @@ export class ButtonBox extends DocComponent<FieldViewProps, ButtonDocument>(Butt
             this.dropDisposer();
         }
         if (ele) {
-            this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } });
+            this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
         }
     }
 
@@ -65,9 +65,10 @@ export class ButtonBox extends DocComponent<FieldViewProps, ButtonDocument>(Butt
     @undoBatch
     @action
     drop = (e: Event, de: DragManager.DropEvent) => {
-        if (de.data instanceof DragManager.DocumentDragData && e.target) {
-            this.props.Document[(e.target as any).textContent] = new List<Doc>(de.data.droppedDocuments.map((d, i) =>
-                d.onDragStart ? de.data.draggedDocuments[i] : d));
+        const docDragData = de.complete.docDragData;
+        if (docDragData && e.target) {
+            this.props.Document[(e.target as any).textContent] = new List<Doc>(docDragData.droppedDocuments.map((d, i) =>
+                d.onDragStart ? docDragData.draggedDocuments[i] : d));
             e.stopPropagation();
         }
     }
diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx
index 6d0ea33fa..5e3306a11 100644
--- a/src/client/views/nodes/ContentFittingDocumentView.tsx
+++ b/src/client/views/nodes/ContentFittingDocumentView.tsx
@@ -32,7 +32,7 @@ interface ContentFittingDocumentViewProps {
     onClick?: ScriptField;
     getTransform: () => Transform;
     addDocument: (document: Doc) => boolean;
-    moveDocument: (document: Doc, target: Doc, addDoc: ((doc: Doc) => boolean)) => boolean;
+    moveDocument: (document: Doc, target: Doc | undefined, addDoc: ((doc: Doc) => boolean)) => boolean;
     removeDocument: (document: Doc) => boolean;
     active: (outsideReaction: boolean) => boolean;
     whenActiveChanged: (isActive: boolean) => void;
@@ -60,11 +60,12 @@ export class ContentFittingDocumentView extends React.Component<ContentFittingDo
     @undoBatch
     @action
     drop = (e: Event, de: DragManager.DropEvent) => {
-        if (de.data instanceof DragManager.DocumentDragData) {
+        const docDragData = de.complete.docDragData;
+        if (docDragData) {
             this.props.childDocs && this.props.childDocs.map(otherdoc => {
                 const target = Doc.GetProto(otherdoc);
                 target.layout = ComputedField.MakeFunction("this.image_data[0]");
-                target.layoutCustom = Doc.MakeDelegate(de.data.draggedDocuments[0]);
+                target.layoutCustom = Doc.MakeDelegate(docDragData.draggedDocuments[0]);
             });
             e.stopPropagation();
         }
diff --git a/src/client/views/nodes/DocuLinkBox.tsx b/src/client/views/nodes/DocuLinkBox.tsx
index a22472e9e..3e2e74c67 100644
--- a/src/client/views/nodes/DocuLinkBox.tsx
+++ b/src/client/views/nodes/DocuLinkBox.tsx
@@ -5,7 +5,7 @@ import { makeInterface } from "../../../new_fields/Schema";
 import { NumCast, StrCast, Cast } from "../../../new_fields/Types";
 import { Utils } from '../../../Utils';
 import { DocumentManager } from "../../util/DocumentManager";
-import { DragLinksAsDocuments } from "../../util/DragManager";
+import { DragManager } from "../../util/DragManager";
 import { DocComponent } from "../DocComponent";
 import "./DocuLinkBox.scss";
 import { FieldView, FieldViewProps } from "./FieldView";
@@ -43,7 +43,7 @@ export class DocuLinkBox extends DocComponent<FieldViewProps, DocLinkSchema>(Doc
             const separation = Math.sqrt((pt[0] - e.clientX) * (pt[0] - e.clientX) + (pt[1] - e.clientY) * (pt[1] - e.clientY));
             const dragdist = Math.sqrt((pt[0] - this._downx) * (pt[0] - this._downx) + (pt[1] - this._downy) * (pt[1] - this._downy));
             if (separation > 100) {
-                DragLinksAsDocuments(this._ref.current!, pt[0], pt[1], Cast(this.props.Document[this.props.fieldKey], Doc) as Doc, this.props.Document); // Containging collection is the document, not a collection... hack.
+                DragManager.StartLinkTargetsDrag(this._ref.current!, pt[0], pt[1], Cast(this.props.Document[this.props.fieldKey], Doc) as Doc, [this.props.Document]); // Containging collection is the document, not a collection... hack.
                 document.removeEventListener("pointermove", this.onPointerMove);
                 document.removeEventListener("pointerup", this.onPointerUp);
             } else if (dragdist > separation) {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 1b2d92027..b096d68fc 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -19,7 +19,6 @@ import { DocumentType } from '../../documents/DocumentTypes';
 import { ClientUtils } from '../../util/ClientUtils';
 import { DocumentManager } from "../../util/DocumentManager";
 import { DragManager, dropActionType } from "../../util/DragManager";
-import { LinkManager } from '../../util/LinkManager';
 import { Scripting } from '../../util/Scripting';
 import { SelectionManager } from "../../util/SelectionManager";
 import SharingManager from '../../util/SharingManager';
@@ -60,7 +59,7 @@ export interface DocumentViewProps {
     dragDivName?: string;
     addDocument?: (doc: Doc) => boolean;
     removeDocument?: (doc: Doc) => boolean;
-    moveDocument?: (doc: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
+    moveDocument?: (doc: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => boolean;
     ScreenToLocalTransform: () => Transform;
     renderDepth: number;
     showOverlays?: (doc: Doc) => { title?: string, caption?: string };
@@ -105,7 +104,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
     @action
     componentDidMount() {
-        this._mainCont.current && (this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, { handlers: { drop: this.drop.bind(this) } }));
+        this._mainCont.current && (this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this)));
 
         !this.props.dontRegisterView && DocumentManager.Instance.DocumentViews.push(this);
     }
@@ -113,7 +112,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     @action
     componentDidUpdate() {
         this._dropDisposer && this._dropDisposer();
-        this._mainCont.current && (this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, { handlers: { drop: this.drop.bind(this) } }));
+        this._mainCont.current && (this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this)));
     }
 
     @action
@@ -132,12 +131,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             dragData.moveDocument = this.Document.onDragStart ? undefined : this.props.moveDocument;
             dragData.applyAsTemplate = applyAsTemplate;
             dragData.dragDivName = this.props.dragDivName;
-            DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, {
-                handlers: {
-                    dragComplete: action((emptyFunction))
-                },
-                hideSource: !dropAction && !this.Document.onDragStart
-            });
+            DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, { hideSource: !dropAction && !this.Document.onDragStart });
         }
     }
 
@@ -190,7 +184,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     buttonClick = async (altKey: boolean, ctrlKey: boolean) => {
         const maximizedDocs = await DocListCastAsync(this.Document.maximizedDocs);
         const summarizedDocs = await DocListCastAsync(this.Document.summarizedDocs);
-        const linkDocs = LinkManager.Instance.getAllRelatedLinks(this.props.Document);
+        const linkDocs = DocListCast(this.props.Document.links);
         let expandedDocs: Doc[] = [];
         expandedDocs = maximizedDocs ? [...maximizedDocs, ...expandedDocs] : expandedDocs;
         expandedDocs = summarizedDocs ? [...summarizedDocs, ...expandedDocs] : expandedDocs;
@@ -324,23 +318,24 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     @undoBatch
     @action
     drop = async (e: Event, de: DragManager.DropEvent) => {
-        if (de.data instanceof DragManager.AnnotationDragData) {
+        if (de.complete.annoDragData) {
             /// this whole section for handling PDF annotations looks weird.  Need to rethink this to make it cleaner
             e.stopPropagation();
-            (de.data as any).linkedToDoc = true;
+            de.complete.annoDragData.linkedToDoc = true;
 
-            DocUtils.MakeLink({ doc: de.data.annotationDocument }, { doc: this.props.Document, ctx: this.props.ContainingCollectionDoc }, `Link from ${StrCast(de.data.annotationDocument.title)}`);
+            DocUtils.MakeLink({ doc: de.complete.annoDragData.annotationDocument }, { doc: this.props.Document, ctx: this.props.ContainingCollectionDoc },
+                `Link from ${StrCast(de.complete.annoDragData.annotationDocument.title)}`);
         }
-        if (de.data instanceof DragManager.DocumentDragData && de.data.applyAsTemplate) {
-            Doc.ApplyTemplateTo(de.data.draggedDocuments[0], this.props.Document, "layoutCustom");
+        if (de.complete.docDragData && de.complete.docDragData.applyAsTemplate) {
+            Doc.ApplyTemplateTo(de.complete.docDragData.draggedDocuments[0], this.props.Document, "layoutCustom");
             e.stopPropagation();
         }
-        if (de.data instanceof DragManager.LinkDragData) {
+        if (de.complete.linkDragData) {
             e.stopPropagation();
             // const docs = await SearchUtil.Search(`data_l:"${destDoc[Id]}"`, true);
             // const views = docs.map(d => DocumentManager.Instance.getDocumentView(d)).filter(d => d).map(d => d as DocumentView);
-            de.data.linkSourceDocument !== this.props.Document &&
-                (de.data.linkDocument = DocUtils.MakeLink({ doc: de.data.linkSourceDocument }, { doc: this.props.Document, ctx: this.props.ContainingCollectionDoc }, "in-text link being created")); // TODODO this is where in text links get passed
+            de.complete.linkDragData.linkSourceDocument !== this.props.Document &&
+                (de.complete.linkDragData.linkDocument = DocUtils.MakeLink({ doc: de.complete.linkDragData.linkSourceDocument }, { doc: this.props.Document, ctx: this.props.ContainingCollectionDoc }, "in-text link being created")); // TODODO this is where in text links get passed
         }
     }
 
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index ce1c468ad..c56fde186 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -39,7 +39,7 @@ export interface FieldViewProps {
     addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => boolean;
     pinToPres: (document: Doc) => void;
     removeDocument?: (document: Doc) => boolean;
-    moveDocument?: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
+    moveDocument?: (document: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => boolean;
     ScreenToLocalTransform: () => Transform;
     active: (outsideReaction?: boolean) => boolean;
     whenActiveChanged: (isActive: boolean) => void;
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 7d404b28f..af09e57f4 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -246,22 +246,22 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
     protected createDropTarget = (ele: HTMLDivElement) => {
         this._proseRef = ele;
         this.dropDisposer && this.dropDisposer();
-        ele && (this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } }));
+        ele && (this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this)));
     }
 
     @undoBatch
     @action
     drop = async (e: Event, de: DragManager.DropEvent) => {
-        if (de.data instanceof DragManager.DocumentDragData) {
-            const draggedDoc = de.data.draggedDocuments.length && de.data.draggedDocuments[0];
+        if (de.complete.docDragData) {
+            const draggedDoc = de.complete.docDragData.draggedDocuments.length && de.complete.docDragData.draggedDocuments[0];
             // replace text contents whend dragging with Alt
-            if (draggedDoc && draggedDoc.type === DocumentType.TEXT && !Doc.AreProtosEqual(draggedDoc, this.props.Document) && de.mods === "AltKey") {
+            if (draggedDoc && draggedDoc.type === DocumentType.TEXT && !Doc.AreProtosEqual(draggedDoc, this.props.Document) && de.altKey) {
                 if (draggedDoc.data instanceof RichTextField) {
                     Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new RichTextField(draggedDoc.data.Data, draggedDoc.data.Text);
                     e.stopPropagation();
                 }
                 // apply as template when dragging with Meta
-            } else if (draggedDoc && draggedDoc.type === DocumentType.TEXT && !Doc.AreProtosEqual(draggedDoc, this.props.Document) && de.mods === "MetaKey") {
+            } else if (draggedDoc && draggedDoc.type === DocumentType.TEXT && !Doc.AreProtosEqual(draggedDoc, this.props.Document) && de.metaKey) {
                 draggedDoc.isTemplateDoc = true;
                 let newLayout = Doc.Layout(draggedDoc);
                 if (typeof (draggedDoc.layout) === "string") {
@@ -272,8 +272,8 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
                 this.Document.layoutKey = "layoutCustom";
                 e.stopPropagation();
                 // embed document when dragging with a userDropAction or an embedDoc flag set
-            } else if (de.data.userDropAction || de.data.embedDoc) {
-                const target = de.data.droppedDocuments[0];
+            } else if (de.complete.docDragData.userDropAction || de.complete.docDragData.embedDoc) {
+                const target = de.complete.docDragData.droppedDocuments[0];
                 // const link = DocUtils.MakeLink({ doc: this.dataDoc, ctx: this.props.ContainingCollectionDoc }, { doc: target }, "Embedded Doc:" + target.title);
                 // if (link) {
                 target.fitToBox = true;
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 4b3da3dae..f6aa47f15 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -67,18 +67,18 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
 
     protected createDropTarget = (ele: HTMLDivElement) => {
         this._dropDisposer && this._dropDisposer();
-        ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } }));
+        ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this)));
     }
 
     @undoBatch
     @action
     drop = (e: Event, de: DragManager.DropEvent) => {
-        if (de.data instanceof DragManager.DocumentDragData) {
-            if (de.mods === "AltKey" && de.data.draggedDocuments.length && de.data.draggedDocuments[0].data instanceof ImageField) {
-                Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new ImageField(de.data.draggedDocuments[0].data.url);
+        if (de.complete.docDragData) {
+            if (de.altKey && de.complete.docDragData.draggedDocuments.length && de.complete.docDragData.draggedDocuments[0].data instanceof ImageField) {
+                Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new ImageField(de.complete.docDragData.draggedDocuments[0].data.url);
                 e.stopPropagation();
             }
-            de.mods === "MetaKey" && de.data.droppedDocuments.forEach(action((drop: Doc) => {
+            de.metaKey && de.complete.docDragData.droppedDocuments.forEach(action((drop: Doc) => {
                 this.extensionDoc && Doc.AddDocToList(Doc.GetProto(this.extensionDoc), "Alternates", drop);
                 e.stopPropagation();
             }));
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index b114cd7ed..c85350264 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -409,6 +409,7 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
         if ((this.Document.scale || 1) !== 1) return;
         if ((e.button !== 0 || e.altKey) && this.active(true)) {
             this._setPreviewCursor && this._setPreviewCursor(e.clientX, e.clientY, true);
+            e.stopPropagation();
         }
         this._marqueeing = false;
         if (!e.altKey && e.button === 0 && this.active(true)) {
@@ -559,14 +560,9 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
         const targetDoc = Docs.Create.TextDocument({ width: 200, height: 200, title: "Note linked to " + this.props.Document.title });
         const annotationDoc = this.highlight("rgba(146, 245, 95, 0.467)"); // yellowish highlight color when dragging out a text selection
         if (annotationDoc) {
-            const dragData = new DragManager.AnnotationDragData(this.props.Document, annotationDoc, targetDoc);
-            DragManager.StartAnnotationDrag([ele], dragData, e.pageX, e.pageY, {
-                handlers: {
-                    dragComplete: () => !(dragData as any).linkedToDoc &&
-                        DocUtils.MakeLink({ doc: annotationDoc }, { doc: dragData.dropDocument, ctx: dragData.targetContext }, `Annotation from ${this.Document.title}`, "link from PDF")
-
-                },
-                hideSource: false
+            DragManager.StartPdfAnnoDrag([ele], new DragManager.PdfAnnoDragData(this.props.Document, annotationDoc, targetDoc), e.pageX, e.pageY, {
+                dragComplete: e => !e.aborted && e.annoDragData && !e.annoDragData.linkedToDoc &&
+                    DocUtils.MakeLink({ doc: annotationDoc }, { doc: e.annoDragData.dropDocument, ctx: e.annoDragData.targetContext }, `Annotation from ${this.Document.title}`, "link from PDF")
             });
         }
     }
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index fe21c29c3..515c248f7 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -12,13 +12,11 @@ import { Utils } from '../../../Utils';
 import { Docs } from '../../documents/Documents';
 import { SetupDrag } from '../../util/DragManager';
 import { SearchUtil } from '../../util/SearchUtil';
-import { MainView } from '../MainView';
 import { FilterBox } from './FilterBox';
 import "./FilterBox.scss";
 import "./SearchBox.scss";
 import { SearchItem } from './SearchItem';
 import { IconBar } from './IconBar';
-import { string } from 'prop-types';
 
 library.add(faTimes);
 
diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx
index cdb6dcf7c..8cd4b3caa 100644
--- a/src/client/views/search/SearchItem.tsx
+++ b/src/client/views/search/SearchItem.tsx
@@ -4,14 +4,13 @@ import { faCaretUp, faChartBar, faFile, faFilePdf, faFilm, faFingerprint, faGlob
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { action, computed, observable, runInAction } from "mobx";
 import { observer } from "mobx-react";
-import { Doc } from "../../../new_fields/Doc";
+import { Doc, DocListCast } from "../../../new_fields/Doc";
 import { Id } from "../../../new_fields/FieldSymbols";
 import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
 import { emptyFunction, returnEmptyString, returnFalse, returnOne, Utils, emptyPath } from "../../../Utils";
 import { DocumentType } from "../../documents/DocumentTypes";
 import { DocumentManager } from "../../util/DocumentManager";
 import { DragManager, SetupDrag } from "../../util/DragManager";
-import { LinkManager } from "../../util/LinkManager";
 import { SearchUtil } from "../../util/SearchUtil";
 import { Transform } from "../../util/Transform";
 import { SEARCH_THUMBNAIL_SIZE } from "../../views/globalCssVariables.scss";
@@ -211,7 +210,7 @@ export class SearchItem extends React.Component<SearchItemProps> {
     }
 
     @computed
-    get linkCount() { return LinkManager.Instance.getAllRelatedLinks(this.props.doc).length; }
+    get linkCount() { return DocListCast(this.props.doc.links).length; }
 
     @action
     pointerDown = (e: React.PointerEvent) => { e.preventDefault(); e.button === 0 && SearchBox.Instance.openSearch(e); }
@@ -267,10 +266,7 @@ export class SearchItem extends React.Component<SearchItemProps> {
     onPointerDown = (e: React.PointerEvent<HTMLDivElement>) => {
         e.stopPropagation();
         const doc = Doc.IsPrototype(this.props.doc) ? Doc.MakeDelegate(this.props.doc) : this.props.doc;
-        DragManager.StartDocumentDrag([e.currentTarget], new DragManager.DocumentDragData([doc]), e.clientX, e.clientY, {
-            handlers: { dragComplete: emptyFunction },
-            hideSource: false,
-        });
+        DragManager.StartDocumentDrag([e.currentTarget], new DragManager.DocumentDragData([doc]), e.clientX, e.clientY);
     }
 
     render() {
-- 
cgit v1.2.3-70-g09d2


From eead07720f1f196e9ee81cd44b26759dc44e04f9 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 12 Dec 2019 21:00:36 -0500
Subject: fixed warnings.  Limited the size of pasted text boxes

---
 src/client/documents/Documents.ts           |  1 +
 src/client/util/DragManager.ts              | 14 ++++++-------
 src/client/views/DocumentButtonBar.tsx      |  2 +-
 src/client/views/PreviewCursor.tsx          | 32 +++++++++++------------------
 src/client/views/ScriptBox.tsx              |  2 +-
 src/client/views/linking/LinkMenuGroup.tsx  |  2 +-
 src/client/views/nodes/FormattedTextBox.tsx | 11 +++++++---
 7 files changed, 30 insertions(+), 34 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 5d5bdfcbd..7df08c7e6 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -112,6 +112,7 @@ export interface DocumentOptions {
     dropConverter?: ScriptField; // script to run when documents are dropped on this Document.
     strokeWidth?: number;
     color?: string;
+    limitHeight?:number; // maximum height for newly created (eg, from pasting) text documents
     // [key: string]: Opt<Field>;
 }
 
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 95529311a..326262895 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -101,10 +101,10 @@ export namespace DragManager {
     export class DragCompleteEvent {
         constructor(aborted: boolean, dragData: { [id: string]: any }) {
             this.aborted = aborted;
-            this.docDragData = dragData instanceof DocumentDragData ? dragData as DocumentDragData : undefined;
-            this.annoDragData = dragData instanceof PdfAnnoDragData ? dragData as PdfAnnoDragData : undefined;
-            this.linkDragData = dragData instanceof LinkDragData ? dragData as LinkDragData : undefined;
-            this.columnDragData = dragData instanceof ColumnDragData ? dragData as ColumnDragData : undefined;
+            this.docDragData = dragData instanceof DocumentDragData ? dragData : undefined;
+            this.annoDragData = dragData instanceof PdfAnnoDragData ? dragData : undefined;
+            this.linkDragData = dragData instanceof LinkDragData ? dragData : undefined;
+            this.columnDragData = dragData instanceof ColumnDragData ? dragData : undefined;
         }
         aborted: boolean;
         docDragData?: DocumentDragData;
@@ -140,14 +140,12 @@ export namespace DragManager {
         linkSourceDocument: Doc;
         dontClearTextBox?: boolean;
         linkDocument?: Doc;
-        [id: string]: any;
     }
     export class ColumnDragData {
         constructor(colKey: SchemaHeaderField) {
             this.colKey = colKey;
         }
         colKey: SchemaHeaderField;
-        [id: string]: any;
     }
     // used by PDFs to conditionally (if the drop completes) create a text annotation when dragging from the PDF toolbar when a text region has been selected.
     // this is pretty clunky and should be rethought out using linkDrag or DocumentDrag
@@ -216,7 +214,7 @@ export namespace DragManager {
 
     // drag links and drop link targets (aliasing them if needed)
     export async function StartLinkTargetsDrag(dragEle: HTMLElement, downX: number, downY: number, sourceDoc: Doc, specificLinks?: Doc[]) {
-        let draggedDocs = (specificLinks ? specificLinks : DocListCast(sourceDoc.links)).map(link => LinkManager.Instance.getOppositeAnchor(link, sourceDoc)).filter(l => l) as Doc[];
+        const draggedDocs = (specificLinks ? specificLinks : DocListCast(sourceDoc.links)).map(link => LinkManager.Instance.getOppositeAnchor(link, sourceDoc)).filter(l => l) as Doc[];
 
         if (draggedDocs.length) {
             const moddrag: Doc[] = [];
@@ -225,7 +223,7 @@ export namespace DragManager {
                 if (doc) moddrag.push(doc);
             }
 
-            let dragData = new DragManager.DocumentDragData(moddrag.length ? moddrag : draggedDocs);
+            const dragData = new DragManager.DocumentDragData(moddrag.length ? moddrag : draggedDocs);
             dragData.moveDocument = (doc: Doc, targetCollection: Doc | undefined, addDocument: (doc: Doc) => boolean): boolean => {
                 const document = SelectionManager.SelectedDocuments()[0];
                 document && document.props.removeDocument && document.props.removeDocument(doc);
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index f014c1329..58728ab7f 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -114,7 +114,7 @@ export class DocumentButtonBar extends React.Component<{ views: DocumentView[],
                         const proto = Doc.GetProto(linkDoc);
                         proto.sourceContext = this.props.views[0].props.ContainingCollectionDoc;
 
-                        const anchor2Title = linkDoc.anchor2 instanceof Doc ? StrCast((linkDoc.anchor2 as Doc).title) : "-untitled-";
+                        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) {
                             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/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index fd0287b6c..8adcf1220 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -5,7 +5,7 @@ import * as React from 'react';
 import "./PreviewCursor.scss";
 import { Docs } from '../documents/Documents';
 // import { Transform } from 'prosemirror-transform';
-import { Doc } from '../../new_fields/Doc';
+import { Doc, HeightSym } from '../../new_fields/Doc';
 import { Transform } from "../util/Transform";
 import { TraceMobx } from '../../new_fields/util';
 
@@ -27,61 +27,53 @@ export class PreviewCursor extends React.Component<{}> {
         if (PreviewCursor.Visible) {
             if (e.clipboardData) {
                 const newPoint = PreviewCursor._getTransform().transformPoint(PreviewCursor._clickPoint[0], PreviewCursor._clickPoint[1]);
-                runInAction(() => { PreviewCursor.Visible = false; });
-
+                runInAction(() => PreviewCursor.Visible = false);
 
                 if (e.clipboardData.getData("text/plain") !== "") {
 
                     // tests for youtube and makes video document
                     if (e.clipboardData.getData("text/plain").indexOf("www.youtube.com/watch") !== -1) {
                         const url = e.clipboardData.getData("text/plain").replace("youtube.com/watch?v=", "youtube.com/embed/");
-                        PreviewCursor._addDocument(Docs.Create.VideoDocument(url, {
+                        return PreviewCursor._addDocument(Docs.Create.VideoDocument(url, {
                             title: url, width: 400, height: 315,
                             nativeWidth: 600, nativeHeight: 472.5,
                             x: newPoint[0], y: newPoint[1]
                         }));
-                        return;
                     }
 
                     // tests for URL and makes web document
                     const re: any = /^https?:\/\//g;
                     if (re.test(e.clipboardData.getData("text/plain"))) {
                         const url = e.clipboardData.getData("text/plain");
-                        PreviewCursor._addDocument(Docs.Create.WebDocument(url, {
-                            title: url, width: 300, height: 300,
+                        return PreviewCursor._addDocument(Docs.Create.WebDocument(url, {
+                            title: url, width: 500, height: 300,
                             // nativeWidth: 300, nativeHeight: 472.5,
                             x: newPoint[0], y: newPoint[1]
                         }));
-                        return;
                     }
 
                     // creates text document
-                    const newBox = Docs.Create.TextDocument({
-                        width: 200, height: 100,
+                    return PreviewCursor._addLiveTextDoc(Docs.Create.TextDocument({
+                        width: 500,
+                        autoHeight: true,
                         x: newPoint[0],
                         y: newPoint[1],
+                        limitHeight: 400,
                         title: "-pasted text-"
-                    });
-
-                    newBox.proto!.autoHeight = true;
-                    PreviewCursor._addLiveTextDoc(newBox);
-                    return;
+                    }));
                 }
                 //pasting in images
                 if (e.clipboardData.getData("text/html") !== "" && e.clipboardData.getData("text/html").includes("<img src=")) {
                     const re: any = /<img src="(.*?)"/g;
                     const arr: any[] = re.exec(e.clipboardData.getData("text/html"));
 
-                    const img: Doc = Docs.Create.ImageDocument(
+                    return PreviewCursor._addDocument(Docs.Create.ImageDocument(
                         arr[1], {
                         width: 300, title: arr[1],
                         x: newPoint[0],
                         y: newPoint[1],
-                    });
-                    PreviewCursor._addDocument(img);
-                    return;
+                    }));
                 }
-
             }
         }
     }
diff --git a/src/client/views/ScriptBox.tsx b/src/client/views/ScriptBox.tsx
index 956e83238..ded2329b4 100644
--- a/src/client/views/ScriptBox.tsx
+++ b/src/client/views/ScriptBox.tsx
@@ -113,7 +113,7 @@ export class ScriptBox extends React.Component<ScriptBoxProps> {
                 return;
             }
 
-            var div = document.createElement("div");
+            const div = document.createElement("div");
             div.style.width = "90";
             div.style.height = "20";
             div.style.background = "gray";
diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx
index da56435f2..abd17ec4d 100644
--- a/src/client/views/linking/LinkMenuGroup.tsx
+++ b/src/client/views/linking/LinkMenuGroup.tsx
@@ -46,7 +46,7 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
             document.removeEventListener("pointermove", this.onLinkButtonMoved);
             document.removeEventListener("pointerup", this.onLinkButtonUp);
 
-            let targets = this.props.group.map(l => LinkManager.Instance.getOppositeAnchor(l, this.props.sourceDoc)).filter(d => d) as Doc[];
+            const targets = this.props.group.map(l => LinkManager.Instance.getOppositeAnchor(l, this.props.sourceDoc)).filter(d => d) as Doc[];
             DragManager.StartLinkTargetsDrag(this._drag.current!, e.x, e.y, this.props.sourceDoc, targets);
         }
         e.stopPropagation();
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index af09e57f4..a298fd6af 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -630,7 +630,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
             { fireImmediately: true }
         );
 
-        setTimeout(() => this.tryUpdateHeight(), 0);
+        setTimeout(() => this.tryUpdateHeight(NumCast(this.layoutDoc.limitHeight, 0)));
     }
 
     pushToGoogleDoc = async () => {
@@ -1087,10 +1087,15 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
     }
 
     @action
-    tryUpdateHeight() {
-        const scrollHeight = this._ref.current?.scrollHeight;
+    tryUpdateHeight(limitHeight?: number) {
+        let scrollHeight = this._ref.current?.scrollHeight;
         if (!this.layoutDoc.animateToPos && this.layoutDoc.autoHeight && scrollHeight &&
             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
+            if (limitHeight && scrollHeight > limitHeight) {
+                scrollHeight = limitHeight;
+                this.layoutDoc.limitHeight = undefined;
+                this.layoutDoc.autoHeight = false;
+            }
             const nh = this.Document.isTemplateField ? 0 : NumCast(this.dataDoc.nativeHeight, 0);
             const dh = NumCast(this.layoutDoc.height, 0);
             const newHeight = Math.max(10, (nh ? dh / nh * scrollHeight : scrollHeight) + (this.props.ChromeHeight ? this.props.ChromeHeight() : 0));
-- 
cgit v1.2.3-70-g09d2

