/// 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();