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.
5 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.
7 You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
9 <!DOCTYPE html
><html><head><meta charset=
"UTF-8">
17 flex-direction: column;
23 flex-direction: column;
26 webview{display: none;width:
100%;height:
100%}
27 .curWV{display: inherit !important;}
28 .autocomplete-active {
29 background-color: DodgerBlue !important;
33 /*the container must be positioned relative:*/
36 display: inline-block;
41 border:
1px solid #d4d4d4;
45 /*position the autocomplete items to be the same width as the container:*/
50 .autocomplete-items div {
52 background-color: #fff;
54 .autocomplete-items div:hover {
55 background-color: #e9e9e9;
59 const fs
= require('fs');
60 const path
= require('path');
61 const readline
= require('readline');
67 var autocStrArray
= [];
68 var defaultSE
= "https://www.bing.com/search?q=%s";
69 var historyFile
= path
.join(__dirname
,'history.rec');
71 var bQueryHistory
= false;
72 var autocMode
= 0; //0 for substring, 1 for startsWith
73 const JSPREFIX_LOAD
= "(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)}";
74 const BML_md
= JSPREFIX_LOAD
+ "await _loadJs('https://cdn.jsdelivr.net/npm/marked@12.0.2/marked.min.js');let b=d.body;b.innerHTML=marked.parse(b.textContent)})()";
77 let lastKeys_millis
= 0;
79 fs
.readFile(path
.join(__dirname
,'search.json'), 'utf8', (err
, jsonString
) => {
81 coloncommand(":js fetch2file(repositoryurl,'search.json')");
84 initSearchEngines(jsonString
,false);
86 fs
.readFile(path
.join(__dirname
,'mapkeys.json'), 'utf8', (err
, jsonStr
) => {
88 coloncommand(":js fetch2file(repositoryurl,'mapkeys.json')");
92 mapKeys
= JSON
.parse(jsonStr
);
95 appendAutoc_rec(path
.join(__dirname
,'default.autoc'),null);
96 appendAutoc_rec(path
.join(__dirname
,'bookmark.rec'),' ');
98 function initSearchEngines(jsonStr
){
101 engines
=JSON
.parse(jsonStr
, (key
, value
)=>{
102 if(!val1st
&& !(/^\d+$/u.test(key
))) val1st
=value
;
105 if(val1st
) defaultSE
=val1st
;
108 function save(filePath
, u8array
){
109 //alert(Object.prototype.toString.call(u8array))
110 fs
.writeFile (filePath
, u8array
, (err
) => {
117 function print2PDF(filePath
, options
){
118 tabs
.children
[iTab
].printToPDF(options
)
119 .then(u8array
=>save(filePath
,u8array
));
121 function bookmark(args
){//b [filenamestem] url title :bookmark
122 let bmFileName
= "bookmark.rec";
123 let tab
= tabs
.children
[iTab
];
124 let url
= tab
.getURL();
126 bmFileName
= args
[1]+".rec";
127 let title
= tab
.getTitle();
128 let line
= title
+ " " + url
+ "\n";
129 fs
.appendFile(path
.join(__dirname
,bmFileName
), line
, (err
)=>{});
131 function switchTab(i
){
132 let tab
= tabs
.children
[iTab
];
133 if(document
.activeElement
== tab
) tab
.blur();
134 tab
.classList
.remove('curWV');
136 tabs
.children
[iTab
].classList
.add('curWV');
138 function cbFinishLoad(e
){
140 let js
= tab
.dataset
.jsonce
;
142 tab
.dataset
.jsonce
= null;
143 tab
.executeJavaScript(js
,false);
145 if(!bHistory
) return;
146 let histItem
= tab
.getTitle()+" "+tab
.getURL()+"\n";
147 fs
.appendFile(historyFile
, histItem
, (err
) => {});
149 function initTab(tab
){
150 tab
.allowpopups
= true;
151 tab
.addEventListener('did-finish-load',cbFinishLoad
);
154 var tab
= document
.createElement('webview');
156 tabs
.appendChild(tab
);
158 function tabInc(num
){
159 let nTabs
= tabs
.children
.length
;
165 function tabDec(num
){
166 let nTabs
= tabs
.children
.length
;
173 let nTabs
= tabs
.children
.length
;
174 if(nTabs
<2) return "";//no remain tab
175 let tab
= tabs
.children
[iTab
];
176 closedUrls
.push(tab
.getURL());
177 if(document
.activeElement
== tab
) tab
.blur();
178 tabs
.removeChild(tab
);
180 if(iTab
>=nTabs
) iTab
=iTab
-1;
181 tabs
.children
[iTab
].classList
.add('curWV');
182 return getWinTitle();
185 tabs
.children
[iTab
].executeJavaScript(js
,false);
187 function getWinTitle(){
188 let t
=tabs
.children
[iTab
];
189 let title
= (iTab
+1) + '/' + tabs
.children
.length
;
190 try{title
=title
+' '+t
.getTitle()+' '+t
.getURL()}catch(e
){}
193 async
function appendAutoc_rec(filename
, delimit
){
195 const readInterface
= readline
.createInterface ({
196 input
: fs
.createReadStream (filename
, 'utf8'),
199 for await (const line
of readInterface
) {
201 if(delimit
&& (iS
=line
.lastIndexOf(delimit
))>0){
202 autocStrArray
.push(line
.substring(iS
+1));
204 autocStrArray
.push(line
);
208 function keyPress(e
){
209 var inputE
= document
.forms
[0].q
;
210 if (e
.altKey
||e
.metaKey
)
216 tabJS("window.scrollTo(0,0)");
219 tabJS("window.scrollTo(0,document.body.scrollHeight)");
229 if(inputE
=== document
.activeElement
) return;
231 h
= -3*document
.documentElement
.clientHeight
/4;
235 h
= 3*document
.documentElement
.clientHeight
/4;
238 h
= -3*document
.documentElement
.clientHeight
/4;
243 if(inputE
=== document
.activeElement
&&
244 0!==inputE
.nextElementSibling
.children
.length
)
250 let js
= `javascript:window.scrollBy(0,${h})`;
255 if(inputE
=== document
.activeElement
){
256 if (9===e
.keyCode
){//tab completion
260 var curMillis
= Date
.now();
261 if(curMillis
-lastKeys_millis
>1000)
263 lastKeys_millis
= curMillis
;
274 lastKeys
= !lastKeys
? key
: lastKeys
+ key
;
275 let cmds
= mapKeys
[lastKeys
];
276 if(cmds
){//try to run cmds
277 let keyLen
= lastKeys
.length
;
279 if(lastKeys
.length
!= keyLen
) return;
281 for(var cmd
of cmds
.split("\n"))
286 function getQ(){return document
.forms
[0].q
.value
;}
287 function bang(query
, iSpace
){
290 let name
= query
.slice(0,iSpace
);
291 let engine
= engines
[name
];
294 query
= query
.substring(iSpace
+1);
297 return se
.replace('%s',query
);
299 function coloncommand(q
){
302 function coloncommand_render(cmd
){
303 args
= cmd
.substring(1).split(/\s+/);
323 function autoc(args
){
324 if(2!=args
.length
) return;
325 let fpath
= path
.join(__dirname
,args
[1]);
328 if (!fs
.existsSync(fname
)){
329 fname
= fpath
+".autoc";
330 if (!fs
.existsSync(fname
))
331 fname
= fpath
+".rec";
335 appendAutoc_rec(fname
,delimit
);
338 if(2!=args
.length
) return;
339 let filename
= args
[1]+".js";
340 fs
.readFile(path
.join(__dirname
,filename
), 'utf8', (err
,str
) => {
342 tabs
.children
[iTab
].executeJavaScript(str
,false);
345 function savePdf(args
){
346 let filename
= "ebrowser.pdf";
349 let c0
= args
[1].charCodeAt(0);
351 if(123!=c0
){//not '{' options then it is filename
352 filename
= args
[1] + ".pdf";
355 if(i
==args
.length
-1){//:Pdf [filename] {...}
356 if(2==args
[i
].length
){// '{}'
357 let width
= document
.body
.clientWidth
/96;
358 tabs
.children
[iTab
].executeJavaScript("document.documentElement.scrollHeight",
361 printBackground
:true,
362 pageSize
:{width
:width
,height
:h
/96}};
363 print2PDF(filename
,opts
);
368 options
= JSON
.parse(args
[i
]);
373 print2PDF(filename
,options
);
375 function bangcommand(q
){
376 let iS
= q
.indexOf(' ',1);
377 if(iS
<0) iS
=q
.length
;
378 let fname
= q
.substring(1,iS
);
379 let fpath
= path
.join(__dirname
,fname
+'.js');
380 if (fs
.existsSync(fpath
)) {
381 fs
.readFile(fpath
, 'utf8',(err
, js
)=>{
386 const prefix
= "(function(){";
387 const postfix
= "})(`";
389 const fjs
= `${prefix}${js}${postfix}${q}${end}`;
394 function recQueryHistory(q
){
396 fs
.appendFile(path
.join(__dirname
,"history.autoc"), q
, (err
)=>{});
398 function handleQuery(q
){
400 let c0
=q
.charCodeAt(0);
407 tabs
.children
[iTab
].findInPage(q
.substring(1));
410 let c1
=q
.charCodeAt(1);
414 coloncommand_render(q
);
423 let c6
= q
.charCodeAt(6);
425 let c5
= q
.charCodeAt(5);
426 if(47===c5
&& 58===q
.charCodeAt(4))//http/file urls
428 if(58===c5
&& 47===q
.charCodeAt(7))//https://
430 }else if(q
.startsWith("javascript:")){
431 tabs
.children
[iTab
].executeJavaScript(q
.substring(11),false);
434 }else if(q
.startsWith("view-source:")) break;
435 else if(q
.startsWith("data:")) break;
437 let iS
= q
.indexOf(' ');
439 if(q
.length
>5 && 58===q
.charCodeAt(5)){// about:
442 if(q
.indexOf('.')>0){
446 url
= defaultSE
.replace('%s',q
);
453 tabs
.children
[iTab
].src
=url
;
455 function autocomplete(inp
,container
,arr
) {
457 function clickItem(e
){inp
.value
= e
.target
.dataset
.str
;}
458 function appendElement(el
,dataindex
){
459 el
.dataset
.str
= arr
[dataindex
];
460 el
.addEventListener("click", clickItem
);
461 container
.appendChild(el
);
463 inp
.addEventListener("input", function(e
) {
465 var b
, i
, val
= this.value
;
467 if (!val
) { return false;}
471 for (i
= 0; i
< arr
.length
; i
++) {
472 let iStr
= arr
[i
].indexOf(val
);
475 b
= document
.createElement("DIV");
476 b
.innerHTML
= arr
[i
].substr(0,iStr
);
477 b
.innerHTML
+= "<strong>" + arr
[i
].substr(iStr
, val
.length
) + "</strong>";
478 b
.innerHTML
+= arr
[i
].substr(iStr
+val
.length
);
480 if(container
.children
.length
>MAXITEMS
) break;
485 for (i
= 0; i
< arr
.length
; i
++) {
486 if (arr
[i
].substr(0, val
.length
) === val
) {
487 b
= document
.createElement("DIV");
488 b
.innerHTML
= "<strong>" + arr
[i
].substr(0, val
.length
) + "</strong>";
489 b
.innerHTML
+= arr
[i
].substr(val
.length
);
491 if(container
.children
.length
>MAXITEMS
) break;
496 inp
.addEventListener("keydown", function(e
) {
497 var x
= container
.getElementsByTagName("div");
498 if (0===x
.length
) return false;
499 if (e
.keyCode
== 40) {//downarrow
502 } else if (e
.keyCode
== 38) { //up
505 } else if (e
.keyCode
== 13) {
506 if (currentFocus
> -1) {
508 if (x
) x
[currentFocus
].click();
514 function addActive(x
) {
516 if (currentFocus
>= x
.length
) currentFocus
= 0;
517 if (currentFocus
< 0) currentFocus
= (x
.length
- 1);
518 x
[currentFocus
].classList
.add("autocomplete-active");
520 function removeActive(x
) {
521 for (var i
= 0; i
< x
.length
; i
++) {
522 x
[i
].classList
.remove("autocomplete-active");
525 function closeAllLists() {
526 container
.innerHTML
= '';
528 inp
.addEventListener("blur", function () {
529 setTimeout(()=>container
.classList
.add("invis"),200);
531 inp
.addEventListener("focus", function () {
532 container
.classList
.remove("invis");
538 <form class=
"autocomplete" autocomplete=
"off" action=
"javascript:handleQuery(getQ())">
539 <input type=
"text" name=q
style=
"width:100%" autofocus
>
540 <div class=
"autocomplete-items"></div>
542 <div class=
"webviews">
543 <webview class=
"curWV" allowpopups
></webview>
546 tabs
= document
.body
.children
[1];
547 initTab(tabs
.children
[0]);
549 let inp
= document
.forms
[0].q
;
550 autocomplete(inp
,inp
.nextElementSibling
,autocStrArray
);
552 document
.addEventListener('keydown', keyPress
);