Merge pull request #621 from chocolateboy/tab-close-select-left

Add an option to close the current tab and select the tab to the left
jh-changes
Shin'ya Ueoka 5 years ago committed by GitHub
commit 7104f122f9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 214
      README.md
  2. 3
      e2e/settings.js
  3. 20
      e2e/tab.test.js
  4. 2
      src/background/controllers/OperationController.ts
  5. 6
      src/background/usecases/TabUseCase.ts
  6. 2
      src/content/presenters/ScrollPresenter.ts
  7. 12
      src/settings/keymaps.ts
  8. 3
      src/shared/SettingData.ts
  9. 3
      src/shared/Settings.ts
  10. 31
      src/shared/operations.ts

@ -6,41 +6,43 @@
[![CircleCI](https://circleci.com/gh/ueokande/vim-vixen.svg?style=svg)](https://circleci.com/gh/ueokande/vim-vixen) [![CircleCI](https://circleci.com/gh/ueokande/vim-vixen.svg?style=svg)](https://circleci.com/gh/ueokande/vim-vixen)
[![devDependencies Status](https://david-dm.org/ueokande/vim-vixen/dev-status.svg)](https://david-dm.org/ueokande/vim-vixen?type=dev) [![devDependencies Status](https://david-dm.org/ueokande/vim-vixen/dev-status.svg)](https://david-dm.org/ueokande/vim-vixen?type=dev)
Vim Vixen is a Firefox add-on which allows you to navigate with the keyboard on the browser. Vim Vixen is a Firefox add-on which allows you to easily navigate the web by
Firefox started to support WebExtensions API and will stop supporting add-ons using legacy APIs from version 57. keyboard. Since version 57, Firefox has migrated to the WebExtensions API and
For this reason, many legacy add-ons do not work on Firefox 57. has dropped support for legacy add-ons. Vim Vixen is a new choice for Vim users
Vim Vixen is a new choice for Vim users since Vim Vixen uses the WebExtensions API. since it uses the WebExtensions API.
## Basic usage ## Basic usage
### Key-maps ### Keymaps
The key-maps are configurable in the add-ons preferences by navigating to `about:addons` and selecting "Extensions". Keymaps are configurable in the add-on's preferences by navigating to `about:addons` and selecting "Extensions".
The default mappings are as follows: The default mappings are as follows:
#### Console #### Console
- <kbd>:</kbd>: open console - <kbd>:</kbd>: open the console
- <kbd>o</kbd>, <kbd>t</kbd>, <kbd>w</kbd>: open a page in current tab, new tab, or new window - <kbd>o</kbd>, <kbd>t</kbd>, <kbd>w</kbd>: open a page in the current tab, a new tab, or new window
- <kbd>O</kbd>, <kbd>T</kbd>, <kbd>W</kbd>: similar to <kbd>o</kbd>, <kbd>t</kbd>, <kbd>w</kbd>, but that contains current URL - <kbd>O</kbd>, <kbd>T</kbd>, <kbd>W</kbd>: similar to <kbd>o</kbd>, <kbd>t</kbd>, <kbd>w</kbd>, but using the current URL
- <kbd>b</kbd>: Select tabs by URL or title - <kbd>b</kbd>: select tabs by URL or title
- <kbd>a</kbd>: add current page to the bookmarks - <kbd>a</kbd>: add the current page to your bookmarks
See [console commands](#console-commands) section for more detailed description See the [console commands](#console-commands) section for a more detailed description.
#### Tabs #### Tabs
- <kbd>d</kbd>: delete tab
- <kbd>!</kbd><kbd>d</kbd>: delete pinned tab - <kbd>d</kbd>: delete the current tab and select the tab to its right
- <kbd>u</kbd>: reopen close tab - <kbd>D</kbd>: delete the current tab and select the tab to its left
- <kbd>r</kbd>: reload current tab - <kbd>!</kbd><kbd>d</kbd>: delete a pinned tab
- <kbd>R</kbd>: reload current tab without cache - <kbd>u</kbd>: reopen a close tab
- <kbd>K</kbd> or <kbd>g</kbd><kbd>T</kbd>: select previous tab - <kbd>r</kbd>: reload the current tab
- <kbd>J</kbd> or <kbd>g</kbd><kbd>t</kbd>: select next tab - <kbd>R</kbd>: reload the current tab, bypassing the cache
- <kbd>g</kbd><kbd>0</kbd>: select first tab - <kbd>K</kbd> or <kbd>g</kbd><kbd>T</kbd>: select the previous tab
- <kbd>g</kbd><kbd>$</kbd>: select last tab - <kbd>J</kbd> or <kbd>g</kbd><kbd>t</kbd>: select the next tab
- <kbd>Ctrl</kbd>+<kbd>6</kbd>: open previously selected tab - <kbd>g</kbd><kbd>0</kbd>: select the first tab
- <kbd>z</kbd><kbd>p</kbd>: pin tab - <kbd>g</kbd><kbd>$</kbd>: select the last tab
- <kbd>z</kbd><kbd>d</kbd>: duplicate tab - <kbd>Ctrl</kbd>+<kbd>6</kbd>: open the previously-selected tab
- <kbd>z</kbd><kbd>p</kbd>: pin the curent tab tab
- <kbd>z</kbd><kbd>d</kbd>: duplicate the current tab
#### Scrolling #### Scrolling
@ -48,18 +50,19 @@ See [console commands](#console-commands) section for more detailed description
- <kbd>j</kbd>: scroll down - <kbd>j</kbd>: scroll down
- <kbd>h</kbd>: scroll left - <kbd>h</kbd>: scroll left
- <kbd>l</kbd>: scroll right - <kbd>l</kbd>: scroll right
- <kbd>Ctrl</kbd>+<kbd>U</kbd>: scroll up for a half page - <kbd>Ctrl</kbd>+<kbd>U</kbd>: scroll up half a page
- <kbd>Ctrl</kbd>+<kbd>D</kbd>: scroll down for a half page - <kbd>Ctrl</kbd>+<kbd>D</kbd>: scroll down half a page
- <kbd>Ctrl</kbd>+<kbd>B</kbd>: scroll up for a whole page - <kbd>Ctrl</kbd>+<kbd>B</kbd>: scroll up a page
- <kbd>Ctrl</kbd>+<kbd>F</kbd>: scroll down for a whole page - <kbd>Ctrl</kbd>+<kbd>F</kbd>: scroll down a page
- <kbd>g</kbd><kbd>g</kbd>: scroll to top of a page - <kbd>g</kbd><kbd>g</kbd>: scroll to the top of a page
- <kbd>G</kbd>: scroll to bottom of a page - <kbd>G</kbd>: scroll to the bottom of a page
- <kbd>0</kbd>: scroll to the leftmost part of a page - <kbd>0</kbd>: scroll to the leftmost part of a page
- <kbd>$</kbd>: scroll to the rightmost part of a page - <kbd>$</kbd>: scroll to the rightmost part of a page
- <kbd>m</kbd>: set a mark from current position - <kbd>m</kbd>: set a mark for the current position
- <kbd>'</kbd>: jump to position by the mark - <kbd>'</kbd>: jump to a marked position
Lowercase alphabet mark (`[a-z]`) stores position on the current tab. Upper alphabet and numeric mark (`[A-Z0-9]`) stores position and tab. Lowercase marks (`[a-z]`) store the position of the current tab. Uppercase and
numeric marks (`[A-Z0-9]`) store the position and the tab.
#### Zoom #### Zoom
@ -69,109 +72,111 @@ Lowercase alphabet mark (`[a-z]`) stores position on the current tab. Upper alph
#### Navigation #### Navigation
- <kbd>f</kbd>: start following links in the page in the current tab - <kbd>f</kbd>: follow links in the page in the current tab
- <kbd>F</kbd>: start following links in the page in new tabs - <kbd>F</kbd>: follow links in the page in a new tab
- <kbd>H</kbd>: go back in history - <kbd>H</kbd>: go back in history
- <kbd>L</kbd>: go forward in history - <kbd>L</kbd>: go forward in history
- <kbd>[</kbd><kbd>[</kbd>, <kbd>]</kbd><kbd>]</kbd>: find prev or next links and open it - <kbd>[</kbd><kbd>[</kbd>, <kbd>]</kbd><kbd>]</kbd>: find a link to the previous/next page and open it
- <kbd>g</kbd><kbd>u</kbd>: go to parent directory - <kbd>g</kbd><kbd>u</kbd>: go to the parent directory
- <kbd>g</kbd><kbd>U</kbd>: go to root directory - <kbd>g</kbd><kbd>U</kbd>: go to the root directory
- <kbd>g</kbd><kbd>i</kbd>: focus first input - <kbd>g</kbd><kbd>i</kbd>: focus the first input field
Plugin can be configured to follow links in new tabs in background instead of Vim Vixen can be configured to follow links opened in tabs in the background
switching to a new tab immediately. To do this you need to update config file: instead of switching to a new tab immediately. To do this, you'll need to update
change `"background"` property of `"follow.start"` action to true: the config file: change the `"background"` property of the `"follow.start"`
action to `true`, e.g.:
```json ```json
{ {
"keymaps": { "keymaps": {
"F": { "type": "follow.start", "newTab": true, "background": true }, "F": { "type": "follow.start", "newTab": true, "background": true }
} }
} }
``` ```
#### Misc #### Misc
- <kbd>y</kbd>: copy URL in current tab - <kbd>y</kbd>: copy the URL of the current tab to the clipboard
- <kbd>p</kbd>: open clipboard's URL in current tab - <kbd>p</kbd>: open the clipboard's URL in the current tab
- <kbd>P</kbd>: open clipboard's URL in new tab - <kbd>P</kbd>: open the clipboard's URL in new tab
- <kbd>Shift</kbd>+<kbd>Esc</kbd>: enable or disable the add-on in current tab. - <kbd>Shift</kbd>+<kbd>Esc</kbd>: enable or disable the add-on in the current tab
- <kbd>/</kbd>: start to find a keyword in the page - <kbd>/</kbd>: start searching for text in the page
- <kbd>n</kbd>: find next keyword in the page - <kbd>n</kbd>: find the next search result in the page
- <kbd>N</kbd>: find prev keyword in the page - <kbd>N</kbd>: find the previous search result in the page
- <kbd>g</kbd><kbd>f</kbd>: view page source - <kbd>g</kbd><kbd>f</kbd>: view the source of the current tab
### Console commands ### Console commands
Vim Vixen provides a console for `ex`-style commands similar to Vimperator. Vim Vixen provides a console for `ex`-style commands, similar to Vimperator.
Open the console with <kbd>:</kbd>. Or start it with initial values using Open the console with <kbd>:</kbd>. Or populate it with initial values using
<kbd>o</kbd>/<kbd>O</kbd>, <kbd>t</kbd>/<kbd>T</kbd>, <kbd>o</kbd>/<kbd>O</kbd>, <kbd>t</kbd>/<kbd>T</kbd>, or
or <kbd>w</kbd>/<kbd>W</kbd>. <kbd>w</kbd>/<kbd>W</kbd>.
#### `:open` command #### `:open`
The `:open` command operates two different ways, depending on the parameter. The `:open` command operates two different ways, depending on the parameter.
When the parameter is a URL, that URL is opened in the current tab. When the parameter is a URL, it's opened in the current tab.
``` ```
:open http://github.com/ueokande :open http://github.com/ueokande
``` ```
Otherwise, the current tab will open a search page with the supplied string (defaults to Google). Otherwise, the current tab opens a search page with the supplied string (defaults to Google).
``` ```
:open How to contribute to Vim-Vixen :open How to contribute to Vim-Vixen
``` ```
To use a search engine other than the default, specify which search engine to use as the first parameter. To use a search engine other than the default, specify the search engine to use as the first parameter.
``` ```
:open yahoo How to contribute to Vim-Vixen :open yahoo How to contribute to Vim-Vixen
``` ```
To adjust the search engine default and add/remove search engines, see the [search engines](#search-engines) section. To adjust the default search-engine and add/remove search engines, see the [search engines](#search-engines) section.
#### `:tabopen` command #### `:tabopen`
Open a URL or search keywords by search engine in new tab. Open a URL or search-engine query in a new tab.
#### `:quit` or `:q` command #### `:quit` or `:q`
Close the current tab. Close the current tab.
#### `:quitall` or `:qa` command #### `:quitall` or `:qa`
Close all tabs. Close all tabs.
#### `:bdelete` command #### `:bdelete`
Close a certain tab. Close a certain tab.
You can add `!` at the end of the command to close tab even if it is pinned: You can add `!` to the end of the command to close a tab even if it is pinned:
``` ```
:bdelete! :bdelete!
``` ```
#### `:bdeletes` command #### `:bdeletes`
Close tabs matches with keywords. Close tabs matching the specified keywords.
You can add `!` at the end of the command to close even pinned tabs: You can add `!` to the end of the command to close pinned tabs:
``` ```
:bdeletes! :bdeletes!
``` ```
#### `:winopen` command #### `:winopen`
Open a URL or search keywords by search engine in new window. Open a URL or search-engine query in a new window.
#### `:buffer` command #### `:buffer`
Select tabs by URL or title matched by keywords. Select tabs by URL or title keywords.
#### `:addbookmark` command #### `:addbookmark`
Create a bookmark from the current URL. Create a bookmark from the current URL.
@ -179,20 +184,19 @@ Create a bookmark from the current URL.
:addbookmark My bookmark title :addbookmark My bookmark title
``` ```
The key map <kbd>a</kbd> is a convenient way to create a bookmark from the The keymap <kbd>a</kbd> is a convenient way to create a bookmark for the
current page. That shows `:addbookmark` with a title from the current page into current page. It populates the console with `:addbookmark` and the title of
console. the current page.
#### `:set` command #### `:set`
`:set` command can temporary override properties using console. See The `:set` command can be used to temporarily override properties in the
[properties](#properties) section for more detailed description of available console. See the [properties](#properties) section for more details on
properties. the available properties.
### Properties ### Properties
Plugin supports configurable properties which can be configured in JSON Vim Vixen can be configured by defining settings in a JSON document, e.g.:
settings:
```json ```json
{ {
@ -202,33 +206,35 @@ settings:
} }
``` ```
Properties can be temporary overwritten by `:set` command in console. Properties can be temporarily overridden by using the `:set` command in the
console.
List of available properties you can find below: The following properties are available:
#### `smoothscroll` property #### `smoothscroll`
Enable/disable smooth scroll. Enable/disable smooth scrolling.
``` ```
:set smoothscroll " enable smooth scroll :set smoothscroll " enable smooth scrolling
:set nosmoothscroll " disable smooth scroll :set nosmoothscroll " disable smooth scrolling
``` ```
#### `hintchars` property #### `hintchars`
Set hint characters Set hint characters.
``` ```
:set hintchars=0123456789 :set hintchars=0123456789
``` ```
#### `complete` property #### `complete`
Set completion items on `open`, `tabopen` `winopen` commands. Set completion items on `open`, `tabopen`, and `winopen` commands.
The allowed value is character sequence of `s`, `b`, or `h`. The allowed value is character sequence of `s`, `b`, or `h`.
Hit <kbd>Tab</kbd> or <kbd>Shift</kbd>+<kbd>Tab</kbd> to Select an item from the completion list. Hit <kbd>Tab</kbd> or <kbd>Shift</kbd>+<kbd>Tab</kbd> to select an item from the completion list.
Each character presents as following: Each character represents the following:
- `s`: search engines - `s`: search engines
- `b`: bookmark items - `b`: bookmark items
- `h`: history items. - `h`: history items.
@ -239,10 +245,10 @@ Each character presents as following:
### Search engines ### Search engines
Vim Vixen supports search by search engines like Google and Yahoo. Vim Vixen supports searching with search engines such as Google and Yahoo.
You can configure search engines, including the default search engine, in the add-ons preferences. You can configure search engines, including the default search engine, in the add-on's preferences.
The URLs specified in `"engines"` must contain a {}-placeholder, which will be The URLs specified in `"engines"` must contain a `{}`-placeholder, which will be
replaced with the search keyword parameters of the command. replaced with the search keyword parameters of the command.
```json ```json
@ -264,7 +270,7 @@ replaced with the search keyword parameters of the command.
### Blacklist ### Blacklist
The blacklist allows you to disable the plugin for certain pages by URL patterns. The blacklist allows you to disable the plugin for certain pages by URL patterns.
For instance, when you describe `"*.slack.com"`, the plugin is disabled on any Slack rooms. For instance, you could use `"*.slack.com"` to disable the plugin on all Slack channels.
In addition, you can also specify path patterns, such as `"example.com/mail/*"`. In addition, you can also specify path patterns, such as `"example.com/mail/*"`.
```json ```json
@ -279,6 +285,14 @@ In addition, you can also specify path patterns, such as `"example.com/mail/*"`.
You can toggle Vim Vixen between disabled and enabled with You can toggle Vim Vixen between disabled and enabled with
<kbd>shift</kbd>+<kbd>Esc</kbd>. <kbd>shift</kbd>+<kbd>Esc</kbd>.
## Compatibility
- Firefox 60 ESR
## Copyright
Copyright © 2017-2019 by Shin'ya Ueoka
## Licence ## Licence
MIT MIT

@ -24,7 +24,8 @@ module.exports = {
"G": { "type": "scroll.bottom" }, "G": { "type": "scroll.bottom" },
"$": { "type": "scroll.end" }, "$": { "type": "scroll.end" },
"d": { "type": "tabs.close" }, "d": { "type": "tabs.close" },
"D": { "type": "tabs.close.right" }, "D": { "type": "tabs.close", "select": "left" },
"x$": { "type": "tabs.close.right" },
"!d": { "type": "tabs.close.force" }, "!d": { "type": "tabs.close.force" },
"u": { "type": "tabs.reopen" }, "u": { "type": "tabs.reopen" },
"K": { "type": "tabs.prev", "count": 1 }, "K": { "type": "tabs.prev", "count": 1 },

@ -55,18 +55,32 @@ describe("tab test", () => {
await browser.windows.remove(win.id); await browser.windows.remove(win.id);
}); });
it('deletes tab by d', async () => { it('deletes tab and selects right by d', async () => {
await browser.tabs.update(tabs[3].id, { active: true });
let body = await session.findElementByCSS('body'); let body = await session.findElementByCSS('body');
await body.sendKeys('d'); await body.sendKeys('d');
let current = await browser.tabs.query({ windowId: win.id }); let current = await browser.tabs.query({ windowId: win.id });
assert(current.length === tabs.length - 1); assert(current.length === tabs.length - 1);
assert(current[3].active);
assert(current[3].url === tabs[4].url);
});
it('deletes tab and selects left by D', async () => {
await browser.tabs.update(tabs[3].id, { active: true });
let body = await session.findElementByCSS('body');
await body.sendKeys(Key.Shift, 'D');
let current = await browser.tabs.query({ windowId: win.id });
assert(current.length === tabs.length - 1);
assert(current[2].active);
assert(current[2].url === tabs[2].url);
}); });
it('deletes tabs to the right by D', async () => { it('deletes all tabs to the right by x$', async () => {
await browser.tabs.update(tabs[1].id, { active: true }); await browser.tabs.update(tabs[1].id, { active: true });
let body = await session.findElementByCSS('body'); let body = await session.findElementByCSS('body');
await body.sendKeys(Key.Shift, 'd'); await body.sendKeys('x', '$');
let current = await browser.tabs.query({ windowId: win.id }); let current = await browser.tabs.query({ windowId: win.id });
assert(current.length === 2); assert(current.length === 2);

@ -32,7 +32,7 @@ export default class OperationController {
doOperation(operation: operations.Operation): Promise<any> { doOperation(operation: operations.Operation): Promise<any> {
switch (operation.type) { switch (operation.type) {
case operations.TAB_CLOSE: case operations.TAB_CLOSE:
return this.tabUseCase.close(false); return this.tabUseCase.close(false, operation.select === 'left');
case operations.TAB_CLOSE_RIGHT: case operations.TAB_CLOSE_RIGHT:
return this.tabUseCase.closeRight(); return this.tabUseCase.closeRight();
case operations.TAB_CLOSE_FORCE: case operations.TAB_CLOSE_FORCE:

@ -12,11 +12,15 @@ export default class TabUseCase {
) { ) {
} }
async close(force: boolean): Promise<any> { async close(force: boolean, selectLeft = false): Promise<any> {
let tab = await this.tabPresenter.getCurrent(); let tab = await this.tabPresenter.getCurrent();
if (!force && tab.pinned) { if (!force && tab.pinned) {
return Promise.resolve(); return Promise.resolve();
} }
if (selectLeft && tab.index > 0) {
let tabs = await this.tabPresenter.getAll();
await this.tabPresenter.select(tabs[tab.index - 1].id as number);
}
return this.tabPresenter.remove([tab.id as number]); return this.tabPresenter.remove([tab.id as number]);
} }

@ -90,7 +90,7 @@ class Scroller {
clearTimeout(lastTimeoutId); clearTimeout(lastTimeoutId);
lastTimeoutId = null; lastTimeoutId = null;
} }
lastTimeoutId = setTimeout(resetScrolling, 100); lastTimeoutId = window.setTimeout(resetScrolling, 100);
} }
} }

@ -18,11 +18,11 @@ const fields = [
['mark.set.prefix', 'Set mark at current position'], ['mark.set.prefix', 'Set mark at current position'],
['mark.jump.prefix', 'Jump to the mark'], ['mark.jump.prefix', 'Jump to the mark'],
], [ ], [
['tabs.close', 'Close a tab'], ['tabs.close?{"select":"right"}', 'Close a tab'],
['tabs.close.right', 'Close tabs to the right'], ['tabs.close.right', 'Close all tabs to the right'],
['tabs.reopen', 'Reopen closed tab'], ['tabs.reopen', 'Reopen closed tab'],
['tabs.next', 'Select next Tab'], ['tabs.next', 'Select next tab'],
['tabs.prev', 'Select prev Tab'], ['tabs.prev', 'Select prev tab'],
['tabs.first', 'Select first tab'], ['tabs.first', 'Select first tab'],
['tabs.last', 'Select last tab'], ['tabs.last', 'Select last tab'],
['tabs.reload?{"cache":false}', 'Reload current tab'], ['tabs.reload?{"cache":false}', 'Reload current tab'],
@ -50,8 +50,8 @@ const fields = [
['command.show', 'Open console'], ['command.show', 'Open console'],
['command.show.open?{"alter":false}', 'Open URL'], ['command.show.open?{"alter":false}', 'Open URL'],
['command.show.open?{"alter":true}', 'Alter URL'], ['command.show.open?{"alter":true}', 'Alter URL'],
['command.show.tabopen?{"alter":false}', 'Open URL in new Tab'], ['command.show.tabopen?{"alter":false}', 'Open URL in new tab'],
['command.show.tabopen?{"alter":true}', 'Alter URL in new Tab'], ['command.show.tabopen?{"alter":true}', 'Alter URL in new tab'],
['command.show.winopen?{"alter":false}', 'Open URL in new window'], ['command.show.winopen?{"alter":false}', 'Open URL in new window'],
['command.show.winopen?{"alter":true}', 'Alter URL in new window'], ['command.show.winopen?{"alter":true}', 'Alter URL in new window'],
['command.show.buffer', 'Open buffer command'], ['command.show.buffer', 'Open buffer command'],

@ -353,7 +353,8 @@ export const DefaultSettingData: SettingData = SettingData.valueOf({
"G": { "type": "scroll.bottom" }, "G": { "type": "scroll.bottom" },
"$": { "type": "scroll.end" }, "$": { "type": "scroll.end" },
"d": { "type": "tabs.close" }, "d": { "type": "tabs.close" },
"D": { "type": "tabs.close.right" }, "D": { "type": "tabs.close", "select": "left" },
"x$": { "type": "tabs.close.right" },
"!d": { "type": "tabs.close.force" }, "!d": { "type": "tabs.close.force" },
"u": { "type": "tabs.reopen" }, "u": { "type": "tabs.reopen" },
"K": { "type": "tabs.prev" }, "K": { "type": "tabs.prev" },

@ -146,7 +146,8 @@ export const DefaultSetting: Settings = {
'G': { 'type': 'scroll.bottom' }, 'G': { 'type': 'scroll.bottom' },
'$': { 'type': 'scroll.end' }, '$': { 'type': 'scroll.end' },
'd': { 'type': 'tabs.close' }, 'd': { 'type': 'tabs.close' },
'D': { 'type': 'tabs.close.right' }, 'D': { 'type': 'tabs.close', 'select': 'left' },
'x$': { 'type': 'tabs.close.right' },
'!d': { 'type': 'tabs.close.force' }, '!d': { 'type': 'tabs.close.force' },
'u': { 'type': 'tabs.reopen' }, 'u': { 'type': 'tabs.reopen' },
'K': { 'type': 'tabs.prev' }, 'K': { 'type': 'tabs.prev' },

@ -201,6 +201,7 @@ export interface PageHomeOperation {
export interface TabCloseOperation { export interface TabCloseOperation {
type: typeof TAB_CLOSE; type: typeof TAB_CLOSE;
select?: 'left' | 'right';
} }
export interface TabCloseForceOperation { export interface TabCloseForceOperation {
@ -367,28 +368,41 @@ export type Operation =
const assertOptionalBoolean = (obj: any, name: string) => { const assertOptionalBoolean = (obj: any, name: string) => {
if (Object.prototype.hasOwnProperty.call(obj, name) && if (Object.prototype.hasOwnProperty.call(obj, name) &&
typeof obj[name] !== 'boolean') { typeof obj[name] !== 'boolean') {
throw new TypeError(`Not a boolean parameter '${name}'`); throw new TypeError(`Not a boolean parameter: '${name}'`);
}
};
const assertOptionalString = (obj: any, name: string, values?: string[]) => {
if (Object.prototype.hasOwnProperty.call(obj, name)) {
let value = obj[name];
if (typeof value !== 'string') {
throw new TypeError(`Not a string parameter: '${name}'`);
}
if (values && values.length && values.indexOf(value) === -1) {
// eslint-disable-next-line max-len
throw new TypeError(`Invalid parameter for '${name}': '${value}'`);
}
} }
}; };
const assertRequiredNumber = (obj: any, name: string) => { const assertRequiredNumber = (obj: any, name: string) => {
if (!Object.prototype.hasOwnProperty.call(obj, name) || if (!Object.prototype.hasOwnProperty.call(obj, name) ||
typeof obj[name] !== 'number') { typeof obj[name] !== 'number') {
throw new TypeError(`Missing number parameter '${name}`); throw new TypeError(`Missing number parameter: '${name}`);
} }
}; };
const assertRequiredString = (obj: any, name: string) => { const assertRequiredString = (obj: any, name: string) => {
if (!Object.prototype.hasOwnProperty.call(obj, name) || if (!Object.prototype.hasOwnProperty.call(obj, name) ||
typeof obj[name] !== 'string') { typeof obj[name] !== 'string') {
throw new TypeError(`Missing string parameter '${name}`); throw new TypeError(`Missing string parameter: '${name}`);
} }
}; };
// eslint-disable-next-line complexity, max-lines-per-function // eslint-disable-next-line complexity, max-lines-per-function
export const valueOf = (o: any): Operation => { export const valueOf = (o: any): Operation => {
if (!Object.prototype.hasOwnProperty.call(o, 'type')) { if (!Object.prototype.hasOwnProperty.call(o, 'type')) {
throw new TypeError(`missing 'type' field`); throw new TypeError(`Missing 'type' field`);
} }
switch (o.type) { switch (o.type) {
case COMMAND_SHOW_OPEN: case COMMAND_SHOW_OPEN:
@ -416,6 +430,12 @@ export const valueOf = (o: any): Operation => {
type: PAGE_HOME, type: PAGE_HOME,
newTab: Boolean(typeof o.newTab === undefined ? false : o.newTab), newTab: Boolean(typeof o.newTab === undefined ? false : o.newTab),
}; };
case TAB_CLOSE:
assertOptionalString(o, 'select', ['left', 'right']);
return {
type: TAB_CLOSE,
select: (typeof o.select === undefined ? 'right' : o.select),
};
case TAB_RELOAD: case TAB_RELOAD:
assertOptionalBoolean(o, 'cache'); assertOptionalBoolean(o, 'cache');
return { return {
@ -458,7 +478,6 @@ export const valueOf = (o: any): Operation => {
case NAVIGATE_ROOT: case NAVIGATE_ROOT:
case FOCUS_INPUT: case FOCUS_INPUT:
case PAGE_SOURCE: case PAGE_SOURCE:
case TAB_CLOSE:
case TAB_CLOSE_FORCE: case TAB_CLOSE_FORCE:
case TAB_CLOSE_RIGHT: case TAB_CLOSE_RIGHT:
case TAB_REOPEN: case TAB_REOPEN:
@ -483,5 +502,5 @@ export const valueOf = (o: any): Operation => {
case REPEAT_LAST: case REPEAT_LAST:
return { type: o.type }; return { type: o.type };
} }
throw new TypeError('unknown operation type: ' + o.type); throw new TypeError('Unknown operation type: ' + o.type);
}; };