From 64119b5d8766725025b8b2bfda72f2401bba0f00 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 16 Sep 2021 21:04:57 -0400
Subject: added search() component method.  changed search menu to call search
 on documents that match search string.  added seach bar for web pages.

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

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index be0b078ec..94cf1c5a6 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -902,7 +902,9 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                 HistoryUtil.pushState(state);
             }
         }
-        SelectionManager.DeselectAll();
+        if (SelectionManager.Views().length !== 1 || SelectionManager.Views()[0].Document !== doc) {
+            SelectionManager.DeselectAll();
+        }
         if (this.props.Document.scrollHeight || this.props.Document.scrollTop !== undefined) {
             this.props.focus(doc, options);
         } else {
-- 
cgit v1.2.3-70-g09d2


From eb50e46332c2f0b48c7cb165d55245aab317ef08 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 21 Sep 2021 19:43:32 -0400
Subject: fixed up some darkScheme css, enabled comic mode for developers,
 enabled opening up fields on dashboards in myDocuments

---
 src/client/documents/Documents.ts                  |  2 +-
 src/client/util/CurrentUserUtils.ts                | 30 ++++++++++------
 src/client/util/SelectionManager.ts                | 14 ++------
 src/client/util/SettingsManager.tsx                | 19 +++++-----
 src/client/views/ContextMenu.scss                  |  1 -
 src/client/views/DocumentDecorations.scss          | 28 +++++++++++----
 src/client/views/DocumentDecorations.tsx           | 42 +++++++++++++---------
 src/client/views/MainView.scss                     | 12 +++----
 src/client/views/MainView.tsx                      | 14 ++++----
 src/client/views/StyleProvider.tsx                 |  5 +--
 .../views/collections/CollectionSchemaView.tsx     |  2 +-
 src/client/views/collections/TreeView.tsx          |  9 +++--
 .../CollectionFreeFormLayoutEngines.tsx            |  3 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  4 ++-
 .../collectionSchema/CollectionSchemaView.tsx      |  7 ++--
 src/client/views/nodes/DocumentView.tsx            |  6 ++--
 src/client/views/nodes/button/FontIconBox.tsx      |  2 --
 .../views/nodes/formattedText/DashDocView.tsx      |  3 +-
 src/mobile/MobileInterface.tsx                     |  4 +--
 19 files changed, 118 insertions(+), 89 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index fafbc4a7d..206f65bfd 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -862,7 +862,7 @@ export namespace Docs {
         export function DockDocument(documents: Array<Doc>, config: string, options: DocumentOptions, id?: string) {
             const tabs = TreeDocument(documents, { title: "On-Screen Tabs", childDontRegisterViews: true, freezeChildren: "remove|add", treeViewExpandedViewLock: true, treeViewExpandedView: "data", _fitWidth: true, system: true, isFolder: true });
             const all = TreeDocument([], { title: "Off-Screen Tabs", childDontRegisterViews: true, freezeChildren: "add", treeViewExpandedViewLock: true, treeViewExpandedView: "data", system: true, isFolder: true });
-            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List([tabs, all]), { freezeChildren: "remove|add", treeViewExpandedViewLock: true, treeViewExpandedView: "data", ...options, _viewType: CollectionViewType.Docking, dockingConfig: config }, id);
+            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List([tabs, all]), { freezeChildren: "remove|add", ...options, _viewType: CollectionViewType.Docking, dockingConfig: config }, id);
         }
 
         export function DirectoryImportDocument(options: DocumentOptions = {}) {
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index d5dc9e2be..568a9ddbd 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -20,6 +20,7 @@ import { Networking } from "../Network";
 import { CollectionDockingView } from "../views/collections/CollectionDockingView";
 import { DimUnit } from "../views/collections/collectionMulticolumn/CollectionMulticolumnView";
 import { CollectionView, CollectionViewType } from "../views/collections/CollectionView";
+import { TreeView } from "../views/collections/TreeView";
 import { Colors } from "../views/global/globalEnums";
 import { MainView } from "../views/MainView";
 import { ButtonType, NumButtonType } from "../views/nodes/button/FontIconBox";
@@ -38,7 +39,6 @@ import { ColorScheme } from "./SettingsManager";
 import { SharingManager } from "./SharingManager";
 import { SnappingManager } from "./SnappingManager";
 import { UndoManager } from "./UndoManager";
-import { TreeView } from "../views/collections/TreeView";
 
 interface Button {
     title?: string;
@@ -816,16 +816,19 @@ export class CurrentUserUtils {
                 _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", treeViewType: "fileSystem", isFolder: true, system: true,
                 explainer: "This is your collection of dashboards. A dashboard represents the tab configuration of your workspace. To manage documents as folders, go to the Files."
             }));
-            // const toggleTheme = ScriptField.MakeScript(`Doc.UserDoc().darkScheme = !Doc.UserDoc().darkScheme`);
-            // const toggleComic = ScriptField.MakeScript(`toggleComicMode()`);
-            // const snapshotDashboard = ScriptField.MakeScript(`snapshotDashboard()`);
+            const toggleDarkTheme = ScriptField.MakeScript(`this.colorScheme = this.colorScheme ? undefined : "${ColorScheme.Dark}"`);
+            const toggleComic = ScriptField.MakeScript(`toggleComicMode()`);
+            const snapshotDashboard = ScriptField.MakeScript(`snapshotDashboard()`);
             const shareDashboard = ScriptField.MakeScript(`shareDashboard(self)`);
             const removeDashboard = ScriptField.MakeScript('removeDashboard(self)');
-            (doc.myDashboards as any as Doc).childContextMenuScripts = new List<ScriptField>([newDashboard!, shareDashboard!, removeDashboard!]);
-            (doc.myDashboards as any as Doc).childContextMenuLabels = new List<string>(["Create New Dashboard", "Share Dashboard", "Remove Dashboard"]);
-            (doc.myDashboards as any as Doc).childContextMenuIcons = new List<string>(["plus", "user-friends", "times"]);
-            // (doc.myDashboards as any as Doc).childContextMenuScripts = new List<ScriptField>([newDashboard!, toggleTheme!, toggleComic!, snapshotDashboard!, shareDashboard!, removeDashboard!]);
-            // (doc.myDashboards as any as Doc).childContextMenuLabels = new List<string>(["Create New Dashboard", "Toggle Theme Colors", "Toggle Comic Mode", "Snapshot Dashboard", "Share Dashboard", "Remove Dashboard"]);
+            const developerFilter = ScriptField.MakeFunction('!IsNoviceMode()');
+            // (doc.myDashboards as any as Doc).childContextMenuScripts = new List<ScriptField>([newDashboard!, shareDashboard!, removeDashboard!]);
+            // (doc.myDashboards as any as Doc).childContextMenuLabels = new List<string>(["Create New Dashboard", "Share Dashboard", "Remove Dashboard"]);
+            // (doc.myDashboards as any as Doc).childContextMenuIcons = new List<string>(["plus", "user-friends", "times"]);
+            (doc.myDashboards as any as Doc).childContextMenuScripts = new List<ScriptField>([newDashboard!, toggleDarkTheme!, toggleComic!, snapshotDashboard!, shareDashboard!, removeDashboard!]);
+            (doc.myDashboards as any as Doc).childContextMenuLabels = new List<string>(["Create New Dashboard", "Toggle Dark Theme", "Toggle Comic Mode", "Snapshot Dashboard", "Share Dashboard", "Remove Dashboard"]);
+            (doc.myDashboards as any as Doc).childContextMenuIcons = new List<string>(["plus", "chalkboard", "tv", "camera", "users", "times"]);
+            (doc.myDashboards as any as Doc).childContextMenuFilters = new List<ScriptField>([undefined as any, developerFilter, developerFilter, developerFilter, undefined as any, undefined as any]);
         }
         return doc.myDashboards as any as Doc;
     }
@@ -1302,7 +1305,6 @@ export class CurrentUserUtils {
             }, { fireImmediately: true });
         // Document properties on load
         doc.system = true;
-        doc.darkScheme = ColorScheme.Dark;
         doc.noviceMode = doc.noviceMode === undefined ? "true" : doc.noviceMode;
         doc.title = Doc.CurrentUserEmail;
         doc._raiseWhenDragged = true;
@@ -1321,7 +1323,6 @@ export class CurrentUserUtils {
         doc.fontColor = StrCast(doc.fontColor, "black");
         doc.fontHighlight = StrCast(doc.fontHighlight, "");
         doc.defaultAclPrivate = BoolCast(doc.defaultAclPrivate, false);
-        doc.activeCollectionBackground = StrCast(doc.activeCollectionBackground, "white");
         doc.activeCollectionNestedBackground = Cast(doc.activeCollectionNestedBackground, "string", null);
         doc.noviceMode = BoolCast(doc.noviceMode, true);
         doc["constants-snapThreshold"] = NumCast(doc["constants-snapThreshold"], 10); //
@@ -1358,6 +1359,10 @@ export class CurrentUserUtils {
         // });
         setTimeout(() => DocServer.UPDATE_SERVER_CACHE(), 2500);
         doc.fieldInfos = await Docs.setupFieldInfos();
+        if (doc.activeDashboard instanceof Doc) {
+            // undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss)
+            doc.activeDashboard.colorScheme = doc.activeDashboard.colorScheme === ColorScheme.Light ? undefined : doc.activeDashboard.colorScheme;
+        }
         return doc;
     }
 
@@ -1637,4 +1642,7 @@ Scripting.addGlobal(function makeTopLevelFolder() {
     const folder = Docs.Create.TreeDocument([], { title: "Untitled folder", _stayInCollection: true, isFolder: true });
     TreeView._editTitleOnLoad = { id: folder[Id], parent: undefined };
     return Doc.AddDocToList(Doc.UserDoc().myFilesystem as Doc, "data", folder);
+});
+Scripting.addGlobal(function toggleComicMode() {
+    Doc.UserDoc().renderStyle = Doc.UserDoc().renderStyle === "comic" ? undefined : "comic";
 });
\ No newline at end of file
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index bac13373c..e507ec3bf 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -2,7 +2,6 @@ import { action, observable, ObservableMap } from "mobx";
 import { computedFn } from "mobx-utils";
 import { Doc, Opt } from "../../fields/Doc";
 import { DocumentType } from "../documents/DocumentTypes";
-import { CollectionSchemaView } from "../views/collections/collectionSchema/CollectionSchemaView";
 import { CollectionViewType } from "../views/collections/CollectionView";
 import { DocumentView } from "../views/nodes/DocumentView";
 
@@ -13,12 +12,10 @@ export namespace SelectionManager {
         @observable IsDragging: boolean = false;
         SelectedViews: ObservableMap<DocumentView, Doc> = new ObservableMap();
         @observable SelectedSchemaDocument: Doc | undefined;
-        @observable SelectedSchemaCollection: CollectionSchemaView | undefined;
 
         @action
-        SelectSchemaView(collectionView: Opt<CollectionSchemaView>, doc: Opt<Doc>) {
+        SelectSchemaViewDoc(doc: Opt<Doc>) {
             manager.SelectedSchemaDocument = doc;
-            manager.SelectedSchemaCollection = collectionView;
         }
         @action
         SelectView(docView: DocumentView, ctrlPressed: boolean): void {
@@ -33,7 +30,6 @@ export namespace SelectionManager {
             } else if (!ctrlPressed && Array.from(manager.SelectedViews.entries()).length > 1) {
                 Array.from(manager.SelectedViews.keys()).map(dv => dv !== docView && dv.props.whenChildContentsActiveChanged(false));
                 manager.SelectedSchemaDocument = undefined;
-                manager.SelectedSchemaCollection = undefined;
                 manager.SelectedViews.clear();
                 manager.SelectedViews.set(docView, docView.rootDoc);
             }
@@ -47,7 +43,6 @@ export namespace SelectionManager {
         }
         @action
         DeselectAll(): void {
-            manager.SelectedSchemaCollection = undefined;
             manager.SelectedSchemaDocument = undefined;
             Array.from(manager.SelectedViews.keys()).map(dv => dv.props.whenChildContentsActiveChanged(false));
             manager.SelectedViews.clear();
@@ -62,8 +57,8 @@ export namespace SelectionManager {
     export function SelectView(docView: DocumentView, ctrlPressed: boolean): void {
         manager.SelectView(docView, ctrlPressed);
     }
-    export function SelectSchemaView(colSchema: Opt<CollectionSchemaView>, document: Opt<Doc>): void {
-        manager.SelectSchemaView(colSchema, document);
+    export function SelectSchemaViewDoc(document: Opt<Doc>): void {
+        manager.SelectSchemaViewDoc(document);
     }
 
     const IsSelectedCache = computedFn(function isSelected(doc: DocumentView) {  // wrapping get() in a computedFn only generates mobx() invalidations when the return value of the function for the specific get parameters has changed
@@ -96,9 +91,6 @@ export namespace SelectionManager {
     export function SelectedSchemaDoc(): Doc | undefined {
         return manager.SelectedSchemaDocument;
     }
-    export function SelectedSchemaCollection(): CollectionSchemaView | undefined {
-        return manager.SelectedSchemaCollection;
-    }
     export function Docs(): Doc[] {
         return Array.from(manager.SelectedViews.values()).filter(doc => doc?._viewType !== CollectionViewType.Docking);
     }
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index bd91db779..6a26dfdc7 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -19,9 +19,9 @@ export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
 
 export enum ColorScheme {
-    Dark = "Dark",
-    Light = "Light",
-    System = "Match System"
+    Dark = "-Dark",
+    Light = "-Light",
+    System = "-MatchSystem"
 }
 
 @observer
@@ -38,7 +38,7 @@ export class SettingsManager extends React.Component<{}> {
     @observable activeTab = "Accounts";
 
     @computed get backgroundColor() { return Doc.UserDoc().activeCollectionBackground; }
-    @computed get colorScheme() { return Doc.UserDoc().colorScheme; }
+    @computed get colorScheme() { return CurrentUserUtils.ActiveDashboard.colorScheme; }
 
     constructor(props: {}) {
         super(props);
@@ -81,16 +81,16 @@ export class SettingsManager extends React.Component<{}> {
         const scheme: ColorScheme = (e.currentTarget as any).value;
         switch (scheme) {
             case ColorScheme.Light:
-                Doc.UserDoc().colorScheme = ColorScheme.Light;
+                CurrentUserUtils.ActiveDashboard.colorScheme = undefined; // undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss)
                 addStyleSheetRule(SettingsManager._settingsStyle, "lm_header", { background: "#d3d3d3 !important" });
                 break;
             case ColorScheme.Dark:
-                Doc.UserDoc().colorScheme = ColorScheme.Dark;
+                CurrentUserUtils.ActiveDashboard.colorScheme = ColorScheme.Dark;
                 addStyleSheetRule(SettingsManager._settingsStyle, "lm_header", { background: "black !important" });
                 break;
             case ColorScheme.System: default:
                 window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
-                    Doc.UserDoc().colorScheme = e.matches ? ColorScheme.Dark : ColorScheme.Light;
+                    CurrentUserUtils.ActiveDashboard.colorScheme = e.matches ? ColorScheme.Dark : undefined; // undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss)
                 });
                 break;
         }
@@ -119,6 +119,7 @@ export class SettingsManager extends React.Component<{}> {
         </div>;
 
         const colorSchemes = [ColorScheme.Light, ColorScheme.Dark, ColorScheme.System];
+        const schemeMap = ["Light", "Dark", "Match system"];
 
         return <div className="colors-content">
             <div className="preferences-color">
@@ -132,8 +133,8 @@ export class SettingsManager extends React.Component<{}> {
             <div className="preferences-colorScheme">
                 <div className="preferences-color-text">Color Scheme</div>
                 <div className="preferences-color-controls">
-                    <select className="scheme-select" onChange={this.changeColorScheme} defaultValue={StrCast(Doc.UserDoc().colorScheme)}>
-                        {colorSchemes.map(scheme => <option key={scheme} value={scheme}> {scheme} </option>)}
+                    <select className="scheme-select" onChange={this.changeColorScheme} defaultValue={StrCast(CurrentUserUtils.ActiveDashboard?.colorScheme)}>
+                        {colorSchemes.map((scheme, i) => <option key={scheme} value={scheme}> {schemeMap[i]} </option>)}
                     </select>
                 </div>
             </div>
diff --git a/src/client/views/ContextMenu.scss b/src/client/views/ContextMenu.scss
index 47ae0424b..ea24dbf6d 100644
--- a/src/client/views/ContextMenu.scss
+++ b/src/client/views/ContextMenu.scss
@@ -8,7 +8,6 @@
     flex-direction: column;
     background: whitesmoke;
     border-radius: 3px;
-    border: solid $light-gray 1px;
 }
 
 // .contextMenu-item:first-child {
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index a9f50f81b..d8ad47ecb 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -2,10 +2,14 @@
 
 $linkGap: 3px;
 
+.documentDecorations-Dark,
 .documentDecorations {
   position: absolute;
   z-index: 2000;
 }
+.documentDecorations-Dark {
+    background: dimgray;
+}
 .documentDecorations-container {
     z-index: $docDecorations-zindex;
     position: absolute;
@@ -50,12 +54,17 @@ $linkGap: 3px;
         pointer-events: auto;
         background: $medium-gray;
         opacity: 0.1;
-
         &:hover {
             opacity: 1;
         }
     }
 
+    .documentDecorations-resizer-Dark
+    {
+        background: $light-gray;
+        opacity: 0.2;
+    }
+
     .documentDecorations-topLeftResizer,
     .documentDecorations-leftResizer,
     .documentDecorations-bottomLeftResizer {
@@ -221,6 +230,7 @@ $linkGap: 3px;
     cursor: ns-resize;
   }
 
+    .documentDecorations-title-Dark,
     .documentDecorations-title {
         opacity: 1;
         grid-column-start: 2;
@@ -233,14 +243,22 @@ $linkGap: 3px;
         height: 22px;
         position: absolute;
 
-        .documentDecorations-titleSpan {
+        .documentDecorations-titleSpan, 
+        .documentDecorations-titleSpan-Dark  {
             width: 100%;
             border-radius: 8px;
-            background: #ffffffcf;
+            background: #ffffffa0;
             position: absolute;
             display: inline-block;
             cursor: move;
         }
+        .documentDecorations-titleSpan-Dark {
+            background: hsla(0, 0%, 0%, 0.412);
+        }
+    }
+    .documentDecorations-title-Dark {
+        color: white;
+        background: black;
     }
 
   .documentDecorations-contextMenu {
@@ -439,10 +457,6 @@ $linkGap: 3px;
   }
 }
 
-.documentDecorations-darkScheme {
-  background: dimgray;
-}
-
 #template-list {
   position: absolute;
   top: 25px;
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index d785d5419..e9a54d6a5 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -8,7 +8,7 @@ import { AclAdmin, AclEdit, DataSym, Doc, DocListCast, Field, HeightSym, WidthSy
 import { Document } from '../../fields/documentSchemas';
 import { InkField } from "../../fields/InkField";
 import { ScriptField } from '../../fields/ScriptField';
-import { Cast, NumCast } from "../../fields/Types";
+import { Cast, NumCast, StrCast } from "../../fields/Types";
 import { GetEffectiveAcl } from '../../fields/util';
 import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../Utils";
 import { Docs } from "../documents/Documents";
@@ -27,6 +27,8 @@ import { LightboxView } from './LightboxView';
 import { DocumentView } from "./nodes/DocumentView";
 import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
 import React = require("react");
+import { dark } from '@material-ui/core/styles/createPalette';
+import { color } from 'd3-color';
 
 @observer
 export class DocumentDecorations extends React.Component<{ PanelWidth: number, PanelHeight: number, boundsLeft: number, boundsTop: number }, { value: string }> {
@@ -241,7 +243,8 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
         InkStrokeProperties.Instance?._lock && SelectionManager.Views().filter(dv => dv.rootDoc.type === DocumentType.INK)
             .forEach(dv => fixedAspect = Doc.NativeAspect(dv.rootDoc));
 
-        if (fixedAspect && (this._resizeHdlId === "documentDecorations-bottomRightResizer" || this._resizeHdlId === "documentDecorations-topLeftResizer")) { // need to generalize for bl and tr drag handles
+        const resizeHdl = this._resizeHdlId.split(" ")[0];
+        if (fixedAspect && (resizeHdl === "documentDecorations-bottomRightResizer" || resizeHdl === "documentDecorations-topLeftResizer")) { // need to generalize for bl and tr drag handles
             const project = (p: number[], a: number[], b: number[]) => {
                 const atob = [b[0] - a[0], b[1] - a[1]];
                 const atop = [p[0] - a[0], p[1] - a[1]];
@@ -264,7 +267,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
         this._snapY = thisPt.y;
         let dragBottom = false, dragRight = false, dragBotRight = false;
         let dX = 0, dY = 0, dW = 0, dH = 0;
-        switch (this._resizeHdlId) {
+        switch (this._resizeHdlId.split(" ")[0]) {
             case "": break;
             case "documentDecorations-topLeftResizer":
                 dX = -1;
@@ -437,11 +440,17 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
                 </div>
             </Tooltip>);
 
+        const colorScheme = StrCast(CurrentUserUtils.ActiveDashboard?.colorScheme);
         const titleArea = this._edtingTitle ?
-            <input ref={this._keyinput} className="documentDecorations-title" style={{ width: `calc(100% - ${seldoc?.props.hideResizeHandles ? 0 : 20}px` }} type="text" name="dynbox" autoComplete="on" value={this._accumulatedTitle}
-                onBlur={e => this.titleBlur()} onChange={action(e => this._accumulatedTitle = e.target.value)} onKeyPress={this.titleEntered} /> :
+            <input ref={this._keyinput} className={`documentDecorations-title${colorScheme}`}
+                style={{ width: `calc(100% - ${seldoc?.props.hideResizeHandles ? 0 : 20}px` }}
+                type="text" name="dynbox" autoComplete="on"
+                value={this._accumulatedTitle}
+                onBlur={e => this.titleBlur()}
+                onChange={action(e => this._accumulatedTitle = e.target.value)}
+                onKeyPress={this.titleEntered} /> :
             <div className="documentDecorations-title" style={{ width: `calc(100% - ${seldoc?.props.hideResizeHandles ? 0 : 20}px` }} key="title" onPointerDown={this.onTitleDown} >
-                <span className="documentDecorations-titleSpan">{`${this.selectionTitle}`}</span>
+                <span className={`documentDecorations-titleSpan${colorScheme}`}>{`${this.selectionTitle}`}</span>
             </div>;
 
         let inMainMenuPanel = false;
@@ -457,8 +466,9 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
         bounds.b = Math.max(bounds.y, Math.max(topBounds, Math.min(window.innerHeight, bounds.b + this._resizeBorderWidth / 2 + this._linkBoxHeight) - this._resizeBorderWidth / 2 - this._linkBoxHeight));
 
         const useRotation = seldoc.rootDoc.type === DocumentType.INK;
+        const resizerScheme = colorScheme ? "documentDecorations-resizer" + colorScheme : "";
 
-        return (<div className="documentDecorations" style={{ background: CurrentUserUtils.ActiveDashboard?.darkScheme ? "dimgray" : "" }} >
+        return (<div className={`documentDecorations${colorScheme}`}>
             <div className="documentDecorations-background" style={{
                 width: (bounds.r - bounds.x + this._resizeBorderWidth) + "px",
                 height: (bounds.b - bounds.y + this._resizeBorderWidth) + "px",
@@ -481,15 +491,15 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
                         <>
                             {SelectionManager.Views().length !== 1 || hideTitle ? (null) :
                                 topBtn("iconify", `window-${seldoc.finalLayoutKey.includes("icon") ? "restore" : "minimize"}`, undefined, this.onIconifyClick, `${seldoc.finalLayoutKey.includes("icon") ? "De" : ""}Iconify Document`)}
-                            <div key="tl" className="documentDecorations-topLeftResizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
-                            <div key="t" className="documentDecorations-topResizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
-                            <div key="tr" className="documentDecorations-topRightResizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
-                            <div key="l" className="documentDecorations-leftResizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
-                            <div key="c" className="documentDecorations-centerCont"></div>
-                            <div key="r" className="documentDecorations-rightResizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
-                            <div key="bl" className="documentDecorations-bottomLeftResizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
-                            <div key="b" className="documentDecorations-bottomResizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
-                            <div key="br" className="documentDecorations-bottomRightResizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+                            <div key="tl" className={`documentDecorations-topLeftResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+                            <div key="t" className={`documentDecorations-topResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+                            <div key="tr" className={`documentDecorations-topRightResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+                            <div key="l" className={`documentDecorations-leftResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+                            <div key="c" className={`documentDecorations-centerCont ${resizerScheme}`}></div>
+                            <div key="r" className={`documentDecorations-rightResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+                            <div key="bl" className={`documentDecorations-bottomLeftResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+                            <div key="b" className={`documentDecorations-bottomResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
+                            <div key="br" className={`documentDecorations-bottomRightResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
 
                             {seldoc.props.renderDepth <= 1 || !seldoc.props.ContainingCollectionView ? (null) :
                                 topBtn("selector", "arrow-alt-circle-up", undefined, this.onSelectorClick, "tap to select containing document")}
diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss
index 4f871f5ec..7fa841002 100644
--- a/src/client/views/MainView.scss
+++ b/src/client/views/MainView.scss
@@ -41,7 +41,7 @@
 }
 
 .mainView-container,
-.mainView-container-dark {
+.mainView-container-Dark {
     width: 100%;
     height: 100%;
     position: absolute;
@@ -65,7 +65,7 @@
     }
 }
 
-.mainView-container-dark {
+.mainView-container-Dark {
     color: $light-gray;
 
     .lm_goldenlayout {
@@ -91,7 +91,7 @@
 
     .contextMenu-cont,
     .contextMenu-item {
-        background: $medium-gray;
+        background: $dark-gray;
     }
 
     .contextMenu-item:hover {
@@ -144,7 +144,7 @@
     }
 }
 
-.mainView-innerContent, .mainView-innerContent-dark {
+.mainView-innerContent, .mainView-innerContent-Dark {
     display: contents;
     flex-direction: row;
     position: relative;
@@ -175,7 +175,7 @@
 .mainView-libraryHandle {
     background-color: $light-gray;
 }
-.mainView-innerContent-dark 
+.mainView-innerContent-Dark 
 {
     .propertiesView {
         background-color: #252525;
@@ -198,7 +198,7 @@
         background: #353535;
     }
 }
-.mainView-container-dark {
+.mainView-container-Dark {
     .contextMenu-cont {
         background: $medium-gray;
         color: $white;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index d854f118f..c99ba447c 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -26,7 +26,7 @@ import { HistoryUtil } from '../util/History';
 import { Hypothesis } from '../util/HypothesisUtils';
 import { Scripting } from '../util/Scripting';
 import { SelectionManager } from '../util/SelectionManager';
-import { SettingsManager } from '../util/SettingsManager';
+import { SettingsManager, ColorScheme } from '../util/SettingsManager';
 import { SharingManager } from '../util/SharingManager';
 import { SnappingManager } from '../util/SnappingManager';
 import { Transform } from '../util/Transform';
@@ -88,7 +88,7 @@ export class MainView extends React.Component {
     @computed private get topOfMainDocContent() { return this.topOfMainDoc + this.dashboardTabHeight; }
     @computed private get leftScreenOffsetOfMainDocView() { return this.leftMenuWidth() - 2; }
     @computed private get userDoc() { return Doc.UserDoc(); }
-    @computed private get darkScheme() { return BoolCast(CurrentUserUtils.ActiveDashboard?.darkScheme); }
+    @computed private get colorScheme() { return StrCast(CurrentUserUtils.ActiveDashboard?.colorScheme); }
     @computed private get mainContainer() { return this.userDoc ? CurrentUserUtils.ActiveDashboard : CurrentUserUtils.GuestDashboard; }
     @computed public get mainFreeform(): Opt<Doc> { return (docs => (docs?.length > 1) ? docs[1] : undefined)(DocListCast(this.mainContainer!.data)); }
 
@@ -430,10 +430,10 @@ export class MainView extends React.Component {
         const transform = this._leftMenuFlyoutWidth ? 'translate(-28px, 0px)' : undefined;
         return <>
             {this.leftMenuPanel}
-            <div key="inner" className={`mainView-innerContent${this.darkScheme ? "-dark" : ""}`}>
+            <div key="inner" className={`mainView-innerContent${this.colorScheme}`}>
                 {this.flyout}
                 <div className="mainView-libraryHandle" style={{ display: !this._leftMenuFlyoutWidth ? "none" : undefined }} onPointerDown={this.onFlyoutPointerDown} >
-                    <FontAwesomeIcon icon="chevron-left" color={this.darkScheme ? "white" : "black"} style={{ opacity: "50%" }} size="sm" />
+                    <FontAwesomeIcon icon="chevron-left" color={this.colorScheme === ColorScheme.Dark ? "white" : "black"} style={{ opacity: "50%" }} size="sm" />
                 </div>
                 <div className="mainView-innerContainer" style={{ width: `calc(100% - ${width}px)`, transform: transform }}>
                     <CollectionMenu panelWidth={this.topMenuWidth} panelHeight={this.topMenuHeight} />
@@ -441,7 +441,7 @@ export class MainView extends React.Component {
                     {this.dockingContent}
 
                     <div className="mainView-propertiesDragger" key="props" onPointerDown={this.onPropertiesPointerDown} style={{ right: this._leftMenuFlyoutWidth ? 0 : this.propertiesWidth() - 1 }}>
-                        <FontAwesomeIcon icon={this.propertiesWidth() < 10 ? "chevron-left" : "chevron-right"} color={this.darkScheme ? Colors.WHITE : Colors.BLACK} size="sm" />
+                        <FontAwesomeIcon icon={this.propertiesWidth() < 10 ? "chevron-left" : "chevron-right"} color={this.colorScheme === ColorScheme.Dark ? Colors.WHITE : Colors.BLACK} size="sm" />
                     </div>
                     <div className="properties-container">
                         {this.propertiesWidth() < 10 ? (null) : <PropertiesView styleProvider={DefaultStyleProvider} width={this.propertiesWidth()} height={this.propertiesHeight()} />}
@@ -459,7 +459,7 @@ export class MainView extends React.Component {
                     this._dashUIHeight = r.getBoundingClientRect().height;
                 })).observe(r);
             }} style={{
-                color: this.darkScheme ? "rgb(205,205,205)" : "black",
+                color: this.colorScheme === ColorScheme.Dark ? "rgb(205,205,205)" : "black",
                 height: `calc(100% - ${this.topOfDashUI}px)`,
                 width: "100%",
             }} >
@@ -601,7 +601,7 @@ export class MainView extends React.Component {
     }
 
     render() {
-        return (<div className={"mainView-container" + (this.darkScheme ? "-dark" : "")}
+        return (<div className={`mainView-container${this.colorScheme}`}
             onScroll={() => ((ele) => ele.scrollTop = ele.scrollLeft = 0)(document.getElementById("root")!)}
             ref={r => {
                 r && new _global.ResizeObserver(action(() => { this._windowWidth = r.getBoundingClientRect().width; this._windowHeight = r.getBoundingClientRect().height; })).observe(r);
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 3c88a4830..1eb7a222e 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -22,6 +22,7 @@ import "./StyleProvider.scss";
 import React = require("react");
 import Color = require('color');
 import { lightOrDark } from '../../Utils';
+import { ColorScheme } from '../util/SettingsManager';
 
 export enum StyleLayers {
     Background = "background"
@@ -49,7 +50,7 @@ export enum StyleProp {
     FontSize = "fontSize",                // size of text font
 }
 
-function darkScheme() { return BoolCast(CurrentUserUtils.ActiveDashboard?.darkScheme); }
+function darkScheme() { return CurrentUserUtils.ActiveDashboard?.colorScheme === ColorScheme.Dark; }
 
 function toggleBackground(doc: Doc) {
     UndoManager.RunInBatch(() => runInAction(() => {
@@ -143,7 +144,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
                                     doc.annotationOn ? "#00000015" : // faint interior for collections on PDFs, images, etc
                                         StrCast((props?.renderDepth || 0) > 0 ?
                                             Doc.UserDoc().activeCollectionNestedBackground :
-                                            Doc.UserDoc().activeCollectionBackground)));
+                                            Doc.UserDoc().activeCollectionBackground ?? (darkScheme() ? Colors.BLACK : Colors.WHITE))));
                     break;
                 //if (doc._viewType !== CollectionViewType.Freeform && doc._viewType !== CollectionViewType.Time) return "rgb(62,62,62)";
                 default: docColor = docColor || (darkScheme() ? Colors.DARK_GRAY : Colors.WHITE); break;
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 6a22acae8..3ea190a98 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -353,7 +353,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
     @action setFocused = (doc: Doc) => this._focusedTable = doc;
 
     @action setPreviewDoc = (doc: Opt<Doc>) => {
-        SelectionManager.SelectSchemaView(this, doc);
+        SelectionManager.SelectSchemaViewDoc(doc);
         this._previewDoc = doc;
     }
 
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 97de097e0..a3da0e0e4 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -481,13 +481,16 @@ export class TreeView extends React.Component<TreeViewProps> {
     }
 
     @computed get validExpandViewTypes() {
-        if (this.doc.viewType === CollectionViewType.Docking) return [this.fieldKey];
+        if (this.props.treeView.dashboardMode && Doc.UserDoc().noviceMode) {
+            return [this.doc.viewType === CollectionViewType.Docking ? this.fieldKey : "layout"];
+        }
         const annos = () => DocListCast(this.doc[this.fieldKey + "-annotations"]).length ? "annotations" : "";
         const links = () => DocListCast(this.doc.links).length ? "links" : "";
-        const data = () => this.childDocs && !this.props.treeView.dashboardMode ? this.fieldKey : "";
+        const data = () => this.childDocs ? this.fieldKey : "";
         const aliases = () => this.props.treeView.dashboardMode ? "" : "aliases";
         const fields = () => Doc.UserDoc().noviceMode ? "" : "fields";
-        return [data(), "layout", ...(this.props.treeView.fileSysMode ? [aliases(), links(), annos()] : []), fields()].filter(m => m);
+        const layout = this.doc.viewType === CollectionViewType.Docking ? [] : ["layout"];
+        return [data(), ...layout, ...(this.props.treeView.fileSysMode ? [aliases(), links(), annos()] : []), fields()].filter(m => m);
     }
     @action
     expandNextviewType = () => {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 37444a9dc..b3c57d33a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -7,6 +7,7 @@ import { Cast, NumCast, StrCast } from "../../../../fields/Types";
 import { aggregateBounds } from "../../../../Utils";
 import { CurrentUserUtils } from "../../../util/CurrentUserUtils";
 import React = require("react");
+import { ColorScheme } from "../../../util/SettingsManager";
 
 export interface ViewDefBounds {
     type: string;
@@ -361,7 +362,7 @@ export function computeTimelineLayout(
         groupNames.push({ type: "text", text: toLabel(Math.ceil(maxTime)), x: Math.ceil(maxTime - minTime) * scaling, y: 0, height: fontHeight, fontSize, payload: undefined });
     }
 
-    const divider = { type: "div", color: CurrentUserUtils.ActiveDashboard?.darkScheme ? "dimGray" : "black", x: 0, y: 0, width: panelDim[0], height: -1, payload: undefined };
+    const divider = { type: "div", color: CurrentUserUtils.ActiveDashboard?.colorScheme === ColorScheme.Dark ? "dimGray" : "black", x: 0, y: 0, width: panelDim[0], height: -1, payload: undefined };
     return normalizeResults(panelDim, fontHeight, docMap, poolData, viewDefsToJSX, groupNames, (maxTime - minTime) * scaling, [divider]);
 
     function layoutDocsAtTime(keyDocs: Doc[], key: number) {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 94cf1c5a6..0b12f6c21 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -51,6 +51,7 @@ import "./CollectionFreeFormView.scss";
 import { MarqueeView } from "./MarqueeView";
 import React = require("react");
 import Color = require("color");
+import { ColorScheme } from "../../../util/SettingsManager";
 
 export const panZoomSchema = createSchema({
     _panX: "number",
@@ -1419,6 +1420,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         const renderGridSpace = gridSpace * this.zoomScaling();
         const w = this.props.PanelWidth() + 2 * renderGridSpace;
         const h = this.props.PanelHeight() + 2 * renderGridSpace;
+        const strokeStyle = CurrentUserUtils.ActiveDashboard?.colorScheme === ColorScheme.Dark ? "rgba(255,255,255,0.5)" : "rgba(0, 0,0,0.5)";
         return <canvas className="collectionFreeFormView-grid" width={w} height={h} style={{ transform: `translate(${shiftX}px, ${shiftY}px)` }}
             ref={(el) => {
                 const ctx = el?.getContext('2d');
@@ -1429,7 +1431,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                     ctx.setLineDash(gridSpace > 50 ? [3, 3] : [1, 5]);
                     ctx.clearRect(0, 0, w, h);
                     if (ctx) {
-                        ctx.strokeStyle = "rgba(0, 0, 0, 0.5)";
+                        ctx.strokeStyle = strokeStyle;
                         ctx.beginPath();
                         for (let x = Cx - renderGridSpace; x <= w - Cx; x += renderGridSpace) {
                             ctx.moveTo(x, Cy - h);
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index dfe99ffc8..12493ecc1 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -337,8 +337,9 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
             {this.renderTypes(this._col)}
             {this.renderColors(this._col)}
             <div className="collectionSchema-headerMenu-group">
-                <button onClick={() => { this.deleteColumn(this._col.heading); }}
-                >Hide Column</button>
+                <button onClick={() => { this.deleteColumn(this._col.heading); }}>
+                    Hide Column
+                </button>
             </div>
         </div>;
     }
@@ -353,7 +354,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
     @action setFocused = (doc: Doc) => this._focusedTable = doc;
 
     @action setPreviewDoc = (doc: Opt<Doc>) => {
-        SelectionManager.SelectSchemaView(this, doc);
+        SelectionManager.SelectSchemaViewDoc(doc);
         this._previewDoc = doc;
     }
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 3e15ed661..5d0b91b91 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -25,7 +25,6 @@ import { InteractionUtils } from '../../util/InteractionUtils';
 import { LinkManager } from '../../util/LinkManager';
 import { Scripting } from '../../util/Scripting';
 import { SelectionManager } from "../../util/SelectionManager";
-import { ColorScheme } from "../../util/SettingsManager";
 import { SharingManager } from '../../util/SharingManager';
 import { SnappingManager } from '../../util/SnappingManager';
 import { Transform } from "../../util/Transform";
@@ -50,6 +49,7 @@ import { ScriptingBox } from "./ScriptingBox";
 import { PresBox } from './trails/PresBox';
 import React = require("react");
 import { IconProp } from "@fortawesome/fontawesome-svg-core";
+import { ColorScheme } from "../../util/SettingsManager";
 const { Howl } = require('howler');
 
 interface Window {
@@ -1061,9 +1061,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     render() {
         TraceMobx();
         const highlightIndex = this.props.LayoutTemplateString ? (Doc.IsHighlighted(this.props.Document) ? 6 : 0) : Doc.isBrushedHighlightedDegree(this.props.Document); // bcz: Argh!! need to identify a tree view doc better than a LayoutTemlatString
-        const highlightColor = (Doc.UserDoc().colorScheme === ColorScheme.Dark ?
-            ["transparent", "#65350c", "#65350c", "yellow", "magenta", "cyan", "orange"] :
-            ["transparent", "#4476F7", "#4476F7", "yellow", "magenta", "cyan", "orange"])[highlightIndex];
+        const highlightColor = ["transparent", "rgb(68, 118, 247)", "rgb(68, 118, 247)", "yellow", "magenta", "cyan", "orange"][highlightIndex];
         const highlightStyle = ["solid", "dashed", "solid", "solid", "solid", "solid", "solid"][highlightIndex];
         const excludeTypes = !this.props.treeViewDoc ? [DocumentType.FONTICON, DocumentType.INK] : [DocumentType.FONTICON];
         let highlighting = !this.props.disableDocBrushing && highlightIndex && !excludeTypes.includes(this.layoutDoc.type as any) && this.layoutDoc._viewType !== CollectionViewType.Linear;
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index adcac0aef..520034c3c 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -14,7 +14,6 @@ import { WebField } from '../../../../fields/URLField';
 import { DocumentType } from '../../../documents/DocumentTypes';
 import { Scripting } from "../../../util/Scripting";
 import { SelectionManager } from '../../../util/SelectionManager';
-import { ColorScheme } from '../../../util/SettingsManager';
 import { UndoManager, undoBatch } from '../../../util/UndoManager';
 import { CollectionViewType } from '../../collections/CollectionView';
 import { ContextMenu } from '../../ContextMenu';
@@ -463,7 +462,6 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon
     render() {
         const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
         const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
-        const dark: boolean = Doc.UserDoc().colorScheme === ColorScheme.Dark;
 
         const label = !this.label || !Doc.UserDoc()._showLabel ? (null) :
             <div className="fontIconBox-label" style={{ color: color, backgroundColor: backgroundColor, position: "absolute" }}>
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx
index e519de1c5..149836e93 100644
--- a/src/client/views/nodes/formattedText/DashDocView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocView.tsx
@@ -12,6 +12,7 @@ import { FormattedTextBox } from "./FormattedTextBox";
 import React = require("react");
 import * as ReactDOM from 'react-dom';
 import { observer } from "mobx-react";
+import { ColorScheme } from "../../../util/SettingsManager";
 
 export class DashDocView {
     _fieldWrapper: HTMLSpanElement; // container for label and value
@@ -20,7 +21,7 @@ export class DashDocView {
         this._fieldWrapper = document.createElement("span");
         this._fieldWrapper.style.position = "relative";
         this._fieldWrapper.style.textIndent = "0";
-        this._fieldWrapper.style.border = "1px solid " + StrCast(tbox.layoutDoc.color, (CurrentUserUtils.ActiveDashboard.darkScheme ? "dimGray" : "lightGray"));
+        this._fieldWrapper.style.border = "1px solid " + StrCast(tbox.layoutDoc.color, (CurrentUserUtils.ActiveDashboard?.colorScheme === ColorScheme.Dark ? "dimGray" : "lightGray"));
         this._fieldWrapper.style.width = node.attrs.width;
         this._fieldWrapper.style.height = node.attrs.height;
         this._fieldWrapper.style.display = node.attrs.hidden ? "none" : "inline-block";
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index 404e828ea..652804126 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -17,7 +17,7 @@ import { Docs, DocumentOptions, DocUtils } from '../client/documents/Documents';
 import { DocumentType } from "../client/documents/DocumentTypes";
 import { CurrentUserUtils } from '../client/util/CurrentUserUtils';
 import { Scripting } from '../client/util/Scripting';
-import { SettingsManager } from '../client/util/SettingsManager';
+import { SettingsManager, ColorScheme } from '../client/util/SettingsManager';
 import { Transform } from '../client/util/Transform';
 import { UndoManager } from "../client/util/UndoManager";
 import { TabDocView } from '../client/views/collections/TabDocView';
@@ -403,7 +403,7 @@ export class MobileInterface extends React.Component {
         const freeformDoc = CurrentUserUtils.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions);
         const dashboardDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600 }], { title: `Dashboard ${dashboardCount}` }, id, "row");
 
-        const toggleTheme = ScriptField.MakeScript(`self.darkScheme = !self.darkScheme`);
+        const toggleTheme = ScriptField.MakeScript(`self.colorScheme = self.colorScheme ? undefined: ${ColorScheme.Dark}}`);
         const toggleComic = ScriptField.MakeScript(`toggleComicMode()`);
         const cloneDashboard = ScriptField.MakeScript(`cloneDashboard()`);
         dashboardDoc.contextMenuScripts = new List<ScriptField>([toggleTheme!, toggleComic!, cloneDashboard!]);
-- 
cgit v1.2.3-70-g09d2


From 643df77e6366b7164307ffe195ed9de83b68e5ae Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 24 Sep 2021 01:21:38 -0400
Subject: wrapped Color() calls in DashColor() to catch exceptions for color
 strings no found

---
 src/Utils.ts                                                  | 11 ++++++++++-
 src/client/views/StyleProvider.tsx                            |  5 ++---
 src/client/views/collections/TabDocView.tsx                   |  3 +--
 .../collections/collectionFreeForm/CollectionFreeFormView.tsx |  1 -
 src/client/views/nodes/CollectionFreeFormDocumentView.tsx     |  5 ++---
 src/client/views/nodes/trails/PresBox.tsx                     |  3 +--
 src/fields/Doc.ts                                             |  9 ++++-----
 src/fields/RichTextUtils.ts                                   |  4 ++--
 8 files changed, 22 insertions(+), 19 deletions(-)

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

diff --git a/src/Utils.ts b/src/Utils.ts
index db33875ea..7ec4f69f3 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -573,10 +573,19 @@ export function simulateMouseClick(element: Element | null | undefined, x: numbe
     }
 }
 
+export function DashColor(color: string) {
+    try {
+        return Color(color.toLowerCase());
+    } catch (e) {
+        console.log("COLOR error:", e);
+        return Color("red");
+    }
+}
+
 export function lightOrDark(color: any) {
     const nonAlphaColor = color.startsWith("#") ? (color as string).substring(0, 7) :
         color.startsWith("rgba") ? color.replace(/,.[^,]*\)/, ")").replace("rgba", "rgb") : color;
-    const col = Color(nonAlphaColor.toLowerCase()).rgb();
+    const col = DashColor(nonAlphaColor).rgb();
     const colsum = (col.red() + col.green() + col.blue());
     if (colsum / col.alpha() > 400 || col.alpha() < 0.25) return Colors.DARK_GRAY;
     else return Colors.WHITE;
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 507c3d2b2..cd6e11bda 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -7,7 +7,7 @@ import { Doc, Opt, StrListCast } from "../../fields/Doc";
 import { List } from '../../fields/List';
 import { listSpec } from '../../fields/Schema';
 import { BoolCast, Cast, NumCast, StrCast } from "../../fields/Types";
-import { lightOrDark } from '../../Utils';
+import { lightOrDark, DashColor } from '../../Utils';
 import { DocumentType } from '../documents/DocumentTypes';
 import { CurrentUserUtils } from '../util/CurrentUserUtils';
 import { ColorScheme } from '../util/SettingsManager';
@@ -21,7 +21,6 @@ import { FieldViewProps } from './nodes/FieldView';
 import { SliderBox } from './nodes/SliderBox';
 import "./StyleProvider.scss";
 import React = require("react");
-import Color = require('color');
 
 export enum StyleLayers {
     Background = "background"
@@ -148,7 +147,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
                 //if (doc._viewType !== CollectionViewType.Freeform && doc._viewType !== CollectionViewType.Time) return "rgb(62,62,62)";
                 default: docColor = docColor || (darkScheme() ? Colors.DARK_GRAY : Colors.WHITE); break;
             }
-            if (docColor && (!doc || props?.layerProvider?.(doc) === false)) docColor = Color(docColor.toLowerCase()).fade(0.5).toString();
+            if (docColor && (!doc || props?.layerProvider?.(doc) === false)) docColor = DashColor(docColor).fade(0.5).toString();
             return docColor;
         }
         case StyleProp.BoxShadow: {
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index f63e5f844..eb95bb913 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -22,6 +22,7 @@ import { SelectionManager } from '../../util/SelectionManager';
 import { SnappingManager } from '../../util/SnappingManager';
 import { Transform } from '../../util/Transform';
 import { undoBatch, UndoManager } from "../../util/UndoManager";
+import { Colors, Shadows } from '../global/globalEnums';
 import { LightboxView } from '../LightboxView';
 import { DocFocusOptions, DocumentView, DocumentViewProps } from "../nodes/DocumentView";
 import { PinProps, PresBox, PresMovement } from '../nodes/trails';
@@ -32,8 +33,6 @@ import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormV
 import { CollectionView, CollectionViewType } from './CollectionView';
 import "./TabDocView.scss";
 import React = require("react");
-import Color = require('color');
-import { Colors, Shadows } from '../global/globalEnums';
 const _global = (window /* browser */ || global /* node */) as any;
 
 interface TabDocViewProps {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 0b12f6c21..5b9f6139f 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -50,7 +50,6 @@ import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCurso
 import "./CollectionFreeFormView.scss";
 import { MarqueeView } from "./MarqueeView";
 import React = require("react");
-import Color = require("color");
 import { ColorScheme } from "../../../util/SettingsManager";
 
 export const panZoomSchema = createSchema({
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index c76a051f7..fe34d6687 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -7,7 +7,7 @@ import { listSpec } from "../../../fields/Schema";
 import { ComputedField } from "../../../fields/ScriptField";
 import { Cast, NumCast, StrCast } from "../../../fields/Types";
 import { TraceMobx } from "../../../fields/util";
-import { numberRange } from "../../../Utils";
+import { DashColor, numberRange } from "../../../Utils";
 import { DocumentManager } from "../../util/DocumentManager";
 import { SelectionManager } from "../../util/SelectionManager";
 import { Transform } from "../../util/Transform";
@@ -18,7 +18,6 @@ import { StyleProp } from "../StyleProvider";
 import "./CollectionFreeFormDocumentView.scss";
 import { DocumentView, DocumentViewProps } from "./DocumentView";
 import React = require("react");
-import Color = require("color");
 
 export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
     dataProvider?: (doc: Doc, replica: string) => { x: number, y: number, zIndex?: number, opacity?: number, highlight?: boolean, z: number, transition?: string } | undefined;
@@ -165,7 +164,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
             PanelHeight: this.panelHeight,
         };
         const background = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
-        const mixBlendMode = StrCast(this.layoutDoc.mixBlendMode) as any || (typeof background === "string" && Color(background.toLowerCase()).alpha() !== 1 ? "multiply" : undefined);
+        const mixBlendMode = StrCast(this.layoutDoc.mixBlendMode) as any || (typeof background === "string" && DashColor(background).alpha() !== 1 ? "multiply" : undefined);
         return <div className={"collectionFreeFormDocumentView-container"}
             style={{
                 outline: this.Highlight ? "orange solid 2px" : "",
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index add84ff83..8e61a224c 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -30,8 +30,7 @@ import { LightboxView } from "../../LightboxView";
 import { CollectionFreeFormDocumentView } from "../CollectionFreeFormDocumentView";
 import { FieldView, FieldViewProps } from '../FieldView';
 import "./PresBox.scss";
-import Color = require("color");
-import { PresEffect, PresStatus, PresMovement } from "./PresEnums";
+import { PresEffect, PresMovement, PresStatus } from "./PresEnums";
 
 export class PinProps {
     audioRange?: boolean;
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 57bd0f46f..d8690831f 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -1,15 +1,17 @@
+import { IconProp } from "@fortawesome/fontawesome-svg-core";
 import { saveAs } from "file-saver";
 import { action, computed, observable, ObservableMap, runInAction } from "mobx";
 import { computedFn } from "mobx-utils";
 import { alias, map, serializable } from "serializr";
 import { DocServer } from "../client/DocServer";
 import { DocumentType } from "../client/documents/DocumentTypes";
+import { CurrentUserUtils } from "../client/util/CurrentUserUtils";
 import { LinkManager } from "../client/util/LinkManager";
 import { Scripting, scriptingGlobal } from "../client/util/Scripting";
 import { SelectionManager } from "../client/util/SelectionManager";
 import { afterDocDeserialize, autoObject, Deserializable, SerializationHelper } from "../client/util/SerializationHelper";
 import { UndoManager } from "../client/util/UndoManager";
-import { intersectRect, Utils } from "../Utils";
+import { DashColor, intersectRect, Utils } from "../Utils";
 import { DateField } from "./DateField";
 import { Copy, HandleUpdate, Id, OnUpdate, Parent, Self, SelfProxy, ToScriptString, ToString, Update } from "./FieldSymbols";
 import { List } from "./List";
@@ -23,9 +25,6 @@ import { Cast, FieldValue, NumCast, StrCast, ToConstructor } from "./Types";
 import { AudioField, ImageField, PdfField, VideoField, WebField } from "./URLField";
 import { deleteProperty, GetEffectiveAcl, getField, getter, inheritParentAcls, makeEditable, makeReadOnly, normalizeEmail, setter, SharingPermissions, updateFunction } from "./util";
 import JSZip = require("jszip");
-import { CurrentUserUtils } from "../client/util/CurrentUserUtils";
-import { IconProp } from "@fortawesome/fontawesome-svg-core";
-import Color = require("color");
 
 export namespace Field {
     export function toKeyValueString(doc: Doc, key: string): string {
@@ -1089,7 +1088,7 @@ export namespace Doc {
 
     export function matchFieldValue(doc: Doc, key: string, value: any): boolean {
         if (Utils.HasTransparencyFilter(value)) {
-            const isTransparent = (color: string) => color !== "" && (Color(color).alpha() !== 1);
+            const isTransparent = (color: string) => color !== "" && (DashColor(color).alpha() !== 1);
             return isTransparent(StrCast(doc[key]));
         }
         if (typeof value === "string") {
diff --git a/src/fields/RichTextUtils.ts b/src/fields/RichTextUtils.ts
index 0b5f14d74..a19be5df9 100644
--- a/src/fields/RichTextUtils.ts
+++ b/src/fields/RichTextUtils.ts
@@ -2,7 +2,7 @@ import { AssertionError } from "assert";
 import { docs_v1 } from "googleapis";
 import { Fragment, Mark, Node } from "prosemirror-model";
 import { sinkListItem } from "prosemirror-schema-list";
-import { Utils } from "../Utils";
+import { Utils, DashColor } from "../Utils";
 import { Docs, DocUtils } from "../client/documents/Documents";
 import { schema } from "../client/views/nodes/formattedText/schema_rts";
 import { GooglePhotos } from "../client/apis/google_docs/GooglePhotosClientUtils";
@@ -482,7 +482,7 @@ export namespace RichTextUtils {
         }
 
         const fromHex = (color: string): docs_v1.Schema$OptionalColor => {
-            const c = Color(color);
+            const c = DashColor(color);
             return fromRgb.convert(c.red(), c.green(), c.blue());
         };
 
-- 
cgit v1.2.3-70-g09d2


From f70aa7ba42153b2c283b7b9af04a5e75ea19a7ad Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 27 Sep 2021 09:49:26 -0400
Subject: made docs with no author Admin for everyone. fixed run-time warnings:
 not memoized accesses when panning, NaNs drawing some labelBoxes, filter box
 filter boolean options,

---
 .../collectionFreeForm/CollectionFreeFormView.tsx    | 20 +++++++++++++-------
 src/client/views/nodes/DocumentView.tsx              |  6 +++---
 src/client/views/nodes/FilterBox.tsx                 |  6 +++---
 src/fields/util.ts                                   |  3 ++-
 4 files changed, 21 insertions(+), 14 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 5b9f6139f..b2db1168d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,4 +1,4 @@
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
+import { action, computed, IReactionDisposer, observable, reaction, runInAction, ObservableMap } from "mobx";
 import { observer } from "mobx-react";
 import { computedFn } from "mobx-utils";
 import { DateField } from "../../../../fields/DateField";
@@ -811,14 +811,14 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     setPan(panX: number, panY: number, panTime: number = 0, clamp: boolean = false) {
         if (!this.isAnnotationOverlay && clamp) {
             // this section wraps the pan position, horizontally and/or vertically whenever the content is panned out of the viewing bounds
-            const docs = this.childLayoutPairs.filter(pair => pair.layout instanceof Doc).map(pair => pair.layout);
-            const measuredDocs = docs.filter(doc => doc && this.childDataProvider(doc, "") && this.childSizeProvider(doc, "")).
-                map(doc => ({ ...this.childDataProvider(doc, ""), ...this.childSizeProvider(doc, "") }));
+            const docs = this.childLayoutPairs.map(pair => pair.layout).filter(doc => doc instanceof Doc);
+            const measuredDocs = docs.map(doc => ({ pos: this.childPositionProviderUnmemoized(doc, ""), size: this.childSizeProviderUnmemoized(doc, "") }))
+                .filter(({ pos, size }) => pos && size).map(({ pos, size }) => ({ pos: pos!, size: size! }));
             if (measuredDocs.length) {
-                const ranges = measuredDocs.reduce(({ xrange, yrange }, { x, y, width, height }) =>  // computes range of content
+                const ranges = measuredDocs.reduce(({ xrange, yrange }, { pos, size }) =>  // computes range of content
                     ({
-                        xrange: { min: Math.min(xrange.min, x), max: Math.max(xrange.max, x + width) },
-                        yrange: { min: Math.min(yrange.min, y), max: Math.max(yrange.max, y + height) }
+                        xrange: { min: Math.min(xrange.min, pos.x), max: Math.max(xrange.max, pos.x + (size.width || 0)) },
+                        yrange: { min: Math.min(yrange.min, pos.y), max: Math.max(yrange.max, pos.y + (size.height || 0)) }
                     })
                     , {
                         xrange: { min: Number.MAX_VALUE, max: -Number.MAX_VALUE },
@@ -1105,10 +1105,16 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         }
     }
 
+    childPositionProviderUnmemoized = (doc: Doc, replica: string) => {
+        return this._layoutPoolData.get(doc[Id] + (replica || ""));
+    }
     childDataProvider = computedFn(function childDataProvider(this: any, doc: Doc, replica: string) {
         return this._layoutPoolData.get(doc[Id] + (replica || ""));
     }.bind(this));
 
+    childSizeProviderUnmemoized = (doc: Doc, replica: string) => {
+        return this._layoutSizeData.get(doc[Id] + (replica || ""));
+    }
     childSizeProvider = computedFn(function childSizeProvider(this: any, doc: Doc, replica: string) {
         return this._layoutSizeData.get(doc[Id] + (replica || ""));
     }.bind(this));
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 6a7cd108a..1187521b3 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1147,10 +1147,10 @@ export class DocumentView extends React.Component<DocumentViewProps> {
     @computed get nativeScaling() {
         if (this.shouldNotScale) return 1;
         const minTextScale = this.Document.type === DocumentType.RTF ? 0.1 : 0;
-        if (this.fitWidth || this.props.PanelHeight() / this.effectiveNativeHeight > this.props.PanelWidth() / this.effectiveNativeWidth) {
-            return Math.max(minTextScale, this.props.PanelWidth() / this.effectiveNativeWidth);  // width-limited or fitWidth
+        if (this.fitWidth || this.props.PanelHeight() / (this.effectiveNativeHeight || 1) > this.props.PanelWidth() / (this.effectiveNativeWidth || 1)) {
+            return Math.max(minTextScale, this.props.PanelWidth() / (this.effectiveNativeWidth || 1));  // width-limited or fitWidth
         }
-        return Math.max(minTextScale, this.props.PanelHeight() / this.effectiveNativeHeight); // height-limited or unscaled
+        return Math.max(minTextScale, this.props.PanelHeight() / (this.effectiveNativeHeight || 1)); // height-limited or unscaled
     }
 
     @computed get panelWidth() { return this.effectiveNativeWidth ? this.effectiveNativeWidth * this.nativeScaling : this.props.PanelWidth(); }
diff --git a/src/client/views/nodes/FilterBox.tsx b/src/client/views/nodes/FilterBox.tsx
index e9f19bf9e..2041c7399 100644
--- a/src/client/views/nodes/FilterBox.tsx
+++ b/src/client/views/nodes/FilterBox.tsx
@@ -377,9 +377,9 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc
             </div>
 
             <div className="filterBox-select-bool">
-                <select className="filterBox-selection" onChange={this.changeBool}>
-                    <option value="AND" key="AND" selected={(FilterBox.targetDoc.currentFilter as Doc)?.filterBoolean === "AND"}>AND</option>
-                    <option value="OR" key="OR" selected={(FilterBox.targetDoc.currentFilter as Doc)?.filterBoolean === "OR"}>OR</option>
+                <select className="filterBox-selection" onChange={this.changeBool} defaultValue={StrCast((FilterBox.targetDoc.currentFilter as Doc)?.filterBoolean)}>
+                    <option value="AND" key="AND">AND</option>
+                    <option value="OR" key="OR">OR</option>
                 </select>
                 <div className="filterBox-select-text">filters together</div>
             </div>
diff --git a/src/fields/util.ts b/src/fields/util.ts
index 439c4d333..3590c2dea 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -191,7 +191,8 @@ let HierarchyMapping: Map<symbol, number> | undefined;
 function getEffectiveAcl(target: any, user?: string): symbol {
     const targetAcls = target[AclSym];
     const userChecked = user || Doc.CurrentUserEmail;    // if the current user is the author of the document / the current user is a member of the admin group
-    if (userChecked === (target.__fields?.author || target.author)) return AclAdmin; // target may be a Doc of Proxy, so check __fields.author and .author
+    const targetAuthor = (target.__fields?.author || target.author); // target may be a Doc of Proxy, so check __fields.author and .author
+    if (userChecked === targetAuthor || !targetAuthor) return AclAdmin;
     if (SnappingManager.GetCachedGroupByName("Admin")) return AclAdmin;
 
     if (targetAcls && Object.keys(targetAcls).length) {
-- 
cgit v1.2.3-70-g09d2


From a46d659229120cdb139f716b1a48ec6b887807bb Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 6 Oct 2021 19:40:40 -0400
Subject: fixed lightbox view to shrinkwrap collections when opened, but not
 force fit contents to allow users to navigate.   fixed comparison box to show
 context of maker'/annotation documents.  fixed ink pointer events to honor
 pointerEvents prop (eg when embedded in a comparisonBox that turns them off).

---
 src/client/views/InkingStroke.tsx                            |  2 +-
 src/client/views/LightboxView.tsx                            |  4 ++--
 .../collectionFreeForm/CollectionFreeFormView.tsx            |  2 +-
 .../collections/collectionSchema/CollectionSchemaCells.tsx   |  1 -
 src/client/views/nodes/ComparisonBox.tsx                     | 12 +++++++++---
 5 files changed, 13 insertions(+), 8 deletions(-)

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

diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index d05a4a6e4..752db1413 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -209,7 +209,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
             inkStrokeWidth, inkStrokeWidth + (highlightIndex && closed && (new Color(fillColor)).alpha() < 1 ? 6 : 15),
             StrCast(this.layoutDoc.strokeLineJoin), StrCast(this.layoutDoc.strokeLineCap),
             StrCast(this.layoutDoc.strokeBezier), !closed ? "none" : fillColor === "transparent" ? "none" : fillColor, startMarker, endMarker,
-            undefined, inkScaleX, inkScaleY, "", this.props.layerProvider?.(this.props.Document) === false ? "none" : "visiblepainted", 0.0, false);
+            undefined, inkScaleX, inkScaleY, "", this.props.pointerEvents ?? (this.props.layerProvider?.(this.props.Document) === false ? "none" : "visiblepainted"), 0.0, false);
         // Set of points rendered upon the ink that can be added if a user clicks on one.
 
         return (
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index ec30a6a5d..ec3bf6c18 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -214,7 +214,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
                         LightboxView.SetLightboxDoc(undefined);
                     }
                 }}  >
-                
+
                 <div className="lightboxView-contents" style={{
                     left: this.leftBorder,
                     top: this.topBorder,
@@ -237,7 +237,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
                         DataDoc={undefined}
                         LayoutTemplate={LightboxView.LightboxDocTemplate}
                         addDocument={undefined}
-                        fitContentsToDoc={this.fitToBox}
+                        // fitContentsToDoc={this.fitToBox} // bcz: why do we want this?  when we initially open a colletion, we shrinkwrap it which allows for user navigation.  if we later encounter a collection, it's not clear to me that we want to make it either shrinkwrap or fitContents...
                         isDocumentActive={returnFalse}
                         isContentActive={returnTrue}
                         addDocTab={this.addDocTab}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index b2db1168d..7dcd63b80 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1036,7 +1036,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             showTitle={this.props.childShowTitle}
             dontRegisterView={this.props.dontRenderDocuments || this.props.dontRegisterView}
             pointerEvents={this.backgroundActive || this.props.childPointerEvents ? "all" :
-                (this.props.viewDefDivClick || (engine === "pass" && !this.props.isSelected(true))) ? "none" : undefined}
+                (this.props.viewDefDivClick || (engine === "pass" && !this.props.isSelected(true))) ? "none" : this.props.pointerEvents}
             jitterRotation={this.props.styleProvider?.(childLayout, this.props, StyleProp.JitterRotation) || 0}
         //fitToBox={this.props.fitToBox || BoolCast(this.props.freezeChildDimensions)} // bcz: check this
         />;
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx
index ed196349e..0274cc49c 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx
@@ -103,7 +103,6 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
         this.props.changeFocusedCellByIndex(this.props.row, this.props.col);
         this.props.setPreviewDoc(this.props.rowProps.original);
 
-        console.log("click cell");
         let url: string;
         if (url = StrCast(this.props.rowProps.row.href)) {
             try {
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index f23c68409..b1aada158 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -89,14 +89,20 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
         };
         const displayDoc = (which: string) => {
             var whichDoc = Cast(this.dataDoc[which], Doc, null);
-            if (whichDoc?.type === DocumentType.MARKER) whichDoc = Cast(whichDoc.annotationOn, Doc, null);
+            //if (whichDoc?.type === DocumentType.MARKER) 
+            const targetDoc = Cast(whichDoc.annotationOn, Doc, null) ?? whichDoc;
             return whichDoc ? <>
-                <DocumentView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit}
+                <DocumentView
+                    ref={(r) => {
+                        whichDoc !== targetDoc && r?.focus(targetDoc);
+                    }}
+                    {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit}
                     isContentActive={returnFalse}
                     isDocumentActive={returnFalse}
                     styleProvider={this.docStyleProvider}
-                    Document={whichDoc}
+                    Document={targetDoc}
                     DataDoc={undefined}
+                    hideLinkButton={true}
                     pointerEvents={"none"} />
                 {clearButton(which)}
             </> :  // placeholder image if doc is missing
-- 
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/collectionFreeForm/CollectionFreeFormView.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

