1 import { create, insert, insertBatch, search } from '@lyrasearch/lyra';
2 import { map } from 'lodash';
3 import { OutlineNode } from 'outline';
4 import keyboardJS from 'keyboardjs';
5 import {isVisible} from 'dom';
9 <div class="modal-content" id="search">
10 <input type="text" id="search-query" placeholder="enter fuzzy search terms">
11 <ul id="search-results">
20 state: 'ready' | 'notready'
24 this.state = 'notready';
27 async createIndex(schema: Record<string, any>) {
28 this.db = await create({
35 keyboardJS.withContext('search', () => {
36 keyboardJS.bind('escape', e => {
37 document.querySelector('.modal').remove();
38 keyboardJS.setContext('navigation');
41 keyboardJS.bind('down', e => {
43 document.getElementById('search-query').blur();
44 const el = document.querySelector('.search-result.selected');
45 if(el.nextElementSibling) {
46 el.classList.remove('selected');
47 el.nextElementSibling.classList.add('selected');
48 if(!isVisible(el.nextElementSibling as HTMLElement)) {
49 el.nextElementSibling.scrollIntoView();
54 keyboardJS.bind('up', e => {
56 const el = document.querySelector('.search-result.selected');
57 if(el.previousElementSibling) {
58 el.classList.remove('selected');
59 el.previousElementSibling.classList.add('selected');
60 if(!isVisible(el.previousElementSibling as HTMLElement)) {
61 el.previousElementSibling.scrollIntoView();
66 keyboardJS.bind('enter', e => {
67 const el = document.querySelector('.search-result.selected');
68 const docId = el.getAttribute('data-id');
70 document.querySelector('.modal').remove();
71 keyboardJS.setContext('navigation');
73 if(this.onTermSelection) {
74 this.onTermSelection(docId);
79 keyboardJS.withContext('navigation', () => {
80 keyboardJS.bind('shift + f', e => {
84 document.querySelector('body').innerHTML += searchModal;
85 const el = document.getElementById('search-query');
87 el.addEventListener('keyup', this.debounceSearch.bind(this));
88 keyboardJS.setContext('search');
93 debounceSearch(e: KeyboardEvent) {
95 clearInterval(this.debounce);
98 const el = e.target as HTMLTextAreaElement;
99 const query = el.value.toString().trim();
102 this.debounce = setTimeout(() => {
103 this.displaySearch(query, e);
108 async displaySearch(terms: string, e: KeyboardEvent) {
112 const res = await this.search(terms);
114 const resultContainer = document.getElementById('search-results');
116 if(res.hits.length === 0) {
117 resultContainer.innerHTML = '<li><em>No Results</em></li>';
121 const html = res.hits.map((doc, idx) => {
122 const content = doc.document.content.toString();
123 const display = content.substring(0, 100);
126 <li class="search-result ${idx === 0 ? 'selected' : ''}" data-id="${doc.id}">${display}${content.length > display.length ? '...': ''}</li>
130 resultContainer.innerHTML = html.join("\n");
133 indexDoc(doc: Record<string, any>) {
134 return insert(this.db, doc)
137 indexBatch(docs: Record<string, OutlineNode>) {
138 return insertBatch(this.db, map(docs, doc => doc as any));
141 search(term: string) {
142 return search(this.db, {
144 properties: ["content"]