kumabook/bebop

View on GitHub
src/actions.js

Summary

Maintainability
F
3 days
Test Coverage
/* eslint-disable no-multi-spaces, comma-spacing, object-curly-newline */
import browser from 'webextension-polyfill';
import isUrl from 'is-url';
import { click } from './link';
import * as cursor from './cursor';
import { getActiveContentTab } from './utils/tabs';
import { requestArg } from './utils/args';
import { manage as manageCookies } from './utils/cookies';
import {
  restorePrevious,
  restore as restoreSession,
  forget as forgetSession,
} from './utils/sessions';
import { downloadBookmarks } from './utils/hatebu';

const actionsOfType = {};
const actionList = [];
const EMPTY_URLS = ['about:newtab', 'about:blank'];

const noop = () => Promise.resolve();

function filter(candidates, candidateType) {
  return candidates.filter(({ type }) => type === candidateType);
}

function first(candidates, candidateType) {
  const candidate = candidates.find(c => c.type === candidateType);
  if (candidate) {
    return [candidate];
  }
  return [];
}

export function activateTab(cs) {
  return Promise.all(first(cs, 'tab').map(({ args }) => browser.tabs.update(args[0], { active: true })));
}

export function closeTab(cs) {
  return Promise.all(filter(cs, 'tab').map(({ args }) => browser.tabs.remove(args[0])));
}

export function open(url) {
  return getActiveContentTab().then((tab) => {
    if (!url) {
      return Promise.resolve();
    }
    if (tab && EMPTY_URLS.includes(tab.url)) {
      return browser.tabs.update(tab.id, { url });
    }
    return browser.tabs.create({ url });
  });
}

export function openWindow(url, { incognito } = { incognito: false }) {
  return browser.windows.create({ url, incognito });
}

export function go(url) {
  return getActiveContentTab().then(tab => browser.tabs.update(tab.id, { url }));
}

export function openUrlsInNewTab(candidates) {
  return Promise.all(candidates.map(({ args }) => {
    if (isUrl(args[0])) {
      return open(args[0]);
    }
    return Promise.resolve();
  }));
}

export function openUrlsInNewWindow(candidates) {
  return openWindow(candidates.map(({ args }) => args[0]));
}

export function openUrlsInPrivateWindow(candidates) {
  return openWindow(candidates.map(({ args }) => args[0]), { incognito: true });
}

export function openUrl(candidates) {
  const candidate = candidates.find(c => isUrl(c.args[0]));
  if (candidate) {
    return go(candidate.args[0]);
  }
  return Promise.resolve();
}

export function openHatebuEntryPage(candidates) {
  const candidate = candidates.find(c => isUrl(c.args[0]));
  if (candidate) {
    const pageURL = new URL(candidate.args[0]);
    let hatenaPrefix = 'http://b.hatena.ne.jp/entry/';
    if (pageURL.protocol === 'https:') {
      hatenaPrefix += 's/';
    }
    const hatebuEntryPage = hatenaPrefix + pageURL.host + pageURL.pathname;
    return go(hatebuEntryPage);
  }
  return Promise.resolve();
}

export function clickLink(cs) {
  return Promise.all(filter(cs, 'link').map(({ args }) => click(args[0])));
}

function link2Url({ args }) {
  if (args[0] && isUrl(args[0].url)) {
    return { args: [args[0].url] };
  }
  return { args: [] };
}

export function openLinkInNewTab(candidates) {
  return openUrlsInNewTab(candidates.map(link2Url));
}

export function openLinkInNewWindow(candidates) {
  return openUrlsInNewWindow(candidates.map(link2Url));
}

export function openLinkInPrivateWindow(candidates) {
  return openUrlsInPrivateWindow(candidates.map(link2Url));
}

function googleUrl(q) {
  return `https://www.google.com/search?q=${q}`;
}

export function searchInNewTab(cs) {
  return Promise.all(first(cs, 'search').map(({ args }) => {
    if (browser.search) {
      return browser.search.search({ query: args[0] });
    }
    return open(googleUrl(args[0]));
  }));
}

export function search(cs) {
  return getActiveContentTab().then(tab => Promise.all(first(cs, 'search').map(({ args }) => {
    if (browser.search) {
      return browser.search.search({ query: args[0], tabId: tab.id });
    }
    return go(googleUrl(args[0]));
  })));
}

export function deleteHistory(cs) {
  return Promise.all(filter(cs, 'history').map(({ args }) => browser.history.deleteUrl({ url: args[0] })));
}

export function deleteBookmark(cs) {
  return Promise.all(filter(cs, 'bookmark').map(({ args }) => browser.bookmarks.remove(args[1])));
}

export function runCommand(cs) {
  return Promise.all(filter(cs, 'command').map(async ({ args }) => {
    const [name] = args;
    switch (name) {
      case 'open-options':
        return browser.runtime.openOptionsPage();
      case 'go-forward':
        return Promise.resolve();
      case 'go-back':
        return Promise.resolve();
      case 'go-root':
        return Promise.resolve();
      case 'go-parent':
        return Promise.resolve();
      case 'reload':
        return Promise.resolve();
      case 'add-bookmark':
        return getActiveContentTab()
          .then(({ title, url }) => browser.bookmarks.create({
            title,
            url,
          }));
      case 'remove-bookmark': {
        const { url } = await getActiveContentTab();
        const bs      = await browser.bookmarks.search({ url });
        return Promise.all(bs.map(b => browser.bookmarks.remove(b.id)));
      }
      case 'set-zoom': {
        const { id: tabId } = await getActiveContentTab();
        const zoom          = await browser.tabs.getZoom(tabId);
        const zoomFactor    = await requestArg({
          type:    'number',
          title:   'zoom factor',
          minimum: 0.3,
          maximum: 3,
          default: zoom,
        });
        return browser.tabs.setZoom(tabId, parseFloat(zoomFactor));
      }
      case 'restore-previous-session': {
        return restorePrevious();
      }
      case 'manage-cookies': {
        const { url } = await getActiveContentTab();
        return manageCookies(url);
      }
      case 'download-hatebu': {
        const { hatenaUserName } = await browser.storage.local.get('hatenaUserName');
        return downloadBookmarks(hatenaUserName);
      }
      default:
        return Promise.resolve();
    }
  }));
}

export function runCommandOnContent(cs) {
  return Promise.all(filter(cs, 'command').map(({ args }) => {
    const [name] = args;
    switch (name) {
      case 'go-forward':
        window.history.forward();
        break;
      case 'go-back':
        window.history.back();
        break;
      case 'go-root': {
        const { protocol, host } = window.location;
        window.location.href = `${protocol}//${host}`;
        break;
      }
      case 'go-parent': {
        window.location.href = `${window.location}/../`;
        break;
      }
      case 'reload':
        window.location.reload();
        break;
      default:
        break;
    }
    return Promise.resolve();
  }));
}

const searchActions = [
  { id: 'search'           , label: 'search'           , icon: 'open', handler: search        , contentHandler: noop },
  { id: 'search-in-new-tab', label: 'search in new tab', icon: 'tab' , handler: searchInNewTab, contentHandler: noop },
];

const linkActions = [
  { id: 'click'                      , label: 'click'                      , icon: 'click'  , handler: noop                   , contentHandler: clickLink },
  { id: 'open-link-in-new-tab'       , label: 'open link in new tab'       , icon: 'tab'    , handler: openLinkInNewTab       , contentHandler: noop },
  { id: 'open-link-in-new-window'    , label: 'open link in new window'    , icon: 'window' , handler: openLinkInNewWindow    , contentHandler: noop },
  { id: 'open-link-in-private-window', label: 'open link in private window', icon: 'private', handler: openLinkInPrivateWindow, contentHandler: noop },
];

const tabActions = [
  { id: 'activate-tab', label: 'activate tab', icon: 'tab'   , handler: activateTab, contentHandler: noop },
  { id: 'close-tab'   , label: 'close tab(s)', icon: 'delete', handler: closeTab   , contentHandler: noop },
];

const historyActions = [
  { id: 'open-url'                   , label: 'open'                         , icon: 'open'   , handler: openUrl                , contentHandler: noop },
  { id: 'open-urls-in-new-tab'       , label: 'open url(s) in new tab(s)'    , icon: 'tab'    , handler: openUrlsInNewTab       , contentHandler: noop },
  { id: 'open-urls-in-new-window'    , label: 'open url(s) in new window'    , icon: 'window' , handler: openUrlsInNewWindow    , contentHandler: noop },
  { id: 'open-urls-in-private-window', label: 'open url(s) in private window', icon: 'private', handler: openUrlsInPrivateWindow, contentHandler: noop },
  { id: 'delete-history'             , label: 'delete history(s)'            , icon: 'delete' , handler: deleteHistory          , contentHandler: noop },
];

const bookmarkActions = [
  { id: 'open-url'                   , label: 'open'                         , icon: 'open'   , handler: openUrl                , contentHandler: noop },
  { id: 'open-urls-in-new-tab'       , label: 'open url(s) in new tab(s)'    , icon: 'tab'    , handler: openUrlsInNewTab       , contentHandler: noop },
  { id: 'open-urls-in-new-window'    , label: 'open url(s) in new window'    , icon: 'window' , handler: openUrlsInNewWindow    , contentHandler: noop },
  { id: 'open-urls-in-private-window', label: 'open url(s) in private window', icon: 'private', handler: openUrlsInPrivateWindow, contentHandler: noop },
  { id: 'delete-bookmark'            , label: 'delete bookmark(s)'           , icon: 'delete' , handler: deleteBookmark         , contentHandler: noop },
];

const hatebuActions = [
  { id: 'open-url'                   , label: 'open'                         , icon: 'open'   , handler: openUrl                , contentHandler: noop },
  { id: 'open-urls-in-new-tab'       , label: 'open url(s) in new tab(s)'    , icon: 'tab'    , handler: openUrlsInNewTab       , contentHandler: noop },
  { id: 'open-urls-in-new-window'    , label: 'open url(s) in new window'    , icon: 'window' , handler: openUrlsInNewWindow    , contentHandler: noop },
  { id: 'open-urls-in-private-window', label: 'open url(s) in private window', icon: 'private', handler: openUrlsInPrivateWindow, contentHandler: noop },
  { id: 'edit-bookmark'              , label: 'edit bookmark(s)'             , icon: 'open'   , handler: openHatebuEntryPage    , contentHandler: noop },
];

const sessionActions = [
  { id: 'restore-session', label: 'restore-session', icon: 'session', handler: restoreSession },
];

if (browser.sessions && browser.sessions.forgetClosedTab) {
  sessionActions.push({ id: 'forget-session' , label: 'forget-session' , icon: 'session', handler: forgetSession });
}

const cursorActions = [
  { id: 'forward-char'        , label: 'Forward char'        , icon: null, handler: noop, contentHandler: cursor.forwardChar },
  { id: 'backward-char'       , label: 'Backward char'       , icon: null, handler: noop, contentHandler: cursor.backwardChar },
  { id: 'beginning-of-line'   , label: 'Beginning of line'   , icon: null, handler: noop, contentHandler: cursor.beginningOfLine },
  { id: 'end-of-line'         , label: 'End of line'         , icon: null, handler: noop, contentHandler: cursor.endOfLine },
  { id: 'next-line'           , label: 'Next line'           , icon: null, handler: noop, contentHandler: cursor.nextLine },
  { id: 'previous-line'       , label: 'Previous line'       , icon: null, handler: noop, contentHandler: cursor.previousLine },
  { id: 'end-of-buffer'       , label: 'End of buffer'       , icon: null, handler: noop, contentHandler: cursor.endOfBuffer },
  { id: 'beginning-of-buffer' , label: 'Beginning of buffer' , icon: null, handler: noop, contentHandler: cursor.beginningOfBuffer },
  { id: 'delete-backward-char', label: 'Delete backward char', icon: null, handler: noop, contentHandler: cursor.deleteBackwardChar },
  { id: 'kill-line'           , label: 'Kill line'           , icon: null, handler: noop, contentHandler: cursor.killLine },
];

const commandActions = [
  { id: 'run-command', label: 'run command', icon: 'command', handler: runCommand, contentHandler: runCommandOnContent },
];

export function action2Candidate(c) {
  if (!c) {
    return null;
  }
  return Object.assign({}, c, {
    id:         c.id,
    label:      c.label,
    type:       'action',
    faviconUrl: c.icon ? browser.extension.getURL(`images/${c.icon}.png`) : null,
  });
}

export function register(name, actions) {
  actionsOfType[name] = actions;
  actions.forEach((c) => {
    if (!actionList.find(v => v.id === c.id)) {
      actionList.push(c);
    }
  });
}

export function query(type = '', q = '') {
  const actions = actionsOfType[type] ? actionsOfType[type] : [];
  return actions.filter(c => c.label.includes(q)).map(action2Candidate);
}

export function find(id = '') {
  return action2Candidate(actionList.find(c => c.id === id));
}

export function init() {
  register('search'  , searchActions);
  register('link'    , linkActions);
  register('tab'     , tabActions);
  register('history' , historyActions);
  register('bookmark', bookmarkActions);
  register('hatebu'  , hatebuActions);
  register('session' , sessionActions);
  register('cursor'  , cursorActions);
  register('command' , commandActions);
}