"
+ ""
+ " (⏏ )"
)
// 1st) replace External link
H = H.replace(/@\[(http.?[^\]]*)\]/g," [$1]")
// 2nd) replace relative (to page) link
H = H.replace(/@\[([^\]]*)\]/g," [$1]")
// Add support for inner links: '@[#internalId]'
H = H.replace(/@\[#([^\]]*)\]/g, "
[$1]
")
H = H.replace(/Gº([^º\n]*)º/g, " $1 ")
H = H.replace(/Rº([^º\n]*)º/g, " $1 ")
H = H.replace(/Bº([^º\n]*)º/g, " $1 ")
H = H.replace(/Oº([^º\n]*)º/g, " $1 ")
H = H.replace(/Qº([^º\n]*)º/g, " $1 ")
H = H.replace(/Yº([^º\n]*)º/g, " $1 ")
H = H.replace(/[$]º([^º\n]*)º/g, " $1")
H = H.replace(/_º([^º\n]*)º/g, "$1 ")
H = H.replace(/^º([^º\n]*)º/g, "$1 ")
H = H.replace( /º([^º\n]*)º/g, " $1 ")
H = H.replace( /[˂]/g, "<")
H = H.replace( /[˃]/g, ">")
H = H.replace( /[⅋]/g, "&")
// Some utf-8 hand icons do not work properly while editing in vim/terminal
// but looks much better in the final HTML. Replace icons:
H = H.replace(/☜/g, "👈")
H = H.replace(/☝/g, "👆")
H = H.replace(/☞/g, "👉")
H = H.replace(/☟/g, "👇")
H = H.replace(/[.]\n/g, ". ")
H = H.replace(/[:]\n/g, ": ")
H = H.replace(/\n\s*\n/g, "
")
N.innerHTML = H
// TODO:(enhancement) Add markdown table parser.
// REF: https://github.com/blattmann/mdtablesparser/blob/master/js/parser.js
}
},
}
const MB = { // Menu Bar
renderMenuBar : function (){
const searchDiv = document.createElement('div');
searchDiv.setAttribute("id", "upper_bar")
searchDiv.classList.add("noprint")
searchDiv.innerHTML = ''
+ ''
+ '❓'
+ 'Print'
+ '🔍︎'
+ ''
+ ' '
document.body.insertBefore(searchDiv,document.body.children[0])
document.getElementById("idLabelsFilter").addEventListener("click", SF.showSearchForm)
document.getElementById("idLabelsFilter").addEventListener("click", SF.showSearchForm)
ZC.slider = document.getElementById("zoomSlider" )
ZC.slider.addEventListener("input", ZC.onZoom )
{
// https://stackoverflow.com/questions/27116221/prevent-zoom-cross-browser
const meta = document.createElement('meta')
meta.setAttribute("name", "viewport")
meta.setAttribute("content", "width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no")
document.head.insertBefore(meta,document.head.children[0])
}
document.getElementById("printButton").addEventListener("click", MB.spbQuickPrint )
},
spbQuickPrint : function() {
if (window.confirm('Use browser [print...] for print-previsualization.-')) {
window.print()
}
},
onZoomClosed : function() { },
}
function switchLinksToBlankTarget() {
// Change default a.target to blank.
const nodeList = document.querySelectorAll('a')
const thisDoc=document.location.origin+document.location.pathname;
for (let idx in nodeList) {
const nodeHref = nodeList[idx].href
if (!nodeHref) { continue }
if (! (nodeHref.startsWith("http")) ) continue
if ( nodeHref.startsWith(thisDoc)) continue
nodeList[idx].target='_blank'
}
}
function pageLoadedEnd() {
let id=window.location.hash.replace("#","")
if (id=="") {
id = getParameterByName("id") || ""
}
if (!!id) {
const targetDom = document.getElementById(id)
if (!!targetDom) {
ZW.doOpenZoom(targetDom);
return;
}
}
// Parse query parameters
let csvLabels = getParameterByName("topics") || ""
csvLabels = csvLabels.toLowerCase()
let label_l = (!!csvLabels) ? csvLabels.split(",") : []
label_l.forEach(label => {
LM.onLabelClicked({ target : {value : label} });
})
let query = getParameterByName("query")
if (!!query) { SF.regexQuery = query; }
if (!!query || !!label_l) {
SE.executeSearch()
}
let doShowSearchMenu = getParameterByName("showSearchMenu") || ""
if (["0","false"].indexOf(doShowSearchMenu.toLowerCase())<0) {
SF.showSearchForm();
}
try {
postLoad();
} catch(err) {console.dir(err)}
}
function onPageLoaded() {
if ( window.spbLoaded ) {
console.log("Already loaded")
return
}
console.log("Initializing SPB")
window.spbLoaded = true
try { preLoad(); } catch(err) {console.dir(err)}
IC.initInputControl();
switchLinksToBlankTarget();
TPP.doTextPreProcessing()
ZC.initCSSIndexes()
LM.createLabelIndex()
ZW.renderZoomBox();
SF.renderSearchForm()
MB.renderMenuBar();
pageLoadedEnd()
}
window.addEventListener('load', onPageLoaded )
function getParameterByName(name, url) {
if (!url) url = window.location.href;
name = name.replace(/[\[\]]/g, '\\$&');
const regex = new RegExp('[?&]' + name + '(=([^]*)|&|#|$)'),
results = regex.exec(url);
if (!results) return null;
if (!results[2]) return '';
return decodeURIComponent(results[2].replace(/\+/g, ' '));
}
Array.prototype.union = function(a)
{
var r = this.slice(0);
a.forEach(function(i) { if (r.indexOf(i) < 0) r.push(i); });
return r;
};
Array.prototype.intersection = function(a)
{
var r = [];
var ref = this.slice(0);
a.forEach(function(i) { if (ref.indexOf(i) >= 0) r.push(i); });
return r;
};
const SE = { // (S)earch (E)ngine
searchAndMark : function (node, finalRegex) {
const htmlContent = (SF.singleLineMode)
? node.innerHTML
: node.innerHTML.replace(/\n/gm, ' ')
const searchFound = finalRegex.test(htmlContent)
// reset after search:
// REF: https://stackoverflow.com/questions/11477415/why-does-javascripts-regex-exec-not-always-return-the-same-value
finalRegex.lastIndex = 0;
node.setAttribute("textFound", searchFound?"true":"false")
if (searchFound) {
for (
let nodeI = node.parentElement ;
nodeI != null && nodeI != document.body ;
nodeI = nodeI.parentElement ) {
nodeI.setAttribute("textFound", "true")
}
NAV.visited.push(node)
window.lastElementFound = node
}
return searchFound
},
executeSearch : function(query) {
const unhideButton = document.getElementById("unhide");
unhideButton.setAttribute("hidden","");
if (typeof query != "string") query = "";
if (!!query) { SF.regexQuery = query; }
let finalQueryRegex = SF.regexQuery.replace(/ +/g,".*");
SE.resetTextFoundAttr(false);
const isEmptyQuery = /^\s*$/.test(finalQueryRegex)
if (SF.fullWordMode) {
finalQueryRegex = "\\b"+finalQueryRegex+"\\b"
}
if ((!LM.isAnyLabelSelected()) && isEmptyQuery) { return false; /* Nothing to do */ }
[document.querySelectorAll('div[group]' ),
document.querySelectorAll('div[groupv]')].forEach(nodeList => {
nodeList.forEach(node => {
node.setAttribute("textFound", "false")
})
})
// If some label has been selected then choose only those with matching labels
document.querySelectorAll('*[zoom]').forEach(node => {
node.setAttribute("textFound", "false")
})
var innerZoom_l = []
if (LM.isAnyLabelSelected()) {
let label_l=Object.keys(LM.state.labelMapSelected)
innerZoom_l = LM.getDomListForLabelPrefix(label_l[0]);
for (let idx=0; idx;]?(" + finalQueryRegex + ")", regexFlags)
var numberOfMatches = 0
NAV.visited=[]
var foundElement = false
for (let idx2 in innerZoom_l) {
const node = innerZoom_l[idx2]
if (false/* true => change node background in debug mode */) {
node.setAttribute("textFound", "debug")
}
if (node.innerHTML == null) continue
if (!node.setAttribute ) continue
if (SE.searchAndMark(node,finalRegex)) {
numberOfMatches++
foundElement = true
}
}
if (numberOfMatches == 1) {
ZW.doOpenZoom(lastElementFound);
}
let sMatchText = "no matches"
if (numberOfMatches > 0) {
sMatchText = numberOfMatches + " found"
document.getElementById("searchForm").setAttribute("resultFound","true")
setTimeout(()=>{ document.getElementById("searchForm").removeAttribute("resultFound") } , 4500 )
}
document.getElementById("matchNumber").innerHTML = sMatchText
unhideButton.removeAttribute("hidden","");
return false // avoid event propagation
},
resetTextFoundAttr : function(bKeepHighlightedSearch) {
/*
* bKeepHighlightedSearch = true: => Do not reset textFound== true attribute
* (keep highlighted content in the context of the full page)
* Just remove textFound=false to 'unhide' non-matching zoomable content
* (textFound==false is assigned to display none in css)
* bKeepHighlightedSearch = false => Reset all (remove any textFound attribute)
*/
const removeNodeList = document.querySelectorAll('*[textFound]');
if (removeNodeList.length == 0) return; // Nothing to do.
for (let idx in removeNodeList) {
if (!removeNodeList[idx].setAttribute) continue; // <- Umm: works fine at page-load, fails in following searchs
if (bKeepHighlightedSearch && removeNodeList[idx].getAttribute("textFound") == "true") continue;
removeNodeList[idx].removeAttribute("textFound");
}
}
}