index.html 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836
  1. <!--
  2. Copyright (C) 2024 Richard Hao Cao
  3. Ebrowser is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
  4. Ebrowser is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  5. You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
  6. -->
  7. <!DOCTYPE html><html><head><meta charset="UTF-8">
  8. <style>
  9. html{
  10. height: 100%;
  11. overflow: hidden;
  12. }
  13. body{
  14. display: flex;
  15. flex-direction: column;
  16. height: 100%;
  17. margin-top: 1px;
  18. }
  19. div.webviews{
  20. display: flex;
  21. flex-direction: column;
  22. flex-grow:1;
  23. }
  24. webview{display: none;width:100%;height:100%}
  25. .curWV{display: inherit !important;}
  26. .autocomplete-active {
  27. background-color: DodgerBlue !important;
  28. color: #ffffff;
  29. }
  30. .invis{display: none}
  31. /*the container must be positioned relative:*/
  32. .autocomplete {
  33. position: relative;
  34. display: inline-block;
  35. width:100%;
  36. }
  37. .autocomplete-items {
  38. position: absolute;
  39. border: 1px solid #d4d4d4;
  40. border-bottom: none;
  41. border-top: none;
  42. z-index: 99;
  43. /*position the autocomplete items to be the same width as the container:*/
  44. top: 100%;
  45. left: 0;
  46. right: 0;
  47. }
  48. .autocomplete-items div {
  49. cursor: pointer;
  50. background-color: #fff;
  51. }
  52. .autocomplete-items div:hover {
  53. background-color: #e9e9e9;
  54. }
  55. </style>
  56. <script>
  57. const { ipcRenderer } = require('electron');
  58. const fs = require('fs');
  59. const path = require('path');
  60. const readline = require('readline');
  61. var iTab = 0;
  62. var tabs;
  63. var engines = {};
  64. var mapKeys = {};
  65. var closedUrls = [];
  66. var autocStrArray = [];
  67. var defaultSE = "https://www.bing.com/search?q=%s";
  68. var historyFile = path.join(__dirname,'history.rec');
  69. var bHistory = false;
  70. var bQueryHistory = false;
  71. let sitecssP = path.join(__dirname,"sitecss");
  72. let sitejsP = path.join(__dirname,"sitejs");
  73. let gjsA = [];
  74. let gcssA = [];
  75. let gcssJSA = [];
  76. var bDomainJS = fs.existsSync(sitejsP);
  77. var bDomainCSS = fs.existsSync(sitecssP);
  78. var autocMode = 0; //0 for substring, 1 for startsWith
  79. const BML_head = "(async ()=>{let d=document;async function _loadJs(u){var a=d.createElement('script');a.type='text/javascript';a.async=false;a.src=u;d.body.appendChild(a);await new Promise(resolve=>a.onload=resolve)}";
  80. const BML_tail = "})()";
  81. const BML_md = BML_head + "await _loadJs('https://cdn.jsdelivr.net/npm/marked@12.0.2/marked.min.js');let b=d.body;b.innerHTML=marked.parse(b.textContent)})()";
  82. let lastKeys;
  83. let lastKeys_millis = 0;
  84. var lastVal;
  85. fs.readFile(path.join(__dirname,'search.json'), 'utf8', (err, jsonString) => {
  86. if (err) {
  87. coloncommand(":js fetch2file(repositoryurl,'search.json')");
  88. return;
  89. }
  90. initSearchEngines(jsonString,false);
  91. });
  92. fs.readFile(path.join(__dirname,'mapkeys.json'), 'utf8', (err, jsonStr) => {
  93. if (err) {
  94. coloncommand(":js fetch2file(repositoryurl,'mapkeys.json')");
  95. return;
  96. }
  97. try {
  98. mapKeys = JSON.parse(jsonStr);
  99. }catch(e){}
  100. });
  101. appendAutoc_rec(path.join(__dirname,'default.autoc'),null);
  102. appendAutoc_rec(path.join(__dirname,'bookmark.rec'),' ');
  103. function initSearchEngines(jsonStr){
  104. try{
  105. engines=JSON.parse(jsonStr);
  106. let match = jsonStr.match(/:"([^"]+)"/);
  107. if(match) defaultSE=match[1];
  108. }catch(e){}
  109. }
  110. function save(filePath, u8array){
  111. //alert(Object.prototype.toString.call(u8array))
  112. fs.writeFile (filePath, u8array, (err) => {
  113. if (err) {
  114. console.error (err);
  115. return;
  116. }
  117. });
  118. }
  119. function print2PDF(filePath, options){
  120. tabs.children[iTab].printToPDF(options)
  121. .then(u8array=>save(filePath,u8array));
  122. }
  123. function showHtml(html){
  124. let js = "document.documentElement.innerHTML=`"+html+"`;";
  125. let t=tabs.children[iTab];
  126. t.dataset.jsonce=js;
  127. t.src="about:blank";
  128. }
  129. function bookmark(argrest){//b [filenamestem] :bookmark
  130. let bmFileName = "bookmark.rec";
  131. let tab = tabs.children[iTab];
  132. let url = tab.getURL();
  133. if(argrest)
  134. bmFileName = argrest+".rec";
  135. let title = tab.getTitle();
  136. let line = title + " " + url + "\n";
  137. fs.appendFile(path.join(__dirname,bmFileName), line, (err)=>{});
  138. }
  139. function switchTab(i){
  140. let tab = tabs.children[iTab];
  141. if(document.activeElement == tab) tab.blur();
  142. tab.classList.remove('curWV');
  143. iTab = i;
  144. tabs.children[iTab].classList.add('curWV');
  145. }
  146. async function loadJSFile(tab,jsF){
  147. try {
  148. let js = await fs.promises.readFile(jsF,'utf8');
  149. tab.executeJavaScript(js,false);
  150. }catch(e){}
  151. }
  152. async function loadCSSFile(tab,jsF){
  153. try {
  154. let js = await fs.promises.readFile(jsF,'utf8');
  155. tab.insertCSS(css);
  156. }catch(e){}
  157. }
  158. function cbStartLoading(e){
  159. if(!bDomainCSS) return;
  160. let tab = e.target;
  161. let domain;
  162. try{
  163. domain = new URL(tab.getURL()).hostname;
  164. }catch(e){return;}
  165. let jsF = path.join(sitecssP, domain+".js");
  166. loadJSFile(tab,jsF);
  167. jsF = path.join(sitecssP, domain+".css");
  168. loadCSSFile(tab,jsF);
  169. gcssA.forEach((fname)=>{
  170. jsF = path.join(__dirname, fname);
  171. loadCSSFile(tab,jsF);
  172. });
  173. gcssJSA.forEach((fname)=>{
  174. jsF = path.join(__dirname, fname);
  175. loadJSFile(tab,jsF);
  176. });
  177. }
  178. function cbNavigate(e){
  179. let url = e.url;
  180. if(58===url.charCodeAt(1)){
  181. e.preventDefault();
  182. if(confirm("Proceed to execute risky operations: "+url))
  183. internalLink(url);
  184. else
  185. document.forms[0].q.value = url;
  186. }
  187. }
  188. function cbFinishLoad(e){
  189. let tab = e.target;
  190. let url = tab.getURL();
  191. if(bHistory){
  192. let histItem = tab.getTitle()+" "+url+"\n";
  193. fs.appendFile(historyFile, histItem, (err) => {});
  194. }
  195. let js = tab.dataset.jsonce;
  196. if(js){
  197. tab.dataset.jsonce = null;
  198. tab.executeJavaScript(js,false);
  199. }
  200. if(bDomainJS){
  201. let domain = new URL(url).hostname;
  202. let jsF = path.join(sitejsP, domain+".js");
  203. loadJSFile(tab,jsF);
  204. }
  205. gjsA.forEach((fname)=>{
  206. let jsF = path.join(__dirname, fname);
  207. loadJSFile(tab,jsF);
  208. });
  209. }
  210. function initTab(tab){
  211. tab.allowpopups = true;
  212. tab.addEventListener('did-finish-load',cbFinishLoad);
  213. tab.addEventListener('did-start-loading',cbStartLoading);
  214. tab.addEventListener('will-navigate',cbNavigate);
  215. }
  216. function newTab(){
  217. var tab = document.createElement('webview');
  218. initTab(tab);
  219. tabs.appendChild(tab);
  220. }
  221. function tabInc(num){
  222. let nTabs = tabs.children.length;
  223. if(nTabs<2) return;
  224. let i = iTab +num;
  225. if(i>=nTabs) i=0;
  226. switchTab(i);
  227. }
  228. function tabDec(num){
  229. let nTabs = tabs.children.length;
  230. if(nTabs<2) return;
  231. let i = iTab +num;
  232. if(i<0) i=nTabs-1;
  233. switchTab(i);
  234. }
  235. function tabClose(){
  236. let nTabs = tabs.children.length;
  237. if(nTabs<2) return "";//no remain tab
  238. let tab = tabs.children[iTab];
  239. closedUrls.push(tab.getURL());
  240. if(document.activeElement == tab) tab.blur();
  241. tabs.removeChild(tab);
  242. nTabs--;
  243. if(iTab>=nTabs) iTab=iTab-1;
  244. tabs.children[iTab].classList.add('curWV');
  245. return getWinTitle();
  246. }
  247. function tabJS(js){
  248. tabs.children[iTab].executeJavaScript(js,false);
  249. }
  250. function getWinTitle(){
  251. let t=tabs.children[iTab];
  252. let title = (iTab+1) + '/' + tabs.children.length;
  253. try{title=title+' '+t.getTitle()+' '+t.getURL()}catch(e){}
  254. return title
  255. }
  256. async function appendAutoc_rec(filename, delimit){
  257. try{
  258. const readInterface = readline.createInterface ({
  259. input: fs.createReadStream (filename, 'utf8'),
  260. });
  261. for await (const line of readInterface) {
  262. let iS;
  263. if(delimit && (iS=line.lastIndexOf(delimit))>0){
  264. autocStrArray.push(line.substring(iS+1));
  265. }else
  266. autocStrArray.push(line);
  267. }
  268. lastVal = null; //trigger full search
  269. }catch(e){return;}
  270. }
  271. function keyPress(e){
  272. var inputE = document.forms[0].q;
  273. if (e.altKey||e.metaKey)
  274. return;
  275. var key = e.key;
  276. if(e.ctrlKey){
  277. switch(key){
  278. case "Home":
  279. tabJS("window.scrollTo(0,0)");
  280. return;
  281. case "End":
  282. tabJS("window.scrollTo(0,document.body.scrollHeight)");
  283. return;
  284. }
  285. return;
  286. }
  287. SCROLL: do {
  288. let h = -32;
  289. switch(key){
  290. case " ":
  291. if(inputE === document.activeElement) return;
  292. if(e.shiftKey){
  293. h = -3*document.documentElement.clientHeight/4;
  294. break;
  295. }
  296. case "PageDown":
  297. h = 3*document.documentElement.clientHeight/4;
  298. break;
  299. case "PageUp":
  300. h = -3*document.documentElement.clientHeight/4;
  301. break;
  302. case "ArrowDown":
  303. h = 32;
  304. case "ArrowUp":
  305. if(inputE === document.activeElement &&
  306. 0!==inputE.nextElementSibling.children.length)
  307. return;
  308. break;
  309. default:
  310. break SCROLL;
  311. }
  312. let js = `javascript:window.scrollBy(0,${h})`;
  313. tabJS(js);
  314. return;
  315. }while(false);
  316. if(inputE === document.activeElement){
  317. if (9===e.keyCode){
  318. e.preventDefault();
  319. tabs.children[iTab].focus();
  320. }
  321. return;
  322. }
  323. var curMillis = Date.now();
  324. if(curMillis-lastKeys_millis>1000)
  325. lastKeys = null;
  326. lastKeys_millis = curMillis;
  327. switch (key) {
  328. case "!":
  329. case "/":
  330. case ":":
  331. inputE.value = "";
  332. inputE.focus();
  333. lastKeys = null;
  334. return;
  335. }
  336. lastKeys = !lastKeys ? key : lastKeys + key;
  337. let cmds = mapKeys[lastKeys];
  338. if(cmds){//try to run cmds
  339. let keyLen = lastKeys.length;
  340. setTimeout(()=>{
  341. if(lastKeys.length != keyLen) return;
  342. lastKeys = null;
  343. handleQueries(cmds);
  344. }, 500);
  345. }
  346. }
  347. function getQ(){return document.forms[0].q.value;}
  348. function bang(query, iSpace){
  349. let se=defaultSE;
  350. {
  351. let name = query.slice(0,iSpace);
  352. let engine = engines[name];
  353. if(engine){
  354. se = engine;
  355. query = query.substring(iSpace+1);
  356. }
  357. }
  358. return se.replace('%s',query);
  359. }
  360. function coloncommand(q){
  361. ipcRenderer.send("command",q);
  362. }
  363. function coloncommand_render(cmd){
  364. const delimiter = /\s+/;
  365. let arg0;
  366. let argrest = null;
  367. const match = delimiter.exec(cmd);
  368. if(match){
  369. const sIndex = match.index;
  370. const eIndex = sIndex + match[0].length;
  371. arg0 = cmd.substring(1,sIndex);
  372. argrest = cmd.substring(eIndex);
  373. }else
  374. arg0 = cmd.substring(1);
  375. switch(arg0){
  376. case "ac":
  377. autoc(argrest);
  378. return;
  379. case "b":
  380. bookmark(argrest);
  381. return;
  382. case "bjs":
  383. eval(argrest);
  384. return;
  385. case "bml":
  386. bangcommand(argrest,0);
  387. return;
  388. case "pdf":
  389. savePdf(argrest);
  390. return;
  391. }
  392. }
  393. function autoc(argrest){
  394. if(!argrest) return;
  395. let fpath = path.join(__dirname,argrest);
  396. let fname = fpath;
  397. let delimit = ' ';
  398. if (!fs.existsSync(fname)){
  399. fname = fpath+".autoc";
  400. if (!fs.existsSync(fname))
  401. fname = fpath+".rec";
  402. else
  403. delimit = null;
  404. }
  405. appendAutoc_rec(fname,delimit);
  406. }
  407. function bml(args){
  408. if(2!=args.length) return;
  409. let filename = args[1]+".js";
  410. fs.readFile(path.join(__dirname,filename), 'utf8', (err,str) => {
  411. if (err) return;
  412. tabs.children[iTab].executeJavaScript(str,false);
  413. });
  414. }
  415. function savePdf(argrest){
  416. let filename = "ebrowser.pdf";
  417. let options = {};
  418. if(argrest){
  419. let c0 = argrest.charCodeAt(0);
  420. let iOpts = 0;
  421. if(123!=c0){//not '{' options then it is filename
  422. let iS = argrest.indexOf(' ');
  423. let filestem = argrest;
  424. if(iS>0) {
  425. filestem = argrest.substring(0,iS);
  426. iOpts = iS+1;
  427. }else
  428. iOpts = argrest.length;
  429. filename = filestem + ".pdf";
  430. }
  431. if(argrest.length>iOpts){//:Pdf [filename] {...}
  432. if(iOpts+2==argrest.length){// '{}'
  433. let width = document.body.clientWidth/96;
  434. tabs.children[iTab].executeJavaScript("document.documentElement.scrollHeight",
  435. false).then((h)=>{
  436. let opts = {
  437. printBackground:true,
  438. pageSize:{width:width,height:h/96}};
  439. print2PDF(filename,opts);
  440. });
  441. return;
  442. }else{
  443. try {
  444. options = JSON.parse(argrest.substring(iOpts));
  445. }catch(e){};
  446. }
  447. }
  448. }
  449. print2PDF(filename,options);
  450. }
  451. function bangcommand(q,offset){
  452. let iS = q.indexOf(' ',offset);
  453. if(iS<0) iS=q.length;
  454. let fname = q.substring(offset,iS);
  455. let fpath = path.join(__dirname,fname+'.js');
  456. if (fs.existsSync(fpath)) {
  457. fs.readFile(fpath, 'utf8',(err, js)=>{
  458. if (err) {
  459. console.log(err);
  460. return;
  461. }
  462. const prefix = "(function(){";
  463. const postfix = "})(`";
  464. const end ="`)";
  465. const fjs = `${prefix}${js}${postfix}${q}${end}`;
  466. tabs.children[iTab].executeJavaScript(fjs,false);
  467. });
  468. }
  469. }
  470. function recQueryHistory(q){
  471. if(bQueryHistory)
  472. fs.appendFile(path.join(__dirname,"history.autoc"), q, (err)=>{});
  473. }
  474. function handleQuery(q){
  475. if(q.length>1){
  476. let c0=q.charCodeAt(0);
  477. let c1=q.charCodeAt(1);
  478. switch(c0){
  479. case 33://"!"
  480. if(33!=c1)
  481. coloncommand(q);
  482. else
  483. bangcommand(q,2);
  484. recQueryHistory(q);
  485. return;
  486. case 47://"/"
  487. tabs.children[iTab].findInPage(q.substring(1));
  488. return;
  489. case 58://':'
  490. if(c1>98 && 112!=c1)
  491. coloncommand(q);
  492. else
  493. coloncommand_render(q);
  494. recQueryHistory(q);
  495. return;
  496. }
  497. if(58===c1){
  498. internalLink(q);
  499. return;
  500. }
  501. }
  502. var url=q;
  503. NOREC: do{
  504. do {
  505. if(q.length>12){
  506. let c6 = q.charCodeAt(6);
  507. if(47===c6){// '/'
  508. let c5 = q.charCodeAt(5);
  509. if(47===c5 && 58===q.charCodeAt(4))//http/file urls
  510. break NOREC;
  511. if(58===c5 && 47===q.charCodeAt(7))//https://
  512. break NOREC;
  513. }else if(q.startsWith("javascript:")){
  514. tabs.children[iTab].executeJavaScript(q.substring(11),false);
  515. recQueryHistory(q);
  516. return;
  517. }else if(q.startsWith("view-source:")) break;
  518. else if(q.startsWith("data:")) break;
  519. }
  520. let iS = q.indexOf(' ');
  521. if(iS<0){
  522. if(q.length>5 && 58===q.charCodeAt(5)){// about:
  523. break;
  524. }
  525. if(q.indexOf('.')>0){
  526. url = 'https://'+q;
  527. break NOREC;
  528. }
  529. url = defaultSE.replace('%s',q);
  530. break;
  531. }
  532. url = bang(q, iS);
  533. if(58===url.charCodeAt(1)){
  534. internalLink(url);
  535. recQueryHistory(q);
  536. return;
  537. }
  538. }while(false);
  539. recQueryHistory(q);
  540. }while(false);
  541. tabs.children[iTab].src=url;
  542. }
  543. function internalLink(url){
  544. let cmd = url.charCodeAt(2);
  545. let subcmd = url.charCodeAt(3);
  546. switch(cmd){
  547. case 48://'0' i:0
  548. {
  549. let iColon = url.indexOf(':',5);
  550. let name = decodeURIComponent(url.slice(4,iColon));
  551. let rurl = url.substring(iColon+1);
  552. switch(subcmd){
  553. case 47://'/' i:0/
  554. if(106===url.charCodeAt(4) && 115===url.charCodeAt(5) && 47===url.charCodeAt(6)){
  555. //i:0/js/xx:[url]
  556. let fname = name;
  557. let pname = path.join(__dirname,fname);
  558. if(fs.existsSync(pname)){
  559. (async ()=>{
  560. try {
  561. let js = await fs.promises.readFile(pname,'utf8');
  562. let t=tabs.children[iTab];
  563. t.dataset.jsonce=js;
  564. t.src = rurl;
  565. }catch(e){}
  566. })();
  567. }
  568. }
  569. return;
  570. case 48: //i:00
  571. {
  572. let endS;
  573. let is = url.indexOf('%s',iColon+1);
  574. if(is<0)
  575. endS = '%s"\n';
  576. else
  577. endS = '"\n';
  578. let pname = path.join(__dirname,"search.json");
  579. let str = '"'+name+'":"'+rurl+
  580. endS;
  581. jsonAppend(pname,125,str);
  582. }
  583. return;
  584. case 49: //i:01
  585. {
  586. let pname = path.join(__dirname,"menu.json");
  587. let str = '"'+name+'",":bjs handleQuery(`'+
  588. rurl+'${tabs.children[iTab].getURL()}`)"\n';
  589. jsonAppend(pname,93,str);
  590. }
  591. return;
  592. case 50: //i:02
  593. {
  594. let pname = path.join(__dirname,"uas.json");
  595. let str = '"'+name+'":"'+rurl+'"\n';
  596. jsonAppend(pname,125,str);
  597. }
  598. return;
  599. default:
  600. }
  601. }
  602. return;
  603. case 56: //i:8
  604. switch(subcmd){
  605. case 100: //i:8d[url] to download
  606. tabs.children[iTab].downloadURL(url.substring(4));
  607. return;
  608. }
  609. return;
  610. case 112: //i:p[url]#[querystring] for http POST
  611. {
  612. let iQ = url.indexOf('#',12);
  613. let data;
  614. let rurl;
  615. let headers;
  616. switch(subcmd){
  617. case 49: //i:p1
  618. {
  619. if(iQ>4) {
  620. rurl = url.substring(4,iQ);
  621. data = [{
  622. type: 'rawData',
  623. bytes: Buffer.from(url.substring(iQ+1))
  624. }];
  625. }else{
  626. rurl = url.substring(4);
  627. data = [];
  628. }
  629. headers = 'Content-Type: application/x-www-form-urlencoded';
  630. break;
  631. }
  632. case 50: //i:p2[url]#[filePath] post request with local file
  633. rurl = url.substring(4,iQ);
  634. data = [{type: 'file', filePath:url.substring(iQ+1)}];
  635. headers = 'Content-Type: multipart/form-data';
  636. break;
  637. default:
  638. return;
  639. }
  640. tabs.children[iTab].loadURL(rurl,{postData:data,extraHeaders:headers});
  641. return;
  642. }
  643. case 113: //i:q for multiple queries
  644. handleQueries(url.substring(3));
  645. return;
  646. }
  647. }
  648. function handleQueries(cmds){
  649. for(var cmd of cmds.split("\n"))
  650. handleQuery(cmd);
  651. }
  652. async function jsonAppend(filePath, charcode, str){
  653. let fd;
  654. try{
  655. fd = await fs.promises.open(filePath, 'r+');
  656. }catch(e){
  657. try {
  658. fd = await fs.promises.open(filePath, 'w+');
  659. }catch(e1){return}
  660. }
  661. try{
  662. const stats = await fd.stat();
  663. const fileSize = stats.size;
  664. const buffer = Buffer.alloc(1);
  665. let position = fileSize-1;
  666. while (position >= 0) {
  667. await fd.read(buffer, 0, 1, position);
  668. if (buffer[0] === charcode) break;
  669. position--;
  670. }
  671. let endS = String.fromCharCode(charcode);
  672. if(position<0){//re-write whole file
  673. str = String.fromCharCode(charcode-2)+str+endS;
  674. position = 0;
  675. }else
  676. str = ","+str+endS;
  677. await fd.truncate(position);
  678. const buf = Buffer.from(str);
  679. await fd.write(buf, 0, buf.length, position);
  680. await fd.close();
  681. }catch(e){console.log(e)}
  682. }
  683. function autocomplete(inp,container,arr) {
  684. var currentFocus;
  685. function clickItem(e){inp.value = arr[e.target.dataset.index];}
  686. function appendElement(el,dataindex){
  687. el.dataset.index = dataindex;
  688. el.addEventListener("click", clickItem);
  689. container.appendChild(el);
  690. }
  691. function itemInnerHTML(b,str,val,iStr){
  692. b.innerHTML = str.substr(0,iStr);
  693. b.innerHTML += "<strong>" + str.substr(iStr, val.length) + "</strong>";
  694. b.innerHTML += str.substr(iStr+val.length);
  695. }
  696. inp.addEventListener("input", function(e) {
  697. const MAXITEMS = 30;
  698. var b, i, val = this.value;
  699. if (!val) { return false;}
  700. currentFocus = -1;
  701. let items = container.children;
  702. let iBase = 0;
  703. let lastV = lastVal;
  704. lastVal = val;
  705. if(val.startsWith(lastV)){
  706. let itemsLen = items.length;
  707. if(itemsLen<=0) return;
  708. i = itemsLen -1;
  709. iBase = items[i].dataset.index +1;
  710. switch(autocMode){
  711. case 0:
  712. for (; i>=0; i--) {
  713. b = items[i];
  714. let str = arr[b.dataset.index];
  715. let iStr = str.indexOf(val);
  716. if(iStr<0) {
  717. b.parentNode.removeChild(b);
  718. continue;
  719. }
  720. itemInnerHTML(b,str,val,iStr);
  721. }
  722. break;
  723. case 1:
  724. for (; i>=0; i--) {
  725. b = items[i];
  726. let str = arr[b.dataset.index];
  727. let oLen = lastval.length;
  728. if(!str.startsWith(val.substring(oLen),oLen)) {
  729. b.parentNode.removeChild(b);
  730. continue;
  731. }
  732. itemInnerHTML(b,str,val,0);
  733. }
  734. break;
  735. }
  736. if(itemsLen<MAXITEMS)
  737. return;
  738. else
  739. if(container.children.length>=MAXITEMS) return;
  740. }else
  741. closeAllLists();
  742. i = iBase;
  743. switch(autocMode){
  744. case 0:
  745. for (; i < arr.length; i++) {
  746. let iStr = arr[i].indexOf(val);
  747. if(iStr<0) continue;
  748. {
  749. b = document.createElement("DIV");
  750. itemInnerHTML(b,arr[i],val,iStr);
  751. appendElement(b,i);
  752. if(container.children.length>=MAXITEMS) break;
  753. }
  754. }
  755. return;
  756. case 1://startsWith
  757. for (; i < arr.length; i++) {
  758. if (arr[i].startsWith(val)) {
  759. b = document.createElement("DIV");
  760. itemInnerHTML(b,arr[i],val,0);
  761. appendElement(b,i);
  762. if(container.children.length>=MAXITEMS) break;
  763. }
  764. }
  765. }
  766. });
  767. inp.addEventListener("keydown", function(e) {
  768. var x = container.getElementsByTagName("div");
  769. if (0===x.length) return false;
  770. if (e.keyCode == 40) {//downarrow
  771. currentFocus++;
  772. addActive(x);
  773. } else if (e.keyCode == 38) { //up
  774. currentFocus--;
  775. addActive(x);
  776. } else if (e.keyCode == 13) {
  777. if (currentFocus > -1) {
  778. e.preventDefault();
  779. if (x) x[currentFocus].click();
  780. currentFocus = -1;
  781. }
  782. closeAllLists();
  783. lastVal = null;
  784. }
  785. });
  786. function addActive(x) {
  787. removeActive(x);
  788. if (currentFocus >= x.length) currentFocus = 0;
  789. if (currentFocus < 0) currentFocus = (x.length - 1);
  790. x[currentFocus].classList.add("autocomplete-active");
  791. }
  792. function removeActive(x) {
  793. for (var i = 0; i < x.length; i++) {
  794. x[i].classList.remove("autocomplete-active");
  795. }
  796. }
  797. function closeAllLists() {
  798. container.innerHTML = '';
  799. }
  800. inp.addEventListener("blur", function () {
  801. setTimeout(()=>container.classList.add("invis"),200);
  802. });
  803. inp.addEventListener("focus", function () {
  804. container.classList.remove("invis");
  805. });
  806. }
  807. </script>
  808. </head>
  809. <body>
  810. <form class="autocomplete" autocomplete="off" action="javascript:handleQuery(getQ())">
  811. <input type="text" name=q style="width:100%" autofocus>
  812. <div class="autocomplete-items"></div>
  813. </form>
  814. <div class="webviews">
  815. <webview class="curWV" allowpopups></webview>
  816. </div>
  817. <script>
  818. tabs = document.body.children[1];
  819. initTab(tabs.children[0]);
  820. {
  821. let inp = document.forms[0].q;
  822. autocomplete(inp,inp.nextElementSibling,autocStrArray);
  823. }
  824. document.addEventListener('keydown', keyPress);
  825. </script>
  826. </body></html>