From 9d69ab27de83ead3e499edc9028ba85749407a1e Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 23 Apr 2024 18:35:59 -0400
Subject: more lint cleanup

---
 src/client/views/PropertiesSection.tsx | 59 +++++++++++++++-------------------
 1 file changed, 26 insertions(+), 33 deletions(-)

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

diff --git a/src/client/views/PropertiesSection.tsx b/src/client/views/PropertiesSection.tsx
index 3c9fa1123..f083f03b9 100644
--- a/src/client/views/PropertiesSection.tsx
+++ b/src/client/views/PropertiesSection.tsx
@@ -1,5 +1,8 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+/* eslint-disable react/require-default-props */
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable } from 'mobx';
+import { action, computed } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { SettingsManager } from '../util/SettingsManager';
@@ -10,7 +13,6 @@ export interface PropertiesSectionProps {
     children?: JSX.Element | string | null;
     isOpen: boolean;
     setIsOpen: (bool: boolean) => any;
-    inSection?: boolean;
     setInSection?: (bool: boolean) => any;
     onDoubleClick?: () => void;
 }
@@ -21,44 +23,35 @@ export class PropertiesSection extends React.Component<PropertiesSectionProps> {
         return SettingsManager.userColor;
     }
 
-    @computed get backgroundColor() {
-        return SettingsManager.userBackgroundColor;
-    }
-
     @computed get variantColor() {
         return SettingsManager.userVariantColor;
     }
 
-    @observable isDouble: boolean = false;
-
     render() {
         if (this.props.children === undefined || this.props.children === null) return null;
-        else
-            return (
-                <div className="propertiesView-section" onPointerEnter={action(() => this.props.setInSection && this.props.setInSection(true))} onPointerLeave={action(() => this.props.setInSection && this.props.setInSection(false))}>
-                    <div
-                        className="propertiesView-sectionTitle"
-                        onDoubleClick={action(e => {
-                            this.isDouble = true;
-                            this.props.onDoubleClick && this.props.onDoubleClick();
-                            this.props.setIsOpen(true);
-                            setTimeout(() => (this.isDouble = false), 300);
-                        })}
-                        onClick={action(e => {
-                            this.props.setIsOpen(!this.props.isOpen);
-                        })}
-                        style={{
-                            background: this.variantColor,
-                            // this.props.isOpen ? this.variantColor : this.backgroundColor,
-                            color: this.color,
-                        }}>
-                        {this.props.title}
-                        <div className="propertiesView-sectionTitle-icon">
-                            <FontAwesomeIcon icon={this.props.isOpen ? 'caret-down' : 'caret-right'} size="lg" />
-                        </div>
+        return (
+            <div className="propertiesView-section" onPointerEnter={action(() => this.props.setInSection && this.props.setInSection(true))} onPointerLeave={action(() => this.props.setInSection && this.props.setInSection(false))}>
+                <div
+                    className="propertiesView-sectionTitle"
+                    onDoubleClick={action(() => {
+                        this.props.onDoubleClick && this.props.onDoubleClick();
+                        this.props.setIsOpen(true);
+                    })}
+                    onClick={action(() => {
+                        this.props.setIsOpen(!this.props.isOpen);
+                    })}
+                    style={{
+                        background: this.variantColor,
+                        // this.props.isOpen ? this.variantColor : this.backgroundColor,
+                        color: this.color,
+                    }}>
+                    {this.props.title}
+                    <div className="propertiesView-sectionTitle-icon">
+                        <FontAwesomeIcon icon={this.props.isOpen ? 'caret-down' : 'caret-right'} size="lg" />
                     </div>
-                    {!this.props.isOpen ? null : <div className="propertiesView-content">{this.props.children}</div>}
                 </div>
-            );
+                {!this.props.isOpen ? null : <div className="propertiesView-content">{this.props.children}</div>}
+            </div>
+        );
     }
 }
-- 
cgit v1.2.3-70-g09d2


From aa4f7b37483c516b92181d3374d3151972b98383 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 24 Apr 2024 14:56:48 -0400
Subject: fixed search on pdfs to display results when pDF is not selected. 
 fixed presentation transitions to animate.  changed so that annotaitons on
 pdfs would highlight when following a pres slide.   fixed scrolling to
 annotations (and other viewSpecs) from presentations by using the slide
 target, not the slide as the focus document.  cleaned up search and fixed to
 unhighlight searches on close.  fixe pdf search unhighligting to work.

---
 src/ClientUtils.ts                                 |  17 +-
 src/client/util/DocumentManager.ts                 |   4 +-
 src/client/util/SerializationHelper.ts             |   4 +-
 src/client/views/MarqueeAnnotator.tsx              |  12 +-
 src/client/views/PropertiesSection.tsx             |   3 +-
 src/client/views/PropertiesView.tsx                |   9 +-
 .../CollectionFreeFormPannableContents.tsx         |   4 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   3 +-
 src/client/views/nodes/FieldView.tsx               |   1 -
 src/client/views/nodes/FontIconBox/FontIconBox.tsx |   2 +-
 src/client/views/nodes/PDFBox.scss                 |   2 +-
 src/client/views/nodes/PDFBox.tsx                  |  12 +-
 src/client/views/nodes/trails/PresBox.tsx          |   3 +-
 src/client/views/pdf/Annotation.tsx                |  89 ++++++----
 src/client/views/pdf/PDFViewer.tsx                 |  54 +++---
 src/client/views/search/SearchBox.tsx              | 189 +++++++++++++--------
 src/client/views/selectedDoc/SelectedDocView.tsx   |   2 +-
 src/client/views/selectedDoc/index.ts              |   2 +-
 src/fields/Doc.ts                                  |   3 +
 src/fields/List.ts                                 |   2 +-
 20 files changed, 253 insertions(+), 164 deletions(-)

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

diff --git a/src/ClientUtils.ts b/src/ClientUtils.ts
index 57e0373c4..402f6613a 100644
--- a/src/ClientUtils.ts
+++ b/src/ClientUtils.ts
@@ -3,7 +3,7 @@ import * as React from 'react';
 import { ColorResult } from 'react-color';
 import * as rp from 'request-promise';
 import { numberRange, decimalToHexString } from './Utils';
-import { DocumentType } from './client/documents/DocumentTypes';
+import { CollectionViewType, DocumentType } from './client/documents/DocumentTypes';
 import { Colors } from './client/views/global/globalEnums';
 
 export function DashColor(color: string) {
@@ -100,11 +100,22 @@ export namespace ClientUtils {
         return Date.now() - downTime < ClientUtils.CLICK_TIME && Math.abs(x - downX) < ClientUtils.DRAG_THRESHOLD && Math.abs(y - downY) < ClientUtils.DRAG_THRESHOLD;
     }
 
-    export function cleanDocumentType(type: DocumentType) {
+    export function cleanDocumentTypeExt(type: DocumentType) {
         switch (type) {
+            case DocumentType.PDF:   return 'PDF';
+            case DocumentType.IMG:   return 'Img';
+            case DocumentType.AUDIO: return 'Aud';
+            case DocumentType.COL:   return 'Col';
+            case DocumentType.RTF:   return 'Rtf';
+            default:                 return type.charAt(0).toUpperCase() + type.substring(1,3);
+        } // prettier-ignore
+    }
+    export function cleanDocumentType(type: DocumentType, colType: CollectionViewType) {
+        switch (type) {
+            case DocumentType.PDF:   return 'PDF';
             case DocumentType.IMG:   return 'Image';
             case DocumentType.AUDIO: return 'Audio';
-            case DocumentType.COL:   return 'Collection';
+            case DocumentType.COL:   return 'Collection:'+colType;
             case DocumentType.RTF:   return 'Text';
             default:                 return type.charAt(0).toUpperCase() + type.slice(1);
         } // prettier-ignore
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index cca92816f..ac3721436 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -310,8 +310,8 @@ export class DocumentManager {
             focused = focused || nextFocus !== undefined; // keep track of whether focusing on a view needed to actually change anything
             // eslint-disable-next-line no-await-in-loop
             const { childDocView, viewSpec } = await iterator(docView);
-            if (!childDocView) return { viewSpec: options.anchorDoc ?? viewSpec ?? docView.Document, docView, contextView, focused };
-            contextView = options.anchorDoc?.layout_unrendered && !childDocView.Document.layout_unrendered ? childDocView : docView;
+            if (!childDocView) return { viewSpec: viewSpec ?? docView.Document, docView, contextView, focused };
+            contextView = !childDocView.Document.layout_unrendered ? childDocView : docView;
             docView = childDocView;
         }
         return undefined;
diff --git a/src/client/util/SerializationHelper.ts b/src/client/util/SerializationHelper.ts
index fa1a911f7..d9d22437c 100644
--- a/src/client/util/SerializationHelper.ts
+++ b/src/client/util/SerializationHelper.ts
@@ -65,7 +65,7 @@ export namespace SerializationHelper {
     }
 }
 
-export function Deserializable(className: string, afterDeserialize?: (obj: any) => void | Promise<any>, constructorArgs?: [string]): (constructor: { new (...args: any[]): any }) => void {
+export function Deserializable(classNameForSerializer: string, afterDeserialize?: (obj: any) => void | Promise<any>, constructorArgs?: [string]): (constructor: { new (...args: any[]): any }) => void {
     function addToMap(className: string, Ctor: { new (...args: any[]): any }) {
         const schema = getDefaultModelSchema(Ctor) as any;
         if (schema.targetClass !== Ctor || constructorArgs) {
@@ -78,7 +78,7 @@ export function Deserializable(className: string, afterDeserialize?: (obj: any)
             throw new Error(`Name ${className} has already been registered as deserializable`);
         }
     }
-    return (ctor: { new (...args: any[]): any }) => addToMap(className, ctor);
+    return (ctor: { new (...args: any[]): any }) => addToMap(classNameForSerializer, ctor);
 }
 
 export function autoObject(): PropSchema {
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index 0301632ec..c4c00e0c3 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -95,6 +95,7 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
             presentation_zoomText: true,
             title: '>' + this.props.Document.title,
         });
+        const textRegionAnnoProto = textRegionAnno[DocData];
         let minX = Number.MAX_VALUE;
         let maxX = -Number.MAX_VALUE;
         let minY = Number.MAX_VALUE;
@@ -107,7 +108,7 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
                 textRegion.y = parseInt(anno.style.top ?? '0');
                 textRegion._height = parseInt(anno.style.height ?? '0');
                 textRegion._width = parseInt(anno.style.width ?? '0');
-                textRegion.annoTextRegion = textRegionAnno;
+                textRegion.embedContainer = textRegionAnnoProto;
                 textRegion.backgroundColor = color;
                 annoDocs.push(textRegion);
                 anno.remove();
@@ -118,7 +119,6 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
             })
         );
 
-        const textRegionAnnoProto = textRegionAnno[DocData];
         textRegionAnnoProto.y = Math.max(minY, 0);
         textRegionAnnoProto.x = Math.max(minX, 0);
         textRegionAnnoProto.height = Math.max(maxY, 0) - Math.max(minY, 0);
@@ -202,10 +202,10 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
                 return target;
             };
             DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.props.docView(), sourceAnchorCreator, targetCreator), e.pageX, e.pageY, {
-                dragComplete: e => {
-                    if (!e.aborted && e.annoDragData && e.annoDragData.linkSourceDoc && e.annoDragData.dropDocument && e.linkDocument) {
-                        e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this.props.Document;
-                        e.annoDragData.linkSourceDoc.followLinkZoom = false;
+                dragComplete: dragEv => {
+                    if (!dragEv.aborted && dragEv.annoDragData && dragEv.annoDragData.linkSourceDoc && dragEv.annoDragData.dropDocument && dragEv.linkDocument) {
+                        dragEv.annoDragData.linkSourceDoc.followLinkToggle = dragEv.annoDragData.dropDocument.annotationOn === this.props.Document;
+                        dragEv.annoDragData.linkSourceDoc.followLinkZoom = false;
                     }
                 },
             });
diff --git a/src/client/views/PropertiesSection.tsx b/src/client/views/PropertiesSection.tsx
index f083f03b9..b9a587719 100644
--- a/src/client/views/PropertiesSection.tsx
+++ b/src/client/views/PropertiesSection.tsx
@@ -13,7 +13,6 @@ export interface PropertiesSectionProps {
     children?: JSX.Element | string | null;
     isOpen: boolean;
     setIsOpen: (bool: boolean) => any;
-    setInSection?: (bool: boolean) => any;
     onDoubleClick?: () => void;
 }
 
@@ -30,7 +29,7 @@ export class PropertiesSection extends React.Component<PropertiesSectionProps> {
     render() {
         if (this.props.children === undefined || this.props.children === null) return null;
         return (
-            <div className="propertiesView-section" onPointerEnter={action(() => this.props.setInSection && this.props.setInSection(true))} onPointerLeave={action(() => this.props.setInSection && this.props.setInSection(false))}>
+            <div className="propertiesView-section">
                 <div
                     className="propertiesView-sectionTitle"
                     onDoubleClick={action(() => {
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 8ac0f7d7b..5e1c80f0c 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -114,7 +114,6 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
     @observable openAddSlide: boolean = false;
     @observable openSlideOptions: boolean = false;
 
-    @observable inOptions: boolean = false;
     @observable _controlButton: boolean = false;
     private _disposers: { [name: string]: IReactionDisposer } = {};
 
@@ -602,9 +601,9 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
     }
 
     @computed get currentType() {
-        const documentType = StrCast(this.selectedDoc?.type);
-        const currentType: string = documentType;
-        const capitalizedDocType = ClientUtils.cleanDocumentType(currentType as DocumentType);
+        const docType = StrCast(this.selectedDoc?.type) as DocumentType;
+        const colType = StrCast(this.selectedDoc?.type_collection) as CollectionViewType;
+        const capitalizedDocType = ClientUtils.cleanDocumentType(docType, colType);
 
         return (
             <div>
@@ -1080,7 +1079,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
     @computed get optionsSubMenu() {
         return (
             // prettier-ignore
-            <PropertiesSection title="Options" inSection={this.inOptions} isOpen={this.openOptions} setInSection={bool => { this.inOptions = bool; }} setIsOpen={bool => { this.openOptions = bool; }} onDoubleClick={this.CloseAll}>
+            <PropertiesSection title="Options" isOpen={this.openOptions} setIsOpen={bool => { this.openOptions = bool; }} onDoubleClick={this.CloseAll}>
                 <PropertiesButtons />
             </PropertiesSection>
         );
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx
index 707f6c198..90977d955 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx
@@ -11,7 +11,7 @@ export interface CollectionFreeFormPannableContentsProps {
     Document: Doc;
     viewDefDivClick?: ScriptField;
     children?: React.ReactNode | undefined;
-    transition?: string;
+    transition: () => string;
     isAnnotationOverlay: boolean | undefined;
     showPresPaths: () => boolean;
     transform: () => string;
@@ -53,7 +53,7 @@ export class CollectionFreeFormPannableContents extends ObservableReactComponent
                 }}
                 style={{
                     transform: this._props.transform(),
-                    transition: this._props.transition,
+                    transition: this._props.transition(),
                     width: this._props.isAnnotationOverlay ? undefined : 0, // if not an overlay, then this will be the size of the collection, but panning and zooming will move it outside the visible border of the collection and make it selectable.  This problem shows up after zooming/panning on a background collection -- you can drag the collection by clicking on apparently empty space outside the collection
                 }}>
                 {this.props.children}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index d9c988d54..12a299ce6 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -246,6 +246,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     onChildDoubleClickHandler = () => this._props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick);
     elementFunc = () => this._layoutElements;
     viewTransition = () => (this._panZoomTransition ? '' + this._panZoomTransition : undefined);
+    panZoomTransition = () => (this._panZoomTransition ? `transform ${this._panZoomTransition}ms` : Cast(this.layoutDoc._viewTransition, 'string', Cast(this.Document._viewTransition, 'string', null)));
     fitContentOnce = () => {
         const vals = this.fitToContentVals;
         this.layoutDoc._freeform_panX = vals.bounds.cx;
@@ -1854,7 +1855,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 isAnnotationOverlay={this.isAnnotationOverlay}
                 transform={this.PanZoomCenterXf}
                 showPresPaths={this.showPresPaths}
-                transition={this._panZoomTransition ? `transform ${this._panZoomTransition}ms` : Cast(this.layoutDoc._viewTransition, 'string', Cast(this.Document._viewTransition, 'string', null))}
+                transition={this.panZoomTransition}
                 viewDefDivClick={this._props.viewDefDivClick}>
                 {this.props.children ?? null} {/* most likely case of children is document content that's being annoated: eg., an image */}
                 {this.contentViews}
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 5c91d5aca..3f453eb93 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -29,7 +29,6 @@ export interface FocusViewOptions {
     openLocation?: OpenWhere; // where to open a missing document
     zoomTextSelections?: boolean; // whether to display a zoomed overlay of anchor text selections
     toggleTarget?: boolean; // whether to toggle target on and off
-    anchorDoc?: Doc; // doc containing anchor info to apply at end of focus to target doc
     easeFunc?: 'linear' | 'ease'; // transition method for scrolling
 }
 export type FocusFuncType = (doc: Doc, options: FocusViewOptions) => Opt<number>;
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
index 79738c452..1b2aefbe2 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
@@ -178,7 +178,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
                     if (selected.length > 1) {
                         text = selected.length + ' selected';
                     } else {
-                        text = ClientUtils.cleanDocumentType(StrCast(selected.lastElement().type) as DocumentType);
+                        text = ClientUtils.cleanDocumentType(StrCast(selected.lastElement().type) as DocumentType, '' as CollectionViewType);
                         icon = Doc.toIcon(selected.lastElement());
                     }
                     return (
diff --git a/src/client/views/nodes/PDFBox.scss b/src/client/views/nodes/PDFBox.scss
index 0f5e25a0c..7bca1230f 100644
--- a/src/client/views/nodes/PDFBox.scss
+++ b/src/client/views/nodes/PDFBox.scss
@@ -236,7 +236,7 @@
     //pointer-events: none;
     .pdfViewerDash-text {
         .textLayer {
-            display: none;
+            // display: none;  // this makes search highlights not show up
             span {
                 user-select: none;
             }
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index cc897aaef..b03b90418 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -28,6 +28,7 @@ import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
 import { PinProps, ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
 import { Colors } from '../global/globalEnums';
+// eslint-disable-next-line import/extensions
 import { CreateImage } from './WebBoxRenderer';
 import { PDFViewer } from '../pdf/PDFViewer';
 import { SidebarAnnos } from '../SidebarAnnos';
@@ -225,12 +226,13 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         );
     }
 
-    sidebarAddDocTab = (doc: Doc, where: OpenWhere) => {
-        if (DocListCast(this.Document[this._props.fieldKey + '_sidebar']).includes(doc) && !this.SidebarShown) {
+    sidebarAddDocTab = (docIn: Doc | Doc[], where: OpenWhere) => {
+        const docs = docIn instanceof Doc ? [docIn] : docIn;
+        if (docs.some(doc => DocListCast(this.Document[this._props.fieldKey + '_sidebar']).includes(doc)) && !this.SidebarShown) {
             this.toggleSidebar(false);
             return true;
         }
-        return this._props.addDocTab(doc, where);
+        return this._props.addDocTab(docs, where);
     };
     focus = (anchor: Doc, options: FocusViewOptions) => {
         this._initialScrollTarget = anchor;
@@ -344,7 +346,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
         setupMoveUpEvents(
             this,
             e,
-            (e, down, delta) => {
+            (moveEv, down, delta) => {
                 const localDelta = this._props
                     .ScreenToLocalTransform()
                     .scale(this._props.NativeDimScaling?.() || 1)
@@ -359,7 +361,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
                 }
                 return false;
             },
-            (e, movement, isClick) => !isClick && batch.end(),
+            (clickEv, movement, isClick) => !isClick && batch.end(),
             () => {
                 onButton && this.toggleSidebar();
                 batch.end();
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index a3b1a419b..0a4efbfe4 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -104,7 +104,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return DocCast(this.childDocs[NumCast(this.Document._itemIndex)]);
     }
     @computed get targetDoc() {
-        return Cast(this.activeItem?.presentation_targetDoc, Doc, null);
+        return DocCast(this.activeItem?.presentation_targetDoc);
     }
     public static targetRenderedDoc = (doc: Doc) => {
         const targetDoc = Cast(doc?.presentation_targetDoc, Doc, null);
@@ -774,7 +774,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             effect: activeItem,
             noSelect: true,
             openLocation: targetDoc.type === DocumentType.PRES ? ((OpenWhere.replace + ':' + PresBox.PanelName) as OpenWhere) : OpenWhere.addLeft,
-            anchorDoc: activeItem,
             easeFunc: StrCast(activeItem.presEaseFunc, 'ease') as any,
             zoomTextSelections: BoolCast(activeItem.presentation_zoomText),
             playAudio: BoolCast(activeItem.presPlayAudio),
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index 053c88e17..38578837a 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -1,18 +1,20 @@
+/* eslint-disable react/jsx-props-no-spreading */
 import { action, computed, makeObservable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, DocListCast, Opt } from '../../../fields/Doc';
 import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
-import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../fields/Types';
+import { BoolCast, DocCast, NumCast, StrCast } from '../../../fields/Types';
 import { LinkFollower } from '../../util/LinkFollower';
 import { LinkManager } from '../../util/LinkManager';
-import { undoBatch } from '../../util/UndoManager';
+import { undoable } from '../../util/UndoManager';
+import { ObservableReactComponent } from '../ObservableReactComponent';
 import { OpenWhere } from '../nodes/DocumentView';
 import { FieldViewProps } from '../nodes/FieldView';
 import { AnchorMenu } from './AnchorMenu';
 import './Annotation.scss';
-import { ObservableReactComponent } from '../ObservableReactComponent';
+import { Highlight } from '../../../fields/DocSymbols';
 
 interface IAnnotationProps extends FieldViewProps {
     anno: Doc;
@@ -27,11 +29,41 @@ export class Annotation extends ObservableReactComponent<IAnnotationProps> {
         super(props);
         makeObservable(this);
     }
+
+    @computed get linkHighlighted() {
+        const found = LinkManager.Instance.getAllDirectLinks(this._props.anno).find(link => {
+            const a1 = LinkManager.getOppositeAnchor(link, this._props.anno);
+            return a1 && Doc.GetBrushStatus(DocCast(a1.annotationOn, a1));
+        });
+        return found;
+    }
+    linkHighlightedFunc = () => this.linkHighlighted;
+    highlightedFunc = () => this._props.anno[Highlight];
+
+    deleteAnnotation = undoable(() => {
+        const docAnnotations = DocListCast(this._props.dataDoc[this._props.fieldKey]);
+        this._props.dataDoc[this._props.fieldKey] = new List<Doc>(docAnnotations.filter(a => a !== this._props.anno));
+        AnchorMenu.Instance.fadeOut(true);
+        this._props.select(false);
+    }, 'delete annotation');
+
+    pinToPres = undoable(() => this._props.pinToPres(this._props.anno, {}), 'pin to pres');
+
     render() {
         return (
             <div style={{ display: this._props.anno.textCopied && !Doc.GetBrushHighlightStatus(this._props.anno) ? 'none' : undefined }}>
                 {DocListCast(this._props.anno.text_inlineAnnotations).map(a => (
-                    <RegionAnnotation pointerEvents={this._props.pointerEvents} {...this._props} document={a} key={a[Id]} />
+                    // eslint-disable-next-line no-use-before-define
+                    <RegionAnnotation
+                        pointerEvents={this._props.pointerEvents}
+                        {...this._props}
+                        highlighted={this.highlightedFunc}
+                        linkHighlighted={this.linkHighlightedFunc}
+                        pinToPres={this.pinToPres}
+                        deleteAnnotation={this.deleteAnnotation}
+                        document={a}
+                        key={a[Id]}
+                    />
                 ))}
             </div>
         );
@@ -40,49 +72,41 @@ export class Annotation extends ObservableReactComponent<IAnnotationProps> {
 
 interface IRegionAnnotationProps extends IAnnotationProps {
     document: Doc;
+    linkHighlighted: () => Doc | undefined;
+    highlighted: () => any;
+    deleteAnnotation: () => void;
+    pinToPres: (...args: any[]) => void;
     pointerEvents?: () => Opt<string>;
 }
 @observer
 class RegionAnnotation extends ObservableReactComponent<IRegionAnnotationProps> {
     private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
 
-    @computed get annoTextRegion() {
-        return Cast(this._props.document.annoTextRegion, Doc, null) || this._props.document;
+    @computed get regionDoc() {
+        return DocCast(this._props.document.embedContainer, this._props.document);
     }
 
-    @undoBatch
-    deleteAnnotation = () => {
-        const docAnnotations = DocListCast(this._props.dataDoc[this._props.fieldKey]);
-        this._props.dataDoc[this._props.fieldKey] = new List<Doc>(docAnnotations.filter(a => a !== this.annoTextRegion));
-        AnchorMenu.Instance.fadeOut(true);
-        this._props.select(false);
-    };
-
-    @undoBatch
-    pinToPres = () => this._props.pinToPres(this.annoTextRegion, {});
+    makeTargetToggle = undoable(() => { this.regionDoc.followLinkToggle = !this.regionDoc.followLinkToggle }, "set link toggle"); // prettier-ignore
 
-    @undoBatch
-    makeTargetToggle = () => { this.annoTextRegion.followLinkToggle = !this.annoTextRegion.followLinkToggle }; // prettier-ignore
+    isTargetToggler = () => BoolCast(this.regionDoc.followLinkToggle);
 
-    isTargetToggler = () => BoolCast(this.annoTextRegion.followLinkToggle);
-    @undoBatch
-    showTargetTrail = (anchor: Doc) => {
+    showTargetTrail = undoable((anchor: Doc) => {
         const trail = DocCast(anchor.presentationTrail);
         if (trail) {
             Doc.ActivePresentation = trail;
             this._props.addDocTab(trail, OpenWhere.replaceRight);
         }
-    };
+    }, 'show target trail');
 
     @action
     onContextMenu = (e: React.MouseEvent) => {
         AnchorMenu.Instance.Status = 'annotation';
-        AnchorMenu.Instance.Delete = this.deleteAnnotation.bind(this);
+        AnchorMenu.Instance.Delete = this._props.deleteAnnotation;
         AnchorMenu.Instance.Pinned = false;
-        AnchorMenu.Instance.PinToPres = this.pinToPres;
+        AnchorMenu.Instance.PinToPres = this._props.pinToPres;
         AnchorMenu.Instance.MakeTargetToggle = this.makeTargetToggle;
         AnchorMenu.Instance.IsTargetToggler = this.isTargetToggler;
-        AnchorMenu.Instance.ShowTargetTrail = () => this.showTargetTrail(this.annoTextRegion);
+        AnchorMenu.Instance.ShowTargetTrail = () => this.showTargetTrail(this.regionDoc);
         AnchorMenu.Instance.jumpTo(e.clientX, e.clientY, true);
         e.stopPropagation();
         e.preventDefault();
@@ -94,19 +118,12 @@ class RegionAnnotation extends ObservableReactComponent<IRegionAnnotationProps>
             e.preventDefault();
         } else if (e.button === 0) {
             e.stopPropagation();
-            LinkFollower.FollowLink(undefined, this.annoTextRegion, false);
+            LinkFollower.FollowLink(undefined, this.regionDoc, false);
         }
     };
 
-    @computed get linkHighlighted() {
-        for (const link of LinkManager.Instance.getAllDirectLinks(this._props.document)) {
-            const a1 = LinkManager.getOppositeAnchor(link, this._props.document);
-            if (a1 && Doc.GetBrushStatus(DocCast(a1.annotationOn, this._props.document))) return true;
-        }
-    }
-
     render() {
-        const brushed = this.annoTextRegion && Doc.GetBrushHighlightStatus(this.annoTextRegion);
+        const brushed = this.regionDoc && Doc.GetBrushHighlightStatus(this.regionDoc);
         return (
             <div
                 className="htmlAnnotation"
@@ -128,8 +145,8 @@ class RegionAnnotation extends ObservableReactComponent<IRegionAnnotationProps>
                     height: NumCast(this._props.document._height),
                     opacity: brushed === Doc.DocBrushStatus.highlighted ? 0.5 : undefined,
                     pointerEvents: this._props.pointerEvents?.() as any,
-                    outline: brushed === Doc.DocBrushStatus.unbrushed && this.linkHighlighted ? 'solid 1px lightBlue' : undefined,
-                    backgroundColor: brushed === Doc.DocBrushStatus.highlighted ? 'orange' : StrCast(this._props.document.backgroundColor),
+                    outline: this._props.linkHighlighted() ? 'solid 1px lightBlue' : undefined,
+                    backgroundColor: this._props.highlighted() ? 'orange' : StrCast(this._props.document.backgroundColor),
                 }}
             />
         );
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 0ab952e84..a3fd192f7 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -1,3 +1,5 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
 import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as Pdfjs from 'pdfjs-dist';
@@ -10,7 +12,7 @@ import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { Cast, NumCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, Utils } from '../../../Utils';
+import { emptyFunction } from '../../../Utils';
 import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, ClientUtils, returnAll, returnFalse, returnNone, returnZero, smoothScroll } from '../../../ClientUtils';
 import { DocUtils } from '../../documents/Documents';
 import { SelectionManager } from '../../util/SelectionManager';
@@ -27,9 +29,8 @@ import { AnchorMenu } from './AnchorMenu';
 import { Annotation } from './Annotation';
 import { GPTPopup } from './GPTPopup/GPTPopup';
 import './PDFViewer.scss';
-const _global = (window /* browser */ || global) /* node */ as any;
 
-//pdfjsLib.GlobalWorkerOptions.workerSrc = `/assets/pdf.worker.js`;
+// pdfjsLib.GlobalWorkerOptions.workerSrc = `/assets/pdf.worker.js`;
 // The workerSrc property shall be specified.
 Pdfjs.GlobalWorkerOptions.workerSrc = 'https://unpkg.com/pdfjs-dist@4.1.392/build/pdf.worker.mjs';
 
@@ -43,6 +44,7 @@ interface IViewerProps extends FieldViewProps {
     url: string;
     sidebarAddDoc: (doc: Doc | Doc[], sidebarKey?: string | undefined) => boolean;
     loaded?: (nw: number, nh: number, np: number) => void;
+    // eslint-disable-next-line no-use-before-define
     setPdfViewer: (view: PDFViewer) => void;
     anchorMenuClick?: () => undefined | ((anchor: Doc) => void);
     crop: (region: Doc | undefined, addCrop?: boolean) => Doc | undefined;
@@ -109,8 +111,8 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
 
         this._disposers.layout_autoHeight = reaction(
             () => this._props.layoutDoc._layout_autoHeight,
-            layout_autoHeight => {
-                if (layout_autoHeight) {
+            layoutAutoHeight => {
+                if (layoutAutoHeight) {
                     this._props.layoutDoc._nativeHeight = NumCast(this._props.Document[this._props.fieldKey + '_nativeHeight']);
                     this._props.setHeight?.(NumCast(this._props.Document[this._props.fieldKey + '_nativeHeight']) * (this._props.NativeDimScaling?.() || 1));
                 }
@@ -119,7 +121,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
 
         this._disposers.selected = reaction(
             () => this._props.isSelected(),
-            selected => SelectionManager.Views.length === 1 && this.setupPdfJsViewer(),
+            () => SelectionManager.Views.length === 1 && this.setupPdfJsViewer(),
             { fireImmediately: true }
         );
         this._disposers.curPage = reaction(
@@ -169,7 +171,9 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
                 )
             );
         }
-        runInAction(() => (this._scrollHeight = (this._pageSizes.reduce((size, page) => size + page.height, 0) * 96) / 72));
+        runInAction(() => {
+            this._scrollHeight = (this._pageSizes.reduce((size, page) => size + page.height, 0) * 96) / 72;
+        });
     };
 
     _scrollStopper: undefined | (() => void);
@@ -207,14 +211,18 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
 
     pagesinit = () => {
         if (this._pdfViewer._setDocumentViewerElement?.offsetParent) {
-            runInAction(() => (this._pdfViewer.currentScaleValue = this._props.layoutDoc._freeform_scale = 1));
+            runInAction(() => {
+                this._pdfViewer.currentScaleValue = this._props.layoutDoc._freeform_scale = 1;
+            });
             this.gotoPage(NumCast(this._props.Document._layout_curPage, 1));
         }
         document.removeEventListener('pagesinit', this.pagesinit);
-        var quickScroll: { loc?: string; easeFunc?: 'ease' | 'linear' } | undefined = { loc: this._initialScroll ? this._initialScroll.loc?.toString() : '', easeFunc: this._initialScroll ? this._initialScroll.easeFunc : undefined };
+        let quickScroll: { loc?: string; easeFunc?: 'ease' | 'linear' } | undefined = { loc: this._initialScroll ? this._initialScroll.loc?.toString() : '', easeFunc: this._initialScroll ? this._initialScroll.easeFunc : undefined };
         this._disposers.scale = reaction(
             () => NumCast(this._props.layoutDoc._freeform_scale, 1),
-            scale => (this._pdfViewer.currentScaleValue = scale),
+            scale => {
+                this._pdfViewer.currentScaleValue = scale;
+            },
             { fireImmediately: true }
         );
         this._disposers.scroll = reaction(
@@ -231,7 +239,9 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
                         setTimeout(
                             () => {
                                 this._mainCont.current && (this._scrollStopper = smoothScroll(duration, this._mainCont.current, pos, this._initialScroll?.easeFunc ?? 'ease', this._scrollStopper));
-                                setTimeout(() => (this._forcedScroll = false), duration);
+                                setTimeout(() => {
+                                    this._forcedScroll = false;
+                                }, duration);
                             },
                             this._mainCont.current ? 0 : 250
                         ); // wait for mainCont and try again to scroll
@@ -267,7 +277,9 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
         eventBus._on('pagesinit', this.pagesinit);
         eventBus._on(
             'pagerendered',
-            action(() => (this._showWaiting = false))
+            action(() => {
+                this._showWaiting = false;
+            })
         );
         const pdfLinkService = new PDFJSViewer.PDFLinkService({ eventBus });
         const pdfFindController = new PDFJSViewer.PDFFindController({ linkService: pdfLinkService, eventBus });
@@ -310,7 +322,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
 
     @observable private _scrollTimer: any = undefined;
 
-    onScroll = (e: React.UIEvent<HTMLElement>) => {
+    onScroll = () => {
         if (this._mainCont.current && !this._forcedScroll) {
             this._ignoreScroll = true; // the pdf scrolled, so we need to tell the Doc to scroll but we don't want the doc to then try to set the PDF scroll pos (which would interfere with the smooth scroll animation)
             if (!LinkInfo.Instance?.LinkInfo) {
@@ -345,7 +357,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
             query: searchString,
         };
         if (clear) {
-            this._pdfViewer?.eventBus.dispatch('reset', {});
+            this._pdfViewer?.eventBus.dispatch('findbarclose', {});
         } else if (!searchString) {
             bwd ? this.prevAnnotation() : this.nextAnnotation();
         } else if (this._pdfViewer?.pageViewsReady) {
@@ -392,7 +404,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
     };
 
     @action
-    finishMarquee = (x?: number, y?: number) => {
+    finishMarquee = (/* x?: number, y?: number */) => {
         this._getAnchor = AnchorMenu.Instance?.GetAnchor;
         this.isAnnotating = false;
         this._marqueeref.current?.onTerminateSelection();
@@ -467,7 +479,9 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
         // e.stopPropagation();  // bcz: not sure why this was here.  We need to allow the DocumentView to get clicks to process doubleClicks
     };
 
-    setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void) => (this._setPreviewCursor = func);
+    setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void) => {
+        this._setPreviewCursor = func;
+    };
 
     @action
     onZoomWheel = (e: React.WheelEvent) => {
@@ -490,6 +504,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
         return (
             <div className="pdfViewerDash-annotationLayer" style={{ height: Doc.NativeHeight(this._props.Document), transform: `scale(${NumCast(this._props.layoutDoc._freeform_scale, 1)})` }} ref={this._annotationLayer}>
                 {inlineAnnos.map(anno => (
+                    // eslint-disable-next-line react/jsx-props-no-spreading
                     <Annotation {...this._props} fieldKey={this._props.fieldKey + '_annotations'} pointerEvents={this.pointerEvents} dataDoc={this._props.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />
                 ))}
             </div>
@@ -522,6 +537,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
                 pointerEvents: Doc.ActiveTool !== InkTool.None ? 'all' : undefined,
             }}>
             <CollectionFreeFormView
+                // eslint-disable-next-line react/jsx-props-no-spreading
                 {...this._props}
                 NativeWidth={returnZero}
                 NativeHeight={returnZero}
@@ -529,7 +545,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
                 pointerEvents={this._props.isContentActive() && (SnappingManager.IsDragging || Doc.ActiveTool !== InkTool.None) ? returnAll : returnNone} // freeform view doesn't get events unless something is being dragged onto it.
                 childPointerEvents={this.childPointerEvents} // but freeform children need to get events to allow text editing, etc
                 renderDepth={this._props.renderDepth + 1}
-                isAnnotationOverlay={true}
+                isAnnotationOverlay
                 fieldKey={this._props.fieldKey + '_annotations'}
                 getScrollHeight={this.getScrollHeight}
                 setPreviewCursor={this.setPreviewCursor}
@@ -582,7 +598,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
                     {this.pdfViewerDiv}
                     {this.annotationLayer}
                     {this.overlayLayer}
-                    {this._showWaiting ? <img className="pdfViewerDash-waiting" src={'/assets/loading.gif'} /> : null}
+                    {this._showWaiting ? <img alt="" className="pdfViewerDash-waiting" src="/assets/loading.gif" /> : null}
                     {!this._mainCont.current || !this._annotationLayer.current ? null : (
                         <MarqueeAnnotator
                             ref={this._marqueeref}
@@ -590,7 +606,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
                             getPageFromScroll={this.getPageFromScroll}
                             anchorMenuClick={this._props.anchorMenuClick}
                             scrollTop={0}
-                            isNativeScaled={true}
+                            isNativeScaled
                             annotationLayerScrollTop={NumCast(this._props.Document._layout_scrollTop)}
                             addDocument={this.addDocumentWrapper}
                             docView={this._props.pdfBox.DocumentView!}
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index 0c1a419aa..384e6d654 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -6,7 +6,6 @@ import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, DocListCastAsync, Field, FieldType } from '../../../fields/Doc';
 import { DirectLinks, DocData } from '../../../fields/DocSymbols';
-import { Id } from '../../../fields/FieldSymbols';
 import { DocCast, StrCast } from '../../../fields/Types';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { DocUtils } from '../../documents/Documents';
@@ -16,16 +15,111 @@ import { SearchUtil } from '../../util/SearchUtil';
 import { SettingsManager } from '../../util/SettingsManager';
 import { undoBatch } from '../../util/UndoManager';
 import { ViewBoxBaseComponent } from '../DocComponent';
+import { ObservableReactComponent } from '../ObservableReactComponent';
 import { CollectionDockingView } from '../collections/CollectionDockingView';
 import { IRecommendation, Recommendation } from '../newlightbox/components';
 import { fetchRecommendations } from '../newlightbox/utils';
 import { FieldView, FieldViewProps } from '../nodes/FieldView';
 import './SearchBox.scss';
+import { Id } from '../../../fields/FieldSymbols';
+import { ClientUtils } from '../../../ClientUtils';
 
 const DAMPENING_FACTOR = 0.9;
 const MAX_ITERATIONS = 25;
 const ERROR = 0.03;
 
+export interface SearchBoxItemProps {
+    Document: Doc;
+    searchString: string;
+    isLinkSearch: boolean;
+    matchedKeys: string[];
+    className: string;
+    linkFrom: Doc | undefined;
+    selectItem: (doc: Doc) => void;
+    linkCreateAnchor?: () => Doc | undefined;
+    linkCreated?: (link: Doc) => void;
+}
+@observer
+export class SearchBoxItem extends ObservableReactComponent<SearchBoxItemProps> {
+    constructor(props: SearchBoxItemProps) {
+        super(props);
+        makeObservable(this);
+    }
+
+    /**
+     * @param {Doc} doc - doc to be selected
+     *
+     * This method selects a doc by either jumping to it (centering/zooming in on it)
+     * or opening it in a new tab.
+     */
+    selectElement = async (doc: Doc, finishFunc: () => void) => {
+        await DocumentManager.Instance.showDocument(doc, { willZoomCentered: true }, finishFunc);
+    };
+
+    /**
+     * @param {Doc} doc - doc of the search result that has been clicked on
+     *
+     * This method is called when the user clicks on a search result. The _selectedResult is
+     * updated accordingly and the doc is highlighted with the selectElement method.
+     */
+    onResultClick = action(async (doc: Doc) => {
+        this._props.selectItem(doc);
+        this.selectElement(doc, () => DocumentManager.Instance.getFirstDocumentView(doc)?.ComponentView?.search?.(this._props.searchString, undefined, false));
+    });
+
+    componentWillUnmount(): void {
+        const doc = this._props.Document;
+        DocumentManager.Instance.getFirstDocumentView(doc)?.ComponentView?.search?.('', undefined, true);
+    }
+
+    @undoBatch
+    makeLink = action((linkTo: Doc) => {
+        const linkFrom = this._props.linkCreateAnchor?.();
+        if (linkFrom) {
+            const link = DocUtils.MakeLink(linkFrom, linkTo, {});
+            link && this._props.linkCreated?.(link);
+        }
+    });
+
+    render() {
+        // eslint-disable-next-line no-use-before-define
+        const formattedType = SearchBox.formatType(StrCast(this._props.Document.type), StrCast(this._props.Document.type_collection));
+        const { title } = this._props.Document;
+
+        return (
+            <Tooltip placement="right" title={<div className="dash-tooltip">{title as string}</div>}>
+                <div
+                    onClick={
+                        this._props.isLinkSearch
+                            ? () => this.makeLink(this._props.Document)
+                            : e => {
+                                  this.onResultClick(this._props.Document);
+                                  e.stopPropagation();
+                              }
+                    }
+                    style={{
+                        fontWeight: LinkManager.Links(this._props.linkFrom).find(
+                            link =>
+                                Doc.AreProtosEqual(LinkManager.getOppositeAnchor(link, this._props.linkFrom!), this._props.Document) ||
+                                Doc.AreProtosEqual(DocCast(LinkManager.getOppositeAnchor(link, this._props.linkFrom!)?.annotationOn), this._props.Document)
+                        )
+                            ? 'bold'
+                            : '',
+                    }}
+                    className={this._props.className}>
+                    <div className="searchBox-result-title">{title as string}</div>
+                    <div className="searchBox-result-type" style={{ color: SettingsManager.userVariantColor }}>
+                        {formattedType}
+                    </div>
+                    <div className="searchBox-result-keys" style={{ color: SettingsManager.userVariantColor }}>
+                        {this._props.matchedKeys.join(', ')}
+                    </div>
+                </div>
+            </Tooltip>
+        );
+    }
+}
+
 export interface SearchBoxProps extends FieldViewProps {
     linkSearch: boolean;
     linkFrom?: (() => Doc | undefined) | undefined;
@@ -111,26 +205,6 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
         this._docTypeString = e.target.value;
     });
 
-    /**
-     * @param {Doc} doc - doc of the search result that has been clicked on
-     *
-     * This method is called when the user clicks on a search result. The _selectedResult is
-     * updated accordingly and the doc is highlighted with the selectElement method.
-     */
-    onResultClick = action(async (doc: Doc) => {
-        this._selectedResult = doc;
-        this.selectElement(doc, () => DocumentManager.Instance.getFirstDocumentView(doc)?.ComponentView?.search?.(this._searchString, undefined, false));
-    });
-
-    @undoBatch
-    makeLink = action((linkTo: Doc) => {
-        const linkFrom = this._props.linkCreateAnchor?.();
-        if (linkFrom) {
-            const link = DocUtils.MakeLink(linkFrom, linkTo, {});
-            link && this._props.linkCreated?.(link);
-        }
-    });
-
     /**
      * @param {Doc[]} docs - docs to be searched through recursively
      * @param {number, Doc => void} func - function to be called on each doc
@@ -217,7 +291,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
             if (doc[DocData][DirectLinks].size === 0) {
                 this._linkedDocsOut.set(doc, new Set(this._results.keys()));
 
-                this._results.forEach((_, linkedDoc) => {
+                this._results.forEach((__, linkedDoc) => {
                     this._linkedDocsIn.get(linkedDoc)?.add(doc);
                 });
             } else {
@@ -251,7 +325,6 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
     pageRankIteration(): boolean {
         let converged = true;
         const pageRankFromAll = (1 - DAMPENING_FACTOR) / this._results.size;
-
         const nextPageRanks = new Map<Doc, number>();
 
         this._results.forEach((_, doc) => {
@@ -345,16 +418,6 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
         });
     });
 
-    /**
-     * @param {Doc} doc - doc to be selected
-     *
-     * This method selects a doc by either jumping to it (centering/zooming in on it)
-     * or opening it in a new tab.
-     */
-    selectElement = async (doc: Doc, finishFunc: () => void) => {
-        await DocumentManager.Instance.showDocument(doc, { willZoomCentered: true }, finishFunc);
-    };
-
     /**
      * This method returns a JSX list of the options in the select drop-down menu, which
      * is used to filter the types of documents that appear in the search results.
@@ -365,7 +428,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
 
         return selectValues.map(value => (
             <option key={value} value={value}>
-                {SearchBox.formatType(value, '')}
+                {ClientUtils.cleanDocumentTypeExt(value as DocumentType)}
             </option>
         ));
     }
@@ -374,56 +437,36 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
      * This method renders the search input box, select drop-down menu, and search results.
      */
     render() {
-        let validResults = 0;
-
         const isLinkSearch: boolean = this._props.linkSearch;
-
         const sortedResults = Array.from(this._results.entries()).sort((a, b) => (this._pageRanks.get(b[0]) ?? 0) - (this._pageRanks.get(a[0]) ?? 0)); // sorted by page rank
-
         const resultsJSX = [] as any[];
+        const linkFrom = this._props.linkFrom?.();
 
-        const fromDoc = this._props.linkFrom?.();
-
-        sortedResults.forEach(result => {
+        let validResults = 0;
+        sortedResults.forEach(([Document, matchedKeys]) => {
             let className = 'searchBox-results-scroll-view-result';
 
-            if (this._selectedResult === result[0]) {
+            if (this._selectedResult === Document) {
                 className += ' searchBox-results-scroll-view-result-selected';
             }
 
-            const formattedType = SearchBox.formatType(StrCast(result[0].type), StrCast(result[0].type_collection));
-            const { title } = result[0];
-
-            if (this._docTypeString === 'keys' || this._docTypeString === 'all' || this._docTypeString === result[0].type) {
+            if (this._docTypeString === 'keys' || this._docTypeString === 'all' || this._docTypeString === Document.type) {
                 validResults++;
                 resultsJSX.push(
-                    <Tooltip key={result[0][Id]} placement="right" title={<div className="dash-tooltip">{title as string}</div>}>
-                        <div
-                            onClick={
-                                isLinkSearch
-                                    ? () => this.makeLink(result[0])
-                                    : e => {
-                                          this.onResultClick(result[0]);
-                                          e.stopPropagation();
-                                      }
-                            }
-                            style={{
-                                fontWeight: LinkManager.Links(fromDoc).find(
-                                    link => Doc.AreProtosEqual(LinkManager.getOppositeAnchor(link, fromDoc!), result[0] as Doc) || Doc.AreProtosEqual(DocCast(LinkManager.getOppositeAnchor(link, fromDoc!)?.annotationOn), result[0] as Doc)
-                                )
-                                    ? 'bold'
-                                    : '',
-                            }}
-                            className={className}>
-                            <div className="searchBox-result-title">{title as string}</div>
-                            <div className="searchBox-result-type" style={{ color: SettingsManager.userVariantColor }}>
-                                {formattedType}
-                            </div>
-                            <div className="searchBox-result-keys" style={{ color: SettingsManager.userVariantColor }}>
-                                {result[1].join(', ')}
-                            </div>
-                        </div>
-                    </Tooltip>
+                    <SearchBoxItem
+                        key={Document[Id]}
+                        Document={Document}
+                        selectItem={action((doc: Doc) => {
+                            this._selectedResult = doc;
+                        })}
+                        isLinkSearch={isLinkSearch}
+                        searchString={this._searchString}
+                        matchedKeys={matchedKeys}
+                        linkFrom={linkFrom}
+                        className={className}
+                        linkCreateAnchor={this._props.linkCreateAnchor}
+                        linkCreated={this._props.linkCreated}
+                    />
                 );
             }
         });
diff --git a/src/client/views/selectedDoc/SelectedDocView.tsx b/src/client/views/selectedDoc/SelectedDocView.tsx
index c9c01189e..7ad7b2927 100644
--- a/src/client/views/selectedDoc/SelectedDocView.tsx
+++ b/src/client/views/selectedDoc/SelectedDocView.tsx
@@ -35,7 +35,7 @@ export class SelectedDocView extends React.Component<SelectedDocViewProps> {
                             val: StrCast(doc._id),
                             color: SettingsManager.userColor,
                             background: SettingsManager.userBackgroundColor,
-                            icon: <FontAwesomeIcon size={'1x'} icon={Doc.toIcon(doc)} />,
+                            icon: <FontAwesomeIcon size="1x" icon={Doc.toIcon(doc)} />,
                             onClick: () => DocumentManager.Instance.showDocument(doc, options, emptyFunction),
                         };
                     })}
diff --git a/src/client/views/selectedDoc/index.ts b/src/client/views/selectedDoc/index.ts
index 1f1db91f6..968e9c2d4 100644
--- a/src/client/views/selectedDoc/index.ts
+++ b/src/client/views/selectedDoc/index.ts
@@ -1 +1 @@
-export * from './SelectedDocView'
\ No newline at end of file
+export * from './SelectedDocView';
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 5028e1f8f..7b5e21ab7 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -1235,6 +1235,8 @@ export namespace Doc {
                 // if doc is a layout template then we don't want to highlight the proto since that will be the entire template, not just the specific layout field
                 highlightedDocs.add(doc[DocData]);
                 doc[DocData][Highlight] = true;
+                // want to highlight the targets of presentation docs explicitly since following a pres target does not highlight PDf <Annotations> which are not DocumentViews
+                if (doc.presentation_targetDoc) DocCast(doc.presentation_targetDoc)[Highlight] = true;
             }
         });
     }
@@ -1246,6 +1248,7 @@ export namespace Doc {
                 highlightedDocs.delete(doc[DocData]);
                 doc[Highlight] = doc[DocData][Highlight] = false;
                 doc[Animation] = undefined;
+                if (doc.presentation_targetDoc) DocCast(doc.presentation_targetDoc)[Highlight] = false;
             });
         });
     }
diff --git a/src/fields/List.ts b/src/fields/List.ts
index 852b05bc6..38c47d546 100644
--- a/src/fields/List.ts
+++ b/src/fields/List.ts
@@ -308,7 +308,7 @@ class ListImpl<T extends FieldType> extends ObjectField {
     // eslint-disable-next-line no-use-before-define
     private [SelfProxy]: List<FieldType>; // also used in utils.ts even though it won't be found using find all references
 
-    [ToScriptString]()     { return `new List(${this[ToJavascriptString]})`; } // prettier-ignore
+    [ToScriptString]()     { return `new List(${this[ToJavascriptString]()})`; } // prettier-ignore
     [ToJavascriptString]() { return `[${(this as any).map((field: any) => Field.toScriptString(field))}]`; } // prettier-ignore
     [ToString]()           { return `[${(this as any).map((field: any) => Field.toString(field))}]`; } // prettier-ignore
 }
-- 
cgit v1.2.3-70-g09d2

