From 50fa7640a6318044c7f2b931e3525a45ffe0fdc0 Mon Sep 17 00:00:00 2001 From: FelisCatus Date: Wed, 8 Oct 2014 18:43:57 +0800 Subject: [PATCH] Hide advanced condition types by default. Add text descriptions for condition types. Fix #30. --- omega-i18n/en/messages.json | 67 ++++++++++--- omega-i18n/zh/messages.json | 75 ++++++++++---- omega-web/bower.json | 6 +- omega-web/src/coffee/options.coffee | 11 ++- omega-web/src/less/options.less | 28 ++++++ omega-web/src/omega/app.coffee | 4 +- .../omega/controllers/switch_profile.coffee | 97 +++++++++++++++++-- omega-web/src/omega/directives.coffee | 9 ++ omega-web/src/options.jade | 2 +- omega-web/src/partials/profile_switch.jade | 32 +++++- .../src/partials/rule_remove_confirm.jade | 2 +- omega-web/src/partials/ui.jade | 6 ++ 12 files changed, 284 insertions(+), 55 deletions(-) diff --git a/omega-i18n/en/messages.json b/omega-i18n/en/messages.json index c440cfa..3a52125 100644 --- a/omega-i18n/en/messages.json +++ b/omega-i18n/en/messages.json @@ -29,35 +29,62 @@ "message": "[System Proxy]" }, - "condition_hostWildcard" : { + "condition_HostWildcardCondition" : { "message": "Host wildcard" }, - "condition_hostRegex" : { + "condition_help_HostWildcardCondition" : { + "message": "Matches hosts (domain names) by wildcard.
The asterisk * matches zero or more characters.
The question mark ? matches exactly one character.

Note that rules beginning with *. are specially treated only in Host wildcard conditions.
Example: *.example.com will match www.example.com AND example.com as well.
To match subdomains only, use two asterisks like **.example.com" + }, + "condition_HostRegexCondition" : { "message": "Host regex" }, - "condition_hostLevels" : { + "condition_help_HostRegexCondition" : { + "message": "Like Host wildcard condition, but matches hosts (domain names) by regular expression.
Regular expressions can be hard to construct (and read).
It is recommended to use wildcards for most cases and only use regex for conditions that cannot be achieved by any other condition type." + }, + "condition_HostLevelsCondition" : { "message": "Host levels" }, - "condition_urlWildcard" : { + "condition_help_HostLevelsCondition" : { + "message": "Matches the request if and only if the host level in within the given range.
Host level is defined as the number of dot-separated segments of the host (domain name).
Example: www.example.com is with a host level of 3, while internal is of host level 1." + }, + "condition_UrlWildcardCondition" : { "message": "URL wildcard" }, - "condition_urlRegex" : { + "condition_help_UrlWildcardCondition" : { + "message": "Matches URLs of the request by wildcard.
See the Host wildcard section above for a quick wildcard reference.
Note that URL wildcards are not specially treated (no subdomain magic as in Host wildcard).
So *://*.example.com/* matches http://www.example.com/ but does not match http://example.com/." + }, + "condition_UrlRegexCondition" : { "message": "URL regex" }, - "condition_keyword" : { + "condition_help_UrlRegexCondition" : { + "message": "Matches URL by extremely powerful regular expression.
However, regular expressions can be hard to construct (and read).
It is recommended to use wildcards for most cases and only use regex for conditions that cannot be achieved by any other condition type." + }, + "condition_KeywordCondition" : { "message": "Keyword" }, - "condition_always" : { - "message": "Always" + "condition_help_KeywordCondition" : { + "message": "A keyword condition matches if the URL protocol is HTTP, and the pattern is an exact sub-string of the URL.
It behaves like the URL wildcard pattern http://*pattern*, where pattern is the keyword pattern.
Keyword conditions are useful if you want to bypass a firewall blocking some keywords in the URL, by requesting such URLs through a proxy." }, - "condition_always_details" : { - "message": "(Always matches)" + "condition_FalseCondition" : { + "message": "(Disabled)" }, - "condition_never" : { - "message": "Never" + "condition_details_FalseCondition" : { + "message": "(Condition ignored when matching)" }, - "condition_never_details" : { - "message": "(Never matches)" + "condition_help_FalseCondition" : { + "message": "You can disable a condition by setting its type to (Disabled). A Disabled condition act as if it does not exist.
This feature can be used to disable conditions temporarily.
Disabled conditions still hold the previous information (like patterns) and can be re-enabled by setting the condition type back to the previous type." + }, + "condition_group_default" : { + "message": "" + }, + "condition_group_host" : { + "message": "Host" + }, + "condition_group_url" : { + "message": "Url" + }, + "condition_group_special" : { + "message": "Special" }, "ruleListFormat_Switchy": { @@ -138,6 +165,12 @@ "options_startupProfile_none": { "message": "(Current profile)" }, + "options_showConditionTypesAdvanced": { + "message": "Show advanced condition types" + }, + "options_showConditionTypesAdvancedHelp": { + "message": "Unlocks new types of advanced but complicated switch conditions. For most senarios, the basic condition types should be enough, so this options is not recommended." + }, "options_quickSwitch": { "message": "Quick Switch" }, @@ -333,6 +366,9 @@ "options_conditionType": { "message": "Condition Type" }, + "options_showConditionTypeHelp": { + "message": "Show help" + }, "options_conditionDetails": { "message": "Condition Details" }, @@ -360,6 +396,9 @@ "options_hostLevelsBetween": { "message": "\u2264 host levels \u2264" }, + "options_group_conditionHelp": { + "message": "About Condition Types" + }, "options_group_attachProfile": { "message": "Import online rule lists" }, diff --git a/omega-i18n/zh/messages.json b/omega-i18n/zh/messages.json index 20f7ec4..de96f7b 100644 --- a/omega-i18n/zh/messages.json +++ b/omega-i18n/zh/messages.json @@ -29,35 +29,62 @@ "message": "[系统代理]" }, - "condition_hostWildcard" : { - "message": "主机通配符" + "condition_HostWildcardCondition" : { + "message": "域名通配符" }, - "condition_hostRegex" : { - "message": "主机正则表达式" + "condition_help_HostWildcardCondition" : { + "message": "根据域名(主机名)匹配请求。
星号 * 匹配零个或者多个字符。
问号 ? 匹配任意一个字符。

请注意以 *. 开头的规则有特别处理,会同时匹配子域名和自身。
例如: *.example.com 能匹配 www.example.com ,而且也能匹配 example.com 。
如果只需要匹配子域名,请使用两个星号开头,如 **.example.com。" }, - "condition_hostLevels" : { - "message": "主机层数" + "condition_HostRegexCondition" : { + "message": "域名正则" }, - "condition_urlWildcard" : { + "condition_help_HostRegexCondition" : { + "message": "类似域名通配符,但使用正则表达式.
正则表达式很难编写,且可读性差。
因此,一般情况建议使用通配符。当其他任何条件都不能满足要求时,才使用正则表达式。" + }, + "condition_HostLevelsCondition" : { + "message": "域名层数" + }, + "condition_help_HostLevelsCondition" : { + "message": "如果域名层数在设定的范围内则匹配,否则不匹配。
域名层数是指 域名共有几段(以点分隔).
例如: www.example.com 的域名层数为 3,而 internal 的域名层数为 1." + }, + "condition_UrlWildcardCondition" : { "message": "网址通配符" }, - "condition_urlRegex" : { - "message": "网址正则表达式" + "condition_help_UrlWildcardCondition" : { + "message": "根据通配符规则匹配网址。
关于通配符表达式,请参考上方的域名通配符一节的说明。
请注意网址通配符没有任何特殊处理,不会特殊处理子域名等。
所以 *://*.example.com/* 能匹配 http://www.example.com/ 但是 不匹配 http://example.com/." }, - "condition_keyword" : { + "condition_UrlRegexCondition" : { + "message": "网址正则" + }, + "condition_help_UrlRegexCondition" : { + "message": "使用功能强大的正则表达式来匹配网址。
但正则表达式很难编写,且可读性差。
因此,一般情况建议使用通配符。当其他任何条件都不能满足要求时,才使用正则表达式。" + }, + "condition_KeywordCondition" : { "message": "关键字" }, - "condition_always" : { - "message": "总是" + "condition_help_KeywordCondition" : { + "message": "关键字条件的具体匹配规则是:网址协议为HTTP且网址中包含该关键字。
类似于 http://*关键字*, 其中 关键字 是设定好的关键字。
如果某防火墙根据网址中是否包含关键字来屏蔽网址,那么可以使用关键字条件来通过代理访问这样的请求,以达到绕过防火墙的目的。" }, - "condition_always_details" : { - "message": "(匹配所有请求)" + "condition_FalseCondition" : { + "message": "(禁用)" }, - "condition_never" : { - "message": "从不" + "condition_details_FalseCondition" : { + "message": "(匹配请求时无视此条规则)" }, - "condition_never_details" : { - "message": "(不匹配任何请求)" + "condition_help_FalseCondition" : { + "message": "设置规则类型为(禁用)可以临时禁用某个条件。禁用的条件在匹配时视为不存在。
条件被禁用后,仍然保存有之前的数据(例如通配符或正则),因此当需要时,可以把条件类型改回之前的类型,以方便地重新启用条件。" + }, + "condition_group_default" : { + "message": "" + }, + "condition_group_host" : { + "message": "域名" + }, + "condition_group_url" : { + "message": "网址" + }, + "condition_group_special" : { + "message": "特殊" }, "ruleListFormat_Switchy": { @@ -138,6 +165,12 @@ "options_startupProfile_none": { "message": "(当前情景模式)" }, + "options_showConditionTypesAdvanced": { + "message": "显示高级切换条件" + }, + "options_showConditionTypesAdvancedHelp": { + "message": "启用后,可以使用一些新种类的切换条件,功能强大但难以掌握。对于大多数情况来说,只用基本条件类型就可以满足需求,因此不推荐启用此功能。" + }, "options_quickSwitch": { "message": "快速切换" }, @@ -333,6 +366,9 @@ "options_conditionType": { "message": "条件类型" }, + "options_showConditionTypeHelp": { + "message": "显示帮助" + }, "options_conditionDetails": { "message": "条件设置" }, @@ -360,6 +396,9 @@ "options_hostLevelsBetween": { "message": "\u2264 主机层数 \u2264" }, + "options_group_conditionHelp": { + "message": "条件类型说明" + }, "options_group_attachProfile": { "message": "导入在线规则列表" }, diff --git a/omega-web/bower.json b/omega-web/bower.json index c049417..f3f2141 100644 --- a/omega-web/bower.json +++ b/omega-web/bower.json @@ -37,7 +37,8 @@ "FileSaver": "*", "angular-ui-utils": "bower-validate", "angular-ladda": "~0.1.6", - "bootstrap-select": "~1.6.2" + "bootstrap-select": "~1.6.2", + "angular-sanitize": "~1.2.26" }, "exportsOverride": { "script.js": { @@ -67,6 +68,9 @@ "angular-loader": { "": "*.min.js" }, + "angular-sanitize": { + "": "*.min.js" + }, "angular-spectrum-colorpicker": { "": "dist/*.min.js" }, diff --git a/omega-web/src/coffee/options.coffee b/omega-web/src/coffee/options.coffee index a6181e6..faf0fec 100644 --- a/omega-web/src/coffee/options.coffee +++ b/omega-web/src/coffee/options.coffee @@ -11,9 +11,10 @@ $script 'lib/spin.js/spin.js', -> $script 'lib/angular-ladda/angular-ladda.min.js', 'angular-ladda' $script.ready ['angular-loader'], -> - angular.module 'omega', ['ngLocale', 'ngAnimate', 'ui.bootstrap', 'ui.router', - 'ngProgress', 'ui.sortable', 'angularSpectrumColorpicker', 'ui.validate', - 'angular-ladda', 'omegaTarget', 'omegaDecoration'] + angular.module 'omega', ['ngLocale', 'ngAnimate', 'ngSanitize', + 'ui.bootstrap', 'ui.router', 'ngProgress', 'ui.sortable', + 'angularSpectrumColorpicker', 'ui.validate', 'angular-ladda', 'omegaTarget', + 'omegaDecoration'] $script.ready ['omega-pac'], -> $script 'js/omega.js', 'omega' $script([ @@ -39,7 +40,9 @@ $script.ready ['angular-loader', 'jquery'], -> $script.ready ['angular'], -> $script 'lib/angular-ui-router/angular-ui-router.js', 'angular-ui-router' + $script 'lib/angular-sanitize/angular-sanitize.min.js', 'angular-sanitize' $script.ready ['angular', 'omega', 'omega-deps', 'angular-ui-router', - 'jquery-ui', 'spectrum', 'filesaver', 'blob', 'angular-ladda'], -> + 'jquery-ui', 'spectrum', 'filesaver', 'blob', 'angular-ladda', + 'angular-sanitize'], -> angular.bootstrap document, ['omega'] diff --git a/omega-web/src/less/options.less b/omega-web/src/less/options.less index 33941a2..1316979 100644 --- a/omega-web/src/less/options.less +++ b/omega-web/src/less/options.less @@ -37,6 +37,10 @@ a[role="button"] { min-width: 0 !important; } +.clear-padding { + padding: 0 !important; +} + .align-initial { text-align: left; text-align: initial; @@ -280,6 +284,30 @@ main { } } +.condition-help { + dl { + margin-left: 5px; + padding-left: 10px; + border-left: solid 1px #ccc; + } + dt { + font-size: 1.2em; + } + dd { + padding-left: 20px; + } +} + +.close-condition-help { + float: none !important; + margin-left: 15px !important; + opacity: 1 !important; + + &:hover, &:active { + color: #444 !important; + } +} + .switch-attached { > tr > td { background-color: #F9F9F9; diff --git a/omega-web/src/omega/app.coffee b/omega-web/src/omega/app.coffee index 4f165e5..201d3ba 100644 --- a/omega-web/src/omega/app.coffee +++ b/omega-web/src/omega/app.coffee @@ -20,7 +20,9 @@ angular.module('omega').constant 'getParentName', (name) -> undefined angular.module('omega').config ($stateProvider, $urlRouterProvider, - $httpProvider) -> + $httpProvider, $animateProvider) -> + $animateProvider.classNameFilter(/angular-animate/) + $urlRouterProvider.otherwise '/ui' $stateProvider diff --git a/omega-web/src/omega/controllers/switch_profile.coffee b/omega-web/src/omega/controllers/switch_profile.coffee index 9ac7fb6..4fbc79f 100644 --- a/omega-web/src/omega/controllers/switch_profile.coffee +++ b/omega-web/src/omega/controllers/switch_profile.coffee @@ -1,15 +1,93 @@ angular.module('omega').controller 'SwitchProfileCtrl', ($scope, $modal, profileIcons, getAttachedName) -> - $scope.conditionI18n = - 'HostWildcardCondition': 'condition_hostWildcard' - 'HostRegexCondition': 'condition_hostRegex' - 'HostLevelsCondition': 'condition_hostLevels' - 'UrlWildcardCondition': 'condition_urlWildcard' - 'UrlRegexCondition': 'condition_urlRegex' - 'KeywordCondition': 'condition_keyword' - 'AlwaysCondition': 'condition_always' - 'NeverCondition': 'condition_never' + $scope.basicConditionTypes = [ + { + group: 'default' + types: [ + 'HostWildcardCondition' + 'UrlWildcardCondition' + 'UrlRegexCondition' + 'FalseCondition' + ] + } + ] + + $scope.advancedConditionTypes = [ + { + group: 'host' + types: [ + 'HostWildcardCondition' + 'HostRegexCondition' + 'HostLevelsCondition' + ] + } + { + group: 'url' + types: [ + 'UrlWildcardCondition' + 'UrlRegexCondition' + 'KeywordCondition' + ] + } + { + group: 'special' + types: [ + 'FalseCondition' + ] + } + ] + + expandGroups = (groups) -> + result = [] + for group in groups + for type in group.types + result.push({type: type, group: 'condition_group_' + group.group}) + result + + basicConditionTypesExpanded = expandGroups($scope.basicConditionTypes) + advancedConditionTypesExpanded = expandGroups($scope.advancedConditionTypes) + + basicConditionTypeSet = {} + for type in basicConditionTypesExpanded + basicConditionTypeSet[type.type] = type.type + + $scope.conditionTypes = basicConditionTypesExpanded + + $scope.showConditionTypes = 0 + $scope.hasConditionTypes = 0 + updateHasConditionTypes = -> + return unless $scope.hasConditionTypes == 0 + return unless $scope.profile?.rules? + for rule in $scope.profile.rules + # Convert TrueCondition to a HostWildcardCondition with pattern '*'. + if rule.condition.conditionType == 'TrueCondition' + rule.condition = { + conditionType: 'HostWildcardCondition' + pattern: '*' + } + if not basicConditionTypeSet[rule.condition.conditionType] + $scope.hasConditionTypes = 1 + $scope.showConditionTypes = 1 + break + + $scope.$watch 'options["-showConditionTypes"]', (show) -> + show ||= 0 + if show > 0 + $scope.showConditionTypes = show + else + updateHasConditionTypes() + $scope.showConditionTypes = $scope.hasConditionTypes + if $scope.showConditionTypes == 0 + $scope.conditionTypes = basicConditionTypesExpanded + else + $scope.conditionTypes = advancedConditionTypesExpanded + if not $scope.options["-showConditionTypes"]? + $scope.options["-showConditionTypes"] = $scope.showConditionTypes + unwatchRules?() + + if $scope.hasConditionTypes == 0 + unwatchRules = $scope.$watch 'profile.rules', updateHasConditionTypes, true $scope.addRule = -> rule = @@ -37,7 +115,6 @@ angular.module('omega').controller 'SwitchProfileCtrl', ($scope, $modal, if $scope.options['-confirmDeletion'] scope = $scope.$new('isolate') scope.rule = $scope.profile.rules[index] - scope.conditionI18n = $scope.conditionI18n scope.ruleProfile = $scope.profileByName(scope.rule.profileName) scope.profileIcons = $scope.profileIcons $modal.open( diff --git a/omega-web/src/omega/directives.coffee b/omega-web/src/omega/directives.coffee index 9754d9d..12b6a1e 100644 --- a/omega-web/src/omega/directives.coffee +++ b/omega-web/src/omega/directives.coffee @@ -37,3 +37,12 @@ angular.module('omega').directive 'omegaUpload', -> scope.error({'$error': e.target.error}) reader.readAsText(input.files[0]) input.value = '' +angular.module('omega').directive 'omegaInt2str', -> + restrict: 'A' + priority: 2 # Run post-link after input directive (0) and ngModel (1). + require: 'ngModel' + link: (scope, element, attr, ngModel) -> + ngModel.$parsers.push (value) -> + parseInt(value) + ngModel.$formatters.push (value) -> + '' + value diff --git a/omega-web/src/options.jade b/omega-web/src/options.jade index 0d4c104..1f7b040 100644 --- a/omega-web/src/options.jade +++ b/omega-web/src/options.jade @@ -53,7 +53,7 @@ html(lang='en' ng-controller='MasterCtrl' ng-csp) span.glyphicon.glyphicon-remove-circle = ' ' | {{'options_discard' | tr}} - main.col-lg-10.col-sm-9.col-lg-offset-2.col-sm-offset-3(ui-view) + main.col-lg-10.col-sm-9.col-lg-offset-2.col-sm-offset-3.angular-animate(ui-view) //- Note: Alert type classes cannot be changed in angular-bootstrap. diff --git a/omega-web/src/partials/profile_switch.jade b/omega-web/src/partials/profile_switch.jade index cebd7a6..d07a6fb 100644 --- a/omega-web/src/partials/profile_switch.jade +++ b/omega-web/src/partials/profile_switch.jade @@ -1,4 +1,19 @@ div(ng-controller='SwitchProfileCtrl') + section.settings-group(ng-show='showConditionHelp' ng-init='expandedSection = {id: 0}') + h3 + | {{'options_group_conditionHelp' | tr}} + button.close.close-condition-help(type='button' ng-click='showConditionHelp = false') + span(aria-hidden='true') × + span.sr-only {{'dialog_close' | tr}} + div.condition-help(ng-repeat='group in (showConditionTypes == 0 ? basicConditionTypes : advancedConditionTypes)') + h4(ng-show="!!('condition_group_' + group.group | tr)") + a(ng-click='expandedSection.id = $index' role='button') + span.glyphicon(ng-class="{'glyphicon-chevron-down': expandedSection.id == $index, 'glyphicon-chevron-right': expandedSection.id != $index}") + = ' ' + | {{'condition_group_' + group.group | tr}} + dl(ng-show='expandedSection.id == $index') + dt(ng-repeat-start='type in group.types') {{'condition_' + type | tr}} + dd(ng-repeat-end ng-bind-html='"condition_help_" + type | tr') section.settings-group h3 {{'options_group_switchRules' | tr}} .table-responsive @@ -6,7 +21,12 @@ div(ng-controller='SwitchProfileCtrl') thead tr th(style='white-space: nowrap') {{'options_sort' | tr}} - th {{'options_conditionType' | tr}} + th + | {{'options_conditionType' | tr}} + = ' ' + button.btn.btn-link.btn-sm.clear-padding(title='{{"options_showConditionTypeHelp" | tr}}' + ng-click='showConditionHelp = !showConditionHelp' ng-init='showConditionHelp = false') + span.glyphicon.glyphicon-question-sign th {{'options_conditionDetails' | tr}} th {{'options_resultProfile' | tr}} th {{'options_conditionActions' | tr}} @@ -16,10 +36,12 @@ div(ng-controller='SwitchProfileCtrl') span.glyphicon.glyphicon-sort td select.form-control(ng-model='rule.condition.conditionType' - ng-options='cond as (i18n | tr) for (cond, i18n) in conditionI18n') + ng-options='type.type as ("condition_" + type.type | tr) group by (type.group | tr) for type in conditionTypes') td(ng-switch='rule.condition.conditionType') - span(ng-switch-when='AlwaysCondition') {{'condition_always_details' | tr}} - span(ng-switch-when='NeverCondition') {{'condition_never_details' | tr}} + span(ng-switch-when='FalseCondition') + span(ng-show='!!rule.condition.pattern') + input.form-control(ng-model='rule.condition.pattern' disabled title="{{'condition_details_FalseCondition' | tr}}") + span(ng-show='!rule.condition.pattern') {{'condition_details_FalseCondition' | tr}} span.host-levels-details(ng-switch-when='HostLevelsCondition') input.form-control(type='number' min='1' max='99' ng-model='rule.condition.minValue' required) = ' ' @@ -30,7 +52,7 @@ div(ng-controller='SwitchProfileCtrl') ui-validate='{pattern: "validateCondition(rule.condition, $value)"}') td div.form-control(omega-profile-select='options | profiles:profile' ng-model='rule.profileName' - disp-name='$profile.name | dispName') + disp-name='$profile.name | dispName' ng-class='{disabled: rule.condition.conditionType == "NeverCondition"}') td button.btn.btn-danger.btn-sm(title="{{'options_deleteRule' | tr}}" ng-click='removeRule($index)') span.glyphicon.glyphicon-trash diff --git a/omega-web/src/partials/rule_remove_confirm.jade b/omega-web/src/partials/rule_remove_confirm.jade index f60a541..23f5afc 100644 --- a/omega-web/src/partials/rule_remove_confirm.jade +++ b/omega-web/src/partials/rule_remove_confirm.jade @@ -6,7 +6,7 @@ .modal-body p {{'options_deleteRuleConfirm' | tr}} div.well - span.label.label-info {{conditionI18n[rule.condition.conditionType]}} + span.label.label-info {{'condition_' + rule.condition.conditionType | tr}} = ' ' | {{rule.condition.pattern}} span.pull-right diff --git a/omega-web/src/partials/ui.jade b/omega-web/src/partials/ui.jade index fd5216a..c6ad9f0 100644 --- a/omega-web/src/partials/ui.jade +++ b/omega-web/src/partials/ui.jade @@ -18,6 +18,12 @@ section.settings-group div(omega-profile-select='options | profiles:"all"' ng-model='options["-startupProfileName"]' default-text="{{'options_startupProfile_none' | tr}}" disp-name='$profile.name | dispName' style='display: inline-block;') + div.checkbox + label + input(type='checkbox' ng-model='options["-showConditionTypes"]' ng-true-value='1' ng-false-value='0' omega-int2str) + span {{'options_showConditionTypesAdvanced' | tr}} + p.help-block + | {{'options_showConditionTypesAdvancedHelp' | tr}} div.checkbox label input(type='checkbox' ng-model='options["-enableQuickSwitch"]')