diff --git a/content/addressbook.js b/content/addressbook.js
index 00300d87..96f3532d 100644
--- a/content/addressbook.js
+++ b/content/addressbook.js
@@ -206,9 +206,7 @@ liberator.Addressbook = function () //{{{
"
| Name | Address |
";
for (var i = 0; i < addresses.length; i++)
{
- var displayName = liberator.util.escapeHTML(addresses[i][0]);
- if (displayName.length > 50)
- displayName = displayName.substr(0, 47) + "...";
+ var displayName = liberator.util.escapeHTML(liberator.util.clip(addresses[i][0], 50));
var mailAddr = liberator.util.escapeHTML(addresses[i][1]);
list += "| " + displayName + " | " + mailAddr + " |
";
}
diff --git a/content/bookmarks.js b/content/bookmarks.js
index 65fd7883..926f0503 100644
--- a/content/bookmarks.js
+++ b/content/bookmarks.js
@@ -33,91 +33,155 @@ liberator.Bookmarks = function () //{{{
////////////////////// PRIVATE SECTION /////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{
- const historyService = Components.classes["@mozilla.org/browser/nav-history-service;1"]
- .getService(Components.interfaces.nsINavHistoryService);
- const bookmarksService = Components.classes["@mozilla.org/browser/nav-bookmarks-service;1"]
- .getService(Components.interfaces.nsINavBookmarksService);
- const taggingService = Components.classes["@mozilla.org/browser/tagging-service;1"]
- .getService(Components.interfaces.nsITaggingService);
+ const historyService = PlacesUtils.history;
+ const bookmarksService = PlacesUtils.bookmarks;
+ const taggingService = PlacesUtils.tagging;
const searchService = Components.classes["@mozilla.org/browser/search-service;1"]
.getService(Components.interfaces.nsIBrowserSearchService);
const ioService = Components.classes["@mozilla.org/network/io-service;1"]
.getService(Components.interfaces.nsIIOService);
- var bookmarks = null;
- var keywords = null;
-
- if (liberator.options["preload"])
- setTimeout(function () { load(); }, 100);
-
- function load()
+ function Cache(name, store, serial)
{
- // update our bookmark cache
- bookmarks = [];
- keywords = [];
+ const properties = { uri: 0, title: 1, keyword: 2, tags: 3, id: 4 };
+ const rootFolders = [bookmarksService.toolbarFolder, bookmarksService.bookmarksMenuFolder, bookmarksService.unfiledBookmarksFolder];
- var folders = [bookmarksService.toolbarFolder, bookmarksService.bookmarksMenuFolder, bookmarksService.unfiledBookmarksFolder];
- var query = historyService.getNewQuery();
- var options = historyService.getNewQueryOptions();
- while (folders.length > 0)
+ var bookmarks = [];
+ var self = this;
+
+ this.__defineGetter__("name", function () key);
+ this.__defineGetter__("store", function () store);
+ this.__defineGetter__("bookmarks", function () { this.load(); return bookmarks });
+
+ this.__defineGetter__("keywords",
+ function () [[k[2], k[1], k[0]] for each (k in self.bookmarks) if (k[2])]);
+
+ this.__iterator__ = (val for each (val in self.bookmarks));
+
+ function loadBookmark(node)
{
- //comment out the next line for now; the bug hasn't been fixed; final version should include the next line
- //options.setGroupingMode(options.GROUP_BY_FOLDER);
- query.setFolders(folders, 1);
- var result = historyService.executeQuery(query, options);
- //result.sortingMode = options.SORT_BY_DATE_DESCENDING;
- result.sortingMode = options.SORT_BY_VISITCOUNT_DESCENDING;
- var rootNode = result.root;
- rootNode.containerOpen = true;
+ var keyword = bookmarksService.getKeywordForBookmark(node.itemId);
+ var tags = taggingService.getTagsForURI(ioService.newURI(node.uri, null, null), {});
+ bookmarks.push([node.uri, node.title, keyword, tags, node.itemId]);
+ }
- folders.shift();
- // iterate over the immediate children of this folder
- for (var i = 0; i < rootNode.childCount; i++)
- {
- var node = rootNode.getChild(i);
- if (node.type == node.RESULT_TYPE_FOLDER) // folder
- folders.push(node.itemId);
- else if (node.type == node.RESULT_TYPE_URI) // bookmark
- {
- var kw = bookmarksService.getKeywordForBookmark(node.itemId);
- if (kw)
- keywords.push([kw, node.title, node.uri]);
-
- var count = {};
- var tags = taggingService.getTagsForURI(ioService.newURI(node.uri, null, null), count);
- bookmarks.push([node.uri, node.title, kw, tags]);
- }
+ function readBookmark(id)
+ {
+ return {
+ itemId: id,
+ uri: bookmarksService.getBookmarkURI(id).spec,
+ title: bookmarksService.getItemTitle(id),
}
-
- // close a container after using it!
- rootNode.containerOpen = false;
}
+
+ function deleteBookmark(id)
+ {
+ var length = bookmarks.length;
+ bookmarks = bookmarks.filter(function (item) item[properties.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;
+ }
+
+ this.load = function load()
+ {
+ liberator.dump("cache.load()\n");
+ // update our bookmark cache
+ bookmarks = [];
+ this.__defineGetter__("bookmarks", function () bookmarks);
+
+ var folders = rootFolders.concat([]);
+ var query = historyService.getNewQuery();
+ var options = historyService.getNewQueryOptions();
+ while (folders.length > 0)
+ {
+ //comment out the next line for now; the bug hasn't been fixed; final version should include the next line
+ //options.setGroupingMode(options.GROUP_BY_FOLDER);
+ query.setFolders(folders, 1);
+ folders.shift();
+ var result = historyService.executeQuery(query, options);
+ result.sortingMode = options.SORT_BY_VISITCOUNT_DESCENDING; /* This is silly. Results are still sorted by folder first. --Kris */
+ var rootNode = result.root;
+ rootNode.containerOpen = true;
+
+ // iterate over the immediate children of this folder
+ for (var i = 0; i < rootNode.childCount; i++)
+ {
+ var node = rootNode.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!
+ rootNode.containerOpen = false;
+ }
+ };
+
+ var observer = {
+ onBeginUpdateBatch: function () {},
+ onEndUpdateBatch: function () {},
+ onItemVisited: function () {},
+ onItemMoved: function () {},
+ onItemAdded: function (itemId, folder, index)
+ {
+ // liberator.dump("onItemAdded(" + itemId + ", " + folder + ", " + index + ")\n");
+ if (bookmarksService.getItemType(itemId) == bookmarksService.TYPE_BOOKMARK)
+ {
+ if (rootFolders.indexOf(findRoot(itemId)) >= 0)
+ {
+ loadBookmark(readBookmark(itemId));
+ liberator.storage.fireEvent(name, "add", itemId);
+ }
+ }
+ },
+ onItemRemoved: function (itemId, folder, index)
+ {
+ // liberator.dump("onItemRemoved(" + itemId + ", " + folder + ", " + index + ")\n");
+ if (deleteBookmark(itemId))
+ liberator.storage.fireEvent(name, "remove", itemId);
+ },
+ onItemChanged: function (itemId, property, isAnnotation, value)
+ {
+ if(isAnnotation)
+ return;
+ // liberator.dump("onItemChanged(" + itemId + ", " + property + ", " + value + ")\n");
+ var bookmark = bookmarks.filter(function (item) item[properties.id] == itemId)[0];
+ if(bookmark)
+ {
+ if(property == "tags")
+ value = taggingService.getTagsForURI(ioService.newURI(bookmark[properties.uri], null, null), {});
+ if(property in properties)
+ bookmark[properties[property]] = value;
+ liberator.storage.fireEvent(name, "change", itemId);
+ }
+ },
+ QueryInterface: function (iid) {
+ if (iid.equals(Components.interfaces.nsINavBookmarkObserver) || iid.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ }
+ };
+
+ bookmarksService.addObserver(observer, false);
}
- function flush()
+ var cache = liberator.storage.newObject("bookmark-cache", Cache, false);
+ liberator.storage.addObserver("bookmark-cache", function (key, event, arg)
{
- bookmarks = null;
- if (liberator.options["preload"])
- load();
- }
-
- var observer = {
- onBeginUpdateBatch: function () {},
- onEndUpdateBatch: function () {},
- onItemVisited: function () {},
- /* FIXME: Should probably just update the given bookmark. */
- onItemMoved: flush,
- onItemAdded: flush,
- onItemRemoved: flush,
- onItemChanged: flush,
- QueryInterface: function (iid) {
- if (iid.equals(Components.interfaces.nsINavBookmarkObserver) || iid.equals(Components.interfaces.nsISupports))
- return this;
- throw Components.results.NS_ERROR_NO_INTERFACE;
- }
- };
-
- bookmarksService.addObserver(observer, false);
+ if(event == "add")
+ liberator.autocommands.trigger("BookmarkAdd", "");
+ liberator.statusline.updateUrl();
+ });
/////////////////////////////////////////////////////////////////////////////}}}
////////////////////// OPTIONS /////////////////////////////////////////////////
@@ -256,38 +320,23 @@ liberator.Bookmarks = function () //{{{
// takes about 1 sec
get: function (filter, tags, bypassCache)
{
- if (!bookmarks || bypassCache)
- load();
-
- return liberator.completion.filterURLArray(bookmarks, filter, tags);
+ return liberator.completion.filterURLArray(cache.bookmarks, filter, tags);
},
// if starOnly = true it is saved in the unfiledBookmarksFolder, otherwise in the bookmarksMenuFolder
add: function (starOnly, title, url, keyword, tags)
{
- if (!bookmarks)
- load();
-
- // if no protocol specified, default to http://, isn't there a better way?
- if (!/^[\w-]+:/.test(url))
- url = "http://" + url;
-
try
{
- var uri = ioService.newURI(url, null, null);
+ var uri = PlacesUIUtils.createFixedURI(url);
var id = bookmarksService.insertBookmark(
- starOnly ? bookmarksService.unfiledBookmarksFolder : bookmarksService.bookmarksMenuFolder,
+ bookmarksService[starOnly ? "unfiledBookmarksFolder" : "bookmarksMenuFolder"],
uri, -1, title);
-
if (!id)
return false;
if (keyword)
- {
bookmarksService.setKeywordForBookmark(id, keyword);
- keywords.unshift([keyword, title, url]);
- }
-
if (tags)
taggingService.tagURI(uri, tags);
}
@@ -297,11 +346,6 @@ liberator.Bookmarks = function () //{{{
return false;
}
- // update the display of our "bookmarked" symbol
- liberator.statusline.updateUrl();
-
- liberator.autocommands.trigger("BookmarkAdd", "");
-
return true;
},
@@ -331,15 +375,12 @@ liberator.Bookmarks = function () //{{{
try
{
var uri = ioService.newURI(url, null, null);
- var count = {};
- bookmarksService.getBookmarkIdsForURI(uri, count);
+ return (bookmarksService.getBookmarkedURIFor(uri) != null)
}
catch (e)
{
return false;
}
-
- return count.value > 0;
},
// returns number of deleted bookmarks
@@ -408,10 +449,7 @@ liberator.Bookmarks = function () //{{{
// [keyword, helptext, url]
getKeywords: function ()
{
- if (!keywords)
- load();
-
- return keywords;
+ return cache.keywords;
},
// full search string including engine name as first word in @param text
@@ -457,16 +495,14 @@ liberator.Bookmarks = function () //{{{
}
if (openItems)
- return liberator.open([i[0] for (i in items)], liberator.NEW_TAB);
+ return liberator.open([i[0] for each (i in items)], liberator.NEW_TAB);
var title, url, tags, keyword, extra;
var list = ":" + liberator.util.escapeHTML(liberator.commandline.getCommand()) + "
" +
"| title | URL |
";
for (var i = 0; i < items.length; i++)
{
- title = liberator.util.escapeHTML(items[i][1]);
- if (title.length > 50)
- title = title.substr(0, 47) + "...";
+ title = liberator.util.escapeHTML(liberator.util.clip(items[i][1], 50));
url = liberator.util.escapeHTML(items[i][0]);
keyword = items[i][2];
tags = items[i][3].join(", ");
@@ -762,7 +798,7 @@ liberator.History = function () //{{{
if (openItems)
{
- return liberator.open([i[0] for (i in items)], liberator.NEW_TAB);
+ return liberator.open([i[0] for each (i in items)], liberator.NEW_TAB);
}
else
{
@@ -770,9 +806,7 @@ liberator.History = function () //{{{
"| title | URL |
";
for (var i = 0; i < items.length; i++)
{
- var title = liberator.util.escapeHTML(items[i][1]);
- if (title.length > 50)
- title = title.substr(0, 47) + "...";
+ var title = liberator.util.escapeHTML(liberator.util.clip(items[i][1], 50));
var url = liberator.util.escapeHTML(items[i][0]);
list += "| " + title + " | " + url + " |
";
}
@@ -922,21 +956,13 @@ liberator.QuickMarks = function () //{{{
list: function (filter)
{
- var lowercaseMarks = [];
- var uppercaseMarks = [];
- var numberMarks = [];
+ var marks = [key for ([key,val] in qmarks)];
+ // This was a lot nicer without the lambda...
+ var lowercaseMarks = marks.filter(function (x) /[a-z]/.test(x)).sort();
+ var uppercaseMarks = marks.filter(function (x) /[A-Z]/.test(x)).sort();
+ var numberMarks = marks.filter(function (x) /[0-9]/.test(x)).sort();
- for (var [qmark,] in qmarks)
- {
- if (/[a-z]/.test(qmark))
- lowercaseMarks.push(qmark);
- else if (/[A-Z]/.test(qmark))
- uppercaseMarks.push(qmark);
- else
- numberMarks.push(qmark);
- }
-
- var marks = lowercaseMarks.sort().concat(uppercaseMarks.sort()).concat(numberMarks.sort());
+ marks = Array.concat(lowercaseMarks, uppercaseMarks, numberMarks);
if (marks.length == 0)
{
@@ -946,11 +972,7 @@ liberator.QuickMarks = function () //{{{
if (filter.length > 0)
{
- marks = marks.filter(function (qmark) {
- if (filter.indexOf(qmark) > -1)
- return qmark;
- });
-
+ marks = marks.filter(function (qmark) filter.indexOf(qmark) >= 0)
if (marks.length == 0)
{
liberator.echoerr("E283: No QuickMarks matching \"" + filter + "\"");
diff --git a/content/buffer.js b/content/buffer.js
index ea06065f..96a4d3a9 100644
--- a/content/buffer.js
+++ b/content/buffer.js
@@ -891,16 +891,16 @@ liberator.Buffer = function () //{{{
offsetY = Number(coords[1]) + 1;
}
- var newTab = false, newWindow = false;
+ var ctrlKey = false, shiftKey = false;
switch (where)
{
case liberator.NEW_TAB:
case liberator.NEW_BACKGROUND_TAB:
- newTab = true;
- newWindow = (where == liberator.NEW_BACKGROUND_TAB);
+ ctrlKey = true;
+ shiftKey = (where == liberator.NEW_BACKGROUND_TAB);
break;
case liberator.NEW_WINDOW:
- newWindow = true;
+ shiftKey = true;
break;
case liberator.CURRENT_TAB:
break;
@@ -913,9 +913,8 @@ liberator.Buffer = function () //{{{
var evt = doc.createEvent("MouseEvents");
for each (event in ["mousedown", "mouseup", "click"])
{
- evt.initMouseEvent(event, true, true, view, 1, offsetX, offsetY,
- 0, 0, /*ctrl*/ newTab, /*event.altKey*/0, /*event.shiftKey*/ newWindow,
- /*event.metaKey*/ newTab, 0, null);
+ evt.initMouseEvent(event, true, true, view, 1, offsetX, offsetY, 0, 0,
+ ctrlKey, /*altKey*/0, shiftKey, /*metaKey*/ ctrlKey, 0, null);
elem.dispatchEvent(evt);
}
},
@@ -1016,8 +1015,7 @@ liberator.Buffer = function () //{{{
{
if (frame.document.body.localName.toLowerCase() == "body")
frames.push(frame);
- for (var i = 0; i < frame.frames.length; i++)
- arguments.callee(frame.frames[i]);
+ Array.forEach(frame.frames, arguments.callee);
})(window.content);
if (frames.length == 0) // currently top is always included
@@ -1038,15 +1036,7 @@ liberator.Buffer = function () //{{{
// focused. Since this is not the current FF behaviour,
// we initalize current to -1 so the first call takes us to the
// first frame.
- var current = -1;
- for (var i = 0; i < frames.length; i++)
- {
- if (frames[i] == document.commandDispatcher.focusedWindow)
- {
- var current = i;
- break;
- }
- }
+ var current = frames.indexOf(document.commandDispatcher.focusedWindow);
// calculate the next frame to focus
var next = current;
@@ -1497,19 +1487,21 @@ liberator.Marks = function () //{{{
function getSortedMarks()
{
- var lmarks, umarks;
+ var location = window.content.location.href;
+ var lmarks = [];
// local marks
- lmarks = [[[mark, value[i]] for (i in value)
- if (value[i].location == window.content.location.href)]
- for ([mark, value] in localMarks)];
- lmarks = Array.concat.apply(Array, lmarks);
+ for (let [mark, value] in Iterator(localMarks))
+ {
+ for each (val in value.filter(function (val) val.location == location))
+ lmarks.push([mark, val]);
+ }
lmarks.sort();
// URL marks
// FIXME: why does umarks.sort() cause a "Component is not available =
// NS_ERROR_NOT_AVAILABLE" exception when used here?
- umarks = [[key, mark] for ([key, mark] in urlMarks)];
+ var umarks = [[key, mark] for ([key, mark] in urlMarks)];
umarks.sort(function (a, b) a[0].localeCompare(b[0]));
return lmarks.concat(umarks);
diff --git a/content/completion.js b/content/completion.js
index ba0e9f9d..cd5dcd37 100644
--- a/content/completion.js
+++ b/content/completion.js
@@ -506,7 +506,7 @@ liberator.Completion = function () //{{{
// discard all entries in the 'urls' array, which don't match 'filter
// urls must be of type [["url", "title"], [...]] or optionally
// [["url", "title", keyword, [tags]], [...]]
- filterURLArray: function (urls, filter, tags)
+ filterURLArray: function (urls, filter, filterTags)
{
var filtered = [];
// completions which don't match the url but just the description
@@ -518,49 +518,40 @@ liberator.Completion = function () //{{{
var hasTags = urls[0].length >= 4;
// TODO: create a copy of urls?
- if (!filter && (!hasTags || !tags))
+ if (!filter && (!hasTags || !filterTags))
return urls;
- tags = tags || [];
+ filterTags = filterTags || [];
// TODO: use ignorecase and smartcase settings
- var ignorecase = true;
- if (filter != filter.toLowerCase() || tags.join(",") != tags.join(",").toLowerCase())
- ignorecase = false;
+ var ignorecase = (filter == filter.toLowerCase() && filterTags.join(",") == filterTags.join(",").toLowerCase());
if (ignorecase)
{
filter = filter.toLowerCase();
- tags = tags.map(function (t) { return t.toLowerCase(); });
+ filterTags = filterTags.map(String.toLowerCase);
}
// Longest Common Subsequence
// This shouldn't use buildLongestCommonSubstring for performance
// reasons, so as not to cycle through the urls twice
- outer:
- for (var i = 0; i < urls.length; i++)
+ for each (elem in urls)
{
- var url = urls[i][0] || "";
- var title = urls[i][1] || "";
- var tag = urls[i][3] || [];
+ var url = elem[0] || "";
+ var title = elem[1] || "";
+ var tags = elem[3] || [];
if (ignorecase)
{
url = url.toLowerCase();
title = title.toLowerCase();
- tag = tag.map(function (t) { return t.toLowerCase(); });
+ tags = tags.map(String.toLowerCase);
}
// filter on tags
- for (var j = 0; j < tags.length; j++)
- {
- if (!tags[j])
+ if(filterTags.some(function (tag) tag && tags.indexOf(tag) == -1))
continue;
- if (tag.indexOf(tags[j]) == -1)
- continue outer;
- }
-
if (url.indexOf(filter) == -1)
{
// no direct match of filter in the url, but still accept this item
@@ -568,7 +559,7 @@ liberator.Completion = function () //{{{
if (filter.split(/\s+/).every(function (token) {
return (url.indexOf(token) > -1 || title.indexOf(token) > -1);
}))
- additionalCompletions.push(urls[i]);
+ additionalCompletions.push(elem);
continue;
}
@@ -594,7 +585,7 @@ liberator.Completion = function () //{{{
});
}
- filtered.push(urls[i]);
+ filtered.push(elem);
}
return filtered.concat(additionalCompletions);
diff --git a/content/storage.jsm b/content/storage.jsm
index 429ec603..c325a230 100644
--- a/content/storage.jsm
+++ b/content/storage.jsm
@@ -60,18 +60,20 @@ function loadPref(name, store, type)
if (store)
var pref = getCharPref(prefName(name));
if (pref)
- var obj = json.decode(pref);
- if (obj instanceof type)
- return obj;
+ var result = json.decode(pref);
+ if (result instanceof type)
+ return result;
+}
+
+function savePref(obj)
+{
+ if (obj.store)
+ prefService.setCharPref(prefName(obj.name), obj.serial)
}
var prototype = {
fireEvent: function (event, arg) { storage.fireEvent(this.name, event, arg) },
- save: function ()
- {
- if (this.store)
- prefService.setCharPref(prefName(this.name), this.serial)
- },
+ save: function () { savePref(this) },
};
function ObjectStore(name, store)
@@ -207,6 +209,13 @@ var storage = {
return this[key];
},
+ newObject: function newObject(key, constructor, store)
+ {
+ if(!(key in keys))
+ this._addKey(key, new constructor(key, store, loadPref(key, store, Object)));
+ return this[key];
+ },
+
addObserver: function addObserver(key, callback)
{
if (!(key in observers))
@@ -227,15 +236,20 @@ var storage = {
fireEvent: function fireEvent(key, event, arg)
{
- for each(callback in observers[key])
+ for each (callback in observers[key])
callback(key, event, arg);
},
+ save: function save(key)
+ {
+ savePref(keys[key]);
+ },
+
saveAll: function storeAll()
{
- for each(key in keys)
- key.save();
- }
+ for each (obj in keys)
+ savePref(obj);
+ },
};
// vim: set fdm=marker sw=4 sts=4 et ft=javascript:
diff --git a/content/ui.js b/content/ui.js
index 4f89153c..6f7e0e27 100644
--- a/content/ui.js
+++ b/content/ui.js
@@ -1402,7 +1402,7 @@ liberator.StatusLine = function () //{{{
modified += "+";
if (sh.index < sh.count -1)
modified += "-";
- if (liberator.bookmarks.isBookmarked(url))
+ if (liberator.bookmarks.isBookmarked(liberator.buffer.URL))
modified += "\u2764"; // a heart symbol: ❤
//modified += "\u2665"; // a heart symbol: ♥
diff --git a/content/util.js b/content/util.js
index 7d71259b..7a516deb 100644
--- a/content/util.js
+++ b/content/util.js
@@ -28,6 +28,11 @@ the terms of any one of the MPL, the GPL or the LGPL.
liberator.util = { //{{{
+ clip: function (str, length)
+ {
+ return str.length <= length ? str : str.substr(0, length - 3) + "...";
+ },
+
// TODO: use :highlight color groups
// if "processStrings" is true, any passed strings will be surrounded by " and
// any line breaks are displayed as \n