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 () //{{{ ""; 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 += ""; } 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()) + "
" + "
NameAddress
" + displayName + "" + mailAddr + "
"; 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 () //{{{ "
titleURL
"; 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 += ""; } @@ -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
titleURL
" + title + "" + url + "