var PLUGIN_INFO = {NAME} Hatena Bookmark UserSearch はてなブックマークユーザ検索 2.0pre 2.0pre http://svn.coderepos.org/share/lang/javascript/vimperator-plugins/trunk/hatena-bookmark-search.js Yuichi Tateno MPL 1.1/GPL 2.0/LGPL 2.1 0.1.2 || :bs[earch][!] word :tabbs[earch][!] word ||< ログインしているユーザのブックマークを、URL, コメント, タイトル から検索します。 はてなブックマークユーザページの右上の検索のローカル版のようなイメージです。 XUL/Migemo が入っている場合は Migemo を使い正規表現検索します。 Migemo を利用した検索語の絞り込みはスペース区切りの 2単語までとなります。 Migemo を利用すると検索が重くなるので、遅いマシンやインクリメンタル検索環境下では、以下の設定をすることで migemo 検索をしなくなります。 >|| liberator.globalVariables.hatena_bookmark_no_migemo = true; ||< :bs word では、選択している URL を開きます。:bs! word では、選択している URL のはてなブックマークエントリーページを開きます。:bs と単語を入力しないと、http://b.hatena.ne.jp/my を開きます。:bs! では http://b.hatena.ne.jp/ トップページを開きます。 初回検索時にデータを構築しますが、強制的にデータをロードし直したい時などは >|| :bs -reload ||< としてください。 (invalid options エラーが出る場合は、適当な文字を後ろに付加するか、最新(Nightly)の Vimperator を使ってください) また、:open, :tabopen の補完で、completeオプションに "H" を追加することではてなブックマークの検索が可能です。 >|| :set complete+=H ||< .vimperatorrcに書く場合は >|| autocmd VimperatorEnter .* :set complete+=H ||< としてください。 ]]> ; liberator.plugins.HatenaBookmark = (function(){ var p = function(arg) { Application.console.log(arg); // liberator.log(arg); } const HatenaBookmark = {}; HatenaBookmark.Data = new Struct('url', 'title', 'comment', 'icon'); HatenaBookmark.Data.defaultValue('icon', function() bookmarks.getFavicon(this.url)); HatenaBookmark.Data.prototype.__defineGetter__('stext', function() { if (typeof this._stext == 'undefined') { this._stext = this.comment + "\0" + this.title + "\0" + this.url; } return this._stext; }); HatenaBookmark.Data.prototype.__defineGetter__("extra", function () [ ["comment", this.comment, "Comment"], ].filter(function (item) item[1])); var XMigemoCore; var XMigemoTextUtils; try { XMigemoCore = Cc['@piro.sakura.ne.jp/xmigemo/factory;1'] .getService(Ci.pIXMigemoFactory) .getService("ja"); XMigemoTextUtils = Cc['@piro.sakura.ne.jp/xmigemo/text-utility;1'].getService(Ci.pIXMigemoTextUtils); } catch (e if e instanceof TypeError) { } HatenaBookmark.useMigemo = !!(!liberator.globalVariables.hatena_bookmark_no_migemo && XMigemoCore); HatenaBookmark.Command = { templateDescription: function (item, text) { return <> { !(item.extra && item.extra.length) ? "" : { template.map(item.extra, function (e) <>{e[1]}, <> /* Non-breaking space */) } } }, templateTitleIcon: function (item, text) { var simpleURL = text.replace(/^https?:\/\//, ''); if (simpleURL.indexOf('/') == simpleURL.length-1) simpleURL = simpleURL.replace('/', ''); return <>{item.icon ? : <>}{item.item.title} { simpleURL } }, filter: function (_item) { var item = _item.item; // 'this' is context object. if (HatenaBookmark.useMigemo) { if (!this.migemo) { this.migemo = HatenaBookmark.Command.compileRegexp(this.filter); } var migemo = this.migemo; return migemo.test(item.stext); } else { return this.match(item.url) || this.match(item.comment) || this.match(item.title); } }, compileRegexp: function(str) { let a; with (XMigemoTextUtils) { a = sanitize(trim(str)).split(/\s+/).join(' '); } return new RegExp(XMigemoTextUtils.getANDFindRegExpFromTerms(XMigemoCore.getRegExps(a)), 'gim'); }, execute: function(args) { if (args['-reload']) { HatenaBookmark.UserData.reload(); liberator.echo('HatenaBookmark data reloaded.'); return; } var url = HatenaBookmark.Command.genURL(args); liberator.open(url); }, executeTab: function(args) { var url = HatenaBookmark.Command.genURL(args); liberator.open(url, liberator.NEW_TAB); }, genURL: function(args) { var url = (args.string || '').replace(/\s/g, ''); if (url.length) { if (args.bang) { return 'http://b.hatena.ne.jp/entry/' + url.replace('#', '%23'); } else { return url; } } else { if (args.bang) { return 'http://b.hatena.ne.jp/'; } else { return 'http://b.hatena.ne.jp/my'; } } }, createCompleter: function(titles) { return function(context) { context.format = { anchored: true, title: titles, keys: { text: "url", description: "url", icon: "icon", extra: "extra"}, process: [ HatenaBookmark.Command.templateTitleIcon, HatenaBookmark.Command.templateDescription, ], } context.ignoreCase = true; if (context.migemo) delete context.migemo; context.filters = [HatenaBookmark.Command.filter]; context.completions = HatenaBookmark.UserData.bookmarks; } } } HatenaBookmark.Command.options = { completer: HatenaBookmark.Command.createCompleter(['TITLE', 'Info']), literal: 0, argCount: '*', bang: true, options: [ [['-reload'], commands.OPTION_NOARG] ], } commands.addUserCommand( ['bs[earch]'], 'Hatena Bookmark UserSearch', HatenaBookmark.Command.execute, HatenaBookmark.Command.options, true ); commands.addUserCommand( ['tabbs[earch]'], 'Hatena Bookmark UserSearch', HatenaBookmark.Command.executeTab, HatenaBookmark.Command.options, true ); completion.addUrlCompleter("H", "Hatena Bookmarks", HatenaBookmark.Command.createCompleter(["Hatena Bookmarks"])); HatenaBookmark.UserData = { get bookmarks() { this.init(); return this._bookmarks; }, reload: function() { this._inited = false; this.init(); }, init: function() { if (!this._inited) { if (this._bookmarks) delete this._bookmarks; this._inited = true; this.preloadLimit = 500; this.preload(); } }, preload: function() { this.load({ offset: 0, limit: this.preloadLimit }); }, load: function(query) { var url = 'http://b.hatena.ne.jp/my/search.data'; var xhr = new XMLHttpRequest(); var self = this; if (query.async) { xhr.onreadystatechange = function() { if (xhr.readyState == 4) { if (xhr.status == 200) { self.completeHandler(xhr) } else { liberator.echoerr('XHR Error: ' + xhr.statusText); // throw new Error(xhr.statusText); } } } } xhr.open('POST', url, query.async); xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xhr.send(this.makeQuery(query)); if (!query.async) { if (xhr.status == 200) { this.completeHandler(xhr); } else { liberator.echoerr('XHR Error: ' + xhr.statusText); // throw new Error(xhr.statusText); } } }, makeQuery: function(data) { var pairs = []; var regexp = /%20/g; for (var k in data) { if (typeof data[k] == 'undefined') continue; var v = data[k].toString(); var pair = encodeURIComponent(k).replace(regexp,'+') + '=' + encodeURIComponent(v).replace(regexp,'+'); pairs.push(pair); } return pairs.join('&'); }, completeHandler: function(res) { if (this._loaded) return; if (!this._bookmarks) { this.createDataStructure(res.responseText || ''); if (this._bookmarks.length == this.preloadLimit) { this.load({ offset: this.preloadLimit, async: true }); } else { this._loaded = 1; } } else { this.updateDataStructure(res.responseText || ''); this._loaded = 1; } }, updateDataStructure: function(data) { this.pushData(this._bookmarks, data); }, createDataStructure: function(data) { this._bookmarks = []; this.pushData(this._bookmarks, data); }, pushData: function(ary, data) { var infos = data.split("\n"); var tmp = infos.splice(0, infos.length * 3/4); var len = tmp.length; for (var i = 0; i < len; i+=3) { ary.push(new HatenaBookmark.Data(tmp[i+2]/* url */, tmp[i]/* title */, tmp[i+1]/* comment */)); } }, mapFunc: function(item) { item = item.split("\n"); return { url: item[2], comment: item[1], title: item[0], } }, }; return HatenaBookmark; })();