diff --git a/common/content/bookmarks.js b/common/content/bookmarks.js index 73b7989a..55c683e5 100644 --- a/common/content/bookmarks.js +++ b/common/content/bookmarks.js @@ -104,7 +104,11 @@ var Bookmarks = Module("bookmarks", { * @param {Element} elem A form element for which to add a keyword. */ addSearchKeyword: function (elem) { - let [url, post, charset] = util.parseForm(elem); + if (elem instanceof HTMLFormElement || elem.form) + var [url, post, charset] = util.parseForm(elem); + else + var [url, post, charset] = [elem.href || elem.src, null, elem.ownerDocument.characterSet]; + let options = { "-title": "Search " + elem.ownerDocument.title }; if (post != null) options["-post"] = post; diff --git a/common/content/buffer.js b/common/content/buffer.js index 5d764ad5..60c46159 100644 --- a/common/content/buffer.js +++ b/common/content/buffer.js @@ -162,12 +162,38 @@ var Buffer = Module("buffer", { destroy: function () { }, - getDefaultNames: function getDefaultNames(doc) { - let ext = services.mime.getPrimaryExtension(doc.contentType, doc.location.href.replace(/.*\./, "")); - return [[doc.title, "Page Name"], [doc.location.pathname.replace(/.*\//, ""), "File Name"]] - .filter(function ([leaf, title]) leaf) - .map(function ([leaf, title]) [leaf.replace(util.OS.illegalCharacters, encodeURIComponent) - .replace(RegExp("(\\." + ext + ")?$"), "." + ext), title]); + getDefaultNames: function getDefaultNames(node) { + let url = node.href || node.src || node.documentURI; + let currExt = url.replace(/.*(?:\.([a-z0-9]+))?$/, "$1").toLowerCase(); + + if (isinstance(node, [Document, HTMLImageElement])) { + let type = node.contentType || node.QueryInterface(Ci.nsIImageLoadingContent) + .getRequest(0).mimeType; + + var ext = "." + services.mime.getPrimaryExtension(type, currExt); + } + else if (currExt) + ext = "." + currExt; + else + ext = ""; + let re = ext ? RegExp("(\\." + currExt + ")?$") : /$/; + util.dump(ext.quote(), + isinstance(node, [Document, HTMLImageElement]), + node.contentType); + + var names = []; + if (node.title) + names.push([node.title, "Page Name"]); + if (node.alt) + names.push([node.alt, "Alternate Text"]); + if (!isinstance(node, Document) && node.textContent) + names.push([node.textContent, "Link Text"]); + + names.push([decodeURIComponent(url.replace(/.*?([^\/]*)\/*$/, "$1")), "File Name"]); + + return names.filter(function ([leaf, title]) leaf) + .map(function ([leaf, title]) [leaf.replace(util.OS.illegalCharacters, encodeURIComponent) + .replace(re, ext), title]); }, _triggerLoadAutocmd: function _triggerLoadAutocmd(name, doc, uri) { @@ -740,15 +766,35 @@ var Buffer = Module("buffer", { * dialog. */ saveLink: function (elem, skipPrompt) { - let doc = elem.ownerDocument; - let url = window.makeURLAbsolute(elem.baseURI, elem.href); - let text = elem.textContent; + let doc = elem.ownerDocument; + let uri = util.newURI(elem.href || elem.src, null, util.newURI(elem.baseURI)); + let referrer = util.newURI(doc.documentURI, doc.characterSet); try { - window.urlSecurityCheck(url, doc.nodePrincipal); - // we always want to save that link relative to the current working directory - prefs.set("browser.download.lastDir", io.cwd); - window.saveURL(url, text, null, true, skipPrompt, makeURI(url, doc.characterSet)); + window.urlSecurityCheck(uri.spec, doc.nodePrincipal); + + commandline.input("Save link: ", function (path) { + let file = io.File(path); + if (file.exists() && file.isDirectory()) + file.append(buffer.getDefaultNames(elem)[0][0]); + + try { + if (!file.exists()) + file.create(File.NORMAL_FILE_TYPE, octal(644)); + } + catch (e) { + util.assert(false, "Invalid destination: " + e.name); + } + + var persist = services.Persist(); + persist.persistFlags = persist.PERSIST_FLAGS_FROM_CACHE + | persist.PERSIST_FLAGS_REPLACE_EXISTING_FILES; + persist.saveURI(uri, null, null, null, null, file); + }, { + autocomplete: true, + completer: function (context) completion.savePage(context, elem), + history: "file" + }); } catch (e) { dactyl.echoerr(e); @@ -1365,11 +1411,7 @@ var Buffer = Module("buffer", { if (/^>>/.test(context.filter)) context.advance(/^>>\s*/.exec(context.filter)[0].length); - context.fork("generated", context.filter.replace(/[^/]*$/, "").length, - this, function (context) { - context.completions = buffer.getDefaultNames(content.document); - }); - return context.fork("files", 0, completion, "file"); + return completion.savePage(context, content.document); }, literal: 0 }); @@ -1475,6 +1517,14 @@ var Buffer = Module("buffer", { }); }, vals); }; + + completion.savePage = function savePage(context, node) { + context.fork("generated", context.filter.replace(/[^/]*$/, "").length, + this, function (context) { + context.completions = buffer.getDefaultNames(node); + }); + return context.fork("files", 0, completion, "file"); + }; }, events: function () { events.addSessionListener(config.browser, "DOMContentLoaded", this.closure.onDOMContentLoaded, true); diff --git a/common/content/commandline.js b/common/content/commandline.js index d941bc1e..aa1b5c8c 100644 --- a/common/content/commandline.js +++ b/common/content/commandline.js @@ -461,14 +461,14 @@ var CommandLine = Module("commandline", { let callback = self._input.cancel; self._input = {}; if (callback) - callback.call(self, value != null ? value : commandline.command); + dactyl.trapErrors(callback, self, value != null ? value : commandline.command); } function closePrompt(value) { let callback = self._input.submit; self._input = {}; if (callback) - callback.call(self, value != null ? value : commandline.command); + dactyl.trapErrors(callback, self, value != null ? value : commandline.command); } }, @@ -540,7 +540,12 @@ var CommandLine = Module("commandline", { triggerCallback: function (type, mode) { if (this._callbacks[type] && this._callbacks[type][mode]) - this._callbacks[type][mode].apply(this, Array.slice(arguments, 2)); + try { + this._callbacks[type][mode].apply(this, Array.slice(arguments, 2)); + } + catch (e) { + dactyl.reportError(e, true); + } }, runSilently: function (func, self) { @@ -588,6 +593,7 @@ var CommandLine = Module("commandline", { this.widgets.message = null; modes.push(modes.COMMAND_LINE, this.currentExtendedMode, { + autocomplete: cmd.length, onEvent: this.closure.onEvent, history: (extendedMode || {}).params.history, leave: function (params) { @@ -605,18 +611,19 @@ var CommandLine = Module("commandline", { this.widgets.command = cmd || ""; this.enter(); - - // open the completion list automatically if wanted - if (cmd.length) { - commandline.triggerCallback("change", this.currentExtendedMode, cmd); - this._autocompleteTimer.flush(); - } }, enter: function enter() { - if (modes.getStack(0).params.history) - this._history = CommandLine.History(this.widgets.active.command.inputField, modes.getStack(0).params.history); + let params = modes.getStack(0).params; + + if (params.history) + this._history = CommandLine.History(this.widgets.active.command.inputField, params.history); this._completions = CommandLine.Completions(this.widgets.active.command.inputField); + + if (params.autocomplete) { + commandline.triggerCallback("change", this.currentExtendedMode, commandline.command); + this._autocompleteTimer.flush(); + } }, /** diff --git a/common/content/hints.js b/common/content/hints.js index 7841ad3b..a6c32e9a 100644 --- a/common/content/hints.js +++ b/common/content/hints.js @@ -50,8 +50,7 @@ var Hints = Module("hints", { this._hintModes = {}; this.addMode(";", "Focus hint", buffer.closure.focusElement); this.addMode("?", "Show information for hint", function (elem) buffer.showElementInfo(elem)); - this.addMode("s", "Save hint", function (elem) buffer.saveLink(elem, true)); - this.addMode("a", "Save hint with prompt", function (elem) buffer.saveLink(elem, false)); + this.addMode("s", "Save hint", function (elem) buffer.saveLink(elem, false)); this.addMode("f", "Focus frame", function (elem) dactyl.focus(elem.ownerDocument.defaultView)); this.addMode("F", "Focus frame or pseudo-frame", buffer.closure.focusElement, null, isScrollable); this.addMode("o", "Follow hint", function (elem) buffer.followLink(elem, dactyl.CURRENT_TAB)); @@ -61,6 +60,7 @@ var Hints = Module("hints", { this.addMode("O", "Generate an ‘:open URL’ prompt", function (elem, loc) commandline.open(":", "open " + loc, modes.EX)); this.addMode("T", "Generate a ‘:tabopen URL’ prompt", function (elem, loc) commandline.open(":", "tabopen " + loc, modes.EX)); this.addMode("W", "Generate a ‘:winopen URL’ prompt", function (elem, loc) commandline.open(":", "winopen " + loc, modes.EX)); + this.addMode("a", "Add a bookmark", function (elem) bookmarks.addSearchKeyword(elem)); this.addMode("S", "Add a search keyword", function (elem) bookmarks.addSearchKeyword(elem)); this.addMode("v", "View hint source", function (elem, loc) buffer.viewSource(loc, false)); this.addMode("V", "View hint source in external editor", function (elem, loc) buffer.viewSource(loc, true)); @@ -1157,7 +1157,7 @@ var Hints = Module("hints", { options.add(["extendedhinttags", "eht"], "XPath strings of hintable elements for extended hint modes", "regexpmap", "[iI]:" + xpath(["img"]) + - ",[OTivVWy]:" + xpath(["{a,area}[@href]", "{img,iframe}[@src]"]) + + ",[asOTivVWy]:" + xpath(["{a,area}[@href]", "{img,iframe}[@src]"]) + ",[F]:" + xpath(["body", "code", "div", "html", "p", "pre", "span"]) + ",[S]:" + xpath(["input[not(@type='hidden')]", "textarea", "button", "select"]), { validator: Option.validateXPath }); diff --git a/common/locale/en-US/hints.xml b/common/locale/en-US/hints.xml index 02cb3b7c..baf0196c 100644 --- a/common/locale/en-US/hints.xml +++ b/common/locale/en-US/hints.xml @@ -96,7 +96,6 @@
  • ; to focus a link
  • ? to show information about the element (incomplete)
  • s to save its destination
  • -
  • a to save its destination (prompting for save location)
  • f to focus a frame
  • F to focus a frame or pseudo-frame
  • o to open its location in the current tab
  • @@ -106,6 +105,7 @@
  • O to generate an :open prompt with hint’s URL
  • T to generate a :tabopen prompt with hint’s URL (like ;O)
  • W to generate a :winopen prompt with hint’s URL (like ;T)
  • +
  • a to add a bookmark
  • S to add a search keyword for the hint’s form
  • v to view its destination source
  • V to view its destination source in the external editor
  • diff --git a/common/modules/util.jsm b/common/modules/util.jsm index c17ee4c6..92ac06c2 100644 --- a/common/modules/util.jsm +++ b/common/modules/util.jsm @@ -776,7 +776,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), /** @property {boolean} True if the OS is some other *nix variant. */ get isUnix() !this.isWindows && !this.isMacOSX, /** @property {RegExp} A RegExp which matches illegal characters in path components. */ - get illegalCharacters() this.isWindows ? /[<>:"/\\|?*\x00-\x1f]/ : /\// + get illegalCharacters() this.isWindows ? /[<>:"/\\|?*\x00-\x1f]/g : /\//g }), /** diff --git a/pentadactyl/NEWS b/pentadactyl/NEWS index d07396d5..4fecca2d 100644 --- a/pentadactyl/NEWS +++ b/pentadactyl/NEWS @@ -42,7 +42,9 @@ - Added g; continued extended hint mode, which allows selecting multiple hints. Removed ;F. - Hints are now updated after scrolling and window resizing. - - Added ;S mode for creating search keywords. + - ;s now prompts for a filename on the command-line rather + than in a dialog. + - Added ;a and ;S modes for creating bookmarks and search keywords. - Added 'hintkeys' option. - Added "transliterated" option to 'hintmatching'. * JavaScript completion improvements, including: