From 555d67a31adbe7e6d42f7f7805ba1348e6d505f6 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 24 Jan 2023 17:05:25 -0500
Subject: stopped storing  presCollection in PresBox in favor of computing it. 
 added anchor menu option for viewing linked trail.  fixed showing slected
 slides in presbox when link with up is used.

---
 src/client/views/search/SearchBox.tsx | 13 ++++++-------
 1 file changed, 6 insertions(+), 7 deletions(-)

(limited to 'src/client/views/search/SearchBox.tsx')

diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index aac488559..7e2a5a237 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -10,6 +10,7 @@ import { DocUtils } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { DocumentManager } from '../../util/DocumentManager';
 import { LinkManager } from '../../util/LinkManager';
+import { undoBatch } from '../../util/UndoManager';
 import { CollectionDockingView } from '../collections/CollectionDockingView';
 import { ViewBoxBaseComponent } from '../DocComponent';
 import { FieldView, FieldViewProps } from '../nodes/FieldView';
@@ -113,14 +114,12 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
         this.selectElement(doc, () => DocumentManager.Instance.getFirstDocumentView(doc)?.ComponentView?.search?.(this._searchString, undefined, false));
     });
 
-    // TODO: nda -- Change this method to change what happens when you click on the item.
+    @undoBatch
     makeLink = action((linkTo: Doc) => {
-        if (this.props.linkCreateAnchor) {
-            const linkFrom = this.props.linkCreateAnchor();
-            if (linkFrom) {
-                const link = DocUtils.MakeLink({ doc: linkFrom }, { doc: linkTo });
-                link && this.props.linkCreated?.(link);
-            }
+        const linkFrom = this.props.linkCreateAnchor?.();
+        if (linkFrom) {
+            const link = DocUtils.MakeLink({ doc: linkFrom }, { doc: linkTo });
+            link && this.props.linkCreated?.(link);
         }
     });
 
-- 
cgit v1.2.3-70-g09d2


From 1763ff090ea734ca275c3c28edc6c303c935b801 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 25 Jan 2023 12:59:50 -0500
Subject: added a linkFollow o[ption to choose between opening target, or
 highest level collection containing target. fixed adding marker annotations
 to pdf/web/etc.  fixed following link to wikipedia pages to not create a new
 document each time.  made searchBox's search function static so that it can
 be called programmatically. Fixed LinkDocPreview to not flicker when doing a
 nopreview link follow.  changed PlayTrail to restore state of all freeform
 collections containing source anchor.

---
 src/client/util/DocumentManager.ts                 |  9 ++-
 src/client/util/LinkFollower.ts                    | 23 ++----
 src/client/views/MainView.tsx                      |  6 +-
 src/client/views/PropertiesView.tsx                | 12 ++-
 .../views/collections/CollectionDockingView.tsx    |  9 ++-
 src/client/views/nodes/LinkDocPreview.tsx          | 72 ++++++++++-------
 src/client/views/nodes/trails/PresBox.tsx          | 16 ++--
 src/client/views/pdf/AnchorMenu.tsx                |  4 +-
 src/client/views/search/SearchBox.tsx              | 27 ++++---
 src/fields/Types.ts                                | 90 ++++++++++++----------
 10 files changed, 152 insertions(+), 116 deletions(-)

(limited to 'src/client/views/search/SearchBox.tsx')

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index aa09fb005..7c867d710 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -194,11 +194,16 @@ export class DocumentManager {
         return toReturn;
     }
 
-    static GetContextPath(doc: Opt<Doc>) {
+    static GetContextPath(doc: Opt<Doc>, includeExistingViews?: boolean) {
         if (!doc) return [];
         const srcContext = Cast(doc.context, Doc, null) ?? Cast(Cast(doc.annotationOn, Doc, null)?.context, Doc, null);
         var containerDocContext = srcContext ? [srcContext] : [];
-        while (containerDocContext.length && containerDocContext[0]?.context && DocCast(containerDocContext[0].context)?.viewType !== CollectionViewType.Docking && !DocumentManager.Instance.getDocumentView(containerDocContext[0])) {
+        while (
+            containerDocContext.length &&
+            containerDocContext[0]?.context &&
+            DocCast(containerDocContext[0].context)?.viewType !== CollectionViewType.Docking &&
+            (includeExistingViews || !DocumentManager.Instance.getDocumentView(containerDocContext[0]))
+        ) {
             containerDocContext = [Cast(containerDocContext[0].context, Doc, null), ...containerDocContext];
         }
         return containerDocContext;
diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts
index 5bdfca54a..57618b53c 100644
--- a/src/client/util/LinkFollower.ts
+++ b/src/client/util/LinkFollower.ts
@@ -100,7 +100,7 @@ export class LinkFollower {
                     : linkDoc.anchor1
             ) as Doc;
             if (target) {
-                const doFollow = async (canToggle?: boolean) => {
+                const doFollow = (canToggle?: boolean) => {
                     const options: DocFocusOptions = {
                         playAudio: BoolCast(sourceDoc.followLinkAudio),
                         toggleTarget: canToggle && BoolCast(sourceDoc.followLinkToggle),
@@ -114,27 +114,14 @@ export class LinkFollower {
                         zoomTextSelections: BoolCast(sourceDoc.followLinkZoomText),
                     };
                     if (target.type === DocumentType.PRES) {
-                        const containerAnnoDoc = Cast(sourceDoc, Doc, null);
-                        const containerDoc = containerAnnoDoc || sourceDoc;
-                        var containerDocContext = containerDoc?.context ? [Cast(await containerDoc?.context, Doc, null)] : ([] as Doc[]);
-                        while (containerDocContext.length && containerDocContext[0]?.context && DocCast(containerDocContext[0].context)?.viewType !== CollectionViewType.Docking) {
-                            containerDocContext = [Cast(await containerDocContext[0].context, Doc, null), ...containerDocContext];
-                        }
-                        if (!DocumentManager.Instance.getDocumentView(containerDocContext[0])) {
-                            CollectionDockingView.AddSplit(containerDocContext[0], OpenWhereMod.right);
-                        }
+                        const containerDocContext = DocumentManager.GetContextPath(sourceDoc, true); // gather all views that affect layout of sourceDoc so we can revert them after playing the rail
                         SelectionManager.DeselectAll();
-                        DocumentManager.Instance.AddViewRenderedCb(target, dv => containerDocContext.length && (dv.ComponentView as PresBox).PlayTrail(containerDocContext[0]));
+                        DocumentManager.Instance.AddViewRenderedCb(target, dv => containerDocContext.length && (dv.ComponentView as PresBox).PlayTrail(containerDocContext));
                         PresBox.OpenPresMinimized(target, [0, 0]);
                         finished?.();
                     } else {
-                        const containerAnnoDoc = Cast(target.annotationOn, Doc, null);
-                        const containerDoc = containerAnnoDoc || target;
-                        var containerDocContext = containerDoc?.context ? [Cast(await containerDoc?.context, Doc, null)] : ([] as Doc[]);
-                        while (containerDocContext.length && containerDocContext[0]?.context && !DocumentManager.Instance.getDocumentView(containerDocContext[0]) && DocCast(containerDocContext[0].context)?.viewType !== CollectionViewType.Docking) {
-                            containerDocContext = [Cast(await containerDocContext[0].context, Doc, null), ...containerDocContext];
-                        }
-                        const targetContexts = LightboxView.LightboxDoc ? [containerAnnoDoc || containerDocContext[0]].filter(a => a) : containerDocContext;
+                        const containerDocContext = DocumentManager.GetContextPath(target);
+                        const targetContexts = !sourceDoc.followLinkToOuterContext && containerDocContext.length ? [containerDocContext.lastElement()] : containerDocContext;
                         DocumentManager.Instance.jumpToDocument(target, options, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, OpenWhere.inPlace), finished), targetContexts, allFinished);
                     }
                 };
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 839db5795..895ed9bda 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -948,6 +948,10 @@ export class MainView extends React.Component {
         );
     }
 
+    @computed get linkDocPreview() {
+        return LinkDocPreview.LinkInfo ? <LinkDocPreview {...LinkDocPreview.LinkInfo} /> : null;
+    }
+
     render() {
         return (
             <div
@@ -975,7 +979,7 @@ export class MainView extends React.Component {
                 {this._hideUI ? null : <TopBar />}
                 {LinkDescriptionPopup.descriptionPopup ? <LinkDescriptionPopup /> : null}
                 {DocumentLinksButton.LinkEditorDocView ? <LinkMenu clearLinkEditor={action(() => (DocumentLinksButton.LinkEditorDocView = undefined))} docView={DocumentLinksButton.LinkEditorDocView} /> : null}
-                {LinkDocPreview.LinkInfo ? <LinkDocPreview {...LinkDocPreview.LinkInfo} /> : null}
+                {this.linkDocPreview}
 
                 {((page: string) => {
                     // prettier-ignore
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index af50478b0..411f51d84 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -1,6 +1,6 @@
 import React = require('react');
 import { IconLookup } from '@fortawesome/fontawesome-svg-core';
-import { faAnchor, faArrowRight } from '@fortawesome/free-solid-svg-icons';
+import { faAnchor, faArrowRight, faWindowMaximize } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Checkbox, Tooltip } from '@material-ui/core';
 import { intersection } from 'lodash';
@@ -1689,6 +1689,16 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                                                 <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
                                             </button>
                                         </div>
+                                        <div className="propertiesView-input inline">
+                                            <p>Toggle Follow to Outer Context</p>
+                                            <button
+                                                style={{ background: !this.sourceAnchor?.followLinkToOuterContext ? '' : '#4476f7', borderRadius: 3 }}
+                                                onPointerDown={e => this.toggleAnchorProp(e, 'followLinkToOuterContext', this.sourceAnchor)}
+                                                onClick={e => e.stopPropagation()}
+                                                className="propertiesButton">
+                                                <FontAwesomeIcon className="fa-icon" icon={faWindowMaximize as IconLookup} size="lg" />
+                                            </button>
+                                        </div>
                                         <div className="propertiesView-input inline">
                                             <p>Toggle Target (Show/Hide)</p>
                                             <button
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index ffc004df6..1ead80bd0 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -156,10 +156,13 @@ export class CollectionDockingView extends CollectionSubView() {
         }
         const tab = Array.from(instance.tabMap.keys()).find(tab => tab.contentItem.config.props.panelName === panelName);
         if (tab) {
-            tab.header.parent.addChild(newConfig, undefined);
             const j = tab.header.parent.contentItems.indexOf(tab.contentItem);
-            !addToSplit && j !== -1 && tab.header.parent.contentItems[j].remove();
-            return instance.layoutChanged();
+            if (newConfig.props.documentId !== tab.header.parent.contentItems[j].config.props.documentId) {
+                tab.header.parent.addChild(newConfig, undefined);
+                !addToSplit && j !== -1 && tab.header.parent.contentItems[j].remove();
+                return instance.layoutChanged();
+            }
+            return false;
         }
         return CollectionDockingView.AddSplit(document, panelName, stack, panelName);
     }
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index 7eb42994b..1eab06381 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -4,7 +4,7 @@ import { action, computed, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import wiki from 'wikijs';
 import { Doc, DocCastAsync, DocListCast, HeightSym, Opt, WidthSym } from '../../../fields/Doc';
-import { Cast, NumCast, StrCast } from '../../../fields/Types';
+import { Cast, DocCast, NumCast, PromiseValue, StrCast } from '../../../fields/Types';
 import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnNone, setupMoveUpEvents } from '../../../Utils';
 import { DocServer } from '../../DocServer';
 import { Docs, DocUtils } from '../../documents/Documents';
@@ -17,6 +17,8 @@ import { Transform } from '../../util/Transform';
 import { DocumentView, DocumentViewSharedProps, OpenWhere } from './DocumentView';
 import './LinkDocPreview.scss';
 import React = require('react');
+import { SearchUtil } from '../../util/SearchUtil';
+import { SearchBox } from '../search/SearchBox';
 
 interface LinkDocPreviewProps {
     linkDoc?: Doc;
@@ -36,6 +38,8 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
         LinkDocPreview.LinkInfo !== info && (LinkDocPreview.LinkInfo = info);
     }
 
+    static _instance: Opt<LinkDocPreview>;
+
     _infoRef = React.createRef<HTMLDivElement>();
     _linkDocRef = React.createRef<HTMLDivElement>();
     @observable public static LinkInfo: Opt<LinkDocPreviewProps>;
@@ -46,6 +50,11 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
     @observable _toolTipText = '';
     @observable _hrefInd = 0;
 
+    constructor(props: any) {
+        super(props);
+        LinkDocPreview._instance = this;
+    }
+
     @action init() {
         var linkTarget = this.props.linkDoc;
         this._linkSrc = this.props.linkSrc;
@@ -62,6 +71,7 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
             this._markerTargetDoc = this._targetDoc = linkTarget;
         }
         this._toolTipText = '';
+        this.updateHref();
     }
     componentDidUpdate(props: any) {
         if (props.linkSrc !== this.props.linkSrc || props.linkDoc !== this.props.linkDoc || props.hrefs !== this.props.hrefs) this.init();
@@ -71,6 +81,7 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
         document.addEventListener('pointerdown', this.onPointerDown, true);
     }
 
+    @action
     componentWillUnmount() {
         LinkDocPreview.SetLinkInfo(undefined);
         document.removeEventListener('pointerdown', this.onPointerDown, true);
@@ -80,7 +91,8 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
         !this._linkDocRef.current?.contains(e.target as any) && LinkDocPreview.Clear(); // close preview when not clicking anywhere other than the info bar of the preview
     };
 
-    @computed get href() {
+    @action
+    updateHref() {
         if (this.props.hrefs?.length) {
             const href = this.props.hrefs[this._hrefInd];
             if (href.indexOf(Doc.localServerPath()) !== 0) {
@@ -90,33 +102,33 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
                         .page(href.replace('https://en.wikipedia.org/wiki/', ''))
                         .then(page => page.summary().then(action(summary => (this._toolTipText = summary.substring(0, 500)))));
                 } else {
-                    setTimeout(action(() => (this._toolTipText = 'url => ' + href)));
+                    this._toolTipText = 'url => ' + href;
                 }
             } else {
                 // hyperlink to a document .. decode doc id and retrieve from the server. this will trigger vals() being invalidated
-                const anchorDoc = href.replace(Doc.localServerPath(), '').split('?')[0];
-                anchorDoc &&
-                    DocServer.GetRefField(anchorDoc).then(
-                        action(anchor => {
-                            if (anchor instanceof Doc && DocListCast(anchor.links).length) {
-                                this._linkDoc = this._linkDoc ?? DocListCast(anchor.links)[0];
-                                const automaticLink = this._linkDoc.linkRelationship === LinkManager.AutoKeywords;
-                                if (automaticLink) {
-                                    // automatic links specify the target in the link info, not the source
-                                    const linkTarget = anchor;
-                                    this._linkSrc = LinkManager.getOppositeAnchor(this._linkDoc, linkTarget);
-                                    this._markerTargetDoc = this._targetDoc = linkTarget;
-                                } else {
-                                    this._linkSrc = anchor;
-                                    const linkTarget = LinkManager.getOppositeAnchor(this._linkDoc, this._linkSrc);
-                                    this._markerTargetDoc = linkTarget;
-                                    this._targetDoc = /*linkTarget?.type === DocumentType.MARKER &&*/ linkTarget?.annotationOn ? Cast(linkTarget.annotationOn, Doc, null) ?? linkTarget : linkTarget;
-                                }
-                                this._toolTipText = '';
-                                if (LinkDocPreview.LinkInfo?.noPreview || this._linkSrc?.followLinkToggle || this._markerTargetDoc?.type === DocumentType.PRES) this.followLink();
+                const anchorDocId = href.replace(Doc.localServerPath(), '').split('?')[0];
+                const anchorDoc = anchorDocId ? PromiseValue(DocCast(DocServer.GetCachedRefField(anchorDocId) ?? DocServer.GetRefField(anchorDocId))) : undefined;
+                anchorDoc?.then?.(
+                    action(anchor => {
+                        if (anchor instanceof Doc && DocListCast(anchor.links).length) {
+                            this._linkDoc = this._linkDoc ?? DocListCast(anchor.links)[0];
+                            const automaticLink = this._linkDoc.linkRelationship === LinkManager.AutoKeywords;
+                            if (automaticLink) {
+                                // automatic links specify the target in the link info, not the source
+                                const linkTarget = anchor;
+                                this._linkSrc = LinkManager.getOppositeAnchor(this._linkDoc, linkTarget);
+                                this._markerTargetDoc = this._targetDoc = linkTarget;
+                            } else {
+                                this._linkSrc = anchor;
+                                const linkTarget = LinkManager.getOppositeAnchor(this._linkDoc, this._linkSrc);
+                                this._markerTargetDoc = linkTarget;
+                                this._targetDoc = /*linkTarget?.type === DocumentType.MARKER &&*/ linkTarget?.annotationOn ? Cast(linkTarget.annotationOn, Doc, null) ?? linkTarget : linkTarget;
                             }
-                        })
-                    );
+                            this._toolTipText = 'link to ' + this._targetDoc?.title;
+                            if (LinkDocPreview.LinkInfo?.noPreview || this._linkSrc?.followLinkToggle || this._markerTargetDoc?.type === DocumentType.PRES) this.followLink();
+                        }
+                    })
+                );
             }
             return href;
         }
@@ -158,11 +170,14 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
     };
 
     followLink = () => {
+        LinkDocPreview.Clear();
         if (this._linkDoc && this._linkSrc) {
-            LinkDocPreview.Clear();
             LinkFollower.FollowLink(this._linkDoc, this._linkSrc, this.props.docProps, false);
         } else if (this.props.hrefs?.length) {
-            this.props.docProps?.addDocTab(Docs.Create.WebDocument(this.props.hrefs[0], { title: this.props.hrefs[0], _nativeWidth: 850, _width: 200, _height: 400, useCors: true }), OpenWhere.addRight);
+            const webDoc =
+                Array.from(SearchBox.staticSearchCollection(Doc.MyFilesystem, this.props.hrefs[0]).keys()).lastElement() ??
+                Docs.Create.WebDocument(this.props.hrefs[0], { title: this.props.hrefs[0], _nativeWidth: 850, _width: 200, _height: 400, useCors: true });
+            this.props.docProps?.addDocTab(webDoc, OpenWhere.lightbox);
         }
     };
 
@@ -208,7 +223,6 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
     }
 
     @computed get docPreview() {
-        const href = this.href; // needs to be here to trigger lookup of web pages and docs on server
         return (!this._linkDoc || !this._targetDoc || !this._linkSrc) && !this._toolTipText ? null : (
             <div className="linkDocPreview-inner">
                 {!this.props.showHeader ? null : this.previewHeader}
@@ -285,7 +299,7 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
                 className="linkDocPreview"
                 ref={this._linkDocRef}
                 onPointerDown={this.followLinkPointerDown}
-                style={{ left: this.props.location[0], top: this.props.location[1], width: this.width() + borders, height: this.height() + borders + (this.props.showHeader ? 37 : 0) }}>
+                style={{ display: !this._toolTipText ? 'none' : undefined, left: this.props.location[0], top: this.props.location[1], width: this.width() + borders, height: this.height() + borders + (this.props.showHeader ? 37 : 0) }}>
                 {this.docPreview}
             </div>
         );
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 4a60c99ec..ff05dcdcb 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -624,14 +624,18 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     };
 
     _exitTrail: Opt<() => void>;
-    PlayTrail = (doc: Doc) => {
-        const savedState = { c: doc, x: NumCast(doc.panX), y: NumCast(doc.panY), s: NumCast(doc.viewScale) };
+    PlayTrail = (docs: Doc[]) => {
+        const savedStates = docs.map(doc => (doc._viewType !== CollectionViewType.Freeform ? undefined : { c: doc, x: NumCast(doc.panX), y: NumCast(doc.panY), s: NumCast(doc.viewScale) }));
         this.startPresentation(0);
         this._exitTrail = () => {
-            const { x, y, s, c } = savedState;
-            c._panX = x;
-            c._panY = y;
-            c._viewScale = s;
+            savedStates
+                .filter(savedState => savedState)
+                .map(savedState => {
+                    const { x, y, s, c } = savedState!;
+                    c._panX = x;
+                    c._panY = y;
+                    c._viewScale = s;
+                });
             LightboxView.SetLightboxDoc(undefined);
             Doc.RemoveDocFromList(Doc.MyOverlayDocs, undefined, this.rootDoc);
             return PresStatus.Edit;
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index 9af686d83..c53cc608c 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -50,7 +50,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
     public OnAudio: (e: PointerEvent) => void = unimplementedFunction;
     public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction;
     public StartCropDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction;
-    public Highlight: (color: string, isTargetToggler: boolean) => Opt<Doc> = (color: string, isTargetToggler: boolean) => undefined;
+    public Highlight: (color: string, isTargetToggler: boolean, savedAnnotations?: ObservableMap<number, HTMLDivElement[]>, addAsAnnotation?: boolean) => Opt<Doc> = (color: string, isTargetToggler: boolean) => undefined;
     public GetAnchor: (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => Opt<Doc> = (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => undefined;
     public Delete: () => void = unimplementedFunction;
     public PinToPres: () => void = unimplementedFunction;
@@ -120,7 +120,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
 
     @action
     highlightClicked = (e: React.MouseEvent) => {
-        if (!this.Highlight(this.highlightColor, false) && this.Pinned) {
+        if (!this.Highlight(this.highlightColor, false, undefined, true) && this.Pinned) {
             this.Highlighting = !this.Highlighting;
         }
         AnchorMenu.Instance.fadeOut(true);
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index 7e2a5a237..4c4275ce7 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -2,7 +2,7 @@ import { Tooltip } from '@material-ui/core';
 import { action, computed, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { DirectLinksSym, Doc, DocListCast, DocListCastAsync, Field } from '../../../fields/Doc';
+import { DirectLinksSym, Doc, DocListCast, DocListCastAsync, Field, Opt } from '../../../fields/Doc';
 import { Id } from '../../../fields/FieldSymbols';
 import { DocCast, StrCast } from '../../../fields/Types';
 import { StopEvent } from '../../../Utils';
@@ -205,6 +205,13 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
      */
     @action
     searchCollection(query: string) {
+        this._selectedResult = undefined;
+        this._results = SearchBox.staticSearchCollection(CollectionDockingView.Instance?.rootDoc, query);
+
+        this.computePageRanks();
+    }
+    @action
+    static staticSearchCollection(rootDoc: Opt<Doc>, query: string) {
         const blockedTypes = [DocumentType.PRESELEMENT, DocumentType.KVP, DocumentType.FILTER, DocumentType.SEARCH, DocumentType.SEARCHITEM, DocumentType.FONTICON, DocumentType.BUTTON, DocumentType.SCRIPTING];
         const blockedKeys = [
             'x',
@@ -240,18 +247,15 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
             'panY',
             'viewScale',
         ];
-        const collection = CollectionDockingView.Instance;
         query = query.toLowerCase();
 
-        this._results.clear();
-        this._selectedResult = undefined;
-
-        if (collection !== undefined) {
-            const docs = DocListCast(collection.rootDoc[Doc.LayoutFieldKey(collection.rootDoc)]);
+        const results = new Map<Doc, string[]>();
+        if (rootDoc) {
+            const docs = DocListCast(rootDoc[Doc.LayoutFieldKey(rootDoc)]);
             const docIDs: String[] = [];
             SearchBox.foreachRecursiveDoc(docs, (depth: number, doc: Doc) => {
-                const dtype = StrCast(doc.type, 'string') as DocumentType;
-                if (dtype && !blockedTypes.includes(dtype) && !docIDs.includes(doc[Id]) && depth > 0) {
+                const dtype = StrCast(doc.type) as DocumentType;
+                if (dtype && !blockedTypes.includes(dtype) && !docIDs.includes(doc[Id]) && depth >= 0) {
                     const hlights = new Set<string>();
                     SearchBox.documentKeys(doc).forEach(
                         key =>
@@ -262,14 +266,13 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
                     blockedKeys.forEach(key => hlights.delete(key));
 
                     if (Array.from(hlights.keys()).length > 0) {
-                        this._results.set(doc, Array.from(hlights.keys()));
+                        results.set(doc, Array.from(hlights.keys()));
                     }
                 }
                 docIDs.push(doc[Id]);
             });
         }
-
-        this.computePageRanks();
+        return results;
     }
 
     /**
diff --git a/src/fields/Types.ts b/src/fields/Types.ts
index bf40a0d7b..3ef7cb1de 100644
--- a/src/fields/Types.ts
+++ b/src/fields/Types.ts
@@ -1,48 +1,50 @@
-import { Field, Opt, FieldResult, Doc } from "./Doc";
-import { List } from "./List";
-import { RefField } from "./RefField";
-import { DateField } from "./DateField";
-import { ScriptField } from "./ScriptField";
-import { URLField, WebField, ImageField } from "./URLField";
-import { TextField } from "@material-ui/core";
-import { RichTextField } from "./RichTextField";
-
-export type ToType<T extends InterfaceValue> =
-    T extends "string" ? string :
-    T extends "number" ? number :
-    T extends "boolean" ? boolean :
-    T extends ListSpec<infer U> ? List<U> :
-    // T extends { new(...args: any[]): infer R } ? (R | Promise<R>) : never;
-    T extends DefaultFieldConstructor<infer _U> ? never :
-    T extends { new(...args: any[]): List<Field> } ? never :
-    T extends { new(...args: any[]): infer R } ? R :
-    T extends (doc?: Doc) => infer R ? R : never;
-
-export type ToConstructor<T extends Field> =
-    T extends string ? "string" :
-    T extends number ? "number" :
-    T extends boolean ? "boolean" :
-    T extends List<infer U> ? ListSpec<U> :
-    new (...args: any[]) => T;
+import { Field, Opt, FieldResult, Doc } from './Doc';
+import { List } from './List';
+import { RefField } from './RefField';
+import { DateField } from './DateField';
+import { ScriptField } from './ScriptField';
+import { URLField, WebField, ImageField } from './URLField';
+import { TextField } from '@material-ui/core';
+import { RichTextField } from './RichTextField';
+
+export type ToType<T extends InterfaceValue> = T extends 'string'
+    ? string
+    : T extends 'number'
+    ? number
+    : T extends 'boolean'
+    ? boolean
+    : T extends ListSpec<infer U>
+    ? List<U>
+    : // T extends { new(...args: any[]): infer R } ? (R | Promise<R>) : never;
+    T extends DefaultFieldConstructor<infer _U>
+    ? never
+    : T extends { new (...args: any[]): List<Field> }
+    ? never
+    : T extends { new (...args: any[]): infer R }
+    ? R
+    : T extends (doc?: Doc) => infer R
+    ? R
+    : never;
+
+export type ToConstructor<T extends Field> = T extends string ? 'string' : T extends number ? 'number' : T extends boolean ? 'boolean' : T extends List<infer U> ? ListSpec<U> : new (...args: any[]) => T;
 
 export type ToInterface<T extends Interface> = {
-    [P in Exclude<keyof T, "proto">]: T[P] extends DefaultFieldConstructor<infer F> ? Exclude<FieldResult<F>, undefined> : FieldResult<ToType<T[P]>>;
+    [P in Exclude<keyof T, 'proto'>]: T[P] extends DefaultFieldConstructor<infer F> ? Exclude<FieldResult<F>, undefined> : FieldResult<ToType<T[P]>>;
 };
 
 // type ListSpec<T extends Field[]> = { List: ToContructor<Head<T>> | ListSpec<Tail<T>> };
 export type ListSpec<T extends Field> = { List: ToConstructor<T> };
 
 export type DefaultFieldConstructor<T extends Field> = {
-    type: ToConstructor<T>,
-    defaultVal: T
+    type: ToConstructor<T>;
+    defaultVal: T;
 };
 
 // type ListType<U extends Field[]> = { 0: List<ListType<Tail<U>>>, 1: ToType<Head<U>> }[HasTail<U> extends true ? 0 : 1];
 
 export type Head<T extends any[]> = T extends [any, ...any[]] ? T[0] : never;
-export type Tail<T extends any[]> =
-    ((...t: T) => any) extends ((_: any, ...tail: infer TT) => any) ? TT : [];
-export type HasTail<T extends any[]> = T extends ([] | [any]) ? false : true;
+export type Tail<T extends any[]> = ((...t: T) => any) extends (_: any, ...tail: infer TT) => any ? TT : [];
+export type HasTail<T extends any[]> = T extends [] | [any] ? false : true;
 
 export type InterfaceValue = ToConstructor<Field> | ListSpec<Field> | DefaultFieldConstructor<Field> | ((doc?: Doc) => any);
 //TODO Allow you to optionally specify default values for schemas, which should then make that field not be partial
@@ -58,14 +60,14 @@ export function Cast<T extends CastCtor>(field: FieldResult, ctor: T): FieldResu
 export function Cast<T extends CastCtor>(field: FieldResult, ctor: T, defaultVal: WithoutList<WithoutRefField<ToType<T>>> | null): WithoutList<ToType<T>>;
 export function Cast<T extends CastCtor>(field: FieldResult, ctor: T, defaultVal?: ToType<T> | null): FieldResult<ToType<T>> | undefined {
     if (field instanceof Promise) {
-        return defaultVal === undefined ? field.then(f => Cast(f, ctor) as any) as any : defaultVal === null ? undefined : defaultVal;
+        return defaultVal === undefined ? (field.then(f => Cast(f, ctor) as any) as any) : defaultVal === null ? undefined : defaultVal;
     }
     if (field !== undefined && !(field instanceof Promise)) {
-        if (typeof ctor === "string") {
+        if (typeof ctor === 'string') {
             if (typeof field === ctor) {
                 return field as ToType<T>;
             }
-        } else if (typeof ctor === "object") {
+        } else if (typeof ctor === 'object') {
             if (field instanceof List) {
                 return field as any;
             }
@@ -81,15 +83,15 @@ export function DocCast(field: FieldResult, defaultVal?: Doc) {
 }
 
 export function NumCast(field: FieldResult, defaultVal: number | null = 0) {
-    return Cast(field, "number", defaultVal);
+    return Cast(field, 'number', defaultVal);
 }
 
-export function StrCast(field: FieldResult, defaultVal: string | null = "") {
-    return Cast(field, "string", defaultVal);
+export function StrCast(field: FieldResult, defaultVal: string | null = '') {
+    return Cast(field, 'string', defaultVal);
 }
 
 export function BoolCast(field: FieldResult, defaultVal: boolean | null = false) {
-    return Cast(field, "boolean", defaultVal);
+    return Cast(field, 'boolean', defaultVal);
 }
 export function DateCast(field: FieldResult) {
     return Cast(field, DateField, null);
@@ -113,7 +115,7 @@ type WithoutList<T extends Field> = T extends List<infer R> ? (R extends RefFiel
 export function FieldValue<T extends Field, U extends WithoutList<T>>(field: FieldResult<T>, defaultValue: U): WithoutList<T>;
 export function FieldValue<T extends Field>(field: FieldResult<T>): Opt<T>;
 export function FieldValue<T extends Field>(field: FieldResult<T>, defaultValue?: T): Opt<T> {
-    return (field instanceof Promise || field === undefined) ? defaultValue : field;
+    return field instanceof Promise || field === undefined ? defaultValue : field;
 }
 
 export interface PromiseLike<T> {
@@ -121,5 +123,9 @@ export interface PromiseLike<T> {
 }
 export function PromiseValue<T extends Field>(field: FieldResult<T>): PromiseLike<Opt<T>> {
     if (field instanceof Promise) return field as Promise<Opt<T>>;
-    return { then(cb: ((field: Opt<T>) => void)) { return cb(field); } };
-}
\ No newline at end of file
+    return {
+        then(cb: (field: Opt<T>) => void) {
+            return cb(field);
+        },
+    };
+}
-- 
cgit v1.2.3-70-g09d2

