main.js 8.4 KB

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