From 60ae1deb3f732aba8d0578d8d90359d04d928ebf Mon Sep 17 00:00:00 2001 From: Doug Kearns Date: Sat, 6 Jun 2009 03:21:25 +1000 Subject: Move {xulmus,vimperator}/**/bookmarks.js to common/content. --- common/content/bookmarks.js | 1106 +++++++++++++++++++++++++++++++++++++++ vimperator/content/bookmarks.js | 1045 ------------------------------------ xulmus/content/bookmarks.js | 1097 -------------------------------------- xulmus/content/config.js | 2 + 4 files changed, 1108 insertions(+), 2142 deletions(-) create mode 100644 common/content/bookmarks.js delete mode 100644 vimperator/content/bookmarks.js delete mode 100644 xulmus/content/bookmarks.js diff --git a/common/content/bookmarks.js b/common/content/bookmarks.js new file mode 100644 index 00000000..c7ea97b0 --- /dev/null +++ b/common/content/bookmarks.js @@ -0,0 +1,1106 @@ +/***** 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 + +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 *****/ + +const DEFAULT_FAVICON = "chrome://mozapps/skin/places/defaultFavicon.png"; + +// Try to import older command line history, quick marks, etc. +liberator.registerObserver("load", function () { + let branch = "extensions." + config.name.toLowerCase(); + if (!options.getPref(branch + ".commandline_cmd_history")) + return; + + let store = storage["history-command"]; + let pref = options.getPref(branch + ".commandline_cmd_history"); + for (let [k, v] in Iterator(pref.split("\n"))) + store.push(v); + + store = storage["quickmarks"]; + pref = options.getPref(branch + ".quickmarks") + .split("\n"); + while (pref.length > 0) + store.set(pref.shift(), pref.shift()); + + options.resetPref(branch + ".commandline_cmd_history"); + options.resetPref(branch + ".commandline_search_history"); + options.resetPref(branch + ".quickmarks"); +}); + +// also includes methods for dealing with keywords and search engines +function Bookmarks() //{{{ +{ + //////////////////////////////////////////////////////////////////////////////// + ////////////////////// PRIVATE SECTION ///////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////{{{ + + const historyService = PlacesUtils.history; + const bookmarksService = PlacesUtils.bookmarks; + const taggingService = PlacesUtils.tagging; + 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; + } + + // since we don't use a threaded bookmark loading (by set preload) + // anymore, is this loading synchronization still needed? --mst + 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); + + /////////////////////////////////////////////////////////////////////////////}}} + ////////////////////// 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 + }); + + /////////////////////////////////////////////////////////////////////////////}}} + ////////////////////// 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 Liberator-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 postData = {}; + let searchString = (useDefsearch ? options["defsearch"] + " " : "") + text; + + // we need to make sure our custom alias have been set, even if the user + // did not :open once before + this.getSearchEngines(); + + // ripped from Firefox + if (!window.getShortcutOrURI) + window.getShortcutOrURI = function (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 = window.getShortcutOrURI(searchString, postData); + + if (url == searchString) + url = null; + + if (postData && postData.value) + return [url, postData.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, + [""], "Go to an older position in the jump list", + function (count) { history.stepTo(-Math.max(count, 1)); }, + { flags: Mappings.flags.COUNT }); + + mappings.add(myModes, + [""], "Go to a newer position in the jump list", + function (count) { history.stepTo(Math.max(count, 1)); }, + { flags: Mappings.flags.COUNT }); + + mappings.add(myModes, + ["H", "", ""], "Go back in the browser history", + function (count) { history.stepTo(-Math.max(count, 1)); }, + { flags: Mappings.flags.COUNT }); + + mappings.add(myModes, + ["L", "", ""], "Go forward in the browser history", + function (count) { history.stepTo(Math.max(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" }; + context.compare = CompletionContext.Sort.unsorted; + }, + 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" }; + context.compare = CompletionContext.Sort.unsorted; + }, + 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 + stepTo: function stepTo(steps) + { + let start = 0; + let end = window.getWebNavigation().sessionHistory.count - 1; + let current = window.getWebNavigation().sessionHistory.index; + + if (current == start && steps < 0 || current == end && steps > 0) + liberator.beep(); + else + { + let index = util.Math.constrain(current + steps, start, end); + window.getWebNavigation().gotoIndex(index); + } + }, + + goToStart: function goToStart() + { + let index = window.getWebNavigation().sessionHistory.index; + + if (index > 0) + window.getWebNavigation().gotoIndex(0); + else + liberator.beep(); + + }, + + goToEnd: function goToEnd() + { + let sh = window.getWebNavigation().sessionHistory; + let max = sh.count - 1; + + if (sh.index < max) + window.getWebNavigation().gotoIndex(max); + else + liberator.beep(); + + }, + + // 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)) + return void liberator.beep(); + + 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) + return void liberator.echoerr("E471: Argument required"); + + if (args.bang && args.string) + return void liberator.echoerr("E474: Invalid argument"); + + 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)) + return void liberator.echoerr("E283: No QuickMarks matching \"" + args + "\""); + + 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) + return void liberator.echoerr("No QuickMarks set"); + + if (filter.length > 0) + { + marks = marks.filter(function (qmark) filter.indexOf(qmark) >= 0); + if (marks.length == 0) + return void liberator.echoerr("E283: No QuickMarks matching \"" + filter + "\""); + } + + 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: diff --git a/vimperator/content/bookmarks.js b/vimperator/content/bookmarks.js deleted file mode 100644 index 02d2b8d1..00000000 --- a/vimperator/content/bookmarks.js +++ /dev/null @@ -1,1045 +0,0 @@ -/***** 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 - -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 *****/ - -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.vimperator.commandline_cmd_history")) - return; - - let store = storage["history-command"]; - let pref = options.getPref("extensions.vimperator.commandline_cmd_history"); - for (let [k, v] in Iterator(pref.split("\n"))) - store.push(v); - - store = storage["quickmarks"]; - pref = options.getPref("extensions.vimperator.quickmarks") - .split("\n"); - while (pref.length > 0) - store.set(pref.shift(), pref.shift()); - - options.resetPref("extensions.vimperator.commandline_cmd_history"); - options.resetPref("extensions.vimperator.commandline_search_history"); - options.resetPref("extensions.vimperator.quickmarks"); -}); - -// also includes methods for dealing with keywords and search engines -function Bookmarks() //{{{ -{ - //////////////////////////////////////////////////////////////////////////////// - ////////////////////// PRIVATE SECTION ///////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////{{{ - - const historyService = PlacesUtils.history; - const bookmarksService = PlacesUtils.bookmarks; - const taggingService = PlacesUtils.tagging; - 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; - } - - // since we don't use a threaded bookmark loading (by set preload) - // anymore, is this loading synchronization still needed? --mst - 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); - - /////////////////////////////////////////////////////////////////////////////}}} - ////////////////////// 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 - }); - - /////////////////////////////////////////////////////////////////////////////}}} - ////////////////////// 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 postData = {}; - let searchString = (useDefsearch ? options["defsearch"] + " " : "") + text; - - // we need to make sure our custom alias have been set, even if the user - // did not :open once before - this.getSearchEngines(); - - url = window.getShortcutOrURI(searchString, postData); - if (url == searchString) - url = null; - - if (postData && postData.value) - return [url, postData.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, - [""], "Go to an older position in the jump list", - function (count) { history.stepTo(-Math.max(count, 1)); }, - { flags: Mappings.flags.COUNT }); - - mappings.add(myModes, - [""], "Go to a newer position in the jump list", - function (count) { history.stepTo(Math.max(count, 1)); }, - { flags: Mappings.flags.COUNT }); - - mappings.add(myModes, - ["H", "", ""], "Go back in the browser history", - function (count) { history.stepTo(-Math.max(count, 1)); }, - { flags: Mappings.flags.COUNT }); - - mappings.add(myModes, - ["L", "", ""], "Go forward in the browser history", - function (count) { history.stepTo(Math.max(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" }; - context.compare = CompletionContext.Sort.unsorted; - }, - 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" }; - context.compare = CompletionContext.Sort.unsorted; - }, - 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 - stepTo: function stepTo(steps) - { - let start = 0; - let end = window.getWebNavigation().sessionHistory.count - 1; - let current = window.getWebNavigation().sessionHistory.index; - - if (current == start && steps < 0 || current == end && steps > 0) - liberator.beep(); - else - { - let index = util.Math.constrain(current + steps, start, end); - window.getWebNavigation().gotoIndex(index); - } - }, - - goToStart: function goToStart() - { - let index = window.getWebNavigation().sessionHistory.index; - - if (index > 0) - window.getWebNavigation().gotoIndex(0); - else - liberator.beep(); - - }, - - goToEnd: function goToEnd() - { - let sh = window.getWebNavigation().sessionHistory; - let max = sh.count - 1; - - if (sh.index < max) - window.getWebNavigation().gotoIndex(max); - else - liberator.beep(); - - }, - - // 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)) - return void liberator.beep(); - - 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) - return void liberator.echoerr("E471: Argument required"); - - if (args.bang && args.string) - return void liberator.echoerr("E474: Invalid argument"); - - 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)) - return void liberator.echoerr("E283: No QuickMarks matching \"" + args + "\""); - - 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) - return void liberator.echoerr("No QuickMarks set"); - - if (filter.length > 0) - { - marks = marks.filter(function (qmark) filter.indexOf(qmark) >= 0); - if (marks.length == 0) - return void liberator.echoerr("E283: No QuickMarks matching \"" + filter + "\""); - } - - 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: diff --git a/xulmus/content/bookmarks.js b/xulmus/content/bookmarks.js deleted file mode 100644 index 77e7eec5..00000000 --- a/xulmus/content/bookmarks.js +++ /dev/null @@ -1,1097 +0,0 @@ -/***** 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 - -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); - - /////////////////////////////////////////////////////////////////////////////}}} - ////////////////////// 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 - }); - - /////////////////////////////////////////////////////////////////////////////}}} - ////////////////////// 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 postData = {}; - let searchString = (useDefsearch ? options["defsearch"] + " " : "") + text; - - // we need to make sure our custom alias have been set, even if the user - // did not :open once before - this.getSearchEngines(); - - // ripped from Firefox - 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, postData); - - if (url == searchString) - url = null; - - if (postData && postData.value) - return [url, postData.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, - [""], "Go to an older position in the jump list", - function (count) { history.stepTo(-Math.max(count, 1)); }, - { flags: Mappings.flags.COUNT }); - - mappings.add(myModes, - [""], "Go to a newer position in the jump list", - function (count) { history.stepTo(Math.max(count, 1)); }, - { flags: Mappings.flags.COUNT }); - - mappings.add(myModes, - ["H", "", ""], "Go back in the browser history", - function (count) { history.stepTo(-Math.max(count, 1)); }, - { flags: Mappings.flags.COUNT }); - - mappings.add(myModes, - ["L", "", ""], "Go forward in the browser history", - function (count) { history.stepTo(Math.max(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)) - return void liberator.beep(); - - 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) - return void liberator.echoerr("E471: Argument required"); - - if (args.bang && args.string) - return void liberator.echoerr("E474: Invalid argument"); - - 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)) - return void liberator.echoerr("E283: No QuickMarks matching \"" + args + "\""); - - 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) - return void liberator.echoerr("No QuickMarks set"); - - if (filter.length > 0) - { - marks = marks.filter(function (qmark) filter.indexOf(qmark) >= 0); - if (marks.length == 0) - return void liberator.echoerr("E283: No QuickMarks matching \"" + filter + "\""); - } - - 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: diff --git a/xulmus/content/config.js b/xulmus/content/config.js index f87dbb42..1483f407 100644 --- a/xulmus/content/config.js +++ b/xulmus/content/config.js @@ -26,6 +26,8 @@ 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"); // XXX + const config = { //{{{ /*** required options, no checks done if they really exist, so be careful ***/ name: "Xulmus", -- cgit v1.2.3