diff --git a/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/App.js b/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/App.js index a4da828..f395ddc 100644 --- a/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/App.js +++ b/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/App.js @@ -24,17 +24,24 @@ export default function getApp(theState) { } - setStatusTo(msg) { + setStatusTo(msg, cb) { this.setState( { status: msg, - } + }, + cb ); } - showErrors(err, ...warns) { + showErrors(err, ...args/* ...warns, cb */) { + + const lastArg = args[args.length - 1]; + const cb = (lastArg && typeof lastArg === 'function') + ? args.pop() + : () => {}; + const warns = args; const warningHtml = warns .map( @@ -74,7 +81,8 @@ export default function getApp(theState) { evt.preventDefault(); }}> [Техн.детали]} - ) + ), + cb ); } diff --git a/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/Main.js b/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/Main.js index 1ee7f10..4f8258f 100644 --- a/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/Main.js +++ b/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/Main.js @@ -21,6 +21,8 @@ export default function getMain(theState) { const ApplyMods = getApplyMods(theState); const Notifications = getNotifications(theState); + const checksName = 'pacMods'; + return class Main extends Component { constructor(props) { @@ -64,8 +66,6 @@ export default function getMain(theState) { handleModCheck(that, {targetConf, targetIndex, targetChildren}) { - console.log('CHHHH', targetChildren); - window.foo = targetChildren const oldCats = that.state.catToOrderedMods; const newCats = Object.keys(that.state.catToOrderedMods).reduce((acc, cat) => { @@ -86,9 +86,11 @@ export default function getMain(theState) { } - handleModChange(that) { + handleModChange(that, event) { - that.setState({ifModsChangesStashed: true}); + if (event.target.name === checksName) { + that.setState({ifModsChangesStashed: true}); + } } @@ -127,6 +129,7 @@ export default function getMain(theState) { childrenOfMod: { customProxyStringRaw: createElement(ProxyEditor, props), }, + name: checksName, }, modsHandlers) ), key: 'ownProxies', @@ -137,6 +140,7 @@ export default function getMain(theState) { ModList, Object.assign({}, props, { orderedConfigs: this.state.catToOrderedMods['general'], + name: checksName, }, modsHandlers) ), key: 'mods', diff --git a/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/ModList.js b/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/ModList.js index a6a3e54..3a801b9 100644 --- a/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/ModList.js +++ b/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/ModList.js @@ -14,6 +14,7 @@ export default function getModList(theState) { ( props.onClick({targetConf: conf, targetIndex: index, targetChildren: props.childrenOfMod})} diff --git a/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/ProxyEditor.js b/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/ProxyEditor.js index 86df417..96c8fc0 100644 --- a/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/ProxyEditor.js +++ b/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/ProxyEditor.js @@ -21,12 +21,23 @@ export default function getProxyEditor(theState) { table.editor td, table.editor th { border: 1px solid #ccc; text-align: left; + height: 100%; } - table.editor th { + + /* ADD PANEL */ + table.editor tr.addPanel td { + padding: 0; + } + /* PROXY ROW */ + table.editor tr.proxyRow td:first-child { + text-align: center; + } + + table.editor th:not(:last-child) { padding: 0 0.6em; } - table.editor input, + table.editor input:not([type="submit"]), table.editor select, table.editor select:hover { border: none; @@ -41,6 +52,7 @@ export default function getProxyEditor(theState) { width: 100%; } + /* BUTTONS */ table.editor input[type="submit"], table.editor button { min-width: 0; @@ -49,42 +61,68 @@ export default function getProxyEditor(theState) { padding: 0; border: none; } - table.editor .add { - font-weight: 800; + .only { + /*height: 100%;*/ + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; } - table.editor button.export { + table.editor .add { + font-weight: 900; + } + table.editor .export { padding-right: 2px; } - /* LAST COLUMN */ - table.editor th:nth-last-child(1), - table.editor td:nth-last-child(1) { - height: 100%; - /*border: 0;*/ - padding: 0; + + /* LAST COLUMN: BUTTONS */ + table.editor tr > *:nth-last-child(1) { text-align: center; - } - table.editor td:nth-last-child(2) { - /*border-right: 0; - /* FOR PORT */ padding: 0; + position: relative; + min-width: 1em; } - .laftPadded { + /* LAST-2 COLUMN: HOSTNAME + table.editor td:nth-last-child(3) { padding-left: 2px; - } + }*/ .noPad { padding: 0; } + .padLeft { + padding-left: 2px; + } textarea.textarea { width: 100% !important; min-height: 100%; height: 6em; - border-width: 1px 0 0 0; + /* border-width: 1px 0 0 0;*/ + border: none; + } + + table.editor input:invalid { + color: red !important; + border-radius: 0; + border-bottom: 1px dotted red !important; } `; - const uiRaw = 'ui-proxy-string-raw'; + const UI_RAW = 'ui-proxy-string-raw'; + const MAX_PORT = 65535; + const onlyPort = function onlyPort(event) { + + if (!event.ctrlKey && (/^\D$/.test(event.key) || /^\d$/.test(event.key) && parseInt(`${this.value}${event.key}`) > MAX_PORT)) { + event.preventDefault(); + return false; + } + // Digits, Alt, Tab, Enter, etc. + return true; + + }; + const PROXY_TYPE_LABEL_PAIRS = [['PROXY', 'PROXY/HTTP'],['HTTPS'],['SOCKS4'],['SOCKS5'],['SOCKS']]; return class ProxyEditor extends Component { @@ -92,88 +130,241 @@ export default function getProxyEditor(theState) { super(props); this.state = { - proxyStringRaw: localStorage.getItem(uiRaw) || '', - ifExportMode: false, + proxyStringRaw: localStorage.getItem(UI_RAW) || '', + ifExportsMode: false, + + exportsStatus: '', + ifChangesStashedForApply: false, + stashedExports: false, + + newType: 'HTTPS', }; - // props.funs.setStatusTo('Hello from editor!'); this.switchBtn = ( ); - - } - - handleModeSwitch(that) { - that.setState({ ifExportMode: !that.state.ifExportMode }); + } + + handleTextareaChange(that, event) { + + that.setState({stashedExports: event.target.value}); } - handleAdd(that) { + preventLostOfChanges() { + window.onbeforeunload = () => true; // TODO + + } + + findErrorsForStashedExports() { + + const valid = true; + if(this.state.stashedExports === false) { + return valid; + } + const errors = this.state.stashedExports.trim() + .split(/\s*;\s*/) + .filter((s) => s) + .map((proxyAsString) => { + + const [rawType, addr] = proxyAsString.split(/\s+/); + const knownTypes = PROXY_TYPE_LABEL_PAIRS.map(([type, label]) => type); + if( !knownTypes.includes(rawType.toUpperCase()) ) { + return new Error( + `Неверный тип ${rawType}. Известные типы: ${knownTypes.join(', ')}.` + ); + } + if (!(addr && /^[^:]+:\d+$/.test(addr))) { + return new Error( + `Адрес "${addr}" не соответствует формату "<домен_или_IP_прокси>:<порт_прокси_из_цифр>".` + ); + } + const [hostname, rawPort] = addr.split(':'); + const port = parseInt(rawPort); + if (port < 0 || port > 65535) { + return new Error( + `Порт ${port} должен быть целым числом от 0 до 65535.` + ); + } + return false; + + }).filter((e) => e); + return errors && errors.length ? errors : false; + + } + + handleModeSwitch(that, event) { + event.preventDefault(); // No form submit. + let newProxyStringRaw = that.state.proxyStringRaw; + + const doSwitch = () => that.setState({ + ifExportsMode: !that.state.ifExportsMode, + proxyStirngRaw: newProxyStringRaw, + stashedExports: false, + exportsStatus: '', + }); + + if (that.state.stashedExports !== false) { + + const errors = that.findErrorsForStashedExports(); + if (!errors) { + newProxyStringRaw = that.state.stashedExports; + } else { + that.setState({exportsStatus: 'Имеются ошибки: [забыть]?'}); + return that.props.funs.showErrors(...errors); + } + } + doSwitch(); } + showInvalidMessage(that, event) { + + that.props.funs.showErrors({message: event.target.validationMessage}); + + } + + handleTypeSelect(that, event) { + + that.state.newType = event.target.value; + + } + + handleSubmit(that, event) { + + !that.state.ifExportsMode ? that.handleAdd(that, event) : that.handleModeSwitch(that, event); + + } + + handleAdd(that, event) { + + const form = event.target; + const elements = Array.from(form.elements).reduce((acc, el, index) => { + + acc[el.name || index] = el.value; + el.value = ''; + return acc; + + }, {}); + const type = that.state.newType; + const hostname = elements.hostname; + const port = elements.port; + + that.setState({proxyStringRaw: `${that.state.proxyStringRaw} ${type} ${hostname}:${port};`.trim()}); + + event.preventDefault(); + + } render(props) { return ( -
+ { - !this.state.ifExportMode + !this.state.ifExportsMode ? (( - + - - + - - + + {/* ADD NEW PROXY ENDS. */} + { + this.state.proxyStringRaw.split(/\s*;\s*/g).filter((s) => s).map((proxyAsString) => { + + const [type, addr] = proxyAsString.trim().split(/\s/); + const [hostname, port] = addr.split(':'); + return ( + + + + + ); + + }) + }
протокол домен порт {this.switchBtn}протокол домен / IP порт {this.switchBtn}
-
+ - - - + + {/* LAST-2: HOSTNAME */} + - -
- + {/* LAST-1: PORT */} + +
+ {/* LAST */} +
{type}{hostname}{port} + +
+ +
+ )) : (( + - + -
Прокси видят данные HTTP-сайтов!{this.state.exportsStatus || 'Прокси видят содержимое HTTP-сайтов.'} {this.switchBtn}