From 6605d3ea99c364a8f5f9d98a33e3ac89afb035f7 Mon Sep 17 00:00:00 2001 From: chocolateboy Date: Sun, 28 Jul 2019 05:35:07 +0100 Subject: Add an option to close the current tab and select the tab to the left Add an option to tabs.close to close the current tab and select the tab to the left. Bound to `D` by default, which replaces the tabs.close.right command, which is rarely-used. [1] The old `D` behavior has been moved to `gd`. + update the README and fix some lint errors [1] https://tinyurl.com/y4mj7hjy --- src/background/controllers/OperationController.ts | 2 +- src/background/usecases/TabUseCase.ts | 6 +++++- src/content/presenters/ScrollPresenter.ts | 2 +- src/settings/keymaps.ts | 12 ++++++------ src/shared/SettingData.ts | 3 ++- src/shared/Settings.ts | 3 ++- src/shared/operations.ts | 18 ++++++++++++------ 7 files changed, 29 insertions(+), 17 deletions(-) (limited to 'src') diff --git a/src/background/controllers/OperationController.ts b/src/background/controllers/OperationController.ts index 51cff28..9480ed5 100644 --- a/src/background/controllers/OperationController.ts +++ b/src/background/controllers/OperationController.ts @@ -32,7 +32,7 @@ export default class OperationController { doOperation(operation: operations.Operation): Promise { switch (operation.type) { case operations.TAB_CLOSE: - return this.tabUseCase.close(false); + return this.tabUseCase.close(false, operation.selectLeft); case operations.TAB_CLOSE_RIGHT: return this.tabUseCase.closeRight(); case operations.TAB_CLOSE_FORCE: diff --git a/src/background/usecases/TabUseCase.ts b/src/background/usecases/TabUseCase.ts index 31112a9..386307e 100644 --- a/src/background/usecases/TabUseCase.ts +++ b/src/background/usecases/TabUseCase.ts @@ -12,11 +12,15 @@ export default class TabUseCase { ) { } - async close(force: boolean): Promise { + async close(force: boolean, selectLeft = false): Promise { let tab = await this.tabPresenter.getCurrent(); if (!force && tab.pinned) { 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]); } diff --git a/src/content/presenters/ScrollPresenter.ts b/src/content/presenters/ScrollPresenter.ts index c06efca..e83f172 100644 --- a/src/content/presenters/ScrollPresenter.ts +++ b/src/content/presenters/ScrollPresenter.ts @@ -90,7 +90,7 @@ class Scroller { clearTimeout(lastTimeoutId); lastTimeoutId = null; } - lastTimeoutId = setTimeout(resetScrolling, 100); + lastTimeoutId = window.setTimeout(resetScrolling, 100); } } diff --git a/src/settings/keymaps.ts b/src/settings/keymaps.ts index 33ad26d..b9a08e6 100644 --- a/src/settings/keymaps.ts +++ b/src/settings/keymaps.ts @@ -18,11 +18,11 @@ const fields = [ ['mark.set.prefix', 'Set mark at current position'], ['mark.jump.prefix', 'Jump to the mark'], ], [ - ['tabs.close', 'Close a tab'], - ['tabs.close.right', 'Close tabs to the right'], + ['tabs.close?{"selectLeft":false}', 'Close a tab'], + ['tabs.close.right', 'Close all tabs to the right'], ['tabs.reopen', 'Reopen closed tab'], - ['tabs.next', 'Select next Tab'], - ['tabs.prev', 'Select prev Tab'], + ['tabs.next', 'Select next tab'], + ['tabs.prev', 'Select prev tab'], ['tabs.first', 'Select first tab'], ['tabs.last', 'Select last tab'], ['tabs.reload?{"cache":false}', 'Reload current tab'], @@ -50,8 +50,8 @@ const fields = [ ['command.show', 'Open console'], ['command.show.open?{"alter":false}', 'Open URL'], ['command.show.open?{"alter":true}', 'Alter URL'], - ['command.show.tabopen?{"alter":false}', 'Open URL in new Tab'], - ['command.show.tabopen?{"alter":true}', 'Alter 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.winopen?{"alter":false}', 'Open URL in new window'], ['command.show.winopen?{"alter":true}', 'Alter URL in new window'], ['command.show.buffer', 'Open buffer command'], diff --git a/src/shared/SettingData.ts b/src/shared/SettingData.ts index 1c085cf..94d1e21 100644 --- a/src/shared/SettingData.ts +++ b/src/shared/SettingData.ts @@ -353,7 +353,8 @@ export const DefaultSettingData: SettingData = SettingData.valueOf({ "G": { "type": "scroll.bottom" }, "$": { "type": "scroll.end" }, "d": { "type": "tabs.close" }, - "D": { "type": "tabs.close.right" }, + "D": { "type": "tabs.close", "selectLeft": true }, + "gd": { "type": "tabs.close.right" }, "!d": { "type": "tabs.close.force" }, "u": { "type": "tabs.reopen" }, "K": { "type": "tabs.prev" }, diff --git a/src/shared/Settings.ts b/src/shared/Settings.ts index 0bef342..c51cbc5 100644 --- a/src/shared/Settings.ts +++ b/src/shared/Settings.ts @@ -146,7 +146,8 @@ export const DefaultSetting: Settings = { 'G': { 'type': 'scroll.bottom' }, '$': { 'type': 'scroll.end' }, 'd': { 'type': 'tabs.close' }, - 'D': { 'type': 'tabs.close.right' }, + 'D': { 'type': 'tabs.close', 'selectLeft': true }, + 'gd': { 'type': 'tabs.close.right' }, '!d': { 'type': 'tabs.close.force' }, 'u': { 'type': 'tabs.reopen' }, 'K': { 'type': 'tabs.prev' }, diff --git a/src/shared/operations.ts b/src/shared/operations.ts index 2b03d9d..f657db1 100644 --- a/src/shared/operations.ts +++ b/src/shared/operations.ts @@ -201,6 +201,7 @@ export interface PageHomeOperation { export interface TabCloseOperation { type: typeof TAB_CLOSE; + selectLeft?: boolean; } export interface TabCloseForceOperation { @@ -367,28 +368,28 @@ export type Operation = const assertOptionalBoolean = (obj: any, name: string) => { if (Object.prototype.hasOwnProperty.call(obj, name) && typeof obj[name] !== 'boolean') { - throw new TypeError(`Not a boolean parameter '${name}'`); + throw new TypeError(`Not a boolean parameter: '${name}'`); } }; const assertRequiredNumber = (obj: any, name: string) => { if (!Object.prototype.hasOwnProperty.call(obj, name) || typeof obj[name] !== 'number') { - throw new TypeError(`Missing number parameter '${name}`); + throw new TypeError(`Missing number parameter: '${name}`); } }; const assertRequiredString = (obj: any, name: string) => { if (!Object.prototype.hasOwnProperty.call(obj, name) || 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 export const valueOf = (o: any): Operation => { if (!Object.prototype.hasOwnProperty.call(o, 'type')) { - throw new TypeError(`missing 'type' field`); + throw new TypeError(`Missing 'type' field`); } switch (o.type) { case COMMAND_SHOW_OPEN: @@ -416,6 +417,12 @@ export const valueOf = (o: any): Operation => { type: PAGE_HOME, newTab: Boolean(typeof o.newTab === undefined ? false : o.newTab), }; + case TAB_CLOSE: + assertOptionalBoolean(o, 'selectLeft'); + return { + type: TAB_CLOSE, + selectLeft: Boolean(typeof o.selectLeft === undefined ? false : o.selectLeft), // eslint-disable-line max-len + }; case TAB_RELOAD: assertOptionalBoolean(o, 'cache'); return { @@ -458,7 +465,6 @@ export const valueOf = (o: any): Operation => { case NAVIGATE_ROOT: case FOCUS_INPUT: case PAGE_SOURCE: - case TAB_CLOSE: case TAB_CLOSE_FORCE: case TAB_CLOSE_RIGHT: case TAB_REOPEN: @@ -483,5 +489,5 @@ export const valueOf = (o: any): Operation => { case REPEAT_LAST: return { type: o.type }; } - throw new TypeError('unknown operation type: ' + o.type); + throw new TypeError('Unknown operation type: ' + o.type); }; -- cgit v1.2.3 From 9a3e8f323797e693c6a5b2d09b1c1b753c8ee5ba Mon Sep 17 00:00:00 2001 From: chocolateboy Date: Wed, 31 Jul 2019 15:01:06 +0100 Subject: tabs.close.right: rename `gd` -> `x$` --- e2e/settings.js | 2 +- e2e/tab.test.js | 4 ++-- src/shared/SettingData.ts | 2 +- src/shared/Settings.ts | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/e2e/settings.js b/e2e/settings.js index e3e9787..b69e3e7 100644 --- a/e2e/settings.js +++ b/e2e/settings.js @@ -25,7 +25,7 @@ module.exports = { "$": { "type": "scroll.end" }, "d": { "type": "tabs.close" }, "D": { "type": "tabs.close", "selectLeft": true }, - "gd": { "type": "tabs.close.right" }, + "x$": { "type": "tabs.close.right" }, "!d": { "type": "tabs.close.force" }, "u": { "type": "tabs.reopen" }, "K": { "type": "tabs.prev", "count": 1 }, diff --git a/e2e/tab.test.js b/e2e/tab.test.js index aaaa5d7..c82890e 100644 --- a/e2e/tab.test.js +++ b/e2e/tab.test.js @@ -77,10 +77,10 @@ describe("tab test", () => { assert(current[2].url === tabs[2].url); }); - it('deletes all tabs to the right by gD', async () => { + it('deletes all tabs to the right by x$', async () => { await browser.tabs.update(tabs[1].id, { active: true }); let body = await session.findElementByCSS('body'); - await body.sendKeys('g', Key.Shift, 'D'); + await body.sendKeys('x', '$'); let current = await browser.tabs.query({ windowId: win.id }); assert(current.length === 2); diff --git a/src/shared/SettingData.ts b/src/shared/SettingData.ts index 94d1e21..e6434a2 100644 --- a/src/shared/SettingData.ts +++ b/src/shared/SettingData.ts @@ -354,7 +354,7 @@ export const DefaultSettingData: SettingData = SettingData.valueOf({ "$": { "type": "scroll.end" }, "d": { "type": "tabs.close" }, "D": { "type": "tabs.close", "selectLeft": true }, - "gd": { "type": "tabs.close.right" }, + "x$": { "type": "tabs.close.right" }, "!d": { "type": "tabs.close.force" }, "u": { "type": "tabs.reopen" }, "K": { "type": "tabs.prev" }, diff --git a/src/shared/Settings.ts b/src/shared/Settings.ts index c51cbc5..19101c1 100644 --- a/src/shared/Settings.ts +++ b/src/shared/Settings.ts @@ -147,7 +147,7 @@ export const DefaultSetting: Settings = { '$': { 'type': 'scroll.end' }, 'd': { 'type': 'tabs.close' }, 'D': { 'type': 'tabs.close', 'selectLeft': true }, - 'gd': { 'type': 'tabs.close.right' }, + 'x$': { 'type': 'tabs.close.right' }, '!d': { 'type': 'tabs.close.force' }, 'u': { 'type': 'tabs.reopen' }, 'K': { 'type': 'tabs.prev' }, -- cgit v1.2.3 From f65c068c67371f00b7853b4790b926e672f3ca4f Mon Sep 17 00:00:00 2001 From: chocolateboy Date: Wed, 31 Jul 2019 16:17:37 +0100 Subject: tabs.close: rename selectLeft (boolean) -> select ("left" | "right") before: { "type": "tabs.close", "selectLeft": true | false // (default: false) } after: { "type": "tabs.close", "select": "left" | "right" // (default: "right") } --- e2e/settings.js | 2 +- src/background/controllers/OperationController.ts | 2 +- src/settings/keymaps.ts | 2 +- src/shared/SettingData.ts | 2 +- src/shared/Settings.ts | 2 +- src/shared/operations.ts | 19 ++++++++++++++++--- 6 files changed, 21 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/e2e/settings.js b/e2e/settings.js index b69e3e7..e78add0 100644 --- a/e2e/settings.js +++ b/e2e/settings.js @@ -24,7 +24,7 @@ module.exports = { "G": { "type": "scroll.bottom" }, "$": { "type": "scroll.end" }, "d": { "type": "tabs.close" }, - "D": { "type": "tabs.close", "selectLeft": true }, + "D": { "type": "tabs.close", "select": "left" }, "x$": { "type": "tabs.close.right" }, "!d": { "type": "tabs.close.force" }, "u": { "type": "tabs.reopen" }, diff --git a/src/background/controllers/OperationController.ts b/src/background/controllers/OperationController.ts index 9480ed5..7a10ad6 100644 --- a/src/background/controllers/OperationController.ts +++ b/src/background/controllers/OperationController.ts @@ -32,7 +32,7 @@ export default class OperationController { doOperation(operation: operations.Operation): Promise { switch (operation.type) { case operations.TAB_CLOSE: - return this.tabUseCase.close(false, operation.selectLeft); + return this.tabUseCase.close(false, operation.select === 'left'); case operations.TAB_CLOSE_RIGHT: return this.tabUseCase.closeRight(); case operations.TAB_CLOSE_FORCE: diff --git a/src/settings/keymaps.ts b/src/settings/keymaps.ts index b9a08e6..24ba1a5 100644 --- a/src/settings/keymaps.ts +++ b/src/settings/keymaps.ts @@ -18,7 +18,7 @@ const fields = [ ['mark.set.prefix', 'Set mark at current position'], ['mark.jump.prefix', 'Jump to the mark'], ], [ - ['tabs.close?{"selectLeft":false}', 'Close a tab'], + ['tabs.close?{"select":"right"}', 'Close a tab'], ['tabs.close.right', 'Close all tabs to the right'], ['tabs.reopen', 'Reopen closed tab'], ['tabs.next', 'Select next tab'], diff --git a/src/shared/SettingData.ts b/src/shared/SettingData.ts index e6434a2..14a7d35 100644 --- a/src/shared/SettingData.ts +++ b/src/shared/SettingData.ts @@ -353,7 +353,7 @@ export const DefaultSettingData: SettingData = SettingData.valueOf({ "G": { "type": "scroll.bottom" }, "$": { "type": "scroll.end" }, "d": { "type": "tabs.close" }, - "D": { "type": "tabs.close", "selectLeft": true }, + "D": { "type": "tabs.close", "select": "left" }, "x$": { "type": "tabs.close.right" }, "!d": { "type": "tabs.close.force" }, "u": { "type": "tabs.reopen" }, diff --git a/src/shared/Settings.ts b/src/shared/Settings.ts index 19101c1..2a392df 100644 --- a/src/shared/Settings.ts +++ b/src/shared/Settings.ts @@ -146,7 +146,7 @@ export const DefaultSetting: Settings = { 'G': { 'type': 'scroll.bottom' }, '$': { 'type': 'scroll.end' }, 'd': { 'type': 'tabs.close' }, - 'D': { 'type': 'tabs.close', 'selectLeft': true }, + 'D': { 'type': 'tabs.close', 'select': 'left' }, 'x$': { 'type': 'tabs.close.right' }, '!d': { 'type': 'tabs.close.force' }, 'u': { 'type': 'tabs.reopen' }, diff --git a/src/shared/operations.ts b/src/shared/operations.ts index f657db1..2df2e67 100644 --- a/src/shared/operations.ts +++ b/src/shared/operations.ts @@ -201,7 +201,7 @@ export interface PageHomeOperation { export interface TabCloseOperation { type: typeof TAB_CLOSE; - selectLeft?: boolean; + select?: 'left' | 'right'; } export interface TabCloseForceOperation { @@ -372,6 +372,19 @@ const assertOptionalBoolean = (obj: any, name: string) => { } }; +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) => { if (!Object.prototype.hasOwnProperty.call(obj, name) || typeof obj[name] !== 'number') { @@ -418,10 +431,10 @@ export const valueOf = (o: any): Operation => { newTab: Boolean(typeof o.newTab === undefined ? false : o.newTab), }; case TAB_CLOSE: - assertOptionalBoolean(o, 'selectLeft'); + assertOptionalString(o, 'select', ['left', 'right']); return { type: TAB_CLOSE, - selectLeft: Boolean(typeof o.selectLeft === undefined ? false : o.selectLeft), // eslint-disable-line max-len + select: (typeof o.select === undefined ? 'right' : o.select), }; case TAB_RELOAD: assertOptionalBoolean(o, 'cache'); -- cgit v1.2.3