summaryrefslogtreecommitdiff
path: root/xulmus/content/bookmarks.js
diff options
context:
space:
mode:
Diffstat (limited to 'xulmus/content/bookmarks.js')
-rwxr-xr-xxulmus/content/bookmarks.js1146
1 files changed, 1146 insertions, 0 deletions
diff --git a/xulmus/content/bookmarks.js b/xulmus/content/bookmarks.js
new file mode 100755
index 00000000..1e0ca0a0
--- /dev/null
+++ b/xulmus/content/bookmarks.js
@@ -0,0 +1,1146 @@
+/***** BEGIN LICENSE BLOCK ***** {{{
+Version: MPL 1.1/GPL 2.0/LGPL 2.1
+
+The contents of this file are subject to the Mozilla Public License Version
+1.1 (the "License"); you may not use this file except in compliance with
+the License. You may obtain a copy of the License at
+http://www.mozilla.org/MPL/
+
+Software distributed under the License is distributed on an "AS IS" basis,
+WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+for the specific language governing rights and limitations under the
+License.
+
+Copyright (c) 2006-2009 by Martin Stubenschrott <stubenschrott@gmx.net>
+
+Alternatively, the contents of this file may be used under the terms of
+either the GNU General Public License Version 2 or later (the "GPL"), or
+the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+in which case the provisions of the GPL or the LGPL are applicable instead
+of those above. If you wish to allow use of your version of this file only
+under the terms of either the GPL or the LGPL, and not to allow others to
+use your version of this file under the terms of the MPL, indicate your
+decision by deleting the provisions above and replace them with the notice
+and other provisions required by the GPL or the LGPL. If you do not delete
+the provisions above, a recipient may use your version of this file under
+the terms of any one of the MPL, the GPL or the LGPL.
+}}} ***** END LICENSE BLOCK *****/
+Components.utils.import("resource://gre/modules/utils.js");
+const DEFAULT_FAVICON = "chrome://mozapps/skin/places/defaultFavicon.png";
+
+// Try to import older command line history, quick marks, etc.
+liberator.registerObserver("load", function () {
+ if (!options.getPref("extensions.xulmus.commandline_cmd_history"))
+ return;
+
+ let store = storage["history-command"];
+ let pref = options.getPref("extensions.xulmus.commandline_cmd_history");
+ for (let [k, v] in Iterator(pref.split("\n")))
+ store.push(v);
+
+ store = storage["quickmarks"];
+ pref = options.getPref("extensions.xulmus.quickmarks")
+ .split("\n");
+ while (pref.length > 0)
+ store.set(pref.shift(), pref.shift());
+
+ options.resetPref("extensions.xulmus.commandline_cmd_history");
+ options.resetPref("extensions.xulmus.commandline_search_history");
+ options.resetPref("extensions.xulmus.quickmarks");
+});
+
+// also includes methods for dealing with keywords and search engines
+function Bookmarks() //{{{
+{
+ ////////////////////////////////////////////////////////////////////////////////
+ ////////////////////// PRIVATE SECTION /////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////{{{
+
+ const historyService = PlacesUtils.history; //Cc["@mozilla.org/browser/global-history;1"].getService(Ci.nsIGlobalHistory);
+ const bookmarksService = PlacesUtils.bookmarks //Cc["@songbirdnest.com/servicepane/bookmarks;1"].getService(Ci.sbIBookmarks);
+ const taggingService = PlacesUtils.tagging //Cc["@mozilla.org/browser/tagging-service;1"].getService(Ci.nsITaggingService);
+ const faviconService = Cc["@mozilla.org/browser/favicon-service;1"].getService(Ci.nsIFaviconService);
+
+ // XXX for strange Firefox bug :(
+ // Error: [Exception... "Component returned failure code: 0x8000ffff (NS_ERROR_UNEXPECTED) [nsIObserverService.addObserver]"
+ // nsresult: "0x8000ffff (NS_ERROR_UNEXPECTED)"
+ // location: "JS frame :: file://~firefox/components/nsTaggingService.js :: anonymous :: line 89"
+ // data: no]
+ // Source file: file://~firefox/components/nsTaggingService.js
+ taggingService.getTagsForURI(window.makeURI("http://mysterious.bug"), {});
+
+ const Bookmark = new Struct("url", "title", "icon", "keyword", "tags", "id");
+ const Keyword = new Struct("keyword", "title", "icon", "url");
+ Bookmark.defaultValue("icon", function () getFavicon(this.url));
+ Bookmark.prototype.__defineGetter__("extra", function () [
+ ["keyword", this.keyword, "Keyword"],
+ ["tags", this.tags.join(", "), "Tag"]
+ ].filter(function (item) item[1]));
+
+ const storage = modules.storage;
+ function Cache(name, store, serial)
+ {
+ const rootFolders = [bookmarksService.toolbarFolder, bookmarksService.bookmarksMenuFolder, bookmarksService.unfiledBookmarksFolder];
+ const sleep = liberator.sleep;
+
+ let bookmarks = [];
+ let self = this;
+
+ this.__defineGetter__("name", function () name);
+ this.__defineGetter__("store", function () store);
+ this.__defineGetter__("bookmarks", function () this.load());
+
+ this.__defineGetter__("keywords",
+ function () [new Keyword(k.keyword, k.title, k.icon, k.url) for each (k in self.bookmarks) if (k.keyword)]);
+
+ this.__iterator__ = function () (val for each (val in self.bookmarks));
+
+ function loadBookmark(node)
+ {
+ let uri = util.newURI(node.uri);
+ let keyword = bookmarksService.getKeywordForBookmark(node.itemId);
+ let tags = taggingService.getTagsForURI(uri, {}) || [];
+ let bmark = new Bookmark(node.uri, node.title, node.icon && node.icon.spec, keyword, tags, node.itemId);
+
+ bookmarks.push(bmark);
+
+ return bmark;
+ }
+
+ function readBookmark(id)
+ {
+ return {
+ itemId: id,
+ uri: bookmarksService.getBookmarkURI(id).spec,
+ title: bookmarksService.getItemTitle(id)
+ };
+ }
+
+ function deleteBookmark(id)
+ {
+ let length = bookmarks.length;
+ bookmarks = bookmarks.filter(function (item) item.id != id);
+ return bookmarks.length < length;
+ }
+
+ function findRoot(id)
+ {
+ do
+ {
+ var root = id;
+ id = bookmarksService.getFolderIdForItem(id);
+ } while (id != bookmarksService.placesRoot && id != root);
+ return root;
+ }
+
+ let loading = false;
+ this.load = function load()
+ {
+ if (loading)
+ {
+ while (loading)
+ sleep(10);
+ return bookmarks;
+ }
+
+ // update our bookmark cache
+ bookmarks = [];
+ loading = true;
+
+ let folders = rootFolders.slice();
+ let query = historyService.getNewQuery();
+ let options = historyService.getNewQueryOptions();
+ while (folders.length > 0)
+ {
+ query.setFolders(folders, 1);
+ folders.shift();
+ let result = historyService.executeQuery(query, options);
+ let folder = result.root;
+ folder.containerOpen = true;
+
+ // iterate over the immediate children of this folder
+ for (let i = 0; i < folder.childCount; i++)
+ {
+ let node = folder.getChild(i);
+ if (node.type == node.RESULT_TYPE_FOLDER) // folder
+ folders.push(node.itemId);
+ else if (node.type == node.RESULT_TYPE_URI) // bookmark
+ loadBookmark(node);
+ }
+
+ // close a container after using it!
+ folder.containerOpen = false;
+ }
+ this.__defineGetter__("bookmarks", function () bookmarks);
+ loading = false;
+ return bookmarks;
+ };
+
+ var observer = {
+ onBeginUpdateBatch: function onBeginUpdateBatch() {},
+ onEndUpdateBatch: function onEndUpdateBatch() {},
+ onItemVisited: function onItemVisited() {},
+ onItemMoved: function onItemMoved() {},
+ onItemAdded: function onItemAdded(itemId, folder, index)
+ {
+ // liberator.dump("onItemAdded(" + itemId + ", " + folder + ", " + index + ")\n");
+ if (bookmarksService.getItemType(itemId) == bookmarksService.TYPE_BOOKMARK)
+ {
+ if (rootFolders.indexOf(findRoot(itemId)) >= 0)
+ {
+ let bmark = loadBookmark(readBookmark(itemId));
+ storage.fireEvent(name, "add", bmark);
+ }
+ }
+ },
+ onItemRemoved: function onItemRemoved(itemId, folder, index)
+ {
+ // liberator.dump("onItemRemoved(" + itemId + ", " + folder + ", " + index + ")\n");
+ if (deleteBookmark(itemId))
+ storage.fireEvent(name, "remove", itemId);
+ },
+ onItemChanged: function onItemChanged(itemId, property, isAnnotation, value)
+ {
+ if (isAnnotation)
+ return;
+ // liberator.dump("onItemChanged(" + itemId + ", " + property + ", " + value + ")\n");
+ let bookmark = bookmarks.filter(function (item) item.id == itemId)[0];
+ if (bookmark)
+ {
+ if (property == "tags")
+ value = taggingService.getTagsForURI(util.newURI(bookmark.url), {});
+ if (property in bookmark)
+ bookmark[property] = value;
+ storage.fireEvent(name, "change", itemId);
+ }
+ },
+ QueryInterface: function QueryInterface(iid)
+ {
+ if (iid.equals(Ci.nsINavBookmarkObserver) || iid.equals(Ci.nsISupports))
+ return this;
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+ };
+
+ bookmarksService.addObserver(observer, false);
+ }
+
+ function getFavicon(uri)
+ {
+ try
+ {
+ return faviconService.getFaviconImageForPage(util.newURI(uri)).spec;
+ }
+ catch (e)
+ {
+ return "";
+ }
+ }
+
+ let bookmarkObserver = function (key, event, arg)
+ {
+ if (event == "add")
+ autocommands.trigger("BookmarkAdd", arg);
+ statusline.updateUrl();
+ };
+
+ var cache = storage.newObject("bookmark-cache", Cache, false);
+ storage.addObserver("bookmark-cache", bookmarkObserver, window);
+
+ liberator.registerObserver("enter", function () {
+ if (options["preload"])
+ {
+ // Forces a load, if not already loaded but wait 10sec
+ // so most tabs should be restored and the CPU should be idle again usually
+ setTimeout(function () { liberator.callFunctionInThread(null, function () cache.bookmarks); }, 10000);
+ }
+ });
+
+ /////////////////////////////////////////////////////////////////////////////}}}
+ ////////////////////// OPTIONS /////////////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////{{{
+
+ options.add(["defsearch", "ds"],
+ "Set the default search engine",
+ "string", "google",
+ {
+ completer: function completer(context)
+ {
+ completion.search(context, true)
+ context.completions = [["", "Don't perform searches by default"]].concat(context.completions);
+ },
+ validator: Option.validateCompleter
+ });
+
+ options.add(["preload"],
+ "Speed up first time history/bookmark completion",
+ "boolean", true);
+
+ /////////////////////////////////////////////////////////////////////////////}}}
+ ////////////////////// MAPPINGS ////////////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////{{{
+
+ var myModes = config.browserModes;
+
+ mappings.add(myModes, ["a"],
+ "Open a prompt to bookmark the current URL",
+ function ()
+ {
+ let title = "";
+ if (buffer.title != buffer.URL)
+ title = " -title=\"" + buffer.title + "\"";
+ commandline.open(":", "bmark " + buffer.URL + title, modes.EX);
+ });
+
+ mappings.add(myModes, ["A"],
+ "Toggle bookmarked state of current URL",
+ function () { bookmarks.toggle(buffer.URL); });
+
+ /////////////////////////////////////////////////////////////////////////////}}}
+ ////////////////////// COMMANDS ////////////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////{{{
+
+ commands.add(["ju[mps]"],
+ "Show jumplist",
+ function ()
+ {
+ let sh = window.getWebNavigation().sessionHistory;
+
+ let entries = [sh.getEntryAtIndex(i, false) for (i in util.range(0, sh.count))];
+ let list = template.jumps(sh.index, entries);
+ commandline.echo(list, commandline.HL_NORMAL, commandline.FORCE_MULTILINE);
+ },
+ { argCount: "0" });
+
+ // TODO: Clean this up.
+ function tags(context, args)
+ {
+ let filter = context.filter;
+ let have = filter.split(",");
+
+ args.completeFilter = have.pop();
+
+ let prefix = filter.substr(0, filter.length - args.completeFilter.length);
+ let tags = util.Array.uniq(util.Array.flatten([b.tags for ([k, b] in Iterator(cache.bookmarks))]));
+
+ return [[prefix + tag, tag] for ([i, tag] in Iterator(tags)) if (have.indexOf(tag) < 0)];
+ }
+
+ function title(context, args)
+ {
+ if (!args.bang)
+ return [[content.document.title, "Current Page Title"]];
+ context.keys.text = "title";
+ context.keys.description = "url";
+ return bookmarks.get(args.join(" "), args["-tags"], null, { keyword: args["-keyword"], title: context.filter });
+ }
+
+ function keyword(context, args)
+ {
+ if (!args.bang)
+ return [];
+ context.keys.text = "keyword";
+ return bookmarks.get(args.join(" "), args["-tags"], null, { keyword: context.filter, title: args["-title"] });
+ }
+
+ commands.add(["bma[rk]"],
+ "Add a bookmark",
+ function (args)
+ {
+ let url = args.length == 0 ? buffer.URL : args[0];
+ let title = args["-title"] || (args.length == 0 ? buffer.title : null);
+ let keyword = args["-keyword"] || null;
+ let tags = args["-tags"] || [];
+
+ if (bookmarks.add(false, title, url, keyword, tags, args.bang))
+ {
+ let extra = (title == url) ? "" : " (" + title + ")";
+ liberator.echomsg("Added bookmark: " + url + extra, 1, commandline.FORCE_SINGLELINE);
+ }
+ else
+ liberator.echoerr("Exxx: Could not add bookmark `" + title + "'", commandline.FORCE_SINGLELINE);
+ },
+ {
+ argCount: "?",
+ bang: true,
+ completer: function (context, args)
+ {
+ if (!args.bang)
+ {
+ context.completions = [[content.document.documentURI, "Current Location"]];
+ return;
+ }
+ completion.bookmark(context, args["-tags"], { keyword: args["-keyword"], title: args["-title"] });
+ },
+ options: [[["-title", "-t"], commands.OPTION_STRING, null, title],
+ [["-tags", "-T"], commands.OPTION_LIST, null, tags],
+ [["-keyword", "-k"], commands.OPTION_STRING, function (arg) /\w/.test(arg)]]
+ });
+
+ commands.add(["bmarks"],
+ "List or open multiple bookmarks",
+ function (args)
+ {
+ bookmarks.list(args.join(" "), args["-tags"] || [], args.bang, args["-max"]);
+ },
+ {
+ bang: true,
+ completer: function completer(context, args)
+ {
+ context.quote = null;
+ context.filter = args.join(" ");
+ completion.bookmark(context, args["-tags"]);
+ },
+ options: [[["-tags", "-T"], commands.OPTION_LIST, null, tags],
+ [["-max", "-m"], commands.OPTION_INT]]
+ });
+
+ commands.add(["delbm[arks]"],
+ "Delete a bookmark",
+ function (args)
+ {
+ let url = args.string || buffer.URL;
+ let deletedCount = bookmarks.remove(url);
+
+ liberator.echomsg(deletedCount + " bookmark(s) with url `" + url + "' deleted", 1, commandline.FORCE_SINGLELINE);
+ },
+ {
+ argCount: "?",
+ completer: function completer(context) completion.bookmark(context),
+ literal: 0
+ });
+
+ /////////////////////////////////////////////////////////////////////////////}}}
+ ////////////////////// PUBLIC SECTION //////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////{{{
+
+ return {
+
+ get format() ({
+ anchored: false,
+ title: ["URL", "Info"],
+ keys: { text: "url", description: "title", icon: "icon", extra: "extra", tags: "tags" },
+ process: [template.icon, template.bookmarkDescription]
+ }),
+
+ // if "bypassCache" is true, it will force a reload of the bookmarks database
+ // on my PC, it takes about 1ms for each bookmark to load, so loading 1000 bookmarks
+ // takes about 1 sec
+ get: function get(filter, tags, maxItems, extra)
+ {
+ return completion.runCompleter("bookmark", filter, maxItems, tags, extra);
+ },
+
+ // if starOnly = true it is saved in the unfiledBookmarksFolder, otherwise in the bookmarksMenuFolder
+ add: function add(starOnly, title, url, keyword, tags, force)
+ {
+ try
+ {
+ let uri = util.createURI(url);
+ if (!force)
+ {
+ for (let bmark in cache)
+ {
+ if (bmark[0] == uri.spec)
+ {
+ var id = bmark[5];
+ if (title)
+ bookmarksService.setItemTitle(id, title);
+ break;
+ }
+ }
+ }
+ if (id == undefined)
+ id = bookmarksService.insertBookmark(
+ bookmarksService[starOnly ? "unfiledBookmarksFolder" : "bookmarksMenuFolder"],
+ uri, -1, title || url);
+ if (!id)
+ return false;
+
+ if (keyword)
+ bookmarksService.setKeywordForBookmark(id, keyword);
+ if (tags)
+ taggingService.tagURI(uri, tags);
+ }
+ catch (e)
+ {
+ liberator.log(e, 0);
+ return false;
+ }
+
+ return true;
+ },
+
+ toggle: function toggle(url)
+ {
+ if (!url)
+ return;
+
+ let count = this.remove(url);
+ if (count > 0)
+ {
+ commandline.echo("Removed bookmark: " + url, commandline.HL_NORMAL, commandline.FORCE_SINGLELINE);
+ }
+ else
+ {
+ let title = buffer.title || url;
+ let extra = "";
+ if (title != url)
+ extra = " (" + title + ")";
+ this.add(true, title, url);
+ commandline.echo("Added bookmark: " + url + extra, commandline.HL_NORMAL, commandline.FORCE_SINGLELINE);
+ }
+ },
+
+ isBookmarked: function isBookmarked(url)
+ {
+ try
+ {
+ let uri = util.newURI(url);
+ return (bookmarksService.getBookmarkedURIFor(uri) != null);
+ }
+ catch (e)
+ {
+ return false;
+ }
+ },
+
+ // returns number of deleted bookmarks
+ remove: function remove(url)
+ {
+ if (!url)
+ return 0;
+
+ let i = 0;
+ try
+ {
+ let uri = util.newURI(url);
+ var count = {};
+ let bmarks = bookmarksService.getBookmarkIdsForURI(uri, count);
+
+ for (; i < bmarks.length; i++)
+ bookmarksService.removeItem(bmarks[i]);
+ }
+ catch (e)
+ {
+ liberator.log(e, 0);
+ return i;
+ }
+
+ // update the display of our "bookmarked" symbol
+ statusline.updateUrl();
+
+ return count.value;
+ },
+
+ getFavicon: function (url) getFavicon(url),
+
+ // TODO: add filtering
+ // also ensures that each search engine has a Vimperator-friendly alias
+ getSearchEngines: function getSearchEngines()
+ {
+ let searchEngines = [];
+ let firefoxEngines = services.get("browserSearch").getVisibleEngines({});
+ for (let [,engine] in Iterator(firefoxEngines))
+ {
+ let alias = engine.alias;
+ if (!alias || !/^[a-z0-9_-]+$/.test(alias))
+ alias = engine.name.replace(/^\W*([a-zA-Z_-]+).*/, "$1").toLowerCase();
+ if (!alias)
+ alias = "search"; // for search engines which we can't find a suitable alias
+
+ // make sure we can use search engines which would have the same alias (add numbers at the end)
+ let newAlias = alias;
+ for (let j = 1; j <= 10; j++) // <=10 is intentional
+ {
+ if (!searchEngines.some(function (item) item[0] == newAlias))
+ break;
+
+ newAlias = alias + j;
+ }
+ // only write when it changed, writes are really slow
+ if (engine.alias != newAlias)
+ engine.alias = newAlias;
+
+ searchEngines.push([engine.alias, engine.description, engine.iconURI && engine.iconURI.spec]);
+ }
+
+ return searchEngines;
+ },
+
+ getSuggestions: function getSuggestions(engineName, query, callback)
+ {
+ const responseType = "application/x-suggestions+json";
+
+ let engine = services.get("browserSearch").getEngineByAlias(engineName);
+
+ if (engine && engine.supportsResponseType(responseType))
+ var queryURI = engine.getSubmission(query, responseType).uri.spec;
+ if (!queryURI)
+ return [];
+
+ function process(resp)
+ {
+ let results = [];
+ try
+ {
+ results = services.get("json").decode(resp.responseText)[1];
+ results = [[item, ""] for ([k, item] in Iterator(results)) if (typeof item == "string")];
+ }
+ catch (e) {}
+ if (!callback)
+ return results;
+ callback(results);
+ }
+
+ let resp = util.httpGet(queryURI, callback && process);
+
+ if (!callback)
+ return process(resp);
+ },
+
+ // TODO: add filtering
+ // format of returned array:
+ // [keyword, helptext, url]
+ getKeywords: function getKeywords()
+ {
+ return cache.keywords;
+ },
+
+ // full search string including engine name as first word in @param text
+ // if @param useDefSearch is true, it uses the default search engine
+ // @returns the url for the search string
+ // if the search also requires a postData, [url, postData] is returned
+ getSearchURL: function getSearchURL(text, useDefsearch)
+ {
+ let url = null;
+ let aPostDataRef = {};
+ let searchString = (useDefsearch ? options["defsearch"] + " " : "") + text;
+
+ // we need to make sure our custom alias have been set, even if the user
+ // did not :open <tab> once before
+ this.getSearchEngines();
+
+
+ function getShortcutOrURI(aURL, aPostDataRef)
+ {
+ var shortcutURL = null;
+ var keyword = aURL;
+ var param = "";
+ var searchService = Cc['@mozilla.org/browser/search-service;1'].getService(Ci.nsIBrowserSearchService);
+ var offset = aURL.indexOf(" ");
+ if (offset > 0)
+ {
+ keyword = aURL.substr(0, offset);
+ param = aURL.substr(offset + 1);
+ }
+ if (!aPostDataRef)
+ {
+ aPostDataRef = {};
+ }
+ var engine = searchService.getEngineByAlias(keyword);
+ if (engine)
+ {
+ var submission = engine.getSubmission(param, null);
+ aPostDataRef.value = submission.postData;
+ return submission.uri.spec;
+ }
+ [shortcutURL, aPostDataRef.value] = PlacesUtils.getURLAndPostDataForKeyword(keyword);
+ if (!shortcutURL)
+ {
+ return aURL;
+ }
+ var postData = "";
+ if (aPostDataRef.value)
+ {
+ postData = unescape(aPostDataRef.value);
+ }
+ if (/%s/i.test(shortcutURL) || /%s/i.test(postData))
+ {
+ var charset = "";
+ const re = /^(.*)\&mozcharset=([a-zA-Z][_\-a-zA-Z0-9]+)\s*$/;
+ var matches = shortcutURL.match(re);
+ if (matches)
+ {
+ [, shortcutURL, charset] = matches;
+ }
+ else
+ {
+ try
+ {
+ charset = PlacesUtils.history.getCharsetForURI(makeURI(shortcutURL));
+ } catch (e) { }
+ }
+ var encodedParam = "";
+ if (charset)
+ {
+ encodedParam = escape(convertFromUnicode(charset, param));
+ } else {
+ encodedParam = encodeURIComponent(param);
+ }
+ shortcutURL = shortcutURL.replace(/%s/g, encodedParam).replace(/%S/g, param);
+ if (/%s/i.test(postData))
+ {
+ aPostDataRef.value = getPostDataStream(postData, param, encodedParam, "application/x-www-form-urlencoded");
+ }
+ } else if (param) {
+ aPostDataRef.value = null;
+ return aURL;
+ }
+ return shortcutURL;
+ }
+ url = getShortcutOrURI(searchString, aPostDataRef);
+ if (url == searchString)
+ url = null;
+
+ if (aPostDataRef && aPostDataRef.value)
+ return [url, aPostDataRef.value];
+ else
+ return url; // can be null
+ },
+
+ // if openItems is true, open the matching bookmarks items in tabs rather than display
+ list: function list(filter, tags, openItems, maxItems)
+ {
+ // FIXME: returning here doesn't make sense
+ // Why the hell doesn't it make sense? --Kris
+ // Because it unconditionally bypasses the final error message
+ // block and does so only when listing items, not opening them. In
+ // short it breaks the :bmarks command which doesn't make much
+ // sense to me but I'm old-fashioned. --djk
+ if (!openItems)
+ return completion.listCompleter("bookmark", filter, maxItems, tags);
+ let items = completion.runCompleter("bookmark", filter, maxItems, tags);
+
+ if (items.length)
+ return liberator.open(items.map(function (i) i.url), liberator.NEW_TAB);
+
+ if (filter.length > 0 && tags.length > 0)
+ liberator.echoerr("E283: No bookmarks matching tags: \"" + tags + "\" and string: \"" + filter + "\"");
+ else if (filter.length > 0)
+ liberator.echoerr("E283: No bookmarks matching string: \"" + filter + "\"");
+ else if (tags.length > 0)
+ liberator.echoerr("E283: No bookmarks matching tags: \"" + tags + "\"");
+ else
+ liberator.echoerr("No bookmarks set");
+ }
+ };
+ //}}}
+}; //}}}
+
+function History() //{{{
+{
+ ////////////////////////////////////////////////////////////////////////////////
+ ////////////////////// PRIVATE SECTION /////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////{{{
+
+ const historyService = PlacesUtils.history;
+
+ /////////////////////////////////////////////////////////////////////////////}}}
+ ////////////////////// MAPPINGS ////////////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////{{{
+
+ var myModes = config.browserModes;
+
+ mappings.add(myModes,
+ ["<C-o>"], "Go to an older position in the jump list",
+ function (count) { history.stepTo(-(count > 1 ? count : 1)); },
+ { flags: Mappings.flags.COUNT });
+
+ mappings.add(myModes,
+ ["<C-i>"], "Go to a newer position in the jump list",
+ function (count) { history.stepTo(count > 1 ? count : 1); },
+ { flags: Mappings.flags.COUNT });
+
+ mappings.add(myModes,
+ ["H", "<A-Left>", "<M-Left>"], "Go back in the browser history",
+ function (count) { history.stepTo(-(count > 1 ? count : 1)); },
+ { flags: Mappings.flags.COUNT });
+
+ mappings.add(myModes,
+ ["L", "<A-Right>", "<M-Right>"], "Go forward in the browser history",
+ function (count) { history.stepTo(count > 1 ? count : 1); },
+ { flags: Mappings.flags.COUNT });
+
+ /////////////////////////////////////////////////////////////////////////////}}}
+ ////////////////////// COMMANDS ////////////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////{{{
+
+ commands.add(["ba[ck]"],
+ "Go back in the browser history",
+ function (args)
+ {
+ let url = args.literalArg;
+
+ if (args.bang)
+ {
+ history.goToStart();
+ }
+ else
+ {
+ if (url)
+ {
+ let sh = window.getWebNavigation().sessionHistory;
+ for (let i in util.range(sh.index, 0, -1))
+ {
+ if (sh.getEntryAtIndex(i, false).URI.spec == url)
+ {
+ window.getWebNavigation().gotoIndex(i);
+ return;
+ }
+ }
+ liberator.echoerr("Exxx: URL not found in history");
+ }
+ else
+ {
+ history.stepTo(-Math.max(args.count, 1));
+ }
+ }
+ },
+ {
+ argCount: "?",
+ bang: true,
+ completer: function completer(context)
+ {
+ let sh = window.getWebNavigation().sessionHistory;
+
+ context.anchored = false;
+ context.completions = [sh.getEntryAtIndex(i, false) for (i in util.range(sh.index, 0, -1))];
+ context.keys = { text: function (item) item.URI.spec, description: "title" };
+ },
+ count: true,
+ literal: 0
+ });
+
+ commands.add(["fo[rward]", "fw"],
+ "Go forward in the browser history",
+ function (args)
+ {
+ let url = args.literalArg;
+
+ if (args.bang)
+ {
+ history.goToEnd();
+ }
+ else
+ {
+ if (url)
+ {
+ let sh = window.getWebNavigation().sessionHistory;
+ for (let i in util.range(sh.index + 1, sh.count))
+ {
+ if (sh.getEntryAtIndex(i, false).URI.spec == url)
+ {
+ window.getWebNavigation().gotoIndex(i);
+ return;
+ }
+ }
+ liberator.echoerr("Exxx: URL not found in history");
+ }
+ else
+ {
+ history.stepTo(Math.max(args.count, 1));
+ }
+ }
+ },
+ {
+ argCount: "?",
+ bang: true,
+ completer: function completer(context)
+ {
+ let sh = window.getWebNavigation().sessionHistory;
+
+ context.anchored = false;
+ context.completions = [sh.getEntryAtIndex(i, false) for (i in util.range(sh.index + 1, sh.count))];
+ context.keys = { text: function (item) item.URI.spec, description: "title" };
+ },
+ count: true,
+ literal: 0
+ });
+
+ commands.add(["hist[ory]", "hs"],
+ "Show recently visited URLs",
+ function (args) { history.list(args.join(" "), args.bang, args["-max"] || 1000); },
+ {
+ bang: true,
+ completer: function (context) { context.quote = null; completion.history(context); },
+ // completer: function (filter) completion.history(filter)
+ options: [[["-max", "-m"], options.OPTION_INT]]
+ });
+
+ /////////////////////////////////////////////////////////////////////////////}}}
+ ////////////////////// PUBLIC SECTION //////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////{{{
+
+ return {
+
+ get format() bookmarks.format,
+
+ get service() historyService,
+
+ get: function get(filter, maxItems)
+ {
+ // no query parameters will get all history
+ let query = historyService.getNewQuery();
+ let options = historyService.getNewQueryOptions();
+
+ if (typeof filter == "string")
+ filter = { searchTerms: filter };
+ for (let [k, v] in Iterator(filter))
+ query[k] = v;
+ options.sortingMode = options.SORT_BY_DATE_DESCENDING;
+ options.resultType = options.RESULTS_AS_URI;
+ if (maxItems > 0)
+ options.maxResults = maxItems;
+
+ // execute the query
+ let root = historyService.executeQuery(query, options).root;
+ root.containerOpen = true;
+ let items = util.map(util.range(0, root.childCount), function (i) {
+ let node = root.getChild(i);
+ return {
+ url: node.uri,
+ title: node.title,
+ icon: node.icon ? node.icon.spec : DEFAULT_FAVICON
+ };
+ });
+ root.containerOpen = false; // close a container after using it!
+
+ return items;
+ },
+
+ // TODO: better names and move to buffer.?
+ stepTo: function stepTo(steps)
+ {
+ let index = window.getWebNavigation().sessionHistory.index + steps;
+ if (index >= 0 && index < window.getWebNavigation().sessionHistory.count)
+ window.getWebNavigation().gotoIndex(index);
+ else
+ liberator.beep(); // XXX: really wanted?
+ },
+
+ goToStart: function goToStart()
+ {
+ let index = window.getWebNavigation().sessionHistory.index;
+
+ if (index > 0)
+ window.getWebNavigation().gotoIndex(0);
+ else
+ liberator.beep(); // XXX: really wanted?
+
+ },
+
+ goToEnd: function goToEnd()
+ {
+ let sh = window.getWebNavigation().sessionHistory;
+ let max = sh.count - 1;
+
+ if (sh.index < max)
+ window.getWebNavigation().gotoIndex(max);
+ else
+ liberator.beep(); // XXX: really wanted?
+
+ },
+
+ // if openItems is true, open the matching history items in tabs rather than display
+ list: function list(filter, openItems, maxItems)
+ {
+ // FIXME: returning here doesn't make sense
+ // Why the hell doesn't it make sense? --Kris
+ // See comment at bookmarks.list --djk
+ if (!openItems)
+ return completion.listCompleter("history", filter, maxItems);
+ let items = completion.runCompleter("history", filter, maxItems);
+
+ if (items.length)
+ return liberator.open(items.map(function (i) i.url), liberator.NEW_TAB);
+
+ if (filter.length > 0)
+ liberator.echoerr("E283: No history matching \"" + filter + "\"");
+ else
+ liberator.echoerr("No history set");
+ }
+ };
+ //}}}
+}; //}}}
+
+function QuickMarks() //{{{
+{
+ ////////////////////////////////////////////////////////////////////////////////
+ ////////////////////// PRIVATE SECTION /////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////{{{
+
+ var qmarks = storage.newMap("quickmarks", true);
+
+ /////////////////////////////////////////////////////////////////////////////}}}
+ ////////////////////// MAPPINGS ////////////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////{{{
+
+ var myModes = config.browserModes;
+
+ mappings.add(myModes,
+ ["go"], "Jump to a QuickMark",
+ function (arg) { quickmarks.jumpTo(arg, liberator.CURRENT_TAB); },
+ { flags: Mappings.flags.ARGUMENT });
+
+ mappings.add(myModes,
+ ["gn"], "Jump to a QuickMark in a new tab",
+ function (arg)
+ {
+ quickmarks.jumpTo(arg,
+ /\bquickmark\b/.test(options["activate"]) ?
+ liberator.NEW_TAB : liberator.NEW_BACKGROUND_TAB);
+ },
+ { flags: Mappings.flags.ARGUMENT });
+
+ mappings.add(myModes,
+ ["M"], "Add new QuickMark for current URL",
+ function (arg)
+ {
+ if (/[^a-zA-Z0-9]/.test(arg))
+ {
+ liberator.beep();
+ return;
+ }
+
+ quickmarks.add(arg, buffer.URL);
+ },
+ { flags: Mappings.flags.ARGUMENT });
+
+ /////////////////////////////////////////////////////////////////////////////}}}
+ ////////////////////// COMMANDS ////////////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////{{{
+
+ commands.add(["delqm[arks]"],
+ "Delete the specified QuickMarks",
+ function (args)
+ {
+ // TODO: finish arg parsing - we really need a proper way to do this. :)
+ if (!args.bang && !args.string)
+ {
+ liberator.echoerr("E471: Argument required");
+ return;
+ }
+
+ if (args.bang && args.string)
+ {
+ liberator.echoerr("E474: Invalid argument");
+ return;
+ }
+
+ if (args.bang)
+ quickmarks.removeAll();
+ else
+ quickmarks.remove(args.string);
+ },
+ {
+ bang: true,
+ completer: function (context)
+ {
+ context.title = ["QuickMark", "URL"];
+ context.completions = qmarks;
+ }
+ });
+
+ commands.add(["qma[rk]"],
+ "Mark a URL with a letter for quick access",
+ function (args)
+ {
+ let matches = args.string.match(/^([a-zA-Z0-9])(?:\s+(.+))?$/);
+ if (!matches)
+ liberator.echoerr("E488: Trailing characters");
+ else if (!matches[2])
+ quickmarks.add(matches[1], buffer.URL);
+ else
+ quickmarks.add(matches[1], matches[2]);
+ },
+ { argCount: "+" });
+
+ commands.add(["qmarks"],
+ "Show all QuickMarks",
+ function (args)
+ {
+ args = args.string;
+
+ // ignore invalid qmark characters unless there are no valid qmark chars
+ if (args && !/[a-zA-Z0-9]/.test(args))
+ {
+ liberator.echoerr("E283: No QuickMarks matching \"" + args + "\"");
+ return;
+ }
+
+ let filter = args.replace(/[^a-zA-Z0-9]/g, "");
+ quickmarks.list(filter);
+ });
+
+ /////////////////////////////////////////////////////////////////////////////}}}
+ ////////////////////// PUBLIC SECTION //////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////{{{
+
+ return {
+
+ add: function add(qmark, location)
+ {
+ qmarks.set(qmark, location);
+ liberator.echomsg("Added Quick Mark '" + qmark + "': " + location, 1);
+ },
+
+ remove: function remove(filter)
+ {
+ let pattern = RegExp("[" + filter.replace(/\s+/g, "") + "]");
+
+ for (let [qmark,] in qmarks)
+ {
+ if (pattern.test(qmark))
+ qmarks.remove(qmark);
+ }
+ },
+
+ removeAll: function removeAll()
+ {
+ qmarks.clear();
+ },
+
+ jumpTo: function jumpTo(qmark, where)
+ {
+ let url = qmarks.get(qmark);
+
+ if (url)
+ liberator.open(url, where);
+ else
+ liberator.echoerr("E20: QuickMark not set");
+ },
+
+ list: function list(filter)
+ {
+ let marks = [k for ([k, v] in qmarks)];
+ let lowercaseMarks = marks.filter(function (x) /[a-z]/.test(x)).sort();
+ let uppercaseMarks = marks.filter(function (x) /[A-Z]/.test(x)).sort();
+ let numberMarks = marks.filter(function (x) /[0-9]/.test(x)).sort();
+
+ marks = Array.concat(lowercaseMarks, uppercaseMarks, numberMarks);
+
+ if (marks.length == 0)
+ {
+ liberator.echoerr("No QuickMarks set");
+ return;
+ }
+
+ if (filter.length > 0)
+ {
+ marks = marks.filter(function (qmark) filter.indexOf(qmark) >= 0);
+ if (marks.length == 0)
+ {
+ liberator.echoerr("E283: No QuickMarks matching \"" + filter + "\"");
+ return;
+ }
+ }
+
+ let items = [[mark, qmarks.get(mark)] for ([k, mark] in Iterator(marks))];
+ template.genericTable(items, { title: ["QuickMark", "URL"] });
+ }
+ };
+ //}}}
+}; //}}}
+
+// vim: set fdm=marker sw=4 ts=4 et: