aboutsummaryrefslogtreecommitdiff
path: root/src/client/util/SearchUtil.ts
blob: 733eae5f485613edc617a11bcdcd15131b6eb602 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
import { ObservableMap } from 'mobx';
import { Doc, DocListCast, Field, FieldType, Opt } from '../../fields/Doc';
import { Id } from '../../fields/FieldSymbols';
import { StrCast } from '../../fields/Types';
import { DocumentType } from '../documents/DocumentTypes';
import { DocOptions, FInfo } from '../documents/Documents';

export namespace SearchUtil {
    export type HighlightingResult = { [id: string]: { [key: string]: string[] } };

    export function SearchCollection(collectionDoc: Opt<Doc>, queryIn: string, matchKeyNames: boolean, onlyKeys?: string[]) {
        const blockedTypes = [DocumentType.PRESELEMENT, DocumentType.CONFIG, DocumentType.KVP, DocumentType.FONTICON, DocumentType.BUTTON, DocumentType.SCRIPTING];
        const blockedKeys = matchKeyNames
            ? []
            : Object.entries(DocOptions)
                  .filter(([, info]: [string, FInfo]) => !info?.searchable())
                  .map(([key]) => key);

        const exact = queryIn.startsWith('=');
        const query = queryIn.toLowerCase().split('=').lastElement();

        const results = new ObservableMap<Doc, string[]>();
        if (collectionDoc) {
            const docs = DocListCast(collectionDoc[Doc.LayoutFieldKey(collectionDoc)]);
            // eslint-disable-next-line @typescript-eslint/ban-types
            const docIDs: String[] = [];
            SearchUtil.foreachRecursiveDoc(docs, (depth: number, doc: Doc) => {
                const dtype = StrCast(doc.type) as DocumentType;
                if (dtype && !blockedTypes.includes(dtype) && !docIDs.includes(doc[Id]) && depth >= 0) {
                    const hlights = new Set<string>();
                    (onlyKeys ?? SearchUtil.documentKeys(doc)).forEach(
                        key =>
                            (val => (exact ? val === query.toLowerCase() : val.includes(query.toLowerCase())))(
                            matchKeyNames ? key : Field.toString(doc[key] as FieldType)) 
                        && hlights.add(key)
                    ); // prettier-ignore
                    blockedKeys.forEach(key => hlights.delete(key));

                    if (Array.from(hlights.keys()).length > 0) {
                        results.set(doc, Array.from(hlights.keys()));
                    }
                }
                docIDs.push(doc[Id]);
            });
        }
        return results;
    }
    /**
     * @param {Doc} doc - doc for which keys are returned
     *
     * This method returns a list of a document doc's keys.
     */
    export function documentKeys(doc: Doc) {
        const keys: { [key: string]: boolean } = {};
        Doc.GetAllPrototypes(doc).map(proto =>
            Object.keys(proto).forEach(key => {
                keys[key] = false;
            })
        );
        return Array.from(Object.keys(keys));
    }

    /**
     * @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.
     */
    export function foreachRecursiveDoc(docsIn: Doc[], func: (depth: number, doc: Doc) => void) {
        let docs = docsIn;
        let newarray: Doc[] = [];
        let depth = 0;
        const visited: Doc[] = [];
        while (docs.length > 0) {
            newarray = [];
            // eslint-disable-next-line no-loop-func
            docs.filter(d => d && !visited.includes(d)).forEach(d => {
                visited.push(d);
                const fieldKey = Doc.LayoutFieldKey(d);
                const annos = !Field.toString(Doc.LayoutField(d) as FieldType).includes('CollectionView');
                const data = d[annos ? fieldKey + '_annotations' : fieldKey];
                data && newarray.push(...DocListCast(data));
                const sidebar = d[fieldKey + '_sidebar'];
                sidebar && newarray.push(...DocListCast(sidebar));
                func(depth, d);
            });
            docs = newarray;
            depth++;
        }
    }
}