aboutsummaryrefslogtreecommitdiffstats
path: root/pages
diff options
context:
space:
mode:
Diffstat (limited to 'pages')
-rw-r--r--pages/options.coffee262
-rw-r--r--pages/options.html63
-rw-r--r--pages/popup.coffee90
-rw-r--r--pages/popup.html24
4 files changed, 325 insertions, 114 deletions
diff --git a/pages/options.coffee b/pages/options.coffee
index d73d8f15..7f374f5d 100644
--- a/pages/options.coffee
+++ b/pages/options.coffee
@@ -1,36 +1,148 @@
-$ = (id) -> document.getElementById id
+$ = (id) -> document.getElementById id
bgSettings = chrome.extension.getBackgroundPage().Settings
-editableFields = [ "scrollStepSize", "excludedUrls", "linkHintCharacters", "linkHintNumbers",
- "userDefinedLinkHintCss", "keyMappings", "filterLinkHints", "previousPatterns",
- "nextPatterns", "hideHud", "regexFindMode", "searchUrl", "searchEngines"]
-
-canBeEmptyFields = ["excludedUrls", "keyMappings", "userDefinedLinkHintCss", "searchEngines"]
-
-document.addEventListener "DOMContentLoaded", ->
- populateOptions()
-
- for field in editableFields
- $(field).addEventListener "keyup", onOptionKeyup, false
- $(field).addEventListener "change", enableSaveButton, false
- $(field).addEventListener "change", onDataLoaded, false
-
- $("advancedOptionsLink").addEventListener "click", toggleAdvancedOptions, false
- $("showCommands").addEventListener "click", (->
- showHelpDialog chrome.extension.getBackgroundPage().helpDialogHtml(true, true, "Command Listing"), frameId
- ), false
- document.getElementById("restoreSettings").addEventListener "click", restoreToDefaults
- document.getElementById("saveOptions").addEventListener "click", saveOptions
+#
+# Class hierarchy for various types of option.
+class Option
+ # Base class for all option classes.
+ # Abstract. Option does not define @populateElement or @readValueFromElement.
+
+ # Static. Array of all options.
+ @all = []
+
+ constructor: (field,enableSaveButton) ->
+ @field = field
+ @element = $(@field)
+ @element.addEventListener "change", enableSaveButton
+ @fetch()
+ Option.all.push @
+
+ # Fetch a setting from localStorage, remember the @previous value and populate the DOM element.
+ # Return the fetched value.
+ fetch: ->
+ @populateElement @previous = bgSettings.get @field
+ @previous
+
+ # Write this option's new value back to localStorage, if necessary.
+ save: ->
+ value = @readValueFromElement()
+ if not @areEqual value, @previous
+ bgSettings.set @field, @previous = value
+ bgSettings.performPostUpdateHook @field, value
+
+ # Compare values; this is overridden by sub-classes.
+ areEqual: (a,b) -> a == b
+
+ restoreToDefault: ->
+ bgSettings.clear @field
+ @fetch()
+
+ # Abstract method; only implemented in sub-classes.
+ # Populate the option's DOM element (@element) with the setting's current value.
+ # populateElement: (value) -> DO_SOMETHING
+
+ # Abstract method; only implemented in sub-classes.
+ # Extract the setting's new value from the option's DOM element (@element).
+ # readValueFromElement: -> RETURN_SOMETHING
+
+class NumberOption extends Option
+ populateElement: (value) -> @element.value = value
+ readValueFromElement: -> parseFloat @element.value
+
+class TextOption extends Option
+ populateElement: (value) -> @element.value = value
+ readValueFromElement: -> @element.value.trim()
+
+class NonEmptyTextOption extends Option
+ populateElement: (value) -> @element.value = value
+ # If the new value is not empty, then return it. Otherwise, restore the default value.
+ readValueFromElement: -> if value = @element.value.trim() then value else @restoreToDefault()
+
+class CheckBoxOption extends Option
+ populateElement: (value) -> @element.checked = value
+ readValueFromElement: -> @element.checked
+
+class ExclusionRulesOption extends Option
+ constructor: (args...) ->
+ super(args...)
+ $("exclusionAddButton").addEventListener "click", (event) =>
+ @appendRule { pattern: "", passKeys: "" }
+ @maintainExclusionMargin()
+ # Focus the pattern element in the new rule.
+ @element.children[@element.children.length-1].children[0].children[0].focus()
+ # Scroll the new rule into view.
+ exclusionScrollBox = $("exclusionScrollBox")
+ exclusionScrollBox.scrollTop = exclusionScrollBox.scrollHeight
+
+ populateElement: (rules) ->
+ while @element.firstChild
+ @element.removeChild @element.firstChild
+ for rule in rules
+ @appendRule rule
+ @maintainExclusionMargin()
+
+ # Append a row for a new rule.
+ appendRule: (rule) ->
+ content = document.querySelector('#exclusionRuleTemplate').content
+ row = document.importNode content, true
+
+ for field in ["pattern", "passKeys"]
+ element = row.querySelector ".#{field}"
+ element.value = rule[field]
+ for event in [ "keyup", "change" ]
+ element.addEventListener event, enableSaveButton
+
+ remove = row.querySelector ".exclusionRemoveButton"
+ remove.addEventListener "click", (event) =>
+ row = event.target.parentNode.parentNode
+ row.parentNode.removeChild row
+ enableSaveButton()
+ @maintainExclusionMargin()
+
+ @element.appendChild row
+
+ readValueFromElement: ->
+ rules =
+ for element in @element.children
+ pattern = element.children[0].firstChild.value.trim()
+ passKeys = element.children[1].firstChild.value.trim()
+ { pattern: pattern, passKeys: passKeys }
+ rules.filter (rule) -> rule.pattern
+
+ areEqual: (a,b) ->
+ # Flatten each list of rules to a newline-separated string representation, and then use string equality.
+ # This is correct because patterns and passKeys cannot themselves contain newlines.
+ flatten = (rule) -> if rule and rule.pattern then rule.pattern + "\n" + rule.passKeys else ""
+ a.map(flatten).join("\n") == b.map(flatten).join("\n")
+
+ # Hack. There has to be a better way than...
+ # The y-axis scrollbar for "exclusionRules" is only displayed if it is needed. When visible, it appears on
+ # top of the enclosed content (partially obscuring it). Here, we adjust the margin of the "Remove" button to
+ # compensate.
+ maintainExclusionMargin: ->
+ scrollBox = $("exclusionScrollBox")
+ margin = if scrollBox.clientHeight < scrollBox.scrollHeight then "16px" else "0px"
+ for element in scrollBox.getElementsByClassName "exclusionRemoveButton"
+ element.style["margin-right"] = margin
+
+#
+# Operations for page elements.
+enableSaveButton = ->
+ $("saveOptions").removeAttribute "disabled"
-window.onbeforeunload = -> "You have unsaved changes to options." unless $("saveOptions").disabled
+saveOptions = ->
+ Option.all.map (option) -> option.save()
+ $("saveOptions").disabled = true
-onOptionKeyup = (event) ->
- if (event.target.getAttribute("type") isnt "checkbox" and
- event.target.getAttribute("savedValue") isnt event.target.value)
- enableSaveButton()
+restoreToDefaults = ->
+ return unless confirm "Are you sure you want to permanently return all of Vimium's settings to their defaults?"
+ Option.all.map (option) -> option.restoreToDefault()
+ maintainLinkHintsView()
+ $("saveOptions").disabled = true
-onDataLoaded = ->
+# Display either "linkHintNumbers" or "linkHintCharacters", depending upon "filterLinkHints".
+maintainLinkHintsView = ->
hide = (el) -> el.parentNode.parentNode.style.display = "none"
show = (el) -> el.parentNode.parentNode.style.display = "table-row"
if $("filterLinkHints").checked
@@ -40,64 +152,48 @@ onDataLoaded = ->
show $("linkHintCharacters")
hide $("linkHintNumbers")
-enableSaveButton = ->
- $("saveOptions").removeAttribute "disabled"
-
-# Saves options to localStorage.
-saveOptions = ->
-
- # If the value is unchanged from the default, delete the preference from localStorage; this gives us
- # the freedom to change the defaults in the future.
- for fieldName in editableFields
- field = $(fieldName)
- switch field.getAttribute("type")
- when "checkbox"
- fieldValue = field.checked
- when "number"
- fieldValue = parseFloat field.value
+toggleAdvancedOptions =
+ do (advancedMode=false) ->
+ (event) ->
+ if advancedMode
+ $("advancedOptions").style.display = "none"
+ $("advancedOptionsLink").innerHTML = "Show advanced options&hellip;"
else
- fieldValue = field.value.trim()
- field.value = fieldValue
-
- # If it's empty and not a field that we allow to be empty, restore to the default value
- if not fieldValue and canBeEmptyFields.indexOf(fieldName) is -1
- bgSettings.clear fieldName
- fieldValue = bgSettings.get(fieldName)
- else
- bgSettings.set fieldName, fieldValue
- $(fieldName).value = fieldValue
- $(fieldName).setAttribute "savedValue", fieldValue
- bgSettings.performPostUpdateHook fieldName, fieldValue
+ $("advancedOptions").style.display = "table-row-group"
+ $("advancedOptionsLink").innerHTML = "Hide advanced options"
+ advancedMode = !advancedMode
+ event.preventDefault()
- $("saveOptions").disabled = true
+activateHelpDialog = ->
+ showHelpDialog chrome.extension.getBackgroundPage().helpDialogHtml(true, true, "Command Listing"), frameId
-# Restores select box state to saved value from localStorage.
-populateOptions = ->
- for field in editableFields
- val = bgSettings.get(field) or ""
- setFieldValue $(field), val
- onDataLoaded()
+#
+# Initialization.
+document.addEventListener "DOMContentLoaded", ->
-restoreToDefaults = ->
- for field in editableFields
- val = bgSettings.defaults[field] or ""
- setFieldValue $(field), val
- onDataLoaded()
- enableSaveButton()
-
-setFieldValue = (field, value) ->
- unless field.getAttribute("type") is "checkbox"
- field.value = value
- field.setAttribute "savedValue", value
- else
- field.checked = value
+ # Populate options. The constructor adds each new object to "Option.all".
+ new type(name,enableSaveButton) for name, type of {
+ exclusionRules: ExclusionRulesOption
+ filterLinkHints: CheckBoxOption
+ hideHud: CheckBoxOption
+ keyMappings: TextOption
+ linkHintCharacters: NonEmptyTextOption
+ linkHintNumbers: NonEmptyTextOption
+ nextPatterns: NonEmptyTextOption
+ previousPatterns: NonEmptyTextOption
+ regexFindMode: CheckBoxOption
+ scrollStepSize: NumberOption
+ searchEngines: TextOption
+ searchUrl: NonEmptyTextOption
+ userDefinedLinkHintCss: TextOption
+ }
+
+ $("saveOptions").addEventListener "click", saveOptions
+ $("restoreSettings").addEventListener "click", restoreToDefaults
+ $("advancedOptionsLink").addEventListener "click", toggleAdvancedOptions
+ $("showCommands").addEventListener "click", activateHelpDialog
+ $("filterLinkHints").addEventListener "click", maintainLinkHintsView
+
+ maintainLinkHintsView()
+ window.onbeforeunload = -> "You have unsaved changes to options." unless $("saveOptions").disabled
-toggleAdvancedOptions = do (advancedMode=false) -> (event) ->
- if advancedMode
- $("advancedOptions").style.display = "none"
- $("advancedOptionsLink").innerHTML = "Show advanced options&hellip;"
- else
- $("advancedOptions").style.display = "table-row-group"
- $("advancedOptionsLink").innerHTML = "Hide advanced options"
- advancedMode = !advancedMode
- event.preventDefault()
diff --git a/pages/options.html b/pages/options.html
index b71625e8..fb904316 100644
--- a/pages/options.html
+++ b/pages/options.html
@@ -109,11 +109,6 @@
width: 40px;
margin-right: 3px;
}
- textarea#excludedUrls {
- margin-top: 5px;
- width: 100%;
- min-height: 100px;
- }
textarea#userDefinedLinkHintCss {
width: 100%;;
min-height: 100px;
@@ -178,6 +173,31 @@
padding: 15px 0;
border-top: 1px solid #eee;
}
+ /* Ids and classes for rendering exclusionRules */
+ #exclusionScrollBox {
+ overflow: scroll;
+ overflow-x: hidden;
+ overflow-y: auto;
+ height: 225px;
+ border: 1px solid #bfbfbf;
+ border-radius: 2px;
+ color: #444;
+ }
+ input.pattern, input.passKeys {
+ font-family: Consolas, "Liberation Mono", Courier, monospace;
+ font-size: 14px;
+ }
+ .pattern {
+ width: 250px;
+ }
+ .passKeys {
+ width: 120px;
+ }
+ #exclusionAddButton {
+ float: right;
+ margin-top: 5px;
+ margin-right: 0px;
+ }
</style>
<link rel="stylesheet" type="text/css" href="../content_scripts/vimium.css" />
@@ -196,16 +216,31 @@
</td>
</tr>
<tr>
- <td colspan="3">
- Excluded URLs<br/>
- <div class="help">
- <div class="example">
- e.g. http*://mail.google.com/*<br/>
- This will disable Vimium on Gmail.<br/><br/>
- Enter one URL per line.<br/>
- </div>
+ <td>Excluded URLs<br/>and keys</td>
+ <td>
+ <div class="help">
+ <div class="example">
+ <p>
+ The left column contains URL patterns. Vimium will be wholly or partially disabled for URLs matching these patterns. Patterns are Javascript regular expressions. Additionally, the character "*" matches any zero or more characters.
+ </p>
+ <p>
+ The right column contains keys which Vimium would would normally handle, but should instead be passed through to the underlying web page (for pages matching the corresponding pattern). If empty, then Vimium is wholly disabled.
+ </p>
</div>
- <textarea id="excludedUrls"></textarea>
+ </div>
+ <div>
+ <div id="exclusionScrollBox">
+ <table id="exclusionRules"></table>
+ <template id="exclusionRuleTemplate">
+ <tr>
+ <td><input/ type="text" class="pattern" placeholder="URL pattern"></td>
+ <td><input/ type="text" class="passKeys" placeholder="Exclude keys"></td>
+ <td><input/ type="button" class="exclusionRemoveButton" value="&#x2716;"></td>
+ </tr>
+ </template>
+ </div>
+ <button id="exclusionAddButton">Add Rule</button>
+ </div>
</td>
</tr>
<tbody id='advancedOptions'>
diff --git a/pages/popup.coffee b/pages/popup.coffee
index 6d7afafc..ecf683e5 100644
--- a/pages/popup.coffee
+++ b/pages/popup.coffee
@@ -1,16 +1,88 @@
+
+originalRule = undefined
+originalPattern = undefined
+originalPassKeys = undefined
+
onLoad = ->
document.getElementById("optionsLink").setAttribute "href", chrome.runtime.getURL("pages/options.html")
chrome.tabs.getSelected null, (tab) ->
- # The common use case is to disable Vimium at the domain level.
- # This regexp will match "http://www.example.com/" from "http://www.example.com/path/to/page.html".
- domain = tab.url.match(/[^\/]*\/\/[^\/]*\//) or tab.url
- document.getElementById("popupInput").value = domain + "*"
+ isEnabled = chrome.extension.getBackgroundPage().isEnabledForUrl(url: tab.url)
+ # Check if we have an existing exclusing rule for this page.
+ if isEnabled.rule
+ originalRule = isEnabled.rule
+ originalPattern = originalRule.pattern
+ originalPassKeys = originalRule.passKeys
+ else
+ # The common use case is to disable Vimium at the domain level.
+ # This regexp will match "http://www.example.com/" from "http://www.example.com/path/to/page.html".
+ domain = (tab.url.match(/[^\/]*\/\/[^\/]*\//) or tab.url) + "*"
+ originalRule = null
+ originalPattern = domain
+ originalPassKeys = ""
+ document.getElementById("popupPattern").value = originalPattern
+ document.getElementById("popupPassKeys").value = originalPassKeys
+ onChange()
+
+onChange = ->
+ # As the text in the popup's input elements is changed, update the the popup's buttons accordingly.
+ # Aditionally, enable and disable those buttons as appropriate.
+ pattern = document.getElementById("popupPattern").value.trim()
+ passKeys = document.getElementById("popupPassKeys").value.trim()
+ popupExclude = document.getElementById("popupExclude")
+
+ document.getElementById("popupRemove").disabled =
+ not (originalRule and pattern == originalPattern)
+
+ if originalRule and pattern == originalPattern and passKeys == originalPassKeys
+ popupExclude.disabled = true
+ popupExclude.value = "Update Rule"
+
+ else if originalRule and pattern == originalPattern
+ popupExclude.disabled = false
+ popupExclude.value = "Update Rule"
+
+ else if originalRule
+ popupExclude.disabled = false
+ popupExclude.value = "Add Rule"
+
+ else if pattern
+ popupExclude.disabled = false
+ popupExclude.value = "Add Rule"
-onExcludeUrl = (e) ->
- url = document.getElementById("popupInput").value
- chrome.extension.getBackgroundPage().addExcludedUrl url
- document.getElementById("excludeConfirm").setAttribute "style", "display: inline-block"
+ else
+ popupExclude.disabled = true
+ popupExclude.value = "Add Rule"
+
+showMessage = do ->
+ timer = null
+
+ hideConfirmationMessage = ->
+ document.getElementById("confirmationMessage").setAttribute "style", "display: none"
+ timer = null
+
+ (message) ->
+ document.getElementById("confirmationMessage").setAttribute "style", "display: inline-block"
+ document.getElementById("confirmationMessage").innerHTML = message
+ clearTimeout(timer) if timer
+ timer = setTimeout(hideConfirmationMessage,2000)
+
+addExclusionRule = ->
+ pattern = document.getElementById("popupPattern").value.trim()
+ passKeys = document.getElementById("popupPassKeys").value.trim()
+ chrome.extension.getBackgroundPage().addExclusionRule pattern, passKeys
+ showMessage("Updated.")
+ onLoad()
+
+removeExclusionRule = ->
+ pattern = document.getElementById("popupPattern").value.trim()
+ chrome.extension.getBackgroundPage().removeExclusionRule pattern
+ showMessage("Removed.")
+ onLoad()
document.addEventListener "DOMContentLoaded", ->
- document.getElementById("popupButton").addEventListener "click", onExcludeUrl, false
+ document.getElementById("popupExclude").addEventListener "click", addExclusionRule, false
+ document.getElementById("popupRemove").addEventListener "click", removeExclusionRule, false
+ for field in ["popupPattern", "popupPassKeys"]
+ for event in ["keyup", "change"]
+ document.getElementById(field).addEventListener event, onChange, false
onLoad()
diff --git a/pages/popup.html b/pages/popup.html
index 8ccf7126..86982eae 100644
--- a/pages/popup.html
+++ b/pages/popup.html
@@ -6,17 +6,22 @@
padding: 0px;
}
- #vimiumPopup { width: 300px; }
+ #vimiumPopup { width: 400px; }
#excludeControls {
padding: 10px;
}
- #popupInput {
- width: 160px;
+ #popupPattern, #popupPassKeys {
+ margin: 5px;
+ width: 330px;
+ /* Match the corresponding font and font size used on the options page. */
+ /* TODO (smblott): Match other styles from the options page. */
+ font-family: Consolas, "Liberation Mono", Courier, monospace;
+ font-size: 14px;
}
- #excludeConfirm {
+ #confirmationMessage {
display: inline-block;
width: 18px;
height: 13px;
@@ -24,7 +29,8 @@
display: none;
}
- #popupButton { margin-left: 10px; }
+ #popupRemove { margin: 5px; }
+ #popupExclude { margin: 5px; }
#popupMenu ul {
list-style: none;
@@ -52,9 +58,11 @@
<body>
<div id="vimiumPopup">
<div id="excludeControls">
- <input id="popupInput" type="text" />
- <input id="popupButton" type="button" value="Exclude URL" />
- <span id="excludeConfirm">Saved exclude pattern.</span>
+ <input id="popupPattern" placeholder="Pattern against which to match URLs..." type="text" /><br/>
+ <input id="popupPassKeys" placeholder="Only exclude these keys..." type="text" /><br/>
+ <input id="popupRemove" type="button" value="Remove Rule" />
+ <input id="popupExclude" type="button" value="Add or Update Rule" />
+ <span id="confirmationMessage">Text is added in popup.coffee.</span>
</div>
<div id="popupMenu">