]> BookStack Code Mirror - website/blob - themes/bookstack/static/libs/webidx.js
Updated hacks
[website] / themes / bookstack / static / libs / webidx.js
1 // Taken from https://github.com/gbxyz/webidx/tree/main
2 // BSD 3-Clause License
3 // Copyright (c) 2024, Gavin Brown
4 // Full license: https://github.com/gbxyz/webidx/blob/a28a984d38fd546d1bec4d6a4a5a47ab86cb08f8/LICENSE
5 // Note: File has been modified since originally copied.
6
7 window.webidx = {};
8 const webidx = window.webidx;
9
10 webidx.search = async function (params) {
11   if (!webidx.sql) {
12     //
13     // initialise sql.js
14     //
15     webidx.sql = await window.initSqlJs({locateFile: file => `/libs/${file}`});
16   }
17
18   if (webidx.hasOwnProperty('db')) {
19     return webidx.query(params.query);
20   } else if (webidx.loadingPromise) {
21     await webidx.loadingPromise;
22     await new Promise((res, rej) => setTimeout(res, 10));
23     return webidx.query(params.query);
24   } else {
25     webidx.loadingPromise = webidx.loadDB(params);
26
27     webidx.loadingPromise.then(() => {
28       webidx.loadingPromise = null;
29     });
30
31     return webidx.loadingPromise
32   }
33 };
34
35 webidx.loadDB = function (params) {
36   return new Promise((res, rej) => {
37     var xhr = new XMLHttpRequest();
38
39     xhr.open('GET', params.dbfile);
40     xhr.timeout = params.timeout ?? 5000;
41     xhr.responseType = 'arraybuffer';
42
43     xhr.ontimeout = function() {
44       rej('Unable to load index, please refresh the page.');
45     };
46
47     xhr.onload = function() {
48       webidx.initializeDB(this.response);
49       const results = webidx.query(params.query);
50       res(results);
51     };
52
53     xhr.send();
54   });
55 };
56
57 webidx.initializeDB = function (arrayBuffer) {
58   webidx.db = new webidx.sql.Database(new Uint8Array(arrayBuffer));
59
60   //
61   // prepare statements
62   //
63   webidx.wordQuery  = webidx.db.prepare("SELECT `id` FROM `words` WHERE (`word`=:word)");
64   webidx.idxQuery   = webidx.db.prepare("SELECT `page_id` FROM `index` WHERE (`word`=:word)");
65   webidx.pageQuery  = webidx.db.prepare("SELECT `url`,`title` FROM `pages` WHERE (`id`=:id)");
66 };
67
68 webidx.getWordID = function (word) {
69   webidx.wordQuery.bind([word]);
70   webidx.wordQuery.step();
71   var word_id = webidx.wordQuery.get().shift();
72
73   webidx.wordQuery.reset();
74
75   return word_id;
76 };
77
78 webidx.getPagesHavingWord = function (word_id) {
79   var pages = [];
80
81   webidx.idxQuery.bind([word_id]);
82
83   while (webidx.idxQuery.step()) {
84     pages.push(webidx.idxQuery.get().shift());
85   }
86
87   webidx.idxQuery.reset();
88
89   return pages;
90 };
91
92 webidx.getPage = function (page_id) {
93   webidx.pageQuery.bind([page_id]);
94
95   webidx.pageQuery.step();
96
97   var page = webidx.pageQuery.getAsObject();
98
99   webidx.pageQuery.reset();
100
101   return page;
102 };
103
104 webidx.query = function (query) {
105   //
106   // split the search term into words
107   //
108   var words = query.toLowerCase().split(" ");
109
110   //
111   // this array maps page ID to rank
112   //
113   var pageRank = [];
114
115   //
116   // iterate over each word
117   //
118   while (words.length > 0) {
119     var word = words.shift();
120
121     var invert = false;
122     if (0 == word.indexOf("-")) {
123       invert = true;
124       word = word.substring(1);
125     }
126
127     var word_id = webidx.getWordID(word);
128
129     //
130     // if the word isn't present, ignore it
131     //
132     if (word_id) {
133       var pages = webidx.getPagesHavingWord(word_id);
134
135       pages.forEach(function (page_id) {
136         if (invert) {
137           if (pageRank[page_id]) {
138             pageRank[page_id] -= 65535;
139
140           } else {
141             pageRank[page_id] = -65535;
142             
143           }
144
145         } else {
146           if (pageRank[page_id]) {
147             pageRank[page_id]++;
148
149           } else {
150             pageRank[page_id] = 1;
151
152           }
153         }
154       });
155     }
156   }
157
158   //
159   // transform the results into a format that can be sorted
160   //
161   var sortedPages = [];
162
163   pageRank.forEach(function (rank, page_id) {
164     if (rank > 0) {
165       sortedPages.push({rank: rank, page_id: page_id});
166     }
167   })
168
169   //
170   // sort the results in descending rank order
171   //
172   sortedPages.sort(function(a, b) {
173     return b.rank - a.rank;
174   });
175
176   //
177   // this will be populated with the actual pages
178   //
179   var pages = [];
180
181   //
182   // get page data for each result
183   //
184   sortedPages.forEach(function(result) {
185     pages.push(webidx.getPage(result.page_id));
186   });
187
188   return pages;
189 };
190
191 webidx.regExpQuote = function (str) {
192   return str.replace(/[/\-\\^$*+?.()|[\]{}]/g, '\\$&');
193 };