Add partial blacklist form
This commit is contained in:
parent
7528fe831f
commit
fa6dfb0395
8 changed files with 185 additions and 58 deletions
|
@ -2,17 +2,17 @@ import './BlacklistForm.scss';
|
|||
import AddButton from '../ui/AddButton';
|
||||
import DeleteButton from '../ui/DeleteButton';
|
||||
import React from 'react';
|
||||
import { BlacklistJSON } from '../../../shared/settings/Blacklist';
|
||||
import Blacklist, { BlacklistItem } from '../../../shared/settings/Blacklist';
|
||||
|
||||
interface Props {
|
||||
value: BlacklistJSON;
|
||||
onChange: (value: BlacklistJSON) => void;
|
||||
value: Blacklist;
|
||||
onChange: (value: Blacklist) => void;
|
||||
onBlur: () => void;
|
||||
}
|
||||
|
||||
class BlacklistForm extends React.Component<Props> {
|
||||
public static defaultProps: Props = {
|
||||
value: [],
|
||||
value: new Blacklist([]),
|
||||
onChange: () => {},
|
||||
onBlur: () => {},
|
||||
};
|
||||
|
@ -20,15 +20,13 @@ class BlacklistForm extends React.Component<Props> {
|
|||
render() {
|
||||
return <div className='form-blacklist-form'>
|
||||
{
|
||||
this.props.value
|
||||
.map((item, index) => {
|
||||
if (typeof item !== 'string') {
|
||||
// TODO support partial blacklist;
|
||||
this.props.value.items.map((item, index) => {
|
||||
if (item.partial) {
|
||||
return null;
|
||||
}
|
||||
return <div key={index} className='form-blacklist-form-row'>
|
||||
<input data-index={index} type='text' name='url'
|
||||
className='column-url' value={item}
|
||||
className='column-url' value={item.pattern}
|
||||
onChange={this.bindValue.bind(this)}
|
||||
onBlur={this.props.onBlur}
|
||||
/>
|
||||
|
@ -47,17 +45,17 @@ class BlacklistForm extends React.Component<Props> {
|
|||
bindValue(e: any) {
|
||||
let name = e.target.name;
|
||||
let index = e.target.getAttribute('data-index');
|
||||
let next = this.props.value.slice();
|
||||
let items = this.props.value.items;
|
||||
|
||||
if (name === 'url') {
|
||||
next[index] = e.target.value;
|
||||
items[index] = new BlacklistItem(e.target.value, false, []);
|
||||
} else if (name === 'add') {
|
||||
next.push('');
|
||||
items.push(new BlacklistItem('', false, []));
|
||||
} else if (name === 'delete') {
|
||||
next.splice(index, 1);
|
||||
items.splice(index, 1);
|
||||
}
|
||||
|
||||
this.props.onChange(next);
|
||||
this.props.onChange(new Blacklist(items));
|
||||
if (name === 'delete') {
|
||||
this.props.onBlur();
|
||||
}
|
||||
|
|
28
src/settings/components/form/PartialBlacklistForm.scss
Normal file
28
src/settings/components/form/PartialBlacklistForm.scss
Normal file
|
@ -0,0 +1,28 @@
|
|||
.form-partial-blacklist-form {
|
||||
@mixin row-base {
|
||||
display: flex;
|
||||
|
||||
.column-url {
|
||||
flex: 5;
|
||||
min-width: 0;
|
||||
}
|
||||
.column-keys {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.column-delete {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&-header {
|
||||
@include row-base;
|
||||
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
&-row {
|
||||
@include row-base;
|
||||
}
|
||||
}
|
79
src/settings/components/form/PartialBlacklistForm.tsx
Normal file
79
src/settings/components/form/PartialBlacklistForm.tsx
Normal file
|
@ -0,0 +1,79 @@
|
|||
import './PartialBlacklistForm.scss';
|
||||
import AddButton from '../ui/AddButton';
|
||||
import DeleteButton from '../ui/DeleteButton';
|
||||
import React from 'react';
|
||||
import Blacklist, { BlacklistItem } from '../../../shared/settings/Blacklist';
|
||||
|
||||
interface Props {
|
||||
value: Blacklist;
|
||||
onChange: (value: Blacklist) => void;
|
||||
onBlur: () => void;
|
||||
}
|
||||
|
||||
class PartialBlacklistForm extends React.Component<Props> {
|
||||
public static defaultProps: Props = {
|
||||
value: new Blacklist([]),
|
||||
onChange: () => {},
|
||||
onBlur: () => {},
|
||||
};
|
||||
|
||||
render() {
|
||||
return <div className='form-partial-blacklist-form'>
|
||||
<div className='form-partial-blacklist-form-header'>
|
||||
<div className='column-url'>URL</div>
|
||||
<div className='column-keys'>Keys</div>
|
||||
</div>
|
||||
{
|
||||
this.props.value.items.map((item, index) => {
|
||||
if (!item.partial) {
|
||||
return null;
|
||||
}
|
||||
return <div key={index} className='form-partial-blacklist-form-row'>
|
||||
<input data-index={index} type='text' name='url'
|
||||
className='column-url' value={item.pattern}
|
||||
onChange={this.bindValue.bind(this)}
|
||||
onBlur={this.props.onBlur}
|
||||
/>
|
||||
<input data-index={index} type='text' name='keys'
|
||||
className='column-keys' value={item.keys.join(',')}
|
||||
onChange={this.bindValue.bind(this)}
|
||||
onBlur={this.props.onBlur}
|
||||
/>
|
||||
<DeleteButton data-index={index} name='delete'
|
||||
onClick={this.bindValue.bind(this)}
|
||||
onBlur={this.props.onBlur}
|
||||
/>
|
||||
</div>;
|
||||
})
|
||||
}
|
||||
<AddButton name='add' style={{ float: 'right' }}
|
||||
onClick={this.bindValue.bind(this)} />
|
||||
</div>;
|
||||
}
|
||||
|
||||
bindValue(e: any) {
|
||||
let name = e.target.name;
|
||||
let index = e.target.getAttribute('data-index');
|
||||
let items = this.props.value.items;
|
||||
|
||||
if (name === 'url') {
|
||||
let current = items[index];
|
||||
items[index] = new BlacklistItem(e.target.value, true, current.keys);
|
||||
} else if (name === 'keys') {
|
||||
let current = items[index];
|
||||
items[index] = new BlacklistItem(
|
||||
current.pattern, true, e.target.value.split(','));
|
||||
} else if (name === 'add') {
|
||||
items.push(new BlacklistItem('', true, []));
|
||||
} else if (name === 'delete') {
|
||||
items.splice(index, 1);
|
||||
}
|
||||
|
||||
this.props.onChange(new Blacklist(items));
|
||||
if (name === 'delete') {
|
||||
this.props.onBlur();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default PartialBlacklistForm;
|
|
@ -6,6 +6,7 @@ import SearchForm from './form/SearchForm';
|
|||
import KeymapsForm from './form/KeymapsForm';
|
||||
import BlacklistForm from './form/BlacklistForm';
|
||||
import PropertiesForm from './form/PropertiesForm';
|
||||
import PartialBlacklistForm from './form/PartialBlacklistForm';
|
||||
import * as settingActions from '../../settings/actions/setting';
|
||||
import SettingData, {
|
||||
FormKeymaps, FormSearch, FormSettings, JSONTextSettings,
|
||||
|
@ -53,7 +54,15 @@ class SettingsComponent extends React.Component<Props> {
|
|||
<fieldset>
|
||||
<legend>Blacklist</legend>
|
||||
<BlacklistForm
|
||||
value={form.blacklist.toJSON()}
|
||||
value={form.blacklist}
|
||||
onChange={this.bindBlacklistForm.bind(this)}
|
||||
onBlur={this.save.bind(this)}
|
||||
/>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>Partial blacklist</legend>
|
||||
<PartialBlacklistForm
|
||||
value={form.blacklist}
|
||||
onChange={this.bindBlacklistForm.bind(this)}
|
||||
onBlur={this.save.bind(this)}
|
||||
/>
|
||||
|
@ -138,11 +147,10 @@ class SettingsComponent extends React.Component<Props> {
|
|||
this.props.dispatch(settingActions.set(data));
|
||||
}
|
||||
|
||||
bindBlacklistForm(value: any) {
|
||||
bindBlacklistForm(blacklist: Blacklist) {
|
||||
let data = new SettingData({
|
||||
source: this.props.source,
|
||||
form: (this.props.form as FormSettings).buildWithBlacklist(
|
||||
Blacklist.fromJSON(value)),
|
||||
form: (this.props.form as FormSettings).buildWithBlacklist(blacklist),
|
||||
});
|
||||
this.props.dispatch(settingActions.set(data));
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import Key from './Key';
|
||||
|
||||
export type BlacklistItemJSON = string | {
|
||||
url: string,
|
||||
keys: string[],
|
||||
|
@ -31,7 +33,9 @@ export class BlacklistItem {
|
|||
|
||||
public readonly keys: string[];
|
||||
|
||||
private constructor(
|
||||
private readonly keyEntities: Key[];
|
||||
|
||||
constructor(
|
||||
pattern: string,
|
||||
partial: boolean,
|
||||
keys: string[]
|
||||
|
@ -40,6 +44,7 @@ export class BlacklistItem {
|
|||
this.regex = regexFromWildcard(pattern);
|
||||
this.partial = partial;
|
||||
this.keys = keys;
|
||||
this.keyEntities = this.keys.map(Key.fromMapKey);
|
||||
}
|
||||
|
||||
static fromJSON(raw: any): BlacklistItem {
|
||||
|
@ -81,17 +86,20 @@ export class BlacklistItem {
|
|||
: this.regex.test(url.host);
|
||||
}
|
||||
|
||||
includeKey(url: URL, keys: string): boolean {
|
||||
includeKey(url: URL, key: Key): boolean {
|
||||
if (!this.matches(url)) {
|
||||
return false;
|
||||
}
|
||||
return !this.partial || this.keys.includes(keys);
|
||||
if (!this.partial) {
|
||||
return true;
|
||||
}
|
||||
return this.keyEntities.some(k => k.equals(key));
|
||||
}
|
||||
}
|
||||
|
||||
export default class Blacklist {
|
||||
constructor(
|
||||
private blacklist: BlacklistItem[],
|
||||
public readonly items: BlacklistItem[],
|
||||
) {
|
||||
}
|
||||
|
||||
|
@ -104,14 +112,14 @@ export default class Blacklist {
|
|||
}
|
||||
|
||||
toJSON(): BlacklistJSON {
|
||||
return this.blacklist.map(item => item.toJSON());
|
||||
return this.items.map(item => item.toJSON());
|
||||
}
|
||||
|
||||
includesEntireBlacklist(url: URL): boolean {
|
||||
return this.blacklist.some(item => !item.partial && item.matches(url));
|
||||
return this.items.some(item => !item.partial && item.matches(url));
|
||||
}
|
||||
|
||||
includeKey(url: URL, key: string) {
|
||||
return this.blacklist.some(item => item.includeKey(url, key));
|
||||
includeKey(url: URL, key: Key) {
|
||||
return this.items.some(item => item.includeKey(url, key));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import Key from '../../shared/settings/Key';
|
||||
import Key from './Key';
|
||||
|
||||
export default class KeySequence {
|
||||
constructor(
|
||||
|
|
|
@ -2,13 +2,16 @@ import React from 'react';
|
|||
import ReactDOM from 'react-dom';
|
||||
import ReactTestRenderer from 'react-test-renderer';
|
||||
import ReactTestUtils from 'react-dom/test-utils';
|
||||
import BlacklistForm from 'settings/components/form/BlacklistForm'
|
||||
import { expect } from 'chai'
|
||||
|
||||
import BlacklistForm from '../../../../src/settings/components/form/BlacklistForm'
|
||||
import Blacklist from '../../../../src/shared/settings/Blacklist';
|
||||
|
||||
describe("settings/form/BlacklistForm", () => {
|
||||
describe('render', () => {
|
||||
it('renders BlacklistForm', () => {
|
||||
let root = ReactTestRenderer.create(
|
||||
<BlacklistForm value={['*.slack.com', 'www.google.com/maps']} />,
|
||||
<BlacklistForm value={Blacklist.fromJSON(['*.slack.com', 'www.google.com/maps'])} />,
|
||||
).root;
|
||||
|
||||
let children = root.children[0].children;
|
||||
|
@ -43,10 +46,10 @@ describe("settings/form/BlacklistForm", () => {
|
|||
it('invokes onChange event on edit', (done) => {
|
||||
ReactTestUtils.act(() => {
|
||||
ReactDOM.render(<BlacklistForm
|
||||
value={['*.slack.com', 'www.google.com/maps*']}
|
||||
value={Blacklist.fromJSON(['*.slack.com', 'www.google.com/maps*'])}
|
||||
onChange={value => {
|
||||
expect(value).to.have.lengthOf(2);
|
||||
expect(value).to.have.members(['gitter.im', 'www.google.com/maps*']);
|
||||
let urls = value.items.map(item => item.pattern);
|
||||
expect(urls).to.have.members(['gitter.im', 'www.google.com/maps*']);
|
||||
done();
|
||||
}}
|
||||
/>, container)
|
||||
|
@ -60,10 +63,10 @@ describe("settings/form/BlacklistForm", () => {
|
|||
it('invokes onChange event on delete', (done) => {
|
||||
ReactTestUtils.act(() => {
|
||||
ReactDOM.render(<BlacklistForm
|
||||
value={['*.slack.com', 'www.google.com/maps*']}
|
||||
value={Blacklist.fromJSON(['*.slack.com', 'www.google.com/maps*'])}
|
||||
onChange={value => {
|
||||
expect(value).to.have.lengthOf(1);
|
||||
expect(value).to.have.members(['www.google.com/maps*']);
|
||||
let urls = value.items.map(item => item.pattern);
|
||||
expect(urls).to.have.members(['www.google.com/maps*']);
|
||||
done();
|
||||
}}
|
||||
/>, container)
|
||||
|
@ -76,10 +79,10 @@ describe("settings/form/BlacklistForm", () => {
|
|||
it('invokes onChange event on add', (done) => {
|
||||
ReactTestUtils.act(() => {
|
||||
ReactDOM.render(<BlacklistForm
|
||||
value={['*.slack.com']}
|
||||
value={Blacklist.fromJSON(['*.slack.com'])}
|
||||
onChange={value => {
|
||||
expect(value).to.have.lengthOf(2);
|
||||
expect(value).to.have.members(['*.slack.com', '']);
|
||||
let urls = value.items.map(item => item.pattern);
|
||||
expect(urls).to.have.members(['*.slack.com', '']);
|
||||
done();
|
||||
}}
|
||||
/>, container);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import Blacklist, { BlacklistItem } from '../../../src/shared/settings/Blacklist';
|
||||
import { expect } from 'chai';
|
||||
import Key from '../../../src/shared/settings/Key';
|
||||
|
||||
describe('BlacklistItem', () => {
|
||||
describe('#fromJSON', () => {
|
||||
|
@ -82,11 +83,13 @@ describe('BlacklistItem', () => {
|
|||
|
||||
describe('#includesPartialKeys', () => {
|
||||
it('matches with partial keys', () => {
|
||||
let item = BlacklistItem.fromJSON({url: 'google.com', keys: ['j', 'k']});
|
||||
let item = BlacklistItem.fromJSON({url: 'google.com', keys: ['j', 'k', '<C-U>']});
|
||||
|
||||
expect(item.includeKey(new URL('http://google.com/maps'), 'j')).to.be.true;
|
||||
expect(item.includeKey(new URL('http://google.com/maps'), 'z')).to.be.false;
|
||||
expect(item.includeKey(new URL('http://maps.google.com/'), 'j')).to.be.false;
|
||||
expect(item.includeKey(new URL('http://google.com/maps'), Key.fromMapKey('j'))).to.be.true;
|
||||
expect(item.includeKey(new URL('http://google.com/maps'), Key.fromMapKey('<C-U>'))).to.be.true;
|
||||
expect(item.includeKey(new URL('http://google.com/maps'), Key.fromMapKey('z'))).to.be.false;
|
||||
expect(item.includeKey(new URL('http://google.com/maps'), Key.fromMapKey('u'))).to.be.false;
|
||||
expect(item.includeKey(new URL('http://maps.google.com/'), Key.fromMapKey('j'))).to.be.false;
|
||||
})
|
||||
});
|
||||
});
|
||||
|
@ -147,9 +150,9 @@ describe('Blacklist', () => {
|
|||
{ url: 'github.com', keys: ['j', 'k'] },
|
||||
]);
|
||||
|
||||
expect(blacklist.includeKey(new URL('https://google.com'), 'j')).to.be.true;
|
||||
expect(blacklist.includeKey(new URL('https://github.com'), 'j')).to.be.true;
|
||||
expect(blacklist.includeKey(new URL('https://github.com'), 'a')).to.be.false;
|
||||
expect(blacklist.includeKey(new URL('https://google.com'), Key.fromMapKey('j'))).to.be.true;
|
||||
expect(blacklist.includeKey(new URL('https://github.com'), Key.fromMapKey('j'))).to.be.true;
|
||||
expect(blacklist.includeKey(new URL('https://github.com'), Key.fromMapKey('a'))).to.be.false;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Reference in a new issue