If you like this site and you like coffee, why not buy me one. Because I love it

Click to buy coffe
avatar
Untitled

Guest 23 24th Jan, 2023

JAVASCRIPT 23.38 KB
                                           
                         /* 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'));
});
                      
                                       
To share this paste please copy this url and send to your friends
RAW Paste Data
Recent Pastes
Ta strona używa plików cookie w celu usprawnienia i ułatwienia dostępu do serwisu oraz prowadzenia danych statystycznych. Dalsze korzystanie z tej witryny oznacza akceptację tego stanu rzeczy.
Wykorzystywanie plików Cookie
Jak wyłączyć cookies?
ROZUMIEM