mirror of
https://github.com/gryf/pentadactyl-pm.git
synced 2025-12-20 18:47:58 +01:00
1049 lines
39 KiB
JavaScript
1049 lines
39 KiB
JavaScript
/***** BEGIN LICENSE BLOCK ***** {{{
|
|
Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
|
|
|
The contents of this file are subject to the Mozilla Public License Version
|
|
1.1 (the "License"); you may not use this file except in compliance with
|
|
the License. You may obtain a copy of the License at
|
|
http://www.mozilla.org/MPL/
|
|
|
|
Software distributed under the License is distributed on an "AS IS" basis,
|
|
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
for the specific language governing rights and limitations under the
|
|
License.
|
|
|
|
Copyright (c) 2006-2009 by Martin Stubenschrott <stubenschrott@vimperator.org>
|
|
|
|
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 <tab> 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,
|
|
["<C-o>"], "Go to an older position in the jump list",
|
|
function (count) { history.stepTo(-Math.max(count, 1)); },
|
|
{ flags: Mappings.flags.COUNT });
|
|
|
|
mappings.add(myModes,
|
|
["<C-i>"], "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", "<A-Left>", "<M-Left>"], "Go back in the browser history",
|
|
function (count) { history.stepTo(-Math.max(count, 1)); },
|
|
{ flags: Mappings.flags.COUNT });
|
|
|
|
mappings.add(myModes,
|
|
["L", "<A-Right>", "<M-Right>"], "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 = Math.max(start, Math.min(end, current + steps));
|
|
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))
|
|
{
|
|
liberator.beep();
|
|
return;
|
|
}
|
|
|
|
quickmarks.add(arg, buffer.URL);
|
|
},
|
|
{ flags: Mappings.flags.ARGUMENT });
|
|
|
|
/////////////////////////////////////////////////////////////////////////////}}}
|
|
////////////////////// COMMANDS ////////////////////////////////////////////////
|
|
/////////////////////////////////////////////////////////////////////////////{{{
|
|
|
|
commands.add(["delqm[arks]"],
|
|
"Delete the specified QuickMarks",
|
|
function (args)
|
|
{
|
|
// TODO: finish arg parsing - we really need a proper way to do this. :)
|
|
if (!args.bang && !args.string)
|
|
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:
|