/* globals engine */
'use strict';
// Tests => PDF, discarded tab, about:blank, chrome://extensions/, google, webstore
const pdfjsLib = window['pdfjs-dist/build/pdf'];
pdfjsLib.GlobalWorkerOptions.workerSrc = './parser/pdf.worker.js';
const args = new URLSearchParams(location.search);
document.body.dataset.mode = args.get('mode');
let ready = false;
let docs = 0;
let aid;
const arrange = () => {
clearTimeout(aid);
aid = setTimeout(arrange.do, 100);
};
arrange.do = () => {
const es = [...document.querySelectorAll('.result')];
const vs = es.filter(e => e.getBoundingClientRect().y > 5);
es.forEach((e, c) => {
const n = e.querySelector('[data-id="number"]');
const v = vs.length - es.length + c + 1;
n.textContent = '#' + v;
n.dataset.count = v;
});
};
// keep tabs
const cache = {};
const index = (tab, scope = 'both', options = {}) => {
const od = {
body: '',
date: new Date(document.lastModified).toISOString().split('T')[0].replace(/-/g, ''),
description: '',
frameId: 0,
keywords: '',
lang: 'english',
mime: 'text/html',
title: tab.title,
url: tab.url,
top: true
};
return Promise.race([new Promise(resolve => {
chrome.scripting.executeScript({
target: {
tabId: tab.id,
allFrames: true
},
files: ['/data/collect.js']
}).catch(() => []).then(arr => {
chrome.runtime.lastError;
arr = (arr || []).filter(a => a && a.result).map(a => a.result);
arr = (arr && arr.length ? arr : [od]).map(o => {
o.title = o.title || tab.title;
return o;
});
// support parsing PDF files
let parse = false;
if (options['parse-pdf'] === true) {
if (arr && tab.url && (arr[0].mime === 'application/pdf' || tab.url.indexOf('.pdf') !== -1)) {
if (scope === 'both' || scope === 'body') {
parse = true;
}
}
}
if (parse) {
pdfjsLib.getDocument(tab.url).promise.then(pdf => {
return Promise.all(Array.from(Array(pdf.numPages)).map(async (a, n) => {
const page = await pdf.getPage(n + 1);
const content = await page.getTextContent();
return content.items.map(s => s.str).join('') + '\n\n' +
content.items.map(s => s.str).join('\n');
})).then(a => a.join('\n\n')).then(c => {
arr[0].body = c;
arr[0].pdf = true;
resolve(arr);
});
}).catch(e => {
console.warn('Cannot parse PDF document', tab.url, e);
resolve(arr);
});
}
else {
resolve(arr);
}
});
}), new Promise(resolve => setTimeout(() => {
resolve([od]);
}, options['fetch-timeout']))]).then(async arr => {
try {
arr = arr.filter(a => a && (a.title || a.body));
for (const o of arr) {
o.lang = engine.language(o.lang);
o.title = o.title || tab.title || cache[tab.id].title;
if (o.title) {
cache[tab.id].title = o.title;
}
const favIconUrl = tab.favIconUrl || o.favIconUrl || cache[tab.id].favIconUrl;
if (favIconUrl) {
cache[tab.id].favIconUrl = o.title;
}
if (scope === 'body') {
o.title = '';
}
else if (scope === 'title') {
o.body = '';
}
if (options['max-content-length'] > 0) {
o.body = o.body.slice(0, options['max-content-length']);
}
await engine.add(o, {
tabId: tab.id,
windowId: tab.windowId,
favIconUrl: favIconUrl || 'web.svg',
frameId: o.frameId,
top: o.top,
lang: o.lang
});
}
return arr.length;
}
catch (e) {
console.warn('document skipped', e);
if (e.message.includes('memory access out of bounds')) {
return -1;
}
return 0;
}
});
};
document.addEventListener('engine-ready', async () => {
const prefs = await (new Promise(resolve => chrome.storage.local.get({
'scope': 'both',
'index': 'browser',
'parse-pdf': true,
'fetch-timeout': 10000,
'max-content-length': 100 * 1024,
'duplicates': true,
'highlight-color': 'orange'
}, prefs => resolve(prefs))));
const query = {};
if (prefs.index === 'window' || prefs.index === 'tab') {
query.currentWindow = true;
}
if (prefs.index === 'tab') {
query.active = true;
}
let tabs = await chrome.tabs.query(query);
tabs.forEach(tab => cache[tab.id] = tab);
// highlight
document.documentElement.style.setProperty(
'--highlight-color',
'var(--highlight-' + prefs['highlight-color'] + ')'
);
// index
let ignored = 0;
if (prefs.duplicates) {
const list = new Set();
tabs = tabs.filter(t => {
if (list.has(t.url)) {
ignored += 1;
return false;
}
list.add(t.url);
return true;
});
}
let memory = false;
docs = (await Promise.all(tabs.map(tab => index(tab, prefs.scope, {
'parse-pdf': prefs['parse-pdf'],
'fetch-timeout': prefs['fetch-timeout'],
'max-content-length': prefs['max-content-length']
})))).reduce((p, c) => {
if (c === 0 || c === -1) {
ignored += 1;
}
if (c === -1) {
memory = true;
return p;
}
else {
return p + c;
}
}, 0);
if (memory) {
alert(`Your browser's memory limit for indexing content reached.
Right-click on the toolbar button and reduce the "Maximum Size of Each Content" option and retry.`);
window.close();
}
if (docs === 0) {
root.dataset.empty = 'Nothing to index. You need to have some tabs open.';
}
else {
root.dataset.empty = `Searching among ${docs} document${docs === 1 ? '' : 's'}`;
if (ignored) {
root.dataset.empty += `. ${ignored} tab${ignored === 1 ? ' is' : 's are'} ignored.`;
}
}
ready = true;
// do we have anything to search
const input = document.querySelector('#search input[type=search]');
if (input.value) {
input.dispatchEvent(new Event('input', {
bubbles: true
}));
}
else {
chrome.storage.local.get({
mode: 'none',
query: ''
}, prefs => {
if (prefs.mode === 'selected' || prefs.mode === 'selectedORhistory') {
// do we have selected text
chrome.tabs.query({
currentWindow: true,
active: true
}, ([tab]) => chrome.scripting.executeScript({
target: {
tabId: tab.id
},
func: () => {
return window.getSelection().toString();
}
}, (arr = []) => {
if (chrome.runtime.lastError || input.value) {
return;
}
const query = arr.reduce((p, c) => p || c.result, '');
if (query) {
input.value = query;
input.select();
input.dispatchEvent(new Event('input', {
bubbles: true
}));
}
else if (prefs.mode === 'selectedORhistory' && prefs.query) {
input.value = prefs.query;
input.select();
input.dispatchEvent(new Event('input', {
bubbles: true
}));
}
}));
}
else if (prefs.mode === 'history' && prefs.query) {
input.value = prefs.query;
input.select();
input.dispatchEvent(new Event('input', {
bubbles: true
}));
}
});
}
});
const root = document.getElementById('results');
document.getElementById('search').addEventListener('submit', e => {
e.preventDefault();
});
const search = query => {
// abort all ongoing search requests
for (const c of search.controllers) {
c.abort();
}
search.controllers.length = 0;
const controller = new AbortController();
const {signal} = controller;
search.controllers.push(controller);
const info = document.getElementById('info');
const start = Date.now();
chrome.storage.local.get({
'snippet-size': 300,
'search-size': 30
}, prefs => {
if (signal.aborted) {
return;
}
// detect input language
chrome.i18n.detectLanguage(query, async obj => {
if (signal.aborted) {
return;
}
const lang = engine.language(obj && obj.languages.length ? obj.languages[0].language : 'en');
try {
const {size, estimated} = await engine.search({
query,
lang,
length: prefs['search-size']
});
document.body.dataset.size = size;
if (size === 0) {
info.textContent = '';
return;
}
info.textContent =
`About ${estimated} results (${((Date.now() - start) / 1000).toFixed(2)} seconds in ${docs} documents)`;
const t = document.getElementById('result');
for (let index = 0; index < size; index += 1) {
if (signal.aborted) {
return;
}
try {
const guid = await engine.search.guid(index);
const obj = engine.body(guid);
const percent = await engine.search.percent(index);
const clone = document.importNode(t.content, true);
clone.querySelector('a').href = obj.url;
Object.assign(clone.querySelector('a').dataset, {
tabId: obj.tabId,
windowId: obj.windowId,
frameId: obj.frameId,
index,
guid,
percent
});
clone.querySelector('input[name=search]').checked = index == 0;
clone.querySelector('cite').textContent = obj.url;
clone.querySelector('h2 span[data-id="number"]').textContent = '#' + (index + 1);
clone.querySelector('h2').title = clone.querySelector('h2 span[data-id="title"]').textContent = obj.title;
clone.querySelector('h2 img').src = obj.favIconUrl || cache[obj.tabId].favIconUrl || 'chrome://favicon/' + obj.url;
clone.querySelector('h2 img').onerror = e => {
e.target.src = 'web.svg';
};
if (!obj.top) {
clone.querySelector('h2 span[data-id="type"]').textContent = 'iframe';
}
const code = clone.querySelector('h2 code');
code.textContent = percent + '%';
if (percent > 80) {
code.style['background-color'] = 'green';
}
else if (code > 60) {
code.style['background-color'] = 'orange';
}
else {
code.style['background-color'] = 'gray';
}
const snippet = await engine.search.snippet({
index,
size: prefs['snippet-size']
});
// the HTML code that is returns from snippet is escaped
// https://xapian.org/docs/apidoc/html/classXapian_1_1MSet.html#a6f834ac35fdcc58fcd5eb38fc7f320f1
clone.querySelector('p').content = clone.querySelector('p').innerHTML = snippet;
// intersection observer
new IntersectionObserver(arrange, {
threshold: 1.0
}).observe(clone.querySelector('h2'));
root.appendChild(clone);
}
catch (e) {
console.warn('Cannot add a result', e);
}
}
}
catch (e) {
console.warn(e);
info.textContent = e.message || 'Unknown error occurred';
}
});
});
};
search.controllers = [];
document.getElementById('search').addEventListener('input', e => {
const query = e.target.value.trim();
root.textContent = '';
const info = document.getElementById('info');
if (query && ready) {
search(query);
}
else {
info.textContent = '';
document.body.dataset.size = 0;
}
// save last query
chrome.storage.local.set({query});
});
const deep = async a => {
const guid = a.dataset.guid;
const data = engine.body(guid);
await engine.new(1, 'one-tab');
const prefs = await new Promise(resolve => chrome.storage.local.get({
'snippet-size': 300,
'search-size': 30
}, resolve));
const parts = data.body.split(/\n+/).filter(a => a);
const bodies = [];
let body = '';
for (const part of parts) {
body += '\n' + part;
if (body.length > prefs['snippet-size']) {
bodies.push(body);
body = '';
}
}
if (body) {
bodies.push(body);
}
const lang = data.lang;
try {
for (const body of bodies) {
await engine.add({
body,
lang
}, undefined, undefined, 1);
}
const {size} = await engine.search({
query: document.querySelector('#search input[type=search]').value,
lang,
length: prefs['search-size']
}, 1);
if (size) {
const o = a.closest('.result');
for (let index = size - 1; index >= 0; index -= 1) {
const n = o.cloneNode(true);
const snippet = await engine.search.snippet({
index,
size: prefs['snippet-size']
});
n.classList.add('sub');
n.querySelector('img').remove();
n.querySelector('[data-id=title]').textContent = '⇢ ' + n.querySelector('[data-id=title]').textContent;
n.querySelector('p').content = n.querySelector('p').innerHTML = snippet;
const code = n.querySelector('h2 code');
const percent = await engine.search.percent(index);
code.textContent = percent + '%';
// intersection observer
new IntersectionObserver(arrange, {
threshold: 1.0
}).observe(n.querySelector('h2'));
o.insertAdjacentElement('afterend', n);
}
}
}
catch (e) {
console.warn(e);
}
engine.release(1);
};
document.addEventListener('click', e => {
const a = e.target.closest('[data-cmd]');
if (e.target.dataset.id === 'select') {
return;
}
if (e.target.dataset.id === 'deep-search') {
e.preventDefault();
e.target.textContent = '';
e.target.classList.add('done');
return deep(a);
}
if (a) {
const cmd = a.dataset.cmd;
if (cmd === 'open') {
const {tabId, windowId, frameId} = a.dataset;
const snippet = e.target.closest('.result').querySelector('p').content;
chrome.runtime.sendMessage({
method: 'find',
tabId: Number(tabId),
windowId: Number(windowId),
frameId,
snippet
}, () => window.close());
e.preventDefault();
}
else if (cmd === 'faqs') {
chrome.tabs.create({
url: chrome.runtime.getManifest().homepage_url
});
}
else if (cmd === 'shortcuts') {
chrome.tabs.create({
url: chrome.runtime.getManifest().homepage_url + '#faq25'
});
}
else if (cmd === 'select-all') {
[...document.querySelectorAll('.result [data-id="select"]')].forEach(e => e.checked = true);
document.dispatchEvent(new Event('change'));
}
else if (cmd === 'select-none') {
[...document.querySelectorAll('.result [data-id="select"]')].forEach(e => e.checked = false);
document.dispatchEvent(new Event('change'));
}
else if (cmd === 'group' || cmd === 'delete') {
const ids = [...document.querySelectorAll('#results [data-id=select]:checked')]
.map(e => e.closest('a').dataset.tabId)
.filter((s, i, l) => l.indexOf(s) === i)
.map(Number);
chrome.runtime.sendMessage({
method: cmd,
ids
}, () => window.close());
}
}
});
// keyboard shortcut
window.addEventListener('keydown', e => {
const meta = e.metaKey || e.ctrlKey;
if (e.code === 'Tab') {
e.preventDefault();
const input = document.querySelector('#search input[type=search]');
return input.focus();
}
if (meta && e.code === 'KeyR') {
e.stopPropagation();
e.preventDefault();
location.reload();
}
if (meta && e.code && e.code.startsWith('Digit')) {
e.preventDefault();
const index = Number(e.code.replace('Digit', ''));
const n = document.querySelector(`[data-count="${index}"]`);
if (n) {
n.click();
}
}
else if (meta && e.shiftKey && e.code === 'KeyA') {
e.preventDefault();
document.querySelector('[data-cmd=select-all]').click();
}
else if (meta && e.shiftKey && e.code === 'KeyF') {
e.preventDefault();
document.querySelector('[data-cmd=faqs]').click();
}
else if (meta && e.shiftKey && e.code === 'KeyS') {
e.preventDefault();
document.querySelector('[data-cmd=shortcuts]').click();
}
else if (meta && e.shiftKey && e.code === 'KeyC') {
e.preventDefault();
document.querySelector('[data-cmd=delete]').click();
}
else if (meta && e.shiftKey && e.code === 'KeyG') {
e.preventDefault();
document.querySelector('[data-cmd=group]').click();
}
else if (meta && e.shiftKey && e.code === 'KeyN') {
e.preventDefault();
document.querySelector('[data-cmd=select-none]').click();
}
else if (meta && e.code === 'KeyD') {
e.preventDefault();
const links = [...document.querySelectorAll('[data-tab-id]')]
.map(a => a.href)
.filter((s, i, l) => l.indexOf(s) === i);
if (links.length) {
navigator.clipboard.writeText(links.join('\n')).catch(e => {
console.warn(e);
if (e) {
alert(links.join('\n'));
}
});
}
}
else if (meta && e.code === 'KeyF') {
e.preventDefault();
const input = document.querySelector('#search input[type=search]');
input.focus();
input.select();
}
else if (e.code === 'Escape' && e.target.value === '') {
window.close();
}
else if (e.code === 'Space' && e.shiftKey) {
e.preventDefault();
const i = document.querySelector('.result input[type=radio]:checked');
if (i) {
i.closest('div').querySelector('[data-id=select]').click();
}
}
// extract all tabs into a new window
else if ((e.code === 'Enter' || e.code === 'NumpadEnter') && e.shiftKey) {
e.preventDefault();
const ids = [...document.querySelectorAll('[data-tab-id]')]
.filter(a => meta ? Number(a.dataset.percent) >= 80 : true)
.map(a => a.dataset.tabId)
.filter((s, i, l) => l.indexOf(s) === i)
.map(Number);
if (ids.length) {
chrome.runtime.sendMessage({
method: 'group',
ids
}, () => window.close());
}
}
else if ((e.code === 'Enter' || e.code === 'NumpadEnter')) {
e.preventDefault();
const n = document.querySelector(`.result input[type=radio]:checked + a`);
n.click();
}
else if (e.code === 'ArrowDown') {
e.preventDefault();
const es = [...document.querySelectorAll('.result input[type=radio]')];
const n = es.findIndex(e => e.checked);
if (n !== -1 && n !== es.length - 1) {
es[n + 1].checked = true;
const parent = es[n + 1].parentElement;
if (
parent.getBoundingClientRect().bottom > document.documentElement.clientHeight ||
parent.getBoundingClientRect().top < root.getBoundingClientRect().top
) {
parent.scrollIntoView({block: 'center', behavior: 'smooth'});
}
}
else if (n === es.length - 1) {
es[0].checked = true;
root.scrollTo({top: 0, behavior: 'smooth'});
}
}
else if (e.code === 'ArrowUp') {
e.preventDefault();
const es = [...document.querySelectorAll('.result input[type=radio]')];
const n = es.findIndex(e => e.checked);
if (n === 1) {
es[0].checked = true;
root.scrollTo({top: 0, behavior: 'smooth'});
}
else if (n !== 0) {
es[n - 1].checked = true;
const parent = es[n - 1].parentElement;
if (parent.getBoundingClientRect().top < root.getBoundingClientRect().top) {
parent.scrollIntoView({block: 'center', behavior: 'smooth'});
}
}
else if (n === 0) {
es[es.length - 1].checked = true;
const parent = es[es.length - 1].parentElement;
parent.scrollIntoView({block: 'center', behavior: 'smooth'});
}
}
else if (e.code === 'PageUp') {
e.preventDefault();
const es = [...document.querySelectorAll('.result input[type=radio]')];
es[0].checked = true;
root.scrollTo({top: 0, behavior: 'smooth'});
}
else if (e.code === 'PageDown') {
e.preventDefault();
const es = [...document.querySelectorAll('.result input[type=radio]')];
es[es.length - 1].checked = true;
const parent = es[es.length - 1].parentElement;
parent.scrollIntoView({block: 'center', behavior: 'smooth'});
}
});
// guide
chrome.storage.local.get({
'guide': true
}, prefs => {
if (prefs.guide) {
document.getElementById('guide').classList.remove('hidden');
}
});
document.getElementById('guide-close').addEventListener('click', e => {
e.target.parentElement.classList.add('hidden');
chrome.storage.local.set({
'guide': false
});
});
// permit
chrome.storage.local.get({
'internal': true
}, prefs => {
if (prefs.internal) {
chrome.permissions.contains({
permissions: ['tabs']
}, granted => {
if (granted === false) {
document.getElementById('internal').classList.remove('hidden');
}
});
}
});
document.getElementById('internal-close').addEventListener('click', e => {
e.target.parentElement.classList.add('hidden');
chrome.storage.local.set({
'internal': false
});
});
document.getElementById('internal').addEventListener('submit', e => {
e.preventDefault();
chrome.permissions.request({
permissions: ['tabs']
}, granted => {
if (granted) {
e.target.classList.add('hidden');
}
});
});
// select engine
chrome.storage.local.get({
engine: 'xapian'
}, prefs => {
const s = document.createElement('script');
s.src = '../' + prefs.engine + '/connect.js';
console.info('I am using', prefs.engine, 'engine');
document.body.dataset.engine = prefs.engine;
document.body.appendChild(s);
});
// select results
document.addEventListener('change', () => {
document.body.dataset.menu = Boolean(document.querySelector('#results [data-id="select"]:checked'));
});
Paste Hosted With By Wklejamy.pl