commit
						68673957ed
					
				
					 11 changed files with 4115 additions and 1377 deletions
				
			
		
							
								
								
									
										7
									
								
								QA.md
									
										
									
									
									
								
							
							
						
						
									
										7
									
								
								QA.md
									
										
									
									
									
								
							| 
						 | 
					@ -20,10 +20,8 @@ The behaviors of the console are tested in [Console section](#consoles).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Properties
 | 
					### Properties
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- [ ] Configure custom hint character by `:set hintchars=012345678`
 | 
					- [ ] Toggle smooth scroll by `:set smoothscroll` and `:set nosmoothscroll`
 | 
				
			||||||
- [ ] Smooth scroll by `:set smoothscroll`
 | 
					- [ ] Configure smooth scroll by settings `"smoothscroll": true` and `"smoothscroll": false`
 | 
				
			||||||
- [ ] Non-smooth scroll by `:set nosmoothscroll`
 | 
					 | 
				
			||||||
- [ ] Configure smooth scroll by settings `"smoothscroll": true`, `"smoothscroll": false`
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Settings
 | 
					### Settings
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -45,7 +43,6 @@ The behaviors of the console are tested in [Console section](#consoles).
 | 
				
			||||||
##### Updating
 | 
					##### Updating
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- [ ] changes are updated on textarea blure when no errors
 | 
					- [ ] changes are updated on textarea blure when no errors
 | 
				
			||||||
- [ ] changes are not updated on textarea blure when errors occurs
 | 
					 | 
				
			||||||
- [ ] 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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,6 +3,7 @@ const lanthan = require('lanthan');
 | 
				
			||||||
const path = require('path');
 | 
					const path = require('path');
 | 
				
			||||||
const assert = require('assert');
 | 
					const assert = require('assert');
 | 
				
			||||||
const eventually = require('./eventually');
 | 
					const eventually = require('./eventually');
 | 
				
			||||||
 | 
					const Console = require('./lib/Console');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const Key = lanthan.Key;
 | 
					const Key = lanthan.Key;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -114,7 +115,6 @@ describe('follow properties test', () => {
 | 
				
			||||||
      assert.equal(await hints[2].getStyle('display'), 'block');
 | 
					      assert.equal(await hints[2].getStyle('display'), 'block');
 | 
				
			||||||
      assert.equal(await hints[3].getStyle('display'), 'block');
 | 
					      assert.equal(await hints[3].getStyle('display'), 'block');
 | 
				
			||||||
      assert.equal(await hints[4].getStyle('display'), 'none');
 | 
					      assert.equal(await hints[4].getStyle('display'), 'none');
 | 
				
			||||||
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -147,4 +147,36 @@ describe('follow properties test', () => {
 | 
				
			||||||
      assert.equal(tabs[1].active, false);
 | 
					      assert.equal(tabs[1].active, false);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should show hints with hintchars by settings', async () => {
 | 
				
			||||||
 | 
					    let c = new Console(session);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await body.sendKeys(':');
 | 
				
			||||||
 | 
					    await session.switchToFrame(0);
 | 
				
			||||||
 | 
					    await c.sendKeys('set hintchars=abc', Key.Enter);
 | 
				
			||||||
 | 
					    await session.switchToParentFrame();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await body.sendKeys('f');
 | 
				
			||||||
 | 
					    await eventually(async() => {
 | 
				
			||||||
 | 
					      let hints = await session.findElementsByCSS('.vimvixen-hint');
 | 
				
			||||||
 | 
					      assert.equal(hints.length, 5);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      assert.equal(await hints[0].getText(), 'A');
 | 
				
			||||||
 | 
					      assert.equal(await hints[1].getText(), 'B');
 | 
				
			||||||
 | 
					      assert.equal(await hints[2].getText(), 'C');
 | 
				
			||||||
 | 
					      assert.equal(await hints[3].getText(), 'AA');
 | 
				
			||||||
 | 
					      assert.equal(await hints[4].getText(), 'AB');
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await body.sendKeys('a');
 | 
				
			||||||
 | 
					    await eventually(async() => {
 | 
				
			||||||
 | 
					      let hints = await session.findElementsByCSS('.vimvixen-hint');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      assert.equal(await hints[0].getStyle('display'), 'block');
 | 
				
			||||||
 | 
					      assert.equal(await hints[1].getStyle('display'), 'none');
 | 
				
			||||||
 | 
					      assert.equal(await hints[2].getStyle('display'), 'none');
 | 
				
			||||||
 | 
					      assert.equal(await hints[3].getStyle('display'), 'block');
 | 
				
			||||||
 | 
					      assert.equal(await hints[4].getStyle('display'), 'block');
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										5371
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										5371
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										12
									
								
								package.json
									
										
									
									
									
								
							
							
						
						
									
										12
									
								
								package.json
									
										
									
									
									
								
							| 
						 | 
					@ -24,16 +24,16 @@
 | 
				
			||||||
    "@types/chai": "^4.1.7",
 | 
					    "@types/chai": "^4.1.7",
 | 
				
			||||||
    "@types/mocha": "^5.2.6",
 | 
					    "@types/mocha": "^5.2.6",
 | 
				
			||||||
    "@types/prop-types": "^15.7.1",
 | 
					    "@types/prop-types": "^15.7.1",
 | 
				
			||||||
    "@types/react": "^16.8.15",
 | 
					    "@types/react": "^16.8.18",
 | 
				
			||||||
    "@types/react-dom": "^16.8.4",
 | 
					    "@types/react-dom": "^16.8.4",
 | 
				
			||||||
    "@types/react-redux": "^7.0.8",
 | 
					    "@types/react-redux": "^7.0.9",
 | 
				
			||||||
    "@types/redux-promise": "^0.5.28",
 | 
					    "@types/redux-promise": "^0.5.28",
 | 
				
			||||||
    "@types/sinon": "^7.0.11",
 | 
					    "@types/sinon": "^7.0.11",
 | 
				
			||||||
    "@typescript-eslint/eslint-plugin": "^1.9.0",
 | 
					    "@typescript-eslint/eslint-plugin": "^1.9.0",
 | 
				
			||||||
    "chai": "^4.2.0",
 | 
					    "chai": "^4.2.0",
 | 
				
			||||||
    "css-loader": "^2.1.1",
 | 
					    "css-loader": "^2.1.1",
 | 
				
			||||||
    "eslint": "^5.16.0",
 | 
					    "eslint": "^5.16.0",
 | 
				
			||||||
    "eslint-plugin-react": "^7.12.4",
 | 
					    "eslint-plugin-react": "^7.13.0",
 | 
				
			||||||
    "html-webpack-plugin": "^3.2.0",
 | 
					    "html-webpack-plugin": "^3.2.0",
 | 
				
			||||||
    "jszip": "^3.2.1",
 | 
					    "jszip": "^3.2.1",
 | 
				
			||||||
    "karma": "^4.1.0",
 | 
					    "karma": "^4.1.0",
 | 
				
			||||||
| 
						 | 
					@ -62,8 +62,8 @@
 | 
				
			||||||
    "tsyringe": "^3.2.0",
 | 
					    "tsyringe": "^3.2.0",
 | 
				
			||||||
    "typescript": "^3.4.5",
 | 
					    "typescript": "^3.4.5",
 | 
				
			||||||
    "web-ext-types": "^3.1.0",
 | 
					    "web-ext-types": "^3.1.0",
 | 
				
			||||||
    "webextensions-api-fake": "^0.7.4",
 | 
					    "webextensions-api-fake": "^0.8.0",
 | 
				
			||||||
    "webpack": "^4.30.0",
 | 
					    "webpack": "^4.32.2",
 | 
				
			||||||
    "webpack-cli": "^3.3.1"
 | 
					    "webpack-cli": "^3.3.2"
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -38,6 +38,9 @@ export default class CompletionsUseCase {
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async queryOpen(name: string, keywords: string): Promise<CompletionGroup[]> {
 | 
					  async queryOpen(name: string, keywords: string): Promise<CompletionGroup[]> {
 | 
				
			||||||
 | 
					    // TODO This logic contains view entities.  They should be defined on
 | 
				
			||||||
 | 
					    // content script
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let settings = await this.settingRepository.get();
 | 
					    let settings = await this.settingRepository.get();
 | 
				
			||||||
    let groups: CompletionGroup[] = [];
 | 
					    let groups: CompletionGroup[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -195,7 +198,7 @@ export default class CompletionsUseCase {
 | 
				
			||||||
      .map(pages => filters.filterByPathname(pages, COMPLETION_ITEM_LIMIT))
 | 
					      .map(pages => filters.filterByPathname(pages, COMPLETION_ITEM_LIMIT))
 | 
				
			||||||
      .map(pages => filters.filterByOrigin(pages, COMPLETION_ITEM_LIMIT))[0]
 | 
					      .map(pages => filters.filterByOrigin(pages, COMPLETION_ITEM_LIMIT))[0]
 | 
				
			||||||
      .sort((x: HistoryItem, y: HistoryItem): number => {
 | 
					      .sort((x: HistoryItem, y: HistoryItem): number => {
 | 
				
			||||||
        return Number(x.visitCount) - Number(y.visitCount);
 | 
					        return Number(y.visitCount) - Number(x.visitCount);
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
      .slice(0, COMPLETION_ITEM_LIMIT);
 | 
					      .slice(0, COMPLETION_ITEM_LIMIT);
 | 
				
			||||||
    return histories.map(page => ({
 | 
					    return histories.map(page => ({
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -63,16 +63,16 @@ export default class FollowSlaveUseCase {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (hint instanceof LinkHint) {
 | 
					    if (hint instanceof LinkHint) {
 | 
				
			||||||
      let url = hint.getLink();
 | 
					      let url = hint.getLink();
 | 
				
			||||||
      // ignore taget='_blank'
 | 
					      let openNewTab = newTab;
 | 
				
			||||||
      if (!newTab && hint.getLinkTarget() === '_blank') {
 | 
					      // Open link by background script in order to prevent a popup block
 | 
				
			||||||
        hint.click();
 | 
					      if (hint.getLinkTarget() === '_blank') {
 | 
				
			||||||
        return;
 | 
					        openNewTab = true;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      // eslint-disable-next-line no-script-url
 | 
					      // eslint-disable-next-line no-script-url
 | 
				
			||||||
      if (!url || url === '#' || url.toLowerCase().startsWith('javascript:')) {
 | 
					      if (!url || url === '#' || url.toLowerCase().startsWith('javascript:')) {
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      await this.tabsClient.openUrl(url, newTab, background);
 | 
					      await this.tabsClient.openUrl(url, openNewTab, background);
 | 
				
			||||||
    } else if (hint instanceof InputHint) {
 | 
					    } else if (hint instanceof InputHint) {
 | 
				
			||||||
      hint.activate();
 | 
					      hint.activate();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -54,6 +54,7 @@ class SearchForm extends React.Component<Props> {
 | 
				
			||||||
    </div>;
 | 
					    </div>;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // eslint-disable-next-line max-statements
 | 
				
			||||||
  bindValue(e: any) {
 | 
					  bindValue(e: any) {
 | 
				
			||||||
    let value = this.props.value.toJSON();
 | 
					    let value = this.props.value.toJSON();
 | 
				
			||||||
    let name = e.target.name;
 | 
					    let name = e.target.name;
 | 
				
			||||||
| 
						 | 
					@ -72,8 +73,12 @@ class SearchForm extends React.Component<Props> {
 | 
				
			||||||
      next.default = value.engines[index][0];
 | 
					      next.default = value.engines[index][0];
 | 
				
			||||||
    } else if (name === 'add') {
 | 
					    } else if (name === 'add') {
 | 
				
			||||||
      next.engines.push(['', '']);
 | 
					      next.engines.push(['', '']);
 | 
				
			||||||
    } else if (name === 'delete') {
 | 
					    } else if (name === 'delete' && value.engines.length > 1) {
 | 
				
			||||||
      next.engines.splice(index, 1);
 | 
					      next.engines.splice(index, 1);
 | 
				
			||||||
 | 
					      if (value.engines[index][0] === value.default) {
 | 
				
			||||||
 | 
					        let nextIndex = Math.min(index, next.engines.length - 1);
 | 
				
			||||||
 | 
					        next.default = next.engines[nextIndex][0];
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.props.onChange(FormSearch.valueOf(next));
 | 
					    this.props.onChange(FormSearch.valueOf(next));
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -175,6 +175,7 @@ class SettingsComponent extends React.Component<Props> {
 | 
				
			||||||
    if (from === 'form' && value === 'json') {
 | 
					    if (from === 'form' && value === 'json') {
 | 
				
			||||||
      this.props.dispatch(settingActions.switchToJson(
 | 
					      this.props.dispatch(settingActions.switchToJson(
 | 
				
			||||||
        this.props.form as FormSettings));
 | 
					        this.props.form as FormSettings));
 | 
				
			||||||
 | 
					      this.save();
 | 
				
			||||||
    } else if (from === 'json' && value === 'form') {
 | 
					    } else if (from === 'json' && value === 'form') {
 | 
				
			||||||
      let b = window.confirm(DO_YOU_WANT_TO_CONTINUE);
 | 
					      let b = window.confirm(DO_YOU_WANT_TO_CONTINUE);
 | 
				
			||||||
      if (!b) {
 | 
					      if (!b) {
 | 
				
			||||||
| 
						 | 
					@ -183,6 +184,7 @@ class SettingsComponent extends React.Component<Props> {
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      this.props.dispatch(
 | 
					      this.props.dispatch(
 | 
				
			||||||
        settingActions.switchToForm(this.props.json as JSONSettings));
 | 
					        settingActions.switchToForm(this.props.json as JSONSettings));
 | 
				
			||||||
 | 
					      this.save();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,3 +1,5 @@
 | 
				
			||||||
 | 
					/* eslint-disable max-len */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const fields = [
 | 
					const fields = [
 | 
				
			||||||
  [
 | 
					  [
 | 
				
			||||||
    ['scroll.vertically?{"count":1}', 'Scroll down'],
 | 
					    ['scroll.vertically?{"count":1}', 'Scroll down'],
 | 
				
			||||||
| 
						 | 
					@ -19,8 +21,8 @@ const fields = [
 | 
				
			||||||
    ['tabs.close', 'Close a tab'],
 | 
					    ['tabs.close', 'Close a tab'],
 | 
				
			||||||
    ['tabs.close.right', 'Close tabs to the right'],
 | 
					    ['tabs.close.right', 'Close tabs to the right'],
 | 
				
			||||||
    ['tabs.reopen', 'Reopen closed tab'],
 | 
					    ['tabs.reopen', 'Reopen closed tab'],
 | 
				
			||||||
    ['tabs.next?{"count":1}', 'Select next Tab'],
 | 
					    ['tabs.next', 'Select next Tab'],
 | 
				
			||||||
    ['tabs.prev?{"count":1}', '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'],
 | 
				
			||||||
| 
						 | 
					@ -28,8 +30,8 @@ const fields = [
 | 
				
			||||||
    ['tabs.pin.toggle', 'Toggle pinned state'],
 | 
					    ['tabs.pin.toggle', 'Toggle pinned state'],
 | 
				
			||||||
    ['tabs.duplicate', 'Duplicate a tab'],
 | 
					    ['tabs.duplicate', 'Duplicate a tab'],
 | 
				
			||||||
  ], [
 | 
					  ], [
 | 
				
			||||||
    ['follow.start?{"newTab":false}', 'Follow a link'],
 | 
					    ['follow.start?{"newTab":false,"background":false}', 'Follow a link'],
 | 
				
			||||||
    ['follow.start?{"newTab":true}', 'Follow a link in new tab'],
 | 
					    ['follow.start?{"newTab":true,"background":false}', 'Follow a link in new tab'],
 | 
				
			||||||
    ['navigate.history.prev', 'Go back in histories'],
 | 
					    ['navigate.history.prev', 'Go back in histories'],
 | 
				
			||||||
    ['navigate.history.next', 'Go forward in histories'],
 | 
					    ['navigate.history.next', 'Go forward in histories'],
 | 
				
			||||||
    ['navigate.link.next', 'Open next link'],
 | 
					    ['navigate.link.next', 'Open next link'],
 | 
				
			||||||
| 
						 | 
					@ -37,7 +39,7 @@ const fields = [
 | 
				
			||||||
    ['navigate.parent', 'Go to parent directory'],
 | 
					    ['navigate.parent', 'Go to parent directory'],
 | 
				
			||||||
    ['navigate.root', 'Go to root directory'],
 | 
					    ['navigate.root', 'Go to root directory'],
 | 
				
			||||||
    ['page.source', 'Open page source'],
 | 
					    ['page.source', 'Open page source'],
 | 
				
			||||||
    ['page.home', 'Open start page to current tab'],
 | 
					    ['page.home?{"newTab":false}', 'Open start page to current tab'],
 | 
				
			||||||
    ['page.home?{"newTab":true}', 'Open start page in new tab'],
 | 
					    ['page.home?{"newTab":true}', 'Open start page in new tab'],
 | 
				
			||||||
    ['focus.input', 'Focus input'],
 | 
					    ['focus.input', 'Focus input'],
 | 
				
			||||||
  ], [
 | 
					  ], [
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -101,17 +101,23 @@ export const blacklistValueOf = (o: any): string[] => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const valueOf = (o: any): Settings => {
 | 
					export const valueOf = (o: any): Settings => {
 | 
				
			||||||
  let settings = { ...DefaultSetting };
 | 
					  let settings = { ...DefaultSetting };
 | 
				
			||||||
  if (Object.prototype.hasOwnProperty.call(o, 'keymaps')) {
 | 
					  for (let key of Object.keys(o)) {
 | 
				
			||||||
    settings.keymaps = keymapsValueOf(o.keymaps);
 | 
					    switch (key) {
 | 
				
			||||||
  }
 | 
					    case 'keymaps':
 | 
				
			||||||
  if (Object.prototype.hasOwnProperty.call(o, 'search')) {
 | 
					      settings.keymaps = keymapsValueOf(o.keymaps);
 | 
				
			||||||
    settings.search = searchValueOf(o.search);
 | 
					      break;
 | 
				
			||||||
  }
 | 
					    case 'search':
 | 
				
			||||||
  if (Object.prototype.hasOwnProperty.call(o, 'properties')) {
 | 
					      settings.search = searchValueOf(o.search);
 | 
				
			||||||
    settings.properties = propertiesValueOf(o.properties);
 | 
					      break;
 | 
				
			||||||
  }
 | 
					    case 'properties':
 | 
				
			||||||
  if (Object.prototype.hasOwnProperty.call(o, 'blacklist')) {
 | 
					      settings.properties = propertiesValueOf(o.properties);
 | 
				
			||||||
    settings.blacklist = blacklistValueOf(o.blacklist);
 | 
					      break;
 | 
				
			||||||
 | 
					    case 'blacklist':
 | 
				
			||||||
 | 
					      settings.blacklist = blacklistValueOf(o.blacklist);
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      throw new TypeError('unknown setting: ' + key);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  return settings;
 | 
					  return settings;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -186,5 +186,9 @@ describe('Settings', () => {
 | 
				
			||||||
      expect(value.search.engines).to.be.an('object');
 | 
					      expect(value.search.engines).to.be.an('object');
 | 
				
			||||||
      expect(value.blacklist).to.be.empty;
 | 
					      expect(value.blacklist).to.be.empty;
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it('throws a TypeError with an unknown field', () => {
 | 
				
			||||||
 | 
					      expect(() => settings.valueOf({ name: 'alice' })).to.throw(TypeError)
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Reference in a new issue