Allow attaching a RuleListProfile to a SwitchProfile.

This commit is contained in:
FelisCatus 2014-10-03 17:15:41 +08:00
parent 8a777054d1
commit cdd3bda2cd
10 changed files with 309 additions and 70 deletions

View File

@ -55,10 +55,10 @@
"message": "(Never matches)" "message": "(Never matches)"
}, },
"rulelistFormat_Switchy": { "ruleListFormat_Switchy": {
"message": "Switchy" "message": "Switchy"
}, },
"rulelistFormat_AutoProxy": { "ruleListFormat_AutoProxy": {
"message": "AutoProxy" "message": "AutoProxy"
}, },
@ -340,12 +340,27 @@
"options_addCondition": { "options_addCondition": {
"message": "Add condition" "message": "Add condition"
}, },
"options_switchAttachedProfileInCondition": {
"message": "Rule list rules"
},
"options_switchAttachedProfileInConditionDetails": {
"message": "(Any request matching the rule list below)"
},
"options_switchDefaultProfile": { "options_switchDefaultProfile": {
"message": "Default" "message": "Default"
}, },
"options_hostLevelsBetween": { "options_hostLevelsBetween": {
"message": "\u2264 host levels \u2264" "message": "\u2264 host levels \u2264"
}, },
"options_group_attachProfile": {
"message": "Import online rule lists"
},
"options_attachProfile": {
"message": "Add a rule list"
},
"options_attachProfileHelp": {
"message": "You can reuse an online collection of conditions published by others by adding a rule list."
},
"options_modalHeader_applyOptions": { "options_modalHeader_applyOptions": {
"message": "Apply Options" "message": "Apply Options"
}, },
@ -410,6 +425,24 @@
"options_resetRules_help" : { "options_resetRules_help" : {
"message": "Set profile for all rules" "message": "Set profile for all rules"
}, },
"options_modalHeader_deleteAttached": {
"message": "Remove Rule List"
},
"options_deleteAttachedConfirm": {
"message": "Do you really want to remove the rule list from the current profile?"
},
"options_ruleListLineCount": {
"message": "$COUNT$ line(s) of rules",
"placeholders": {
"count": {
"content": "$1",
"example": "42"
}
}
},
"options_deleteAttached": {
"message": "Remove rule list"
},
"options_modalHeader_newProfile" : { "options_modalHeader_newProfile" : {
"message": "New Profile" "message": "New Profile"
}, },
@ -438,7 +471,7 @@
"message": "Applying different profiles automatically on various conditions such as domains or patterns. (Replaces AutoSwitch mode.)" "message": "Applying different profiles automatically on various conditions such as domains or patterns. (Replaces AutoSwitch mode.)"
}, },
"options_profileTypeRuleListProfile" : { "options_profileTypeRuleListProfile" : {
"message": "Rulelist Profile" "message": "Rule List Profile"
}, },
"options_profileDescRuleListProfile" : { "options_profileDescRuleListProfile" : {
"message": "Reusing an online collection of conditions published by others." "message": "Reusing an online collection of conditions published by others."
@ -550,6 +583,18 @@
"browserAction_titleExternalProxy": { "browserAction_titleExternalProxy": {
"message": "Note: The proxy settings are currently controlled by other app(s)." "message": "Note: The proxy settings are currently controlled by other app(s)."
}, },
"browserAction_defaultRuleDetails": {
"message": "(default)",
"description": "Representation of the default profile being selected on browserAction title."
},
"browserAction_directResult": {
"message": "DIRECT",
"description": "Representation of direct connection being used on browserAction title."
},
"browserAction_attachedPrefix": {
"message": "(RL) ",
"description": "The prefix to indicate a rule list rule on browserAction title. Should be very short."
},
"browserAction_tempRulePrefix": { "browserAction_tempRulePrefix": {
"message": "(TEMP) ", "message": "(TEMP) ",
"description": "The prefix to indicate a temp rule on browserAction title. Should be very short." "description": "The prefix to indicate a temp rule on browserAction title. Should be very short."

View File

@ -55,10 +55,10 @@
"message": "(不匹配任何请求)" "message": "(不匹配任何请求)"
}, },
"rulelistFormat_Switchy": { "ruleListFormat_Switchy": {
"message": "Switchy" "message": "Switchy"
}, },
"rulelistFormat_AutoProxy": { "ruleListFormat_AutoProxy": {
"message": "AutoProxy" "message": "AutoProxy"
}, },
@ -340,12 +340,27 @@
"options_addCondition": { "options_addCondition": {
"message": "添加条件" "message": "添加条件"
}, },
"options_switchAttachedProfileInCondition": {
"message": "规则列表规则"
},
"options_switchAttachedProfileInConditionDetails": {
"message": "(按照规则列表匹配请求)"
},
"options_switchDefaultProfile": { "options_switchDefaultProfile": {
"message": "默认情景模式" "message": "默认情景模式"
}, },
"options_hostLevelsBetween": { "options_hostLevelsBetween": {
"message": "\u2264 主机层数 \u2264" "message": "\u2264 主机层数 \u2264"
}, },
"options_group_attachProfile": {
"message": "导入在线规则列表"
},
"options_attachProfile": {
"message": "添加规则列表"
},
"options_attachProfileHelp": {
"message": "可以添加规则列表,以便引用他人在线发布的一组规则。"
},
"options_modalHeader_applyOptions": { "options_modalHeader_applyOptions": {
"message": "应用选项" "message": "应用选项"
}, },
@ -410,6 +425,24 @@
"options_resetRules_help" : { "options_resetRules_help" : {
"message": "批量设置所有规则的情景模式" "message": "批量设置所有规则的情景模式"
}, },
"options_modalHeader_deleteAttached": {
"message": "移除规则列表"
},
"options_deleteAttachedConfirm": {
"message": "真的要移除当前情景模式的在线规则列表吗?"
},
"options_ruleListLineCount": {
"message": "共计$COUNT$行规则",
"placeholders": {
"count": {
"content": "$1",
"example": "42"
}
}
},
"options_deleteAttached": {
"message": "Remove rule list"
},
"options_modalHeader_newProfile" : { "options_modalHeader_newProfile" : {
"message": "新建情景模式" "message": "新建情景模式"
}, },
@ -550,6 +583,18 @@
"browserAction_titleExternalProxy": { "browserAction_titleExternalProxy": {
"message": "注意:其他应用正在控制当前代理设置。" "message": "注意:其他应用正在控制当前代理设置。"
}, },
"browserAction_defaultRuleDetails": {
"message": "(默认)",
"description": "在图标悬停提示上表示选择了默认情景模式作为结果的文字。"
},
"browserAction_directResult": {
"message": "直接连接",
"description": "在图标悬停提示上表示使用了直接连接的文字。"
},
"browserAction_attachedPrefix": {
"message": "(列表) ",
"description": "在图标悬停提示上显示规则列表规则的前缀。文字应该非常短。"
},
"browserAction_tempRulePrefix": { "browserAction_tempRulePrefix": {
"message": "(临时) ", "message": "(临时) ",
"description": "在图标悬停提示上显示临时规则的前缀。文字应该非常短。" "description": "在图标悬停提示上显示临时规则的前缀。文字应该非常短。"

View File

@ -2,7 +2,7 @@ module.exports =
Conditions: require('./src/conditions') Conditions: require('./src/conditions')
PacGenerator: require('./src/pac_generator') PacGenerator: require('./src/pac_generator')
Profiles: require('./src/profiles') Profiles: require('./src/profiles')
Rulelist: require('./src/rule_list') RuleList: require('./src/rule_list')
ShexpUtils: require('./src/shexp_utils') ShexpUtils: require('./src/shexp_utils')
for name, value of require('./src/utils.coffee') for name, value of require('./src/utils.coffee')

View File

@ -39,6 +39,12 @@ drawIcon = (resultColor, profileColor) ->
icon = ctx.getImageData(0, 0, 19, 19) icon = ctx.getImageData(0, 0, 19, 19)
return iconCache[cacheKey] = icon return iconCache[cacheKey] = icon
charCodeUnderscore = '_'.charCodeAt(0)
isHidden = (name) -> (name.charCodeAt(0) == charCodeUnderscore and
name.charCodeAt(1) == charCodeUnderscore)
dispName = (name) -> chrome.i18n.getMessage('profile_' + name) || name
actionForUrl = (url) -> actionForUrl = (url) ->
options.ready.then(-> options.ready.then(->
request = OmegaPac.Conditions.requestFromUrl(url) request = OmegaPac.Conditions.requestFromUrl(url)
@ -47,25 +53,45 @@ actionForUrl = (url) ->
current = options.currentProfile() current = options.currentProfile()
details = '' details = ''
direct = false direct = false
attached = false
for result in results for result in results
if Array.isArray(result) if Array.isArray(result)
if not result[1]? if not result[1]?
details += "(default) => #{result[0]}\n" attached = false
name = result[0]
if name[0] == '+'
name = name.substr(1)
if isHidden(name)
attached = true
else
details += chrome.i18n.getMessage 'browserAction_defaultRuleDetails'
details += " => #{dispName(name)}\n"
else if result[1].length == 0 else if result[1].length == 0
details += "#{result[0]}\n" if result[0] == 'DIRECT'
details += chrome.i18n.getMessage('browserAction_directResult')
details += '\n'
else
details += "#{result[0]}\n"
else if typeof result[1] == 'string' else if typeof result[1] == 'string'
details += "#{result[1]} => #{result[0]}\n" details += "#{result[1]} => #{result[0]}\n"
else else
condition = (result[1].condition ? result[1]).pattern ? '' condition = (result[1].condition ? result[1]).pattern ? ''
details += "#{condition} => "
if result[0] == 'DIRECT' if result[0] == 'DIRECT'
details += chrome.i18n.getMessage('browserAction_directResult')
details += '\n'
direct = true direct = true
details += "#{condition} => #{result[0]}\n" else
details += "#{result[0]}\n"
else if result.profileName else if result.profileName
if result.isTempRule if result.isTempRule
details += chrome.i18n.getMessage('browserAction_tempRulePrefix') details += chrome.i18n.getMessage('browserAction_tempRulePrefix')
else if attached
details += chrome.i18n.getMessage('browserAction_attachedPrefix')
attached = false
condition = (result.source ? result.condition.pattern ? condition = (result.source ? result.condition.pattern ?
result.condition.conditionType) result.condition.conditionType)
details += "#{condition} => #{result.profileName}\n" details += "#{condition} => #{dispName(result.profileName)}\n"
icon = icon =
if profile.name == current.name and options.isCurrentProfileStatic() if profile.name == current.name and options.isCurrentProfileStatic()
@ -77,8 +103,8 @@ actionForUrl = (url) ->
drawIcon(profile.color, current.color) drawIcon(profile.color, current.color)
return { return {
title: chrome.i18n.getMessage('browserAction_titleWithResult', [ title: chrome.i18n.getMessage('browserAction_titleWithResult', [
current.name dispName(current.name)
profile.name dispName(profile.name)
details details
]) ])
icon: icon icon: icon

View File

@ -68,10 +68,11 @@ module.controller 'PopupCtrl', ($scope, $window, $q, omegaTarget,
$scope.builtinProfiles = [] $scope.builtinProfiles = []
$scope.customProfiles = [] $scope.customProfiles = []
$scope.availableProfiles = availableProfiles $scope.availableProfiles = availableProfiles
charCodeUnderscore = '_'.charCodeAt(0)
for own key, profile of availableProfiles for own key, profile of availableProfiles
if profile.builtin if profile.builtin
$scope.builtinProfiles.push(profile) $scope.builtinProfiles.push(profile)
else else if profile.name.charCodeAt(0) != charCodeUnderscore
$scope.customProfiles.push(profile) $scope.customProfiles.push(profile)
$scope.customProfiles.sort(profileOrder) $scope.customProfiles.sort(profileOrder)
$scope.currentProfile = availableProfiles['+' + currentProfileName] $scope.currentProfile = availableProfiles['+' + currentProfileName]
@ -79,8 +80,12 @@ module.controller 'PopupCtrl', ($scope, $window, $q, omegaTarget,
$scope.isSystemProfile = isSystemProfile $scope.isSystemProfile = isSystemProfile
$scope.externalProfile = externalProfile $scope.externalProfile = externalProfile
refreshOnProfileChange = refreshOnProfileChange refreshOnProfileChange = refreshOnProfileChange
$scope.validResultProfiles = validResultProfiles.map (name) -> $scope.validResultProfiles = []
availableProfiles['+' + name] for name in validResultProfiles
shown = (name.charCodeAt(0) != charCodeUnderscore or
name.charCodeAt(1) != charCodeUnderscore)
if shown
$scope.validResultProfiles.push(availableProfiles['+' + name])
omegaTarget.getActivePageInfo().then((info) -> omegaTarget.getActivePageInfo().then((info) ->
if info if info

View File

@ -271,6 +271,16 @@ main {
} }
} }
.switch-attached {
> tr > td {
background-color: #F9F9F9;
&:first-child {
text-align: center;
}
}
}
.fixed-show-advanced { .fixed-show-advanced {
td { td {
background-color: transparent !important; background-color: transparent !important;

View File

@ -1,4 +1,6 @@
angular.module('omega').controller 'SwitchProfileCtrl', ($scope, $modal) -> angular.module('omega').controller 'SwitchProfileCtrl', ($scope, $modal,
profileIcons) ->
$scope.conditionI18n = $scope.conditionI18n =
'HostWildcardCondition': 'condition_hostWildcard' 'HostWildcardCondition': 'condition_hostWildcard'
'HostRegexCondition': 'condition_hostRegex' 'HostRegexCondition': 'condition_hostRegex'
@ -63,3 +65,52 @@ angular.module('omega').controller 'SwitchProfileCtrl', ($scope, $modal) ->
forceHelperSize: true forceHelperSize: true
forcePlaceholderSize: true forcePlaceholderSize: true
containment: 'parent' containment: 'parent'
$scope.ruleListFormats = OmegaPac.Profiles.ruleListFormats
$scope.$watch 'profile.name', (name) ->
$scope.attachedName = '__ruleListOf_' + name
$scope.attachedKey = OmegaPac.Profiles.nameAsKey('__ruleListOf_' + name)
$scope.$watch 'options[attachedKey]', (attached) ->
$scope.attached = attached
onAttachedChange = (profile, oldProfile) ->
return profile if profile == oldProfile or not profile or not oldProfile
OmegaPac.Profiles.updateRevision(profile)
return profile
$scope.omegaWatchAndChange 'options[attachedKey]', onAttachedChange, true
$scope.$watch 'profile.defaultProfileName', (name) ->
if not $scope.attached
$scope.defaultProfileName = name
$scope.$watch 'attached.defaultProfileName', (name) ->
if name
$scope.defaultProfileName = name
$scope.$watch 'defaultProfileName', (name) ->
($scope.attached || $scope.profile).defaultProfileName = name
$scope.attachNew = ->
$scope.attached = OmegaPac.Profiles.create(
name: $scope.attachedName
defaultProfileName: $scope.profile.defaultProfileName
profileType: 'RuleListProfile'
color: $scope.profile.color
)
OmegaPac.Profiles.updateRevision($scope.attached)
$scope.options[$scope.attachedKey] = $scope.attached
$scope.profile.defaultProfileName = $scope.attachedName
$scope.removeAttached = ->
return unless $scope.attached
scope = $scope.$new('isolate')
scope.attached = $scope.attached
scope.profileIcons = profileIcons
$modal.open(
templateUrl: 'partials/delete_attached.html'
scope: scope
).result.then ->
$scope.profile.defaultProfileName = $scope.attached.defaultProfileName
delete $scope.options[$scope.attachedKey]

View File

@ -0,0 +1,14 @@
.modal-header
button.close(type='button' ng-click='$dismiss()')
span(aria-hidden='true') ×
span.sr-only {{'dialog_close' | tr}}
h4.modal-title {{'options_modalHeader_deleteAttached' | tr}}
.modal-body
p {{'options_deleteAttachedConfirm' | tr}}
.well
span.glyphicon(class='{{profileIcons[attached.profileType]}}')
= ' '
| {{attached.sourceUrl || ('options_ruleListLineCount' | tr:[attached.ruleList.split('\r?\n').length])}}
.modal-footer
button.btn.btn-default(ng-click='$dismiss()') {{'dialog_cancel' | tr}}
button.btn.btn-danger(type='button' ng-click='$close("ok")') {{'options_deleteAttached' | tr}}

View File

@ -16,7 +16,7 @@ div(ng-controller='RuleListProfileCtrl')
.radio.inline-form-control.no-min-width(ng-repeat='format in ruleListFormats') .radio.inline-form-control.no-min-width(ng-repeat='format in ruleListFormats')
label label
input(type='radio' name='formatInput' value='{{format}}' ng-model='profile.format') input(type='radio' name='formatInput' value='{{format}}' ng-model='profile.format')
| {{'rulelistFormat_' + format | tr}} | {{'ruleListFormat_' + format | tr}}
section.settings-group section.settings-group
h3 {{'options_group_ruleListUrl' | tr}} h3 {{'options_group_ruleListUrl' | tr}}
.width-limit(input-group-clear type='url' model='profile.sourceUrl' ng-if='profile') .width-limit(input-group-clear type='url' model='profile.sourceUrl' ng-if='profile')

View File

@ -1,53 +1,96 @@
section.settings-group(ng-controller='SwitchProfileCtrl') div(ng-controller='SwitchProfileCtrl')
h3 {{'options_group_switchRules' | tr}} section.settings-group
.table-responsive h3 {{'options_group_switchRules' | tr}}
table.switch-rules.table.table-bordered.table-condensed.width-limit-xl .table-responsive
thead table.switch-rules.table.table-bordered.table-condensed.width-limit-xl
tr thead
th(style='white-space: nowrap') {{'options_sort' | tr}} tr
th {{'options_conditionType' | tr}} th(style='white-space: nowrap') {{'options_sort' | tr}}
th {{'options_conditionDetails' | tr}} th {{'options_conditionType' | tr}}
th {{'options_resultProfile' | tr}} th {{'options_conditionDetails' | tr}}
th {{'options_conditionActions' | tr}} th {{'options_resultProfile' | tr}}
tbody(ui-sortable='sortableOptions' ng-model='profile.rules') th {{'options_conditionActions' | tr}}
tr(ng-repeat='rule in profile.rules') tbody(ui-sortable='sortableOptions' ng-model='profile.rules')
td.sort-bar tr(ng-repeat='rule in profile.rules')
span.glyphicon.glyphicon-sort td.sort-bar
td span.glyphicon.glyphicon-sort
select.form-control(ng-model='rule.condition.conditionType' td
ng-options='cond as (i18n | tr) for (cond, i18n) in conditionI18n') select.form-control(ng-model='rule.condition.conditionType'
td(ng-switch='rule.condition.conditionType') ng-options='cond as (i18n | tr) for (cond, i18n) in conditionI18n')
span(ng-switch-when='AlwaysCondition') {{'condition_always_details' | tr}} td(ng-switch='rule.condition.conditionType')
span(ng-switch-when='NeverCondition') {{'condition_never_details' | tr}} span(ng-switch-when='AlwaysCondition') {{'condition_always_details' | tr}}
span.host-levels-details(ng-switch-when='HostLevelsCondition') span(ng-switch-when='NeverCondition') {{'condition_never_details' | tr}}
input.form-control(type='number' min='1' max='99' ng-model='rule.condition.minValue' required) span.host-levels-details(ng-switch-when='HostLevelsCondition')
= ' ' input.form-control(type='number' min='1' max='99' ng-model='rule.condition.minValue' required)
span {{'options_hostLevelsBetween' | tr}} = ' '
= ' ' span {{'options_hostLevelsBetween' | tr}}
input.form-control(type='number' max='99' min='1' ng-model='rule.condition.maxValue' required) = ' '
input.form-control(ng-model='rule.condition.pattern' ng-switch-default required input.form-control(type='number' max='99' min='1' ng-model='rule.condition.maxValue' required)
ui-validate='{pattern: "validateCondition(rule.condition, $value)"}') input.form-control(ng-model='rule.condition.pattern' ng-switch-default required
td ui-validate='{pattern: "validateCondition(rule.condition, $value)"}')
div.form-control(omega-profile-select='options | profiles:profile' ng-model='rule.profileName' td
disp-name='$profile.name | dispName') div.form-control(omega-profile-select='options | profiles:profile' ng-model='rule.profileName'
td disp-name='$profile.name | dispName')
button.btn.btn-danger.btn-sm(title="{{'options_deleteRule' | tr}}" ng-click='removeRule($index)') td
span.glyphicon.glyphicon-trash button.btn.btn-danger.btn-sm(title="{{'options_deleteRule' | tr}}" ng-click='removeRule($index)')
tbody span.glyphicon.glyphicon-trash
tr tbody
td(style='border-right: none;') tr
td(style='border-left: none;', colspan='4') td(style='border-right: none;')
button.btn.btn-default.btn-sm(ng-click='addRule()') td(style='border-left: none;', colspan='4')
span.glyphicon.glyphicon-plus button.btn.btn-default.btn-sm(ng-click='addRule()')
= ' ' span.glyphicon.glyphicon-plus
span {{'options_addCondition' | tr}} = ' '
tbody span {{'options_addCondition' | tr}}
tr tbody.switch-attached(ng-if='attached')
td tr
td(colspan='2') {{'options_switchDefaultProfile' | tr}} td(style='border-right: none;')
td span.glyphicon(class='{{profileIcons["RuleListProfile"]}}')
div.form-control(omega-profile-select='options | profiles:profile' ng-model='profile.defaultProfileName' td(style='border-left: none;') {{'options_switchAttachedProfileInCondition' | tr}}
disp-name='$profile.name | dispName') td
td span {{'options_switchAttachedProfileInConditionDetails' | tr}}
button.btn.btn-info.btn-sm(title="{{'options_resetRules_help' | tr}}" ng-click='resetRules()') td
span.glyphicon.glyphicon-chevron-up div.form-control(omega-profile-select='options | profiles:profile' ng-model='attached.matchProfileName'
disp-name='$profile.name | dispName')
td
button.btn.btn-danger.btn-sm(title="{{'options_deleteAttached' | tr}}" ng-click='removeAttached()')
span.glyphicon.glyphicon-trash
tbody
tr
td
td(colspan='2') {{'options_switchDefaultProfile' | tr}}
td
div.form-control(omega-profile-select='options | profiles:profile' ng-model='defaultProfileName'
disp-name='$profile.name | dispName')
td
button.btn.btn-info.btn-sm(title="{{'options_resetRules_help' | tr}}" ng-click='resetRules()')
span.glyphicon.glyphicon-chevron-up
section.settings-group(ng-if='!attached')
h3 {{'options_group_attachProfile' | tr}}
p.help-block {{'options_attachProfileHelp' | tr}}
button.btn.btn-default(ng-click='attachNew()')
span.glyphicon.glyphicon-plus
= ' '
| {{'options_attachProfile' | tr}}
section.settings-group(ng-if='attached')
h3 {{'options_group_ruleListConfig' | tr}}
form
.form-group
label {{'options_ruleListFormat' | tr}}
.radio.inline-form-control.no-min-width(ng-repeat='format in ruleListFormats')
label
input(type='radio' name='formatInput' value='{{format}}' ng-model='attached.format')
| {{'ruleListFormat_' + format | tr}}
.form-group
label {{'options_group_ruleListUrl' | tr}}
.width-limit.inline-form-control(input-group-clear type='url' model='attached.sourceUrl'
style='vertical-align: middle')
p.help-block {{'options_ruleListUrlHelp' | tr}}
section.settings-group(ng-if='attached')
h3 {{'options_group_ruleListText' | tr}}
p
button.btn.btn-default(ng-disabled='!attached.sourceUrl' ng-click='updateProfile(attached.name)'
ladda='updatingProfile[attached.name]' data-spinner-color="#000000")
| #[span.glyphicon.glyphicon-download-alt] {{'options_downloadProfileNow' | tr}}
textarea.form-control.width-limit(ng-model='attached.ruleList' rows=20
ng-disabled='!!attached.sourceUrl')