commit
						ab6295d0f1
					
				
					 16 changed files with 1514 additions and 5269 deletions
				
			
		| 
						 | 
					@ -75,10 +75,6 @@ jobs:
 | 
				
			||||||
      - checkout
 | 
					      - checkout
 | 
				
			||||||
      - setup_npm
 | 
					      - setup_npm
 | 
				
			||||||
      - run: npm run build
 | 
					      - run: npm run build
 | 
				
			||||||
      - run:
 | 
					 | 
				
			||||||
          name: Run geckodriver
 | 
					 | 
				
			||||||
          command: geckodriver
 | 
					 | 
				
			||||||
          background: true
 | 
					 | 
				
			||||||
      - run: npm run test:e2e
 | 
					      - run: npm run test:e2e
 | 
				
			||||||
 | 
					
 | 
				
			||||||
workflows:
 | 
					workflows:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -31,6 +31,7 @@
 | 
				
			||||||
    "default-case": "off",
 | 
					    "default-case": "off",
 | 
				
			||||||
    "dot-location": ["error", "property"],
 | 
					    "dot-location": ["error", "property"],
 | 
				
			||||||
    "function-paren-newline": "off",
 | 
					    "function-paren-newline": "off",
 | 
				
			||||||
 | 
					    "function-call-argument-newline": ["error", "consistent"],
 | 
				
			||||||
    "id-length": "off",
 | 
					    "id-length": "off",
 | 
				
			||||||
    "indent": ["error", 2],
 | 
					    "indent": ["error", 2],
 | 
				
			||||||
    "init-declarations": "off",
 | 
					    "init-declarations": "off",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										48
									
								
								QA.md
									
										
									
									
									
								
							
							
						
						
									
										48
									
								
								QA.md
									
										
									
									
									
								
							| 
						 | 
					@ -25,46 +25,10 @@ The behaviors of the console are tested in [Console section](#consoles).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Settings
 | 
					### Settings
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#### JSON Settings
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
##### Validations
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- [ ] show error on invalid json
 | 
					 | 
				
			||||||
- [ ] show error when top-level keys has keys other than `keymaps`, `search`, `blacklist`, and `properties`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
###### `"keymaps"` section
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- [ ] show error on unknown operation name in `"keymaps"`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
###### `"search"` section
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- validations in `"search"` section are not tested in this release
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
##### Updating
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- [ ] changes are updated on textarea blure when no errors
 | 
					 | 
				
			||||||
- [ ] keymap settings are applied to open tabs without reload
 | 
					 | 
				
			||||||
- [ ] search settings are applied to open tabs without reload
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
##### Properties
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- [ ] show errors when invalid property name
 | 
					 | 
				
			||||||
- [ ] show errors when invalid property type
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#### Form Settings
 | 
					#### Form Settings
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<!-- validation on form settings does not implement in 0.7 -->
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
##### Search Engines
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- [ ] able to change default
 | 
					 | 
				
			||||||
- [ ] able to remove item
 | 
					 | 
				
			||||||
- [ ] able to add item
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
##### `"blacklist"` section
 | 
					##### `"blacklist"` section
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- [ ] able to add item
 | 
					 | 
				
			||||||
- [ ] able to remove item
 | 
					 | 
				
			||||||
- [ ] `github.com/a` blocks `github.com/a`, and not blocks `github.com/aa`
 | 
					- [ ] `github.com/a` blocks `github.com/a`, and not blocks `github.com/aa`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
##### Updating
 | 
					##### Updating
 | 
				
			||||||
| 
						 | 
					@ -72,12 +36,6 @@ The behaviors of the console are tested in [Console section](#consoles).
 | 
				
			||||||
- [ ] keymap settings are applied to open tabs without reload
 | 
					- [ ] keymap settings are applied to open tabs without reload
 | 
				
			||||||
- [ ] search settings are applied to open tabs without reload
 | 
					- [ ] search settings are applied to open tabs without reload
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Settings source
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- [ ] show confirmation dialog on switched from json to form
 | 
					 | 
				
			||||||
- [ ] state is saved on source changed
 | 
					 | 
				
			||||||
- [ ] on switching form -> json -> form, first and last form setting is equivalent to first one
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Find mode
 | 
					## Find mode
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- [ ] open console with <kbd>/</kbd>
 | 
					- [ ] open console with <kbd>/</kbd>
 | 
				
			||||||
| 
						 | 
					@ -86,9 +44,3 @@ The behaviors of the console are tested in [Console section](#consoles).
 | 
				
			||||||
- [ ] Wrap search by <kbd>n</kbd>/<kbd>N</kbd>
 | 
					- [ ] Wrap search by <kbd>n</kbd>/<kbd>N</kbd>
 | 
				
			||||||
- [ ] Find with last keyword if keyword is empty
 | 
					- [ ] Find with last keyword if keyword is empty
 | 
				
			||||||
- [ ] Find keyword last used on new tab opened
 | 
					- [ ] Find keyword last used on new tab opened
 | 
				
			||||||
 | 
					 | 
				
			||||||
## Misc
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- [ ] Work on `about:blank`
 | 
					 | 
				
			||||||
- [ ] Able to map `<A-Z>` key.
 | 
					 | 
				
			||||||
- [ ] Open file menu by <kbd>Alt</kbd>+<kbd>F</kbd> (Other than Mac OS)
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -44,7 +44,7 @@ describe("completion on open/tabopen/winopen commands", () => {
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    // Add item into hitories
 | 
					    // Add item into hitories
 | 
				
			||||||
    await session.navigateTo(`https://i-beam.org`);
 | 
					    await session.navigateTo(`https://i-beam.org/404`);
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  after(async() => {
 | 
					  after(async() => {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										99
									
								
								e2e/options.test.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								e2e/options.test.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,99 @@
 | 
				
			||||||
 | 
					const express = require('express');
 | 
				
			||||||
 | 
					const lanthan = require('lanthan');
 | 
				
			||||||
 | 
					const path = require('path');
 | 
				
			||||||
 | 
					const assert = require('assert');
 | 
				
			||||||
 | 
					const eventually = require('./eventually');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const newApp = () => {
 | 
				
			||||||
 | 
					  let app = express();
 | 
				
			||||||
 | 
					  app.get('/', (req, res) => {
 | 
				
			||||||
 | 
					    res.send(`<!DOCTYPEhtml>
 | 
				
			||||||
 | 
					<html lang="en">
 | 
				
			||||||
 | 
					  <body style="width:10000px; height:10000px"></body>
 | 
				
			||||||
 | 
					</html">`);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  return app;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe("options page", () => {
 | 
				
			||||||
 | 
					  const port = 12321;
 | 
				
			||||||
 | 
					  let http;
 | 
				
			||||||
 | 
					  let firefox;
 | 
				
			||||||
 | 
					  let session;
 | 
				
			||||||
 | 
					  let browser;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  before(async() => {
 | 
				
			||||||
 | 
					    http = newApp().listen(port);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    firefox = await lanthan.firefox({
 | 
				
			||||||
 | 
					      spy: path.join(__dirname, '..'),
 | 
				
			||||||
 | 
					      builderf: (builder) => {
 | 
				
			||||||
 | 
					        builder.addFile('build/settings.js');
 | 
				
			||||||
 | 
					        builder.addFile('build/settings.html');
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    await firefox.session.installAddonFromPath(path.join(__dirname, '..'));
 | 
				
			||||||
 | 
					    session = firefox.session;
 | 
				
			||||||
 | 
					    browser = firefox.browser;
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  after(async() => {
 | 
				
			||||||
 | 
					    if (firefox) {
 | 
				
			||||||
 | 
					      await firefox.close();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    http.close();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  beforeEach(async() => {
 | 
				
			||||||
 | 
					    let tabs = await browser.tabs.query({});
 | 
				
			||||||
 | 
					    for (let tab of tabs.slice(1)) {
 | 
				
			||||||
 | 
					      await browser.tabs.remove(tab.id);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const updateTextarea = async(value) => {
 | 
				
			||||||
 | 
					    let textarea = await session.findElementByCSS('textarea');
 | 
				
			||||||
 | 
					    await session.executeScript(`document.querySelector('textarea').value = '${value}'`)
 | 
				
			||||||
 | 
					    await textarea.sendKeys(' ');
 | 
				
			||||||
 | 
					    await session.executeScript(() => document.querySelector('textarea').blur());
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('saves current config on blur', async () => {
 | 
				
			||||||
 | 
					    let url = await browser.runtime.getURL("build/settings.html")
 | 
				
			||||||
 | 
					    await session.navigateTo(url);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await updateTextarea(`{ "blacklist": [ "https://example.com" ] }`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let { settings } = await browser.storage.local.get('settings');
 | 
				
			||||||
 | 
					    assert.equal(settings.source, 'json')
 | 
				
			||||||
 | 
					    assert.equal(settings.json, '{ "blacklist": [ "https://example.com" ] } ')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await updateTextarea(`invalid json`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    settings = (await browser.storage.local.get('settings')).settings;
 | 
				
			||||||
 | 
					    assert.equal(settings.source, 'json')
 | 
				
			||||||
 | 
					    assert.equal(settings.json, '{ "blacklist": [ "https://example.com" ] } ')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let error = await session.findElementByCSS('.settings-ui-input-error');
 | 
				
			||||||
 | 
					    let text = await error.getText();
 | 
				
			||||||
 | 
					    assert.ok(text.startsWith('SyntaxError:'))
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('updates keymaps without reloading', async () => {
 | 
				
			||||||
 | 
					    await browser.tabs.create({ url: `http://127.0.0.1:${port}`, active: false });
 | 
				
			||||||
 | 
					    let url = await browser.runtime.getURL("build/settings.html")
 | 
				
			||||||
 | 
					    await session.navigateTo(url);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let handles = await session.getWindowHandles();
 | 
				
			||||||
 | 
					    await updateTextarea(`{ "keymaps": { "zz": { "type": "scroll.vertically", "count": 10 } } }`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await session.switchToWindow(handles[1]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let body = await session.findElementByCSS('body');
 | 
				
			||||||
 | 
					    await body.sendKeys('zz')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let y = await session.executeScript(() => window.pageYOffset);
 | 
				
			||||||
 | 
					    assert.equal(y, 640);
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										125
									
								
								e2e/options_form.test.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								e2e/options_form.test.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,125 @@
 | 
				
			||||||
 | 
					const lanthan = require('lanthan');
 | 
				
			||||||
 | 
					const path = require('path');
 | 
				
			||||||
 | 
					const assert = require('assert');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe("options form page", () => {
 | 
				
			||||||
 | 
					  let firefox;
 | 
				
			||||||
 | 
					  let session;
 | 
				
			||||||
 | 
					  let browser;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  beforeEach(async() => {
 | 
				
			||||||
 | 
					    firefox = await lanthan.firefox({
 | 
				
			||||||
 | 
					      spy: path.join(__dirname, '..'),
 | 
				
			||||||
 | 
					      builderf: (builder) => {
 | 
				
			||||||
 | 
					        builder.addFile('build/settings.js');
 | 
				
			||||||
 | 
					        builder.addFile('build/settings.html');
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    await firefox.session.installAddonFromPath(path.join(__dirname, '..'));
 | 
				
			||||||
 | 
					    session = firefox.session;
 | 
				
			||||||
 | 
					    browser = firefox.browser;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let tabs = await browser.tabs.query({});
 | 
				
			||||||
 | 
					    for (let tab of tabs.slice(1)) {
 | 
				
			||||||
 | 
					      await browser.tabs.remove(tab.id);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  afterEach(async() => {
 | 
				
			||||||
 | 
					    if (firefox) {
 | 
				
			||||||
 | 
					      await firefox.close();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const setBlacklistValue = async(nth, value) => {
 | 
				
			||||||
 | 
					    let selector = '.form-blacklist-form .column-url';
 | 
				
			||||||
 | 
					    let input = (await session.findElementsByCSS(selector))[nth];
 | 
				
			||||||
 | 
					    await input.sendKeys(value);
 | 
				
			||||||
 | 
					    await session.executeScript(`document.querySelectorAll('${selector}')[${nth}].blur()`);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const setSearchEngineValue = async(nth, name, url) => {
 | 
				
			||||||
 | 
					    let selector = '.form-search-form input.column-name';
 | 
				
			||||||
 | 
					    let input = (await session.findElementsByCSS(selector))[nth];
 | 
				
			||||||
 | 
					    await input.sendKeys(name);
 | 
				
			||||||
 | 
					    await session.executeScript(`document.querySelectorAll('${selector}')[${nth}].blur()`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    selector = '.form-search-form input.column-url';
 | 
				
			||||||
 | 
					    input = (await session.findElementsByCSS(selector))[nth];
 | 
				
			||||||
 | 
					    await input.sendKeys(url);
 | 
				
			||||||
 | 
					    await session.executeScript(`document.querySelectorAll('${selector}')[${nth}].blur()`);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('switch to form settings', async () => {
 | 
				
			||||||
 | 
					    let url = await browser.runtime.getURL("build/settings.html")
 | 
				
			||||||
 | 
					    await session.navigateTo(url);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let useFormInput = await session.findElementByCSS('#setting-source-form');
 | 
				
			||||||
 | 
					    await useFormInput.click();
 | 
				
			||||||
 | 
					    await session.acceptAlert();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let { settings } = await browser.storage.local.get('settings');
 | 
				
			||||||
 | 
					    assert.equal(settings.source, 'form')
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('add blacklist', async () => {
 | 
				
			||||||
 | 
					    let url = await browser.runtime.getURL("build/settings.html")
 | 
				
			||||||
 | 
					    await session.navigateTo(url);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let useFormInput = await session.findElementByCSS('#setting-source-form');
 | 
				
			||||||
 | 
					    await useFormInput.click();
 | 
				
			||||||
 | 
					    await session.acceptAlert();
 | 
				
			||||||
 | 
					    await session.executeScript(() => window.scrollBy(0, 1000));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // assert default
 | 
				
			||||||
 | 
					    let settings = (await browser.storage.local.get('settings')).settings;
 | 
				
			||||||
 | 
					    assert.deepEqual(settings.form.blacklist, [])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // add blacklist items
 | 
				
			||||||
 | 
					    let addButton = await session.findElementByCSS('.form-blacklist-form .ui-add-button');
 | 
				
			||||||
 | 
					    await addButton.click();
 | 
				
			||||||
 | 
					    await setBlacklistValue(0, 'google.com')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    settings = (await browser.storage.local.get('settings')).settings;
 | 
				
			||||||
 | 
					    assert.deepEqual(settings.form.blacklist, ['google.com'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await addButton.click();
 | 
				
			||||||
 | 
					    await setBlacklistValue(1, 'yahoo.com')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    settings = (await browser.storage.local.get('settings')).settings;
 | 
				
			||||||
 | 
					    assert.deepEqual(settings.form.blacklist, ['google.com', 'yahoo.com'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // delete first item
 | 
				
			||||||
 | 
					    let deleteButton = (await session.findElementsByCSS('.form-blacklist-form .ui-delete-button'))[0];
 | 
				
			||||||
 | 
					    await deleteButton.click()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    settings = (await browser.storage.local.get('settings')).settings;
 | 
				
			||||||
 | 
					    assert.deepEqual(settings.form.blacklist, ['yahoo.com'])
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('add search engines', async () => {
 | 
				
			||||||
 | 
					    let url = await browser.runtime.getURL("build/settings.html")
 | 
				
			||||||
 | 
					    await session.navigateTo(url);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let useFormInput = await session.findElementByCSS('#setting-source-form');
 | 
				
			||||||
 | 
					    await useFormInput.click();
 | 
				
			||||||
 | 
					    await session.acceptAlert();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // assert default
 | 
				
			||||||
 | 
					    let settings = (await browser.storage.local.get('settings')).settings;
 | 
				
			||||||
 | 
					    assert.deepEqual(settings.form.search.default, 'google');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // change default
 | 
				
			||||||
 | 
					    let radio = (await session.findElementsByCSS('.form-search-form input[type=radio]'))[2];
 | 
				
			||||||
 | 
					    await radio.click();
 | 
				
			||||||
 | 
					    settings = (await browser.storage.local.get('settings')).settings;
 | 
				
			||||||
 | 
					    assert.deepEqual(settings.form.search.default, 'bing');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let addButton = await session.findElementByCSS('.form-search-form .ui-add-button');
 | 
				
			||||||
 | 
					    await addButton.click();
 | 
				
			||||||
 | 
					    await setSearchEngineValue(6, 'yippy', 'https://www.yippy.com/search?query={}');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    settings = (await browser.storage.local.get('settings')).settings;
 | 
				
			||||||
 | 
					    assert.deepEqual(settings.form.search.engines[6], ['yippy', 'https://www.yippy.com/search?query={}']);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										6439
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										6439
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										63
									
								
								package.json
									
										
									
									
									
								
							
							
						
						
									
										63
									
								
								package.json
									
										
									
									
									
								
							| 
						 | 
					@ -3,12 +3,12 @@
 | 
				
			||||||
  "description": "Vim vixen",
 | 
					  "description": "Vim vixen",
 | 
				
			||||||
  "scripts": {
 | 
					  "scripts": {
 | 
				
			||||||
    "start": "webpack --mode development -w --debug --devtool inline-source-map",
 | 
					    "start": "webpack --mode development -w --debug --devtool inline-source-map",
 | 
				
			||||||
    "build": "NODE_ENV=production webpack --mode production --progress --display-error-details",
 | 
					    "build": "NODE_ENV=production webpack --mode production --progress --display-error-details --devtool inline-source-map",
 | 
				
			||||||
    "package": "npm run build && script/package",
 | 
					    "package": "npm run build && script/package",
 | 
				
			||||||
    "lint": "eslint --ext .js,.jsx,.ts,.tsx src",
 | 
					    "lint": "eslint --ext .js,.jsx,.ts,.tsx src",
 | 
				
			||||||
    "type-checks": "tsc --noEmit",
 | 
					    "type-checks": "tsc --noEmit",
 | 
				
			||||||
    "test": "karma start",
 | 
					    "test": "karma start",
 | 
				
			||||||
    "test:e2e": "mocha --timeout 8000 --retries 5 e2e"
 | 
					    "test:e2e": "mocha --timeout 10000 --retries 5 e2e"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "repository": {
 | 
					  "repository": {
 | 
				
			||||||
    "type": "git",
 | 
					    "type": "git",
 | 
				
			||||||
| 
						 | 
					@ -21,49 +21,50 @@
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "homepage": "https://github.com/ueokande/vim-vixen",
 | 
					  "homepage": "https://github.com/ueokande/vim-vixen",
 | 
				
			||||||
  "devDependencies": {
 | 
					  "devDependencies": {
 | 
				
			||||||
    "@types/chai": "^4.1.7",
 | 
					    "@types/chai": "^4.2.0",
 | 
				
			||||||
    "@types/mocha": "^5.2.6",
 | 
					    "@types/mocha": "^5.2.7",
 | 
				
			||||||
    "@types/prop-types": "^15.7.1",
 | 
					    "@types/prop-types": "^15.7.1",
 | 
				
			||||||
    "@types/react": "^16.8.18",
 | 
					    "@types/react": "^16.9.2",
 | 
				
			||||||
    "@types/react-dom": "^16.8.4",
 | 
					    "@types/react-dom": "^16.9.0",
 | 
				
			||||||
    "@types/react-redux": "^7.0.9",
 | 
					    "@types/react-redux": "^7.1.2",
 | 
				
			||||||
    "@types/redux-promise": "^0.5.28",
 | 
					    "@types/redux-promise": "^0.5.28",
 | 
				
			||||||
    "@types/sinon": "^7.0.11",
 | 
					    "@types/sinon": "^7.0.13",
 | 
				
			||||||
    "@typescript-eslint/eslint-plugin": "^1.9.0",
 | 
					    "@typescript-eslint/eslint-plugin": "^2.0.0",
 | 
				
			||||||
 | 
					    "@typescript-eslint/parser": "^2.0.0",
 | 
				
			||||||
    "chai": "^4.2.0",
 | 
					    "chai": "^4.2.0",
 | 
				
			||||||
    "css-loader": "^2.1.1",
 | 
					    "css-loader": "^3.2.0",
 | 
				
			||||||
    "eslint": "^5.16.0",
 | 
					    "eslint": "^6.2.2",
 | 
				
			||||||
    "eslint-plugin-react": "^7.13.0",
 | 
					    "eslint-plugin-react": "^7.14.3",
 | 
				
			||||||
    "html-webpack-plugin": "^3.2.0",
 | 
					    "html-webpack-plugin": "^3.2.0",
 | 
				
			||||||
    "jszip": "^3.2.1",
 | 
					    "jszip": "^3.2.2",
 | 
				
			||||||
    "karma": "^4.1.0",
 | 
					    "karma": "^4.2.0",
 | 
				
			||||||
    "karma-firefox-launcher": "^1.1.0",
 | 
					    "karma-firefox-launcher": "^1.2.0",
 | 
				
			||||||
    "karma-html2js-preprocessor": "^1.1.0",
 | 
					    "karma-html2js-preprocessor": "^1.1.0",
 | 
				
			||||||
    "karma-mocha": "^1.3.0",
 | 
					    "karma-mocha": "^1.3.0",
 | 
				
			||||||
    "karma-mocha-reporter": "^2.2.5",
 | 
					    "karma-mocha-reporter": "^2.2.5",
 | 
				
			||||||
    "karma-sinon": "^1.0.5",
 | 
					    "karma-sinon": "^1.0.5",
 | 
				
			||||||
    "karma-sourcemap-loader": "^0.3.7",
 | 
					    "karma-sourcemap-loader": "^0.3.7",
 | 
				
			||||||
    "karma-webpack": "^3.0.5",
 | 
					    "karma-webpack": "^4.0.2",
 | 
				
			||||||
    "lanthan": "git+https://github.com/ueokande/lanthan.git#master",
 | 
					    "lanthan": "git+https://github.com/ueokande/lanthan.git#master",
 | 
				
			||||||
    "mocha": "^6.1.4",
 | 
					    "mocha": "^6.2.0",
 | 
				
			||||||
    "node-sass": "^4.12.0",
 | 
					    "node-sass": "^4.12.0",
 | 
				
			||||||
    "react": "^16.8.6",
 | 
					    "react": "^16.9.0",
 | 
				
			||||||
    "react-dom": "^16.8.6",
 | 
					    "react-dom": "^16.9.0",
 | 
				
			||||||
    "react-redux": "^7.0.3",
 | 
					    "react-redux": "^7.1.1",
 | 
				
			||||||
    "react-test-renderer": "^16.8.6",
 | 
					    "react-test-renderer": "^16.9.0",
 | 
				
			||||||
    "redux": "^4.0.1",
 | 
					    "redux": "^4.0.4",
 | 
				
			||||||
    "redux-promise": "^0.6.0",
 | 
					    "redux-promise": "^0.6.0",
 | 
				
			||||||
    "reflect-metadata": "^0.1.13",
 | 
					    "reflect-metadata": "^0.1.13",
 | 
				
			||||||
    "sass-loader": "^7.1.0",
 | 
					    "sass-loader": "^7.3.1",
 | 
				
			||||||
    "sinon": "^7.3.2",
 | 
					    "sinon": "^7.4.1",
 | 
				
			||||||
    "sinon-chrome": "^3.0.1",
 | 
					    "sinon-chrome": "^3.0.1",
 | 
				
			||||||
    "style-loader": "^0.23.1",
 | 
					    "style-loader": "^1.0.0",
 | 
				
			||||||
    "ts-loader": "^6.0.1",
 | 
					    "ts-loader": "^6.0.4",
 | 
				
			||||||
    "tsyringe": "^3.2.0",
 | 
					    "tsyringe": "^3.3.0",
 | 
				
			||||||
    "typescript": "^3.4.5",
 | 
					    "typescript": "^3.6.2",
 | 
				
			||||||
    "web-ext-types": "^3.1.0",
 | 
					    "web-ext-types": "^3.2.1",
 | 
				
			||||||
    "webextensions-api-fake": "^0.8.0",
 | 
					    "webextensions-api-fake": "^0.8.0",
 | 
				
			||||||
    "webpack": "^4.32.2",
 | 
					    "webpack": "^4.39.2",
 | 
				
			||||||
    "webpack-cli": "^3.3.2"
 | 
					    "webpack-cli": "^3.3.7"
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -22,14 +22,14 @@ export default class ContentMessageClient {
 | 
				
			||||||
    return enabled as any as boolean;
 | 
					    return enabled as any as boolean;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  toggleAddonEnabled(tabId: number): Promise<void> {
 | 
					  async toggleAddonEnabled(tabId: number): Promise<void> {
 | 
				
			||||||
    return browser.tabs.sendMessage(tabId, {
 | 
					    await browser.tabs.sendMessage(tabId, {
 | 
				
			||||||
      type: messages.ADDON_TOGGLE_ENABLED,
 | 
					      type: messages.ADDON_TOGGLE_ENABLED,
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  scrollTo(tabId: number, x: number, y: number): Promise<void> {
 | 
					  async scrollTo(tabId: number, x: number, y: number): Promise<void> {
 | 
				
			||||||
    return browser.tabs.sendMessage(tabId, {
 | 
					    await browser.tabs.sendMessage(tabId, {
 | 
				
			||||||
      type: messages.TAB_SCROLL_TO,
 | 
					      type: messages.TAB_SCROLL_TO,
 | 
				
			||||||
      x,
 | 
					      x,
 | 
				
			||||||
      y,
 | 
					      y,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -26,11 +26,20 @@ export default class NotifyPresenter {
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async notifyInvalidSettings(): Promise<void> {
 | 
					  async notifyInvalidSettings(onclick: () => void): Promise<void> {
 | 
				
			||||||
    let title = `Loaded settings is invalid`;
 | 
					    let title = `Loaded settings is invalid`;
 | 
				
			||||||
    // eslint-disable-next-line max-len
 | 
					    // eslint-disable-next-line max-len
 | 
				
			||||||
    let message = 'The default settings is used due to the last saved settings is invalid.  Check your current settings from the add-on preference';
 | 
					    let message = 'The default settings is used due to the last saved settings is invalid.  Check your current settings from the add-on preference';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const listener = (id: string) => {
 | 
				
			||||||
 | 
					      if (id !== NOTIFICATION_ID_INVALID_SETTINGS) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      onclick();
 | 
				
			||||||
 | 
					      browser.notifications.onClicked.removeListener(listener);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    browser.notifications.onClicked.addListener(listener);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await browser.notifications.create(NOTIFICATION_ID_INVALID_SETTINGS, {
 | 
					    await browser.notifications.create(NOTIFICATION_ID_INVALID_SETTINGS, {
 | 
				
			||||||
      'type': 'basic',
 | 
					      'type': 'basic',
 | 
				
			||||||
      'iconUrl': browser.extension.getURL('resources/icon_48x48.png'),
 | 
					      'iconUrl': browser.extension.getURL('resources/icon_48x48.png'),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -21,7 +21,12 @@ export default class SettingUseCase {
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async reload(): Promise<Settings> {
 | 
					  async reload(): Promise<Settings> {
 | 
				
			||||||
    let data = await this.persistentSettingRepository.load();
 | 
					    let data;
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      data = await this.persistentSettingRepository.load();
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      this.showUnableToLoad(e);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    if (!data) {
 | 
					    if (!data) {
 | 
				
			||||||
      data = DefaultSettingData;
 | 
					      data = DefaultSettingData;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -30,10 +35,17 @@ export default class SettingUseCase {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      value = data.toSettings();
 | 
					      value = data.toSettings();
 | 
				
			||||||
    } catch (e) {
 | 
					    } catch (e) {
 | 
				
			||||||
      this.notifyPresenter.notifyInvalidSettings();
 | 
					      this.showUnableToLoad(e);
 | 
				
			||||||
      value = DefaultSettingData.toSettings();
 | 
					      value = DefaultSettingData.toSettings();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    this.settingRepository.update(value!!);
 | 
					    this.settingRepository.update(value!!);
 | 
				
			||||||
    return value;
 | 
					    return value;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private showUnableToLoad(e: Error) {
 | 
				
			||||||
 | 
					    console.error('unable to load settings', e);
 | 
				
			||||||
 | 
					    this.notifyPresenter.notifyInvalidSettings(() => {
 | 
				
			||||||
 | 
					      browser.runtime.openOptionsPage();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -25,12 +25,19 @@ class PropertiesForm extends React.Component<Props> {
 | 
				
			||||||
        Object.keys(types).map((name) => {
 | 
					        Object.keys(types).map((name) => {
 | 
				
			||||||
          let type = types[name];
 | 
					          let type = types[name];
 | 
				
			||||||
          let inputType = '';
 | 
					          let inputType = '';
 | 
				
			||||||
 | 
					          let onChange = this.bindValue.bind(this);
 | 
				
			||||||
          if (type === 'string') {
 | 
					          if (type === 'string') {
 | 
				
			||||||
            inputType = 'text';
 | 
					            inputType = 'text';
 | 
				
			||||||
          } else if (type === 'number') {
 | 
					          } else if (type === 'number') {
 | 
				
			||||||
            inputType = 'number';
 | 
					            inputType = 'number';
 | 
				
			||||||
          } else if (type === 'boolean') {
 | 
					          } else if (type === 'boolean') {
 | 
				
			||||||
            inputType = 'checkbox';
 | 
					            inputType = 'checkbox';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Settings are saved onBlur, but checkbox does not fire it
 | 
				
			||||||
 | 
					            onChange = (e) => {
 | 
				
			||||||
 | 
					              this.bindValue(e);
 | 
				
			||||||
 | 
					              this.props.onBlur();
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
          } else {
 | 
					          } else {
 | 
				
			||||||
            return null;
 | 
					            return null;
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
| 
						 | 
					@ -40,7 +47,7 @@ class PropertiesForm extends React.Component<Props> {
 | 
				
			||||||
              <input type={inputType} name={name}
 | 
					              <input type={inputType} name={name}
 | 
				
			||||||
                className='column-input'
 | 
					                className='column-input'
 | 
				
			||||||
                value={value[name] ? value[name] : ''}
 | 
					                value={value[name] ? value[name] : ''}
 | 
				
			||||||
                onChange={this.bindValue.bind(this)}
 | 
					                onChange={onChange}
 | 
				
			||||||
                onBlur={this.props.onBlur}
 | 
					                onBlur={this.props.onBlur}
 | 
				
			||||||
                checked={value[name]}
 | 
					                checked={value[name]}
 | 
				
			||||||
              />
 | 
					              />
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,7 +5,12 @@ export const load = async(): Promise<SettingData> => {
 | 
				
			||||||
  if (!settings) {
 | 
					  if (!settings) {
 | 
				
			||||||
    return DefaultSettingData;
 | 
					    return DefaultSettingData;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
    return SettingData.valueOf(settings as any);
 | 
					    return SettingData.valueOf(settings as any);
 | 
				
			||||||
 | 
					  } catch (e) {
 | 
				
			||||||
 | 
					    console.error('unable to load settings', e);
 | 
				
			||||||
 | 
					    return DefaultSettingData;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const save = (data: SettingData) => {
 | 
					export const save = (data: SettingData) => {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -21,13 +21,6 @@ export default interface Settings {
 | 
				
			||||||
  blacklist: string[];
 | 
					  blacklist: string[];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const DefaultProperties: Properties = PropertyDefs.defs.reduce(
 | 
					 | 
				
			||||||
  (o: {[name: string]: PropertyDefs.Type}, def) => {
 | 
					 | 
				
			||||||
    o[def.name] = def.defaultValue;
 | 
					 | 
				
			||||||
    return o;
 | 
					 | 
				
			||||||
  }, {}) as Properties;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const keymapsValueOf = (o: any): Keymaps => {
 | 
					export const keymapsValueOf = (o: any): Keymaps => {
 | 
				
			||||||
  return Object.keys(o).reduce((keymaps: Keymaps, key: string): Keymaps => {
 | 
					  return Object.keys(o).reduce((keymaps: Keymaps, key: string): Keymaps => {
 | 
				
			||||||
    let op = operations.valueOf(o[key]);
 | 
					    let op = operations.valueOf(o[key]);
 | 
				
			||||||
| 
						 | 
					@ -82,7 +75,7 @@ export const propertiesValueOf = (o: any): Properties => {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  return {
 | 
					  return {
 | 
				
			||||||
    ...DefaultProperties,
 | 
					    ...PropertyDefs.defaultValues,
 | 
				
			||||||
    ...o,
 | 
					    ...o,
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -368,7 +368,9 @@ 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} (${typeof obj[name]})'`,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -376,7 +378,9 @@ const assertOptionalString = (obj: any, name: string, values?: string[]) => {
 | 
				
			||||||
  if (Object.prototype.hasOwnProperty.call(obj, name)) {
 | 
					  if (Object.prototype.hasOwnProperty.call(obj, name)) {
 | 
				
			||||||
    let value = obj[name];
 | 
					    let value = obj[name];
 | 
				
			||||||
    if (typeof value !== 'string') {
 | 
					    if (typeof value !== 'string') {
 | 
				
			||||||
      throw new TypeError(`Not a string parameter: '${name}'`);
 | 
					      throw new TypeError(
 | 
				
			||||||
 | 
					        `Not a string parameter: '${name}' (${typeof value})`,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (values && values.length && values.indexOf(value) === -1) {
 | 
					    if (values && values.length && values.indexOf(value) === -1) {
 | 
				
			||||||
      // eslint-disable-next-line max-len
 | 
					      // eslint-disable-next-line max-len
 | 
				
			||||||
| 
						 | 
					@ -421,32 +425,32 @@ export const valueOf = (o: any): Operation => {
 | 
				
			||||||
    assertOptionalBoolean(o, 'background');
 | 
					    assertOptionalBoolean(o, 'background');
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
      type: FOLLOW_START,
 | 
					      type: FOLLOW_START,
 | 
				
			||||||
      newTab: Boolean(typeof o.newTab === undefined ? false : o.newTab),
 | 
					      newTab: Boolean(typeof o.newTab === 'undefined' ? false : o.newTab),
 | 
				
			||||||
      background: Boolean(typeof o.background === undefined ? true : o.background), // eslint-disable-line max-len
 | 
					      background: Boolean(typeof o.background === 'undefined' ? true : o.background), // eslint-disable-line max-len
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  case PAGE_HOME:
 | 
					  case PAGE_HOME:
 | 
				
			||||||
    assertOptionalBoolean(o, 'newTab');
 | 
					    assertOptionalBoolean(o, 'newTab');
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
      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:
 | 
					  case TAB_CLOSE:
 | 
				
			||||||
    assertOptionalString(o, 'select', ['left', 'right']);
 | 
					    assertOptionalString(o, 'select', ['left', 'right']);
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
      type: TAB_CLOSE,
 | 
					      type: TAB_CLOSE,
 | 
				
			||||||
      select: (typeof o.select === undefined ? 'right' : o.select),
 | 
					      select: (typeof o.select === 'undefined' ? 'right' : o.select),
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  case TAB_RELOAD:
 | 
					  case TAB_RELOAD:
 | 
				
			||||||
    assertOptionalBoolean(o, 'cache');
 | 
					    assertOptionalBoolean(o, 'cache');
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
      type: TAB_RELOAD,
 | 
					      type: TAB_RELOAD,
 | 
				
			||||||
      cache: Boolean(typeof o.cache === undefined ? false : o.cache),
 | 
					      cache: Boolean(typeof o.cache === 'undefined' ? false : o.cache),
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  case URLS_PASTE:
 | 
					  case URLS_PASTE:
 | 
				
			||||||
    assertOptionalBoolean(o, 'newTab');
 | 
					    assertOptionalBoolean(o, 'newTab');
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
      type: URLS_PASTE,
 | 
					      type: URLS_PASTE,
 | 
				
			||||||
      newTab: Boolean(typeof o.newTab === undefined ? false : o.newTab),
 | 
					      newTab: Boolean(typeof o.newTab === 'undefined' ? false : o.newTab),
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  case INTERNAL_OPEN_URL:
 | 
					  case INTERNAL_OPEN_URL:
 | 
				
			||||||
    assertOptionalBoolean(o, 'newTab');
 | 
					    assertOptionalBoolean(o, 'newTab');
 | 
				
			||||||
| 
						 | 
					@ -456,9 +460,9 @@ export const valueOf = (o: any): Operation => {
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
      type: INTERNAL_OPEN_URL,
 | 
					      type: INTERNAL_OPEN_URL,
 | 
				
			||||||
      url: o.url,
 | 
					      url: o.url,
 | 
				
			||||||
      newTab: Boolean(typeof o.newTab === undefined ? false : o.newTab),
 | 
					      newTab: Boolean(typeof o.newTab === 'undefined' ? false : o.newTab),
 | 
				
			||||||
      newWindow: Boolean(typeof o.newWindow === undefined ? false : o.newWindow), // eslint-disable-line max-len
 | 
					      newWindow: Boolean(typeof o.newWindow === 'undefined' ? false : o.newWindow), // eslint-disable-line max-len
 | 
				
			||||||
      background: Boolean(typeof o.background === undefined ? true : o.background), // eslint-disable-line max-len
 | 
					      background: Boolean(typeof o.background === 'undefined' ? true : o.background), // eslint-disable-line max-len
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  case CANCEL:
 | 
					  case CANCEL:
 | 
				
			||||||
  case ADDON_ENABLE:
 | 
					  case ADDON_ENABLE:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -48,3 +48,9 @@ export const defs: Def[] = [
 | 
				
			||||||
    'which are completed at the open page',
 | 
					    'which are completed at the open page',
 | 
				
			||||||
    'sbh'),
 | 
					    'sbh'),
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const defaultValues = {
 | 
				
			||||||
 | 
					  hintchars: 'abcdefghijklmnopqrstuvwxyz',
 | 
				
			||||||
 | 
					  smoothscroll: false,
 | 
				
			||||||
 | 
					  complete: 'sbh',
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Reference in a new issue