From eed7f32cda030d9ef2d8aaae168c11832460a653 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sat, 25 Sep 2021 16:09:21 -0400
Subject: fix for some youtube urls that didn't upload.

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

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

diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index cb8b55cb2..e662f3ddf 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -356,7 +356,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
 
                     const batch = UndoManager.StartBatch("youtube upload");
                     const generatedDocuments: Doc[] = [];
-                    this.slowLoadDocuments((uriList || text).split("v=")[1], options, generatedDocuments, text, completed, e.clientX, e.clientY, addDocument).then(batch.end);
+                    this.slowLoadDocuments((uriList || text).split("v=")[1].split("&")[0], options, generatedDocuments, text, completed, e.clientX, e.clientY, addDocument).then(batch.end);
 
                     return;
                 }
-- 
cgit v1.2.3-70-g09d2


From 3e0a9ff2c708891a15a681e5af549caf0b18ff60 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 26 Oct 2021 12:06:36 -0400
Subject: when document is in lightbox view and a link is followed to its
 sidebar, we no longer reopen the document in its context.  fixed selecting
 annotations on web/pdf.  don't change document height when shown in a
 linkPreview.  webBox fixes for pages with scripts. fixed range bounds when
 clicking on web text

several fixes to web pages on server and client.
client webbox allows clicks on divs with onclick instead of doing selection
       also hacky fix so that google search url doesn't keep expanding by removing 'q=' regions
       also added prevent/allow script menu item
server grabs all html and hides id="google.." which are ads.
      also rewrites hrefs starting with http to route through corsProxy
      also removes target=_blank tags to prevent pages from opening outside of dash.
     also cleaned up routes and comments
     also when not logged in, references to anything in dash domain route to /home
---
 package-lock.json                                  |   5 +
 package.json                                       |   1 +
 src/Utils.ts                                       |   2 +-
 src/client/util/LinkManager.ts                     |   6 +-
 src/client/views/DocumentDecorations.tsx           |   4 +-
 src/client/views/InkControlPtHandles.tsx           |  16 +--
 src/client/views/InkStrokeProperties.ts            |  72 +++++++------
 src/client/views/InkTangentHandles.tsx             |   6 +-
 src/client/views/InkingStroke.tsx                  |   9 +-
 src/client/views/collections/CollectionSubView.tsx |   5 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   4 +-
 src/client/views/nodes/DocumentView.tsx            |   6 +-
 src/client/views/nodes/WebBox.tsx                  |  77 +++++++++++---
 src/server/server_Initialization.ts                | 115 ++++++++++++---------
 14 files changed, 206 insertions(+), 122 deletions(-)

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

diff --git a/package-lock.json b/package-lock.json
index c1dd8506f..3aff3a549 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9264,6 +9264,11 @@
             "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==",
             "optional": true
         },
+        "memorystream": {
+            "version": "0.3.1",
+            "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz",
+            "integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI="
+        },
         "meow": {
             "version": "3.7.0",
             "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
diff --git a/package.json b/package.json
index 5d10c0d54..99c818062 100644
--- a/package.json
+++ b/package.json
@@ -194,6 +194,7 @@
         "libxmljs": "^0.19.7",
         "lodash": "^4.17.15",
         "material-ui": "^0.20.2",
+        "memorystream": "^0.3.1",
         "mobile-detect": "^1.4.4",
         "mobx": "^5.15.7",
         "mobx-react": "^5.4.4",
diff --git a/src/Utils.ts b/src/Utils.ts
index 53182cc9c..bfb29fe8d 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -599,7 +599,7 @@ export function getWordAtPoint(elem: any, x: number, y: number): string | undefi
         range.selectNodeContents(elem);
         var currentPos = 0;
         const endPos = range.endOffset;
-        while (currentPos + 1 < endPos) {
+        while (currentPos + 1 <= endPos) {
             range.setStart(elem, currentPos);
             range.setEnd(elem, currentPos + 1);
             const rangeRect = range.getBoundingClientRect();
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index 90a8f2737..62b13e2c6 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -234,8 +234,10 @@ export class LinkManager {
                         setTimeout(LightboxView.Next);
                         finished?.();
                     } else {
-                        const containerDoc = Cast(target.annotationOn, Doc, null) || target;
-                        const targetContext = Cast(containerDoc?.context, Doc, null);
+                        const containerAnnoDoc = Cast(target.annotationOn, Doc, null);
+                        const containerDoc = containerAnnoDoc || target;
+                        const containerDocContext = Cast(containerDoc?.context, Doc, null);
+                        const targetContext = LightboxView.LightboxDoc ? containerAnnoDoc || containerDocContext : containerDocContext;
                         const targetNavContext = !Doc.AreProtosEqual(targetContext, currentContext) ? targetContext : undefined;
                         DocumentManager.Instance.jumpToDocument(target, zoom, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, "lightbox"), finished), targetNavContext, linkDoc, undefined, sourceDoc, finished);
                     }
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 17a81149c..5b44a0552 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -195,11 +195,13 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
     @action
     onRotateDown = (e: React.PointerEvent): void => {
         this._rotateUndo = UndoManager.StartBatch("rotatedown");
+        const pt = { x: (this.Bounds.x + this.Bounds.r) / 2, y: (this.Bounds.y + this.Bounds.b) / 2 };
         setupMoveUpEvents(this, e,
             (e: PointerEvent, down: number[], delta: number[]) => {
                 const movement = { X: delta[0], Y: e.clientY - down[1] };
                 const angle = Math.max(1, Math.abs(movement.Y / 10));
-                InkStrokeProperties.Instance?.rotateInk(2 * movement.X / angle * (Math.PI / 180));
+                const selectedInk = SelectionManager.Views().filter(i => Document(i.rootDoc).type === DocumentType.INK);
+                InkStrokeProperties.Instance?.rotateInk(selectedInk, 2 * movement.X / angle * (Math.PI / 180), pt);
                 return false;
             },
             () => {
diff --git a/src/client/views/InkControlPtHandles.tsx b/src/client/views/InkControlPtHandles.tsx
index 0644488b3..73de4a3e0 100644
--- a/src/client/views/InkControlPtHandles.tsx
+++ b/src/client/views/InkControlPtHandles.tsx
@@ -3,18 +3,20 @@ import { action, observable } from "mobx";
 import { observer } from "mobx-react";
 import { Doc } from "../../fields/Doc";
 import { ControlPoint, InkData, PointData } from "../../fields/InkField";
+import { List } from "../../fields/List";
 import { listSpec } from "../../fields/Schema";
 import { Cast } from "../../fields/Types";
 import { setupMoveUpEvents } from "../../Utils";
 import { Transform } from "../util/Transform";
 import { UndoManager } from "../util/UndoManager";
 import { Colors } from "./global/globalEnums";
-import { InkStrokeProperties } from "./InkStrokeProperties";
-import { List } from "../../fields/List";
 import { InkingStroke } from "./InkingStroke";
+import { InkStrokeProperties } from "./InkStrokeProperties";
+import { DocumentView } from "./nodes/DocumentView";
 
 export interface InkControlProps {
     inkDoc: Doc;
+    inkView: DocumentView;
     inkCtrlPoints: InkData;
     screenCtrlPoints: InkData;
     screenSpaceLineWidth: number;
@@ -51,12 +53,12 @@ export class InkControlPtHandles extends React.Component<InkControlProps> {
             setupMoveUpEvents(this, e,
                 action((e: PointerEvent, down: number[], delta: number[]) => {
                     if (!this.controlUndo) this.controlUndo = UndoManager.StartBatch("drag ink ctrl pt");
-                    InkStrokeProperties.Instance?.moveControlPtHandle(delta[0] * screenScale, delta[1] * screenScale, controlIndex);
+                    InkStrokeProperties.Instance?.moveControlPtHandle(this.props.inkView, delta[0] * screenScale, delta[1] * screenScale, controlIndex);
                     return false;
                 }),
                 action(() => {
                     if (this.controlUndo) {
-                        InkStrokeProperties.Instance?.snapControl(this.props.inkDoc, controlIndex);
+                        InkStrokeProperties.Instance?.snapControl(this.props.inkView, controlIndex);
                     }
                     this.controlUndo?.end();
                     this.controlUndo = undefined;
@@ -71,11 +73,11 @@ export class InkControlPtHandles extends React.Component<InkControlProps> {
                         } else {
                             if (brokenIndices?.includes(equivIndex)) {
                                 if (!this.controlUndo) this.controlUndo = UndoManager.StartBatch("make smooth");
-                                InkStrokeProperties.Instance?.snapHandleTangent(equivIndex, handleIndexA, handleIndexB);
+                                InkStrokeProperties.Instance?.snapHandleTangent(this.props.inkView, equivIndex, handleIndexA, handleIndexB);
                             }
                             if (equivIndex !== controlIndex && brokenIndices?.includes(controlIndex)) {
                                 if (!this.controlUndo) this.controlUndo = UndoManager.StartBatch("make smooth");
-                                InkStrokeProperties.Instance?.snapHandleTangent(controlIndex, handleIndexA, handleIndexB);
+                                InkStrokeProperties.Instance?.snapHandleTangent(this.props.inkView, controlIndex, handleIndexA, handleIndexB);
                             }
                         }
                         this.controlUndo?.end();
@@ -98,7 +100,7 @@ export class InkControlPtHandles extends React.Component<InkControlProps> {
     @action
     onDelete = (e: KeyboardEvent) => {
         if (["-", "Backspace", "Delete"].includes(e.key)) {
-            InkStrokeProperties.Instance?.deletePoints();
+            InkStrokeProperties.Instance?.deletePoints(this.props.inkView);
             e.stopPropagation();
         }
     }
diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts
index ee30caa3d..33e25bbbb 100644
--- a/src/client/views/InkStrokeProperties.ts
+++ b/src/client/views/InkStrokeProperties.ts
@@ -1,16 +1,15 @@
 import { Bezier } from "bezier-js";
-import { action, computed, observable, reaction } from "mobx";
-import { Doc } from "../../fields/Doc";
-import { Document } from "../../fields/documentSchemas";
+import { action, observable, reaction } from "mobx";
+import { Doc, Opt } from "../../fields/Doc";
 import { InkData, InkField, InkTool, PointData } from "../../fields/InkField";
 import { List } from "../../fields/List";
 import { listSpec } from "../../fields/Schema";
 import { Cast, NumCast } from "../../fields/Types";
 import { DocumentType } from "../documents/DocumentTypes";
 import { CurrentUserUtils } from "../util/CurrentUserUtils";
-import { SelectionManager } from "../util/SelectionManager";
 import { undoBatch } from "../util/UndoManager";
 import { InkingStroke } from "./InkingStroke";
+import { DocumentView } from "./nodes/DocumentView";
 
 export class InkStrokeProperties {
     static Instance: InkStrokeProperties | undefined;
@@ -25,21 +24,16 @@ export class InkStrokeProperties {
         reaction(() => CurrentUserUtils.SelectedTool, tool => (tool !== InkTool.None) && (this._controlButton = false));
     }
 
-    @computed get selectedInk() {
-        const inks = SelectionManager.Views().filter(i => Document(i.rootDoc).type === DocumentType.INK);
-        return inks.length ? inks : undefined;
-    }
-
     /**
      * Helper function that enables other functions to be applied to a particular ink instance.
      * @param func The inputted function.
      * @param requireCurrPoint Indicates whether the current selected point is needed.
      */
-    applyFunction = (func: (doc: Doc, ink: InkData, ptsXscale: number, ptsYscale: number) => { X: number, Y: number }[] | undefined, requireCurrPoint: boolean = false) => {
+    applyFunction = (strokes: Opt<DocumentView | DocumentView[]>, func: (view: DocumentView, ink: InkData, ptsXscale: number, ptsYscale: number, inkStrokeWidth: number) => { X: number, Y: number }[] | undefined, requireCurrPoint: boolean = false) => {
         var appliedFunc = false;
-        this.selectedInk?.forEach(action(inkView => {
-            if (this.selectedInk?.length === 1 && (!requireCurrPoint || this._currentPoint !== -1)) {
-                const doc = Document(inkView.rootDoc);
+        (strokes instanceof DocumentView ? [strokes] : strokes)?.forEach(action(inkView => {
+            if (!requireCurrPoint || this._currentPoint !== -1) {
+                const doc = inkView.rootDoc;
                 if (doc.type === DocumentType.INK && doc.width && doc.height) {
                     const ink = Cast(doc.data, InkField)?.inkData;
                     if (ink) {
@@ -47,7 +41,7 @@ export class InkStrokeProperties {
                         const oldYrange = (ys => ({ coord: NumCast(doc.y), min: Math.min(...ys), max: Math.max(...ys) }))(ink.map(p => p.Y));
                         const ptsXscale = ((NumCast(doc._width) - NumCast(doc.strokeWidth)) / ((oldXrange.max - oldXrange.min) || 1)) || 1;
                         const ptsYscale = ((NumCast(doc._height) - NumCast(doc.strokeWidth)) / ((oldYrange.max - oldYrange.min) || 1)) || 1;
-                        const newPoints = func(doc, ink, ptsXscale, ptsYscale);
+                        const newPoints = func(inkView, ink, ptsXscale, ptsYscale, NumCast(doc.strokeWidth));
                         if (newPoints) {
                             const newXrange = (xs => ({ min: Math.min(...xs), max: Math.max(...xs) }))(newPoints.map(p => p.X));
                             const newYrange = (ys => ({ min: Math.min(...ys), max: Math.max(...ys) }))(newPoints.map(p => p.Y));
@@ -73,8 +67,9 @@ export class InkStrokeProperties {
      */
     @undoBatch
     @action
-    addPoints = (t: number, i: number, controls: { X: number, Y: number }[]) => {
-        this.applyFunction((doc: Doc, ink: InkData) => {
+    addPoints = (inkView: DocumentView, t: number, i: number, controls: { X: number, Y: number }[]) => {
+        this.applyFunction(inkView, (view: DocumentView, ink: InkData) => {
+            const doc = view.rootDoc;
             const array = [controls[i], controls[i + 1], controls[i + 2], controls[i + 3]];
             const newsegs = new Bezier(array.map(p => ({ x: p.X, y: p.Y }))).split(t);
             const splicepts = [...newsegs.left.points, ...newsegs.right.points];
@@ -143,9 +138,10 @@ export class InkStrokeProperties {
      */
     @undoBatch
     @action
-    deletePoints = () => this.applyFunction((doc: Doc, ink: InkData) => {
+    deletePoints = (inkView: DocumentView) => this.applyFunction(inkView, (view: DocumentView, ink: InkData) => {
+        const doc = view.rootDoc;
         const newPoints: { X: number, Y: number }[] = [];
-        const toRemove = Math.floor(((this._currentPoint + 2) / 4));
+        const toRemove = Math.floor((this._currentPoint + 2) / 4);
         const last = this._currentPoint === ink.length - 1;
         for (let i = 0; i < ink.length; i++) {
             if (Math.floor((i + 2) / 4) !== toRemove && (toRemove !== 0 || i > 3)) {
@@ -164,17 +160,22 @@ export class InkStrokeProperties {
      */
     @undoBatch
     @action
-    rotateInk = (angle: number) => {
-        this.applyFunction((doc: Doc, ink: InkData, xScale: number, yScale: number) => {
-            const oldXrange = (xs => ({ coord: NumCast(doc.x), min: Math.min(...xs), max: Math.max(...xs) }))(ink.map(p => p.X));
-            const oldYrange = (ys => ({ coord: NumCast(doc.y), min: Math.min(...ys), max: Math.max(...ys) }))(ink.map(p => p.Y));
-            const centerPoint = { X: (oldXrange.min + oldXrange.max) / 2, Y: (oldYrange.min + oldYrange.max) / 2 };
+    rotateInk = (inkStrokes: DocumentView[], angle: number, scrpt: { x: number, y: number }) => {
+        this.applyFunction(inkStrokes, (view: DocumentView, ink: InkData, xScale: number, yScale: number, inkStrokeWidth: number) => {
+            const oldXrangeMin = Math.min(...ink.map(p => p.X));
+            const oldYrangeMin = Math.min(...ink.map(p => p.Y));
+            const docViewCenterPt = view.screenToLocalTransform().transformPoint(scrpt.x, scrpt.y);
+            const inkCenterPt = {
+                X: (docViewCenterPt[0] - inkStrokeWidth / 2) / xScale + oldXrangeMin,
+                Y: (docViewCenterPt[1] - inkStrokeWidth / 2) / yScale + oldYrangeMin
+            };
             const newPoints = ink.map(i => {
-                const pt = { X: i.X - centerPoint.X, Y: i.Y - centerPoint.Y };
+                const pt = { X: i.X - inkCenterPt.X, Y: i.Y - inkCenterPt.Y };
                 const newX = Math.cos(angle) * pt.X - Math.sin(angle) * pt.Y * yScale / xScale;
                 const newY = Math.sin(angle) * pt.X * xScale / yScale + Math.cos(angle) * pt.Y;
-                return { X: newX + centerPoint.X, Y: newY + centerPoint.Y };
+                return { X: newX + inkCenterPt.X, Y: newY + inkCenterPt.Y };
             });
+            const doc = view.rootDoc;
             doc.rotation = NumCast(doc.rotation) + angle;
             return newPoints;
         });
@@ -185,8 +186,8 @@ export class InkStrokeProperties {
      */
     @undoBatch
     @action
-    moveControlPtHandle = (deltaX: number, deltaY: number, controlIndex: number) =>
-        this.applyFunction((doc: Doc, ink: InkData, xScale: number, yScale: number) => {
+    moveControlPtHandle = (inkView: DocumentView, deltaX: number, deltaY: number, controlIndex: number) =>
+        this.applyFunction(inkView, (view: DocumentView, ink: InkData, xScale: number, yScale: number) => {
             const order = controlIndex % 4;
             const closed = InkingStroke.IsClosed(ink);
 
@@ -235,7 +236,8 @@ export class InkStrokeProperties {
     /**
      * Handles the movement/scaling of a control point.
      */
-    snapControl = (inkDoc: Doc, controlIndex: number) => {
+    snapControl = (inkView: DocumentView, controlIndex: number) => {
+        const inkDoc = inkView.rootDoc;
         const ink = Cast(inkDoc.data, InkField)?.inkData;
         if (ink) {
             const closed = InkingStroke.IsClosed(ink);
@@ -257,8 +259,8 @@ export class InkStrokeProperties {
             const near = Math.sqrt((nearestPt.X - refPt.X) * (nearestPt.X - refPt.X) * ptsXscale * ptsXscale +
                 (nearestPt.Y - refPt.Y) * (nearestPt.Y - refPt.Y) * ptsYscale * ptsYscale);
 
-            if (near / (this.selectedInk?.lastElement().props.ScreenToLocalTransform().Scale || 1) < 10) {
-                return this.moveControlPtHandle((nearestPt.X - ink[controlIndex].X) * ptsXscale, (nearestPt.Y - ink[controlIndex].Y) * ptsYscale, controlIndex);
+            if (near / (inkView.props.ScreenToLocalTransform().Scale || 1) < 10) {
+                return this.moveControlPtHandle(inkView, (nearestPt.X - ink[controlIndex].X) * ptsXscale, (nearestPt.Y - ink[controlIndex].Y) * ptsYscale, controlIndex);
             }
         }
         return false;
@@ -269,8 +271,9 @@ export class InkStrokeProperties {
      * @param handleIndexA The handle point that retains its current position.
      * @param handleIndexB The handle point that is rotated to be 180 degrees from its opposite.
      */
-    snapHandleTangent = (controlIndex: number, handleIndexA: number, handleIndexB: number) => {
-        this.applyFunction((doc: Doc, ink: InkData) => {
+    snapHandleTangent = (inkView: DocumentView, controlIndex: number, handleIndexA: number, handleIndexB: number) => {
+        this.applyFunction(inkView, (view: DocumentView, ink: InkData) => {
+            const doc = view.rootDoc;
             const brokenIndices = Cast(doc.brokenInkIndices, listSpec("number"), []);
             const ind = brokenIndices.findIndex(value => value === controlIndex);
             if (ind !== -1) {
@@ -330,8 +333,9 @@ export class InkStrokeProperties {
      */
     @undoBatch
     @action
-    moveTangentHandle = (deltaX: number, deltaY: number, handleIndex: number, oppositeHandleIndex: number, controlIndex: number) =>
-        this.applyFunction((doc: Doc, ink: InkData, xScale: number, yScale: number) => {
+    moveTangentHandle = (inkView: DocumentView, deltaX: number, deltaY: number, handleIndex: number, oppositeHandleIndex: number, controlIndex: number) =>
+        this.applyFunction(inkView, (view: DocumentView, ink: InkData, xScale: number, yScale: number) => {
+            const doc = view.rootDoc;
             const closed = InkingStroke.IsClosed(ink);
             const oldHandlePoint = ink[handleIndex];
             const oppositeHandlePoint = ink[oppositeHandleIndex];
diff --git a/src/client/views/InkTangentHandles.tsx b/src/client/views/InkTangentHandles.tsx
index df5bebf31..f88a20448 100644
--- a/src/client/views/InkTangentHandles.tsx
+++ b/src/client/views/InkTangentHandles.tsx
@@ -10,11 +10,13 @@ import { emptyFunction, setupMoveUpEvents } from "../../Utils";
 import { Transform } from "../util/Transform";
 import { UndoManager } from "../util/UndoManager";
 import { Colors } from "./global/globalEnums";
-import { InkStrokeProperties } from "./InkStrokeProperties";
 import { InkingStroke } from "./InkingStroke";
+import { InkStrokeProperties } from "./InkStrokeProperties";
+import { DocumentView } from "./nodes/DocumentView";
 
 export interface InkHandlesProps {
     inkDoc: Doc;
+    inkView: DocumentView;
     screenCtrlPoints: InkData;
     screenSpaceLineWidth: number;
     ScreenToLocalTransform: () => Transform;
@@ -37,7 +39,7 @@ export class InkTangentHandles extends React.Component<InkHandlesProps> {
             setupMoveUpEvents(this, e, (e: PointerEvent, down: number[], delta: number[]) => {
                 if (!controlUndo) controlUndo = UndoManager.StartBatch("DocDecs move tangent");
                 if (e.altKey) this.onBreakTangent(controlIndex);
-                InkStrokeProperties.Instance?.moveTangentHandle(-delta[0] * screenScale, -delta[1] * screenScale, handleIndex, oppositeHandleIndex, controlIndex);
+                InkStrokeProperties.Instance?.moveTangentHandle(this.props.inkView, -delta[0] * screenScale, -delta[1] * screenScale, handleIndex, oppositeHandleIndex, controlIndex);
                 return false;
             }, () => {
                 controlUndo?.end();
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 752db1413..d312331d0 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -1,17 +1,17 @@
 import React = require("react");
-import { Bezier } from "bezier-js";
 import { action, IReactionDisposer, observable, reaction } from "mobx";
 import { observer } from "mobx-react";
 import { Doc } from "../../fields/Doc";
 import { documentSchema } from "../../fields/documentSchemas";
 import { InkData, InkField, InkTool } from "../../fields/InkField";
 import { makeInterface } from "../../fields/Schema";
-import { Cast, NumCast, StrCast, BoolCast } from "../../fields/Types";
+import { BoolCast, Cast, NumCast, StrCast } from "../../fields/Types";
 import { TraceMobx } from "../../fields/util";
 import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../Utils";
 import { CognitiveServices } from "../cognitive_services/CognitiveServices";
 import { CurrentUserUtils } from "../util/CurrentUserUtils";
 import { InteractionUtils } from "../util/InteractionUtils";
+import { SnappingManager } from "../util/SnappingManager";
 import { ContextMenu } from "./ContextMenu";
 import { ViewBoxBaseComponent } from "./DocComponent";
 import { Colors } from "./global/globalEnums";
@@ -20,7 +20,6 @@ import "./InkStroke.scss";
 import { InkStrokeProperties } from "./InkStrokeProperties";
 import { InkTangentHandles } from "./InkTangentHandles";
 import { FieldView, FieldViewProps } from "./nodes/FieldView";
-import { SnappingManager } from "../util/SnappingManager";
 import Color = require("color");
 
 type InkDocument = makeInterface<[typeof documentSchema]>;
@@ -83,7 +82,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
                         InkStrokeProperties.Instance && (InkStrokeProperties.Instance._currentPoint = -1);
                         this._handledClick = true;  // mark the double-click pseudo pointerevent so we can block the real mouse event from propagating to DocumentView
                     } else if (this._properties?._controlButton) {
-                        this._nearestT && this._nearestSeg !== undefined && InkStrokeProperties.Instance?.addPoints(this._nearestT, this._nearestSeg, this.inkScaledData().inkData.slice());
+                        this._nearestT && this._nearestSeg !== undefined && InkStrokeProperties.Instance?.addPoints(this.props.docViewPath().lastElement(), this._nearestT, this._nearestSeg, this.inkScaledData().inkData.slice());
                     }
                 }), this._properties?._controlButton, this._properties?._controlButton
             );
@@ -166,6 +165,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
                         StrCast(inkDoc.strokeLineJoin), StrCast(this.layoutDoc.strokeLineCap), StrCast(inkDoc.strokeBezier),
                         "none", startMarker, endMarker, StrCast(inkDoc.strokeDash), 1, 1, "", "none", 1.0, false)}
                     <InkControlPtHandles
+                        inkView={this.props.docViewPath().lastElement()}
                         inkDoc={inkDoc}
                         inkCtrlPoints={inkData}
                         screenCtrlPoints={screenHdlPts}
@@ -173,6 +173,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
                         screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth}
                         ScreenToLocalTransform={this.props.ScreenToLocalTransform} />
                     <InkTangentHandles
+                        inkView={this.props.docViewPath().lastElement()}
                         inkDoc={inkDoc}
                         screenCtrlPoints={screenHdlPts}
                         screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index e662f3ddf..5dffc65fc 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -378,7 +378,6 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
                 // }
             }
             if (uriList) {
-                console.log("Web URI = ", uriList);
                 // const existingWebDoc = await Hypothesis.findWebDoc(uriList);
                 // if (existingWebDoc) {
                 //     const alias = Doc.MakeAlias(existingWebDoc);
@@ -390,7 +389,6 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
                 //     addDocument(alias);
                 // } else 
                 {
-                    console.log("Adding ...");
                     const newDoc = Docs.Create.WebDocument(uriList.split("#annotations:")[0], {// clean hypothes.is URLs that reference a specific annotation (eg. https://en.wikipedia.org/wiki/Cartoon#annotations:t7qAeNbCEeqfG5972KR2Ig)
                         ...options,
                         title: uriList.split("#annotations:")[0],
@@ -399,8 +397,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
                         _nativeWidth: 850,
                         useCors: true
                     });
-                    console.log(" ... " + newDoc.title);
-                    console.log(" ... " + addDocument(newDoc) + " " + newDoc.title);
+                    addDocument(newDoc);
                 }
                 return;
             }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 7dcd63b80..febccbfcc 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -75,7 +75,7 @@ export type collectionFreeformViewProps = {
     scaleField?: string;
     noOverlay?: boolean; // used to suppress docs in the overlay (z) layer (ie, for minimap since overlay doesn't scale)
     engineProps?: any;
-    dontRenderDocuments?: boolean; // used for annotation overlays which need to distribute documents into different freeformviews with different mixBlendModes depending on whether they are trnasparent or not.  
+    dontRenderDocuments?: boolean; // used for annotation overlays which need to distribute documents into different freeformviews with different mixBlendModes depending on whether they are transparent or not.  
     // However, this screws up interactions since only the top layer gets events.  so we render the freeformview a 3rd time with all documents in order to get interaction events (eg., marquee) but we don't actually want to display the documents.
 };
 
@@ -1472,7 +1472,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             getContainerTransform={this.getContainerTransform}
             getTransform={this.getTransform}
             isAnnotationOverlay={this.isAnnotationOverlay}>
-            <div ref={this._marqueeRef} style={{ display: this.props.dontRenderDocuments ? "none" : undefined }}>
+            <div className="marqueeView-div" ref={this._marqueeRef} style={{ opacity: this.props.dontRenderDocuments ? 0 : undefined }}>
                 {this.layoutDoc._backgroundGridShow ? this.backgroundGrid : (null)}
                 <CollectionFreeFormViewPannableContents
                     isAnnotationOverlay={this.isAnnotationOverlay}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 1ccf38de2..949e0e168 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -813,7 +813,11 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     screenToLocal = () => this.props.ScreenToLocalTransform().translate(0, -this.headerMargin);
     contentScaling = () => this.ContentScale;
     onClickFunc = () => this.onClickHandler;
-    setHeight = (height: number) => this.layoutDoc._height = height;
+    setHeight = (height: number) => {
+        if (this.props.renderDepth !== -1) {
+            this.layoutDoc._height = height;
+        }
+    }
     setContentView = action((view: { getAnchor?: () => Doc, forward?: () => boolean, back?: () => boolean }) => this._componentView = view);
     isContentActive = (outsideReaction?: boolean) => {
         return CurrentUserUtils.SelectedTool !== InkTool.None ||
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 37d716993..9956cc36b 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -58,8 +58,10 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     private _sidebarRef = React.createRef<SidebarAnnos>();
     private _searchRef = React.createRef<HTMLInputElement>();
     private _searchString = "";
+    @observable private _webUrl = "";  // url of the src parameter of the embedded iframe but not necessarily the rendered page - eg, when following a link, the rendered page changes but we don't wan the src parameter to also change as that would cause an unnecessary re-render.  
+    @observable private _hackHide = false;  // apparently changing the value of the 'sandbox' prop doesn't necessarily apply it to the active iframe.  so thisforces the ifrmae to be rebuilt when allowScripts is toggled
     @observable private _searching: boolean = false;
-    @observable _showSidebar = false;
+    @observable private _showSidebar = false;
     @observable private _scrollTimer: any;
     @observable private _overlayAnnoInfo: Opt<Doc>;
     @observable private _marqueeing: number[] | undefined;
@@ -79,6 +81,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         super(props);
         Doc.SetNativeWidth(this.dataDoc, Doc.NativeWidth(this.dataDoc) || 850);
         Doc.SetNativeHeight(this.dataDoc, Doc.NativeHeight(this.dataDoc) || this.Document[HeightSym]() / this.Document[WidthSym]() * 850);
+        runInAction(() => this._webUrl = this._url);  // setting the weburl will change the src parameter of the embedded iframe and force a navigation to it.
     }
 
     @action
@@ -239,7 +242,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
         this._marqueeing = [e.clientX * scale + mainContBounds.translateX,
         e.clientY * scale + mainContBounds.translateY - NumCast(this.layoutDoc._scrollTop) * scale];
-        if (word) {
+        if (word || (e.target as any || "").className.includes("rangeslider") || (e.target as any)?.onclick || (e.target as any)?.parentNode?.onclick) {
             setTimeout(action(() => this._marqueeing = undefined), 100); // bcz: hack .. anchor menu is setup within MarqueeAnnotator so we need to at least create the marqueeAnnotator even though we aren't using it.
         } else {
             this._iframeClick = this._iframe ?? undefined;
@@ -269,9 +272,24 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     @action
     iframeLoaded = (e: any) => {
         const iframe = this._iframe;
-        this._iframe?.contentDocument?.addEventListener("pointerup", this.iframeUp);
+        let requrlraw = decodeURIComponent(iframe?.contentWindow?.location.href.replace(Utils.prepend("") + "/corsProxy/", "") ?? this._url.toString());
+        if (requrlraw !== this._url.toString()) {
+            if (requrlraw.match(/q=.*&/)?.length && this._url.toString().match(/q=.*&/)?.length) {
+                const matches = requrlraw.match(/[^a-zA-z]q=[^&]*/g);
+                const newsearch = matches?.lastElement()!;
+                if (matches) {
+                    requrlraw = requrlraw.substring(0, requrlraw.indexOf(newsearch));
+                    for (let i = 1; i < Array.from(matches)?.length; i++) {
+                        requrlraw = requrlraw.replace(matches[i], "");
+                    }
+                }
+                requrlraw = requrlraw.replace(/q=[^&]*/, newsearch.substring(1)).replace("search&", "search?").replace("?gbv=1", "");
+            }
+            this.submitURL(requrlraw, undefined, true);
+        }
         if (iframe?.contentDocument) {
-            iframe?.contentDocument.addEventListener("pointerdown", this.iframeDown);
+            iframe.contentDocument.addEventListener("pointerup", this.iframeUp);
+            iframe.contentDocument.addEventListener("pointerdown", this.iframeDown);
             this._scrollHeight = Math.max(this.scrollHeight, iframe?.contentDocument.body.scrollHeight);
             setTimeout(action(() => this._scrollHeight = Math.max(this.scrollHeight, iframe?.contentDocument?.body.scrollHeight || 0)), 5000);
             const initialScroll = this._initialScroll;
@@ -281,13 +299,15 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 this._initialScroll = undefined;
             }
             iframe.setAttribute("enable-annotation", "true");
-            iframe.contentDocument.addEventListener("click", undoBatch(action(e => {
+            iframe.contentDocument.addEventListener("click", undoBatch(action((e: MouseEvent) => {
                 let href = "";
-                for (let ele = e.target; ele; ele = ele.parentElement) {
+                for (let ele = e.target as any; ele; ele = ele.parentElement) {
                     href = (typeof (ele.href) === "string" ? ele.href : ele.href?.baseVal) || ele.parentElement?.href || href;
                 }
-                if (href && this.webField?.origin) {
-                    this.submitURL(href.replace(Utils.prepend(""), this.webField?.origin));
+                const origin = this.webField?.origin;
+                if (href && origin) {
+                    e.stopPropagation();
+                    setTimeout(() => this.submitURL(href.replace(Utils.prepend(""), origin)));
                     if (this._outerRef.current) {
                         this._outerRef.current.scrollTop = NumCast(this.layoutDoc._scrollTop);
                         this._outerRef.current.scrollLeft = 0;
@@ -338,8 +358,15 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         const future = Cast(this.dataDoc[this.fieldKey + "-future"], listSpec("string"), []);
         const history = Cast(this.dataDoc[this.fieldKey + "-history"], listSpec("string"), []);
         if (future.length) {
+            const curUrl = this._url;
             this.dataDoc[this.fieldKey + "-history"] = new List<string>([...history, this._url]);
             this.dataDoc[this.fieldKey] = new WebField(new URL(future.pop()!));
+            if (this._webUrl === this._url) {
+                this._webUrl = curUrl;
+                setTimeout(action(() => this._webUrl = this._url));
+            } else {
+                this._webUrl = this._url;
+            }
             return true;
         }
         return false;
@@ -350,10 +377,16 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         const future = Cast(this.dataDoc[this.fieldKey + "-future"], listSpec("string"));
         const history = Cast(this.dataDoc[this.fieldKey + "-history"], listSpec("string"), []);
         if (history.length) {
+            const curUrl = this._url;
             if (future === undefined) this.dataDoc[this.fieldKey + "-future"] = new List<string>([this._url]);
             else this.dataDoc[this.fieldKey + "-future"] = new List<string>([...future, this._url]);
             this.dataDoc[this.fieldKey] = new WebField(new URL(history.pop()!));
-            console.log(this._urlHash);
+            if (this._webUrl === this._url) {
+                this._webUrl = curUrl;
+                setTimeout(action(() => this._webUrl = this._url));
+            } else {
+                this._webUrl = this._url;
+            }
             return true;
         }
         return false;
@@ -362,9 +395,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     static urlHash = (s: string) => {
         return Math.abs(s.split('').reduce((a: any, b: any) => { a = ((a << 5) - a) + b.charCodeAt(0); return a & a; }, 0));
     }
-
     @action
-    submitURL = (newUrl?: string, preview?: boolean) => {
+    submitURL = (newUrl?: string, preview?: boolean, dontUpdateIframe?: boolean) => {
         if (!newUrl) return;
         if (!newUrl.startsWith("http")) newUrl = "http://" + newUrl;
         try {
@@ -376,7 +408,10 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 this.layoutDoc._scrollTop = 0;
                 future && (future.length = 0);
             }
-            if (!preview) this.dataDoc[this.fieldKey] = new WebField(new URL(newUrl));
+            if (!preview) {
+                this.dataDoc[this.fieldKey] = new WebField(new URL(newUrl));
+                !dontUpdateIframe && (this._webUrl = this._url);
+            }
         } catch (e) {
             console.log("WebBox URL error:" + this._url);
         }
@@ -429,6 +464,15 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         const funcs: ContextMenuProps[] = [];
         if (!cm.findByDescription("Options...")) {
             !Doc.UserDoc().noviceMode && funcs.push({ description: (this.layoutDoc.useCors ? "Don't Use" : "Use") + " Cors", event: () => this.layoutDoc.useCors = !this.layoutDoc.useCors, icon: "snowflake" });
+            funcs.push({
+                description: (this.layoutDoc.allowScripts ? "Prevent" : "Allow") + " Scripts", event: () => {
+                    this.layoutDoc.allowScripts = !this.layoutDoc.allowScripts;
+                    if (this._iframe) {
+                        runInAction(() => this._hackHide = true);
+                        setTimeout(action(() => this._hackHide = false));
+                    }
+                }, icon: "snowflake"
+            });
             funcs.push({
                 description: (!this.layoutDoc.forceReflow ? "Force" : "Prevent") + " Reflow", event: () => {
                     const nw = !this.layoutDoc.forceReflow ? undefined : Doc.NativeWidth(this.layoutDoc) - this.sidebarWidth() / (this.props.scaling?.() || 1);
@@ -468,18 +512,19 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     }
 
     @computed get urlContent() {
+        if (this._hackHide) return (null);
         const field = this.dataDoc[this.props.fieldKey];
         let view;
         if (field instanceof HtmlField) {
             view = <span className="webBox-htmlSpan" dangerouslySetInnerHTML={{ __html: field.html }} />;
         } else if (field instanceof WebField) {
-            const url = this.layoutDoc.useCors ? Utils.CorsProxy(this._url) : this._url;
+            const url = this.layoutDoc.useCors ? Utils.CorsProxy(this._webUrl) : this._webUrl;
             view = <iframe className="webBox-iframe" enable-annotation={"true"}
                 style={{ pointerEvents: this._scrollTimer ? "none" : undefined }}
                 ref={action((r: HTMLIFrameElement | null) => this._iframe = r)} src={url} onLoad={this.iframeLoaded}
                 // the 'allow-top-navigation' and 'allow-top-navigation-by-user-activation' attributes are left out to prevent iframes from redirecting the top-level Dash page
                 // sandbox={"allow-forms allow-modals allow-orientation-lock allow-pointer-lock allow-popups allow-popups-to-escape-sandbox allow-presentation allow-same-origin allow-scripts"} />;
-                sandbox={"allow-forms allow-modals allow-orientation-lock allow-pointer-lock allow-popups allow-popups-to-escape-sandbox allow-presentation allow-same-origin"} />;
+                sandbox={`${this.layoutDoc.allowScripts ? "allow-scripts" : ""} allow-forms allow-modals allow-orientation-lock allow-pointer-lock allow-popups allow-popups-to-escape-sandbox allow-presentation allow-same-origin`} />;
         } else {
             view = <iframe className="webBox-iframe" enable-annotation={"true"}
                 style={{ pointerEvents: this._scrollTimer ? "none" : undefined }} // if we allow pointer events when scrolling is on, then reversing direction does not work smoothly
@@ -615,12 +660,12 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 moveDocument={this.moveDocument}
                 addDocument={this.sidebarAddDocument}
                 styleProvider={this.childStyleProvider}
-                pointerEvents={CurrentUserUtils.SelectedTool !== InkTool.None || this._isAnnotating || SnappingManager.GetIsDragging() ? "all" : "none"} />;
+                childPointerEvents={this.props.isContentActive() ? "all" : undefined}
+                pointerEvents={this._isAnnotating || SnappingManager.GetIsDragging() ? "all" : "none"} />;
         return (
             <div className="webBox" ref={this._mainCont}
                 style={{ pointerEvents: this.pointerEvents() }} >
                 <div className={`webBox-container`} style={{ pointerEvents }} onContextMenu={this.specificContextMenu}>
-                    <base target="_blank" />
                     <div className={"webBox-outerContent"} ref={this._outerRef}
                         style={{
                             width: `calc(${100 / scale}% - ${this.sidebarWidth() / scale * (this._previewWidth ? scale : 1)}px)`,
diff --git a/src/server/server_Initialization.ts b/src/server/server_Initialization.ts
index 00a801e03..de93b64c3 100644
--- a/src/server/server_Initialization.ts
+++ b/src/server/server_Initialization.ts
@@ -13,6 +13,7 @@ import * as request from 'request';
 import * as webpack from 'webpack';
 import * as wdm from 'webpack-dev-middleware';
 import * as whm from 'webpack-hot-middleware';
+import * as zlib from 'zlib';
 import { publicDirectory } from '.';
 import { logPort } from './ActionUtilities';
 import { SSL } from './apis/google/CredentialsLoader';
@@ -36,39 +37,30 @@ export let resolvedPorts: { server: number, socket: number } = { server: 1050, s
 export let resolvedServerUrl: string;
 
 export default async function InitializeServer(routeSetter: RouteSetter) {
+    const isRelease = determineEnvironment();
     const app = buildWithMiddleware(express());
-    // Root route of express app
-    app.get("/", (req, res) => res.redirect("/home"));
 
-    app.use(express.static(publicDirectory, {
-        setHeaders: res => res.setHeader("Access-Control-Allow-Origin", "*")
-    }));
-    app.use("/images", express.static(publicDirectory));
+    // route table managed by express.  routes are tested sequentially against each of these map rules.  when a match is found, the handler is called to process the request
+    app.get(new RegExp(/^\/+$/), (req, res) => res.redirect(req.user ? "/home" : "/login")); // target urls that consist of one or more '/'s with nothing in between
+    app.use(express.static(publicDirectory, { setHeaders: res => res.setHeader("Access-Control-Allow-Origin", "*") }));  //all urls that start with dash's public directory: /files/  (e.g., /files/images, /files/audio, etc)
     app.use(cors({ origin: (_origin: any, callback: any) => callback(null, true) }));
-
     app.use(wdm(compiler, { publicPath: config.output.publicPath }));
     app.use(whm(compiler));
-
-    registerAuthenticationRoutes(app);
-    registerCorsProxy(app);
-
-    const isRelease = determineEnvironment();
-
+    registerAuthenticationRoutes(app);  // this adds routes to authenticate a user (login, etc)
+    registerCorsProxy(app);  // this adds a /corsProxy/ route to allow clients to get to urls that would otherwise be blocked by cors policies
     isRelease && !SSL.Loaded && SSL.exit();
-
-    routeSetter(new RouteManager(app, isRelease));
-    registerRelativePath(app);
+    routeSetter(new RouteManager(app, isRelease)); // this sets up all the regular supervised routes (things like /home, download/upload api's, pdf, search, session, etc)
+    registerEmbeddedBrowseRelativePathHandler(app); // this allows renered web pages which internally have relative paths to find their content
 
     let server: HttpServer | HttpsServer;
-    const { serverPort, serverName } = process.env;
-    isRelease && serverPort && (resolvedPorts.server = Number(serverPort));
+    isRelease && process.env.serverPort && (resolvedPorts.server = Number(process.env.serverPort));
     await new Promise<void>(resolve => server = isRelease ?
         createServer(SSL.Credentials, app).listen(resolvedPorts.server, resolve) :
         app.listen(resolvedPorts.server, resolve)
     );
     logPort("server", resolvedPorts.server);
 
-    resolvedServerUrl = `${isRelease && serverName ? `https://${serverName}.com` : "http://localhost"}:${resolvedPorts.server}`;
+    resolvedServerUrl = `${isRelease && process.env.serverName ? `https://${process.env.serverName}.com` : "http://localhost"}:${resolvedPorts.server}`;
 
     // initialize the web socket (bidirectional communication: if a user changes
     // a field on one client, that change must be broadcast to all other clients)
@@ -141,54 +133,81 @@ function registerAuthenticationRoutes(server: express.Express) {
 }
 
 function registerCorsProxy(server: express.Express) {
-    const headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/;
     server.use("/corsProxy", async (req, res) => {
-
         const referer = req.headers.referer ? decodeURIComponent(req.headers.referer) : "";
-        const requrlraw = decodeURIComponent(req.url.substring(1));
+        let requrlraw = decodeURIComponent(req.url.substring(1));
+        const qsplit = requrlraw.split("?q=");
+        const newqsplit = requrlraw.split("&q=");
+        if (qsplit.length > 1 && newqsplit.length > 1) {
+            const lastq = newqsplit[newqsplit.length - 1];
+            requrlraw = qsplit[0] + "?q=" + lastq.split("&")[0] + "&" + qsplit[1].split("&")[1];
+        }
         const requrl = requrlraw.startsWith("/") ? referer + requrlraw : requrlraw;
-        // cors weirdness here... 
-        // if the referer is a cors page and the cors() route (I think) redirected to /corsProxy/<path> and the requested url path was relative, 
+        // cors weirdness here...
+        // if the referer is a cors page and the cors() route (I think) redirected to /corsProxy/<path> and the requested url path was relative,
         // then we redirect again to the cors referer and just add the relative path.
         if (!requrl.startsWith("http") && req.originalUrl.startsWith("/corsProxy") && referer?.includes("corsProxy")) {
             res.redirect(referer + (referer.endsWith("/") ? "" : "/") + requrl);
         } else {
-            try {
-                await new Promise<void>((resolve, reject) => {
-                    request(requrl).on("response", resolve).on("error", reject);
-                });
-            } catch {
-                console.log(`Malformed CORS url: ${requrl}`);
-                return res.send();
-            }
-            req.pipe(request(requrl)).on("response", res => {
-                const headers = Object.keys(res.headers);
-                headers.forEach(headerName => {
-                    const header = res.headers[headerName];
-                    if (Array.isArray(header)) {
-                        res.headers[headerName] = header.filter(h => !headerCharRegex.test(h));
-                    } else if (header) {
-                        if (headerCharRegex.test(header as any)) {
-                            delete res.headers[headerName];
-                        }
-                    }
-                });
-            }).on("error", () => console.log(`Malformed CORS url: ${requrl}`)).pipe(res);
+            proxyServe(req, requrl, res);
         }
     });
 }
 
-function registerRelativePath(server: express.Express) {
+function proxyServe(req: any, requrl: string, response: any) {
+    const htmlBodyMemoryStream = new (require('memorystream'))();
+    req.headers.cookie = "";
+    req.pipe(request(requrl))
+        .on("error", (e: any) => console.log(`Malformed CORS url: ${requrl}`, e))
+        .on("end", () => {
+            var rewrittenHtmlBody: any = undefined;
+            req.pipe(request(requrl))
+                .on("response", (res: any) => {
+                    const headers = Object.keys(res.headers);
+                    const headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/;
+                    headers.forEach(headerName => {
+                        const header = res.headers[headerName];
+                        if (Array.isArray(header)) {
+                            res.headers[headerName] = header.filter(h => !headerCharRegex.test(h));
+                        } else if (headerCharRegex.test(header || "")) {
+                            delete res.headers[headerName];
+                        }
+                        if (headerName === "content-encoding" && header.includes("gzip")) {
+                            try {
+                                const replacer = (match: any, href: string, offset: any, string: any) => {
+                                    return `href="${resolvedServerUrl + "/corsProxy/http" + href}"`;
+                                };
+                                const zipToStringDecoder = new (require('string_decoder').StringDecoder)('utf8');
+                                const htmlText = zipToStringDecoder.write(zlib.gunzipSync(htmlBodyMemoryStream.read()).toString('utf8')
+                                    .replace('<head>', '<head> <style>[id ^= "google"] { display: none; } </style>')
+                                    .replace(/href="http([^"]*)"/g, replacer)
+                                    .replace(/target="_blank"/g, ""));
+                                rewrittenHtmlBody = zlib.gzipSync(htmlText);
+                            } catch (e) { console.log(e); }
+                        }
+                    });
+                })
+                .on('data', (e: any) => {
+                    rewrittenHtmlBody && response.send(rewrittenHtmlBody);
+                    rewrittenHtmlBody = undefined;
+                })
+                .pipe(response);
+        })
+        .pipe(htmlBodyMemoryStream);
+}
+
+function registerEmbeddedBrowseRelativePathHandler(server: express.Express) {
     server.use("*", (req, res) => {
         const relativeUrl = req.originalUrl;
-        if (!res.headersSent && req.headers.referer?.includes("corsProxy")) { // a request for something by a proxied referrer means it must be a relative reference.  So construct a proxied absolute reference here.
+        if (!req.user) res.redirect("/home");  // When no user is logged in, we interpret a relative URL as being a reference to something they don't have access to and redirect to /home 
+        else if (!res.headersSent && req.headers.referer?.includes("corsProxy")) { // a request for something by a proxied referrer means it must be a relative reference.  So construct a proxied absolute reference here.
             const proxiedRefererUrl = decodeURIComponent(req.headers.referer); // (e.g., http://localhost:<port>/corsProxy/https://en.wikipedia.org/wiki/Engelbart)
             const dashServerUrl = proxiedRefererUrl.match(/.*corsProxy\//)![0]; // the dash server url (e.g.: http://localhost:<port>/corsProxy/ )
             const actualReferUrl = proxiedRefererUrl.replace(dashServerUrl, ""); // the url of the referer without the proxy (e.g., : http:s//en.wikipedia.org/wiki/Engelbart)
             const absoluteTargetBaseUrl = actualReferUrl.match(/http[s]?:\/\/[^\/]*/)![0]; // the base of the original url (e.g.,  https://en.wikipedia.org)
             const redirectedProxiedUrl = dashServerUrl + encodeURIComponent(absoluteTargetBaseUrl + relativeUrl); // the new proxied full url (e..g, http://localhost:<port>/corsProxy/https://en.wikipedia.org/<somethingelse>)
             res.redirect(redirectedProxiedUrl);
-        } else if (relativeUrl.startsWith("/search")) { // detect search query and use default search engine
+        } else if (relativeUrl.startsWith("/search") && !req.headers.referer?.includes("corsProxy")) { // detect search query and use default search engine
             res.redirect(req.headers.referer + "corsProxy/" + encodeURIComponent("http://www.google.com" + relativeUrl));
         } else {
             res.end();
-- 
cgit v1.2.3-70-g09d2

