From 75ecb9aa46fd0f67a9dd301c12375478b13e3523 Mon Sep 17 00:00:00 2001 From: FelisCatus Date: Mon, 9 Feb 2015 21:14:23 +0800 Subject: [PATCH] Allow editing switch profiles as omega rule list. Fix #80. --- omega-i18n/en/messages.json | 50 ++++ omega-i18n/zh_CN/messages.json | 50 ++++ omega-i18n/zh_HK/messages.json | 50 ++++ omega-i18n/zh_TW/messages.json | 50 ++++ omega-pac/src/rule_list.coffee | 38 ++- omega-web/src/omega/controllers/master.coffee | 1 + .../omega/controllers/switch_profile.coffee | 234 +++++++++++++----- omega-web/src/partials/io.jade | 2 +- omega-web/src/partials/profile_switch.jade | 32 ++- 9 files changed, 428 insertions(+), 79 deletions(-) diff --git a/omega-i18n/en/messages.json b/omega-i18n/en/messages.json index f0e8e9e..b39c916 100644 --- a/omega-i18n/en/messages.json +++ b/omega-i18n/en/messages.json @@ -98,6 +98,47 @@ "ruleList_usageUrl": { "message": "https://github.com/FelisCatus/SwitchyOmega/wiki/RuleListUsage" }, + "ruleList_error_resultNotEnabled": { + "message": "Missing '@with result' directive!" + }, + "ruleList_error_unknownProfile": { + "message": "Unknown profile: $PROFILE$", + "placeholders": { + "profile": { + "content": "$1", + "example": "some profile" + } + } + }, + "ruleList_error_missingResultProfile": { + "message": "Missing result profile name at Line $LNO$: $SOURCE$", + "placeholders": { + "lno": { + "content": "$1", + "example": "11" + }, + "source": { + "content": "$2", + "example": "*.example.com" + } + } + }, + "ruleList_error_invalidRule": { + "message": "Invalid rule at Line $LNO$: $SOURCE$", + "placeholders": { + "lno": { + "content": "$1", + "example": "11" + }, + "source": { + "content": "$2", + "example": ":invalid rule" + } + } + }, + "ruleList_error_noDefaultRule": { + "message": "Missing default rule with catch-all '*' condition!" + }, "dialog_close": { "message": "Close" @@ -344,6 +385,15 @@ "options_profileUnsupportedHelp": { "message": "The options could be broken, or from a newer version of this program." }, + "options_profileEditSource": { + "message": "Edit source code" + }, + "options_profileEditSourceHelp": { + "message": "Show help about the source code format" + }, + "options_profileEditSourceHelpUrl": { + "message": "https://github.com/FelisCatus/SwitchyOmega/wiki/SwitchyOmega-conditions-format#result-profile" + }, "options_group_proxyServers": { "message": "Proxy servers" }, diff --git a/omega-i18n/zh_CN/messages.json b/omega-i18n/zh_CN/messages.json index aa25290..873daab 100644 --- a/omega-i18n/zh_CN/messages.json +++ b/omega-i18n/zh_CN/messages.json @@ -98,6 +98,47 @@ "ruleList_usageUrl": { "message": "https://github.com/FelisCatus/SwitchyOmega/wiki/RuleListUsage" }, + "ruleList_error_resultNotEnabled": { + "message": "语法错误:缺少 '@with result' 指令。" + }, + "ruleList_error_unknownProfile": { + "message": "Unknown profile: $PROFILE$", + "placeholders": { + "profile": { + "content": "$1", + "example": "some profile" + } + } + }, + "ruleList_error_missingResultProfile": { + "message": "语法错误:缺少结果情景模式名称。 行号 $LNO$: $SOURCE$", + "placeholders": { + "lno": { + "content": "$1", + "example": "11" + }, + "source": { + "content": "$2", + "example": "*.example.com" + } + } + }, + "ruleList_error_invalidRule": { + "message": "语法错误:非法规则。行号 $LNO$: $SOURCE$", + "placeholders": { + "lno": { + "content": "$1", + "example": "11" + }, + "source": { + "content": "$2", + "example": ":invalid rule" + } + } + }, + "ruleList_error_noDefaultRule": { + "message": "语法错误:缺少匹配全部请求的默认规则。应在最后添加'*'规则和默认情景模式。" + }, "dialog_close": { "message": "关闭" @@ -344,6 +385,15 @@ "options_profileUnsupportedHelp": { "message": "选项文件已经损坏,或者当前版本过低无法处理选项。" }, + "options_profileEditSource": { + "message": "编辑源代码" + }, + "options_profileEditSourceHelp": { + "message": "显示源代码格式相关的帮助" + }, + "options_profileEditSourceHelpUrl": { + "message": "https://github.com/FelisCatus/SwitchyOmega/wiki/SwitchyOmega-conditions-format#result-profile" + }, "options_group_proxyServers": { "message": "代理服务器" }, diff --git a/omega-i18n/zh_HK/messages.json b/omega-i18n/zh_HK/messages.json index e202ab1..7c53418 100644 --- a/omega-i18n/zh_HK/messages.json +++ b/omega-i18n/zh_HK/messages.json @@ -98,6 +98,47 @@ "ruleList_usageUrl": { "message": "https://github.com/FelisCatus/SwitchyOmega/wiki/RuleListUsage" }, + "ruleList_error_resultNotEnabled": { + "message": "語法錯誤:缺少 '@with result' 指令。" + }, + "ruleList_error_unknownProfile": { + "message": "Unknown profile: $PROFILE$", + "placeholders": { + "profile": { + "content": "$1", + "example": "some profile" + } + } + }, + "ruleList_error_missingResultProfile": { + "message": "語法錯誤:缺少結果情景模式名稱。 行號 $LNO$: $SOURCE$", + "placeholders": { + "lno": { + "content": "$1", + "example": "11" + }, + "source": { + "content": "$2", + "example": "*.example.com" + } + } + }, + "ruleList_error_invalidRule": { + "message": "語法錯誤:非法規則。行號 $LNO$: $SOURCE$", + "placeholders": { + "lno": { + "content": "$1", + "example": "11" + }, + "source": { + "content": "$2", + "example": ":invalid rule" + } + } + }, + "ruleList_error_noDefaultRule": { + "message": "語法錯誤:缺少匹配全部請求的預設規則。應在最後新增'*'規則和預設情景模式。" + }, "dialog_close": { "message": "關閉" @@ -344,6 +385,15 @@ "options_profileUnsupportedHelp": { "message": "選項文件已經損壞,或者當前版本過低無法處理選項。" }, + "options_profileEditSource": { + "message": "編輯原始碼" + }, + "options_profileEditSourceHelp": { + "message": "顯示原始碼格式相關的幫助" + }, + "options_profileEditSourceHelpUrl": { + "message": "https://github.com/FelisCatus/SwitchyOmega/wiki/SwitchyOmega-conditions-format#result-profile" + }, "options_group_proxyServers": { "message": "代理服務器" }, diff --git a/omega-i18n/zh_TW/messages.json b/omega-i18n/zh_TW/messages.json index d08b969..9d422ea 100644 --- a/omega-i18n/zh_TW/messages.json +++ b/omega-i18n/zh_TW/messages.json @@ -98,6 +98,47 @@ "ruleList_usageUrl": { "message": "https://github.com/FelisCatus/SwitchyOmega/wiki/RuleListUsage" }, + "ruleList_error_resultNotEnabled": { + "message": "語法錯誤:缺少 '@with result' 指令。" + }, + "ruleList_error_unknownProfile": { + "message": "Unknown profile: $PROFILE$", + "placeholders": { + "profile": { + "content": "$1", + "example": "some profile" + } + } + }, + "ruleList_error_missingResultProfile": { + "message": "語法錯誤:缺少結果情景模式名稱。 行號 $LNO$: $SOURCE$", + "placeholders": { + "lno": { + "content": "$1", + "example": "11" + }, + "source": { + "content": "$2", + "example": "*.example.com" + } + } + }, + "ruleList_error_invalidRule": { + "message": "語法錯誤:非法規則。行號 $LNO$: $SOURCE$", + "placeholders": { + "lno": { + "content": "$1", + "example": "11" + }, + "source": { + "content": "$2", + "example": ":invalid rule" + } + } + }, + "ruleList_error_noDefaultRule": { + "message": "語法錯誤:缺少匹配全部請求的預設規則。應在最後新增'*'規則和預設情景模式。" + }, "dialog_close": { "message": "關閉" @@ -344,6 +385,15 @@ "options_profileUnsupportedHelp": { "message": "選項檔案已經損壞,或者當前版本過低無法處理選項。" }, + "options_profileEditSource": { + "message": "編輯原始碼" + }, + "options_profileEditSourceHelp": { + "message": "顯示原始碼格式相關的幫助" + }, + "options_profileEditSourceHelpUrl": { + "message": "https://github.com/FelisCatus/SwitchyOmega/wiki/SwitchyOmega-conditions-format#result-profile" + }, "options_group_proxyServers": { "message": "代理伺服器" }, diff --git a/omega-pac/src/rule_list.coffee b/omega-pac/src/rule_list.coffee index 77a3e6d..531ae46 100644 --- a/omega-pac/src/rule_list.coffee +++ b/omega-pac/src/rule_list.coffee @@ -70,7 +70,7 @@ module.exports = exports = switchy = exports['Switchy'] parser = switchy.getParser(text) return unless parser == 'parseOmega' - return unless /(^|\n)@with\s+results?(\r|\n)/i.test(text) + return unless /(^|\n)@with\s+results?(\r|\n|$)/i.test(text) refs = {} for line in text.split(/\n|\r/) line = line.trim() @@ -107,7 +107,7 @@ module.exports = exports = ruleList += line + eol if withResult # TODO(catus): Also special chars and sequences in defaultProfileName. - ruleList += '* +' + defaultProfileName + eol + ruleList += eol + '* +' + defaultProfileName + eol return ruleList getParser: (text) -> @@ -174,11 +174,20 @@ module.exports = exports = parseOmega: (text, matchProfileName, defaultProfileName, args = {}) -> {strict} = args + if strict + error = (fields) -> + err = new Error(fields.message) + for own key, value of fields + err[key] = value + throw err + includeSource = args.source ? true rules = [] rulesWithDefaultProfile = [] withResult = false exclusiveProfile = null + lno = 0 for line in text.split(/\n|\r/) + lno++ line = line.trim() continue if line.length == 0 switch line[0] @@ -199,6 +208,7 @@ module.exports = exports = continue source = null + exclusiveProfile = null if strict if line[0] == '!' profile = if withResult then null else defaultProfileName source = line @@ -206,7 +216,12 @@ module.exports = exports = else if withResult iSpace = line.lastIndexOf(' +') if iSpace < 0 - throw new Error("Missing result profile name: " + line) if strict + error?({ + message: "Missing result profile name: " + line + reason: 'missingResultProfile' + source: line + sourceLineNo: lno + }) continue profile = line.substr(iSpace + 2).trim() line = line.substr(0, iSpace).trim() @@ -216,10 +231,18 @@ module.exports = exports = cond = Conditions.fromStr(line) if not cond - throw new Error("Invalid rule: " + line) if strict + error?({ + message: "Invalid rule: " + line + reason: 'invalidRule' + source: source ? line + sourceLineNo: lno + }) continue - rule = {condition: cond, profileName: profile, source: source ? line} + rule = + condition: cond + profileName: profile + source: if includeSource then source ? line rules.push(rule) if not profile rulesWithDefaultProfile.push(rule) @@ -227,7 +250,10 @@ module.exports = exports = if withResult if not exclusiveProfile if strict - throw new Error("Missing default rule with catch-all '*' condition") + error?({ + message: "Missing default rule with catch-all '*' condition" + reason: 'noDefaultRule' + }) exclusiveProfile = defaultProfileName || 'direct' for rule in rulesWithDefaultProfile rule.profileName = exclusiveProfile diff --git a/omega-web/src/omega/controllers/master.coffee b/omega-web/src/omega/controllers/master.coffee index b0be72d..41b8931 100644 --- a/omega-web/src/omega/controllers/master.coffee +++ b/omega-web/src/omega/controllers/master.coffee @@ -78,6 +78,7 @@ angular.module('omega').controller 'MasterCtrl', ($scope, $rootScope, $window, $rootScope.applyOptions = -> return unless checkFormValid() + return if $rootScope.$broadcast('omegaApplyOptions').defaultPrevented plainOptions = angular.fromJson(angular.toJson($rootScope.options)) patch = diff.diff($rootScope.optionsOld, plainOptions) omegaTarget.optionsPatch(patch).then -> diff --git a/omega-web/src/omega/controllers/switch_profile.coffee b/omega-web/src/omega/controllers/switch_profile.coffee index aaecf1f..026d517 100644 --- a/omega-web/src/omega/controllers/switch_profile.coffee +++ b/omega-web/src/omega/controllers/switch_profile.coffee @@ -1,6 +1,57 @@ -angular.module('omega').controller 'SwitchProfileCtrl', ($scope, $location, - $modal, profileIcons, getAttachedName, omegaTarget, $timeout, trFilter) -> +angular.module('omega').controller 'SwitchProfileCtrl', ($scope, $rootScope, + $location, $timeout, $q, $modal, profileIcons, getAttachedName, omegaTarget, + trFilter) -> + # == Rule list == + $scope.ruleListFormats = OmegaPac.Profiles.ruleListFormats + exportRuleList = -> + text = OmegaPac.RuleList.Switchy.compose($scope.profile) + + eol = '\r\n' + info = '\n' + info += '; Require: SwitchyOmega >= 2.3.2' + eol + info += "; Date: #{new Date().toLocaleDateString()}" + eol + info += "; Usage: #{trFilter('ruleList_usageUrl')}" + eol + + text = text.replace('\n', info) + + blob = new Blob [text], {type: "text/plain;charset=utf-8"} + fileName = $scope.profile.name.replace(/\W+/g, '_') + saveAs(blob, "OmegaRules_#{fileName}.sorl") + + exportLegacyRuleList = -> + wildcardRules = '' + regexpRules = '' + for rule in $scope.profile.rules + i = '' + if rule.profileName == $scope.attachedOptions.defaultProfileName + i = '!' + switch rule.condition.conditionType + when 'HostWildcardCondition' + wildcardRules += i + '@*://' + rule.condition.pattern + '/*' + '\r\n' + when 'UrlWildcardCondition' + wildcardRules += i + '@' + rule.condition.pattern + '\r\n' + when 'UrlRegexCondition' + regexpRules += i + rule.condition.pattern + '\r\n' + + text = """ + ; Summary: Proxy Switchy! Exported Rule List + ; Date: #{new Date().toLocaleDateString()} + ; Website: http://bit.ly/proxyswitchy + + #BEGIN + + [wildcard] + #{wildcardRules} + [regexp] + #{regexpRules} + #END + """ + blob = new Blob [text], {type: "text/plain;charset=utf-8"} + fileName = $scope.profile.name.replace(/\W+/g, '_') + saveAs(blob, "SwitchyRules_#{fileName}.ssrl") + + # == Condition types == $scope.showConditionHelp = ($location.search().help == 'condition') $scope.basicConditionTypes = [ @@ -40,53 +91,6 @@ angular.module('omega').controller 'SwitchProfileCtrl', ($scope, $location, } ] - exportRuleList = -> - text = OmegaPac.RuleList.Switchy.compose($scope.profile) - - eol = '\r\n' - info = '\n' - info += '; Require: SwitchyOmega >= 2.3.2' + eol - info += "; Date: #{new Date().toLocaleDateString()}" + eol - info += "; Usage: #{trFilter('ruleList_usageUrl')}" + eol - - text = text.replace('\n', info) - - blob = new Blob [text], {type: "text/plain;charset=utf-8"} - fileName = $scope.profile.name.replace(/\W+/g, '_') - saveAs(blob, "OmegaRules_#{fileName}.sorl") - - exportLegacyRuleList = -> - wildcardRules = '' - regexpRules = '' - for rule in $scope.profile.rules - i = '' - if rule.profileName == $scope.defaultProfileName - i = '!' - switch rule.condition.conditionType - when 'HostWildcardCondition' - wildcardRules += i + '@*://' + rule.condition.pattern + '/*' + '\r\n' - when 'UrlWildcardCondition' - wildcardRules += i + '@' + rule.condition.pattern + '\r\n' - when 'UrlRegexCondition' - regexpRules += i + rule.condition.pattern + '\r\n' - - text = """ - ; Summary: Proxy Switchy! Exported Rule List - ; Date: #{new Date().toLocaleDateString()} - ; Website: http://bit.ly/proxyswitchy - - #BEGIN - - [wildcard] - #{wildcardRules} - [regexp] - #{regexpRules} - #END - """ - blob = new Blob [text], {type: "text/plain;charset=utf-8"} - fileName = $scope.profile.name.replace(/\W+/g, '_') - saveAs(blob, "SwitchyRules_#{fileName}.ssrl") - expandGroups = (groups) -> result = [] for group in groups @@ -149,6 +153,14 @@ angular.module('omega').controller 'SwitchProfileCtrl', ($scope, $location, if $scope.hasConditionTypes == 0 unwatchRules = $scope.$watch 'profile.rules', updateHasConditionTypes, true + # == Rules == + rulesReadyDefer = $q.defer() + rulesReady = rulesReadyDefer.promise + stopWatchingForRules = $scope.$watch 'profile.rules', (rules) -> + return unless rules + stopWatchingForRules() + rulesReadyDefer.resolve(rules) + $scope.addRule = -> rule = if $scope.profile.rules.length > 0 @@ -156,7 +168,7 @@ angular.module('omega').controller 'SwitchProfileCtrl', ($scope, $location, angular.copy(templ) else condition: {conditionType: 'HostWildcardCondition', pattern: ''} - profileName: $scope.profile.defaultProfileName + profileName: $scope.attachedOptions.defaultProfileName if rule.condition.pattern rule.condition.pattern = '' $scope.profile.rules.push rule @@ -201,7 +213,8 @@ angular.module('omega').controller 'SwitchProfileCtrl', ($scope, $location, $scope.resetRules = -> scope = $scope.$new('isolate') - scope.ruleProfile = $scope.profileByName($scope.defaultProfileName) + scope.ruleProfile = + $scope.profileByName($scope.attachedOptions.defaultProfileName) scope.dispNameFilter = $scope.dispNameFilter scope.options = $scope.options $modal.open( @@ -209,7 +222,7 @@ angular.module('omega').controller 'SwitchProfileCtrl', ($scope, $location, scope: scope ).result.then -> for rule in $scope.profile.rules - rule.profileName = $scope.defaultProfileName + rule.profileName = $scope.attachedOptions.defaultProfileName $scope.sortableOptions = handle: '.sort-bar' @@ -219,8 +232,9 @@ angular.module('omega').controller 'SwitchProfileCtrl', ($scope, $location, forcePlaceholderSize: true containment: 'parent' - $scope.ruleListFormats = OmegaPac.Profiles.ruleListFormats - + # == Attached == + attachedReadyDefer = $q.defer() + attachedReady = attachedReadyDefer.promise $scope.$watch 'profile.name', (name) -> $scope.attachedName = getAttachedName(name) $scope.attachedKey = OmegaPac.Profiles.nameAsKey($scope.attachedName) @@ -250,7 +264,7 @@ angular.module('omega').controller 'SwitchProfileCtrl', ($scope, $location, $scope.$watch 'profile.defaultProfileName', (name) -> $scope.attachedOptions.enabled = (name == $scope.attachedName) if not $scope.attached or not $scope.attachedOptions.enabled - $scope.defaultProfileName = name + $scope.attachedOptions.defaultProfileName = name $scope.$watch 'attachedOptions.enabled', (enabled, oldValue) -> return if enabled == oldValue @@ -261,16 +275,18 @@ angular.module('omega').controller 'SwitchProfileCtrl', ($scope, $location, if $scope.profile.defaultProfileName == $scope.attachedName if $scope.attached $scope.profile.defaultProfileName = $scope.attached.defaultProfileName - $scope.defaultProfileName = $scope.attached.defaultProfileName + $scope.attachedOptions.defaultProfileName = + $scope.attached.defaultProfileName else $scope.profile.defaultProfileName = 'direct' - $scope.defaultProfileName = 'direct' + $scope.attachedOptions.defaultProfileName = 'direct' $scope.$watch 'attached.defaultProfileName', (name) -> if name and $scope.attachedOptions.enabled - $scope.defaultProfileName = name + $scope.attachedOptions.defaultProfileName = name - $scope.$watch 'defaultProfileName', (name) -> + $scope.$watch 'attachedOptions.defaultProfileName', (name) -> + attachedReadyDefer.resolve() if $scope.attached and $scope.attachedOptions.enabled $scope.attached.defaultProfileName = name else @@ -301,11 +317,95 @@ angular.module('omega').controller 'SwitchProfileCtrl', ($scope, $location, $scope.profile.defaultProfileName = $scope.attached.defaultProfileName delete $scope.options[$scope.attachedKey] - stopWatchingForGuide = $scope.$watch 'profile.rules', (rules) -> - return unless rules - stopWatchingForGuide() - omegaTarget.state(['web.switchGuide', 'firstRun' - ]).then ([switchGuide, firstRun]) -> - return if firstRun or switchGuide == 'shown' - $script 'js/switch_profile_guide.js' - omegaTarget.state('web.switchGuide', 'shown') + # == Edit source == + stateEditorKey = 'web._profileEditor.' + $scope.profile.name + $scope.loadRules = false + $scope.editSource = false + parseOmegaRules = (code, {detect, requireResult} = {}) -> + setError = (error) -> + if error.reason + args = error.args ? [ + error.sourceLineNo + error.source + ] + message = trFilter('ruleList_error_' + error.reason, args) + error.message = message if message + return {error: error} + if detect and not OmegaPac.RuleList.Switchy.detect(code) + return {error: {reason: 'notSwitchy'}} + refs = OmegaPac.RuleList.Switchy.directReferenceSet({ + ruleList: code + }) + if requireResult and not refs + return setError({reason: 'resultNotEnabled'}) + for own key, name of refs + if not OmegaPac.Profiles.byKey(key, $scope.options) + return setError({reason: 'unknownProfile', args: [name]}) + try + return rules: OmegaPac.RuleList.Switchy.parseOmega(code, null, null, + {strict: true, source: false}) + catch err + return setError(err) + parseSource = -> + return true unless $scope.source + {rules, error} = parseOmegaRules($scope.source.code.trim(), + requireResult: true) + if error + $scope.source.error = error + $scope.editSource = true + return false + else + $scope.source.error = undefined + $scope.attachedOptions.defaultProfileName = rules.pop().profileName + # Try to merge with existing rules if possible. + diff = jsondiffpatch.create( + objectHash: (obj) -> JSON.stringify(obj) + textDiff: minLength: 1 / 0 + ) + oldRules = angular.fromJson(angular.toJson($scope.profile.rules)) + patch = diff.diff(oldRules, rules) + jsondiffpatch.patch($scope.profile.rules, patch) + return true + $scope.toggleSource = -> $q.all([attachedReady, rulesReady]).then -> + $scope.editSource = not $scope.editSource + if $scope.editSource + args = + rules: $scope.profile.rules + defaultProfileName: $scope.attachedOptions.defaultProfileName + code = OmegaPac.RuleList.Switchy.compose(args, withResult: true) + $scope.source = {code: code} + else + return unless parseSource() + $scope.source = null + $scope.loadRules = true + omegaTarget.state(stateEditorKey, {editSource: $scope.editSource}) + + $scope.$on 'omegaApplyOptions', (event) -> + if $scope.attached?.ruleList and not $scope.attached.sourceUrl + $scope.attachedRuleListError = undefined + {error} = parseOmegaRules($scope.attached.ruleList.trim(), detect: true) + if error + if error.reason != 'resultNotEnabled' and error.reason != 'notSwitchy' + $scope.attachedRuleListError = error + event.preventDefault() + angular.element('#attached-rulelist')[0].focus() + else + $scope.attached.format = 'Switchy' + + if $scope.editSource and $scope.source.touched + event.preventDefault() + if parseSource() + $scope.source.touched = false + $timeout -> + $rootScope.applyOptions() + + omegaTarget.state(stateEditorKey).then (opts) -> + if opts?.editSource + $scope.toggleSource() + else + $scope.loadRules = true + getState = omegaTarget.state(['web.switchGuide', 'firstRun']) + $q.all([rulesReady, getState]).then ([_, [switchGuide, firstRun]]) -> + return if firstRun or switchGuide == 'shown' + $script 'js/switch_profile_guide.js' + omegaTarget.state('web.switchGuide', 'shown') diff --git a/omega-web/src/partials/io.jade b/omega-web/src/partials/io.jade index f604587..c767277 100644 --- a/omega-web/src/partials/io.jade +++ b/omega-web/src/partials/io.jade @@ -6,7 +6,7 @@ section.settings-group .text-info span.glyphicon.glyphicon-info-sign = ' ' - {{'options_exportProfileHelp' | tr}} + | {{'options_exportProfileHelp' | tr}} div.checkbox(ng-show='!(options["-showConditionTypes"] > 0)') label input(type='checkbox' ng-model='options["-exportLegacyRuleList"]') diff --git a/omega-web/src/partials/profile_switch.jade b/omega-web/src/partials/profile_switch.jade index 9d5e90e..c698d56 100644 --- a/omega-web/src/partials/profile_switch.jade +++ b/omega-web/src/partials/profile_switch.jade @@ -15,8 +15,26 @@ div(ng-controller='SwitchProfileCtrl') 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.switch-rules-wrapper + h3 + | {{'options_group_switchRules' | tr}} + = ' ' + button.btn(ng-click='toggleSource()' ng-class='editSource ? "btn-primary active" : "btn-default"') + span.glyphicon.glyphicon-edit + = ' ' + | {{'options_profileEditSource' | tr}} + = ' ' + a.btn.btn-link.btn-sm.clear-padding.toggle-condition-help( + ng-show='editSource' target='_blank' + title='{{"options_profileEditSourceHelp" | tr}}' + href='{{"options_profileEditSourceHelpUrl" | tr}}') + span.glyphicon.glyphicon-question-sign + .alert.alert-danger.width-limit(ng-show='source.error') + span.glyphicon.glyphicon-remove + = ' ' + | {{source.error.message}} + .rules-source(ng-show='editSource') + textarea.monospace.form-control.width-limit(ng-model='source.code' rows=20 ng-change='source.touched = true; $root.optionsDirty = true') + .table-responsive.switch-rules-wrapper(ng-if='loadRules' ng-show='!editSource') table.switch-rules.table.table-bordered.table-condensed.width-limit-xl thead tr @@ -94,7 +112,7 @@ div(ng-controller='SwitchProfileCtrl') td td(colspan='2') {{'options_switchDefaultProfile' | tr}} td - div(omega-profile-select='options | profiles:profile' ng-model='defaultProfileName' + div(omega-profile-select='options | profiles:profile' ng-model='attachedOptions.defaultProfileName' disp-name='dispNameFilter' options='options') td button.btn.btn-info.btn-sm(title="{{'options_resetRules_help' | tr}}" ng-click='resetRules()') @@ -131,5 +149,9 @@ div(ng-controller='SwitchProfileCtrl') | {{'options_ruleListLastUpdate' | tr:[(attached.lastUpdate | date:'medium')]}} p.alert.alert-danger.width-limit(ng-show='attached.sourceUrl && !attached.lastUpdate') | {{'options_ruleListObsolete' | tr}} - textarea.monospace.form-control.width-limit(ng-model='attached.ruleList' rows=20 - ng-disabled='!!attached.sourceUrl') + p.alert.alert-danger.width-limit(ng-show='attachedRuleListError') + span.glyphicon.glyphicon-remove + = ' ' + | {{attachedRuleListError.message}} + textarea#attached-rulelist.monospace.form-control.width-limit(rows=20 + ng-model='attached.ruleList' ng-disabled='!!attached.sourceUrl')