| 1 | var PLUGIN_INFO = |
|---|
| 2 | <VimperatorPlugin> |
|---|
| 3 | <name>{NAME}</name> |
|---|
| 4 | <description>Access to Hatena Sevices quickly.</description> |
|---|
| 5 | <description lang="ja">はてなのサーヴィスに簡単にアクセス出来ます.</description> |
|---|
| 6 | <minVersion>2.1a1pre</minVersion> |
|---|
| 7 | <maxVersion>2.1a1pre</maxVersion> |
|---|
| 8 | <updateURL>http://svn.coderepos.org/share/lang/javascript/vimperator-plugins/trunk/access_hatena.js</updateURL> |
|---|
| 9 | <author mail="masa138@gmail.com" homepage="http://www.hatena.ne.jp/masa138/">Masayuki KIMURA and id:hitode909</author> |
|---|
| 10 | <version>0.62</version> |
|---|
| 11 | <detail><![CDATA[ |
|---|
| 12 | |
|---|
| 13 | == Commands == |
|---|
| 14 | :accesshatena |
|---|
| 15 | Access to http://www.hatena.ne.jp/ |
|---|
| 16 | |
|---|
| 17 | :accesshatena {host} |
|---|
| 18 | Access to http://{host}.hatena.ne.jp/ |
|---|
| 19 | |
|---|
| 20 | :accesshatena {host} {user_id} |
|---|
| 21 | Access to http://{host}.hatena.ne.jp/{user_id}/ |
|---|
| 22 | |
|---|
| 23 | :accesshatena {group_name}.g {user_id} |
|---|
| 24 | Access to http://{group_name}.g.hatena.ne.jp/{user_id}/ |
|---|
| 25 | |
|---|
| 26 | |
|---|
| 27 | == Global variables == |
|---|
| 28 | maxRecentHostsSize |
|---|
| 29 | 直近何件の履歴を検索してホスト補完をするか |
|---|
| 30 | |
|---|
| 31 | collectLogSpan |
|---|
| 32 | どのくらいの期間の履歴を検索するか |
|---|
| 33 | |
|---|
| 34 | accessHatenaUseWedata |
|---|
| 35 | Wedata から拒否 ID リストを取ってくるか |
|---|
| 36 | |
|---|
| 37 | accessHatenaIgnoreIds |
|---|
| 38 | Wedata の拒否リストに含まれないけれど拒否したい ID の記入用 |
|---|
| 39 | |
|---|
| 40 | |
|---|
| 41 | == .vimperatorrc == |
|---|
| 42 | コマンドが長いので,以下の様に短い物にマッピングすると便利です. |
|---|
| 43 | >|| |
|---|
| 44 | map ; :accesshatena |
|---|
| 45 | ||< |
|---|
| 46 | # 最後にスペースを入れておくと直ぐにホストの入力から始められます. |
|---|
| 47 | |
|---|
| 48 | ]]></detail> |
|---|
| 49 | </VimperatorPlugin>; |
|---|
| 50 | (function(){ |
|---|
| 51 | var useWedata; |
|---|
| 52 | var ignoreIds; |
|---|
| 53 | var ids; |
|---|
| 54 | var recentHosts; |
|---|
| 55 | var maxRecentHostsSize; |
|---|
| 56 | var historyCompletions; |
|---|
| 57 | var collectLogSpan; |
|---|
| 58 | var pageFor; |
|---|
| 59 | var title; |
|---|
| 60 | var isFirst; |
|---|
| 61 | var isIncreased; |
|---|
| 62 | var isUpdated; |
|---|
| 63 | var lastLocation; |
|---|
| 64 | |
|---|
| 65 | function Title() { |
|---|
| 66 | this.initialize.apply(this, arguments); |
|---|
| 67 | } |
|---|
| 68 | Title.prototype = { |
|---|
| 69 | initialize: function() { |
|---|
| 70 | this.title = []; |
|---|
| 71 | this.n = 0; |
|---|
| 72 | }, |
|---|
| 73 | key: function(host, id) { |
|---|
| 74 | return [host, id.replace('/', '')].join(':'); |
|---|
| 75 | }, |
|---|
| 76 | set: function(host, id, title) { |
|---|
| 77 | var key = this.key(host, id); |
|---|
| 78 | if (this.title[key] == null) { |
|---|
| 79 | this.title[key] = title; |
|---|
| 80 | this.n++; |
|---|
| 81 | } |
|---|
| 82 | }, |
|---|
| 83 | get: function(host, id, title) { |
|---|
| 84 | if (!this.title[this.key(host, id)]) return host + '.hatena.ne.jp/' + id; |
|---|
| 85 | return this.title[this.key(host, id)]; |
|---|
| 86 | } |
|---|
| 87 | }; |
|---|
| 88 | |
|---|
| 89 | function init() { |
|---|
| 90 | ids = []; |
|---|
| 91 | recentHosts = []; |
|---|
| 92 | historyCompletions = []; |
|---|
| 93 | pageFor = []; |
|---|
| 94 | title = new Title(); |
|---|
| 95 | isFirst = true; |
|---|
| 96 | isIncreased = false; |
|---|
| 97 | isUpdated = false; |
|---|
| 98 | lastLocation = window.content.location.href.replace(/^https?:\/\//, ''); |
|---|
| 99 | |
|---|
| 100 | maxRecentHostsSize = liberator.globalVariables.maxRecentHostsSize || 10; |
|---|
| 101 | collectLogSpan = liberator.globalVariables.collectLogSpan || 24 * 7 * 4 * 60 * 60 * 1000000; // 4 weeks |
|---|
| 102 | useWedata = liberator.globalVariables.accessHatenaUseWedata; |
|---|
| 103 | ignoreIds = liberator.globalVariables.accessHatenaIgnoreIds; |
|---|
| 104 | useWedata = (useWedata != null) ? useWedata : true; |
|---|
| 105 | ignoreIds = (ignoreIds != null) ? ignoreIds : ['login']; |
|---|
| 106 | |
|---|
| 107 | if (useWedata) { |
|---|
| 108 | loadWedata(); |
|---|
| 109 | } |
|---|
| 110 | } |
|---|
| 111 | init(); |
|---|
| 112 | |
|---|
| 113 | function prepareSearch() { |
|---|
| 114 | var historyService = Components.classes["@mozilla.org/browser/nav-history-service;1"].getService(Components.interfaces.nsINavHistoryService); |
|---|
| 115 | var options = historyService.getNewQueryOptions(); |
|---|
| 116 | var query = historyService.getNewQuery(); |
|---|
| 117 | query.beginTimeReference = query.TIME_RELATIVE_NOW; |
|---|
| 118 | query.beginTime = -1 * collectLogSpan; |
|---|
| 119 | query.endTimeReference = query.TIME_RELATIVE_NOW; |
|---|
| 120 | query.endTime = 0; |
|---|
| 121 | query.domain = "hatena.ne.jp"; |
|---|
| 122 | |
|---|
| 123 | return historyService.executeQuery(query, options).root; |
|---|
| 124 | } |
|---|
| 125 | |
|---|
| 126 | function collectLog() { |
|---|
| 127 | var root = prepareSearch(); |
|---|
| 128 | ids = []; |
|---|
| 129 | recentHosts = []; |
|---|
| 130 | historyCompletions = []; |
|---|
| 131 | historyCompletions.h = []; |
|---|
| 132 | |
|---|
| 133 | root.containerOpen = true; |
|---|
| 134 | for (var i = 0, length = root.childCount; i < length; i++) { |
|---|
| 135 | var page = root.getChild(i); |
|---|
| 136 | page.uri.match('^https?://([a-zA-Z0-9.]+)\\.hatena\\.ne\\.jp/([a-zA-Z][a-zA-Z0-9_-]{1,30}[a-zA-Z0-9]/?)?'); |
|---|
| 137 | var host = RegExp.$1; |
|---|
| 138 | var id = RegExp.$2; |
|---|
| 139 | var _recent_hosts_length = recentHosts.length; |
|---|
| 140 | if (host != '') { |
|---|
| 141 | if (!pageFor[host]) { |
|---|
| 142 | pageFor[host] = page; |
|---|
| 143 | isIncreased = true; |
|---|
| 144 | } else if (pageFor[host].uri.length > page.uri.length) { // より短いアドレスのタイトルが妥当 |
|---|
| 145 | pageFor[host] = page; |
|---|
| 146 | isUpdated = true; |
|---|
| 147 | } |
|---|
| 148 | |
|---|
| 149 | if (_recent_hosts_length < maxRecentHostsSize && recentHosts.indexOf(host) == -1) { |
|---|
| 150 | recentHosts.push(host); |
|---|
| 151 | } else if (recentHosts.indexOf(host) == -1 && historyCompletions.h.indexOf(host) == -1) { |
|---|
| 152 | historyCompletions.push([host, pageFor[host].title]); |
|---|
| 153 | historyCompletions.h.push(host); |
|---|
| 154 | } |
|---|
| 155 | } |
|---|
| 156 | if (id != '' && !id.replace('/', '').match(new RegExp('^(?:' + ignoreIds.join('|') + ')$'))) { // Wedata の拒否リストに入っていなかったら |
|---|
| 157 | var index = ids.indexOf(id); |
|---|
| 158 | if (index == -1) { |
|---|
| 159 | ids.unshift(id); |
|---|
| 160 | } else { |
|---|
| 161 | ids.splice(index, 1); |
|---|
| 162 | ids.unshift(id); |
|---|
| 163 | } |
|---|
| 164 | if (isFirst || isIncreased || isUpdated) { // 初回か,pageFor に何か追加されたか,pageFor が更新されたら |
|---|
| 165 | isIncreased = false; |
|---|
| 166 | isUpdated = false; |
|---|
| 167 | if (page.title != '' && title.get(host, id) != page.title) { |
|---|
| 168 | title.set(host, id, page.title); |
|---|
| 169 | } |
|---|
| 170 | } |
|---|
| 171 | } |
|---|
| 172 | } |
|---|
| 173 | root.containerOpen = false; |
|---|
| 174 | isFirst = false; |
|---|
| 175 | } |
|---|
| 176 | collectLog(); |
|---|
| 177 | |
|---|
| 178 | function loadWedata() { |
|---|
| 179 | var url = 'http://wedata.net/databases/access_hatena_ignore_id/items.json'; |
|---|
| 180 | var req = XMLHttpRequest(); |
|---|
| 181 | |
|---|
| 182 | req.open('GET', url, true); |
|---|
| 183 | req.onload = registerIgnoreIds; |
|---|
| 184 | req.onerror = function(e) { liberator.echoerr('Error in access_hatena.js: loadWedata'); }; |
|---|
| 185 | req.send(null); |
|---|
| 186 | } |
|---|
| 187 | |
|---|
| 188 | function registerIgnoreIds(e) { |
|---|
| 189 | var req = this; |
|---|
| 190 | var json = eval(req.responseText); |
|---|
| 191 | for (var i in json) if (json.hasOwnProperty(i)) { |
|---|
| 192 | var id = json[i].data.id; |
|---|
| 193 | if (ignoreIds.indexOf(id) == -1 && id != '') { |
|---|
| 194 | ignoreIds.push(id) |
|---|
| 195 | } |
|---|
| 196 | } |
|---|
| 197 | } |
|---|
| 198 | |
|---|
| 199 | commands.addUserCommand(["accesshatena"], "Access to Hatene Service Quickly", |
|---|
| 200 | function(args) { |
|---|
| 201 | var host = args[0] ? encodeURIComponent(args[0].toString()) : 'www'; |
|---|
| 202 | var id = args[1] ? encodeURIComponent(args[1].toString()).replace('%2F', '/') : ''; |
|---|
| 203 | var uri = 'http://' + host + '.hatena.ne.jp/' + id; |
|---|
| 204 | liberator.open(uri, liberator.CURRENT_TAB); |
|---|
| 205 | lastLocation = ''; |
|---|
| 206 | }, { |
|---|
| 207 | completer: function (context, args) { |
|---|
| 208 | if (args.length == 1) { |
|---|
| 209 | context.title = ["Host", "Service"]; |
|---|
| 210 | if (window.content.location.href.replace(/^https?:\/\//, '') != lastLocation) { // ページ遷移がない場合に何度も collectLog() しないように |
|---|
| 211 | collectLog(); |
|---|
| 212 | lastLocation = window.content.location.href.replace(/https?:\/\//, ''); |
|---|
| 213 | } |
|---|
| 214 | context.completions = [[recentHosts[i], pageFor[recentHosts[i]].title] for (i in recentHosts) if (recentHosts.hasOwnProperty(i))].concat(historyCompletions); |
|---|
| 215 | } else if (args.length == 2) { |
|---|
| 216 | var host = args[0].toString(); |
|---|
| 217 | context.title = ["ID", "Page"]; |
|---|
| 218 | context.completions = [[ids[i], title.get(host, ids[i])] for (i in ids) if (ids.hasOwnProperty(i))]; |
|---|
| 219 | } |
|---|
| 220 | } |
|---|
| 221 | } |
|---|
| 222 | ); |
|---|
| 223 | |
|---|
| 224 | commands.addUserCommand(["accesshatenainit"], "Initialize Access Hatena", |
|---|
| 225 | function() { |
|---|
| 226 | init(); |
|---|
| 227 | liberator.echo('Finish initializing.'); |
|---|
| 228 | } |
|---|
| 229 | ); |
|---|
| 230 | })(); |
|---|
| 231 | // vim:sw=4 ts=4 et: |
|---|