From 48dbde117728e4b36bae11f0f16a796059ecf7ee Mon Sep 17 00:00:00 2001
From: dg314 <david_grossman@brown.edu>
Date: Sat, 17 Jul 2021 15:13:39 -0400
Subject: initial search panel

---
 src/client/views/MainView.tsx | 11 +++++------
 1 file changed, 5 insertions(+), 6 deletions(-)

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

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 4eeb1fc95..bc9c6f4d2 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -180,8 +180,8 @@ export class MainView extends React.Component {
             const targClass = targets[0].className.toString();
             if (SearchBox.Instance._searchbarOpen || SearchBox.Instance.open) {
                 const check = targets.some((thing) =>
-                    (thing.className === "collectionSchemaView-searchContainer" || (thing as any)?.dataset.icon === "filter" ||
-                        thing.className === "collectionSchema-header-menuOptions"));
+                (thing.className === "collectionSchemaView-searchContainer" || (thing as any)?.dataset.icon === "filter" ||
+                    thing.className === "collectionSchema-header-menuOptions"));
                 !check && SearchBox.Instance.resetSearch(true);
             }
             !targClass.includes("contextMenu") && ContextMenu.Instance.closeMenu();
@@ -388,7 +388,6 @@ export class MainView extends React.Component {
                     SettingsManager.Instance.open();
                     break;
                 case "Catalog":
-                    SearchBox.Instance._searchFullDB = "My Stuff";
                     SearchBox.Instance.enter(undefined);
                     break;
                 case "Help":
@@ -523,7 +522,7 @@ export class MainView extends React.Component {
         </svg>;
     }
 
-    @computed get search() {
+    /*@computed get search() {
         TraceMobx();
         return <div className="mainView-searchPanel">
             <SearchBox Document={CurrentUserUtils.MySearchPanelDoc}
@@ -555,7 +554,7 @@ export class MainView extends React.Component {
                 ContainingCollectionView={undefined}
                 ContainingCollectionDoc={undefined} />
         </div>;
-    }
+    }*/
 
     @computed get invisibleWebBox() { // see note under the makeLink method in HypothesisUtils.ts
         return !DocumentLinksButton.invisibleWebDoc ? null :
@@ -604,7 +603,7 @@ export class MainView extends React.Component {
             <GroupManager />
             <GoogleAuthenticationManager />
             <DocumentDecorations boundsLeft={this.leftOffset} boundsTop={this.topOffset} />
-            {this.search}
+            {/*this.search*/}
             <CollectionMenu />
             {LinkDescriptionPopup.descriptionPopup ? <LinkDescriptionPopup /> : null}
             {DocumentLinksButton.LinkEditorDocView ? <LinkMenu docView={DocumentLinksButton.LinkEditorDocView} changeFlyout={emptyFunction} /> : (null)}
-- 
cgit v1.2.3-70-g09d2


From fc1559fd74bde212f418b27806b68a76748153ad Mon Sep 17 00:00:00 2001
From: dg314 <david_grossman@brown.edu>
Date: Thu, 29 Jul 2021 15:12:03 -0400
Subject: search panel updates

---
 src/client/util/CurrentUserUtils.ts    |  11 +-
 src/client/views/MainView.tsx          |   6 +-
 src/client/views/search/SearchBox.scss | 173 ++++--------
 src/client/views/search/SearchBox.tsx  | 491 +++++++++++++++------------------
 4 files changed, 291 insertions(+), 390 deletions(-)

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

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 596ccd9a6..31384da3b 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -947,17 +947,12 @@ export class CurrentUserUtils {
 
     // Search sidebar is where searches within the document are performed
     static setupSearchSidebar(doc: Doc) {
-        if (doc.mySearchPanelDoc === undefined) {
-            doc.mySearchPanelDoc = new PrefetchProxy(Docs.Create.SearchDocument({
-                _width: 500, _height: 300, backgroundColor: "dimGray", ignoreClick: true, _searchDoc: true,
+        if (doc.mySearchPanel === undefined) {
+            doc.mySearchPanel = new PrefetchProxy(Docs.Create.SearchDocument({
+                backgroundColor: "dimGray", ignoreClick: true, _searchDoc: true,
                 childDropAction: "alias", _lockedPosition: true, _viewType: CollectionViewType.Schema, title: "sidebar search stack", system: true
             })) as any as Doc;
         }
-        if (doc.mySearchPanel === undefined) {
-            const searchPanelDoc = Cast(doc.mySearchPanelDoc, Doc, null);
-            //const newUpload = CurrentUserUtils.ficon({ onClick: ScriptField.MakeScript("importDocument()"), toolTip: "Import External document", _stayInCollection: true, _hideContextMenu: true, title: "Import", icon: "upload", system: true });
-            doc.mySearchPanel = new PrefetchProxy(Docs.Create.StackingDocument([searchPanelDoc], { title: "Search", _yMargin: 20, ignoreClick: true, _chromeHidden: true, _stayInCollection: true, _hideContextMenu: true, _lockedPosition: true, system: true }));
-        }
     }
 
     static setupClickEditorTemplates(doc: Doc) {
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index bc9c6f4d2..0d957518b 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -178,12 +178,12 @@ export class MainView extends React.Component {
         const targets = document.elementsFromPoint(e.x, e.y);
         if (targets.length) {
             const targClass = targets[0].className.toString();
-            if (SearchBox.Instance._searchbarOpen || SearchBox.Instance.open) {
+            /*if (SearchBox.Instance._resultsOpen) {
                 const check = targets.some((thing) =>
                 (thing.className === "collectionSchemaView-searchContainer" || (thing as any)?.dataset.icon === "filter" ||
                     thing.className === "collectionSchema-header-menuOptions"));
                 !check && SearchBox.Instance.resetSearch(true);
-            }
+            }*/
             !targClass.includes("contextMenu") && ContextMenu.Instance.closeMenu();
             !["timeline-menu-desc", "timeline-menu-item", "timeline-menu-input"].includes(targClass) && TimelineMenu.Instance.closeMenu();
         }
@@ -192,7 +192,7 @@ export class MainView extends React.Component {
     initEventListeners = () => {
         window.addEventListener("drop", e => e.preventDefault(), false);  // prevent default behavior of navigating to a new web page
         window.addEventListener("dragover", e => e.preventDefault(), false);
-        document.addEventListener("pointermove", action(e => SearchBox.Instance._undoBackground = UndoManager.batchCounter ? "#000000a8" : undefined));
+        //document.addEventListener("pointermove", action(e => SearchBox.Instance._undoBackground = UndoManager.batchCounter ? "#000000a8" : undefined));
         document.addEventListener("pointerdown", this.globalPointerDown);
         document.addEventListener("click", (e: MouseEvent) => {
             if (!e.cancelBubble) {
diff --git a/src/client/views/search/SearchBox.scss b/src/client/views/search/SearchBox.scss
index 4f5b7e41a..f8e994da7 100644
--- a/src/client/views/search/SearchBox.scss
+++ b/src/client/views/search/SearchBox.scss
@@ -2,141 +2,84 @@
 @import "./NaviconButton.scss";
 
 .searchBox-container {
-    display: flex;
-    flex-direction: column;
     width: 100%;
     height: 100%;
-    position: relative;
     font-size: 10px;
     line-height: 1;
-    overflow-y: auto;
-    overflow-x: visible;
-    background: lightgrey;
-    overflow: visible;
+    background: none;
     z-index: 1000;
+    padding: 0px;
+    cursor: default;
 
     .searchBox-bar {
-        height: $searchpanel-height;
+        width: 100%;
+        height: 35px;
         display: flex;
         justify-content: center;
         align-items: center;
-        background-color: black;
+        background-color: none;
+        padding: 5px;
 
-        .searchBox-lozenges {
-            position: absolute;
-            left: 15;
-            display: flex;
-
-            .searchBox-lozenge-user,
-            .searchBox-lozenge-dashboard,
-            .searchBox-lozenge {
-                height: 18px;
-                padding: 4px;
-                margin-right: 5px;
-                display: flex;
-                align-items: center;
-                border: grey 1px solid;
-                .searchBox-logoff,
-                .searchBox-dashboards {
-                    border-radius: 3px;
-                    background: olivedrab;
-                    color: white;
-                    display: none;
-                    margin-left: 5px;
-                    padding: 1px 2px 1px 2px;
-                    cursor: pointer;
-                }
-                .searchBox-logoff {
-                    background: red;
-                }
-
-                .searchBox-dashSelect{
-                    border: none;
-                    background-color: transparent;
+        .searchBox-type {
+            display: block;
+            width: 50px;
+            outline: none;
+            padding: 1px 5px 1px 5px;
+            color: black;
+            height: 25px;
+            border: 1px solid black;
+        }
 
-                    &:hover {
-                        cursor: pointer;
-                    }
-                }
-            }
-            .searchBox-lozenge-user:hover {
-                .searchBox-logoff {
-                    display:inline-block;
-                }
-            }
-            .searchBox-lozenge-dashboard:hover {
-                .searchBox-dashboards {
-                    display:inline-block;
-                }
-            }
+        .searchBox-input {
+            display: block;
+            width: calc(100% - 50px);
+            outline: none;
+            padding: 1px 5px 1px 5px;
+            color: black;
+            height: 25px;
+            border: 1px solid black;
         }
-        .searchBox-query {
-            position: relative;
+    }
+
+    .searchBox-results-container {
+        display: flex;
+        flex-direction: column;
+        width: 100%;
+        height: 100%;
+        justify-content: "center";
+    
+        .searchBox-results-count {
             display: flex;
-            width: 450;
+            color: gray;
+            margin-left: 5px;
         }
-        .searchBox-barChild {
+    
+        .searchBox-results-scroll-view {
+            margin-top: 10px;
+            display: inline-block;
+            width: 100%;
+            height: calc(100% - 55px);
+            overflow-y: scroll;
 
-            &.searchBox-collection {
-                flex: 0 1 auto;
-                margin-left: 2px;
-                margin-right: 2px
-            }
+            .searchBox-results-scroll-view-result {
+                display: inline-block;
+                vertical-align: middle;
+                width: 100%;
+                height: 40px;
+                cursor: pointer;
+                font-size: 15px;
+                padding: 10px;
 
-            &.searchBox-input {
-                margin:5px;
-                border-radius:20px;
-                border:black;    
-                display: block;
-                width: 130px;
-                -webkit-transition: width 0.4s;
-                transition: width 0.4s;
-                align-self: stretch;
-                outline:none;
-                &:focus {
-                    width: 500px;
-                    outline:none;
+                &.searchBox-results-scroll-view-result-selected {
+                    background: gray;
                 }
-            }
-            &.searchBox-filter {
-                align-self: stretch;
-                button{
-                    transform:none;
-                    &:hover {
-                        transform: none;
-                    }
-                }
-            }
 
-            &.searchBox-submit {
-                margin-left: 2px;
-                margin-right: 2px
-            }
-
-            &.searchBox-close {
-                color: $light-color;
-                max-height: $searchpanel-height;
+                .titletitle {
+                    display: relative;
+                    float: left;
+                    left: 50px;
+                }
             }
         }
-    }   
-}
-
-.searchBox-results {
-    display: flex;
-    flex-direction: column;
-    top: 300px;
-    display: flex;
-    flex-direction: column;
-    height: 100%;
-    overflow: visible;
-
-    .no-result {
-        width: 500px;
-        background: $light-color-secondary;
-        padding: 10px;
-        height: 50px;
-        text-transform: uppercase;
-        text-align: left;
-        font-weight: bold;
     }
 }
\ No newline at end of file
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index cc4590969..374a754bf 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -3,7 +3,7 @@ import { Tooltip } from '@material-ui/core';
 import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { Doc, DocListCast, Field, Opt, DocListCastAsync, DataSym, HeightSym } from '../../../fields/Doc';
+import { Doc, DocListCast, Field, Opt, DocListCastAsync, DataSym, HeightSym, FieldsSym } from '../../../fields/Doc';
 import { documentSchema } from "../../../fields/documentSchemas";
 import { Copy, Id, ToString } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
@@ -26,6 +26,9 @@ import "./SearchBox.scss";
 import { undoBatch } from "../../util/UndoManager";
 import { DocServer } from "../../DocServer";
 import { MainView } from "../MainView";
+import { DocumentManager } from "../../util/DocumentManager";
+import { CollectionSchemaBooleanCell } from "../collections/CollectionSchemaCells";
+import { transpileModule } from "typescript";
 
 export const searchSchema = createSchema({ Document: Doc });
 
@@ -38,33 +41,18 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
     public static Instance: SearchBox;
 
     private _allIcons: string[] = [DocumentType.INK, DocumentType.AUDIO, DocumentType.COL, DocumentType.IMG, DocumentType.LINK, DocumentType.PDF, DocumentType.RTF, DocumentType.VID, DocumentType.WEB];
-    private _numResultsPerPage = 500;
-    private _numTotalResults = -1;
-    private _endIndex = -1;
-    private _lockPromise?: Promise<void>;
     private _resultsSet = new Map<Doc, number>();
     private _inputRef = React.createRef<HTMLInputElement>();
-    private _maxSearchIndex: number = 0;
-    private _curRequest?: Promise<any> = undefined;
     private _disposers: { [name: string]: IReactionDisposer } = {};
     private _blockedTypes = [DocumentType.PRESELEMENT, DocumentType.KVP, DocumentType.FILTER, DocumentType.SEARCH, DocumentType.SEARCHITEM, DocumentType.FONTICON, DocumentType.BUTTON, DocumentType.SCRIPTING];
 
-    private docsforfilter: Doc[] | undefined = [];
-    private realTotalResults: number = 0;
-    private newsearchstring = "";
-
-    @observable _undoBackground: string | undefined = "";
+    @observable _searchString = "";
+    @observable _docTypeString = "all";
     @observable _icons: string[] = this._allIcons;
     @observable _results: [Doc, string[], string[]][] = [];
-    @observable _visibleElements: JSX.Element[] = [];
-    @observable _visibleDocuments: Doc[] = [];
+    @observable _selectedResult: Doc | undefined = undefined;
     @observable _deletedDocsStatus: boolean = false;
     @observable _onlyAliases: boolean = true;
-    @observable _searchbarOpen = false;
-    @observable _noResults = "";
-    @observable _pageStart = 0;
-    @observable open = false;
-    @observable children = 0;
     @computed get filter() { return this._results?.length && (this.currentSelectedCollection?.props.Document._searchFilterDocs || this.currentSelectedCollection?.props.Document._docFilters); }
 
     constructor(props: any) {
@@ -76,8 +64,6 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
         if (this._inputRef.current) {
             this._inputRef.current.focus();
         }
-        this._disposers.filters = reaction(() => this.props.Document._docFilters,
-            (filters: any) => this.setSearchFilter(this.currentSelectedCollection, !this.filter ? undefined : this.docsforfilter));
     });
 
     componentWillUnmount() {
@@ -86,32 +72,25 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
 
     @computed get currentSelectedCollection() { return CollectionDockingView.Instance; }
 
-    onChange = action((e: React.ChangeEvent<HTMLInputElement>) => {
-        this.newsearchstring = e.target.value;
-        if (e.target.value === "") {
-            console.log("Reset start");
-            this.docsforfilter = undefined;
-            this.setSearchFilter(this.currentSelectedCollection, undefined);
-            this.resetSearch(false);
-
-            this.open = false;
-            this._results = [];
-            this._resultsSet.clear();
-            this._visibleElements = [];
-            this._numTotalResults = -1;
-            this._endIndex = -1;
-            this._curRequest = undefined;
-            this._maxSearchIndex = 0;
-        }
+    onInputChange = action((e: React.ChangeEvent<HTMLInputElement>) => {
+        this._searchString = e.target.value;
+        this.submitSearch();
     });
 
-    enter = action((e: React.KeyboardEvent | undefined) => {
+    onSelectChange = action((e: React.ChangeEvent<HTMLSelectElement>) => {
+        this._docTypeString = e.target.value;
+        this.submitSearch();
+    });
+
+    /*enter = action((e: React.KeyboardEvent | undefined) => {
         if (!e || e.key === "Enter") {
-            this.layoutDoc._searchString = this.newsearchstring;
-            this._pageStart = 0;
-            this.open = true;
             this.submitSearch();
         }
+    });*/
+
+    onResultClick = action((doc: Doc) => {
+        this.selectElement(doc);
+        this._selectedResult = doc;
     });
 
     @action
@@ -126,21 +105,6 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
         return finalDocs;
     }
 
-    static async foreachRecursiveDocAsync(docs: Doc[], func: (doc: Doc) => void) {
-        let newarray: Doc[] = [];
-        while (docs.length > 0) {
-            newarray = [];
-            await Promise.all(docs.filter(d => d).map(async d => {
-                const fieldKey = Doc.LayoutFieldKey(d);
-                const annos = !Field.toString(Doc.LayoutField(d) as Field).includes("CollectionView");
-                const data = d[annos ? fieldKey + "-annotations" : fieldKey];
-                const docs = await DocListCastAsync(data);
-                docs && newarray.push(...docs);
-                func(d);
-            }));
-            docs = newarray;
-        }
-    }
     static foreachRecursiveDoc(docs: Doc[], func: (doc: Doc) => void) {
         let newarray: Doc[] = [];
         while (docs.length > 0) {
@@ -172,15 +136,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
             });
 
             this._results = found;
-            this.docsforfilter = this._results.map(r => r[0]);
-            this.setSearchFilter(selectedCollection, this.filter && found.length ? this.docsforfilter : undefined);
-            this._numTotalResults = found.length;
-            this.realTotalResults = found.length;
         }
-        else {
-            this._noResults = "No collection selected :(";
-        }
-
     }
 
     static documentKeys(doc: Doc) {
@@ -197,246 +153,253 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
 
     @action
     submitSearch = async () => {
-        this.resetSearch(false);
-
-        //this.props.Document._docFilters = new List();
-        this._noResults = "";
+        this.resetSearch();
 
         this.dataDoc[this.fieldKey] = new List<Doc>([]);
-        this.children = 0;
-        let query = StrCast(this.layoutDoc._searchString);
+        let query = StrCast(this._searchString);
         Doc.SetSearchQuery(query);
         this._results = [];
         this._resultsSet.clear();
-        this._visibleElements = [];
-        this._visibleDocuments = [];
 
         if (query) {
-            this._endIndex = 12;
-            this._maxSearchIndex = 0;
-            this._numTotalResults = -1;
             this.searchCollection(query);
-            runInAction(() => {
-                this.open = this._searchbarOpen = true;
-                this.resultsScrolled();
-            });
         }
     }
 
-    getAllResults = async (query: string) => {
-        return SearchUtil.Search(query, true, { fq: this.filterQuery, start: 0, rows: 10000000 });
-    }
+    resetSearch = action(() => {
+        this._results.forEach(result => {
+            Doc.UnBrushDoc(result[0]);
+            Doc.ClearSearchMatches();
+        });
+    });
 
-    private get filterQuery() {
-        const baseExpr = "NOT system_b:true";
-        const authorExpr = undefined;
-        const includeDeleted = this._deletedDocsStatus ? "" : " NOT deleted_b:true";
-        const typeExpr = this._onlyAliases ? "NOT {!join from=id to=proto_i}type_t:*" : `(type_t:* OR {!join from=id to=proto_i}type_t:*) ${this._blockedTypes.map(type => `NOT ({!join from=id to=proto_i}type_t:${type}) AND NOT type_t:${type}`).join(" AND ")}`;
-        // fq: type_t:collection OR {!join from=id to=proto_i}type_t:collection   q:text_t:hello
-        return [baseExpr, authorExpr, includeDeleted, typeExpr].filter(q => q).join(" AND ").replace(/AND $/, "");
+    selectElement = async (doc: Doc) => {
+        await DocumentManager.Instance.jumpToDocument(doc, true); // documents open in new tab instead of on right    
     }
 
-    @computed get primarySort() {
-        const suffixMap = (type: ColumnType) => {
-            switch (type) {
-                case ColumnType.Date: return "_d";
-                case ColumnType.String: return "_t";
-                case ColumnType.Boolean: return "_b";
-                case ColumnType.Number: return "_n";
+    render() {
+        const results = this._results.map(result => {
+            var className = "searchBox-results-scroll-view-result";
+
+            if (this._selectedResult == result[0]) {
+                className += " searchBox-results-scroll-view-result-selected"
             }
-        };
-        const headers = Cast(this.props.Document._schemaHeaders, listSpec(SchemaHeaderField), []);
-        return headers.reduce((p: Opt<string>, header: SchemaHeaderField) => p || (header.desc !== undefined && suffixMap(header.type) ? (header.heading + suffixMap(header.type) + (header.desc ? " desc" : " asc")) : undefined), undefined);
-    }
 
-    searchDatabase = async (query: string) => {
-        this._lockPromise && (await this._lockPromise);
-        this._lockPromise = new Promise(async res => {
-            while (this._results.length <= this._endIndex && (this._numTotalResults === -1 || this._maxSearchIndex < this._numTotalResults)) {
-                this._curRequest = SearchUtil.Search(query, true, { onlyAliases: true, allowAliases: true, /*sort: this.primarySort,*/ fq: this.filterQuery, start: 0, rows: this._numResultsPerPage, hl: "on", "hl.fl": "*", }).then(action(async (res: SearchUtil.DocSearchResult) => {
-                    // happens at the beginning
-                    this.realTotalResults = res.numFound <= 0 ? 0 : res.numFound;
-                    if (res.numFound !== this._numTotalResults && this._numTotalResults === -1) {
-                        this._numTotalResults = res.numFound;
-                    }
-                    const highlighting = res.highlighting || {};
-                    const highlightList = res.docs.map(doc => highlighting[doc[Id]]);
-                    const lines = new Map<string, string[]>();
-                    res.docs.map((doc, i) => lines.set(doc[Id], res.lines[i]));
-                    const docs = res.docs;
-                    const highlights: typeof res.highlighting = {};
-                    docs.forEach((doc, index) => highlights[doc[Id]] = highlightList[index]);
-                    const filteredDocs = this.filterDocsByType(docs);
-
-                    runInAction(() => filteredDocs.forEach((doc, i) => {
-                        const index = this._resultsSet.get(doc);
-                        const highlight = highlights[doc[Id]];
-                        const line = lines.get(doc[Id]) || [];
-                        const hlights = highlight ? Object.keys(highlight).map(key => key.substring(0, key.length - 2)).filter(k => k) : [];
-                        // if (this.findCommonElements(hlights)) {
-                        // }
-                        if (index === undefined) {
-                            this._resultsSet.set(doc, this._results.length);
-                            this._results.push([doc, hlights, line]);
-                        } else {
-                            this._results[index][1].push(...hlights);
-                            this._results[index][2].push(...line);
-                        }
-                    }));
-
-                    this._curRequest = undefined;
-                }));
-                this._maxSearchIndex += this._numResultsPerPage;
-
-                await this._curRequest;
+            if (this._docTypeString == "all" || this._docTypeString == result[0].type) {
+                return (<div key={result[0][Id]} onClick={() => this.onResultClick(result[0])} className={className}><div className="titletitle">{result[0].title}</div></div>)
             }
 
-            this.resultsScrolled();
+            return null;
+        })
 
-            const selectedCollection = this.currentSelectedCollection;//SelectionManager.SelectedDocuments()[0];
-            this.docsforfilter = this._results.map(r => r[0]);
-            this.setSearchFilter(selectedCollection, this.filter ? this.docsforfilter : undefined);
-            res();
-        });
-        return this._lockPromise;
+        results.filter(result => result)
+
+        return (
+            <div style={{ pointerEvents: "all" }} className="searchBox-container">
+                <div className="searchBox-bar">
+                    <select name="type" id="searchBox-type" className="searchBox-type" onChange={this.onSelectChange}>
+                        <option value="all">All</option>
+                        <option value="rtf">Text</option>
+                        <option value="img">Image</option>
+                        <option value="pdf">PDF</option>
+                    </select>
+                    <input defaultValue={""} autoComplete="off" onChange={this.onInputChange} type="text" placeholder="Search..." id="search-input" ref={this._inputRef} className="searchBox-input" />
+                </div >
+                <div className="searchBox-results-container">
+                    <div className="searchBox-results-count">
+                        {`${results.length}` + " result" + (results.length == 1 ? "" : "s")}
+                    </div>
+                    <div className="searchBox-results-scroll-view">
+                        {results}
+                    </div>
+                </div>
+            </div >
+        );
     }
+}
+
+
+
+/*
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { Tooltip } from '@material-ui/core';
+import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { Doc, DocListCast, Field, Opt, DocListCastAsync, DataSym, HeightSym, FieldsSym } from '../../../fields/Doc';
+import { documentSchema } from "../../../fields/documentSchemas";
+import { Copy, Id, ToString } from '../../../fields/FieldSymbols';
+import { List } from '../../../fields/List';
+import { createSchema, listSpec, makeInterface } from '../../../fields/Schema';
+import { SchemaHeaderField } from '../../../fields/SchemaHeaderField';
+import { Cast, NumCast, StrCast } from '../../../fields/Types';
+import { emptyFunction, returnFalse, returnZero, setupMoveUpEvents, Utils } from '../../../Utils';
+import { Docs } from '../../documents/Documents';
+import { DocumentType } from "../../documents/DocumentTypes";
+import { CurrentUserUtils } from "../../util/CurrentUserUtils";
+import { SetupDrag } from '../../util/DragManager';
+import { SearchUtil } from '../../util/SearchUtil';
+import { Transform } from '../../util/Transform';
+import { CollectionDockingView } from "../collections/CollectionDockingView";
+import { CollectionSchemaView, ColumnType } from "../collections/CollectionSchemaView";
+import { CollectionViewType } from '../collections/CollectionView';
+import { ViewBoxBaseComponent } from "../DocComponent";
+import { FieldView, FieldViewProps } from '../nodes/FieldView';
+import "./SearchBox.scss";
+import { undoBatch } from "../../util/UndoManager";
+import { DocServer } from "../../DocServer";
+import { MainView } from "../MainView";
+import { SelectionManager } from "../../util/SelectionManager";
+import { CollectionSchemaBooleanCell } from "../collections/CollectionSchemaCells";
+import { transpileModule } from "typescript";
+import { DocumentManager } from "../../util/DocumentManager";
+
+export const searchSchema = createSchema({ Document: Doc });
+
+type SearchBoxDocument = makeInterface<[typeof documentSchema, typeof searchSchema]>;
+const SearchBoxDocument = makeInterface(documentSchema, searchSchema);
+
+@observer
+export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDocument>(SearchBoxDocument) {
+    public static LayoutString(fieldKey: string) { return FieldView.LayoutString(SearchBox, fieldKey); }
+    public static Instance: SearchBox;
+
+    @observable _searchString = "";
+    @observable _docTypeString = "all";
+    @observable _results: [Doc, string[], string[]][] = [];
+    @observable _selectedResult: Doc | undefined = undefined;
+    @observable _deletedDocsStatus: boolean = false;
+    @observable _onlyAliases: boolean = true;
 
-    @action.bound
-    openSearch(e: React.SyntheticEvent) {
-        e.stopPropagation();
-        this._results.forEach(result => Doc.BrushDoc(result[0]));
+    constructor(props: any) {
+        super(props);
+        SearchBox.Instance = this;
     }
 
-    resetSearch = action((close: boolean) => {
-        this._results.forEach(result => {
-            Doc.UnBrushDoc(result[0]);
-            Doc.ClearSearchMatches();
-        });
-        close && (this.open = this._searchbarOpen = false);
+    onInputChange = action((e: React.ChangeEvent<HTMLInputElement>) => {
+        this._searchString = e.target.value;
+        this.submitSearch();
     });
 
-    @action.bound
-    closeResults() {
-        this._results = [];
-        this._resultsSet.clear();
-        this._visibleElements = [];
-        this._visibleDocuments = [];
-        this._numTotalResults = -1;
-        this._endIndex = -1;
-        this._curRequest = undefined;
-    }
+    onSelectChange = action((e: React.ChangeEvent<HTMLSelectElement>) => {
+        this._docTypeString = e.target.value;
+        this.submitSearch();
+    });
 
-    @action
-    resultsScrolled = (e?: React.UIEvent<HTMLDivElement>) => {
-        this._endIndex = 30;
-        const headers = new Set<string>(["title", "author", "text", "type", "data", "*lastModified", "context"]);
+    onResultClick = action((doc: Doc) => {
+        this.selectElement(doc);
+        this._selectedResult = doc;
+    });
 
-        if (this._numTotalResults <= this._maxSearchIndex) {
-            this._numTotalResults = this._results.length;
+    static foreachRecursiveDoc(docs: Doc[], func: (doc: Doc) => void) {
+        const blockedTypes = [DocumentType.PRESELEMENT, DocumentType.KVP, DocumentType.FILTER, DocumentType.SEARCH, DocumentType.SEARCHITEM, DocumentType.FONTICON, DocumentType.BUTTON, DocumentType.SCRIPTING];
+        let newarray: Doc[] = [];
+        while (docs.length > 0) {
+            newarray = [];
+            docs.filter(d => d).forEach(d => {
+                const dtype = StrCast(d.type, "string") as DocumentType;
+                if (dtype && !blockedTypes.includes(dtype)) {
+                    const fieldKey = Doc.LayoutFieldKey(d);
+                    const annos = !Field.toString(Doc.LayoutField(d) as Field).includes("CollectionView");
+                    const data = d[annos ? fieldKey + "-annotations" : fieldKey];
+                    data && newarray.push(...DocListCast(data));
+                    func(d);
+                }
+            });
+            docs = newarray;
         }
+    }
 
-        // only hit right at the beginning
-        // visibleElements is all of the elements (even the ones you can't see)
-        if (this._visibleElements.length !== this._numTotalResults) {
-            // undefined until a searchitem is put in there
-            this._visibleElements = Array<JSX.Element>(this._numTotalResults === -1 ? 0 : this._numTotalResults);
-            this._visibleDocuments = Array<Doc>(this._numTotalResults === -1 ? 0 : this._numTotalResults);
-        }
-        let max = this._numResultsPerPage;
-        max > this._results.length ? max = this._results.length : console.log("");
-        for (let i = this._pageStart; i < max; i++) {
-            //if the index is out of the window then put a placeholder in
-            //should ones that have already been found get set to placeholders?
-
-            let result: [Doc, string[], string[]] | undefined = undefined;
-
-            result = this._results[i];
-            if (result) {
-                const highlights = Array.from([...Array.from(new Set(result[1]).values())]);
-                const lines = new List<string>(result[2]);
-                highlights.forEach((item) => headers.add(item));
-                Doc.SetSearchMatch(result[0], { searchMatch: 1 });
-                if (i < this._visibleDocuments.length) {
-                    this._visibleDocuments[i] = result[0];
-                    Doc.BrushDoc(result[0]);
-                    Doc.AddDocToList(this.dataDoc, this.props.fieldKey, result[0]);
-                    this.children++;
+    @action
+    searchCollection(query: string) {
+        const selectedCollection = CollectionDockingView.Instance;
+        query = query.toLowerCase();
+
+        if (selectedCollection !== undefined) {
+            console.log("hello111")
+            // this._currentSelectedCollection = selectedCollection;
+            const docs = DocListCast(selectedCollection.dataDoc[Doc.LayoutFieldKey(selectedCollection.dataDoc)]);
+            const found: [Doc, string[], string[]][] = [];
+            SearchBox.foreachRecursiveDoc(docs, (doc: Doc) => {
+                console.log("HELLO")
+                if (this._docTypeString == "all" || this._docTypeString == doc.type) {
+                    const hlights = new Set<string>();
+                    SearchBox.documentKeys(doc).forEach(key => Field.toString(doc[key] as Field).toLowerCase().includes(query) && hlights.add(key));
+                    Array.from(hlights.keys()).length > 0 && found.push([doc, Array.from(hlights.keys()), []]);
                 }
-            }
-        }
-        if (this.props.Document._schemaHeaders === undefined) {
-            this.props.Document._schemaHeaders = new List<SchemaHeaderField>([new SchemaHeaderField("title", "#f1efeb")]);
-        }
-        if (this._maxSearchIndex >= this._numTotalResults) {
-            this._visibleElements.length = this._results.length;
-            this._visibleDocuments.length = this._results.length;
+            });
+
+            this._results = found;
+            //this.setSearchFilter(selectedCollection, this.filter && found.length ? this._docsforfilter : undefined);
         }
     }
 
-    getTransform = () => this.props.ScreenToLocalTransform().translate(-5, -65);// listBox padding-left and pres-box-cont minHeight
-    panelHeight = () => this.props.PanelHeight();
-    selectElement = (doc: Doc) => { /* this.gotoDocument(this.childDocs.indexOf(doc), NumCasst(this.layoutDoc._itemIndex)); */ };
-    returnHeight = () => NumCast(this.layoutDoc._height);
-    returnLength = () => Math.min(window.innerWidth, 51 + 205 * Cast(this.props.Document._schemaHeaders, listSpec(SchemaHeaderField), []).length);
-
-    setSearchFilter = action((collectionView: { props: { Document: Doc } }, docsForFilter: Doc[] | undefined) => {
-        if (collectionView) {
-            const docFilters = Cast(this.props.Document._docFilters, listSpec("string"), null);
-            collectionView.props.Document._searchFilterDocs = docsForFilter?.length ? new List<Doc>(docsForFilter) : undefined;
-            collectionView.props.Document._docFilters = docsForFilter?.length && docFilters?.length ? new List<string>(docFilters) : undefined;
+    static documentKeys(doc: Doc) {
+        const keys: { [key: string]: boolean } = {};
+        // bcz: ugh.  this is untracked since otherwise a large collection of documents will blast the server for all their fields.
+        //  then as each document's fields come back, we update the documents _proxies.  Each time we do this, the whole schema will be
+        //  invalidated and re-rendered.   This workaround will inquire all of the document fields before the options button is clicked.
+        //  then by the time the options button is clicked, all of the fields should be in place.  If a new field is added while this menu
+        //  is displayed (unlikely) it won't show up until something else changes.
+        //TODO Types
+        Doc.GetAllPrototypes(doc).map(proto => Object.keys(proto).forEach(key => keys[key] = false));
+        return Array.from(Object.keys(keys));
+    }
+
+    @action
+    submitSearch = async () => {
+        Doc.ClearSearchMatches();
+        this._results = [];
+
+        this.dataDoc[this.fieldKey] = new List<Doc>([]);
+        let query = StrCast(this._searchString);
+        Doc.SetSearchQuery(query);
+        this._results = [];
+
+        if (query) {
+            this.searchCollection(query);
         }
-    });
+    }
+
+    selectElement = async (doc: Doc) => {
+        await DocumentManager.Instance.jumpToDocument(doc, true); // documents open in new tab instead of on right
+    }
 
     render() {
-        const myDashboards = DocListCast(CurrentUserUtils.MyDashboards.data);
+        const results = this._results.map(result => {
+            var className = "searchBox-results-scroll-view-result";
+
+            if (this._selectedResult == result[0]) {
+                className += " searchBox-results-scroll-view-result-selected"
+            }
+
+            return (<div key={result[0][Id]} onClick={() => this.onResultClick(result[0])} className={className}><div className="titletitle">{result[0].title}</div></div>)
+        })
 
         return (
             <div style={{ pointerEvents: "all" }} className="searchBox-container">
-                <div className="searchBox-bar" style={{ background: SearchBox.Instance._undoBackground }}>
-                    <div className="searchBox-query" >
-                        <input defaultValue={""} autoComplete="off" onChange={this.onChange} type="text" placeholder="Search..." id="search-input" ref={this._inputRef}
-                            className="searchBox-barChild searchBox-input" onKeyPress={this.enter}
-                            style={{ padding: 1, paddingLeft: 20, paddingRight: 60, color: "black", height: 20, width: 250 }} />
-                        <div style={{ display: "flex", alignItems: "center" }}>
-                            <div style={{ position: "absolute", left: 10 }}>
-                                <FontAwesomeIcon icon={"search"} size="lg"
-                                    style={{ color: "black", padding: 1, position: "relative" }} />
-                            </div>
-                            <div style={{ position: "absolute", left: Doc.UserDoc().noviceMode ? 220 : 200, width: 30, zIndex: 9000, color: "grey", background: "white", }}>
-                                {`${this._results.length}` + " of " + `${this.realTotalResults}`}
-                            </div>
-                        </div>
-                    </div >
+                <div className="searchBox-bar">
+                    <select name="type" id="searchBox-type" className="searchBox-type" onChange={this.onSelectChange}>
+                        <option value="all">All</option>
+                        <option value="rtf">Text</option>
+                        <option value="img">Img</option>
+                        <option value="pdf">PDF</option>
+                        <option value="video">Vid</option>
+                        <option value="audio">Aud</option>
+                        <option value="inks">Ink</option>
+                        <option value="col">Col</option>
+                    </select>
+                    <input defaultValue={""} autoComplete="off" onChange={this.onInputChange} type="text" placeholder="Search..." id="search-input" className="searchBox-input" />
                 </div >
-                {!this._searchbarOpen ? (null) :
-                    <div style={{ zIndex: 20000, color: "black" }} ref={(r) => r?.focus()}>
-                        <div style={{ display: "flex", justifyContent: "center", }}>
-                            <div style={{ display: this.open ? "flex" : "none", overflow: "auto", position: "absolute" }}>
-                                <CollectionSchemaView {...this.props}
-                                    CollectionView={undefined}
-                                    addDocument={returnFalse}
-                                    Document={this.props.Document}
-                                    moveDocument={returnFalse}
-                                    removeDocument={returnFalse}
-                                    PanelHeight={this.open ? this.returnHeight : returnZero}
-                                    PanelWidth={this.open ? this.returnLength : returnZero}
-                                    scrollOverflow={length > window.innerWidth || this.children > 6 ? true : false}
-                                    focus={this.selectElement}
-                                    ScreenToLocalTransform={Transform.Identity}
-                                />
-                                <div style={{ position: "absolute", right: 5, bottom: 7, width: 15, height: 15, }}
-                                    onPointerDown={e => setupMoveUpEvents(this, e, (e: PointerEvent, down: number[], delta: number[]) => {
-                                        this.props.Document._height = NumCast(this.props.Document._height) + delta[1];
-                                        return false;
-                                    }, returnFalse, emptyFunction)}
-                                >
-                                    <FontAwesomeIcon icon="grip-lines" size="lg" />
-                                </div>
-                            </div>
-                        </div>
+                <div className="searchBox-results-container">
+                    <div className="searchBox-results-count">
+                        {`${this._results.length}` + " result" + (this._results.length == 1 ? "" : "s")}
                     </div>
-                }
+                    <div className="searchBox-results-scroll-view">
+                        {results}
+                    </div>
+                </div>
             </div >
         );
     }
-}
\ No newline at end of file
+}*/
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From 2ec093982c06965086326df73d365e1b54f6a2a2 Mon Sep 17 00:00:00 2001
From: dg314 <david_grossman@brown.edu>
Date: Wed, 4 Aug 2021 03:19:16 -0400
Subject: search panel updates

---
 src/client/util/CurrentUserUtils.ts    | 27 ++++++-----
 src/client/views/GlobalKeyHandler.ts   |  7 ++-
 src/client/views/MainView.tsx          | 37 --------------
 src/client/views/search/SearchBox.scss | 19 ++++++--
 src/client/views/search/SearchBox.tsx  | 89 +++++++++++++++++-----------------
 5 files changed, 81 insertions(+), 98 deletions(-)

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

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 31384da3b..734b96b1e 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -46,6 +46,7 @@ export class CurrentUserUtils {
     //TODO tfs: these should be temporary...
     private static mainDocId: string | undefined;
 
+    public static searchBtn: Doc;
     public static get id() { return this.curr_id; }
     public static get MainDocId() { return this.mainDocId; }
     public static set MainDocId(id: string | undefined) { this.mainDocId = id; }
@@ -533,15 +534,6 @@ export class CurrentUserUtils {
         ];
     }
 
-    /*static setupSearchPanel(doc: Doc) {
-        if (doc.mySearchPanelDoc === undefined) {
-            doc.mySearchPanelDoc = new PrefetchProxy(Docs.Create.SearchDocument({
-                _width: 500, _height: 300, backgroundColor: "dimGray", ignoreClick: true, _searchDoc: true,
-                childDropAction: "alias", _lockedPosition: true, _viewType: CollectionViewType.Schema, title: "sidebar search stack", system: true
-            })) as any as Doc;
-        }
-    }*/
-
     static async setupMenuPanel(doc: Doc, sharingDocumentId: string, linkDatabaseId: string) {
         if (doc.menuStack === undefined) {
             await this.setupSharingSidebar(doc, sharingDocumentId, linkDatabaseId);  // sets up the right sidebar collection for mobile upload documents and sharing
@@ -562,10 +554,23 @@ export class CurrentUserUtils {
                     _height: 60,
                     watchedDocuments,
                     onClick: ScriptField.MakeScript(click, { scriptContext: "any" })
-                }));
+                })
+            );
+
+            menuBtns.forEach(menuBtn => {
+                if (menuBtn.title == "Search") {
+                    this.searchBtn = menuBtn;
+                }
+            });
             // hack -- last button is assumed to be the userDoc
             menuBtns[menuBtns.length - 1].hidden = ComputedField.MakeFunction("IsNoviceMode()");
 
+            menuBtns.forEach(menuBtn => {
+                if (menuBtn.title == "Search") {
+                    doc.searchBtn = menuBtn
+                }
+            })
+
             doc.menuStack = new PrefetchProxy(Docs.Create.StackingDocument(menuBtns, {
                 title: "menuItemPanel",
                 childDropAction: "alias",
@@ -950,7 +955,7 @@ export class CurrentUserUtils {
         if (doc.mySearchPanel === undefined) {
             doc.mySearchPanel = new PrefetchProxy(Docs.Create.SearchDocument({
                 backgroundColor: "dimGray", ignoreClick: true, _searchDoc: true,
-                childDropAction: "alias", _lockedPosition: true, _viewType: CollectionViewType.Schema, title: "sidebar search stack", system: true
+                childDropAction: "alias", _lockedPosition: true, _viewType: CollectionViewType.Schema, title: "Search Sidebar", system: true
             })) as any as Doc;
         }
     }
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index d92cba3eb..2a3cb36c7 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -27,7 +27,6 @@ import { LightboxView } from "./LightboxView";
 import { MainView } from "./MainView";
 import { DocumentLinksButton } from "./nodes/DocumentLinksButton";
 import { AnchorMenu } from "./pdf/AnchorMenu";
-import { SearchBox } from "./search/SearchBox";
 import { CurrentUserUtils } from "../util/CurrentUserUtils";
 import { SettingsManager } from "../util/SettingsManager";
 
@@ -225,7 +224,11 @@ export class KeyManager {
                 PromiseValue(Cast(Doc.UserDoc()["tabs-button-tools"], Doc)).then(pv => pv && (pv.onClick as ScriptField).script.run({ this: pv }));
                 break;
             case "f":
-                SearchBox.Instance.enter(undefined);
+                const searchBtn = Doc.UserDoc().searchBtn as Doc;
+
+                if (searchBtn) {
+                    MainView.Instance.selectMenu(searchBtn);
+                }
                 break;
             case "o":
                 const target = SelectionManager.Views()[0];
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 0d957518b..35222e91f 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -387,9 +387,6 @@ export class MainView extends React.Component {
                 case "Settings":
                     SettingsManager.Instance.open();
                     break;
-                case "Catalog":
-                    SearchBox.Instance.enter(undefined);
-                    break;
                 case "Help":
                     break;
                 default:
@@ -522,40 +519,6 @@ export class MainView extends React.Component {
         </svg>;
     }
 
-    /*@computed get search() {
-        TraceMobx();
-        return <div className="mainView-searchPanel">
-            <SearchBox Document={CurrentUserUtils.MySearchPanelDoc}
-                DataDoc={CurrentUserUtils.MySearchPanelDoc}
-                fieldKey="data"
-                dropAction="move"
-                isSelected={returnTrue}
-                isContentActive={returnTrue}
-                select={returnTrue}
-                setHeight={returnFalse}
-                addDocument={undefined}
-                addDocTab={this.addDocTabFunc}
-                pinToPres={emptyFunction}
-                rootSelected={returnTrue}
-                styleProvider={DefaultStyleProvider}
-                layerProvider={undefined}
-                removeDocument={undefined}
-                ScreenToLocalTransform={Transform.Identity}
-                PanelWidth={this.getPWidth}
-                PanelHeight={this.getPHeight}
-                renderDepth={0}
-                focus={DocUtils.DefaultFocus}
-                docViewPath={returnEmptyDoclist}
-                whenChildContentsActiveChanged={emptyFunction}
-                bringToFront={emptyFunction}
-                docFilters={returnEmptyFilter}
-                docRangeFilters={returnEmptyFilter}
-                searchFilterDocs={returnEmptyDoclist}
-                ContainingCollectionView={undefined}
-                ContainingCollectionDoc={undefined} />
-        </div>;
-    }*/
-
     @computed get invisibleWebBox() { // see note under the makeLink method in HypothesisUtils.ts
         return !DocumentLinksButton.invisibleWebDoc ? null :
             <div className="mainView-invisibleWebRef" ref={DocumentLinksButton.invisibleWebRef}>
diff --git a/src/client/views/search/SearchBox.scss b/src/client/views/search/SearchBox.scss
index f8e994da7..e4d1ac6a3 100644
--- a/src/client/views/search/SearchBox.scss
+++ b/src/client/views/search/SearchBox.scss
@@ -22,17 +22,18 @@
 
         .searchBox-type {
             display: block;
-            width: 50px;
+            width: 55px;
             outline: none;
             padding: 1px 5px 1px 5px;
             color: black;
             height: 25px;
             border: 1px solid black;
+            border-right: 0px;
         }
 
         .searchBox-input {
             display: block;
-            width: calc(100% - 50px);
+            width: calc(100% - 55px);
             outline: none;
             padding: 1px 5px 1px 5px;
             color: black;
@@ -74,10 +75,20 @@
                     background: gray;
                 }
 
-                .titletitle {
+                .searchBox-result-title {
                     display: relative;
                     float: left;
-                    left: 50px;
+                    width: calc(100% - 60px);
+                    text-align: left;
+                }
+
+                .searchBox-result-type {
+                    font-size: 12px;
+                    display: relative;
+                    float: right;
+                    width: 60px;
+                    text-align: right;
+                    color: #333;
                 }
             }
         }
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index 374a754bf..3cbb4b3b1 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -35,29 +35,30 @@ export const searchSchema = createSchema({ Document: Doc });
 type SearchBoxDocument = makeInterface<[typeof documentSchema, typeof searchSchema]>;
 const SearchBoxDocument = makeInterface(documentSchema, searchSchema);
 
+const selectValues = ["all", "rtf", "image", "pdf", "web", "video", "audio", "collection"]
+
 @observer
 export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDocument>(SearchBoxDocument) {
     public static LayoutString(fieldKey: string) { return FieldView.LayoutString(SearchBox, fieldKey); }
     public static Instance: SearchBox;
 
-    private _allIcons: string[] = [DocumentType.INK, DocumentType.AUDIO, DocumentType.COL, DocumentType.IMG, DocumentType.LINK, DocumentType.PDF, DocumentType.RTF, DocumentType.VID, DocumentType.WEB];
     private _resultsSet = new Map<Doc, number>();
     private _inputRef = React.createRef<HTMLInputElement>();
-    private _disposers: { [name: string]: IReactionDisposer } = {};
-    private _blockedTypes = [DocumentType.PRESELEMENT, DocumentType.KVP, DocumentType.FILTER, DocumentType.SEARCH, DocumentType.SEARCHITEM, DocumentType.FONTICON, DocumentType.BUTTON, DocumentType.SCRIPTING];
 
     @observable _searchString = "";
     @observable _docTypeString = "all";
-    @observable _icons: string[] = this._allIcons;
     @observable _results: [Doc, string[], string[]][] = [];
     @observable _selectedResult: Doc | undefined = undefined;
     @observable _deletedDocsStatus: boolean = false;
     @observable _onlyAliases: boolean = true;
-    @computed get filter() { return this._results?.length && (this.currentSelectedCollection?.props.Document._searchFilterDocs || this.currentSelectedCollection?.props.Document._docFilters); }
 
     constructor(props: any) {
         super(props);
         SearchBox.Instance = this;
+        this._searchString = "reset_search";
+        this.submitSearch();
+        this._searchString = "";
+        this.submitSearch();
     }
 
     componentDidMount = action(() => {
@@ -67,11 +68,9 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
     });
 
     componentWillUnmount() {
-        Object.values(this._disposers).forEach(disposer => disposer?.());
+        this.resetSearch;
     }
 
-    @computed get currentSelectedCollection() { return CollectionDockingView.Instance; }
-
     onInputChange = action((e: React.ChangeEvent<HTMLInputElement>) => {
         this._searchString = e.target.value;
         this.submitSearch();
@@ -79,32 +78,13 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
 
     onSelectChange = action((e: React.ChangeEvent<HTMLSelectElement>) => {
         this._docTypeString = e.target.value;
-        this.submitSearch();
     });
 
-    /*enter = action((e: React.KeyboardEvent | undefined) => {
-        if (!e || e.key === "Enter") {
-            this.submitSearch();
-        }
-    });*/
-
     onResultClick = action((doc: Doc) => {
         this.selectElement(doc);
         this._selectedResult = doc;
     });
 
-    @action
-    filterDocsByType(docs: Doc[]) {
-        const finalDocs: Doc[] = [];
-        docs.forEach(doc => {
-            const layoutresult = StrCast(doc.type, "string") as DocumentType;
-            if (layoutresult && !this._blockedTypes.includes(layoutresult) && this._icons.includes(layoutresult)) {
-                finalDocs.push(doc);
-            }
-        });
-        return finalDocs;
-    }
-
     static foreachRecursiveDoc(docs: Doc[], func: (doc: Doc) => void) {
         let newarray: Doc[] = [];
         while (docs.length > 0) {
@@ -120,23 +100,39 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
         }
     }
 
+    static formatType(type: String): String {
+        if (type == "pdf") {
+            return "PDF";
+        }
+        else if (type == "image") {
+            return "Img";
+        }
+
+        return type.charAt(0).toUpperCase() + type.substring(1, 3);
+    }
+
     @action
     searchCollection(query: string) {
-        const selectedCollection = this.currentSelectedCollection;//SelectionManager.SelectedDocuments()[0];
+        const selectedCollection = CollectionDockingView.Instance;
+        const blockedTypes = [DocumentType.PRESELEMENT, DocumentType.KVP, DocumentType.FILTER, DocumentType.SEARCH, DocumentType.SEARCHITEM, DocumentType.FONTICON, DocumentType.BUTTON, DocumentType.SCRIPTING];
         query = query.toLowerCase();
 
+        this._results = []
+
         if (selectedCollection !== undefined) {
-            // this._currentSelectedCollection = selectedCollection;
             const docs = DocListCast(selectedCollection.dataDoc[Doc.LayoutFieldKey(selectedCollection.dataDoc)]);
-            const found: [Doc, string[], string[]][] = [];
             SearchBox.foreachRecursiveDoc(docs, (doc: Doc) => {
-                const hlights = new Set<string>();
-                SearchBox.documentKeys(doc).forEach(key => Field.toString(doc[key] as Field).toLowerCase().includes(query) && hlights.add(key));
-                Array.from(hlights.keys()).length > 0 && found.push([doc, Array.from(hlights.keys()), []]);
+                const dtype = StrCast(doc.type, "string") as DocumentType;
+                if (dtype && !blockedTypes.includes(dtype)) {
+                    const hlights = new Set<string>();
+                    SearchBox.documentKeys(doc).forEach(key => Field.toString(doc[key] as Field).toLowerCase().includes(query) && hlights.add(key));
+                    Array.from(hlights.keys()).length > 0 && this._results.push([doc, Array.from(hlights.keys()), []]);
+                }
             });
-
-            this._results = found;
         }
+
+        this._results = Array.from(new Set(this._results))
+        this._selectedResult = undefined
     }
 
     static documentKeys(doc: Doc) {
@@ -155,7 +151,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
     submitSearch = async () => {
         this.resetSearch();
 
-        this.dataDoc[this.fieldKey] = new List<Doc>([]);
+        //this.dataDoc[this.fieldKey] = new List<Doc>([]);
         let query = StrCast(this._searchString);
         Doc.SetSearchQuery(query);
         this._results = [];
@@ -169,6 +165,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
     resetSearch = action(() => {
         this._results.forEach(result => {
             Doc.UnBrushDoc(result[0]);
+            Doc.UnHighlightDoc(result[0]);
             Doc.ClearSearchMatches();
         });
     });
@@ -178,6 +175,8 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
     }
 
     render() {
+        var validResults = 0;
+
         const results = this._results.map(result => {
             var className = "searchBox-results-scroll-view-result";
 
@@ -186,28 +185,30 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
             }
 
             if (this._docTypeString == "all" || this._docTypeString == result[0].type) {
-                return (<div key={result[0][Id]} onClick={() => this.onResultClick(result[0])} className={className}><div className="titletitle">{result[0].title}</div></div>)
+                validResults++;
+                return (<div key={result[0][Id]} onClick={() => this.onResultClick(result[0])} className={className}><div className="searchBox-result-title">{result[0].title}</div><div className="searchBox-result-type">{SearchBox.formatType(StrCast(result[0].type))}</div></div>)
             }
 
             return null;
         })
 
-        results.filter(result => result)
+        results.filter(result => result);
+
+        const selectOptions = selectValues.map(value => {
+            return <option key={value} value={value}>{SearchBox.formatType(value)}</option>
+        })
 
         return (
             <div style={{ pointerEvents: "all" }} className="searchBox-container">
                 <div className="searchBox-bar">
                     <select name="type" id="searchBox-type" className="searchBox-type" onChange={this.onSelectChange}>
-                        <option value="all">All</option>
-                        <option value="rtf">Text</option>
-                        <option value="img">Image</option>
-                        <option value="pdf">PDF</option>
+                        {selectOptions}
                     </select>
-                    <input defaultValue={""} autoComplete="off" onChange={this.onInputChange} type="text" placeholder="Search..." id="search-input" ref={this._inputRef} className="searchBox-input" />
+                    <input defaultValue={""} autoComplete="off" onChange={this.onInputChange} type="text" placeholder="Search..." id="search-input" className="searchBox-input" ref={this._inputRef} />
                 </div >
                 <div className="searchBox-results-container">
                     <div className="searchBox-results-count">
-                        {`${results.length}` + " result" + (results.length == 1 ? "" : "s")}
+                        {`${validResults}` + " result" + (validResults == 1 ? "" : "s")}
                     </div>
                     <div className="searchBox-results-scroll-view">
                         {results}
-- 
cgit v1.2.3-70-g09d2


From d511c116330c3a125e6557aa5be70392aca4914b Mon Sep 17 00:00:00 2001
From: dg314 <david_grossman@brown.edu>
Date: Thu, 5 Aug 2021 16:38:00 -0400
Subject: comments

---
 src/client/views/MainView.tsx                      |   6 -
 .../views/collections/CollectionSchemaHeaders.tsx  |   2 +-
 src/client/views/search/SearchBox.scss             |  22 +-
 src/client/views/search/SearchBox.tsx              | 382 +++++++++------------
 4 files changed, 177 insertions(+), 235 deletions(-)

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

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 35222e91f..97c88930e 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -178,12 +178,6 @@ export class MainView extends React.Component {
         const targets = document.elementsFromPoint(e.x, e.y);
         if (targets.length) {
             const targClass = targets[0].className.toString();
-            /*if (SearchBox.Instance._resultsOpen) {
-                const check = targets.some((thing) =>
-                (thing.className === "collectionSchemaView-searchContainer" || (thing as any)?.dataset.icon === "filter" ||
-                    thing.className === "collectionSchema-header-menuOptions"));
-                !check && SearchBox.Instance.resetSearch(true);
-            }*/
             !targClass.includes("contextMenu") && ContextMenu.Instance.closeMenu();
             !["timeline-menu-desc", "timeline-menu-item", "timeline-menu-input"].includes(targClass) && TimelineMenu.Instance.closeMenu();
         }
diff --git a/src/client/views/collections/CollectionSchemaHeaders.tsx b/src/client/views/collections/CollectionSchemaHeaders.tsx
index 3b52e6408..c98dcd1d0 100644
--- a/src/client/views/collections/CollectionSchemaHeaders.tsx
+++ b/src/client/views/collections/CollectionSchemaHeaders.tsx
@@ -424,7 +424,7 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> {
                         e.target.checked === true ? Doc.setDocFilter(this.props.Document, this._key, key, "check") : Doc.setDocFilter(this.props.Document, this._key, key, "remove");
                         e.target.checked === true ? this.closeResultsVisibility = "contents" : console.log("");
                         e.target.checked === true ? this.props.col.setColor("green") : this.updateFilter();
-                        e.target.checked === true && SearchBox.Instance.filter === true ? Doc.setDocFilter(docs[0], this._key, key, "check") : Doc.setDocFilter(docs[0], this._key, key, "remove");
+                        e.target.checked === true ? Doc.setDocFilter(docs[0], this._key, key, "check") : Doc.setDocFilter(docs[0], this._key, key, "remove");
                     }}
                     checked={bool}
                 />
diff --git a/src/client/views/search/SearchBox.scss b/src/client/views/search/SearchBox.scss
index e4d1ac6a3..e7526dac8 100644
--- a/src/client/views/search/SearchBox.scss
+++ b/src/client/views/search/SearchBox.scss
@@ -66,13 +66,13 @@
                 display: inline-block;
                 vertical-align: middle;
                 width: 100%;
-                height: 40px;
+                height: 50px;
                 cursor: pointer;
                 font-size: 15px;
-                padding: 10px;
+                padding: 11px;
 
                 &.searchBox-results-scroll-view-result-selected {
-                    background: gray;
+                    background: #999;
                 }
 
                 .searchBox-result-title {
@@ -84,11 +84,25 @@
 
                 .searchBox-result-type {
                     font-size: 12px;
+                    margin-top: 6px;
                     display: relative;
                     float: right;
                     width: 60px;
                     text-align: right;
-                    color: #333;
+                    color: #222;
+                }
+
+                .searchBox-result-keys {
+                    font-size: 10px;
+                    margin-top: 1px;
+                    display: relative;
+                    float: left;
+                    width: 100%;
+                    text-align: left;
+                    color: #555;
+                    white-space: nowrap;
+                    overflow: hidden;
+                    text-overflow: ellipsis;
                 }
             }
         }
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index 3cbb4b3b1..8ef09aa56 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -27,66 +27,105 @@ import { undoBatch } from "../../util/UndoManager";
 import { DocServer } from "../../DocServer";
 import { MainView } from "../MainView";
 import { DocumentManager } from "../../util/DocumentManager";
-import { CollectionSchemaBooleanCell } from "../collections/CollectionSchemaCells";
-import { transpileModule } from "typescript";
 
 export const searchSchema = createSchema({ Document: Doc });
 
 type SearchBoxDocument = makeInterface<[typeof documentSchema, typeof searchSchema]>;
 const SearchBoxDocument = makeInterface(documentSchema, searchSchema);
 
-const selectValues = ["all", "rtf", "image", "pdf", "web", "video", "audio", "collection"]
-
+/**
+ * This is the SearchBox component. It represents the search box input and results in
+ * the search panel on the left side of the screen.
+ */
 @observer
 export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDocument>(SearchBoxDocument) {
     public static LayoutString(fieldKey: string) { return FieldView.LayoutString(SearchBox, fieldKey); }
     public static Instance: SearchBox;
 
-    private _resultsSet = new Map<Doc, number>();
     private _inputRef = React.createRef<HTMLInputElement>();
+    private _selectedCollection = CollectionDockingView.Instance;
 
     @observable _searchString = "";
     @observable _docTypeString = "all";
-    @observable _results: [Doc, string[], string[]][] = [];
+    @observable _results: [Doc, string[]][] = [];
     @observable _selectedResult: Doc | undefined = undefined;
     @observable _deletedDocsStatus: boolean = false;
     @observable _onlyAliases: boolean = true;
 
+    /**
+     * This is the constructor for the SearchBox class.
+     */
     constructor(props: any) {
         super(props);
         SearchBox.Instance = this;
-        this._searchString = "reset_search";
-        this.submitSearch();
-        this._searchString = "";
-        this.submitSearch();
     }
 
+    /** 
+     * This method is called when the SearchBox component is first mounted. When the user opens
+     * the search panel, the search input box is automatically selected. This allows the user to
+     * type in the search input box immediately, without needing clicking on it first.
+     */
     componentDidMount = action(() => {
         if (this._inputRef.current) {
             this._inputRef.current.focus();
         }
     });
 
+    /**
+     * This method is called when the SearchBox component is about to be unmounted. When the user
+     * closes the search panel, the search and its results are reset.
+     */
     componentWillUnmount() {
-        this.resetSearch;
+        this.resetSearch();
     }
 
+    /**
+     * This method is called when the text in the search input box is modified by the user. The
+     * _searchString is updated to the new value of the text in the input box and submitSearch
+     * is called to update the search results accordingly.
+     * 
+     * (Note: There is no longer a need to press enter to submit a search. Any update to the input
+     * causes a search to be submitted automatically.)
+     */
     onInputChange = action((e: React.ChangeEvent<HTMLInputElement>) => {
         this._searchString = e.target.value;
         this.submitSearch();
     });
 
+    /**
+     * This method is called when the option in the select drop-down menu is changed. The
+     * _docTypeString is updated to the new value of the option in the drop-down menu. This
+     * is used to filter the results of the search to documents of a specific type.
+     * 
+     * (Note: This doesn't affect the results array, so there is no need to submit a new
+     * search here. The results of the search on the _searchString query are simply filtered
+     * by type directly before rendering them.)
+     */
     onSelectChange = action((e: React.ChangeEvent<HTMLSelectElement>) => {
         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((doc: Doc) => {
         this.selectElement(doc);
         this._selectedResult = doc;
     });
 
-    static foreachRecursiveDoc(docs: Doc[], func: (doc: Doc) => void) {
+    /**
+     * @param {Doc[]} docs - docs to be searched through recursively
+     * @param {number, Doc => void} func - function to be called on each doc
+     *
+     * This method iterates through an array of docs and all docs within those docs, calling
+     * the function func on each doc.
+     */
+    static foreachRecursiveDoc(docs: Doc[], func: (depth: number, doc: Doc) => void) {
         let newarray: Doc[] = [];
+        var depth = 0;
         while (docs.length > 0) {
             newarray = [];
             docs.filter(d => d).forEach(d => {
@@ -94,12 +133,45 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
                 const annos = !Field.toString(Doc.LayoutField(d) as Field).includes("CollectionView");
                 const data = d[annos ? fieldKey + "-annotations" : fieldKey];
                 data && newarray.push(...DocListCast(data));
-                func(d);
+                func(depth, d);
             });
             docs = newarray;
+            depth++;
         }
     }
 
+    /**
+     * @param {Doc[]} docs - docs to be searched through recursively
+     * @param {number, Doc => void} func - function to be called on each doc
+     *
+     * This method iterates asynchronously through an array of docs and all docs within those
+     * docs, calling the function func on each doc.
+     */
+    static async foreachRecursiveDocAsync(docs: Doc[], func: (depth: number, doc: Doc) => void) {
+        let newarray: Doc[] = [];
+        var depth = 0;
+        while (docs.length > 0) {
+            newarray = [];
+            await Promise.all(docs.filter(d => d).map(async d => {
+                const fieldKey = Doc.LayoutFieldKey(d);
+                const annos = !Field.toString(Doc.LayoutField(d) as Field).includes("CollectionView");
+                const data = d[annos ? fieldKey + "-annotations" : fieldKey];
+                const docs = await DocListCastAsync(data);
+                docs && newarray.push(...docs);
+                func(depth, d);
+            }));
+            docs = newarray;
+            depth++;
+        }
+    }
+
+    /**
+     * @param {String} type - string representing the type of a doc
+     *
+     * This method converts a doc type string of any length to a 3-letter doc type string in
+     * which the first letter is capitalized. This is used when displaying the type on the
+     * right side of each search result.
+     */
     static formatType(type: String): String {
         if (type == "pdf") {
             return "PDF";
@@ -111,57 +183,75 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
         return type.charAt(0).toUpperCase() + type.substring(1, 3);
     }
 
+    /**
+     * @param {String} query - search query string
+     *
+     * This method searches the CollectionDockingView instance for a certain query and puts
+     * the matching results in the results array. Docs are considered to be matching results
+     * when the query is a substring of many different pieces of its metadata (title, text,
+     * author, etc).
+     */
     @action
     searchCollection(query: string) {
-        const selectedCollection = CollectionDockingView.Instance;
         const blockedTypes = [DocumentType.PRESELEMENT, DocumentType.KVP, DocumentType.FILTER, DocumentType.SEARCH, DocumentType.SEARCHITEM, DocumentType.FONTICON, DocumentType.BUTTON, DocumentType.SCRIPTING];
+        const blockedKeys = ["x", "y", "proto", "width", "autoHeight", "acl-Override", "acl-Public", "context", "zIndex", "height", "text-scrollHeight", "text-height", "cloneFieldFilter", "isPrototype", "text-annotations",
+            "dragFactory-count", "text-noTemplate", "aliases", "system", "layoutKey", "baseProto", "xMargin", "yMargin", "links", "layout", "layout_keyValue", "fitWidth", "viewType", "title-custom",
+            "panX", "panY", "viewScale"]
         query = query.toLowerCase();
 
         this._results = []
+        this._selectedResult = undefined
 
-        if (selectedCollection !== undefined) {
-            const docs = DocListCast(selectedCollection.dataDoc[Doc.LayoutFieldKey(selectedCollection.dataDoc)]);
-            SearchBox.foreachRecursiveDoc(docs, (doc: Doc) => {
+        if (this._selectedCollection !== undefined) {
+            const docs = DocListCast(this._selectedCollection.dataDoc[Doc.LayoutFieldKey(this._selectedCollection.dataDoc)]);
+            const docIDs: String[] = []
+            SearchBox.foreachRecursiveDoc(docs, (depth: number, doc: Doc) => {
                 const dtype = StrCast(doc.type, "string") as DocumentType;
-                if (dtype && !blockedTypes.includes(dtype)) {
+                if (dtype && !blockedTypes.includes(dtype) && !docIDs.includes(doc[Id]) && depth > 0) {
                     const hlights = new Set<string>();
                     SearchBox.documentKeys(doc).forEach(key => Field.toString(doc[key] as Field).toLowerCase().includes(query) && hlights.add(key));
-                    Array.from(hlights.keys()).length > 0 && this._results.push([doc, Array.from(hlights.keys()), []]);
+                    blockedKeys.forEach(key => {
+                        hlights.delete(key);
+                    })
+                    Array.from(hlights.keys()).length > 0 && this._results.push([doc, Array.from(hlights.keys())]);
                 }
+                docIDs.push(doc[Id])
             });
         }
-
-        this._results = Array.from(new Set(this._results))
-        this._selectedResult = undefined
     }
 
+    /**
+     * @param {Doc} doc - doc for which keys are returned
+     * 
+     * This method returns a list of a document doc's keys.
+     */
     static documentKeys(doc: Doc) {
         const keys: { [key: string]: boolean } = {};
-        // bcz: ugh.  this is untracked since otherwise a large collection of documents will blast the server for all their fields.
-        //  then as each document's fields come back, we update the documents _proxies.  Each time we do this, the whole schema will be
-        //  invalidated and re-rendered.   This workaround will inquire all of the document fields before the options button is clicked.
-        //  then by the time the options button is clicked, all of the fields should be in place.  If a new field is added while this menu
-        //  is displayed (unlikely) it won't show up until something else changes.
-        //TODO Types
         Doc.GetAllPrototypes(doc).map(proto => Object.keys(proto).forEach(key => keys[key] = false));
         return Array.from(Object.keys(keys));
     }
 
+    /**
+     * This method submits a search with the _searchString as its query and updates
+     * the results array accordingly.
+     */
     @action
     submitSearch = async () => {
         this.resetSearch();
 
-        //this.dataDoc[this.fieldKey] = new List<Doc>([]);
         let query = StrCast(this._searchString);
         Doc.SetSearchQuery(query);
         this._results = [];
-        this._resultsSet.clear();
 
         if (query) {
             this.searchCollection(query);
         }
     }
 
+    /**
+     * This method resets the search by iterating through each result and removing all
+     * brushes and highlights. All search matches are cleared as well.
+     */
     resetSearch = action(() => {
         this._results.forEach(result => {
             Doc.UnBrushDoc(result[0]);
@@ -170,10 +260,32 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
         });
     });
 
+    /**
+     * @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) => {
-        await DocumentManager.Instance.jumpToDocument(doc, true); // documents open in new tab instead of on right    
+        await DocumentManager.Instance.jumpToDocument(doc, true);
+    }
+
+    /**
+     * 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.
+     */
+    @computed
+    public get selectOptions() {
+        const selectValues = ["all", "rtf", "image", "pdf", "web", "video", "audio", "collection"]
+
+        return selectValues.map(value => {
+            return <option key={value} value={value}>{SearchBox.formatType(value)}</option>
+        })
     }
 
+    /**
+     * This method renders the search input box, select drop-down menu, and search results.
+     */
     render() {
         var validResults = 0;
 
@@ -186,7 +298,19 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
 
             if (this._docTypeString == "all" || this._docTypeString == result[0].type) {
                 validResults++;
-                return (<div key={result[0][Id]} onClick={() => this.onResultClick(result[0])} className={className}><div className="searchBox-result-title">{result[0].title}</div><div className="searchBox-result-type">{SearchBox.formatType(StrCast(result[0].type))}</div></div>)
+                return (
+                    <div key={result[0][Id]} onClick={() => this.onResultClick(result[0])} className={className}>
+                        <div className="searchBox-result-title">
+                            {result[0].title}
+                        </div>
+                        <div className="searchBox-result-type">
+                            {SearchBox.formatType(StrCast(result[0].type))}
+                        </div>
+                        <div className="searchBox-result-keys">
+                            {result[1].join(", ")}
+                        </div>
+                    </div>
+                )
             }
 
             return null;
@@ -194,15 +318,11 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
 
         results.filter(result => result);
 
-        const selectOptions = selectValues.map(value => {
-            return <option key={value} value={value}>{SearchBox.formatType(value)}</option>
-        })
-
         return (
             <div style={{ pointerEvents: "all" }} className="searchBox-container">
                 <div className="searchBox-bar">
                     <select name="type" id="searchBox-type" className="searchBox-type" onChange={this.onSelectChange}>
-                        {selectOptions}
+                        {this.selectOptions}
                     </select>
                     <input defaultValue={""} autoComplete="off" onChange={this.onInputChange} type="text" placeholder="Search..." id="search-input" className="searchBox-input" ref={this._inputRef} />
                 </div >
@@ -217,190 +337,4 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
             </div >
         );
     }
-}
-
-
-
-/*
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { Tooltip } from '@material-ui/core';
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
-import { observer } from 'mobx-react';
-import * as React from 'react';
-import { Doc, DocListCast, Field, Opt, DocListCastAsync, DataSym, HeightSym, FieldsSym } from '../../../fields/Doc';
-import { documentSchema } from "../../../fields/documentSchemas";
-import { Copy, Id, ToString } from '../../../fields/FieldSymbols';
-import { List } from '../../../fields/List';
-import { createSchema, listSpec, makeInterface } from '../../../fields/Schema';
-import { SchemaHeaderField } from '../../../fields/SchemaHeaderField';
-import { Cast, NumCast, StrCast } from '../../../fields/Types';
-import { emptyFunction, returnFalse, returnZero, setupMoveUpEvents, Utils } from '../../../Utils';
-import { Docs } from '../../documents/Documents';
-import { DocumentType } from "../../documents/DocumentTypes";
-import { CurrentUserUtils } from "../../util/CurrentUserUtils";
-import { SetupDrag } from '../../util/DragManager';
-import { SearchUtil } from '../../util/SearchUtil';
-import { Transform } from '../../util/Transform';
-import { CollectionDockingView } from "../collections/CollectionDockingView";
-import { CollectionSchemaView, ColumnType } from "../collections/CollectionSchemaView";
-import { CollectionViewType } from '../collections/CollectionView';
-import { ViewBoxBaseComponent } from "../DocComponent";
-import { FieldView, FieldViewProps } from '../nodes/FieldView';
-import "./SearchBox.scss";
-import { undoBatch } from "../../util/UndoManager";
-import { DocServer } from "../../DocServer";
-import { MainView } from "../MainView";
-import { SelectionManager } from "../../util/SelectionManager";
-import { CollectionSchemaBooleanCell } from "../collections/CollectionSchemaCells";
-import { transpileModule } from "typescript";
-import { DocumentManager } from "../../util/DocumentManager";
-
-export const searchSchema = createSchema({ Document: Doc });
-
-type SearchBoxDocument = makeInterface<[typeof documentSchema, typeof searchSchema]>;
-const SearchBoxDocument = makeInterface(documentSchema, searchSchema);
-
-@observer
-export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDocument>(SearchBoxDocument) {
-    public static LayoutString(fieldKey: string) { return FieldView.LayoutString(SearchBox, fieldKey); }
-    public static Instance: SearchBox;
-
-    @observable _searchString = "";
-    @observable _docTypeString = "all";
-    @observable _results: [Doc, string[], string[]][] = [];
-    @observable _selectedResult: Doc | undefined = undefined;
-    @observable _deletedDocsStatus: boolean = false;
-    @observable _onlyAliases: boolean = true;
-
-    constructor(props: any) {
-        super(props);
-        SearchBox.Instance = this;
-    }
-
-    onInputChange = action((e: React.ChangeEvent<HTMLInputElement>) => {
-        this._searchString = e.target.value;
-        this.submitSearch();
-    });
-
-    onSelectChange = action((e: React.ChangeEvent<HTMLSelectElement>) => {
-        this._docTypeString = e.target.value;
-        this.submitSearch();
-    });
-
-    onResultClick = action((doc: Doc) => {
-        this.selectElement(doc);
-        this._selectedResult = doc;
-    });
-
-    static foreachRecursiveDoc(docs: Doc[], func: (doc: Doc) => void) {
-        const blockedTypes = [DocumentType.PRESELEMENT, DocumentType.KVP, DocumentType.FILTER, DocumentType.SEARCH, DocumentType.SEARCHITEM, DocumentType.FONTICON, DocumentType.BUTTON, DocumentType.SCRIPTING];
-        let newarray: Doc[] = [];
-        while (docs.length > 0) {
-            newarray = [];
-            docs.filter(d => d).forEach(d => {
-                const dtype = StrCast(d.type, "string") as DocumentType;
-                if (dtype && !blockedTypes.includes(dtype)) {
-                    const fieldKey = Doc.LayoutFieldKey(d);
-                    const annos = !Field.toString(Doc.LayoutField(d) as Field).includes("CollectionView");
-                    const data = d[annos ? fieldKey + "-annotations" : fieldKey];
-                    data && newarray.push(...DocListCast(data));
-                    func(d);
-                }
-            });
-            docs = newarray;
-        }
-    }
-
-    @action
-    searchCollection(query: string) {
-        const selectedCollection = CollectionDockingView.Instance;
-        query = query.toLowerCase();
-
-        if (selectedCollection !== undefined) {
-            console.log("hello111")
-            // this._currentSelectedCollection = selectedCollection;
-            const docs = DocListCast(selectedCollection.dataDoc[Doc.LayoutFieldKey(selectedCollection.dataDoc)]);
-            const found: [Doc, string[], string[]][] = [];
-            SearchBox.foreachRecursiveDoc(docs, (doc: Doc) => {
-                console.log("HELLO")
-                if (this._docTypeString == "all" || this._docTypeString == doc.type) {
-                    const hlights = new Set<string>();
-                    SearchBox.documentKeys(doc).forEach(key => Field.toString(doc[key] as Field).toLowerCase().includes(query) && hlights.add(key));
-                    Array.from(hlights.keys()).length > 0 && found.push([doc, Array.from(hlights.keys()), []]);
-                }
-            });
-
-            this._results = found;
-            //this.setSearchFilter(selectedCollection, this.filter && found.length ? this._docsforfilter : undefined);
-        }
-    }
-
-    static documentKeys(doc: Doc) {
-        const keys: { [key: string]: boolean } = {};
-        // bcz: ugh.  this is untracked since otherwise a large collection of documents will blast the server for all their fields.
-        //  then as each document's fields come back, we update the documents _proxies.  Each time we do this, the whole schema will be
-        //  invalidated and re-rendered.   This workaround will inquire all of the document fields before the options button is clicked.
-        //  then by the time the options button is clicked, all of the fields should be in place.  If a new field is added while this menu
-        //  is displayed (unlikely) it won't show up until something else changes.
-        //TODO Types
-        Doc.GetAllPrototypes(doc).map(proto => Object.keys(proto).forEach(key => keys[key] = false));
-        return Array.from(Object.keys(keys));
-    }
-
-    @action
-    submitSearch = async () => {
-        Doc.ClearSearchMatches();
-        this._results = [];
-
-        this.dataDoc[this.fieldKey] = new List<Doc>([]);
-        let query = StrCast(this._searchString);
-        Doc.SetSearchQuery(query);
-        this._results = [];
-
-        if (query) {
-            this.searchCollection(query);
-        }
-    }
-
-    selectElement = async (doc: Doc) => {
-        await DocumentManager.Instance.jumpToDocument(doc, true); // documents open in new tab instead of on right
-    }
-
-    render() {
-        const results = this._results.map(result => {
-            var className = "searchBox-results-scroll-view-result";
-
-            if (this._selectedResult == result[0]) {
-                className += " searchBox-results-scroll-view-result-selected"
-            }
-
-            return (<div key={result[0][Id]} onClick={() => this.onResultClick(result[0])} className={className}><div className="titletitle">{result[0].title}</div></div>)
-        })
-
-        return (
-            <div style={{ pointerEvents: "all" }} className="searchBox-container">
-                <div className="searchBox-bar">
-                    <select name="type" id="searchBox-type" className="searchBox-type" onChange={this.onSelectChange}>
-                        <option value="all">All</option>
-                        <option value="rtf">Text</option>
-                        <option value="img">Img</option>
-                        <option value="pdf">PDF</option>
-                        <option value="video">Vid</option>
-                        <option value="audio">Aud</option>
-                        <option value="inks">Ink</option>
-                        <option value="col">Col</option>
-                    </select>
-                    <input defaultValue={""} autoComplete="off" onChange={this.onInputChange} type="text" placeholder="Search..." id="search-input" className="searchBox-input" />
-                </div >
-                <div className="searchBox-results-container">
-                    <div className="searchBox-results-count">
-                        {`${this._results.length}` + " result" + (this._results.length == 1 ? "" : "s")}
-                    </div>
-                    <div className="searchBox-results-scroll-view">
-                        {results}
-                    </div>
-                </div>
-            </div >
-        );
-    }
-}*/
\ No newline at end of file
+}
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2

