From a5a4cc47944766a29e8b6dc2364c35de52c1400d Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 17 Sep 2021 02:40:40 -0400
Subject: fixed top-down resizing of web pages and pdfs.   turned off Cors
 option for novices.

---
 src/client/views/DocumentDecorations.tsx | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

(limited to 'src/client/views/DocumentDecorations.tsx')

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 6f9697703..3d6f157b6 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -280,6 +280,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
             case "documentDecorations-topResizer":
                 dY = -1;
                 dH = -move[1];
+                dragBottom = true;
                 break;
             case "documentDecorations-bottomLeftResizer":
                 dX = -1;
@@ -387,10 +388,10 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
         this._inkDragDocs.map(oldbds => ({ oldbds, inkPts: Cast(oldbds.doc.data, InkField)?.inkData || [] }))
             .forEach(({ oldbds: { doc, x, y, width, height }, inkPts }) => {
                 Doc.GetProto(doc).data = new InkField(inkPts.map(ipt =>  // (new x — oldx) + newWidth * (oldxpoint /oldWidth)
-                    ({
-                        X: (NumCast(doc.x) - x) + NumCast(doc.width) * ipt.X / width,
-                        Y: (NumCast(doc.y) - y) + NumCast(doc.height) * ipt.Y / height
-                    })));
+                ({
+                    X: (NumCast(doc.x) - x) + NumCast(doc.width) * ipt.X / width,
+                    Y: (NumCast(doc.y) - y) + NumCast(doc.height) * ipt.Y / height
+                })));
                 Doc.SetNativeWidth(doc, undefined);
                 Doc.SetNativeHeight(doc, undefined);
             });
-- 
cgit v1.2.3-70-g09d2


From 240f2c9bda4a9a32d84d1de93820f6fbc8eef458 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 20 Sep 2021 12:24:34 -0400
Subject: fixed equations/ink to work properly when not fitwidth in lightbox. 
 don't show titles for ink or equations using fields now.

---
 src/client/documents/Documents.ts                  |  8 +++++---
 src/client/views/DocumentDecorations.tsx           | 20 ++++++++++--------
 src/client/views/nodes/DocumentView.tsx            |  2 +-
 src/client/views/nodes/EquationBox.scss            |  3 +++
 src/client/views/nodes/EquationBox.tsx             |  7 ++++++-
 .../views/nodes/formattedText/FormattedTextBox.tsx | 24 ++++++++++------------
 6 files changed, 37 insertions(+), 27 deletions(-)

(limited to 'src/client/views/DocumentDecorations.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index e8185400e..fafbc4a7d 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -430,7 +430,7 @@ export namespace Docs {
             }],
             [DocumentType.EQUATION, {
                 layout: { view: EquationBox, dataField: defaultDataKey },
-                options: { links: ComputedField.MakeFunction("links(self)") as any }
+                options: { links: ComputedField.MakeFunction("links(self)") as any, hideResizeHandles: true, hideDecorationTitle: true }
             }],
             [DocumentType.FUNCPLOT, {
                 layout: { view: FunctionPlotBox, dataField: defaultDataKey },
@@ -463,9 +463,9 @@ export namespace Docs {
                 layout: { view: CollectionView, dataField: defaultDataKey },
                 options: { links: ComputedField.MakeFunction("links(self)") as any, hideLinkButton: true }
             }],
-            [DocumentType.INK, {
+            [DocumentType.INK, { // NOTE: this is unused!! ink fields are filled in directly within the InkDocument() method
                 layout: { view: InkingStroke, dataField: defaultDataKey },
-                options: { _fontFamily: "cursive", backgroundColor: "transparent", links: ComputedField.MakeFunction("links(self)") as any }
+                options: { links: ComputedField.MakeFunction("links(self)") as any }
             }],
             [DocumentType.SCREENSHOT, {
                 layout: { view: ScreenshotBox, dataField: defaultDataKey },
@@ -716,6 +716,8 @@ export namespace Docs {
             I.type = DocumentType.INK;
             I.layout = InkingStroke.LayoutString("data");
             I.color = color;
+            I.hideDecorationTitle = true;  // don't show title when selected
+            I.hideOpenButton = true; // don't show open full screen button when selected
             I.fillColor = fillColor;
             I.strokeWidth = strokeWidth;
             I.strokeBezier = strokeBezier;
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 3d6f157b6..d785d5419 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -388,10 +388,10 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
         this._inkDragDocs.map(oldbds => ({ oldbds, inkPts: Cast(oldbds.doc.data, InkField)?.inkData || [] }))
             .forEach(({ oldbds: { doc, x, y, width, height }, inkPts }) => {
                 Doc.GetProto(doc).data = new InkField(inkPts.map(ipt =>  // (new x — oldx) + newWidth * (oldxpoint /oldWidth)
-                ({
-                    X: (NumCast(doc.x) - x) + NumCast(doc.width) * ipt.X / width,
-                    Y: (NumCast(doc.y) - y) + NumCast(doc.height) * ipt.Y / height
-                })));
+                    ({
+                        X: (NumCast(doc.x) - x) + NumCast(doc.width) * ipt.X / width,
+                        Y: (NumCast(doc.y) - y) + NumCast(doc.height) * ipt.Y / height
+                    })));
                 Doc.SetNativeWidth(doc, undefined);
                 Doc.SetNativeHeight(doc, undefined);
             });
@@ -421,7 +421,9 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
         if (SnappingManager.GetIsDragging() || bounds.r - bounds.x < 1 || bounds.x === Number.MAX_VALUE || !seldoc || this._hidden || isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) {
             return (null);
         }
-        const canOpen = SelectionManager.Views().some(docView => !docView.props.Document._stayInCollection && !docView.props.Document.isGroup);
+        const hideResizers = seldoc.props.hideResizeHandles || seldoc.rootDoc.hideResizeHandles;
+        const hideTitle = seldoc.props.hideDecorationTitle || seldoc.rootDoc.hideDecorationTitle;
+        const canOpen = SelectionManager.Views().some(docView => !docView.props.Document._stayInCollection && !docView.props.Document.isGroup && !docView.props.Document.hideOpenButton);
         const canDelete = SelectionManager.Views().some(docView => {
             const collectionAcl = docView.props.ContainingCollectionView ? GetEffectiveAcl(docView.props.ContainingCollectionDoc?.[DataSym]) : AclEdit;
             return (!docView.rootDoc._stayInCollection || docView.rootDoc.isInkMask) &&
@@ -473,12 +475,12 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
                     top: bounds.y - this._resizeBorderWidth / 2 - this._titleHeight,
                 }}>
                     {!canDelete ? <div /> : topBtn("close", "times", undefined, this.onCloseClick, "Close")}
-                    {seldoc.props.hideDecorationTitle || seldoc.props.Document.type === DocumentType.EQUATION ? (null) : titleArea}
-                    {seldoc.props.hideResizeHandles || seldoc.props.Document.type === DocumentType.EQUATION ? (null) :
+                    {hideTitle ? (null) : titleArea}{!canOpen ? (null) : topBtn("open", "external-link-alt", this.onMaximizeDown, undefined, "Open in Tab (ctrl: as alias, shift: in new collection)")}
+
+                    {hideResizers ? (null) :
                         <>
-                            {SelectionManager.Views().length !== 1 || seldoc.Document.type === DocumentType.INK ? (null) :
+                            {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`)}
-                            {!canOpen ? (null) : topBtn("open", "external-link-alt", this.onMaximizeDown, undefined, "Open in Tab (ctrl: as alias, shift: in new collection)")}
                             <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()} />
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index d2b4b0348..60ceac007 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1168,7 +1168,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         }
         const xf = (this.docView?.props.ScreenToLocalTransform().scale(this.nativeScaling)).inverse();
         const [[left, top], [right, bottom]] = [xf.transformPoint(0, 0), xf.transformPoint(this.panelWidth, this.panelHeight)];
-        if (this.docView.props.LayoutTemplateString?.includes("LinkAnchorBox")) {
+        if (this.docView.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
             const docuBox = this.docView.ContentDiv.getElementsByClassName("linkAnchorBox-cont");
             if (docuBox.length) return docuBox[0].getBoundingClientRect();
         }
diff --git a/src/client/views/nodes/EquationBox.scss b/src/client/views/nodes/EquationBox.scss
index e69de29bb..6c9d53d10 100644
--- a/src/client/views/nodes/EquationBox.scss
+++ b/src/client/views/nodes/EquationBox.scss
@@ -0,0 +1,3 @@
+.equationBox-cont {
+    transform-origin: top left;
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/EquationBox.tsx b/src/client/views/nodes/EquationBox.tsx
index 11ef6562f..f1f802c13 100644
--- a/src/client/views/nodes/EquationBox.tsx
+++ b/src/client/views/nodes/EquationBox.tsx
@@ -84,8 +84,13 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps, EquationDo
     }
     render() {
         TraceMobx();
-        return (<div onPointerDown={e => !e.ctrlKey && e.stopPropagation()}
+        const scale = (this.props.scaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1);
+        return (<div className="equationBox-cont"
+            onPointerDown={e => !e.ctrlKey && e.stopPropagation()}
             style={{
+                transform: `scale(${scale})`,
+                width: `${100 / scale}%`,
+                height: `${100 / scale}%`,
                 pointerEvents: !this.props.isSelected() ? "none" : undefined,
             }}
             onKeyDown={e => e.stopPropagation()}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 3e5c4456c..ebd509669 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1469,19 +1469,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         }
     }
     tryUpdateScrollHeight = () => {
-        if (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath())) {
-            const margins = 2 * NumCast(this.layoutDoc._yMargin, this.props.yPadding || 0);
-            const children = this.ProseRef?.children.length ? Array.from(this.ProseRef.children[0].children) : undefined;
-            if (children) {
-                const proseHeight = !this.ProseRef ? 0 : children.reduce((p, child) => p + Number(getComputedStyle(child).height.replace("px", "")), margins);
-                const scrollHeight = this.ProseRef && Math.min(NumCast(this.layoutDoc.docMaxAutoHeight, proseHeight), proseHeight);
-                if (scrollHeight && this.props.renderDepth && !this.props.dontRegisterView) {  // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
-                    const setScrollHeight = () => this.rootDoc[this.fieldKey + "-scrollHeight"] = scrollHeight;
-                    if (this.rootDoc === this.layoutDoc.doc || this.layoutDoc.resolvedDataDoc) {
-                        setScrollHeight();
-                    } else {
-                        setTimeout(setScrollHeight, 10); // if we have a template that hasn't been resolved yet, we can't set the height or we'd be setting it on the unresolved template.  So set a timeout and hope its arrived...
-                    }
+        const margins = 2 * NumCast(this.layoutDoc._yMargin, this.props.yPadding || 0);
+        const children = this.ProseRef?.children.length ? Array.from(this.ProseRef.children[0].children) : undefined;
+        if (children) {
+            const proseHeight = !this.ProseRef ? 0 : children.reduce((p, child) => p + Number(getComputedStyle(child).height.replace("px", "")), margins);
+            const scrollHeight = this.ProseRef && Math.min(NumCast(this.layoutDoc.docMaxAutoHeight, proseHeight), proseHeight);
+            if (scrollHeight && this.props.renderDepth && !this.props.dontRegisterView) {  // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
+                const setScrollHeight = () => this.rootDoc[this.fieldKey + "-scrollHeight"] = scrollHeight;
+                if (this.rootDoc === this.layoutDoc.doc || this.layoutDoc.resolvedDataDoc) {
+                    setScrollHeight();
+                } else {
+                    setTimeout(setScrollHeight, 10); // if we have a template that hasn't been resolved yet, we can't set the height or we'd be setting it on the unresolved template.  So set a timeout and hope its arrived...
                 }
             }
         }
-- 
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/DocumentDecorations.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 dab30019adba202ba2266acf046ad3c41f90c6e2 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 27 Sep 2021 14:06:51 -0400
Subject: videoBox fitsWidth properly in lightbox.  documentdecorations less
 hacky for deciding when to allow nativewidth/height changes by adding props
 to document type templates.  fixed height of properties pane & updated CSS
 for leftflyout & properties pane.  marquees no longer oveflow image/video
 boxes.

---
 src/client/documents/Documents.ts        | 12 +++++++++---
 src/client/views/DocumentDecorations.tsx | 29 +++++++++++------------------
 src/client/views/GlobalKeyHandler.ts     |  2 +-
 src/client/views/MainView.scss           |  9 ++++-----
 src/client/views/MainView.tsx            | 12 ++++++------
 src/client/views/nodes/ImageBox.scss     |  1 +
 src/client/views/nodes/ImageBox.tsx      |  4 ++--
 src/client/views/nodes/VideoBox.scss     | 15 ++++++++++++++-
 src/client/views/nodes/VideoBox.tsx      | 22 +++++++++++++++-------
 src/client/views/nodes/WebBox.tsx        |  8 ++++----
 10 files changed, 67 insertions(+), 47 deletions(-)

(limited to 'src/client/views/DocumentDecorations.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 8ac647b99..403620bdd 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -352,7 +352,7 @@ export namespace Docs {
         const TemplateMap: TemplateMap = new Map([
             [DocumentType.RTF, {
                 layout: { view: FormattedTextBox, dataField: "text" },
-                options: { _height: 150, _xMargin: 10, _yMargin: 10, links: ComputedField.MakeFunction("links(self)") as any }
+                options: { _height: 150, _xMargin: 10, _yMargin: 10, nativeDimModifiable: true, nativeHeightUnfrozen: true, links: ComputedField.MakeFunction("links(self)") as any }
             }],
             [DocumentType.SEARCH, {
                 layout: { view: SearchBox, dataField: defaultDataKey },
@@ -372,7 +372,7 @@ export namespace Docs {
             }],
             [DocumentType.WEB, {
                 layout: { view: WebBox, dataField: defaultDataKey },
-                options: { _height: 300, _fitWidth: true, links: ComputedField.MakeFunction("links(self)") as any }
+                options: { _height: 300, _fitWidth: true, nativeDimModifiable: true, nativeHeightUnfrozen: true, links: ComputedField.MakeFunction("links(self)") as any }
             }],
             [DocumentType.COL, {
                 layout: { view: CollectionView, dataField: defaultDataKey },
@@ -392,7 +392,7 @@ export namespace Docs {
             }],
             [DocumentType.PDF, {
                 layout: { view: PDFBox, dataField: defaultDataKey },
-                options: { _curPage: 1, _fitWidth: true, links: ComputedField.MakeFunction("links(self)") as any }
+                options: { _curPage: 1, _fitWidth: true, nativeDimModifiable: true, nativeHeightUnfrozen: true, links: ComputedField.MakeFunction("links(self)") as any }
             }],
             [DocumentType.IMPORT, {
                 layout: { view: DirectoryImportBox, dataField: defaultDataKey },
@@ -506,6 +506,12 @@ export namespace Docs {
             const prototypeIds = Object.values(DocumentType).filter(type => type !== DocumentType.NONE).map(type => type + suffix);
             // fetch the actual prototype documents from the server
             const actualProtos = Docs.newAccount ? {} : await DocServer.GetRefFields(prototypeIds);
+            Cast(actualProtos[DocumentType.WEB + suffix], Doc, null).nativeHeightUnfrozen = true; // to avoid having to recreate the DB
+            Cast(actualProtos[DocumentType.PDF + suffix], Doc, null).nativeHeightUnfrozen = true;
+            Cast(actualProtos[DocumentType.RTF + suffix], Doc, null).nativeHeightUnfrozen = true;
+            Cast(actualProtos[DocumentType.WEB + suffix], Doc, null).nativeDimModifiable = true; // to avoid having to recreate the DB
+            Cast(actualProtos[DocumentType.PDF + suffix], Doc, null).nativeDimModifiable = true;
+            Cast(actualProtos[DocumentType.RTF + suffix], Doc, null).nativeDimModifiable = true;
 
             // update this object to include any default values: DocumentOptions for all prototypes
             prototypeIds.map(id => {
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index e9a54d6a5..bd61c9f60 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -319,12 +319,12 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
                 let height = (doc._height || (nheight / nwidth * width));
                 height = !height || isNaN(height) ? 20 : height;
                 const scale = docView.props.ScreenToLocalTransform().Scale;
-                const canModifyNativeDim = e.ctrlKey || doc.allowReflow;
+                const modifyNativeDim = (e.ctrlKey || doc.forceReflow) && doc.nativeDimModifiable;
                 if (nwidth && nheight) {
                     if (nwidth / nheight !== width / height && !dragBottom) {
                         height = nheight / nwidth * width;
                     }
-                    if (canModifyNativeDim && !dragBottom) { // ctrl key enables modification of the nativeWidth or nativeHeight durin the interaction
+                    if (modifyNativeDim && !dragBottom) { // ctrl key enables modification of the nativeWidth or nativeHeight durin the interaction
                         if (Math.abs(dW) > Math.abs(dH)) dH = dW * nheight / nwidth;
                         else dW = dH * nwidth / nheight;
                     }
@@ -334,34 +334,27 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
                 doc.x = (doc.x || 0) + dX * (actualdW - width);
                 doc.y = (doc.y || 0) + dY * (actualdH - height);
                 const fixedAspect = (nwidth && nheight);
-                if (canModifyNativeDim && [DocumentType.IMG, DocumentType.SCREENSHOT, DocumentType.VID].includes(doc.type as DocumentType)) {
-                    dW !== 0 && runInAction(() => {
-                        const dataDoc = doc[DataSym];
-                        const nw = Doc.NativeWidth(dataDoc);
-                        const nh = Doc.NativeHeight(dataDoc);
-                        Doc.SetNativeWidth(dataDoc, nw + (dW > 0 ? 10 : -10));
-                        Doc.SetNativeHeight(dataDoc, nh + (dW > 0 ? 10 : -10) * nh / nw);
-                    });
-                }
-                else if (fixedAspect) {
-                    if ((Math.abs(dW) > Math.abs(dH) && (!dragBottom || !canModifyNativeDim)) || dragRight) {
-                        if (dragRight && canModifyNativeDim) {
+                if (fixedAspect) {
+                    if ((Math.abs(dW) > Math.abs(dH) && (!dragBottom || !modifyNativeDim)) || dragRight) {
+                        if (dragRight && modifyNativeDim) {
                             doc._nativeWidth = actualdW / (doc._width || 1) * Doc.NativeWidth(doc);
                         } else {
                             if (!doc._fitWidth) doc._height = nheight / nwidth * actualdW;
-                            else if (!canModifyNativeDim || dragBotRight) doc._height = actualdH;
+                            else if (!modifyNativeDim || dragBotRight) doc._height = actualdH;
                         }
                         doc._width = actualdW;
                     }
                     else {
-                        if (dragBottom && (canModifyNativeDim || docView.layoutDoc._fitWidth)) { // frozen web pages and others that fitWidth can't grow horizontally to match a vertical resize so the only choice is to change the nativeheight even if the ctrl key isn't used
+                        if (dragBottom && (modifyNativeDim ||
+                            (docView.layoutDoc.nativeHeightUnfrozen && docView.layoutDoc._fitWidth))) { // frozen web pages, PDFs, and some RTFS have frozen nativewidth/height.  But they are marked to allow their nativeHeight to be explicitly modified with fitWidth and vertical resizing. (ie, with fitWidth they can't grow horizontally to match a vertical resize so it makes more sense to change their nativeheight even if the ctrl key isn't used)
                             doc._nativeHeight = actualdH / (doc._height || 1) * Doc.NativeHeight(doc);
                             doc._autoHeight = false;
                         } else {
                             if (!doc._fitWidth) doc._width = nwidth / nheight * actualdH;
-                            else if (!canModifyNativeDim || dragBotRight) doc._width = actualdW;
+                            else if (!modifyNativeDim || dragBotRight) doc._width = actualdW;
                         }
-                        doc._height = actualdH;
+                        if (!modifyNativeDim) doc._height = Math.min(nheight / nwidth * NumCast(doc._width), actualdH);
+                        else doc._height = actualdH;
                     }
                 } else {
                     dH && (doc._height = actualdH);
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 574d28b8f..364bf05e2 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -141,7 +141,7 @@ export class KeyManager {
             case "delete":
             case "backspace":
                 if (document.activeElement?.tagName !== "INPUT" && document.activeElement?.tagName !== "TEXTAREA") {
-                    const selected = SelectionManager.Views().filter(dv => !dv.topMost);;
+                    const selected = SelectionManager.Views().filter(dv => !dv.topMost);
                     UndoManager.RunInBatch(() => {
                         SelectionManager.DeselectAll();
                         selected.map(dv => !dv.props.Document._stayInCollection && dv.props.removeDocument?.(dv.props.Document));
diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss
index 7fa841002..c3cdb7dde 100644
--- a/src/client/views/MainView.scss
+++ b/src/client/views/MainView.scss
@@ -109,9 +109,9 @@
 
 .properties-container {
     height: 100%;
-    position: relative;
-    left: 100%;
-    top: calc(-100% - 36px);
+    position: absolute;
+    right: 0;
+    top: 0;
     z-index: 3000;
 }
 
@@ -298,9 +298,8 @@
     width: var(--flyoutHandleWidth);
     height: 55px;
     top: 50%;
-    left: -10px;
     border-radius: 8px;
-    position: relative;
+    position: absolute;
     z-index: 41; // lm_maximised has a z-index of 40 and this needs to be above that
     touch-action: none;
     cursor: grab;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 7edcd6217..3f7df705f 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -426,23 +426,23 @@ export class MainView extends React.Component {
     }
 
     @computed get mainInnerContent() {
-        const width = this.propertiesWidth() + this._leftMenuFlyoutWidth + this.leftMenuWidth();
-        const transform = this._leftMenuFlyoutWidth ? 'translate(-28px, 0px)' : undefined;
+        const leftMenuFlyoutWidth = this._leftMenuFlyoutWidth + this.leftMenuWidth();
+        const width = this.propertiesWidth() + leftMenuFlyoutWidth;
         return <>
             {this.leftMenuPanel}
             <div key="inner" className={`mainView-innerContent${this.colorScheme}`}>
                 {this.flyout}
-                <div className="mainView-libraryHandle" style={{ display: !this._leftMenuFlyoutWidth ? "none" : undefined }} onPointerDown={this.onFlyoutPointerDown} >
+                <div className="mainView-libraryHandle" style={{ left: leftMenuFlyoutWidth - 10 /* ~half width of handle */, display: !this._leftMenuFlyoutWidth ? "none" : undefined }} onPointerDown={this.onFlyoutPointerDown} >
                     <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 }}>
+                <div className="mainView-innerContainer" style={{ width: `calc(100% - ${width}px)` }}>
 
                     {this.dockingContent}
 
-                    <div className="mainView-propertiesDragger" key="props" onPointerDown={this.onPropertiesPointerDown} style={{ right: this._leftMenuFlyoutWidth ? 0 : this.propertiesWidth() - 1 }}>
+                    <div className="mainView-propertiesDragger" key="props" onPointerDown={this.onPropertiesPointerDown} style={{ right: this.propertiesWidth() - 1 }}>
                         <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">
+                    <div className="properties-container" style={{ width: this.propertiesWidth() }}>
                         {this.propertiesWidth() < 10 ? (null) : <PropertiesView styleProvider={DefaultStyleProvider} width={this.propertiesWidth()} height={this.propertiesHeight()} />}
                     </div>
                 </div>
diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss
index 9142a68ed..4238f6d29 100644
--- a/src/client/views/nodes/ImageBox.scss
+++ b/src/client/views/nodes/ImageBox.scss
@@ -13,6 +13,7 @@
         width: 100%;
         pointer-events: none;
         mix-blend-mode: multiply; // bcz: makes text fuzzy!
+        overflow: hidden;
     }
 
     .imageBox-fader {
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index b927e98db..89f70985c 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -354,15 +354,15 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
             }} >
             <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit}
                 renderDepth={this.props.renderDepth + 1}
-                isAnnotationOverlay={true}
                 fieldKey={this.annotationKey}
                 CollectionView={undefined}
+                isAnnotationOverlay={true}
                 annotationLayerHostsContent={true}
                 PanelWidth={this.props.PanelWidth}
                 PanelHeight={this.props.PanelHeight}
                 ScreenToLocalTransform={this.screenToLocalTransform}
-                scaling={returnOne}
                 select={emptyFunction}
+                scaling={returnOne}
                 whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
                 removeDocument={this.removeDocument}
                 moveDocument={this.moveDocument}
diff --git a/src/client/views/nodes/VideoBox.scss b/src/client/views/nodes/VideoBox.scss
index cdd36eb3b..4871599b8 100644
--- a/src/client/views/nodes/VideoBox.scss
+++ b/src/client/views/nodes/VideoBox.scss
@@ -14,6 +14,9 @@
     height: 100%;
     position: relative;
     .videoBox-viewer {
+        display:flex;
+        flex-direction: column;
+        height: 100%;
         border-radius: inherit;
         opacity: 0.99;  // hack!  overcomes some kind of Chrome weirdness where buttons (e.g., snapshot) disappear at some point as the video is resized larger
     }
@@ -26,7 +29,17 @@
     .videoBox-stackPanel {
         z-index: -1;
         width: 100%;
-        position: absolute; 
+        position: relative; 
+    }
+
+    .videoBox-annotationLayer {
+        position: relative;
+        transform-origin: left top;
+        top: 0;
+        width: 100%;
+        pointer-events: none;
+        mix-blend-mode: multiply; // bcz: makes text fuzzy!
+        overflow: hidden;
     }
 }
 
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index e3ba638b3..440ccf638 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -550,7 +550,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     }
 
     @computed get annotationLayer() {
-        return <div className="imageBox-annotationLayer" style={{ transition: this.transition, height: `${this.heightPercent}%` }} ref={this._annotationLayer} />;
+        return <div className="videoBox-annotationLayer" style={{ transition: this.transition, height: `${this.heightPercent}%` }} ref={this._annotationLayer} />;
     }
 
     marqueeDown = (e: React.PointerEvent) => {
@@ -568,10 +568,11 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         this.props.select(true);
     });
 
+    @computed get fitWidth() { return this.props.docViewPath?.().lastElement().fitWidth; }
     contentFunc = () => [this.youtubeVideoId ? this.youtubeContent : this.content];
     scaling = () => this.props.scaling?.() || 1;
-    panelWidth = () => this.props.PanelWidth() * this.heightPercent / 100;
-    panelHeight = () => this.layoutDoc._fitWidth ? this.panelWidth() / (Doc.NativeAspect(this.rootDoc) || 1) : this.props.PanelHeight() * this.heightPercent / 100;
+    panelWidth = (): number => this.fitWidth ? this.props.PanelWidth() : (Doc.NativeAspect(this.rootDoc) || 1) * this.panelHeight();
+    panelHeight = (): number => this.fitWidth ? this.panelWidth() / (Doc.NativeAspect(this.rootDoc) || 1) : this.heightPercent / 100 * this.props.PanelHeight();
     screenToLocalTransform = () => {
         const offset = (this.props.PanelWidth() - this.panelWidth()) / 2 / this.scaling();
         return this.props.ScreenToLocalTransform().translate(-offset, 0).scale(100 / this.heightPercent);
@@ -585,10 +586,17 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         return (<div className="videoBox" onContextMenu={this.specificContextMenu} ref={this._mainCont}
             style={{
                 pointerEvents: this.props.layerProvider?.(this.layoutDoc) === false ? "none" : undefined,
-                borderRadius
+                borderRadius,
+                overflow: this.props.docViewPath?.().lastElement().fitWidth ? "auto" : undefined
             }} onWheel={e => { e.stopPropagation(); e.preventDefault(); }}>
             <div className="videoBox-viewer" onPointerDown={this.marqueeDown} >
-                <div style={{ position: "absolute", transition: this.transition, width: this.panelWidth(), height: this.panelHeight(), top: 0, left: `${(100 - this.heightPercent) / 2}%` }}>
+                <div style={{
+                    position: "absolute", transition: this.transition,
+                    width: this.panelWidth(),
+                    height: this.panelHeight(),
+                    top: 0,
+                    left: (this.props.PanelWidth() - this.panelWidth()) / 2
+                }}>
                     <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit}
                         renderDepth={this.props.renderDepth + 1}
                         fieldKey={this.annotationKey}
@@ -608,9 +616,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                         {this.contentFunc}
                     </CollectionFreeFormView>
                 </div>
-                {this.uIButtons}
                 {this.annotationLayer}
-                {this.renderTimeline}
                 {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? (null) :
                     <MarqueeAnnotator
                         rootDoc={this.rootDoc}
@@ -625,6 +631,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                         annotationLayer={this._annotationLayer.current}
                         mainCont={this._mainCont.current}
                     />}
+                {this.renderTimeline}
+                {this.uIButtons}
             </div>
         </div >);
     }
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 8e6586735..37d716993 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -430,9 +430,9 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         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.allowReflow ? "Allow" : "Prevent") + " Reflow", event: () => {
-                    const nw = !this.layoutDoc.allowReflow ? undefined : Doc.NativeWidth(this.layoutDoc) - this.sidebarWidth() / (this.props.scaling?.() || 1);
-                    this.layoutDoc.allowReflow = !nw;
+                description: (!this.layoutDoc.forceReflow ? "Force" : "Prevent") + " Reflow", event: () => {
+                    const nw = !this.layoutDoc.forceReflow ? undefined : Doc.NativeWidth(this.layoutDoc) - this.sidebarWidth() / (this.props.scaling?.() || 1);
+                    this.layoutDoc.forceReflow = !nw;
                     if (nw) {
                         Doc.SetInPlace(this.layoutDoc, this.fieldKey + "-nativeWidth", nw, true);
                     }
@@ -536,7 +536,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     @computed get content() {
         const interactive = !this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick && this.props.isContentActive() && this.props.pointerEvents !== "none" && CurrentUserUtils.SelectedTool === InkTool.None && !DocumentDecorations.Instance?.Interacting;
         return <div className={"webBox-cont" + (interactive ? "-interactive" : "")}
-            style={{ width: !this.layoutDoc.allowReflow ? NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]) || `100%` : "100%", }}>
+            style={{ width: !this.layoutDoc.forceReflow ? NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]) || `100%` : "100%", }}>
             {this.urlContent}
         </div>;
     }
-- 
cgit v1.2.3-70-g09d2


From aed57a2d6435007676409aeba562fc11d0c4a44d Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 29 Sep 2021 01:38:15 -0400
Subject: a number of undo/redo fixes for ink (snapping to tangent, add points,
 dragging tangents).  also tried to make storage of undo events more efficient
 when dragging ink controls (avoid saving hundreds of copies of the InkField)

---
 src/client/util/UndoManager.ts           | 19 +++++++++++++++++++
 src/client/views/DocumentDecorations.tsx |  5 ++++-
 src/client/views/InkControlPtHandles.tsx | 27 ++++++++++++++++-----------
 src/client/views/InkStrokeProperties.ts  | 19 +++++++++++--------
 src/client/views/InkTangentHandles.tsx   | 24 ++++++++++++------------
 src/client/views/InkingStroke.tsx        |  8 ++++----
 src/fields/util.ts                       | 13 ++++++-------
 7 files changed, 72 insertions(+), 43 deletions(-)

(limited to 'src/client/views/DocumentDecorations.tsx')

diff --git a/src/client/util/UndoManager.ts b/src/client/util/UndoManager.ts
index 05fb9f378..536d90371 100644
--- a/src/client/util/UndoManager.ts
+++ b/src/client/util/UndoManager.ts
@@ -70,6 +70,7 @@ export namespace UndoManager {
     export interface UndoEvent {
         undo: () => void;
         redo: () => void;
+        prop: string;
     }
     type UndoBatch = UndoEvent[];
 
@@ -104,6 +105,23 @@ export namespace UndoManager {
     export function GetOpenBatches(): Without<Batch, 'end'>[] {
         return openBatches;
     }
+    export function FilterBatches(fieldTypes: string[]) {
+        var fieldCounts: { [key: string]: number } = {};
+        const lastStack = UndoManager.undoStack.lastElement();
+        if (lastStack) {
+            lastStack.forEach(ev => fieldTypes.includes(ev.prop) && (fieldCounts[ev.prop] = (fieldCounts[ev.prop] || 0) + 1));
+            var fieldCount2: { [key: string]: number } = {};
+            runInAction(() =>
+                UndoManager.undoStack[UndoManager.undoStack.length - 1] = lastStack.filter(ev => {
+                    if (fieldTypes.includes(ev.prop)) {
+                        fieldCount2[ev.prop] = (fieldCount2[ev.prop] || 0) + 1;
+                        if (fieldCount2[ev.prop] === 1 || fieldCount2[ev.prop] === fieldCounts[ev.prop]) return true;
+                        return false;
+                    }
+                    return true;
+                }));
+        }
+    }
     export function TraceOpenBatches() {
         console.log(`Open batches:\n\t${openBatches.map(batch => batch.batchName).join("\n\t")}\n`);
     }
@@ -140,6 +158,7 @@ export namespace UndoManager {
         batchCounter--;
         // console.log("End " + batchCounter);
         if (batchCounter === 0 && currentBatch?.length) {
+            // console.log("------ended----")
             if (!cancel) {
                 undoStack.push(currentBatch);
             }
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index bd61c9f60..bd9c3509b 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -202,7 +202,10 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
                 InkStrokeProperties.Instance?.rotateInk(2 * movement.X / angle * (Math.PI / 180));
                 return false;
             },
-            () => this._rotateUndo?.end(),
+            () => {
+                this._rotateUndo?.end();
+                UndoManager.FilterBatches(["data", "x", "y", "width", "height"]);
+            },
             emptyFunction);
         this._prevY = e.clientY;
         this._inkCenterPts = SelectionManager.Views()
diff --git a/src/client/views/InkControlPtHandles.tsx b/src/client/views/InkControlPtHandles.tsx
index eb0eebcdf..898c3bf26 100644
--- a/src/client/views/InkControlPtHandles.tsx
+++ b/src/client/views/InkControlPtHandles.tsx
@@ -24,7 +24,6 @@ export interface InkControlProps {
 export class InkControlPtHandles extends React.Component<InkControlProps> {
 
     @observable private _overControl = -1;
-    @observable private _overAddPoint = -1;
     /**
      * Handles the movement of a selected control point when the user clicks and drags.
      * @param controlIndex The index of the currently selected control point.
@@ -32,8 +31,7 @@ export class InkControlPtHandles extends React.Component<InkControlProps> {
     @action
     onControlDown = (e: React.PointerEvent, controlIndex: number): void => {
         if (InkStrokeProperties.Instance) {
-            InkStrokeProperties.Instance.moveControl(0, 0, 1);
-            const controlUndo = UndoManager.StartBatch("DocDecs set radius");
+            var controlUndo: UndoManager.Batch | undefined;
             const screenScale = this.props.ScreenToLocalTransform().Scale;
             const order = controlIndex % 4;
             const handleIndexA = ((order === 3 ? controlIndex - 1 : controlIndex - 2) + this.props.inkCtrlPoints.length) % this.props.inkCtrlPoints.length;
@@ -41,17 +39,26 @@ export class InkControlPtHandles extends React.Component<InkControlProps> {
             const brokenIndices = Cast(this.props.inkDoc.brokenInkIndices, listSpec("number"));
             setupMoveUpEvents(this, e,
                 (e: PointerEvent, down: number[], delta: number[]) => {
+                    if (!controlUndo) controlUndo = UndoManager.StartBatch("drag ink ctrl pt");
                     InkStrokeProperties.Instance?.moveControl(-delta[0] * screenScale, -delta[1] * screenScale, controlIndex);
                     return false;
                 },
-                () => controlUndo?.end(),
+                () => {
+                    controlUndo?.end();
+                    UndoManager.FilterBatches(["data", "x", "y", "width", "height"]);
+                },
                 action((e: PointerEvent, doubleTap: boolean | undefined) => {
                     const equivIndex = controlIndex === 0 ? this.props.inkCtrlPoints.length - 1 : controlIndex === this.props.inkCtrlPoints.length - 1 ? 0 : controlIndex;
-                    if (doubleTap && brokenIndices?.includes(equivIndex)) {
-                        InkStrokeProperties.Instance?.snapHandleTangent(equivIndex, handleIndexA, handleIndexB);
-                    }
-                    if (doubleTap && brokenIndices?.includes(controlIndex)) {
-                        InkStrokeProperties.Instance?.snapHandleTangent(controlIndex, handleIndexA, handleIndexB);
+                    if (doubleTap) {
+                        if (brokenIndices?.includes(equivIndex)) {
+                            if (!controlUndo) controlUndo = UndoManager.StartBatch("make smooth");
+                            InkStrokeProperties.Instance?.snapHandleTangent(equivIndex, handleIndexA, handleIndexB);
+                        }
+                        if (equivIndex !== controlIndex && brokenIndices?.includes(controlIndex)) {
+                            if (!controlUndo) controlUndo = UndoManager.StartBatch("make smooth");
+                            InkStrokeProperties.Instance?.snapHandleTangent(controlIndex, handleIndexA, handleIndexB);
+                        }
+                        controlUndo?.end();
                     }
                 }));
         }
@@ -62,8 +69,6 @@ export class InkControlPtHandles extends React.Component<InkControlProps> {
      */
     @action onEnterControl = (i: number) => { this._overControl = i; };
     @action onLeaveControl = () => { this._overControl = -1; };
-    @action onEnterAddPoint = (i: number) => { this._overAddPoint = i; };
-    @action onLeaveAddPoint = () => { this._overAddPoint = -1; };
 
     /**
      * Deletes the currently selected point.
diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts
index 6a503cc91..3770eb7c1 100644
--- a/src/client/views/InkStrokeProperties.ts
+++ b/src/client/views/InkStrokeProperties.ts
@@ -214,14 +214,16 @@ export class InkStrokeProperties {
      */
     snapHandleTangent = (controlIndex: number, handleIndexA: number, handleIndexB: number) => {
         this.applyFunction((doc: Doc, ink: InkData) => {
-            const brokenIndices = Cast(doc.brokenInkIndices, listSpec("number"));
-            if (brokenIndices) {
-                doc.brokenInkIndices = new List<number>(brokenIndices.filter(brokenIndex => brokenIndex !== controlIndex));
+            const brokenIndices = Cast(doc.brokenInkIndices, listSpec("number"), []);
+            const ind = brokenIndices.findIndex(value => value === controlIndex);
+            if (ind !== -1) {
+                brokenIndices.splice(ind, 1);
                 const [controlPoint, handleA, handleB] = [ink[controlIndex], ink[handleIndexA], ink[handleIndexB]];
                 const oppositeHandleA = this.rotatePoint(handleA, controlPoint, Math.PI);
                 const angleDifference = this.angleChange(handleB, oppositeHandleA, controlPoint);
-                ink[handleIndexB] = this.rotatePoint(handleB, controlPoint, angleDifference);
-                return ink;
+                const inkCopy = ink.slice();
+                inkCopy[handleIndexB] = this.rotatePoint(handleB, controlPoint, angleDifference);
+                return inkCopy;
             }
         });
     }
@@ -277,13 +279,14 @@ export class InkStrokeProperties {
             const oppositeHandlePoint = ink[oppositeHandleIndex];
             const controlPoint = ink[controlIndex];
             const newHandlePoint = { X: ink[handleIndex].X - deltaX / xScale, Y: ink[handleIndex].Y - deltaY / yScale };
-            ink[handleIndex] = newHandlePoint;
+            const inkCopy = ink.slice();
+            inkCopy[handleIndex] = newHandlePoint;
             const brokenIndices = Cast(doc.brokenInkIndices, listSpec("number"));
             // Rotate opposite handle if user hasn't held 'Alt' key or not first/final control (which have only 1 handle).
             if ((!brokenIndices || !brokenIndices?.includes(controlIndex)) && (closed || (handleIndex !== 1 && handleIndex !== ink.length - 2))) {
                 const angle = this.angleChange(oldHandlePoint, newHandlePoint, controlPoint);
-                ink[oppositeHandleIndex] = this.rotatePoint(oppositeHandlePoint, controlPoint, angle);
+                inkCopy[oppositeHandleIndex] = this.rotatePoint(oppositeHandlePoint, controlPoint, angle);
             }
-            return ink;
+            return inkCopy;
         })
 }
\ No newline at end of file
diff --git a/src/client/views/InkTangentHandles.tsx b/src/client/views/InkTangentHandles.tsx
index dbe9ca027..759e48134 100644
--- a/src/client/views/InkTangentHandles.tsx
+++ b/src/client/views/InkTangentHandles.tsx
@@ -27,18 +27,21 @@ export class InkTangentHandles extends React.Component<InkHandlesProps> {
      */
     onHandleDown = (e: React.PointerEvent, handleIndex: number): void => {
         if (InkStrokeProperties.Instance) {
-            InkStrokeProperties.Instance.moveControl(0, 0, 1);
-            const controlUndo = UndoManager.StartBatch("DocDecs set radius");
+            var controlUndo: UndoManager.Batch | undefined;
             const screenScale = this.props.ScreenToLocalTransform().Scale;
             const order = handleIndex % 4;
             const oppositeHandleRawIndex = order === 1 ? handleIndex - 3 : handleIndex + 3;
             const oppositeHandleIndex = (oppositeHandleRawIndex < 0 ? this.props.screenCtrlPoints.length + oppositeHandleRawIndex : oppositeHandleRawIndex) % this.props.screenCtrlPoints.length;
             const controlIndex = (order === 1 ? handleIndex - 1 : handleIndex + 2) % this.props.screenCtrlPoints.length;
             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?.moveHandle(-delta[0] * screenScale, -delta[1] * screenScale, handleIndex, oppositeHandleIndex, controlIndex);
                 return false;
-            }, () => controlUndo?.end(), emptyFunction
+            }, () => {
+                controlUndo?.end();
+                UndoManager.FilterBatches(["data", "x", "y", "width", "height"]);
+            }, emptyFunction
             );
         }
     }
@@ -50,15 +53,12 @@ export class InkTangentHandles extends React.Component<InkHandlesProps> {
      */
     @action
     onBreakTangent = (controlIndex: number) => {
-        const doc = this.props.inkDoc;
-        if (doc) {
-            const closed = this.props.screenCtrlPoints.lastElement().X === this.props.screenCtrlPoints[0].X && this.props.screenCtrlPoints.lastElement().Y === this.props.screenCtrlPoints[0].Y;
-            const brokenIndices = Cast(doc.brokenInkIndices, listSpec("number")) || new List;
-            if (!brokenIndices?.includes(controlIndex) &&
-                ((controlIndex > 0 && controlIndex < this.props.screenCtrlPoints.length - 1) || closed)) {
-                brokenIndices.push(controlIndex);
-                doc.brokenInkIndices = brokenIndices;
-            }
+        const closed = this.props.screenCtrlPoints.lastElement().X === this.props.screenCtrlPoints[0].X && this.props.screenCtrlPoints.lastElement().Y === this.props.screenCtrlPoints[0].Y;
+        var brokenIndices = Cast(this.props.inkDoc.brokenInkIndices, listSpec("number"));
+        if (!brokenIndices?.includes(controlIndex) &&
+            ((controlIndex > 0 && controlIndex < this.props.screenCtrlPoints.length - 1) || closed)) {
+            if (brokenIndices) brokenIndices.push(controlIndex);
+            else this.props.inkDoc.brokenInkIndices = new List<number>([controlIndex]);
         }
     }
 
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index b921014a3..867677005 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -77,8 +77,8 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
                         this._properties._controlButton = true;
                         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);
+                    } else if (this._properties?._controlButton) {
+                        this._nearestT && this._nearestSeg !== undefined && InkStrokeProperties.Instance?.addPoints(this._nearestT, this._nearestSeg, this.inkScaledData().inkData.slice());
                     }
                 }), this._properties?._controlButton, this._properties?._controlButton
             );
@@ -125,7 +125,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
     }
 
     @action
-    onPointerOver = (e: React.PointerEvent) => {
+    onPointerMove = (e: React.PointerEvent) => {
         const { inkData, inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData();
         const screenPts = inkData.map(point => this.props.ScreenToLocalTransform().inverse().transformPoint(
             (point.X - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2,
@@ -212,7 +212,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
                     overflow: "visible",
                 }}
                 onPointerLeave={action(e => this._nearestScrPt = undefined)}
-                onPointerMove={this.props.isSelected() ? this.onPointerOver : undefined}
+                onPointerMove={this.props.isSelected() ? this.onPointerMove : undefined}
                 onPointerDown={this.onPointerDown}
                 onClick={e => this._handledClick && e.stopPropagation()}
                 onContextMenu={() => {
diff --git a/src/fields/util.ts b/src/fields/util.ts
index 3590c2dea..99dfc04d9 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -98,13 +98,12 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
         } else {
             DocServer.registerDocWithCachedUpdate(receiver, prop as string, curValue);
         }
-        !receiver[Initializing] && (!receiver[UpdatingFromServer] || receiver[ForceServerWrite]) && UndoManager.AddEvent({
-            redo: () => receiver[prop] = value,
-            undo: () => {
-                // console.log("Undo: " + prop + " = " + curValue); // bcz: uncomment to log undo
-                receiver[prop] = curValue;
-            }
-        });
+        !receiver[Initializing] && (!receiver[UpdatingFromServer] || receiver[ForceServerWrite]) &&
+            UndoManager.AddEvent({
+                redo: () => receiver[prop] = value,
+                undo: () => receiver[prop] = curValue,
+                prop: prop?.toString()
+            });
         return true;
     }
     return false;
-- 
cgit v1.2.3-70-g09d2


From 93bc6d3fbfa35c7f47b7000e0ea36d9ab81d7eba Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 14 Oct 2021 15:33:41 -0400
Subject: fixed document resizing.

---
 src/client/views/DocumentDecorations.tsx | 29 ++++++++++++++++++++---------
 1 file changed, 20 insertions(+), 9 deletions(-)

(limited to 'src/client/views/DocumentDecorations.tsx')

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index bd9c3509b..17a81149c 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -318,8 +318,10 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
                 const doc = Document(docView.rootDoc);
                 const nwidth = docView.nativeWidth;
                 const nheight = docView.nativeHeight;
-                const width = (doc._width || 0);
-                let height = (doc._height || (nheight / nwidth * width));
+                const docheight = doc._height || 0;
+                const docwidth = doc._width || 0;
+                const width = docwidth;
+                let height = (docheight || (nheight / nwidth * width));
                 height = !height || isNaN(height) ? 20 : height;
                 const scale = docView.props.ScreenToLocalTransform().Scale;
                 const modifyNativeDim = (e.ctrlKey || doc.forceReflow) && doc.nativeDimModifiable;
@@ -332,17 +334,18 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
                         else dW = dH * nwidth / nheight;
                     }
                 }
-                const actualdW = Math.max(width + (dW * scale), 20);
-                const actualdH = Math.max(height + (dH * scale), 20);
-                doc.x = (doc.x || 0) + dX * (actualdW - width);
-                doc.y = (doc.y || 0) + dY * (actualdH - height);
+                let actualdW = Math.max(width + (dW * scale), 20);
+                let actualdH = Math.max(height + (dH * scale), 20);
                 const fixedAspect = (nwidth && nheight);
                 if (fixedAspect) {
                     if ((Math.abs(dW) > Math.abs(dH) && (!dragBottom || !modifyNativeDim)) || dragRight) {
                         if (dragRight && modifyNativeDim) {
                             doc._nativeWidth = actualdW / (doc._width || 1) * Doc.NativeWidth(doc);
                         } else {
-                            if (!doc._fitWidth) doc._height = nheight / nwidth * actualdW;
+                            if (!doc._fitWidth) {
+                                actualdH = nheight / nwidth * actualdW;
+                                doc._height = actualdH;
+                            }
                             else if (!modifyNativeDim || dragBotRight) doc._height = actualdH;
                         }
                         doc._width = actualdW;
@@ -353,10 +356,16 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
                             doc._nativeHeight = actualdH / (doc._height || 1) * Doc.NativeHeight(doc);
                             doc._autoHeight = false;
                         } else {
-                            if (!doc._fitWidth) doc._width = nwidth / nheight * actualdH;
+                            if (!doc._fitWidth) {
+                                actualdW = nwidth / nheight * actualdH;
+                                doc._width = actualdW;
+                            }
                             else if (!modifyNativeDim || dragBotRight) doc._width = actualdW;
                         }
-                        if (!modifyNativeDim) doc._height = Math.min(nheight / nwidth * NumCast(doc._width), actualdH);
+                        if (!modifyNativeDim) {
+                            actualdH = Math.min(nheight / nwidth * NumCast(doc._width), actualdH);
+                            doc._height = actualdH;
+                        }
                         else doc._height = actualdH;
                     }
                 } else {
@@ -364,6 +373,8 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
                     dW && (doc._width = actualdW);
                     dH && (doc._autoHeight = false);
                 }
+                doc.x = (doc.x || 0) + dX * (actualdW - docwidth);
+                doc.y = (doc.y || 0) + dY * (actualdH - docheight);
                 doc._lastModified = new DateField();
             }
             const val = this._dragHeights.get(docView.layoutDoc);
-- 
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/DocumentDecorations.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

