main.js 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. import init, {
  2. initialize,
  3. generate_public_parameters,
  4. generate_query,
  5. decode_response
  6. } from '../pkg/client.js';
  7. import './bz2.js';
  8. import './wtf_wikipedia.js';
  9. import './wtf-plugin-html.js';
  10. wtf.extend(wtfHtml);
  11. const API_URL = "";
  12. const SETUP_URL = "/setup";
  13. const QUERY_URL = "/query";
  14. async function postData(url = '', data = {}, json = false) {
  15. const response = await fetch(url, {
  16. method: 'POST',
  17. mode: 'cors',
  18. cache: 'no-store',
  19. credentials: 'omit',
  20. headers: {
  21. 'Content-Type': 'application/octet-stream',
  22. 'Content-Length': data.length
  23. },
  24. redirect: 'follow',
  25. referrerPolicy: 'no-referrer',
  26. body: data
  27. });
  28. if (json) {
  29. return response.json();
  30. } else {
  31. let data = await response.arrayBuffer();
  32. return new Uint8Array(data);
  33. }
  34. }
  35. async function getData(url = '', json = false) {
  36. const response = await fetch(url, {
  37. method: 'GET',
  38. cache: 'default',
  39. credentials: 'omit',
  40. redirect: 'follow',
  41. referrerPolicy: 'no-referrer'
  42. });
  43. if (json) {
  44. return response.json();
  45. } else {
  46. let data = await response.arrayBuffer();
  47. return new Uint8Array(data);
  48. }
  49. }
  50. const api = {
  51. setup: async (data) => postData(API_URL + SETUP_URL, data, true),
  52. query: async (data) => postData(API_URL + QUERY_URL, data, false)
  53. }
  54. function extractTitle(article) {
  55. var title = "";
  56. var endTitleTagIdx = article.indexOf("</title>");
  57. if (endTitleTagIdx != -1) {
  58. title = article.slice(0, endTitleTagIdx);
  59. }
  60. return title;
  61. }
  62. function preprocessWikiText(wikiText, targetTitle) {
  63. targetTitle = targetTitle.toLowerCase();
  64. let articles = wikiText.split("<title>")
  65. .filter(d => d.length > 10)
  66. .filter(d => {
  67. return extractTitle(d).toLowerCase() == targetTitle;
  68. });
  69. if (articles.length === 0) {
  70. console.log("error decoding...");
  71. return "";
  72. }
  73. let d = articles[0];
  74. let title = extractTitle(d);
  75. let articlePageMatch = d.match(/<text>/);
  76. if (!articlePageMatch) {
  77. console.log("error decoding...");
  78. return "";
  79. }
  80. let startPageContentIdx = articlePageMatch.index + articlePageMatch[0].length;
  81. let endPageContentIdx = d.slice(startPageContentIdx).indexOf("</text>")
  82. d = d.slice(startPageContentIdx, endPageContentIdx);
  83. d = d
  84. .replace(/&lt;ref[\s\S]{0,500}?&lt;\/ref&gt;/gi, "")
  85. .replace(/&lt;ref[\s\S]{0,500}?\/&gt;/gi, "")
  86. .replace(/&lt;ref&gt;[\s\S]{0,500}?&lt;\/ref&gt;/gi, "")
  87. .replace(/&lt;![\s\S]{0,500}?--&gt;/gi, "");
  88. return {
  89. "wikiText": d,
  90. "title": title
  91. };
  92. }
  93. function postProcessWikiHTML(wikiHTML, title) {
  94. wikiHTML = wikiHTML.replace(/<img.*?\/>/g, "");
  95. wikiHTML = "<h2 class=\"title\">"+title+"</h2>" + wikiHTML
  96. return wikiHTML;
  97. }
  98. function resultToHtml(result, title) {
  99. let decompressedData = bz2.decompress(result);
  100. let wikiText = new TextDecoder("utf-8").decode(decompressedData);
  101. let processedData = preprocessWikiText(wikiText, title);
  102. let wikiHTML = wtf(processedData.wikiText).html();
  103. wikiHTML = postProcessWikiHTML(wikiHTML, processedData.title);
  104. return "<article>" + wikiHTML + "</article>";
  105. }
  106. window.resultToHtml = resultToHtml;
  107. function addBold(suggestion, query) {
  108. return '<span class="highlight">'
  109. + suggestion.slice(0,query.length)
  110. + "</span>"
  111. + suggestion.slice(query.length);
  112. }
  113. function showSuggestionsBox(suggestions, query) {
  114. var htmlSuggestions = '<div class="suggestions">'
  115. + suggestions.map(m => "<div>"+addBold(m, query)+"</div>").join('')
  116. + "</div>";
  117. document.querySelector('.searchbox').insertAdjacentHTML('afterend', htmlSuggestions);
  118. document.querySelectorAll('.suggestions > div').forEach((el) => {
  119. el.onclick = (e) => {
  120. document.querySelector(".searchbox").value = el.innerHTML
  121. .replace('<span class="highlight">', '')
  122. .replace('</span>', '');
  123. clearExistingSuggestionsBox();
  124. document.querySelector('#make_query').click();
  125. }
  126. });
  127. }
  128. function clearExistingSuggestionsBox() {
  129. var existing = document.querySelector('.suggestions');
  130. if (existing) {
  131. existing.remove();
  132. }
  133. }
  134. function hasTitle(title) {
  135. return title && window.title_index.hasOwnProperty(title) && window.title_index[title] < window.numArticles;
  136. }
  137. function followRedirects(title) {
  138. if (hasTitle(title)) {
  139. return title;
  140. } else if (window.redirects.hasOwnProperty(title) && hasTitle(window.redirects[title])) {
  141. return window.redirects[title];
  142. } else {
  143. return null;
  144. }
  145. }
  146. function queryTitleOnClick(title) {
  147. return async (e) => {
  148. e.preventDefault();
  149. queryTitle(title);
  150. return false;
  151. }
  152. }
  153. function enableLinks(element) {
  154. element.querySelectorAll('a').forEach((el) => {
  155. var linkTitle = el.getAttribute("href").slice(2).replace(/_/g, " ").toLowerCase();
  156. if (hasTitle(linkTitle)) {
  157. el.onclick = queryTitleOnClick(linkTitle);
  158. } else {
  159. var redirected = followRedirects(linkTitle);
  160. if (redirected !== null && hasTitle(redirected)) {
  161. el.onclick = queryTitleOnClick(redirected);
  162. } else {
  163. el.classList.add("broken")
  164. }
  165. }
  166. })
  167. }
  168. async function query(targetIdx, title) {
  169. if (!window.hasSetUp) {
  170. console.log("Initializing...");
  171. window.client = initialize();
  172. console.log("done");
  173. console.log("Generating public parameters...");
  174. let publicParameters = generate_public_parameters(window.client);
  175. console.log(`done (${publicParameters.length} bytes)`);
  176. console.log("Sending public parameters...");
  177. let setup_resp = await api.setup(new Blob([publicParameters.buffer]));
  178. console.log("sent.");
  179. console.log(setup_resp);
  180. window.id = setup_resp["id"];
  181. window.hasSetUp = true;
  182. }
  183. console.log("Generating query... ("+targetIdx+")");
  184. let query = generate_query(window.client, window.id, targetIdx);
  185. console.log(`done (${query.length} bytes)`);
  186. console.log("Sending query...");
  187. let response = await api.query(query);
  188. console.log("sent.");
  189. console.log(`done, got (${response.length} bytes)`);
  190. console.log(response);
  191. console.log("Decoding result...");
  192. let result = decode_response(window.client, response)
  193. console.log("done.")
  194. console.log("Final result:")
  195. console.log(result);
  196. let resultHtml = resultToHtml(result, title);
  197. let outputArea = document.getElementById("output");
  198. outputArea.innerHTML = resultHtml;
  199. enableLinks(outputArea);
  200. }
  201. async function queryTitle(targetTitle) {
  202. let redirectedTitle = followRedirects(targetTitle);
  203. let articleIndex = window.title_index[redirectedTitle];
  204. return await query(articleIndex, targetTitle);
  205. }
  206. async function run() {
  207. await init();
  208. window.numArticles = 65536;
  209. window.articleSize = 100000;
  210. let makeQueryBtn = document.querySelector('#make_query');
  211. let searchBox = document.querySelector(".searchbox");
  212. let title_index_p = getData("data/enwiki-20220320-index.json", true);
  213. let redirect_backlinks_p = getData("data/redirects-old.json", true);
  214. window.title_index = await title_index_p;
  215. let keys = Object.keys(window.title_index);
  216. for (var i = 0; i < keys.length; i++) {
  217. let key = keys[i];
  218. window.title_index[key] /= window.articleSize;
  219. window.title_index[key.toLowerCase()] = window.title_index[key];
  220. }
  221. let redirect_backlinks = await redirect_backlinks_p;
  222. keys = Object.keys(redirect_backlinks);
  223. window.redirects = {}
  224. for (var i = 0; i < keys.length; i++) {
  225. let redirect_dest = keys[i];
  226. let redirect_srcs = redirect_backlinks[redirect_dest];
  227. for (var j = 0; j < redirect_srcs.length; j++) {
  228. window.redirects[redirect_srcs[j].toLowerCase()] = redirect_dest;
  229. }
  230. }
  231. searchBox.addEventListener('input', (e) => {
  232. clearExistingSuggestionsBox();
  233. let search = e.target.value;
  234. if (search.length < 1) return;
  235. var matching = Object.keys(window.title_index).filter((v) => v.startsWith(search));
  236. if (matching.length == 0) return;
  237. matching.sort();
  238. if (matching.length > 10) matching = matching.slice(0, 10);
  239. showSuggestionsBox(matching, search);
  240. })
  241. makeQueryBtn.onclick = async () => {
  242. makeQueryBtn.disabled = true;
  243. await queryTitle(searchBox.value);
  244. makeQueryBtn.disabled = false;
  245. }
  246. }
  247. run();