/* * also includes methods for dealing with * keywords and search engines */ function Bookmarks() //{{{ { //////////////////////////////////////////////////////////////////////////////// ////////////////////// PRIVATE SECTION ///////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////{{{ const search_service = Components.classes["@mozilla.org/browser/search-service;1"]. getService(Components.interfaces.nsIBrowserSearchService); const rdf_service = Components.classes["@mozilla.org/rdf/rdf-service;1"]. getService( Components.interfaces.nsIRDFService ); var bookmarks = null; var keywords = null; if (vimperator.options["preload"]) setTimeout(function() { load(); } , 100); function load() { // update our bookmark cache var root = rdf_service.GetResource("NC:BookmarksRoot"); bookmarks = new Array(); // also clear our bookmark cache keywords = new Array(); var bmarks = []; // here getAllChildren will store the bookmarks BookmarksUtils.getAllChildren(root, bmarks); for (var bm in bmarks) { if (bmarks[bm][0] && bmarks[bm][1]) bookmarks.push([bmarks[bm][1].Value, bmarks[bm][0].Value ]); // keyword if (bmarks[bm][1] && bmarks[bm][2]) keywords.push([bmarks[bm][2].Value, bmarks[bm][0].Value, bmarks[bm][1].Value]); } } /////////////////////////////////////////////////////////////////////////////}}} ////////////////////// PUBLIC SECTION ////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////{{{ /* * @return a new Array() of our bookmarks */ this.get = function() { if (!bookmarks) load(); return bookmarks; } /** * @TODO: keyword support */ this.add = function (title, uri, keyword) { if (!bookmarks) load(); folder = rdf_service.GetResource("NC:BookmarksRoot"); var rSource = BookmarksUtils.createBookmark(title, uri, keyword, title); var selection = BookmarksUtils.getSelectionFromResource(rSource); var target = BookmarksUtils.getTargetFromFolder(folder); BookmarksUtils.insertAndCheckSelection("newbookmark", selection, target); //also update bookmark cache bookmarks.unshift([uri, title]); return true; } /* no idea what it does, it Just Works (TM) * * @returns number of deleted bookmarks */ this.remove = function(url) { var deleted = 0; if (!url) return 0; // gNC_NS for trunk, NC_NS for 1.X //try { var pNC_NS; pNC_NS = gNC_NS;} catch (err) { pNC_NS = NC_NS;} if (!BMSVC || !BMDS || !RDF || !gNC_NS) // defined from firefox return 0; var curfolder = RDF.GetResource("NC:BookmarksRoot"); var urlArc = RDF.GetResource(gNC_NS + "URL"); var urlLiteral = RDF.GetLiteral(url); if (BMDS.hasArcIn(urlLiteral, urlArc)) { var bmResources, bmResource, title, uri, type, ptype; bmResources = BMSVC.GetSources(urlArc, urlLiteral, true); while (bmResources.hasMoreElements()) { bmResource = bmResources.getNext(); type = BookmarksUtils.resolveType(bmResource); if (type != "ImmutableBookmark") { ptype = BookmarksUtils.resolveType(BMSVC.getParent(bmResource)); // alert(type); // if ( type == "Folder") // store the current folder // curfolder = bmResource; if ( (type == "Bookmark" || type == "IEFavorite") && ptype != "Livemark") { title = BookmarksUtils.getProperty(bmResource, gNC_NS + "Name"); uri = BookmarksUtils.getProperty(bmResource, gNC_NS + "URL"); if (uri == url) { RDFC.Init(BMDS, BMSVC.getParent(bmResource)); RDFC.RemoveElement(bmResource, true); deleted++; } } } } } // also update bookmark cache, if we removed at least one bookmark if (deleted > 0) load(); return deleted; } /* also ensures that each search engine has a vimperator-friendly alias */ this.getSearchEngines = function() { var search_engines = new Array(); var firefox_engines = search_service.getVisibleEngines({ }); for (var i in firefox_engines) { if (!firefox_engines[i].alias || !firefox_engines[i].alias.match(/^[a-z0-9_]+$/)) { var alias = firefox_engines[i].name.replace(/^\W*(\w+).*/, "$1").toLowerCase(); firefox_engines[i].alias = alias; } search_engines.push([firefox_engines[i].alias, firefox_engines[i].description]); } return search_engines; } // format of returned array: // [keyword, helptext, url] this.getKeywords = function() { if (!keywords) load(); return keywords; } // if the engine name is null, it uses the default search engine // @returns the url for the search string this.getSearchURL = function(text, engine_name) { var url = null; if (!engine_name || engine_name == "") engine_name = vimperator.options["defsearch"]; // first checks the search engines for a match var engine = search_service.getEngineByAlias(engine_name); if (engine) { if (text) url = engine.getSubmission(text, null).uri.spec; else url = engine.searchForm; } else // check for keyword urls { if (!keywords) load(); for (var i in keywords) { if (keywords[i][0] == engine_name) { if (text == null) text = ""; url = keywords[i][2].replace(/%s/g, encodeURIComponent(text)); break; } } } // if we came here, the engine_name is neither a search engine or URL return url; } this.list = function(filter, fullmode) { if (fullmode) openURLsInNewTab("chrome://browser/content/bookmarks/bookmarksPanel.xul", true); else { var items = vimperator.bookmarks.get(filter); vimperator.previewwindow.show(items); } } /* res = parseBookmarkString("-t tag1,tag2 -T title http://www.orf.at"); res.tags is an array of tags res.title is the title or "" if no one was given res.url is the url as a string returns null, if parsing failed */ Bookmarks.parseBookmarkString = function(str) { var res = {}; res.tags = []; res.title = null; res.url = null; var re_title = /^\s*((-t|--title)\s+(\w+|\".*\"))(.*)/; var re_tags = /^\s*((-T|--tags)\s+((\w+)(,\w+)*))(.*)/; var re_url = /^\s*(\".+\"|\S+)(.*)/; var match_tags = null; var match_title = null; var match_url = null; while (!str.match(/^\s*$/)) { /* first check for --tags */ match_tags = str.match(re_tags); if (match_tags != null) { str = match_tags[match_tags.length-1]; // the last captured parenthesis is the rest of the string tags = match_tags[3].split(","); res.tags = res.tags.concat(tags); } else /* then for --titles */ { match_title = str.match(re_title); if (match_title != null) { // only one title allowed if (res.title != null) return null; str = match_title[match_title.length-1]; // the last captured parenthesis is the rest of the string var title = match_title[3]; if (title.charAt(0) == '"') title = title.substring(1,title.length-1); res.title = title; } else /* at last check for an url */ { match_url = str.match(re_url); if (match_url != null) { // only one url allowed if (res.url != null) return null; str = match_url[match_url.length-1]; // the last captured parenthesis is the rest of the string url = match_url[1]; if (url.charAt(0) == '"') url = url.substring(1,url.length-1); res.url = url; } else return null; // no url, tag or title found but still text left, abort } } } return res; } //}}} } //}}} function History() //{{{ { //////////////////////////////////////////////////////////////////////////////// ////////////////////// PRIVATE SECTION ///////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////{{{ const rdf_service = Components.classes["@mozilla.org/rdf/rdf-service;1"]. getService( Components.interfaces.nsIRDFService ); const global_history_service = Components.classes["@mozilla.org/browser/global-history;2"]. getService(Components.interfaces.nsIRDFDataSource); var history = null; if (vimperator.options["preload"]) setTimeout(function() { load(); } , 100); function load() { history = new Array(); var historytree = document.getElementById("hiddenHistoryTree"); if (!historytree) return; if (historytree.hidden) { historytree.hidden = false; historytree.database.AddDataSource(global_history_service); } if (!historytree.ref) historytree.ref = "NC:HistoryRoot"; var nameResource = rdf_service.GetResource(gNC_NS + "Name"); var builder = historytree.builder.QueryInterface(Components.interfaces.nsIXULTreeBuilder); var count = historytree.view.rowCount; for (var i = count-1; i >= 0; i--) { var res = builder.getResourceAtIndex(i); var url = res.Value; var title; var titleRes = historytree.database.GetTarget(res, nameResource, true); if (!titleRes) continue; var titleLiteral = titleRes.QueryInterface(Components.interfaces.nsIRDFLiteral); if (titleLiteral) title = titleLiteral.Value; else title = ""; history.push([url, title]); } } /////////////////////////////////////////////////////////////////////////////}}} ////////////////////// PUBLIC SECTION ////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////{{{ /* * @return a new Array() of our bookmarks */ this.get = function() { if (!history) load(); return history; } this.add = function (url, title) { if (!history) load(); history = history.filter(function(elem) { return elem[0] != url; }); history.unshift([url, title]); return true; }; // TODO: better names? this.stepTo = function(steps) { var index = getWebNavigation().sessionHistory.index + steps; if (index >= 0 && index < getWebNavigation().sessionHistory.count) { getWebNavigation().gotoIndex(index); } else { vimperator.beep(); if (index < 0) vimperator.echo("Cannot go past beginning of history"); else vimperator.echo("Cannot go past end of history"); } } this.goToStart = function() { var index = getWebNavigation().sessionHistory.index; if (index == 0) { vimperator.echo("Already at beginning of history"); return; } getWebNavigation().gotoIndex(0); } this.goToEnd = function() { var index = getWebNavigation().sessionHistory.index; var max = getWebNavigation().sessionHistory.count -1; if (index == max) { vimperator.echo("Already at end of history"); return; } getWebNavigation().gotoIndex(max); } this.list = function(filter, fullmode) { if (fullmode) openURLsInNewTab("chrome://browser/content/history/history-panel.xul", true); else { var items = vimperator.history.get(filter); vimperator.previewwindow.show(items); } } //}}} } //}}} function Marks() //{{{ { //////////////////////////////////////////////////////////////////////////////// ////////////////////// PRIVATE SECTION ///////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////{{{ var local_marks = {}; var url_marks = {}; var pending_jumps = []; var appcontent = document.getElementById("appcontent"); if (appcontent) appcontent.addEventListener("load", onPageLoad, true); function onPageLoad(event) { var win = event.originalTarget.defaultView; for (var i = 0, length = pending_jumps.length; i < length; i++) { var mark = pending_jumps[i]; if (win.location.href == mark.location) { win.scrollTo(mark.position.x * win.scrollMaxX, mark.position.y * win.scrollMaxY); pending_jumps.splice(i, 1); return; } } } function removeLocalMark(mark) { if (mark in local_marks) { var win = window.content; for (var i = 0; i < local_marks[mark].length; i++) { if (local_marks[mark][i].location == win.location.href) { vimperator.log("Deleting local mark: " + mark + " | " + local_marks[mark][i].location + " | (" + local_marks[mark][i].position.x + ", " + local_marks[mark][i].position.y + ") | tab: " + vimperator.tabs.index(local_marks[mark][i].tab), 5); local_marks[mark].splice(i, 1); if (local_marks[mark].length == 0) delete local_marks[mark]; break; } } } } function removeURLMark(mark) { if (mark in url_marks) { vimperator.log("Deleting URL mark: " + mark + " | " + url_marks[mark].location + " | (" + url_marks[mark].position.x + ", " + url_marks[mark].position.y + ") | tab: " + vimperator.tabs.index(url_marks[mark].tab), 5); delete url_marks[mark]; } } function isLocalMark(mark) { return /^[a-z]$/.test(mark); } function isURLMark(mark) { return /^[A-Z0-9]$/.test(mark); } function getSortedMarks() { // local marks var lmarks = []; for (var mark in local_marks) { for (var i = 0; i < local_marks[mark].length; i++) { if (local_marks[mark][i].location == window.content.location.href) lmarks.push([mark, local_marks[mark][i]]); } } lmarks.sort(); // URL marks var umarks = []; for (var mark in url_marks) umarks.push([mark, url_marks[mark]]); // FIXME: why does umarks.sort() cause a "Component is not available = // NS_ERROR_NOT_AVAILABLE" exception when used here? umarks.sort(function(a, b) { if (a[0] < b[0]) return -1 else if (a[0] > b[0]) return 1; return 0 }); return lmarks.concat(umarks); } /////////////////////////////////////////////////////////////////////////////}}} ////////////////////// PUBLIC SECTION ////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////{{{ // TODO: add support for frameset pages this.add = function(mark) { var win = window.content; if (win.document.body.localName.toLowerCase() == "frameset") { vimperator.echo("marks support for frameset pages not implemented yet"); return; } var x = win.scrollMaxX ? win.pageXOffset / win.scrollMaxX : 0; var y = win.scrollMaxY ? win.pageYOffset / win.scrollMaxY : 0; var position = { x: x, y: y }; if (isURLMark(mark)) { vimperator.log("Adding URL mark: " + mark + " | " + win.location.href + " | (" + position.x + ", " + position.y + ") | tab: " + vimperator.tabs.index(vimperator.tabs.getTab()), 5); url_marks[mark] = { location: win.location.href, position: position, tab: vimperator.tabs.getTab() }; } else if (isLocalMark(mark)) { // remove any previous mark of the same name for this location removeLocalMark(mark); if (!local_marks[mark]) local_marks[mark] = []; vimperator.log("Adding local mark: " + mark + " | " + win.location.href + " | (" + position.x + ", " + position.y + ")", 5); local_marks[mark].push({ location: win.location.href, position: position }); } } this.remove = function(filter, special) { if (special) { // :delmarks! only deletes a-z marks for (var mark in local_marks) removeLocalMark(mark); } else { var pattern = new RegExp("[" + filter.replace(/\s+/g, '') + "]"); for (var mark in url_marks) { if (pattern.test(mark)) removeURLMark(mark); } for (var mark in local_marks) { if (pattern.test(mark)) removeLocalMark(mark); } } } this.jumpTo = function(mark) { var ok = false; if (isURLMark(mark)) { var slice = url_marks[mark]; if (slice && slice.tab && slice.tab.linkedBrowser) { if (!slice.tab.parentNode) { pending_jumps.push(slice); // NOTE: this obviously won't work on generated pages using // non-unique URLs, like Vimperator's help :( openURLsInNewTab(slice.location, true); return; } var index = vimperator.tabs.index(slice.tab); if (index != -1) { vimperator.tabs.select(index); var win = slice.tab.linkedBrowser.contentWindow; if (win.location.href != slice.location) { pending_jumps.push(slice); win.location.href = slice.location; return; } vimperator.log("Jumping to URL mark: " + mark + " | " + slice.location + " | (" + slice.position.x + ", " + slice.position.y + ") | tab: " + vimperator.tabs.index(slice.tab), 5); win.scrollTo(slice.position.x * win.scrollMaxX, slice.position.y * win.scrollMaxY); ok = true; } } } else if (isLocalMark(mark)) { var win = window.content; var slice = local_marks[mark] || []; for (var i = 0; i < slice.length; i++) { if (win.location.href == slice[i].location) { vimperator.log("Jumping to local mark: " + mark + " | " + slice[i].location + " | (" + slice[i].position.x + ", " + slice[i].position.y + ")", 5); win.scrollTo(slice[i].position.x * win.scrollMaxX, slice[i].position.y * win.scrollMaxY); ok = true; } } } if (!ok) vimperator.echoerr("E20: Mark not set"); // FIXME: move up? } this.list = function(filter) { var marks = getSortedMarks(); if (marks.length == 0) { vimperator.echoerr("No marks set"); return; } if (filter.length > 0) { marks = marks.filter(function(mark) { if (filter.indexOf(mark[0]) > -1) return mark; }); if (marks.length == 0) { vimperator.echoerr("E283: No marks matching \"" + filter + "\""); return; } } var list = ""; for (var i = 0; i < marks.length; i++) { list += "" + "" + "" + "" + "" + ""; } list += "
marklinecolfile
 " + marks[i][0] + "" + Math.round(marks[i][1].position.y * 100) + "%" + Math.round(marks[i][1].position.x * 100) + "%" + marks[i][1].location + "
"; vimperator.commandline.echo(list, true); // TODO: force of multiline widget a better way } //}}} } //}}} function QuickMarks() //{{{ { //////////////////////////////////////////////////////////////////////////////// ////////////////////// PRIVATE SECTION ///////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////{{{ var marks = {}; // load the saved quickmarks -- TODO: change to sqlite var saved_marks = Options.getPref("quickmarks", "").split("\n"); for (var i = 0; i < saved_marks.length - 1; i += 2) { marks[saved_marks[i]] = saved_marks[i + 1]; } /////////////////////////////////////////////////////////////////////////////}}} ////////////////////// PUBLIC SECTION ////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////{{{ this.add = function(mark, location) { marks[mark] = location; } this.remove = function(filter) { var pattern = new RegExp("[" + filter.replace(/\s+/g, '') + "]"); for (var mark in marks) { if (pattern.test(mark)) delete marks[mark]; } } this.jumpTo = function(mark, newtab) { var url = marks[mark]; if (url) { if (newtab) openURLsInNewTab(url, true); else openURLs(url); } else vimperator.echoerr("E20: QuickMark not set"); // FIXME: move up? } this.list = function() { var is_empty = true; var list = ""; for (var i in marks) { is_empty = false; list += ""; } list += "
markURL
 " + i + "" + marks[i] + "
"; if (!is_empty) vimperator.commandline.echo(list, true); // TODO: force of multiline widget a better way else vimperator.echoerr("No quickmarks defined"); } this.destroy = function() { // save the marks var saved_marks = ""; for (var i in marks) { saved_marks += i + "\n"; saved_marks += marks[i] + "\n"; } Options.setPref("quickmarks", saved_marks); } //}}} } //}}} // vim: set fdm=marker sw=4 ts=4 et: