From d7cb584516619406284134523c0650389fa57d05 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 13 Apr 2020 11:16:37 -0400
Subject: added something to make search icons active

---
 src/client/views/search/SearchBox.tsx | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

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

diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index 19a4d558e..0947bff8d 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -318,6 +318,7 @@ export class SearchBox extends React.Component<SearchProps> {
         const types = this.filterTypes;
         const includeDeleted = this.getDataStatus() ? "" : " AND NOT deleted_b:true";
         const includeIcons = this.getDataStatus() ? "" : " AND NOT type_t:fonticonbox";
+        // fq: type_t:collection OR {!join from=id to=proto_i}type_t:collection   q:text_t:hello
         return "NOT baseProto_b:true" + includeDeleted + includeIcons + (types ? ` AND (${types.map(type => `({!join from=id to=proto_i}type_t:"${type}" AND NOT type_t:*) OR type_t:"${type}"`).join(" ")})` : "");
     }
 
@@ -652,7 +653,7 @@ export class SearchBox extends React.Component<SearchProps> {
                         <button className="filter-item" style={this._nodeStatus ? { background: "#aaaaa3" } : {}} onClick={this.handleNodeChange}>Nodes</button>
                     </div>
                     <div id={`node${this.props.id}`} className="filter-body" style={this._nodeStatus ? { borderTop: "grey 1px solid" } : { borderTop: "0px" }}>
-                        <IconBar />
+                        <IconBar setIcons={(icons: string[]) => this._icons = icons} />
                     </div>
                     <div className="filter-key" id={`key${this.props.id}`} style={this._keyStatus ? { borderTop: "grey 1px solid" } : { borderTop: "0px" }}>
                         <div className="filter-keybar">
-- 
cgit v1.2.3-70-g09d2


From 38a0f3f0b690599d72e52456ecfc14c081811e89 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 20 Apr 2020 19:05:05 -0400
Subject: fixed dragging off search result collection

---
 src/client/views/search/SearchBox.tsx   | 19 +++++++-----------
 src/server/ApiManagers/SearchManager.ts | 35 +++++++++++++++++----------------
 2 files changed, 25 insertions(+), 29 deletions(-)

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

diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index 0947bff8d..f332104fc 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -167,15 +167,7 @@ export class SearchBox extends React.Component<SearchProps> {
     }
 
     basicRequireWords(query: string): string {
-        const oldWords = query.split(" ");
-        const newWords: string[] = [];
-        oldWords.forEach(word => {
-            const newWrd = "+" + word;
-            newWords.push(newWrd);
-        });
-        query = newWords.join(" ");
-
-        return query;
+        return query.split(" ").join(" + ").replace(/ + /, "");
     }
 
     @action
@@ -316,10 +308,13 @@ export class SearchBox extends React.Component<SearchProps> {
 
     private get filterQuery() {
         const types = this.filterTypes;
-        const includeDeleted = this.getDataStatus() ? "" : " AND NOT deleted_b:true";
-        const includeIcons = this.getDataStatus() ? "" : " AND NOT type_t:fonticonbox";
+        const baseExpr = "NOT baseProto_b:true";
+        const includeDeleted = this.getDataStatus() ? "" : " NOT deleted_b:true";
+        const includeIcons = this.getDataStatus() ? "" : " NOT type_t:fonticonbox";
+        const typeExpr = !types ? "" : ` (${types.map(type => `({!join from=id to=proto_i}type_t:"${type}" AND NOT type_t:*) OR type_t:"${type}"`).join(" ")})`;
         // fq: type_t:collection OR {!join from=id to=proto_i}type_t:collection   q:text_t:hello
-        return "NOT baseProto_b:true" + includeDeleted + includeIcons + (types ? ` AND (${types.map(type => `({!join from=id to=proto_i}type_t:"${type}" AND NOT type_t:*) OR type_t:"${type}"`).join(" ")})` : "");
+        const query = [baseExpr, includeDeleted, includeIcons, typeExpr].join(" AND ").replace(/AND $/, "");
+        return query;
     }
 
     getDataStatus() { return this._deletedDocsStatus; }
diff --git a/src/server/ApiManagers/SearchManager.ts b/src/server/ApiManagers/SearchManager.ts
index 5f7d1cf6d..9e1cc120d 100644
--- a/src/server/ApiManagers/SearchManager.ts
+++ b/src/server/ApiManagers/SearchManager.ts
@@ -1,18 +1,14 @@
-import ApiManager, { Registration } from "./ApiManager";
-import { Method } from "../RouteManager";
-import { Search } from "../Search";
-const findInFiles = require('find-in-files');
+import { exec } from "child_process";
+import { cyan, green, red, yellow } from "colors";
 import * as path from 'path';
-import { pathToDirectory, Directory } from "./UploadManager";
-import { red, cyan, yellow, green } from "colors";
-import RouteSubscriber from "../RouteSubscriber";
-import { exec, execSync } from "child_process";
-import { onWindows } from "..";
-import { get } from "request-promise";
 import { log_execution } from "../ActionUtilities";
 import { Database } from "../database";
-import rimraf = require("rimraf");
-import { mkdirSync, chmod, chmodSync } from "fs";
+import { Method } from "../RouteManager";
+import RouteSubscriber from "../RouteSubscriber";
+import { Search } from "../Search";
+import ApiManager, { Registration } from "./ApiManager";
+import { Directory, pathToDirectory } from "./UploadManager";
+const findInFiles = require('find-in-files');
 
 export class SearchManager extends ApiManager {
 
@@ -48,12 +44,17 @@ export class SearchManager extends ApiManager {
                     res.send([]);
                     return;
                 }
-                const results = await findInFiles.find({ 'term': q, 'flags': 'ig' }, pathToDirectory(Directory.text), ".txt$");
                 const resObj: { ids: string[], numFound: number, lines: string[] } = { ids: [], numFound: 0, lines: [] };
-                for (const result in results) {
-                    resObj.ids.push(path.basename(result, ".txt").replace(/upload_/, ""));
-                    resObj.lines.push(results[result].line);
-                    resObj.numFound++;
+                try {
+                    const results = await findInFiles.find({ 'term': q, 'flags': 'ig' }, pathToDirectory(Directory.text), ".txt$");
+                    for (const result in results) {
+                        resObj.ids.push(path.basename(result, ".txt").replace(/upload_/, ""));
+                        resObj.lines.push(results[result].line);
+                        resObj.numFound++;
+                    }
+                }
+                catch (e) {
+                    console.error(e);
                 }
                 res.send(resObj);
             }
-- 
cgit v1.2.3-70-g09d2


From a9b6fba3a742f95815e2664770079bebd3b87d5f Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 20 Apr 2020 22:34:29 -0400
Subject: fixed search to avoid server crashes by stripping off initial ?*+
 characters.  made search parameters persistent on queryBox

---
 src/client/documents/Documents.ts                  |  1 +
 src/client/util/SearchUtil.ts                      |  2 +-
 src/client/views/nodes/QueryBox.tsx                | 13 +++++--
 src/client/views/search/IconBar.tsx                |  4 +--
 src/client/views/search/SearchBox.tsx              | 40 +++++++++++-----------
 src/server/ApiManagers/SearchManager.ts            | 17 ++++-----
 .../authentication/models/current_user_utils.ts    |  1 +
 7 files changed, 42 insertions(+), 36 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index e3cc8ccfe..5ff8f29ec 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -140,6 +140,7 @@ export interface DocumentOptions {
     icon?: string;
     sourcePanel?: Doc; // panel to display in 'targetContainer' as the result of a button onClick script
     targetContainer?: Doc; // document whose proto will be set to 'panel' as the result of a onClick click script
+    searchFileTypes?: List<string>; // file types allowed in a search query
     strokeWidth?: number;
     treeViewPreventOpen?: boolean; // ignores the treeViewOpen Doc flag which allows a treeViewItem's expand/collapse state to be independent of other views of the same document in the tree view
     treeViewHideTitle?: boolean; // whether to hide the title of a tree view
diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts
index 2026bf940..6501da34a 100644
--- a/src/client/util/SearchUtil.ts
+++ b/src/client/util/SearchUtil.ts
@@ -44,7 +44,7 @@ export namespace SearchUtil {
         const { ids, highlighting } = result;
 
         const txtresult = query !== "*" && JSON.parse(await rp.get(Utils.prepend("/textsearch"), {
-            qs: { ...options, q: query },
+            qs: { ...options, q: query.replace(/^[ \+\?\*\|]*/, "") }, // a leading '+' leads to a server crash since findInFiles doesn't handle regex failures
         }));
 
         const fileids = txtresult ? txtresult.ids : [];
diff --git a/src/client/views/nodes/QueryBox.tsx b/src/client/views/nodes/QueryBox.tsx
index 947167200..76885eada 100644
--- a/src/client/views/nodes/QueryBox.tsx
+++ b/src/client/views/nodes/QueryBox.tsx
@@ -3,13 +3,14 @@ import { IReactionDisposer } from "mobx";
 import { observer } from "mobx-react";
 import { documentSchema } from "../../../new_fields/documentSchemas";
 import { Id } from '../../../new_fields/FieldSymbols';
-import { makeInterface } from "../../../new_fields/Schema";
-import { StrCast } from "../../../new_fields/Types";
+import { makeInterface, listSpec } from "../../../new_fields/Schema";
+import { StrCast, Cast } from "../../../new_fields/Types";
 import { SelectionManager } from "../../util/SelectionManager";
 import { ViewBoxAnnotatableComponent } from '../DocComponent';
 import { SearchBox } from "../search/SearchBox";
 import { FieldView, FieldViewProps } from './FieldView';
 import "./QueryBox.scss";
+import { List } from "../../../new_fields/List";
 
 type QueryDocument = makeInterface<[typeof documentSchema]>;
 const QueryDocument = makeInterface(documentSchema);
@@ -28,7 +29,13 @@ export class QueryBox extends ViewBoxAnnotatableComponent<FieldViewProps, QueryD
     render() {
         const dragging = !SelectionManager.GetIsDragging() ? "" : "-dragging";
         return <div className={`queryBox${dragging}`} onWheel={(e) => e.stopPropagation()} >
-            <SearchBox id={this.props.Document[Id]} searchQuery={StrCast(this.dataDoc.searchQuery)} filterQquery={StrCast(this.dataDoc.filterQuery)} />
+            <SearchBox
+                id={this.props.Document[Id]}
+                setSearchQuery={q => this.dataDoc.searchQuery = q}
+                searchQuery={StrCast(this.dataDoc.searchQuery)}
+                setSearchFileTypes={q => this.dataDoc.searchFileTypes = new List<string>(q)}
+                searchFileTypes={Cast(this.dataDoc.searchFileTypes, listSpec("string"), [])}
+                filterQquery={StrCast(this.dataDoc.filterQuery)} />
         </div >;
     }
 }
\ No newline at end of file
diff --git a/src/client/views/search/IconBar.tsx b/src/client/views/search/IconBar.tsx
index 9cf5a9c87..9b7cf2fc6 100644
--- a/src/client/views/search/IconBar.tsx
+++ b/src/client/views/search/IconBar.tsx
@@ -25,7 +25,7 @@ library.add(faGlobeAsia);
 library.add(faBan);
 
 export interface IconBarProps {
-    setIcons: (icons: string[]) => {};
+    setIcons: (icons: string[]) => void;
 }
 
 
@@ -44,7 +44,7 @@ export class IconBar extends React.Component<IconBarProps> {
 
     @action.bound
     updateIcon(newArray: string[]) {
-    this._icons = newArray;
+        this._icons = newArray;
         this.props.setIcons?.(this._icons);
     }
 
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index f332104fc..b0a49f750 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -19,13 +19,17 @@ import { FieldView } from '../nodes/FieldView';
 import { DocumentType } from "../../documents/DocumentTypes";
 import { DocumentView } from '../nodes/DocumentView';
 import { SelectionManager } from '../../util/SelectionManager';
+import { listSpec } from '../../../new_fields/Schema';
 
 library.add(faTimes);
 
 export interface SearchProps {
     id: string;
-    searchQuery?: string;
+    searchQuery: string;
     filterQquery?: string;
+    setSearchQuery: (q: string) => {};
+    searchFileTypes: string[];
+    setSearchFileTypes: (types: string[]) => {}
 }
 
 export enum Keys {
@@ -37,7 +41,8 @@ export enum Keys {
 @observer
 export class SearchBox extends React.Component<SearchProps> {
 
-    @observable private _searchString: string = "";
+    private get _searchString() { return this.props.searchQuery; }
+    private set _searchString(value) { this.props.setSearchQuery(value); }
     @observable private _resultsOpen: boolean = false;
     @observable private _searchbarOpen: boolean = false;
     @observable private _results: [Doc, string[], string[]][] = [];
@@ -72,20 +77,16 @@ export class SearchBox extends React.Component<SearchProps> {
         this.resultsScrolled = this.resultsScrolled.bind(this);
     }
 
-    componentDidMount = () => {
+    componentDidMount = action(() => {
         if (this.inputRef.current) {
             this.inputRef.current.focus();
-            runInAction(() => this._searchbarOpen = true);
+            this._searchbarOpen = true;
         }
-        if (this.props.searchQuery && this.props.filterQquery) {
-            console.log(this.props.searchQuery);
-            const sq = this.props.searchQuery;
-            runInAction(() => {
-                this._searchString = sq;
-                this.submitSearch();
-            });
+        if (this.props.searchQuery) { // bcz: why was this here?    } && this.props.filterQquery) {
+            this._searchString = this.props.searchQuery;
+            this.submitSearch();
         }
-    }
+    })
 
 
     @action
@@ -133,7 +134,10 @@ export class SearchBox extends React.Component<SearchProps> {
     //this also serves as an indicator if the word status filter is applied
     @observable private _filterOpen: boolean = false;
     //if icons = all icons, then no icon filter is applied
-    @observable private _icons: string[] = this._allIcons;
+    get _icons() { return this.props.searchFileTypes; }
+    set _icons(value) {
+        this.props.setSearchFileTypes(value);
+    }
     //if all of these are true, no key filter is applied
     @observable private _titleFieldStatus: boolean = true;
     @observable private _authorFieldStatus: boolean = true;
@@ -206,12 +210,6 @@ export class SearchBox extends React.Component<SearchProps> {
         return this._icons.length === this._allIcons.length ? undefined : this._icons;
     }
 
-    @action.bound
-    updateIcon(newArray: string[]) { this._icons = newArray; }
-
-    @action.bound
-    getIcons(): string[] { return this._icons; }
-
     //TODO: basically all of this
     //gets all of the collections of all the docviews that are selected
     //if a collection is the only thing selected, search only in that collection (not its container)
@@ -648,7 +646,9 @@ export class SearchBox extends React.Component<SearchProps> {
                         <button className="filter-item" style={this._nodeStatus ? { background: "#aaaaa3" } : {}} onClick={this.handleNodeChange}>Nodes</button>
                     </div>
                     <div id={`node${this.props.id}`} className="filter-body" style={this._nodeStatus ? { borderTop: "grey 1px solid" } : { borderTop: "0px" }}>
-                        <IconBar setIcons={(icons: string[]) => this._icons = icons} />
+                        <IconBar setIcons={(icons: string[]) => {
+                            this._icons = icons;
+                        }} />
                     </div>
                     <div className="filter-key" id={`key${this.props.id}`} style={this._keyStatus ? { borderTop: "grey 1px solid" } : { borderTop: "0px" }}>
                         <div className="filter-keybar">
diff --git a/src/server/ApiManagers/SearchManager.ts b/src/server/ApiManagers/SearchManager.ts
index 9e1cc120d..753c31fcf 100644
--- a/src/server/ApiManagers/SearchManager.ts
+++ b/src/server/ApiManagers/SearchManager.ts
@@ -45,16 +45,13 @@ export class SearchManager extends ApiManager {
                     return;
                 }
                 const resObj: { ids: string[], numFound: number, lines: string[] } = { ids: [], numFound: 0, lines: [] };
-                try {
-                    const results = await findInFiles.find({ 'term': q, 'flags': 'ig' }, pathToDirectory(Directory.text), ".txt$");
-                    for (const result in results) {
-                        resObj.ids.push(path.basename(result, ".txt").replace(/upload_/, ""));
-                        resObj.lines.push(results[result].line);
-                        resObj.numFound++;
-                    }
-                }
-                catch (e) {
-                    console.error(e);
+                let results: any;
+                const dir = pathToDirectory(Directory.text);
+                results = await findInFiles.find({ 'term': q, 'flags': 'ig' }, dir, ".txt$");
+                for (const result in results) {
+                    resObj.ids.push(path.basename(result, ".txt").replace(/upload_/, ""));
+                    resObj.lines.push(results[result].line);
+                    resObj.numFound++;
                 }
                 res.send(resObj);
             }
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index e0ca5a468..3955f64fc 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -413,6 +413,7 @@ export class CurrentUserUtils {
                 _width: 50, _height: 25, title: "Search", fontSize: 10,
                 letterSpacing: "0px", textTransform: "unset", borderRounding: "5px 5px 0px 0px", boxShadow: "3px 3px 0px rgb(34, 34, 34)",
                 sourcePanel: new PrefetchProxy(Docs.Create.QueryDocument({ title: "search stack", })) as any as Doc,
+                searchFileTypes: new List<string>([DocumentType.RTF, DocumentType.IMG, DocumentType.PDF, DocumentType.VID, DocumentType.WEB, DocumentType.SCRIPTING]),
                 targetContainer: new PrefetchProxy(sidebarContainer) as any as Doc,
                 lockedPosition: true,
                 onClick: ScriptField.MakeScript("this.targetContainer.proto = this.sourcePanel")
-- 
cgit v1.2.3-70-g09d2


From 682782a1337003de1694d1625d262a1efddcb02d Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 21 Apr 2020 23:50:20 -0400
Subject: tweaked HTMLtag syntax slightly for onClick functions to be specified
 without quotes

---
 package-lock.json                                  | 27 ++++++++++++++++++
 package.json                                       |  2 ++
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  1 -
 src/client/views/nodes/DocumentContentsView.tsx    | 32 ++++++++++++++++------
 src/client/views/search/SearchBox.tsx              |  4 +--
 5 files changed, 54 insertions(+), 12 deletions(-)

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

diff --git a/package-lock.json b/package-lock.json
index 940321132..176d1f27e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -43,6 +43,15 @@
                 "regenerator-runtime": "^0.13.4"
             }
         },
+        "@babel/runtime-corejs3": {
+            "version": "7.9.2",
+            "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.9.2.tgz",
+            "integrity": "sha512-HHxmgxbIzOfFlZ+tdeRKtaxWOMUoCG5Mu3wKeUmOxjYrwb3AAHgnmtCUbPPK11/raIWLIBK250t8E2BPO0p7jA==",
+            "requires": {
+                "core-js-pure": "^3.0.0",
+                "regenerator-runtime": "^0.13.4"
+            }
+        },
         "@babel/types": {
             "version": "7.9.5",
             "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.5.tgz",
@@ -1194,6 +1203,11 @@
             "resolved": "https://registry.npmjs.org/@types/word-extractor/-/word-extractor-0.3.0.tgz",
             "integrity": "sha512-XcVsLXMpPT6Lv4Qvsc2yPoWg3zPVKEIyQ21eQ1ka58zZjtd7vUZ2kx0KydRbsNRNGopEIiwlemGzgo46dytR2Q=="
         },
+        "@types/xregexp": {
+            "version": "4.3.0",
+            "resolved": "https://registry.npmjs.org/@types/xregexp/-/xregexp-4.3.0.tgz",
+            "integrity": "sha512-3gJTS9gt27pS7U9q5IVqo4YvKSlkf2ck8ish6etuDj6LIRxkL/2Y8RMUtK/QzvE1Yv2zwWV5yemI2BS0GGGFnA=="
+        },
         "@types/yargs": {
             "version": "13.0.8",
             "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.8.tgz",
@@ -3456,6 +3470,11 @@
             "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
             "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY="
         },
+        "core-js-pure": {
+            "version": "3.6.5",
+            "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.6.5.tgz",
+            "integrity": "sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA=="
+        },
         "core-util-is": {
             "version": "1.0.2",
             "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
@@ -18092,6 +18111,14 @@
             "resolved": "https://registry.npmjs.org/xoauth2/-/xoauth2-1.2.0.tgz",
             "integrity": "sha1-8u76wRRyyXHqO8RuVU60sSMhRuU="
         },
+        "xregexp": {
+            "version": "4.3.0",
+            "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.3.0.tgz",
+            "integrity": "sha512-7jXDIFXh5yJ/orPn4SXjuVrWWoi4Cr8jfV1eHv9CixKSbU+jY4mxfrBwAuDvupPNKpMUY+FeIqsVw/JLT9+B8g==",
+            "requires": {
+                "@babel/runtime-corejs3": "^7.8.3"
+            }
+        },
         "xtend": {
             "version": "4.0.2",
             "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
diff --git a/package.json b/package.json
index ed846fcff..f1b4be520 100644
--- a/package.json
+++ b/package.json
@@ -127,6 +127,7 @@
         "@types/uuid": "^3.4.6",
         "@types/webpack": "^4.41.3",
         "@types/word-extractor": "^0.3.0",
+        "@types/xregexp": "^4.3.0",
         "@types/youtube": "0.0.38",
         "adm-zip": "^0.4.13",
         "archiver": "^3.1.1",
@@ -258,6 +259,7 @@
         "word-extractor": "^0.3.0",
         "words-to-numbers": "^1.5.1",
         "xoauth2": "^1.2.0",
+        "xregexp": "^4.3.0",
         "youtube": "^0.1.0"
     }
 }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index d2106808e..3b5101a4d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -44,7 +44,6 @@ import MarqueeOptionsMenu from "./MarqueeOptionsMenu";
 import { MarqueeView } from "./MarqueeView";
 import React = require("react");
 import { CollectionViewType } from "../CollectionView";
-import { Script } from "vm";
 
 library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload);
 
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index d577068b7..b5af68ba1 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -35,10 +35,10 @@ import { WebBox } from "./WebBox";
 import { InkingStroke } from "../InkingStroke";
 import React = require("react");
 import { RecommendationsBox } from "../RecommendationsBox";
-
 import { TraceMobx } from "../../../new_fields/util";
 import { ScriptField } from "../../../new_fields/ScriptField";
-import ReactTable from "react-table";
+import XRegExp = require("xregexp");
+
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
 type BindingProps = Without<FieldViewProps, 'fieldKey'>;
@@ -59,13 +59,13 @@ const ObserverJsxParser: typeof JsxParser = ObserverJsxParser1 as any;
 interface HTMLtagProps {
     Document: Doc;
     htmltag: string;
+    onClick?: ScriptField;
 }
-//"<HTMLdiv borderRadius='100px' onClick={() => this.bannerColor=this.bannerColor==='red'?'green':'red'} width='100%' height='100%' transform='rotate({2*this.x+this.y}deg)' backgroundColor='{this.bannerColor}'><ImageBox {...props} fieldKey={'data'}/><HTMLspan width='100%'  marginTop='50%'  height='10%'  position='absolute' backgroundColor='green'>{this.title}</HTMLspan></HTMLdiv>"
-@observer
+//"<HTMLdiv borderRadius='100px' onClick={this.bannerColor=this.bannerColor==='red'?'green':'red'} width='100%' height='100%' transform='rotate({2*this.x+this.y}deg)'><ImageBox {...props} fieldKey={'data'}/><HTMLspan width='100%'  marginTop='50%'  height='10%'  position='absolute' backgroundColor='{this.bannerColor===`green`?`dark`:`light`}grey'>{this.title}</HTMLspan></HTMLdiv>"@observer
 export class HTMLtag extends React.Component<HTMLtagProps> {
     click = (e: React.MouseEvent) => {
-        const clickScript = (this.props as any).onClick;
-        ScriptField.MakeScript(clickScript, { self: Doc.name, this: Doc.name })?.script.run({ this: this.props.Document });
+        const clickScript = (this.props as any).onClick as Opt<ScriptField>;
+        clickScript?.script.run({ this: this.props.Document });
     }
     render() {
         const style: { [key: string]: any } = {};
@@ -129,11 +129,12 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
         return Doc.expandTemplateLayout(template, this.props.Document, params ? "(" + params + ")" : this.props.layoutKey);
     }
 
-    CreateBindings(): JsxBindings {
+    CreateBindings(onClick: Opt<ScriptField>): JsxBindings {
         const list = {
             ...OmitKeys(this.props, ['parentActive'], (obj: any) => obj.active = this.props.parentActive).omit,
             Document: this.layoutDoc,
             DataDoc: this.dataDoc,
+            onClick: onClick
         };
         return { props: list };
     }
@@ -141,6 +142,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
     render() {
         TraceMobx();
         let layoutFrame = this.layout;
+
         // replace code content with a script  >{content}<   as in  <HTMLdiv>{this.title}</HTMLdiv>
         const replacer = (match: any, expr: string, offset: any, string: any) => {
             return ">" + (ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name })?.script.run({ this: this.props.Document }).result as string || "") + "<";
@@ -159,9 +161,21 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
         };
         layoutFrame = layoutFrame.replace(/<\/HTML([a-zA-Z0-9_-]+)/g, replacer3);
 
+        // add onClick function to props
+        const splits = layoutFrame.split("onClick=");
+        let onClick: Opt<ScriptField>;
+        if (splits.length > 1) {
+            const code = XRegExp.matchRecursive(splits[1], "{", "}", "", { valueNames: ["between", "left", "match", "right", "between"] });
+            layoutFrame = splits[0] + " onClick={props.onClick} " + splits[1].substring(code[1].end + 1);
+            onClick = ScriptField.MakeScript(code[1].value, { this: Doc.name, self: Doc.name });
+        }
+
+        const bindings = this.CreateBindings(onClick);
+        //  layoutFrame = splits.length > 1 ? splits[0] + splits[1].replace(/{([^{}]|(?R))*}/, replacer4) : ""; // might have been more elegant if javascript supported recursive patterns
+
         return (this.props.renderDepth > 12 || !layoutFrame || !this.layoutDoc) ? (null) :
             this.props.forceLayout === "FormattedTextBox" && this.props.forceFieldKey ?
-                <FormattedTextBox {...this.CreateBindings().props} fieldKey={this.props.forceFieldKey} />
+                <FormattedTextBox {...bindings.props} fieldKey={this.props.forceFieldKey} />
                 :
                 <ObserverJsxParser
                     key={42}
@@ -174,7 +188,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
                         ColorBox, DashWebRTCVideo, LinkAnchorBox, InkingStroke, DocHolderBox, LinkBox, ScriptingBox,
                         RecommendationsBox, ScreenshotBox, HTMLtag
                     }}
-                    bindings={this.CreateBindings()}
+                    bindings={bindings}
                     jsx={layoutFrame}
                     showWarnings={true}
 
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index b0a49f750..e41b725b1 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -29,7 +29,7 @@ export interface SearchProps {
     filterQquery?: string;
     setSearchQuery: (q: string) => {};
     searchFileTypes: string[];
-    setSearchFileTypes: (types: string[]) => {}
+    setSearchFileTypes: (types: string[]) => {};
 }
 
 export enum Keys {
@@ -86,7 +86,7 @@ export class SearchBox extends React.Component<SearchProps> {
             this._searchString = this.props.searchQuery;
             this.submitSearch();
         }
-    })
+    });
 
 
     @action
-- 
cgit v1.2.3-70-g09d2

