///
const viewerUrl = chrome.runtime.getURL('override.html');
function getBodyTextContent(): string | null {
let result = '';
for (const n of document.body.childNodes) {
if (n.nodeType === Node.TEXT_NODE)
result += n.nodeValue;
else if (n.nodeName === 'PRE')
result += n.textContent;
else return null;
}
return result.trim();
}
function getTextContent(element: HTMLElement) {
let result = '';
if (!element)
return result;
const nodes = element.childNodes;
for (const n of nodes) {
if (n.nodeType === Node.TEXT_NODE)
result += n.nodeValue;
else if (!(n instanceof HTMLElement))
result += n.textContent;
else if (n.classList.contains('line-numbers-rows') || // ignore line number for HighlightJs
n.classList.contains('CodeMirror-linenumber'))
continue;
else
result += getTextContent(n);
}
return result;
}
function guessCodeElement(element: HTMLElement): HTMLElement {
let parentEl = findParentElement(element, el => el.classList.contains('CodeMirror-code'));
if (parentEl)
return parentEl;
parentEl = findParentElement(element, el => el.nodeName === 'CODE' || el.nodeName === 'PRE');
if (parentEl)
return parentEl;
parentEl = findParentElementByName(element, 'TD');
if (parentEl && parentEl.previousElementSibling && parentEl.previousElementSibling.hasAttribute('data-line-number')) { // github
const result = findParentElementByName(parentEl, 'TABLE');
if (result)
return result;
}
return element;
}
function findParentElement(element: HTMLElement, pred: (el: HTMLElement) => boolean): HTMLElement | null {
for (let el: HTMLElement | null = element; el; el = el.parentElement) {
if (pred(el))
return el;
}
return null;
}
function findParentElementByName(element: HTMLElement, name: string): HTMLElement | null {
return findParentElement(element, el => el.nodeName === name);
}
function couldSupport(str: string | null) {
if (!str)
return false;
for (let i = 0; i < 1000 && i < str.length; i++) {
if (' \n\r\t'.indexOf(str[i]) >= 0)
continue;
return str[i] === '[' || str[i] === '{';
}
return false;
}
let embeddedViewer: boolean;
let viewerLoaded: boolean;
let source: string;
function main() {
console.log('in main');
registerContextmenuListener();
const text = getBodyTextContent();
if (!couldSupport(text))
return;
source = text!;
embeddedViewer = true;
const originHtml = document.body.innerHTML;
document.body.innerHTML = `
${originHtml}
`;
window.addEventListener('message', (evt) => {
console.log(`evt.data.type:${evt.data.type}`);
if (evt.data.type === 'tdv-ready') {
setTdvData(evt.source as Window, getSource());
if (embeddedViewer)
showTreeDoc(true);
viewerLoaded = true;
} else if (evt.data.type === 'tdv-show-source') {
showTreeDoc(false);
}
});
}
// https://stackoverflow.com/questions/7703697/how-to-retrieve-the-element-where-a-contextmenu-has-been-executed
let clickedEl: HTMLElement | null = null;
function registerContextmenuListener() {
document.addEventListener('contextmenu', event => clickedEl = event.target as HTMLElement, true);
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request === 'getClickedEl')
sendResponse({value: (clickedEl as any).value});
else if (request === 'tdv-show-treedoc') {
if (viewerLoaded) // It's possible load fail due to CSP policy on the host page.
showTreeDoc(true);
else if (clickedEl && clickedEl.nodeName === 'A' && clickedEl.getAttribute('HREF')) {
const absolutionUrl = new URL(clickedEl.getAttribute('HREF')!, document.baseURI).href;
const win = window.open(`${viewerUrl}?dataUrl=${absolutionUrl}`, '_blank');
} else if (getSource()) {
const win = window.open(viewerUrl, '_blank');
// Not sure will target windows postMessage to opener stop working, for now, we use timer to push the data
// Which is not ideal.
setTimeout(() => setTdvData(win!, getSource()), 100);
} else {
console.warn('received tdv-show-treedoc event: but nothing is selected. Could due to selection in different frame. Event will be ignored');
}
}
});
}
function showTreeDoc(show: boolean) {
document.getElementById('iframe')!.style.visibility = show ? '' : 'hidden';
document.getElementById('originHtml')!.style.visibility = show ? 'hidden' : '';
}
function setTdvData(target: Window, data: any) {
target.postMessage({type: 'tdv-setData', data}, '*');
}
function getSource(): string | undefined {
const selection = window.getSelection()?.toString().trim();
if (selection && selection.length > 10)
return selection;
if (source != null)
return source;
else if (clickedEl)
return getTextContent(guessCodeElement(clickedEl)).trim();
else {
console.warn("Nothing is selected. Shouldn't happen");
return '';
}
}
main();