diff options
98 files changed, 7722 insertions, 1587 deletions
diff --git a/PMWriter.js b/PMWriter.js index 5cc7948..a4fc95c 100644 --- a/PMWriter.js +++ b/PMWriter.js @@ -67,7 +67,7 @@ const DOCUMENT_TITLE = 'Vimperator Plugins in CodeRepos'; const CodeRepos = 'http://coderepos.org/share/browser/lang/javascript/vimperator-plugins/trunk/'; const CodeReposBranch = 'http://coderepos.org/share/browser/lang/javascript/vimperator-plugins/branches/'; - const CodeReposFile = 'http://svn.coderepos.org/share/lang/javascript/vimperator-plugins/trunk/'; + const CodeReposFile = 'http://github.com/vimpr/vimperator-plugins/blob/master/'; function Context (file) { this.NAME = file.leafName.replace(/\..*/, '') @@ -1,5 +1,6 @@ /*** BEGIN LICENSE BLOCK {{{ Copyright (c) 2008 suVene<suvene@zeromemory.info> + Copyright (c) 2008-2011 anekos<anekos@snca.net> distributable under the terms of an MIT-style license. http://www.opensource.jp/licenses/mit-license.html @@ -11,10 +12,10 @@ var PLUGIN_INFO = <description>Vimperator plugins library?</description> <description lang="ja">適当なライブラリっぽいものたち。</description> <author mail="suvene@zeromemory.info" homepage="http://zeromemory.sblo.jp/">suVene</author> + <author mail="anekos@snca.net" homepage="http://snca.net/">anekos</author> <license>MIT</license> - <version>0.1.34</version> + <version>0.1.37</version> <minVersion>2.3pre</minVersion> - <maxVersion>3.0</maxVersion> <updateURL>https://github.com/vimpr/vimperator-plugins/raw/master/_libly.js</updateURL> <detail><![CDATA[ == Objects == @@ -149,11 +150,11 @@ Request(url, headers, options): addEventListener(name, func): イベントリスナを登録する。 name: - 'onSuccess': + 'success': 成功時 - 'onFailure': + 'failure': 失敗を表すステータスコードが返ってきた時 - 'onException': + 'exception': 例外発生時 func: イベント発火時の処理 @@ -195,6 +196,11 @@ clearCache: liberator.plugins.libly = {}; var libly = liberator.plugins.libly; +// XXX for backward compatibillity +function fixEventName(name) { + return name.replace(/^on/, '').toLowerCase(); +} + libly.$U = {//{{{ // Logger {{{ getLogger: function(prefix) { @@ -239,8 +245,8 @@ libly.$U = {//{{{ let pluginPath; Error('hoge').stack.split(/\n/).some( function (s) - let (m = s.match(/^\(\)@chrome:\/\/liberator\/content\/liberator\.js -> (.+):\d+$/)) - (m && (pluginPath = m[1])) + let (m = s.match(/^(?:\(\))?@chrome:\/\/liberator\/content\/liberator\.js -> (.+):\d+$/)) + (m && (pluginPath = m[1].replace(/\?.*$/, ''))) ); return pluginPath; } @@ -275,7 +281,7 @@ libly.$U = {//{{{ let self = this, args = arguments; return func.call(self, function (_args) original.apply(self, _args || args), args); }; - libly.$U.extend(current, {original: original.original || original, restore: restore}); + libly.$U.extend(current, {original: original && original.original || original, restore: restore}); return libly.$U.extend({ original: original, current: current, @@ -516,14 +522,16 @@ libly.Request.prototype = { this.observers = {}; }, addEventListener: function(name, func) { + name = fixEventName(name); try { if (typeof this.observers[name] == 'undefined') this.observers[name] = []; this.observers[name].push(func); } catch (e) { - if (!this.fireEvent('onException', new libly.Response(this), e)) throw e; + if (!this.fireEvent('exception', new libly.Response(this), e)) throw e; } }, fireEvent: function(name, args, asynchronous) { + name = fixEventName(name); if (!(this.observers[name] instanceof Array)) return false; this.observers[name].forEach(function(event) { if (asynchronous) { @@ -566,7 +574,7 @@ libly.Request.prototype = { this._onStateChange(); } catch (e) { - if (!this.fireEvent('onException', new libly.Response(this), e)) throw e; + if (!this.fireEvent('exception', new libly.Response(this), e)) throw e; } }, _onStateChange: function() { @@ -591,9 +599,9 @@ libly.Request.prototype = { libly.Request.requestCount--; try { this._complete = true; - this.fireEvent('on' + (this.isSuccess() ? 'Success' : 'Failure'), res, this.options.asynchronous); + this.fireEvent(this.isSuccess() ? 'success' : 'failure', res, this.options.asynchronous); } catch (e) { - if (!this.fireEvent('onException', res, e)) throw e; + if (!this.fireEvent('exception', res, e)) throw e; } } }, @@ -641,6 +649,7 @@ libly.Response.prototype = { this.status = this.getStatus(); this.statusText = this.getStatusText(); this.responseText = (this.transport.responseText == null) ? '' : this.transport.responseText; + this.responseXML = this.transport.responseXML; } this.doc = null; @@ -681,8 +690,8 @@ libly.Wedata.prototype = { getItems: function(expire, itemCallback, finalCallback) { var logger = this.logger; - var STORE_KEY = 'plugins-libly-wedata-' + this.dbname + '-items'; - var store = storage.newMap(STORE_KEY, true); + var STORE_KEY = 'plugins-libly-wedata-' + encodeURIComponent(this.dbname) + '-items'; + var store = storage.newMap(STORE_KEY, {store: true}); var cache = store && store.get('data'); if (store && cache && new Date(store.get('expire')) > new Date()) { @@ -708,8 +717,8 @@ libly.Wedata.prototype = { } } - var req = new libly.Request(this.HOST_NAME + 'databases/' + this.dbname + '/items.json'); - req.addEventListener('onSuccess', libly.$U.bind(this, function(res) { + var req = new libly.Request(this.HOST_NAME + 'databases/' + encodeURIComponent(this.dbname) + '/items.json'); + req.addEventListener('success', libly.$U.bind(this, function(res) { var text = res.responseText; if (!text) { errDispatcher('response is null.', cache); @@ -728,8 +737,8 @@ libly.Wedata.prototype = { if (typeof finalCallback == 'function') finalCallback(true, json); })); - req.addEventListener('onFailure', function() errDispatcher('onFailure', cache)); - req.addEventListener('onException', function() errDispatcher('onException', cache)); + req.addEventListener('failure', function() errDispatcher('onFailure', cache)); + req.addEventListener('exception', function() errDispatcher('onException', cache)); req.get(); } }; diff --git a/access_hatena.js b/access_hatena.js index 3cb56ce..b291b02 100644 --- a/access_hatena.js +++ b/access_hatena.js @@ -133,7 +133,7 @@ map ; :accesshatena root.containerOpen = true; for (var i = 0, length = root.childCount; i < length; i++) { var page = root.getChild(i); - page.uri.match('^https?://([a-zA-Z0-9.]+)\\.hatena\\.ne\\.jp/([a-zA-Z][a-zA-Z0-9_-]{1,30}[a-zA-Z0-9]/?)?'); + page.uri.match('^https?://([a-zA-Z0-9.]+)\\.hatena\\.ne\\.jp/([a-zA-Z][a-zA-Z0-9_-]{1,30}[a-zA-Z0-9](?:\\+[a-zA-Z0-9_-]{1,30})?/?)?'); var host = RegExp.$1; var id = RegExp.$2; var _recent_hosts_length = recentHosts.length; @@ -218,7 +218,8 @@ map ; :accesshatena } else if (args.length == 2) { var host = args[0].toString(); context.title = ["ID", "Page"]; - context.completions = [[ids[i], title.get(host, ids[i])] for (i in ids) if (ids.hasOwnProperty(i))]; + var _completions = [[ids[i], title.get(host, ids[i])] for (i in ids) if (ids.hasOwnProperty(i))]; + context.completions = host != 'd' ? _completions.filter(function(i){ return !/\+/.test(i[0]) }) : _completions; } } } diff --git a/addhatebu.js b/addhatebu.js index b45be21..7fff76e 100644 --- a/addhatebu.js +++ b/addhatebu.js @@ -7,7 +7,7 @@ var INFO = <author email="mitsugu.oyama@gmail.com">Mitsugu Oyama</author> <license href="http://opensource.org/licenses/mit-license.php">MIT</license> <project name="Vimperator" minVersion="2.3"/> - <p>Toggle login box of <link topic="http://www.pixiv.net/">pixiv</link> by this plugin. </p> + <p>Add contents to Hatena Bookmarks. </p> <item> <tags>'addhatebu'</tags> <spec>:addhatebu</spec> @@ -60,7 +60,7 @@ let PLUGIN_INFO = let INFO = <> <plugin name="alias" version="1.0.0" - href="http://svn.coderepos.org/share/lang/javascript/vimperator-plugins/trunk/alias.js" + href="http://github.com/vimpr/vimperator-plugins/blob/master/alias.js" summary="Define the alias for a command." lang="en-US" xmlns="http://vimperator.org/namespaces/liberator"> @@ -84,7 +84,7 @@ let INFO = </ex></code> </plugin> <plugin name="alias" version="1.0.0" - href="http://svn.coderepos.org/share/lang/javascript/vimperator-plugins/trunk/alias.js" + href="http://github.com/vimpr/vimperator-plugins/blob/master/alias.js" summary="コマンドに別名(エイリアス|alias)をつける。" lang="ja" xmlns="http://vimperator.org/namespaces/liberator"> @@ -113,6 +113,15 @@ let INFO = (function () { + // FIXME "vim[perator]" に対応してない + function removeOld (name) { + let cmd = commands.get(name); + if (!cmd) + return; + if (cmd.specs instanceof Array) + cmd.specs = cmd.specs.filter(function (it) it !== name); + } + commands.addUserCommand( ['alias'], 'Define the alias for a command.', @@ -123,6 +132,7 @@ let INFO = if (!cmd) return liberator.echoerr('Not found command with: ' + oldName); + removeOld(newName); cmd.specs.push(newName); // XXX 必要でない気もする。実際コマンドの検索には要らない。 Command.prototype.init.call(cmd, cmd.specs, cmd.description, cmd.action); diff --git a/amebanow.js b/amebanow.js index 966aee1..3f06aa1 100644 --- a/amebanow.js +++ b/amebanow.js @@ -60,7 +60,7 @@ let PLUGIN_INFO = // INFO {{{ let INFO = <plugin name="AmebaNow" version="1.0.4" - href="http://svn.coderepos.org/share/lang/javascript/vimperator-plugins/trunk/amebanow.js" + href="http://github.com/vimpr/vimperator-plugins/blob/master/amebanow.js" summary="AmebaNau" xmlns="http://vimperator.org/namespaces/liberator"> <author email="anekos@snca.net">anekos</author> diff --git a/appendAnchor.js b/appendAnchor.js index 77496e8..8ca02a2 100644 --- a/appendAnchor.js +++ b/appendAnchor.js @@ -73,7 +73,7 @@ let PLUGIN_INFO = // build URL let scheme = RegExp.$1, host = RegExp.$2, lastMatch = RegExp.lastMatch; - if (/^ttps?$/(scheme)) scheme = 'h' + scheme; + if (/^ttps?$/.test(scheme)) scheme = 'h' + scheme; let href = scheme + '://' + host; // reset range @@ -0,0 +1,165 @@ +// INFO {{{
+let INFO =
+<>
+ <plugin name="atnd" version="0.1.0"
+ href="http://github.com/vimpr/vimperator-plugins/blob/master/atnd.js"
+ summary="Atndのイベント検索"
+ lang="ja"
+ xmlns="http://vimperator.org/namespaces/liberator">
+ <author email="ninja.tottori@gmail.com">ninjatottori</author>
+ <license>New BSD License</license>
+ <project name="Vimperator" minVersion="3.0"/>
+ <p>Event search on Atnd</p>
+ <item>
+ <tags>:atnd</tags>
+ <description>
+ <p>search on atnd</p>
+ <p>:atnd keyword で補完リストにkeywordで取得したイベントを補完します。<br />
+ 補完リストを使わず、:atnd keyword <Enter> するとMOWに表示します
+ </p>
+ </description>
+ </item>
+ </plugin>
+</>;
+// }}}
+
+
+(function(){
+ commands.addUserCommand(['atnd','atend'], 'atnd.org',
+ function(args){
+ if(args.length == 1) {
+ Atnd.get_events(args);
+ } else {
+ liberator.open(args[args.length-1], liberator.NEW_BACKGROUND_TAB);
+ }
+ },
+ {
+ options : [],
+ completer : listCompleter,
+ },
+ true
+ );
+
+ function listCompleter(context,args){ // {{{
+
+ context.title = ["url","title"]
+ context.filters = [CompletionContext.Filter.textDescription];
+ context.compare = void 0;
+ context.anchored = false;
+ context.incomplete = true;
+ Atnd.get_event2(context,args);
+
+ } //}}}
+
+ let Atnd = { //{{{
+
+ base_url : "http://api.atnd.org/",
+ get_events : function(args){ //{{{
+
+ let keyword = args.join(",");
+ if(!keyword) return;
+ let count = (liberator.globalVariables.atnd_get_count) ? liberator.globalVariables.atnd_get_count : 10 ;
+
+ let req = new libly.Request(
+ this.base_url + "events/?keyword=" + keyword + "&format=json&count=" + count , //url
+ null, //headers
+ { // options
+ asynchronous:true,
+ }
+ );
+
+ req.addEventListener("success",function(data){
+ let res = libly.$U.evalJson(data.responseText);
+ if(res.results_returned == 0){
+ liberator.echo("keyword: " + keyword + " was no result");
+ return;
+ }
+
+ let res_events = res.events.filter(isEnded,new Date())
+ let html,div = "";
+ html = <style type="text/css"><![CDATA[
+ div.head{font:medium Arial;font-weight:bold;text-decoration:underline;color:#EA1F00;padding-left:0.3em;padding-bottom:0.3em;}
+ div.result{padding:0.3em 2em;}
+ .result a{color:#7fff00;}
+ .result a:hover{color:#ff1493;}
+ ]]></style> +
+ <div class="head" >
+ {res_events.length + "/" + res.results_available + " events matched(include ended events)"}
+ </div>;
+ for (let i = 0 ; i < res_events.length ; i++) {
+ let r = res_events[i]
+ div += <div class="result">
+ <a href={r.event_url} >
+ {r.title}
+ {" - " + r.started_at}
+ </a>
+ {" - " + r.catch}
+ {" - " + r.address}
+ </div>;
+ };
+ liberator.echo(html + div)
+
+ function isEnded(elements,index,array){
+ return ((this - new Date(elements.started_at)) < 0)
+ }
+ });
+
+ req.addEventListener("failure",function(data){
+ liberator.echoerr(data.statusText);
+ });
+
+ req.get();
+
+ }, //}}}
+ get_event2 : function(context,args){ //{{{
+ let keyword = args.join(",");
+ //if(!keyword) return;
+ let count = (liberator.globalVariables.atnd_get_count) ? liberator.globalVariables.atnd_get_count : 10 ;
+
+
+ let req = new libly.Request(
+ this.base_url + "events/?keyword=" + keyword + "&format=json&count=" + count , //url
+ null, //headers
+ { // options
+ asynchronous:true,
+ }
+ );
+ req.addEventListener("success",function(data){
+ let res = libly.$U.evalJson(data.responseText);
+ if(res.results_returned == 0){
+ return ;
+ }
+
+ let event_data = [];
+ let res_events = res.events.filter(isEnded,new Date());
+ for (let i = 0 ; i < res_events.length ; i++) {
+ let r = res_events[i];
+ event_data.push([r.event_url,r.title + " " + r.started_at + " " + r.catch + " " + r.address]);
+ };
+ context.incomplete = false;
+ context.completions = event_data;
+
+ function isEnded(elements,index,array){
+ return ((this - new Date(elements.started_at)) < 0)
+ }
+ });
+
+ req.addEventListener("failure",function(data){
+ context.incomplete = false;
+ liberator.echoerr(data.statusText);
+ });
+ req.get();
+ }, // }}}
+
+
+ } // }}}
+
+
+ // for debug
+ function e(v,c){ // {{{
+ if(c) util.copyToClipboard(v);
+ liberator.log(v,-1)
+ } // }}}
+
+})();
+
diff --git a/auto_detect_link.js b/auto_detect_link.js index c2083af..4f11cc8 100644 --- a/auto_detect_link.js +++ b/auto_detect_link.js @@ -3,10 +3,9 @@ var PLUGIN_INFO = <name>Auto Detect Link</name> <description>Find (next|previous) link, and jump.</description> <description lang="ja">(次|前)っぽいページへのリンクを探してジャンプ</description> - <version>1.8.2</version> + <version>1.8.3</version> <author mail="anekos@snca.net" homepage="http://d.hatena.ne.jp/nokturnalmortum/">anekos</author> <minVersion>2.0pre</minVersion> - <maxVersion>2.4</maxVersion> <updateURL>https://github.com/vimpr/vimperator-plugins/raw/master/auto_detect_link.js</updateURL> <license document="http://creativecommons.org/licenses/by-sa/3.0/"> Creative Commons Attribution-Share Alike 3.0 Unported @@ -182,7 +181,7 @@ var PLUGIN_INFO = // 要素をクリックする function clickElement (elem) - buffer.followLink(elem); + buffer.followLink(elem, liberator.CURRENT_TAB); // 開いたURIなどの表示 diff --git a/auto_source.js b/auto_source.js index b1ff95f..48dffb8 100644 --- a/auto_source.js +++ b/auto_source.js @@ -68,7 +68,7 @@ let PLUGIN_INFO = </VimperatorPlugin>; let INFO = <plugin name="Auto Source" version="1.6.0" - href="http://svn.coderepos.org/share/lang/javascript/vimperator-plugins/trunk/auto_source.js" + href="http://github.com/vimpr/vimperator-plugins/blob/master/auto_source.js" summary="Sourcing automatically when the specified file is modified." xmlns="http://vimperator.org/namespaces/liberator"> <author email="anekos@snca.net">anekos</author> @@ -135,7 +135,7 @@ let INFO = function source (filepath) { io.source(filepath); - if (/\.js$/(filepath)) { + if (/\.js$/.test(filepath)) { let ctx = liberator.plugins.contexts[filepath]; if (ctx) { if (typeof liberator.plugins[ctx.NAME] === 'undefined') @@ -38,13 +38,12 @@ let PLUGIN_INFO = <name>bit.ly</name> <description>Get short alias by bit.ly and j.mp</description> <description lang="ja">bit.ly や j.mp で短縮URLを得る</description> - <version>2.1.0</version> + <version>2.1.2</version> <author mail="anekos@snca.net" homepage="http://d.hatena.ne.jp/nokturnalmortum/">anekos</author> <license>new BSD License (Please read the source code comments of this plugin)</license> <license lang="ja">修正BSDライセンス (ソースコードのコメントを参照してください)</license> <updateURL>https://github.com/vimpr/vimperator-plugins/raw/master/bitly.js</updateURL> <minVersion>2.0pre</minVersion> - <maxVersion>2.4</maxVersion> <detail><![CDATA[ == Commands == :bitly [<URL>] @@ -88,7 +87,10 @@ let PLUGIN_INFO = function (apiKey) { let login = LoginInfo(HostName, null, Realm, username, apiKey, '', ''); PasswordManager.addLogin(login); - callback(login); + callback(); + }, + { + default: let (e = content.document.querySelector('#bitly_api_key')) (e ? e.value : '') } ); } @@ -96,13 +98,17 @@ let PLUGIN_INFO = } function shorten (url, domain, command, callback) { + function fixResponseText (s) + s.trim(); + + liberator.log(arguments); function get () { let req = new XMLHttpRequest(); req.onreadystatechange = function () { if (req.readyState != 4) return; if (req.status == 200) - return callback && callback(req.responseText, req); + return callback && callback(fixResponseText(req.responseText), req); else return liberator.echoerr(req.statusText); }; @@ -115,7 +121,7 @@ let PLUGIN_INFO = 'format=txt'; req.open('GET', requestUri, callback); req.send(null); - return !callback && req.responseText.trim(); + return !callback && fixResponseText(req.responseText); } if (!url) @@ -127,7 +133,8 @@ let PLUGIN_INFO = return get(); if (callback) { - setupAuth(get); + let args = Array.slice(arguments); + setupAuth(function () shorten.apply(this, args)); } else { liberator.echoerr('Not found API Key!! Try :bitly command, before use.'); } diff --git a/buffer-multiple-hints.js b/buffer-multiple-hints.js new file mode 100644 index 0000000..076a4a5 --- /dev/null +++ b/buffer-multiple-hints.js @@ -0,0 +1,89 @@ +/* NEW BSD LICENSE {{{ +Copyright (c) 2011, anekos. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. + + +################################################################################### +# http://sourceforge.jp/projects/opensource/wiki/licenses%2Fnew_BSD_license # +# に参考になる日本語訳がありますが、有効なのは上記英文となります。 # +################################################################################### + +}}} */ + +// INFO {{{ +let INFO = +<> + <plugin name="BufferMultipeHints" version="1.0.0" + href="http://vimpr.github.com/" + summary="Open multiple hints in tabs (;F) at the same time." + lang="en-US" + xmlns="http://vimperator.org/namespaces/liberator"> + <author email="anekos@snca.net">anekos</author> + <license>New BSD License</license> + <project name="Vimperator" minVersion="3.0"/> + <require type="plugin">_libly.js</require> + <p>This plugin requires _libly.js.</p> + </plugin> + <plugin name="BufferMultipeHints" version="1.0.0" + href="http://vimpr.github.com/" + summary=";F のヒントで同時にタブを開くようにします。" + lang="ja" + xmlns="http://vimperator.org/namespaces/liberator"> + <author email="anekos@snca.net">anekos</author> + <license>New BSD License</license> + <project name="Vimperator" minVersion="3.0"/> + <require type="plugin">_libly.js</require> + <p>This plugin requires _libly.js.</p> + </plugin> +</>; +// }}} + + +(function () { + + if (!plugins.libly) + return liberator.echoerr(__context__.NAME + ': Please install _libly.js.'); + + let scheduled = []; + + plugins.libly.$U.around( + events, + 'onEscape', + function (next) { + try { + if (scheduled.length) + scheduled.forEach(function (elem) buffer.followLink(elem, liberator.NEW_BACKGROUND_TAB)); + } finally { + scheduled = []; + return next(); + } + } + ); + + hints._hintModes['F'].action = function (elem) (scheduled.push(elem), hints.show("F")); + +})(); + +// vim:sw=2 ts=2 et si fdm=marker: diff --git a/caret-hint.js b/caret-hint.js index ba6772e..60c6ec0 100644 --- a/caret-hint.js +++ b/caret-hint.js @@ -288,7 +288,7 @@ let INFO = // Vimperator 3.0 までと 3.1 以降に両対応 const [CaretKey, VisualKey] = - /caret/(mappings.getDefault(modes.NORMAL, 'i').description) ? ['i', 'v'] : ['c', 'v']; + /caret/.exec(mappings.getDefault(modes.NORMAL, 'i').description) ? ['i', 'v'] : ['c', 'v']; function moveCaret (elem, head, select) { let doc = elem.ownerDocument; diff --git a/chaika.js b/chaika.js new file mode 100644 index 0000000..9855b5c --- /dev/null +++ b/chaika.js @@ -0,0 +1,128 @@ +/* NEW BSD LICENSE {{{ +Copyright (c) 2011, anekos. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. + + +################################################################################### +# http://sourceforge.jp/projects/opensource/wiki/licenses%2Fnew_BSD_license # +# に参考になる日本語訳がありますが、有効なのは上記英文となります。 # +################################################################################### + +}}} */ + +// INFO {{{ +let INFO = +<> + <plugin name="Chaika-Controller" version="1.0.0" + href="http://vimpr.github.com/" + summary="for Chika" + lang="en-US" + xmlns="http://vimperator.org/namespaces/liberator"> + <author email="anekos@snca.net">anekos</author> + <license>New BSD License</license> + <project name="Vimperator" minVersion="3.0"/> + <p></p> + <item> + <tags>:chaika deletelog</tags> + <spec>:chaika deletelog</spec> + <description><p> + Delete current thread log. + </p></description> + </item> + <item> + <tags>:chaika open</tags> + <spec>:chaika open</spec> + <description><p> + open current page in chaika. + </p></description> + </item> + </plugin> +</>; +// }}} + +(function () { + const Chaika = {}; + Components.utils.import("resource://chaika-modules/ChaikaThread.js", Chaika); + Components.utils.import("resource://chaika-modules/Chaika2chViewer.js", Chaika); + + function deleteCurrentThreadLog () { + let currentURI = getBrowser().currentURI.QueryInterface(Ci.nsIURL); + + if (currentURI.host != "localhost" && currentURI.host != "127.0.0.1") + throw "Not chaika tab page"; + + if (currentURI.path.substring(0, 8) != "/thread/") + throw "Not chaika tab page"; + + liberator.log(currentURI); + let URI = util.newURI(currentURI.path.substring(8)); + + let thread = new Chaika.ChaikaThread(URI); + thread.deteleThreadData(); + } + + commands.addUserCommand( + ['chaika'], + 'Chaika control', + function (args) {}, + { + subCommands: [ + new Command( + ['o', 'open'], + 'Open current page', + function (args) { + liberator.open('http://127.0.0.1:8823/thread/' + buffer.URL); + } + ), + new Command( + ['deletelog'], + 'Delete current thread log', + function (args) { + try { + deleteCurrentThreadLog(); + liberator.echo('Current thread log has been deleted.'); + } catch (ex) { + liberator.echoerr(ex); + } + } + ), + new Command( + ['auth'], + 'Maru auth(?)', + function (args) { + Chaika.Chaika2chViewer.auth(); + }, + { + argCount: '0' + } + ) + ] + }, + true + ); +})(); + +// vim:sw=2 ts=2 et si fdm=marker: + diff --git a/commandBookmarklet.js b/commandBookmarklet.js index 3375dce..811691c 100644 --- a/commandBookmarklet.js +++ b/commandBookmarklet.js @@ -75,8 +75,11 @@ if (!items.length) { } items.forEach(function (item) { + let name = toValidCommandName(item.title); + if (commands.get(name)) + return; commands.addUserCommand( - [toValidCommandName(item.title)], + [name], 'bookmarklet : ' + item.title, function () evalScript(item.url), { shortHelp: 'Bookmarklet' }, @@ -1,6 +1,6 @@ var INFO = <plugin name="copy" version="0.7.6" - href="http://svn.coderepos.org/share/lang/javascript/vimperator-plugins/trunk/copy.js" + href="http://github.com/vimpr/vimperator-plugins/blob/master/copy.js" summary="copy strings from the template (like CopyURL+)" xmlns="http://vimperator.org/namespaces/liberator"> <author email="teramako@gmail.com">teramako</author> @@ -265,7 +265,7 @@ function wedataRegister(item){ return function(value, value2){ var STORE_KEY = 'plugins-copy-ok-func'; - var store = storage.newMap(STORE_KEY, true); + var store = storage.newMap(STORE_KEY, {store: true}); var check = store.get(item.label); var ans; @@ -390,7 +390,7 @@ if (liberator.globalVariables.copy_use_wedata){ copy_templates.forEach(function(item) excludeLabelsMap[item.label] = item.value); if (liberator.globalVariables.copy_wedata_exclude_labels) liberator.globalVariables.copy_wedata_exclude_labels.forEach(function(item) excludeLabelsMap[item] = 1); - var wedata = new libly.Wedata("vimp%20copy"); + var wedata = new libly.Wedata("vimp copy"); wedata.getItems(24 * 60 * 60 * 1000, wedataRegister); } loadWedata(); diff --git a/delicious_search.js b/delicious_search.js index 3f431ca..fb5d2c9 100644 --- a/delicious_search.js +++ b/delicious_search.js @@ -3,6 +3,7 @@ let PLUGIN_INFO = <name>{NAME}</name> <description>search DeliciousBookmark and that completer</description> <require type="extension" id="{2fa4ed95-0317-4c6a-a74c-5f3e3912c1f9}">Delicious Bookmarks</require> +<require type="extension" id="delicious@vjkarunapg.com">Delicious Extension</require> <author mail="teramako@gmail.com" homepage="http://vimperator.g.hatena.ne.jp/teramako/">teramako</author> <version>0.3</version> <minVersion>2.0pre</minVersion> @@ -43,17 +44,12 @@ set go-=D liberator.plugins.delicious = (function(){ -let uuid = PLUGIN_INFO.require[0].@id.toString(); +const YDELS_CLASS = "@yahoo.com/nsYDelLocalStore;1"; let ydls = null; -if ( typeof Application.extensions === "object" && Application.extensions.has(uuid) && Application.extensions.get(uuid).enabled ){ - ydls = Cc["@yahoo.com/nsYDelLocalStore;1"].getService(Ci.nsIYDelLocalStore); -} -else if ( typeof Application.getExtensions === "function" ) { - Application.getExtensions(function(extensions) { - if ( extensions.has(uuid) && extensions.get(uuid).enabled ) { - ydls = Cc["@yahoo.com/nsYDelLocalStore;1"].getService(Ci.nsIYDelLocalStore); - } - }); +if (YDELS_CLASS in Cc) { + ydls = Cc[YDELS_CLASS].getService(Ci.nsIYDelLocalStore); +} else { + return; } const ss = Cc["@mozilla.org/storage/service;1"].getService(Ci.mozIStorageService); @@ -284,7 +280,7 @@ let self = { }; self.init(); liberator.registerObserver("shutdown", self.close); -config.guioptions['D'] = ['Delicious Toolbar',['ybToolbar']]; +config.toolbars.delicious = [['ybToolbar'], 'Delicious Toolbar']; completion.addUrlCompleter("D", "Delicious Bookmarks", self.urlCompleter); return self; })(); diff --git a/direct_bookmark.js b/direct_bookmark.js index 1d3688b..90cf3bf 100644 --- a/direct_bookmark.js +++ b/direct_bookmark.js @@ -1,13 +1,11 @@ -// Last Change: 21-Jan-2010. Jan 2008
var PLUGIN_INFO =
<VimperatorPlugin>
<name>{NAME}</name>
<description>Direct Post to Social Bookmarks</description>
<author mail="trapezoid.g@gmail.com" homepage="http://unsigned.g.hatena.ne.jp/Trapezoid">Trapezoid</author>
- <version>0.15</version>
+ <version>0.16.1</version>
<license>GPL</license>
<minVersion>2.0pre</minVersion>
- <maxVersion>2.2</maxVersion>
<updateURL>https://github.com/vimpr/vimperator-plugins/raw/master/direct_bookmark.js</updateURL>
<detail><![CDATA[
Social Bookmark direct add script for Vimperator 2.2
@@ -28,6 +26,7 @@ for Migemo search: require XUL/Migemo Extension 'h': Hatena Bookmark
'd': del.icio.us
'l': livedoor clip
+ 'g': Google Bookmarks
'p': Places (Firefox bookmarks)
Usage: let g:direct_sbm_use_services_by_tag = "hdl"
||<
@@ -388,6 +387,28 @@ for Migemo search: require XUL/Migemo Extension });
return hatena_tags;
},
+ userTags:function(url, results){
+ var url = 'http://b.hatena.ne.jp/entry/jsonlite/?url=' + encodeURIComponent(url)
+
+ return Deferred.http({
+ method: "get",
+ url: url,
+ }).next(function(xhr){
+ if(xhr.status != 200)
+ return;
+ let json = JSON.parse(xhr.responseText);
+ if (!json)
+ return;
+ let tags = json.bookmarks.map(function(it) it.tags);
+ tags = tags.filter(function(it) it.length);
+ if (!tags.length)
+ return;
+ tags = Array.concat.apply([], tags);
+ tags = tags.map(String.trim);
+ tags = util.Array.uniq(tags);
+ results.push(tags);
+ });
+ },
icon:function(url){
return '<img src="http://b.hatena.ne.jp/entry/image/' + url + '" style="vertical-align: middle;" />';
},
@@ -399,7 +420,7 @@ for Migemo search: require XUL/Migemo Extension entryPage:'http://del.icio.us/url/%URL::MD5%',
poster:function(user,password,url,title,comment,tags){
var request_url = 'https://api.del.icio.us/v1/posts/add?' + [
- ['url', url], ['description', title], ['extended', comment], ['tags', tags.join(' ')]
+ ['url', url], ['description', title], ['extended', comment], ['tags', tags.join(',')]
].map(function(p) p[0] + '=' + encodeURIComponent(p[1])).join('&');
return Deferred.http({
method: "get",
@@ -411,16 +432,15 @@ for Migemo search: require XUL/Migemo Extension });
},
tags:function(user,password){
- const feed_url = 'http://feeds.delicious.com/feeds/json/tags/';
- var returnValue = [];
+ const url = 'https://api.del.icio.us/v1/tags/get?';
var xhr = new XMLHttpRequest();
- xhr.open("GET", feed_url + user + "?raw", false, user, password);
+ xhr.open("GET", url, false, user, password);
xhr.send(null);
- var tags = evalFunc("(" + xhr.responseText + ")");
- for(var tag in tags)
- returnValue.push(tag);
- return returnValue;
+ return [
+ e.getAttribute("tag")
+ for ([, e] in Iterator(Array.slice(xhr.responseXML.querySelectorAll('tag'))))
+ ];
},
icon:function(url){
var url = liberator.modules.buffer.URL;
@@ -472,6 +492,8 @@ for Migemo search: require XUL/Migemo Extension var mypage_html = parseHTML(xhr.responseText);
var tags = getElementsByXPath("id(\"tag_list\")/div/span",mypage_html);
+ if (!tags)
+ return [];
tags.forEach(function(tag){
ldc_tags.push(tag.textContent);
@@ -490,7 +512,7 @@ for Migemo search: require XUL/Migemo Extension poster:function(user,password,url,title,comment,tags){
var request_url = 'http://www.google.com/bookmarks/mark';
var params = [
- ['bkmk', url], ['title', title], ['labels', tags.join(',')]
+ ['bkmk', url], ['title', title], ['labels', tags.join(',')], ['annotation', comment]
].map(function(p) p[0] + '=' + encodeURIComponent(p[1])).join('&');
return Deferred.http({
method: "post",
@@ -503,7 +525,24 @@ for Migemo search: require XUL/Migemo Extension if(xhr.status != 200) throw "Google Bookmarks: failed";
});
},
- tags:function(user,password) [],
+ tags:function(user,password){
+ return [];
+
+ // FIXME: NOT WORKS
+ //
+ // var returnValue = [];
+ // var xhr = new XMLHttpRequest();
+ // xhr.open("GET", "https://www.google.com/bookmarks", false, user, password);
+ // xhr.send(null);
+
+ // var html = parseHTML(xhr.responseText);
+ // var tags = getElementsByXPath('id("sidenav")/div/ul/li/a[count(*)=1]/text()',html);
+
+ // tags.forEach(function(tag){
+ // returnValue.push(tag.textContent.match(/\S+/));
+ // });
+ // return returnValue;
+ },
},
'f': {
description:'foves',
@@ -556,9 +595,10 @@ for Migemo search: require XUL/Migemo Extension Application.bookmarks.tags.children.map(function(x) x.title),
},
};
- liberator.plugins.direct_bookmark = { services: services, tags: [] };
+ __context__.services = services;
+ __context__.tags = [];
- function getTagsAsync(arg){
+ function getTagsAsync(onComplete){
var d,first;
d = first = Deferred();
@@ -571,22 +611,34 @@ for Migemo search: require XUL/Migemo Extension return t.concat(tags);
});
});
- d.next(function(tags){liberator.plugins.direct_bookmark.tags = tags.filter(function(e,i,a) a.indexOf(e) == i).sort()})
- .error(function(e){liberator.echoerr("direct_bookmark.js: Exception throwed! " + e)});
+ d.next(function(tags){
+ tags = tags.filter(function(e,i,a) a.indexOf(e) == i);
+ tags.sort();
+ __context__.tags = tags;
+ onComplete(tags);
+ }).error(function(e){liberator.echoerr(e, null, "direct_bookmark.js: ")});
return first;
}
- function getTags(arg){
- var user,password;
- var tags = [];
+ function getUserTags(url, onComplete){
+ var d = new Deferred();
+ var first = d;
+ var results = [];
+
useServicesByTag.split(/\s*/).forEach(function(service){
- var currentService = services[service] || null;
- [user,password] = currentService.account ? getUserAccount.apply(currentService,currentService.account) : [null, null];
- tags = tags.concat(currentService.tags(user,password));
+ var user, password, currentService = services[service] || null;
+ if (!(currentService && currentService.userTags))
+ return;
+ d = d.next(currentService.userTags(url, results));
+ });
+ d.next(function(){
+ let tags = results.length ? Array.concat.apply([], results) : [];
+ onComplete(tags);
});
- liberator.plugins.direct_bookmark.tags = tags.filter(function(e,i,a) a.indexOf(e) == i).sort();
+
+ first.call([]);
}
liberator.modules.commands.addUserCommand(['btags'],"Update Social Bookmark Tags",
- function(arg){setTimeout(function(){getTagsAsync().call([])},0)}, {});
+ function(arg){setTimeout(function(){getTagsAsync().call([])},0)}, {}, true);
liberator.modules.commands.addUserCommand(['bentry'],"Goto Bookmark Entry Page",
function(args){
var service = args.string || useServicesByPost.split(/\s*/)[0];
@@ -619,7 +671,8 @@ for Migemo search: require XUL/Migemo Extension },{
completer: function(filter)
[0, useServicesByPost.split(/\s*/).map(function(p) [p, services[p].description])]
- }
+ },
+ true
);
liberator.modules.commands.addUserCommand(['bicon'],"Show Bookmark Count as Icon",
function(arg){
@@ -630,14 +683,13 @@ for Migemo search: require XUL/Migemo Extension (currentService.description + ': ' + currentService.icon(url)) : null;
}).join('<br />');
liberator.echo(html, true);
- }, {});
+ }, {}, true);
liberator.modules.commands.addUserCommand(['sbm'],"Post to Social Bookmark",
function(arg){
- var comment = "";
var targetServices = useServicesByPost;
if (arg["-s"]) targetServices = arg["-s"];
- if (arg.length > 0) comment = arg.join(" ");
+ comment = arg.literalArg;
var tags = [];
var re = /\[([^\]]+)\]([^\[].*)?/g;
@@ -680,23 +732,62 @@ for Migemo search: require XUL/Migemo Extension d.error(function(e){liberator.echoerr("direct_bookmark.js: Exception throwed! " + e);liberator.log(e);});
setTimeout(function(){first.call();},0);
},{
- completer: function(context, arg){
- let filter = context.filter;
- var match_result = filter.match(/((?:\[[^\]]*\])*)\[?(.*)/); //[all, commited, now inputting]
- var m = new RegExp(XMigemoCore && isUseMigemo ? "^(" + XMigemoCore.getRegExp(match_result[2]) + ")" : "^" + match_result[2],'i');
- var completionList = [];
- // XXX: completer works asynchronous. thus we shouldn't call getTagsAsync().
- if(liberator.plugins.direct_bookmark.tags.length == 0)
- getTags().call([]);
- context.title = ['Tag','Description'];
- context.advance( match_result[1].length );
- context.completions = [["[" + tag + "]","Tag"]
- for each (tag in liberator.plugins.direct_bookmark.tags) if (m.test(tag) && match_result[1].indexOf('[' + tag + ']') < 0)];
+ literal: 0,
+ completer: let (lastURL, lastUserTags, onComplete, done = true) function(context, arg){
+ function set (context, tags) {
+ let filter = context.filter;
+ var match_result = filter.match(/((?:\[[^\]]*\])*)\[?(.*)/); //[all, commited, now inputting]
+ var m = new RegExp(XMigemoCore && isUseMigemo ? "^(" + XMigemoCore.getRegExp(match_result[2]) + ")" : "^" + match_result[2],'i');
+
+ context.advance( match_result[1].length );
+
+ context.incomplete = false;
+ context.completions =
+ [ ["[" + tag + "]","Tag"]
+ for each (tag in tags)
+ if (m.test(tag) && match_result[1].indexOf('[' + tag + ']') < 0) ];
+ }
+
+ context.fork('UserTags', 0, context, function(context){
+ context.title = ['User Tags', 'User Tags'];
+
+ onComplete = function(tags){
+ done = true;
+ lastUserTags = tags;
+ context.incomplete = false;
+ set(context, tags);
+ };
+
+ if (buffer.URL == lastURL){
+ if (done) {
+ onComplete(lastUserTags);
+ } else {
+ context.incomplete = true;
+ }
+ } else {
+ lastURL = buffer.URL;
+ context.incomplete = true;
+ done = false;
+ getUserTags(buffer.URL, function (tags) onComplete(tags));
+ }
+ });
+
+ context.fork('MyTags', 0, context, function(context, arg){
+ context.title = ['My Tag','Description'];
+
+ if(__context__.tags.length == 0){
+ context.incomplete = true;
+ getTagsAsync(set.bind(null, context)).call([]);
+ } else {
+ set(context, __context__.tags);
+ }
+ });
},
options: [
[['-s','-service'], liberator.modules.commands.OPTION_STRING],
]
- }
+ },
+ true
);
})();
// vim:sw=4 ts=4 et:
diff --git a/edit-vimperator-files.js b/edit-vimperator-files.js index 0ee97fc..690ce54 100644 --- a/edit-vimperator-files.js +++ b/edit-vimperator-files.js @@ -36,7 +36,7 @@ THE POSSIBILITY OF SUCH DAMAGE. let INFO = <> <plugin name="EditVimperatorFile" version="1.2.3" - href="http://svn.coderepos.org/share/lang/javascript/vimperator-plugins/trunk/edit-vimperator-files.js" + href="http://github.com/vimpr/vimperator-plugins/blob/master/edit-vimperator-files.js" summary="Open vimperator files with text-editor." lang="en-US" xmlns="http://vimperator.org/namespaces/liberator"> @@ -51,7 +51,7 @@ let INFO = </item> </plugin> <plugin name="EditVimperatorFile" version="1.2.3" - href="http://svn.coderepos.org/share/lang/javascript/vimperator-plugins/trunk/edit-vimperator-files.js" + href="http://github.com/vimpr/vimperator-plugins/blob/master/edit-vimperator-files.js" summary="Vimperator 関連のファイルをエディタで開く" lang="ja" xmlns="http://vimperator.org/namespaces/liberator"> @@ -99,7 +99,7 @@ let INFO = [ [file.path, dir] for ([, file] in Iterator(io.File(dir).readDirectory(false))) - if (file.isFile() && /^[\._]vimperatorrc|\.(js|vimp|css)$/(file.leafName)) + if (file.isFile() && /^[\._]vimperatorrc|\.(js|vimp|css)$/.test(file.leafName)) ] for ([, dir] in Iterator(dirs)) ]); diff --git a/ego_counter.js b/ego_counter.js index 77c8f36..66a3e03 100644 --- a/ego_counter.js +++ b/ego_counter.js @@ -33,10 +33,9 @@ }; })(); - let myHatebu = document.getElementById('status-bar') - .insertBefore(document.createElement('statusbarpanel'), - document.getElementById('security-button') - .nextSibling); + let myHatebu = document.getElementById('status-bar').appendChild( + document.createElement('statusbarpanel') + ); myHatebu.setAttribute('id', 'my-hatebu-count-icon'); myHatebu.setAttribute('class', 'statusbarpanel-iconic'); update(rotate(true)); @@ -47,3 +46,5 @@ }, false); })() + +// vim: sw=2 ts=2 et: diff --git a/epub-reader.js b/epub-reader.js index 6913c80..394c521 100644 --- a/epub-reader.js +++ b/epub-reader.js @@ -1,5 +1,5 @@ /* NEW BSD LICENSE {{{ -Copyright (c) 2010, anekos. +Copyright (c) 2010-2011, anekos. All rights reserved. Redistribution and use in source and binary forms, with or without modification, @@ -35,7 +35,7 @@ THE POSSIBILITY OF SUCH DAMAGE. // INFO {{{ let INFO = <> - <plugin name="ePub Reader" version="1.1.0" + <plugin name="ePub Reader" version="1.1.1" href="http://vimpr.github.com/" summary="for ePub Reader addon" lang="en-US" @@ -68,7 +68,7 @@ let INFO = </code> </p> </plugin> - <plugin name="ePub Reader" version="1.1.0" + <plugin name="ePub Reader" version="1.1.1" href="http://vimpr.github.com/" summary="for ePub Reader addon" lang="ja" @@ -165,7 +165,7 @@ let INFO = jump: withCompleter( function (index) { - buffer.followLink(getIndexLinks()[index]); + buffer.followLink(getIndexLinks()[index], liberator.CURRENT_TAB); }, function (context, args) { context.compare = void 0; @@ -202,7 +202,7 @@ let INFO = ['epubreader'], 'ePub Reader addon controler', function (args) { - if (!ReaderUrls(buffer.URL)) + if (!ReaderUrls.test(buffer.URL)) return liberator.echoerr('Not in ePub Reader'); let [cmd, num] = args; diff --git a/escape-from-iframe.js b/escape-from-iframe.js new file mode 100644 index 0000000..7eede18 --- /dev/null +++ b/escape-from-iframe.js @@ -0,0 +1,71 @@ +/* NEW BSD LICENSE {{{ +Copyright (c) 2011, anekos. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. + + +################################################################################### +# http://sourceforge.jp/projects/opensource/wiki/licenses%2Fnew_BSD_license # +# に参考になる日本語訳がありますが、有効なのは上記英文となります。 # +################################################################################### + +}}} */ + +// INFO {{{ +let INFO = +<> + <plugin name="Escape from iframe." version="1.0.0" + href="http://vimpr.github.com/" + summary="Escape from iframe (focus to parent window)." + lang="en-US" + xmlns="http://vimperator.org/namespaces/liberator"> + <author email="anekos@snca.net">anekos</author> + <license>New BSD License</license> + <project name="Vimperator" minVersion="3.0"/> + <p>Just install this plugin.</p> + <item> + <tags><![CDATA[<Esc>]]></tags> + <spec><Esc></spec> + <description><p>Escape from iframe (focus to parent window).</p></description> + </item> + </plugin> +</>; +// }}} + +(function () { + //let map = mappings.getDefault(modes.NORMAL, '<ESC>'); + mappings.addUserMap( + [modes.NORMAL], + ['<ESC>'], + 'Focus to parent window', + function() { + if (modes.passNextKey || modes.passAllKeys) + return events.onEscape(); + Buffer.focusedWindow = Buffer.focusedWindow.parent; + }, + {} + ); +})(); + +// vim:sw=2 ts=2 et si fdm=marker: diff --git a/extension-manager.js b/extension-manager.js index be06525..241ddc3 100644 --- a/extension-manager.js +++ b/extension-manager.js @@ -62,7 +62,7 @@ let PLUGIN_INFO = let INFO = <> <plugin name="extension-manager" version="1.1.0" - href="http://svn.coderepos.org/share/lang/javascript/vimperator-plugins/trunk/extension-manager.js" + href="http://github.com/vimpr/vimperator-plugins/blob/master/extension-manager.js" summary="extension manager" lang="en-US" xmlns="http://vimperator.org/namespaces/liberator"> @@ -90,7 +90,7 @@ let INFO = </item> </plugin> <plugin name="extension-manager" version="1.1.0" - href="http://svn.coderepos.org/share/lang/javascript/vimperator-plugins/trunk/extension-manager.js" + href="http://github.com/vimpr/vimperator-plugins/blob/master/extension-manager.js" summary="extension manager" lang="ja" xmlns="http://vimperator.org/namespaces/liberator"> diff --git a/facebook.js b/facebook.js new file mode 100644 index 0000000..9c30146 --- /dev/null +++ b/facebook.js @@ -0,0 +1,882 @@ +// INFO {{{
+let INFO =
+<>
+ <plugin name="facebook" version="0.2.0"
+ href="http://github.com/vimpr/vimperator-plugins/blob/master/facebook.js"
+ summary="[facebook.js] コマンドラインからfacebookを操作するプラグイン"
+ lang="ja"
+ xmlns="http://vimperator.org/namespaces/liberator">
+ <author email="ninja.tottori@gmail.com">ninjatottori</author>
+ <license>MIT License</license>
+ <project name="Vimperator" minVersion="8.0"/>
+ <p>
+ コマンドラインからfacebookをあれこれするプラグインです。<br />
+ </p>
+ <h3 tag="facebook-setup">Setup(初期設定)</h3>
+ <item>
+ <tags>:facebook -getAuth</tags>
+ <tags>:facebook -setAccessToken</tags>
+ <spec>:fa<oa>cebook</oa> <a>-getAuth</a></spec>
+ <spec>:fa<oa>cebook</oa> <a>-setAccessToken</a></spec>
+ <description>
+ <p>facebookアプリの認証を行います。
+ facebookにログインした状態で :fa -getAuth するとfacebookのアプリ認証ページに飛びます。
+ 認証ページで承認を行うと結果ページが開きますので、そのままの状態で :fa -setAccessToken して下さい。
+ 一度設定すれば以後は不要です。(自動的にサブコマンドから消えます)
+ </p>
+ </description>
+ </item>
+ <item>
+ <tags>:facebook resetoauth</tags>
+ <spec>:fa<oa>cebook</oa> <a>resetoauth</a></spec>
+ <description>
+ <p>OAuthの認証を再設定します。
+ 実行するとキャッシュされてるアクセスコードをリセットするので、再度上記セットアップ(-getAuth,-setAccessToken)を行って下さい。
+ </p>
+ </description>
+ </item>
+ <h3 tag="facebook-options">Options(rcファイルへの設定)</h3>
+ <item>
+ <tags>g:facebook_auto_load</tags>
+ <spec>let g:facebook_auto_load = <a>0 or 1</a></spec>
+ <description>
+ <p>
+ wallデータの自動更新を設定します。1で有効です。デフォルトは無効になっています。
+ 取得したデータはキャッシュされて、コメントやいいねに利用されるので有効にしておく事をおすすめします。
+ </p>
+ </description>
+ </item>
+ <item>
+ <tags>g:facebook_auto_load_interval</tags>
+ <spec>let g:facebook_auto_interval = <a>msec</a></spec>
+ <description>
+ <p>
+ wallデータの自動更新間隔を設定します。デフォルトは60000(1分)ですが中毒具合によって適宜調整して下さい。
+ あまり間隔を短くしすぎるとエラーになりますのでご注意。
+ </p>
+ </description>
+ </item>
+ <item>
+ <tags>g:facebook_history_limit</tags>
+ <spec>let g:facebook_history_limit = <a>num</a></spec>
+ <description>
+ <p>
+ キャッシュしておく履歴の数。デフォルト100件。
+ </p>
+ </description>
+ </item>
+ <item>
+ <tags>g:facebook_notify</tags>
+ <spec>let g:facebook_notify = <a>0 or 1</a></spec>
+ <description>
+ <p>
+ 通知のオンオフ。デフォルト無効。
+ オンにしておくと更新があった時にいろいろ通知します。
+ フィードが更新された時、メッセージが来た時と新着お知らせに対応。
+ </p>
+ </description>
+ </item>
+ <h3 tag="facebook-post-command">Post(投稿)</h3>
+ <item>
+ <tags>:facebook </tags>
+ <spec>:fa<oa>cebook</oa> text <a>-link</a> url <a>-group</a> id <a>-to</a> id</spec>
+ <description>
+ <p>:fa hogehoge とするとfacebookへ「hogehoge」と投稿します。
+ -link オプションを選ぶと補完リストに開いているURLが出てくるので選択して投稿できます。
+ -group オプションを選ぶと補完リストにグループidが出てくるので選択して投稿するとそのグループにのみ投稿ができます。
+ -to オプションを選ぶと補完リストにフレンドが出てくるので選択して投稿するとそのフレンドのウォールに投稿します。
+ </p>
+ </description>
+ </item>
+ <h3 tag="facebook-open-command">Open</h3>
+ <item>
+ <tags>:facebook <a>open</a></tags>
+ <spec>:fa<oa>cebook</oa> <a>open</a></spec>
+ <description>
+ <p>選択されたアイテムをタブに開きます。
+ リンクが指定されていないデータ(単純なステータス表示など)はその投稿ページを開くだけです。
+ </p>
+ </description>
+ </item>
+ <h3 tag="facebook-get-command">Get(ウォールデータのMOWへの出力)</h3>
+ <item>
+ <tags>:facebook <a>get</a></tags>
+ <spec>:fa<oa>cebook</oa> <a>get</a></spec>
+ <description>
+ <p>facebookのウォールデータを取得してMOWに出力します。
+ ただそれだけの機能なので、そのうちなくなる予定です。
+ </p>
+ </description>
+ </item>
+ <h3 tag="facebook-comment-command">Comment(コメントの投稿)</h3>
+ <item>
+ <tags>:facebook <a>comment</a> <a>id</a> text</tags>
+ <spec>:fa<oa>cebook</oa> <a>comment</a> <a>id</a> text</spec>
+ <description>
+ <p>:fa comment<Space>すると補完リストにキャッシュされているウォールデータが補完リストに表示されます。
+ <Tab>でidを補完してコメントを入力して実行すると対象にコメントを投稿します。
+ </p>
+ </description>
+ </item>
+ <h3 tag="facebook-like-command">Like(いいねの投稿)</h3>
+ <item>
+ <tags>:facebook <a>like</a> <a>id</a></tags>
+ <spec>:fa<oa>cebook</oa> <a>like</a> <a>id</a></spec>
+ <description>
+ <p>:fa like<Space>すると補完リストにキャッシュされているウォールデータが補完リストに表示されます。
+ <Tab>でidを補完して実行すると対象にlike(いいね)をポストします。
+ また、現時点ではlikeの取り消しはできません。
+ </p>
+ </description>
+ </item>
+ <h3 tag="facebook-checkin-command">Check In</h3>
+ <item>
+ <tags>:facebook <a>checkin</a> <a>id</a> text</tags>
+ <spec>:fa<oa>cebook</oa> <a>checkin</a> <a>id</a> text</spec>
+ <description>
+ <p>:fa checkin<Space>すると補完リストに最近チェックインした場所が表示されます。
+ <Tab>でidを補完して実行すると対象にチェックインします。
+ 対象を選んだ後に<Space>textでチェックインコメントを投稿します。
+ </p>
+ </description>
+ </item>
+ <h3 tag="facebook-notification-command">Notification</h3>
+ <item>
+ <tags>:facebook <a>notication</a> <a>url</a></tags>
+ <spec>:fa<oa>cebook</oa> <a>notication</a> <a>url</a></spec>
+ <description>
+ <p>:fa notification<Space> で通知があれば補完リストに何かでます。
+ 選択して実行するとurl開きます。
+ 何も選択せず実行すると通知があるか取得しに行きます(自動更新OFFにしてる人用ですかね)
+ </p>
+ </description>
+ </item>
+ </plugin>
+</>;
+// }}}
+
+(function(){
+
+function presetup(){ // access_token取得前 {{{
+
+ commands.addUserCommand(["facebook","fa"], "facebook util",
+ function(args){
+ if (args["-getAuth"]) {
+ FB.get_auth();
+ } else if (args["-setAccessToken"]) {
+ FB.set_access_token();
+ }
+ },
+ {
+ options: [
+ [["-getAuth"], commands.OPTION_NOARG],
+ [["-setAccessToken"], commands.OPTION_NOARG]
+ ],
+ },
+ true
+ );
+} // }}}
+
+function setup(){ // access_token取得後 {{{
+
+ FB.set_friends();
+ FB.set_groups();
+
+ if(liberator.globalVariables.facebook_auto_load == 1 && __context__.AutoLoader.id == undefined){
+ __context__.AutoLoader.id = setInterval(
+ function(){
+ FB.get_wall_data();
+ FB.notification();
+ }
+ ,liberator.globalVariables.facebook_auto_load_interval || 60000
+ );
+ __context__.AutoLoader.active = true;
+ }
+
+ commands.addUserCommand(["facebook","fa"], "facebook util",
+ function(args){
+ if(args[0] || args["-link"])FB.post_to_wall(args);
+ },
+ {
+ options:[
+ [["-link","-l"],commands.OPTION_STRING,null,tablist],
+ [["-group","-g"],commands.OPTION_STRING,null,grouplist],
+ [["-to","-t"],commands.OPTION_STRING,null,friendlist],
+ ],
+ subCommands: [
+// new Command(["test"], "test",
+// function () {
+// Store.remove("notification");
+// Store.save();
+// },{
+// literal:0,
+// }
+// ),
+ new Command(["resetoauth"], "Reset OAuth Information",
+ function () {
+ FB.resetoauth();
+ },{
+ literal:0,
+ }
+ ),
+ new Command(["notification"], "Get Notifications",
+ function (args) {
+ FB.notification(args)
+ },{
+ literal:0,
+ completer:notify_completer,
+ }
+ ),
+// new Command(["get"], "get walldata from facebook to MOW",
+// function (args) {
+// FB.view_wall_data(args)
+// },{
+// literal:0,
+// completer:friends_completer,
+// }
+// ),
+ new Command(["checkin"], "Check in",
+ function (args) {
+ FB.check_in(args);
+ },{
+ literal:0,
+ completer:checkins_completer,
+ }
+ ),
+ new Command(["comment"], "Comment",
+ function (args) {
+ FB.comment(args);
+ },{
+ literal:0,
+ completer:function(context){
+ feed_completer(context);
+ context.completions = feed_complations();
+ },
+ }
+ ),
+ new Command(["like","l"], "Like",
+ function (args) {
+ FB.like(args);
+ },{
+ literal:0,
+ completer:function(context){
+ feed_completer(context);
+ context.completions = feed_complations();
+ },
+ }
+ ),
+ new Command(["open","o"], "Open url in new tab",
+ function (args) {
+ liberator.open(args[0],liberator.NEW_TAB)
+ },{
+ literal:0,
+ completer:function(context){
+ feed_completer(context);
+ context.completions = feed_complations("open");
+ },
+ }
+ ),
+ ],
+ },
+ true
+ );
+
+ // completers {{{
+ function checkins_completer(context){ // {{{
+
+ context.title = ["id","name"]
+ context.filters = [CompletionContext.Filter.textDescription];
+ context.compare = void 0;
+ context.anchored = false;
+ context.incomplete = true;
+ let url = FB.graph + "me/checkins?access_token=" + FB.access_token;
+ FB.request(url,function(data){
+ res = libly.$U.evalJson(data.responseText)["data"];
+ let checkins = [];
+ for each(let l in res){
+ checkins.push([l["place"]["id"] + "," + l["place"]["location"]["latitude"] + "," + l["place"]["location"]["longitude"] ,l["place"]["name"] + " " + l["place"]["location"]["street"]])
+ }
+ context.incomplete = false;
+ context.completions = checkins;
+ });
+ } // }}}
+
+ function friends_completer(context){ // {{{
+ context.title = ["id","name"]
+ context.filters = [CompletionContext.Filter.textDescription];
+ context.compare = void 0;
+ context.anchored = false;
+
+ let cache = Store.get("friends");
+ let friends = [];
+ for each(let d in cache){
+ friends.push([d["id"],d["name"]]);
+ }
+ context.completions = friends;
+ } // }}}
+
+ function feed_completer(context){ // {{{
+
+ context.title = ["feed"];
+ context.filters = [statusObjectFilter];
+ context.compare = void 0;
+ context.anchored = false;
+ context.createRow = function(item, highlightGroup){
+
+ // タイトル
+ if (highlightGroup === 'CompTitle') {
+ return <div highlight={highlightGroup} style="white-space: nowrap">
+ <li highlight="CompDesc">{item}</li>
+ </div>;
+ }
+
+ let [value, info] = item.item;
+ return <div highlight="CompItem" style="white-space: nowrap">
+ <li highlight="CompDesc">
+ <img src={info.user_icon} style="height:24px;"/>
+ <img src={info.icon}/>
+ {info.user_name}
+ : {info.name} {info.story} {info.message}
+ <span style="color:red;">{info.likes}</span>
+ <span style="color:yellow;">{info.comments}</span>
+ </li>
+ </div>;
+
+ };
+
+ function statusObjectFilter(item)
+ let (desc = item.description)
+ (this.match(desc.user_name) || this.match(desc.message) || this.match(desc.link) || this.match(desc.description));
+
+ } // }}}
+
+ function feed_complations(command) { // {{{
+ let cache = Store.get("feed_cache");
+ let feeds = [];
+ for each(let d in cache){
+ feeds.push([
+ (command === "open") ? (d["link"] || (d["actions"] ? d["actions"][0]["link"] : FB.www)) : d["id"] ,
+ {
+ type:d["type"],
+ icon:d["icon"],
+ user_name:d["from"]["name"],
+ message:(d["message"] || ''),
+ user_icon:FB.graph + d["from"]["id"] + "/picture/" ,
+ link:(d["link"] || ''),
+ likes:d["likes"] ? d["likes"]["count"] + ' likes' : '' ,
+ comments:(d["comments"]["count"] > 0) ? d["comments"]["count"] + ' comments' : '' ,
+ name:(d["name"] || ''),
+ story:(d["story"] || ''),
+ description:(d["description"] || ''),
+ }
+ ]);
+ }
+ return feeds;
+
+ } // }}}
+
+ function notify_completer(context){ // {{{
+
+ let cache = Store.get("notification")
+ if(cache){
+ context.title = ["notification"];
+ context.filters = [statusObjectFilter];
+ context.compare = void 0;
+ context.anchored = false;
+
+ context.createRow = function(item, highlightGroup){
+
+ // タイトル
+ if (highlightGroup === 'CompTitle') {
+ return <div highlight={highlightGroup} style="white-space: nowrap">
+ <li highlight="CompDesc">{item}</li>
+ </div>;
+ }
+
+ let [value, info] = item.item;
+ return <div highlight="CompItem" style="white-space: nowrap">
+ <li highlight="CompDesc">
+ <img src={info.user_icon} style="height:24px;"/>
+ {info.user_name}
+ { /notif.*/.test(info.id) ? <font color="red">[notify]</font> : <font color="yellow">[message]</font>} :
+ {info.message} {info.title}
+ </li>
+ </div>;
+
+ };
+
+ context.incomplete = false;
+ context.completions = notify_complations();
+
+ }else{
+
+ context.title = ["link","description"]
+ context.filters = [CompletionContext.Filter.textDescription];
+ context.compare = void 0;
+ context.anchored = false;
+ context.incomplete = true;
+
+ let url = FB.graph + "me/notifications?access_token=" + FB.access_token;
+ FB.request(url,function(data){
+ res = libly.$U.evalJson(data.responseText)["data"];
+ let notify = [];
+ for each(let n in res){
+ let id = n["id"];
+ let link = n["link"];
+ let title = n["title"] || "";
+ let from = n["from"]["name"] || "";
+ let message = n["message"] || "";
+ notify.push([link,"[" + from + "]" + title + ":" + message])
+ }
+ context.incomplete = false;
+ context.completions = notify;
+ });
+ }
+
+ function statusObjectFilter(item)
+ let (desc = item.description)
+ (this.match(desc.user_name) || this.match(desc.message) || this.match(desc.link) || this.match(desc.description));
+
+ } // }}}
+
+ function notify_complations() { // {{{
+ let cache = Store.get("notification");
+ let notify = [];
+ for each(let d in cache){
+ if(d["unread"]>0){
+ notify.push([
+ d["link"] || FB.www + "messages/",
+ {
+ id:d["id"],
+ user_name:d["from"]["name"],
+ message:(d["message"] || ''),
+ title:(d["title"] || ''),
+ user_icon:FB.graph + d["from"]["id"] + "/picture/" ,
+ link:(d["link"] || ''),
+ }
+ ]);
+ }
+ }
+ return notify;
+
+ } // }}}
+
+ function tablist(context){
+ context.filters = [CompletionContext.Filter.textDescription];
+ let tablist = [];
+ for each([i,tab] in tabs.browsers){
+ tablist.push([tab.currentURI.spec,tabs.getTab(i).label]);
+ }
+ return tablist;
+ }
+
+ function grouplist(context){
+ context.filters = [CompletionContext.Filter.textDescription];
+ let cache = Store.get("groups");
+ let grouplist = [];
+ for each(let d in cache){
+ grouplist.push([d["id"],d["name"]])
+ }
+ return grouplist;
+ }
+
+ function friendlist(context){
+ context.filters = [CompletionContext.Filter.textDescription];
+ let cache = Store.get("friends");
+ let friendlist = [];
+ for each(let d in cache){
+ friendlist.push([d["id"],d["name"]])
+ }
+ return friendlist;
+ }
+
+ // }}}
+
+} // }}}
+
+ let FB = { /// {{{
+ access_token : storage.newMap("facebook",{store:true}).get("access_token"),
+ www : "http://www.facebook.com/",
+ https_www : "https://www.facebook.com/",
+ graph : "https://graph.facebook.com/",
+
+ get_auth : function(){ // get_auth {{{
+ let app_id = "149105991809432";
+ let auth_url = this.https_www + "/dialog/oauth?"
+ + "client_id=" + app_id
+ + "&redirect_uri=https://www.facebook.com/connect/login_success.html"
+ + "&scope=offline_access,publish_stream,read_stream,user_groups,user_checkins,friends_checkins,publish_checkins,manage_notifications,read_mailbox"
+ + "&response_type=token";
+ liberator.open(auth_url,liberator.NEW_TAB);
+ }, // }}}
+
+ set_access_token : function(){ // set_access_token {{{
+ commandline.input("Paste URL",
+ function(res){
+ let token = res.match(/^https:\/\/.*access_token\=(.*)\&.*$/)[1];
+ if(token){
+ Store.set("access_token",token);
+ Store.save();
+ this.access_token = token;
+ liberator.echo("[facebook.js]:set access_token!");
+ setup();
+ }
+ },{
+ completer : function(context){
+ context.title = ['location', 'name street'];
+ context.filters = [CompletionContext.Filter.textDescription];
+ context.completions = (function(){
+ let tablist = [];
+ for each([,tab] in tabs.browsers){
+ if(tab.currentURI.host == "www.facebook.com" && tab.currentURI.path.match(/\/connect\/.*/))
+ tablist.push([tab.currentURI.spec,tab.currentURI.host]);
+ }
+ return tablist;
+ })();
+ },
+ }
+ );
+ }, // }}}
+
+ resetoauth : function() { // resetOAuth {{{
+ this.confirm(
+ "[facebook.js]:Do you want to reset OAuth information?",
+ function(){
+ Store.remove("access_token");
+ Store.save();
+ liberator.echo("[facebook.js]:OAuth information were reset.");
+ presetup();
+ }
+ )
+
+
+ }, // }}}
+
+ confirm: function(msg, onYes, onNo, onCancel) { // {{{
+ if (!onNo)
+ onNo = function () liberator.echo('canceled.');
+
+ commandline.input(
+ msg + " (input 'yes/no'): ",
+ function(s) (s === "yes" ? onYes : onNo)(),
+ {
+ onCancel: onCancel || onNo
+ }
+ );
+ }, // }}}
+
+ post_to_wall : function(data) { // post {{{
+
+ let url = this.graph + (data["-group"] || data["-to"] || "me") + "/feed";
+
+ let post_data = getParameterMap({
+ access_token : this.access_token,
+ message : data.join(' ') || '',
+ link : data["-link"] || '',
+ });
+
+ this.request(url,function(data) echo("[facebook.js]:post success"),true,post_data);
+
+ }, // }}}
+
+ get_wall_data : function() { // set to local storage 'feed_cache' {{{
+
+ let url = FB.graph + "me/home?access_token=" + FB.access_token;
+
+ FB.request(url,function(data){
+ let res = libly.$U.evalJson(data.responseText)["data"];
+ let cache = Store.get("feed_cache") ? Store.get("feed_cache") : [];
+ let data = [];
+
+ let c = 0;
+ for (let i in res){
+ if (res[i].id == (cache[0] ? cache[0].id : undefined)) break;
+ c++;
+ }
+
+ // popup notify
+ if (c > 0 && liberator.globalVariables.facebook_notify == 1)
+ popup("facebook.js",c + " new entry.",FB.graph + res[0]["from"]["id"] + "/picture/" || null);
+
+
+ // cache
+ for (let i in cache){
+ if (res[res.length - 1].id != cache[i].id) continue;
+ i++; // typeof(i) = "string";
+ data = res.concat(cache.splice(i,cache.length));
+ break;
+ }
+ if (data.length == 0) data = res.concat(cache);
+ data = data.splice(0,liberator.globalVariables.facebook_history_limit || 100);
+
+ Store.set("feed_cache",data);
+ Store.save();
+
+ });
+
+ }, // }}}
+
+ view_wall_data : function(data) { // view wall data on MOW {{{
+
+ let url = data[0] ? this.graph + data[0] + "/feed?access_token=" + this.access_token : this.graph + "me/home?access_token=" + this.access_token;
+
+ this.request(url,function(data){
+ let res = libly.$U.evalJson(data.responseText);
+
+ viewWallData(res);
+
+ function viewWallData(data){
+ let buff = "";
+ for each(let d in data["data"]){
+
+ if(d["type"] == "status"){
+ buff += <div><img src={icon_path(d)} width="30px" /> {d["type"]}:{d["message"] || ''} {like_count(d)}{comment_count(d)} <br /> <hr /></div>
+ }else if(d["type"] == "link"){
+ buff += <div><img src={icon_path(d)} width="30px" /> {d["type"]}:{d["message"] || ''} <a href={d["link"]} >{d["link"]}</a> {like_count(d)}{comment_count(d)}<br /><hr /> </div>
+ }else if(d["type"] == "photo"){
+ buff += <div><img src={icon_path(d)} width="30px" /> {d["type"]}:{d["message"] || ''} {like_count(d)}{comment_count(d)} <br /><img src={d["picture"]} /> <br /><hr /> </div>
+ }else if(d["type"] == "checkin"){
+ buff += <div><img src={icon_path(d)} width="30px" /> {d["type"]}:{d["message"] || ''} {d["name"]} {like_count(d)}{comment_count(d)} <br /> <hr /></div>;
+ }else{
+ buff += <div>unknown post : {d["type"]}</div>
+ }
+
+ liberator.echo(buff);
+
+ }
+
+ function icon_path(data) FB.graph + data["from"]["id"] + "/picture/";
+
+ function comment_count(data) {
+ if(!data["comments"]["data"]) return "";
+ return <span style="color:red">({data["comments"]["count"]} comments) </span>;
+ }
+
+ function like_count(data) {
+ if(!data["likes"]) return "";
+ return <span style="color:yellow">({data["likes"]["count"]} Likes) </span>;
+ }
+
+ }
+ });
+
+
+
+
+ }, // }}}
+
+ set_friends : function(data) { // store friends data {{{
+
+ let url = this.graph + "me/friends?access_token=" + this.access_token;
+
+ this.request(url,function(data){
+ let res = libly.$U.evalJson(data.responseText)
+ Store.set('friends',res["data"]);
+ Store.save();
+ })
+
+ }, // }}}
+
+ set_groups : function(data) { // store groups data {{{
+
+ let url = this.graph + "me/groups?access_token=" + this.access_token;
+
+ this.request(url,function(data){
+ let res = libly.$U.evalJson(data.responseText)
+ Store.set('groups',res["data"]);
+ Store.save();
+ })
+
+ }, // }}}
+
+ request : function(url,callback,type,post_data){ // get or post requester. def:get {{{
+
+ let req = new libly.Request(
+ url , //url
+ null, //headers
+ { // options
+ asynchronous:true,
+ postBody:post_data,
+ }
+ );
+
+ req.addEventListener("success",function(data){
+ callback(data);
+ });
+
+ req.addEventListener("failure",function(data){
+ e("[facebook.js:failure] : " + data.responseText)
+ e(data.req.url)
+ e(data)
+ liberator.echoerr("[facebook.js:failure] : " + data.responseText);
+ });
+
+ req.addEventListener("exception",function(data){
+ e("[facebook.js:exception] : " + data.responseText)
+ e(data)
+ liberator.echoerr("[facebook.js:exception] : " + data.responseText);
+ });
+
+ !type ? req.get() : req.post();
+
+ }, // }}}
+
+ check_in : function(data){ // check in {{{
+
+ if(!data[0]) return;
+
+ let url = this.graph + "me/checkins";
+
+ let p = data[0].split(' ')[0].split(',');
+ let place_id = p[0];
+ let latitude = p[1];
+ let longitude = p[2];
+ let message = data[0].split(' ').slice(1,undefined).join(' ');
+
+ let post_data = getParameterMap({
+ access_token : this.access_token,
+ place:place_id,
+ coordinates:'{"latitude":' + latitude + ',"longitude":"' + longitude + '"}',
+ message : message || ''
+ });
+
+ this.request(url,function(data) echo("[facebook.js]:checkin success"),true,post_data);
+
+
+ }, // }}}
+
+ comment : function(data) { // comment {{{
+
+ if(!data[0]) return;
+
+ let target = data[0].split(' ')[0];
+ let message = data[0].split(' ').slice(1,undefined).join(' ');
+
+ let url = this.graph + target + "/comments";
+ let post_data = getParameterMap({
+ access_token : this.access_token,
+ message : message
+ });
+
+ this.request(url,function(data) echo("[facebook.js]:post success"),true,post_data);
+
+ }, // }}}
+
+ like : function(data) { // like {{{
+
+ if(!data[0]) return;
+
+ let url = this.graph + data[0] + "/likes";
+ let post_data = getParameterMap({
+ access_token : this.access_token,
+ });
+
+ this.request(url,function(data) echo("[facebook.js]:post success"),true,post_data);
+
+ }, // }}}
+
+ notification : function(data) { // notification {{{
+
+ try{
+ if(data[0]) return liberator.open(data[0],liberator.NEW_TAB);
+ }catch(e){}
+
+ let post_data = getParameterMap({
+ access_token : this.access_token,
+ batch:'[{"method":"GET","relative_url":"me/notifications"},{"method":"GET","relative_url":"me/inbox"}]',
+ });
+
+
+ this.request(
+ FB.graph,
+ function(data){
+ let res = libly.$U.evalJson(data.responseText);
+ let temp_cache = [];
+ let cache = Store.get("notifications");
+
+ for each(let d in res){
+ if(d["code"]==400){
+ liberator.echoerr("[facebook.js]maybe too much request???");
+ e(d);
+ }
+ let body = libly.$U.evalJson(d["body"]);
+ for each(let n in body["data"]){
+ if(!(is_cache(n.id))){
+ if(/^notif.*/.test(n.id)){ // notification
+ easy_popup(n);
+ }else if(n.unread > 0){ // message
+ easy_popup(n);
+ }
+ }
+ temp_cache.push(n);
+ }
+ }
+ Store.set("notification",temp_cache);
+ Store.save();
+
+ function easy_popup(n){
+ if (liberator.globalVariables.facebook_notify == 1){
+ let title = n["title"] || n["from"]["name"];
+ let from_icon = FB.graph + n["from"]["id"] + "/picture/" || null;
+ let message = n["message"] || "";
+ popup(title,message,from_icon);
+ }
+ }
+ function is_cache(id){
+ let cache = Store.get("notification");
+ for each(let c in cache){
+ if(id == c.id) return true;
+ }
+ return false;
+ }
+ }
+ ,true
+ ,post_data
+ );
+
+ }, // }}}
+
+ } /// }}}
+
+
+ let Store = storage.newMap("facebook",{store:true});
+
+ // 公開オブジェクト
+ if (! __context__.AutoLoader) __context__.AutoLoader = {id:undefined,active:false}
+
+ if(Store.get("access_token")){
+ setup();
+ }else{
+ presetup();
+ }
+
+ function popup(title, text ,image) {
+ Components.classes['@mozilla.org/alerts-service;1'].
+ getService(Components.interfaces.nsIAlertsService).
+ showAlertNotification(image, title, text, false, '', null);
+ }
+
+ function getParameterMap(parameters){
+ let map = "";
+ for (let key in parameters){
+ if (map) map += "&";
+ map += key + "=" + parameters[key];
+ }
+ return map
+ }
+
+ // for debug {{{
+ function e(v,c){
+ if(c) util.copyToClipboard(v);
+ liberator.log(v,-1)
+ }
+
+ function echo(v){
+ liberator.echo(v)
+ } // }}}
+
+
+})();
+
+
+
diff --git a/feedSomeKeys_3.js b/feedSomeKeys_3.js index e265084..82d15ba 100644 --- a/feedSomeKeys_3.js +++ b/feedSomeKeys_3.js @@ -34,8 +34,8 @@ THE POSSIBILITY OF SUCH DAMAGE. // INFO {{{ let INFO = <> - <plugin name="feedSomeKeys" version="1.9.0" - href="http://svn.coderepos.org/share/lang/javascript/vimperator-plugins/trunk/feedSomeKeys_3.js" + <plugin name="feedSomeKeys" version="1.9.3" + href="http://github.com/vimpr/vimperator-plugins/blob/master/feedSomeKeys_3.js" summary="Feed some defined key events into the Web content" lang="en-US" xmlns="http://vimperator.org/namespaces/liberator"> @@ -140,8 +140,8 @@ let INFO = <> :lazy fmaps -u='http://code.google.com/p/vimperator-labs/issues/detail' u </ex></code> </plugin> - <plugin name="feedSomeKeys" version="1.9.0" - href="http://svn.coderepos.org/share/lang/javascript/vimperator-plugins/trunk/feedSomeKeys_3.js" + <plugin name="feedSomeKeys" version="1.9.3" + href="http://github.com/vimpr/vimperator-plugins/blob/master/feedSomeKeys_3.js" summary="Web コンテンツに直接キーイベントを送ります。" lang="ja" xmlns="http://vimperator.org/namespaces/liberator"> @@ -309,6 +309,10 @@ let INFO = <> '\'': KeyEvent.DOM_VK_QUOTE }; + const State = { + feeding: false + }; + function id (v) v; @@ -359,22 +363,35 @@ let INFO = <> } function feed (keys, eventNames, target) { + function finalize (){ + modes.passAllKeys = _passAllKeys; + State.feeding = false; + } + + State.feeding = true; + let _passAllKeys = modes.passAllKeys; - modes.passAllKeys = true; - modes.passNextKey = false; - - for (let [, keyEvent] in Iterator(events.fromString(keys))) { - eventNames.forEach(function (eventName) { - let ke = util.cloneObject(keyEvent); - let [, vkey, name] = eventName.match(/^(v)?(.+)$/); - if (vkey) - virtualize(ke); - let event = createEvent(name, ke); - target.dispatchEvent(event); - }); + + try { + modes.passAllKeys = true; + modes.passNextKey = false; + + for (let [, keyEvent] in Iterator(events.fromString(keys))) { + eventNames.forEach(function (eventName) { + let ke = util.cloneObject(keyEvent); + let [, vkey, name] = eventName.match(/^(v)?(.+)$/); + if (vkey) + virtualize(ke); + let event = createEvent(name, ke); + target.dispatchEvent(event); + }); + } + } catch (e) { + finalize(); + throw e; } - modes.passAllKeys = _passAllKeys; + finalize(); } function makeTryValidator (func) @@ -504,7 +521,8 @@ let INFO = <> ]; if (currentURL) { result.unshift(['^' + util.escapeRegex(buffer.URL), 'Current URL']); - result.unshift([util.escapeRegex(content.document.domain), 'Current domain']); + if (content.document.domain) + result.unshift([util.escapeRegex(content.document.domain), 'Current domain']); } return result; }; @@ -520,7 +538,7 @@ let INFO = <> const ModeStringsCompleter = [ [name, disp + ' mode' + (char ? ' (alias: ' + char + ')' : '')] for ([n, {name, char, disp, extended}] in Iterator(modes._modeMap)) - if (!extended && /^\D+$/(n)) + if (!extended && /^\D+$/.test(n)) ]; @@ -559,6 +577,10 @@ let INFO = <> elem = or(frames, function (f) fromXPath(f.document, args['-xpath'])) || elem; } + if (args['-selector']) { + elem = or(frames, function (f) f.document.querySelector(args['-selector'])) || elem; + } + feed(rhs, args['-events'] || ['keypress'], elem); }, { @@ -602,6 +624,7 @@ let INFO = <> [['-desc', '-description', '-d'], commands.OPTION_STRING], [['-frame', '-f'], commands.OPTION_INT, null, frameCompleter], [['-xpath', '-x'], commands.OPTION_STRING, xpathValidator], + [['-selector', '-s'], commands.OPTION_STRING], [['-prefix', '-p'], commands.OPTION_STRING], [ ['-events', '-e'], @@ -690,7 +713,7 @@ let INFO = <> ); __context__.API = - 'VKeys feed getFrames fromXPath virtualize unmap findMappings list'.split(/\s+/).reduce( + 'State VKeys feed getFrames fromXPath virtualize unmap findMappings list'.split(/\s+/).reduce( function (result, name) (result[name] = eval(name), result), {} diff --git a/gmail-commando.js b/gmail-commando.js index 25ca2a3..7b4e929 100644 --- a/gmail-commando.js +++ b/gmail-commando.js @@ -35,8 +35,8 @@ THE POSSIBILITY OF SUCH DAMAGE. // INFO {{{ let INFO = <> - <plugin name="GMailCommando" version="1.4.5" - href="http://svn.coderepos.org/share/lang/javascript/vimperator-plugins/trunk/gmail-commando.js" + <plugin name="GMailCommando" version="1.4.11" + href="http://github.com/vimpr/vimperator-plugins/blob/master/gmail-commando.js" summary="The handy commands for GMail" lang="en-US" xmlns="http://vimperator.org/namespaces/liberator"> @@ -72,8 +72,8 @@ let INFO = </description> </item> </plugin> - <plugin name="GMailコマンドー" version="1.4.5" - href="http://svn.coderepos.org/share/lang/javascript/vimperator-plugins/trunk/gmail-commando.js" + <plugin name="GMailコマンドー" version="1.4.10" + href="http://github.com/vimpr/vimperator-plugins/blob/master/gmail-commando.js" summary="便利なGMail用コマンドー" lang="ja" xmlns="http://vimperator.org/namespaces/liberator"> @@ -118,6 +118,9 @@ let INFO = function A (list) Array.slice(list); + function click (elem) + buffer.followLink(elem, liberator.CURRENT_TAB); + const Conf = (function () { let gv = liberator.globalVariables; let conf = {}; @@ -227,14 +230,14 @@ let INFO = const Elements = { - get doc() content.frames[3].document, + get doc() content.document.querySelector('#canvas_frame').contentDocument, - get labels() A(this.doc.querySelectorAll('a.n0')).filter(function (it) (/#label/(it.href))), + get labels() A(this.doc.querySelectorAll('a.n0')).filter(function (it) (/#label/.test(it.href))), - // 入力欄 - input - get input() this.doc.getElementById(':re'), + // 入力欄 と 検索ボタンは Buzz の有効無効によって ID が変わる + get input() this.doc.querySelector('input.w-as1.nr'), - get searchButton() this.doc.getElementById(':ri'), + get searchButton() this.doc.querySelector('div.J-Zh-I.J-J5-Ji.L3.J-Zh-I-Js-Zq'), get translateButton () (this.mail && this.mail.querySelector('tr > td.SA > .iL.B9')), get translateButtons () A(this.doc.querySelectorAll('tr > td.SA > .iL.B9')), @@ -262,9 +265,9 @@ let INFO = if (result) return A(result); - buffer.followLink(show()); + click(show()); result = labels(); - buffer.followLink(show()); + click(show()); return A(result); }, @@ -275,17 +278,10 @@ let INFO = Elements.doc.querySelectorAll('.NRYPqe > .J-Zh-I.J-J5-Ji.J-Zh-I.J-Zh-I-Js-Zq')[2] }; - //'.J-M-JJ > input' - //let (e = Elements.doc.querySelector('.J-LC-Jz')) { - // liberator.log(e); - // buffer.followLink(e); - // //plugins.feedSomeKeys_3.API.feed('<Cr>', ['keydown'], e) - //} - const Commando = { get inGmail () { try { - var result = /^mail\.google\.com$/(Elements.doc.location.hostname) + var result = /^mail\.google\.com$/.test(Elements.doc.location.hostname) } catch (e) {} return result; }, @@ -295,7 +291,7 @@ let INFO = if (this.inGmail && !newtab) { Elements.input.value = args; - buffer.followLink(Elements.searchButton); + click(Elements.searchButton); } else { liberator.open(URL + encodeURIComponent(args), liberator.NEW_TAB); } @@ -307,20 +303,20 @@ let INFO = const Commands = { translate: function () { let button = Elements.translateButton || Elements.translateButtons[0]; - buffer.followLink(button); + click(button); }, - translateThread: function () buffer.followLink(Elements.translateThreadButton), - fold: function () buffer.followLink(Elements.foldButton), - unfold: function () buffer.followLink(Elements.unfoldButton), + translateThread: function () click(Elements.translateThreadButton), + fold: function () click(Elements.foldButton), + unfold: function () click(Elements.unfoldButton), label: function (names) { Elements.labelButtons.forEach(function (e) { if (names.some(function (v) (v == e.textContent))) - buffer.followLink(e); + click(e); liberator.log('pressed: ' + e.textContent); }); }, - important: function () buffer.followLink(Elements.importantButton), - unimportant: function () buffer.followLink(Elements.unimportantButton) + important: function () click(Elements.importantButton), + unimportant: function () click(Elements.unimportantButton) }; @@ -393,16 +389,16 @@ let INFO = let input = args.string.slice(0, context.caret); let m; - if (m = /([a-z]+):(?:([^\s\(\)\{\}]*)|[\(\{]([^\(\)\{\}]*))$/(input)) { + if (m = /([a-z]+):(?:([^\s\(\)\{\}]*)|[\(\{]([^\(\)\{\}]*))$/.exec(input)) { if (m[2]) { context.advance(input.length - m[2].length); } else { - let tail = /[^\s]*$/(m[3]); + let tail = /[^\s]*$/.exec(m[3]); context.advance(input.length - tail[0].length); } let key = m[1]; KeywordValueCompleter[key](context, args); - } else if (m = /[-\s]*([^-\s:\(\)\{\}]*)$/(input)) { + } else if (m = /[-\s]*([^-\s:\(\)\{\}]*)$/.exec(input)) { context.advance(input.length - m[1].length); context.completions = [ [v + ':', v] for ([, v] in Iterator(GMailSearchKeyword)) diff --git a/google-plus-commando.js b/google-plus-commando.js new file mode 100644 index 0000000..d1dfed5 --- /dev/null +++ b/google-plus-commando.js @@ -0,0 +1,1770 @@ +/* NEW BSD LICENSE {{{ +Copyright (c) 2011, anekos. +Copyright (c) 2011, teramako. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. + + +################################################################################### +# http://sourceforge.jp/projects/opensource/wiki/licenses%2Fnew_BSD_license # +# に参考になる日本語訳がありますが、有効なのは上記英文となります。 # +################################################################################### + +}}} */ + +// INFO {{{ +let INFO = +<> + <plugin name="GooglePlusCommando" version="2.4.6" + href="http://github.com/vimpr/vimperator-plugins/blob/master/google-plus-commando.js" + summary="The handy commands for Google+" + lang="en-US" + xmlns="http://vimperator.org/namespaces/liberator"> + <author email="anekos@snca.net">anekos</author> + <author email="teramako@gmail.com" homepage="http://d.hatena.ne.jp/teramako/">teramako</author> + <license>New BSD License</license> + <project name="Vimperator" minVersion="3.0"/> + <p>Many Mappings and Post command for Google+</p> + <p>require: feedSomeKeys_3.js and x-hint.js and _libly.js</p> + <item> + <tags>:googleplus-setup</tags> + <spec>:googleplus -setup</spec> + <spec>:gp -setup</spec> + <description> + <p>Should setup at first</p> + <ol> + <li>Login to <a href="htts://plus.google.com/">Google+</a></li> + <li>Execute <ex>:googleplus -setup</ex></li> + </ol> + </description> + </item> + <item> + <tags>:googleplus-nonargs</tags> + <spec>:googleplus</spec> + <spec>:gp</spec> + <description> + <p>when argument is none, select the Google+ tab or open in new tab</p> + </description> + </item> + <item> + <tags>:googleplus :gp</tags> + <spec>:googleplus <oa>-l[link]</oa> <oa>-i[mage] <a>imageURL</a></oa> <oa>-t[o] <a>to</a></oa> <a>message</a></spec> + <spec>:gp <oa>-l[ink]</oa> <oa>-i[mage] <a>imageURL</a></oa> <oa>-t[o]> <a>to</a></oa> <a>message</a></spec> + <description> + <p>Post <a>message</a></p> + <dl> + <dt>-link</dt> + <dd> + Add the current URL. If the selections are available, add the selections as relataed page. + And when <a>-image</a> option is not specified and image elements is contained in the selections, + add the URL of the largest image. + </dd> + <dt>-image</dt> + <dd> + Specify image URL + </dd> + <dt>-to</dt> + <dd> + Specify the circles. Can set multiple. (Default: Anyone) + </dd> + </dl> + </description> + </item> + <item> + <tags>g:gplus_commando_map_</tags> + <spec>let g:gplus_commando_map_<a>command</a> = <a>map-keys</a></spec> + <description> + <p> + Map <a>map-keys</a> for <a>command</a>. + The possible <a>command</a>s. + <dl> + <dt>next</dt> <dd>Go to next entry.</dd> + <dt>prev</dt> <dd>Back to previous entry.</dd> + <dt>share</dt> <dd>Shate current entry.</dd> + <dt>plusone</dt> <dd>+1</dd> + <dt>comment</dt> <dd>Comment to current entry.</dd> + <dt>post</dt> <dd>Post new entry.</dd> + <dt>yank</dt> <dd>Copy the permlink of current entry to clipboard.</dd> + <dt>notification</dt> <dd>Open notification box.</dd> + <dt>cancel</dt> <dd>Cancel current something.</dd> + <dt>submit</dt> <dd>Submit current editing post.</dd> + <dt>unfold</dt> <dd>Unfold something on current entry.</dd> + <dt>menu</dt> <dd>Open the menu of current entry.</dd> + <dt>mute</dt> <dd>Mute current entry.</dd> + <dt>open</dt> <dd>Open something on current entry.</dd> + </dl> + </p> + <p>rc file example</p> + <code> +let g:gplus_commando_map_next = "j" +let g:gplus_commando_map_prev = "k" +let g:gplus_commando_map_share = "s" +let g:gplus_commando_map_plusone = "p" +let g:gplus_commando_map_comment = "c" +let g:gplus_commando_map_post = "C" +let g:gplus_commando_map_yank = "y" +let g:gplus_commando_map_notification = "n" +let g:gplus_commando_map_submit = "<C-CR>" +let g:gplus_commando_map_cancel = "<Esc>" +let g:gplus_commando_map_unfold = "e" +let g:gplus_commando_map_menu = "m" + </code> + </description> + </item> + </plugin> + <plugin name="GooglePlusCommando" version="2.4.6" + href="http://github.com/vimpr/vimperator-plugins/blob/master/google-plus-commando.js" + summary="The handy commands for Google+" + lang="ja-JP" + xmlns="http://vimperator.org/namespaces/liberator"> + <author email="anekos@snca.net">anekos</author> + <author email="teramako@gmail.com" homepage="http://d.hatena.ne.jp/teramako/">teramako</author> + <license>New BSD License</license> + <project name="Vimperator" minVersion="3.0"/> + <p>Many Mappings and Post command for Google+</p> + <p>require: feedSomeKeys_3.js and x-hint.js and _libly.js</p> + <item> + <tags>:googleplus-setup</tags> + <spec>:googleplus -setup</spec> + <spec>:gp -setup</spec> + <description> + <p>Should setup at first</p> + <ol> + <li>Login to <a href="htts://plus.google.com/">Google+</a></li> + <li>Execute <ex>:googleplus -setup</ex></li> + </ol> + </description> + </item> + <item> + <tags>:googleplus-nonargs</tags> + <spec>:googleplus</spec> + <spec>:gp</spec> + <description> + <p>when argument is none, select the Google+ tab or open in new tab</p> + </description> + </item> + <item> + <tags>:googleplus :gp</tags> + <spec>:googleplus <oa>-l[link]</oa> <oa>-i[mage] <a>imageURL</a></oa> <oa>-t[o] <a>to</a></oa> <a>message</a></spec> + <spec>:gp <oa>-l[ink]</oa> <oa>-i[mage] <a>imageURL</a></oa> <oa>-t[o]> <a>to</a></oa> <a>message</a></spec> + <description> + <p>Post <a>message</a></p> + <dl> + <dt>-link</dt> + <dd> + Add the current URL. If the selections are available, add the selections as relataed page. + And when <a>-image</a> option is not specified and image elements is contained in the selections, + add the URL of the largest image. + </dd> + <dt>-image</dt> + <dd> + Specify image URL + </dd> + <dt>-to</dt> + <dd> + Specify the circles. Can set multiple. (Default: Anyone) + </dd> + </dl> + </description> + </item> + <item> + <tags>g:gplus_commando_map_</tags> + <spec>let g:gplus_commando_map_<a>command</a> = <a>map-keys</a></spec> + <description> + <p> + <a>map-keys</a> に <a>command</a> をマップします。 + 使える <a>command</a> は以下の通りです。 + <dl> + <dt>next</dt> <dd>次のエントリに移動</dd> + <dt>prev</dt> <dd>前のエントリに移動</dd> + <dt>share</dt> <dd>現在のエントリを共有</dd> + <dt>plusone</dt> <dd>+1</dd> + <dt>comment</dt> <dd>現在のエントリにコメントする</dd> + <dt>post</dt> <dd>新しく投稿(共有)する</dd> + <dt>yank</dt> <dd>現在のエントリの Permlink をクリップボードにコピーする</dd> + <dt>notification</dt> <dd>通知欄を開く</dd> + <dt>cancel</dt> <dd>編集中のフォームをキャンセルして閉じる</dd> + <dt>submit</dt> <dd>編集中のフォームから投稿する</dd> + <dt>unfold</dt> <dd>現在のエントリ内の折りたたみを解除する</dd> + <dt>menu</dt> <dd>現在のエントリのメニューを開く</dd> + <dt>mute</dt> <dd>現在のエントリをミュートする</dd> + <dt>open</dt> <dd>現在のエントリの画像やリンクなどを開く</dd> + </dl> + </p> + <p>rc file example</p> + <code> +let g:gplus_commando_map_next = "j" +let g:gplus_commando_map_prev = "k" +let g:gplus_commando_map_share = "s" +let g:gplus_commando_map_plusone = "p" +let g:gplus_commando_map_comment = "c" +let g:gplus_commando_map_post = "C" +let g:gplus_commando_map_yank = "y" +let g:gplus_commando_map_notification = "n" +let g:gplus_commando_map_submit = "<C-CR>" +let g:gplus_commando_map_cancel = "<Esc>" +let g:gplus_commando_map_unfold = "e" +let g:gplus_commando_map_menu = "m" + </code> + </description> + </item> + </plugin> +</>; +// }}} + +(function () { + + // Utils {{{ + + function A (list) + Array.slice(list); + + function I (list) + Iterator(list); + + function IA (list) + Iterator(A(list)); + + function click (elem, after) { + click + if (!elem) + throw GPCError('elem is undefined'); + setTimeout( + function () { + buffer.followLink(elem, liberator.CURRENT_TAB); + setTimeout(after, 1); + }, + 1 + ); + } + + function clicks (elems, after) { + if (!(elems && elems.length)) + return; + + setTimeout( + function () { + click(elems[0], elems.length === 1 && after); + clicks(elems.slice(1)); + }, + 1 + ); + } + + function isDisplayed (elem) + (elem && !/none/.test(util.computedStyle(elem).display)); + + function withCount (command) { + return function (count) { + if (count < 1) + count = 1; + for (let i = 0; i < count; i++) + command(); + }; + } + + function GPCError (msg) { + if (this instanceof GPCError) { + liberator.log('GPCError: ' + msg); + this.toString = function () String(msg); + } else { + return new GPCError(msg); + } + } + + function selectFind (doc, selector, func) { + if (!doc) + return; + if (!func) + func = function () true; + for (let [n, v] in IA(doc.querySelectorAll(selector))) { + let res = func(v, n); + if (res) + return v; + } + } + + function getSelector (elem) { + if (!elem) + return; + let cs = elem.getAttribute('class').trim().split(/\s+/); + cs.sort(function (a, b) a.localeCompare(b)); + return '.' + cs.join('.'); + } + + function getSelectorFind (doc, sel, func) { + return getSelector(selectFind(doc, sel, func)); + } + + // }}} + + // Selector {{{ + + const [S, X, R] = (function () { + + function role (name, prefix) + ((prefix || '') + '[role="' + name + '"]'); + + function once (obj, name, fail) { + let func = obj[name]; + Object.defineProperty( + obj, + name, + { + get: let (result) function () (result || (result = func()) || fail) + } + ); + } + + function onceAll (obj, fail) { + for (let [n, v] in I(obj)) { + if (n === 'role') + continue; + if (typeof v === 'function') + once(obj, n, fail); + if (typeof v === 'object') { + onceAll(v, fail); + } + } + } + + let cssRules = { + __iterator__: function (nameOnly) { + if (content.location.host != 'plus.google.com') + return; + + let result = []; + for (let [, sheet] in IA(content.document.styleSheets)) { + for (let [n, rule] in IA(sheet.cssRules)) { + yield nameOnly ? n : [n, rule]; + } + } + }, + + find: function (re, notre) { + let result = []; + for (let [, rule] in I(this)) { + if (re.test(rule.cssText)) { + if (notre && notre.test(rule.cssText)) + continue; + result.push(rule); + } + } + + if (result.length < 1) + throw GPCError('Not fount css rule: ' + re); + + if (result.length == 1) + return result[0].selectorText; + + for (let [, rule] in I(result)) { + liberator.log(rule.cssText); + } + + throw GPCError('Two and more rules are found'); + }, + + finder: function (re, notre) { + let self = this; + return function () self.find(re, notre); + }, + + get: function (klass, returnCssText) { + let reKlass = new RegExp('(^|,)\s*.' + klass + '\s*(,|$)'); + let result = []; + for (let [, rule] in I(this)) { + if (reKlass.test(rule.selectorText)) + result.push(rule); + } + if (returnCssText) { + return result.map(function (it) it.cssText); + } else { + return result; + } + } + }; + + let selector = { + role: role, + typePlusone: '[g\\:entity^="buzz:"]', + editable: '.editable', + + plusone: 'button[id^="po-"]', + + currentEntry: { + root: cssRules.finder(/border-left: 1px solid rgb\(77, 144, 240\);\s*\}/), + unfold: { + comment: cssRules.finder(/url\("\/\/ssl\.gstatic\.com\/s2\/oz\/images\/stream\/expand\.png"\)/), + content: function () { + let content = cssRules.find(/\{ overflow: hidden; padding-bottom: \d+px; padding-top: \d+px; text-overflow: ellipsis ellipsis; \}/); + let buttons = cssRules.find(/^[^,]+\,[^,]+\{\s*color:\s*rgb\(51, 102, 204\);\s*cursor:\s*pointer;\s*\}$/); + return buttons.split(/,/).map(function (b) (content + ' > div > ' + b)).join(', '); + } + }, + menu: { + mute: '----' + }, + menuButton: cssRules.finder(/url\("\/\/ssl\.gstatic\.com\/s2\/oz\/images\/stream\/options_default\.png"\).*margin-right: -44/), + cancel: role('button', '[id$=".cancel"]'), + submit: role('button', '[id$=".post"]'), + }, + post: { + root: function () { + let div = cssRules.find(/\{ margin-left: \d+px; margin-right: \d+px; margin-top: \d+px; width: \d+px; \}/); + return '#contentPane ' + 'div[decorated="true"]' + div; + // onclick の this.className += ' f-Sa' は初めは存在しないっぽい + }, + open: cssRules.finder(/opacity 0.125s ease 0.125s/), + cancel: 'div[id$=".c"]', // :6w.c + }, + notification: '#gbi1', + viewer: { + root: function () { + let n = Elements.doc.querySelector(S.viewer.next); + return n && getSelector(n.parentNode.parentNode); + }, + prev: cssRules.finder(/url\("\/\/ssl\.gstatic\.com\/s2\/oz\/images\/left-arrow2\.png"\)/), + next: cssRules.finder(/url\("\/\/ssl\.gstatic\.com\/s2\/oz\/images\/right-arrow2\.png"\)/) + }, + dialog: { + root: role('dialog', 'body > '), + //cssRules.find(/0pt 4px 16px rgba\(0, 0, 0, 0.2\).*-moz-border-right-color.*z-index: 1101/)); + submit: 'td[valign="top"] > div[role="button"]:nth-child(1)', + cancel: 'td[valign="top"] > div[role="button"]:nth-child(2)' + }, + frames: { + notifications: { + root: 'iframe[src*="/_/notifications/"]', + summary: { + root: '#summary-view', + prev: '#summary-view + div > div > div > span', + next: '#summary-view + div > div > div > span:last-child', + back: '#summary-view + div > div > span', + }, + entry: { + entries: 'div[id^=":"][style*="max-height"]', // :2.diz13l.... + comment: cssRules.finder(/rgb\(221, 221, 221\).*rgb\(153, 153, 153\)/), + mute: 'div[id^=":"][style*="max-height"] > div > div:nth-child(2) > div > div > ' + role('button', 'span') // FIXME + }, + } + }, + closeButton: cssRules.finder(/url\("\/\/ssl\.gstatic\.com\/s2\/oz\/images\/lightbox-sprite2.gif"\).*0%.*0%.*z-index/) + }; + + let xpath = { + hints: [ + 'span[@role="button"]', + 'div[@role="button"]', + 'div[@data-content-type]', + 'img[contains(@class,"O-g-Sd-la")]', // /photos の写真 + //FIXME 'div[contains(@class,"a-z-nb-A")]' + ] + }; + + onceAll(selector, '.MEOW_MEOW_MEOW'); + + return [selector, xpath, cssRules]; + })(); + + // }}} + + // Elements {{{ + + const Elements = (function () { + + return { + get doc() content.document, + get currentEntry () MakeElement(Entry, Elements.doc.querySelector(S.currentEntry.root)), + post: { + get root () Elements.doc.querySelector(S.post.root), + get cancel () Elements.doc.querySelector(S.post.cancel), + get open () Elements.doc.querySelector(S.post.open) + }, + get notification () Elements.doc.querySelector(S.notification), + get viewer () MakeElement(Viewer, Elements.doc.querySelector(S.viewer.root)), + get dialog () MakeElement(Dialog, Elements.doc.querySelector(S.dialog.root)), + + frames: { + get notifications () MakeElement(Notifications, Elements.doc.querySelector(S.frames.notifications.root)) + }, + + get focusedEditor () { + function hasIFrame (elem) { + let iframe = elem.querySelector('iframe'); + return iframe && iframe.contentWindow === win; + } + + // エントリにコメント + function get1 (root) { + function button (editor, name) + editor.parentNode.querySelector(S.role('button', <>[id$=".{name}"]</>)); + + if (!root) + return; + + let editors = A(root.querySelectorAll('div[id$=".editor"]')).filter(hasIFrame); + if (editors.length === 0) + return; + if (editors.length > 1) + throw 'Two and more editors were found.'; + + return { + editor: #1=(editors[0]), + button: { + submit: button(#1#, 'post'), + cancel: button(#1#, 'cancel') + } + }; + } + + // 新しく投稿 + function get2 () { + function button (editor, index) { + let result = editor.querySelectorAll('td > ' + S.role('button'))[index]; + if (result) + return result; + if (index === 1) + return editor.querySelector(S.post.cancel); + } + + const indexes = {submit: 0, cancel: 1}; + + let editors = A(doc.querySelectorAll(S.post.root)).filter(hasIFrame); + if (editors.length === 0) + return; + if (editors.length > 1) + throw 'Two and more editors were found.'; + + return { + editor: #1=(editors[0]), + button: { + submit: button(#1#, 0), + cancel: button(#1#, 1) + } + }; + } + + // ダイアログ + function get3 (root) { + function button (editor, name) + editor.parentNode.querySelector(S.role('button', <>[id$=".{name}"]</>)); + + if (!root) + return; + + let editors = A(root.querySelectorAll('div.editable')).filter(hasIFrame); + if (editors.length === 0) + return; + if (editors.length > 1) + throw 'Two and more editors were found.'; + + return { + editor: #1=(editors[0]), + button: { + submit: root.querySelector(S.dialog.submit), + cancel: root.querySelector(S.dialog.cancel), + } + }; + } + + let doc = content.document; + let win = document.commandDispatcher.focusedWindow; + + return ( + get1(doc) || + get2() || + get1(Elements.frames.notifications.root.contentDocument) || + (Elements.dialog && Elements.dialog.root && get3(Elements.dialog.root)) + ); + }, + + /** + * ノードをHTMLテキストに変換 + * @param {Node} aNode + * @param {String} [aParentTag] 親ノードのタグ名 + * @param {String} [aIndent] インデント文字列 + * @param {Number} [aIndex] ノード番号(ol>li 時のみ使用) + * @return {String} + */ + node2txt: function (aNode, aParentTag, aIndent, aIndex) { + var txt = ""; + switch (aNode.nodeType) { + case Node.DOCUMENT_NODE: // 9 + case Node.DOCUMENT_FRAGMENT_NODE: // 11 + switch (aParentTag) { + case "ol": + case "ul": + case "dl": + aIndent = " "; + break; + default: + aIndent = ""; + } + txt = nodelist2txt(aNode.childNodes, aParentTag, aIndent).join(""); + break; + case Node.TEXT_NODE: // 3 + txt = aNode.nodeValue.replace(/\s+/g, " "); + break; + case Node.ELEMENT_NODE: // 1 + let localName = aNode.localName, + children = aNode.childNodes; + switch (localName) { + case "ul": + case "ol": + case "dl": + txt = "<br/>\n" + nodelist2txt(children, localName, aIndent + " ").join("") + "<br/>\n"; + break; + case "li": + txt = aIndent + (aParentTag == "ol" ? (" " + (aIndex+1)).slice(-2) + ". " : " * ").replace(" ", " ", "g") + + nodelist2txt(children, "li", aIndent).join("") + + "<br/>\n"; + break; + case "dt": + txt = aIndent + "<b>" + nodelist2txt(children, localName, aIndent) + "</b>:<br/>\n"; + break; + case "dd": + txt = aIndent + " " + nodelist2txt(children, localName, aIndent) + "<br/>\n"; + break; + case "br": + txt = "<br/>\n"; + break; + case "img": + txt = "<img src=" + aNode.src.quote() + " width=\"" + aNode.width + "\" height=\"" + aNode.height + "\"/>"; + break; + case "p": + txt = nodelist2txt(children, "p", "").join("") + "<br/>\n"; + break; + case "a": + if (aNode.hasAttribute("href") && aNode.href.indexOf("http") == 0) { + txt = "<a href=" + aNode.href.quote() + (aNode.title ? " title=" + aNode.title.quote() : "") + ">" + + nodelist2txt(children, "a", "").join("") + + "</a>"; + break; + } + default: + txt = '<' + localName + '>' + + nodelist2txt(children, localName, aIndent).join("") + + '</' + localName + '>'; + } + break; + } + return txt; + }, + }; + + function MakeElement (constructor, root) { + if (root && isDisplayed(root)) + return constructor(root); + } + + function Entry (root) { + let self = { + get root () root, + get permlink () [ + e + for ([, e] in Iterator(A(root.querySelectorAll('a')))) + if (!e.getAttribute('oid')) + ][0], + unfold: { + get comment () root.querySelector(S.currentEntry.unfold.comment), + get content () root.querySelector(S.currentEntry.unfold.content) + }, + get buttons () A(self.plusone.parentNode.querySelectorAll(S.role('button'))), + get commentButton () self.buttons[0], + get commentEditor () let (e = root.querySelector(S.editable)) (e && e.parentNode), + get comment() (self.commentEditor || self.commentButton), + get plusone () root.querySelector(S.typePlusone), + get share () self.buttons[1], + menu: { + get root () root.querySelector(S.role('menu')), + get items () A(self.menu.root.querySelectorAll(S.role('menuitem'))), + get mute () self.menu.items.slice(-2)[0] + }, + get menuButton () root.querySelector(S.currentEntry.menuButton), + get cancel () root.querySelector(S.currentEntry.cancel), + get submit () root.querySelector(S.currentEntry.submit) + }; + return self; + } + + function Dialog (root) { + function nButton (n) { + let bs = self.buttons; + if (bs.length === 2) + return bs[n]; + } + let self = { + get root () root, + get buttons () A(root.querySelectorAll(S.role('button'))), + get submit () nButton(0), + get cancel () nButton(1) + }; + return self; + } + + function Viewer (root) { + let self = { + get cancel () root.querySelector(S.closeButton), + get prev () root.querySelector(S.viewer.prev), + get next () root.querySelector(S.viewer.next) + }; + return self; + } + + function Notifications (root) { + let self = { + get root () root, + get visible () { + let h = parseInt(root.style.height, 10) > 0; + if (!h) + return false; + let nwc = plugins.googlePlusCommando.element.frames.notifications.root.contentDocument.querySelector('#nw-content'); + return parseInt(util.computedStyle(nwc).height, 10) > 100; + }, + summary: { + get root () root.contentDocument.querySelector(S.frames.notifications.summary.root), + get visible () isDisplayed(self.summary.root), + }, + entry: { + get root () self.summary.root.nextSibling, + get entries () A(root.contentDocument.querySelectorAll(S.frames.notifications.entry.entries)), + get current () self.entry.entries.filter(isDisplayed)[0], + get visible () isDisplayed(self.entry.root), + get prev () root.contentDocument.querySelector(S.frames.notifications.summary.prev), + get next () root.contentDocument.querySelector(S.frames.notifications.summary.next), + get back () root.contentDocument.querySelector(S.frames.notifications.summary.back), + get comment () self.entry.current.querySelector(S.frames.notifications.entry.comment), + get mute () self.entry.current.querySelector(S.frames.notifications.entry.mute), + get unfold () root.contentDocument.querySelector(S.currentEntry.unfold.comment) + } + }; + return self; + } + + /** + * NodeListの子をテキストにして配列で返す + * @param {NodeList} aChildNoes + * @param {String} aParentTag + * @param {String} aIndent + * @return {String[]} + */ + function nodelist2txt (aChildNodes, aParentTag, aIndent) { + var a = [], index = 0; + for (let i = 0, len = aChildNodes.length, child; child = aChildNodes[i]; ++i) { + let txt = Elements.node2txt(child, aParentTag, aIndent, index); + if (txt) { + a.push(txt); + ++index; + } + } + return a; + } + + return Elements; + })(); + + // }}} + + // Post Help {{{ + + const PostHelp = { + PanelID: 'google-plus-commando-help-panel', + + get panel () Elements.doc.querySelector('#' + PostHelp.PanelID), + + show: function () { + function move (panel) { + let contentHeight = document.getElementById('content').boxObject.height; + let rect = Elements.focusedEditor.editor.getClientRects()[0]; + if (rect.top < (contentHeight / 2)) { + panel.style.top = ''; + panel.style.bottom = '10px'; + } else { + panel.style.top = '10px'; + panel.style.bottom = ''; + } + } + + let doc = Elements.doc; + let parent = doc.body; + + let exists = PostHelp.panel; + if (exists) { + move(exists); + exists.style.display = 'block'; + return; + } + + let panel = doc.createElement('div'); + panel.setAttribute('id', PostHelp.PanelID); + let (ps = panel.style) { + ps.position = 'fixed'; + ps.left = '10px'; + ps.zIndex = 1000; + ps.backgroundColor = 'white'; + ps.border = 'solid 1px grey'; + } + panel.innerHTML = <> + <table> + <tr><th>入力</th> <th>効果</th> <th>解説</th> </tr> + <tr><td>*TEXT*</td> <td><b>TEXT</b></td> <td>太字</td> </tr> + <tr><td>_TEXT_</td> <td><i>TEXT</i></td> <td>斜体</td> </tr> + <tr><td>-TEXT-</td> <td><s>TEXT</s></td> <td>打ち消し線</td> </tr> + <tr><td>*-TEXT-*</td> <td><b><s>TEXT</s></b></td> <td>太字/打消。打消(-)は内側に書く</td> </tr> + <tr><td>-ねこ-</td> <td>☓</td> <td>日本語の打消はダメ</td> </tr> + <tr><td>-ね こ-</td> <td><s>ね こ</s></td> <td>英数字や半角スペースを入れたらOK</td> </tr> + <tr><td>-Aねこす-</td> <td><s>Aねこす</s></td> <td>英数字を前後に入れても良い</td> </tr> + </table> + </>; + + move(panel); + parent.appendChild(panel); + + return; + }, + + hide: function () { + let exists = PostHelp.panel; + if (exists) + exists.style.display = 'none'; + } + }; + + // }}} + + // Commands {{{ + + const Commands = { + moveEntry: function (next) { + let [arrow, vim, dir] = next ? ['<Down>', 'j', 'next'] : ['<Up>', 'k', 'prev']; + + if (Elements.viewer) + return click(Elements.viewer[dir]); + + let notifications = Elements.frames.notifications; + + if (notifications && notifications.visible && notifications.entry.visible) + return click(Elements.frames.notifications.entry[dir]); + + let arrowTarget = (function () { + if (notifications && notifications.visible) + return notifications.root.contentDocument.body; + + let menus = A(Elements.doc.querySelectorAll(S.role('menu', '[tabindex="0"]'))); + if (menus.length === 1) + return menus[0]; + })(); + + plugins.feedSomeKeys_3.API.feed.apply( + null, + arrowTarget ? [arrow, ['keypress'], arrowTarget] : [vim, ['vkeypress'], Elements.doc] + ); + }, + next: withCount(function () Commands.moveEntry(true)), + prev: withCount(function () Commands.moveEntry(false)), + comment: function () { + let after = PostHelp.show; + let notifications = Elements.frames.notifications; + if (notifications && notifications.visible && notifications.entry.visible) { + let e = notifications.entry.current; + e.scrollTop = e.scrollHeight; + click(notifications.entry.comment, after); + } else { + let entry = Elements.currentEntry; + click(entry.comment, after); + } + }, + plusone: function () click(Elements.currentEntry.plusone), + share: function () click(Elements.currentEntry.share), + post: function () { + buffer.scrollTop(); + click(Elements.post.open, PostHelp.show); + }, + yank: function () { + let e = Elements.currentEntry.permlink; + if (!e) + liberator.echoerr('Not found permlink'); + util.copyToClipboard(e.href); + liberator.echo('Copy the permlink to clipboard: ' + e.href); + }, + notification: function () { + click(Elements.notification); + }, + cancel: function () { + let notifications = Elements.frames.notifications; + if (notifications && notifications.visible && notifications.entry.visible) + return click(notifications.entry.back); + + for (let [, n] in Iterator(['dialog', 'viewer'])) { + let e = Elements[n]; + if (e && e.cancel) + return click(e.cancel); + } + + if (Elements.frames.notifications.visible) + return click(Elements.notification); + + click(Elements.doc.body); + }, + submit: function () { + if (liberator.focus) + return; + PostHelp.hide(); + click(Elements.focusedEditor.button.submit); + }, + unfold: function () { + let notifications = Elements.frames.notifications; + if (notifications && notifications.visible && notifications.entry.visible) + return click(notifications.entry.unfold); + + click(Elements.currentEntry.unfold.comment); + click(Elements.currentEntry.unfold.content); + }, + menu: function () { + click(Elements.currentEntry.menuButton); + }, + mute: function () { + let notifications = Elements.frames.notifications; + if (notifications && notifications.visible && notifications.entry.visible) + return click(notifications.entry.mute); + click(Elements.currentEntry.menu.mute); + }, + open: function () { + function clicks (links) { + if (links.length < 1) + throw GPCError('No link.'); + + if (links.length === 1) + return click(links[0]); + + let t = {}; + A(links).forEach(function (link) (t[link.textContent] = link)); + + commandline.input( + 'Select a link', + function (url) { + let link = t[url]; + if (link) { + click(link); + } else { + liberator.open(url, liberator.NEW_TAB); + } + }, + { + completer: function (context) { + context.completions = [ + [link.href, link.textContent] + for ([, link] in IA(links)) + ]; + } + } + ); + } + + let ce = Elements.currentEntry; + if (!ce) + return; + + let dct = ce.root.querySelector('div[data-content-type]'); + if (dct) { + if (!/application\/x-shockwave-flash/.test(dct.getAttribute('data-content-type'))) + return click(dct); + + let links = dct.parentNode.querySelectorAll('a'); + return clicks(links); + } + + let links = ce.root.querySelectorAll('a.ot-anchor'); + clicks(links); + } + }; + + // }}} + + // Define mappiings {{{ + + (function () { + + const MatchingUrls = RegExp('^https://plus\\.google\\.com/*'); + const MappingDescriptionSuffix = ' - Google plus Commando'; + + function defineMapping (mode, cmd) { + let gv = + liberator.globalVariables[ + 'gplus_commando_map_' + + cmd.replace(/[A-Z]/g, function (m) ('_' + m.toLowerCase())) + ]; + if (!gv) + return; + let func = Commands[cmd]; + mappings.addUserMap( + [mode], + gv.split(/\s+/), + cmd + MappingDescriptionSuffix, + function (count) { + try { + func(count); + } catch (e if (e instanceof GPCError)) { + /* DO NOTHING */ + } catch (e) { + liberator.log(e); + liberator.log(e.stack); + throw e; + } + }, + { + count: func.length === 1, + matchingUrls: MatchingUrls + } + ); + } + + 'comment plusone share next prev post yank notification cancel unfold menu mute open'.split(/\s/).forEach(defineMapping.bind(null, modes.NORMAL)); + 'submit'.split(/\s/).forEach(defineMapping.bind(null, modes.INSERT)); + + mappings.addUserMap( + [modes.INSERT], + ['<Esc>'], + 'Escape from input area', + function () { + if (liberator.focus) { + let esc = mappings.getDefault(modes.NORMAL, '<Esc>'); + esc.action.apply(esc, arguments); + } else { + click(Elements.focusedEditor.button.cancel); + // FIXME + window.document.commandDispatcher.advanceFocus(); + modes.reset(); + PostHelp.hide(); + } + }, + { + matchingUrls: MatchingUrls + } + ); + + })(); + + // }}} + + // Define hints {{{ + + (function () { + + const HintStyleName = 'google-plus-commando-hint'; + + function s2x (s) + s.replace(/^\./, ''); + + [ + ['o', 'f', function (e) click(e)], + ['t', 'F', function (e) buffer.followLink(e, liberator.NEW_TAB)], + ].forEach(function ([modeChar, mapKey, action]) { + let modeName = 'google-plus-comando-hint-' + modeChar; + + hints.addMode( + modeName, + hints._hintModes[modeChar].prompt, + function (elem, count) { + function mouseEvent (name) { + let evt = elem.ownerDocument.createEvent('MouseEvents'); + evt.initMouseEvent(name, true, true, elem.ownerDocument.defaultView, 0, 0, 0, 0, 0, false, false, false, false, 0, null); + elem.dispatchEvent(evt); + } + + let plusone = (elem.getAttribute('g:entity') || '').indexOf('buzz:') === 0; + if (plusone) + mouseEvent('mouseover'); + action(elem, count); + if (plusone) + mouseEvent('mouseout'); + }, + function () { + function removeRoot (s) + s.replace(/^\s*\/\//, ''); + + const roots = [ + {get visible () !!Elements.viewer, selector: ('div[contains(@class, "' + s2x(S.viewer.root) + '")]')}, + {get visible () !!Elements.dialog, selector: ('div[contains(@class, "' + s2x(S.dialog.root) + '")]')}, + {get visible () !!Elements.frames.notifications.visible, selector: 'id("nw-content")'} + ]; + + let xpath = options['hinttags'].split(/\s*\|\s*/).map(removeRoot).concat(X.hints); + + for (let [, root] in Iterator(roots)) { + if (!root.visible) + continue; + xpath.push(String(<>div[contains(@class, "{s2x(S.closeButton)}")]</>)); + xpath = xpath.map(function (it) (root.selector + '//' + it)); + break; + } + + styles.addSheet(false, HintStyleName, 'plus\\.google\\.com', S.plusone + '{ display: inline !important }'); + + return xpath.map(function (it) (/^id\(/.test(it) ? it : '//' + it)).join(' | '); + } + ); + + mappings.addUserMap( + [modes.NORMAL], + [liberator.globalVariables['gplus_commando_map_hint_' + modeChar] || mapKey], + 'Hit a hint - Google plus Commando', + function () hints.show(modeName), + { + matchingUrls: RegExp('^https://plus\\.google\\.com/.*') + } + ); + }); + + plugins.libly.$U.around( + hints, + 'hide', + function (next) { + setTimeout(function () styles.removeSheet(false, HintStyleName, 'plus\\.google\\.com'), 0); + return next(); + }, + true + ); + + hints.addMode( + 'G', + 'Google+ Post', + function action (elm) { + var src = elm.src; + commandline.open('', 'googleplus -i ' + src + ' ', modes.EX); + }, + function getPath () util.makeXPath(['img']) + ); + + })(); + + // }}} + + // Define Google+ post command {{{ + + (function () { + + let HOME_URL = 'https://plus.google.com/', + POST_URL_BASE = 'https://plus.google.com/u/0/_/sharebox/post/'; + + /** + * ${RUNTIMEPATH}/info/{profileName}/googlePlus のデータ取得/保存 + * @type {Object} + */ + let store = storage.newMap('googlePlus', {store: true}); + + commands.addUserCommand( + ['gp', 'googleplus'], + 'Google+', + function (args) { + // ---------------------- + // -setup オプション + // ---------------------- + if ('-setup' in args) { + setupGooglePlus(); + return; + } + + let message = args[0] || '', + acls = null; + + // ---------------------- + // -link オプション + // ---------------------- + let win = null; + if ('-link' in args) { + win = content; + } + // ---------------------- + // -imageURL オプション + // ---------------------- + let image = null; + if ('-imageURL' in args) { + image = args['-imageURL']; + } + + // ---------------------- + // -to オプション + // ---------------------- + if ('-to' in args && args['-to'].indexOf('anyone') == -1) + acls = [acl for ([,acl] in Iterator(store.get('CIRCLES', []))) if (args['-to'].indexOf(acl[0]) != -1)]; + + // 引数が何も無い場合は、Google+のページへ + if (!message && !win && !image) { + let tab = getGooglePlusTab(); + if (tab) + gBrowser.mTabContainer.selectedItem = tab; + else + liberator.open(HOME_URL, {where: liberator.NEW_TAB}); + + return; + } + + window.setTimeout(function () { + let pd = new PostData(message, win, image, acls); + postGooglePlus(pd, true); + }, 0); + }, { + literal: 0, + options: [ + [['-link', '-l'], commands.OPTION_NOARG], + [['-imageURL', '-i'], commands.OPTION_STRING], + [['-to', '-t'], commands.OPTION_LIST, null, + function (context, args) { + let [, prefix] = context.filter.match(/^(.*,)[^,]*$/) || []; + if (prefix) + context.advance(prefix.length); + + return [['anyone', 'to public']].concat([v for ([, v] in Iterator(store.get('CIRCLES', [])))]); + }], + [['-setup'], commands.OPTION_NOARG], + ], + },true); + + /** + * Google+のページから必要データを保存する + * @param {function} onComplete + * @return {Boolean} + */ + function setupGooglePlus (onComplete) { + function onSuccess () { + if (onComplete) + onComplete(); + liberator.echomsg('Initialized: googleplus'); + } + + function onFail () { + liberator.echoerr('Faild: initialize googleplus'); + } + + function getFromWindow (win) { + let data = win.OZ_initData; + if (!data) + return false; + // XXX 全てのデータが揃っていないケースがあるようなので、検査する + try { + store.set('UID', data[2][0]); + store.set('AT', data[1][15]); + let circles = data[12][0]; + // CIRCLES[]: [[Name, Description, ID], ...] + store.set('CIRCLES', [ + ['circles', 'Everyone in your circles', '1c'], + ['excircles', 'Everyone in your circles, plus all the people in their circles', '1f'], + ].concat([[c[1][0],c[1][2],c[0][0]] for each(c in circles.slice(0, circles.length / 2))])); + onSuccess(); + return true; + } catch (e) { + liberator.log(e); + return false; + } + } + + // XXX ブラチラ大作戦 + function getFromMakedBrowser () { + let browser = document.createElementNS(XUL, 'browser'); + browser.setAttribute('type', 'content'); + browser.setAttribute('src', 'https://plus.google.com/'); + document.getElementById('main-window').appendChild(browser); + + browser.addEventListener( + 'DOMContentLoaded', + function (e) { + if (e.target !== browser.contentWindow.document) + return; + browser.removeEventListener('DOMContentLoaded', arguments.callee, false); + getFromWindow(browser.contentWindow.wrappedJSObject); + browser.parentNode.removeChild(browser); + }, + false + ); + } + + let found = false; + + let tab = getGooglePlusTab(); + if (tab) + found = getFromWindow(tab.linkedBrowser.contentWindow.wrappedJSObject); + + if (!found) + getFromMakedBrowser(); + } + + /** + * Google+のタブを取ってくる + * @return {Element|null} + */ + function getGooglePlusTab () { + let tabs = gBrowser.tabs; + for (let i = 0, tab; tab = tabs[i]; ++i) { + if (tab.linkedBrowser.currentURI.spec.indexOf(HOME_URL) == 0) { + return tab; + } + } + return null; + } + + /** + * Post to Google+ + * @param {PostData} aPostData + * @param {boolean} resetup + */ + function postGooglePlus (aPostData, aRetry) { + let data = aPostData.getPostData(); + let queries = []; + for (let key in data) + queries.push(key + '=' + encodeURIComponent(data[key])); + + let xhr = new XMLHttpRequest(); + xhr.mozBackgroundRequest = true; + xhr.open('POST', aPostData.POST_URL, true); + xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded;charset=UTF-8'); + xhr.setRequestHeader('Origin', HOME_URL); + + xhr.onreadystatechange = function (aEvent) { + let xhr = aEvent.target, + msg = 'Google+: ', + XBW = window.XULBrowserWindow; + if (xhr.readyState == 4) { + let ok = xhr.status == 200; + msg += ok ? 'Posted' : 'Post failed (' + xhr.statusText + ')'; + if (!ok && aRetry) { + msg += ' ... Retry'; + setupGooglePlus(postGooglePlus.bind(null, aPostData, false)); + } + window.setTimeout(function (XBW, msg) { + if (XBW.jsDefaultStatus.indexOf('Google+:') == 0) + XBW.setJSDefaultStatus(''); + }, 2000, XBW, msg); + } else { + msg += 'sending...'; + } + liberator.log(msg, 0); + XBW.setJSDefaultStatus(msg); + }; + + xhr.send(queries.join('&')); + } + + XPCOMUtils.defineLazyServiceGetter(__context__, 'MIME', '@mozilla.org/mime;1', 'nsIMIMEService'); + + /** + * Google+への送信データ生成 + * @Constructor + * @param {String} aMessage + * @param {Window} [aWindow] 現ページのWindowオブジェクト + * @param {String} [aImageURL] 表示させたい画像URL + * @param {Array} [aACLs] ACL[] + */ + function PostData () { this.init.apply(this, arguments); } + PostData.sequence = 0; + PostData.prototype = { + init: function PD_init (aMessage, aWindow, aImageURL, aACLs) { + this.message = aMessage; + this.window = aWindow; + this.imageURL = aImageURL; + + this.UID = store.get('UID', null); + liberator.assert(this.UID, 'Google+ Error: UID is not set. Please login and `:googleplus -setup\''); + this.AT = store.get('AT', null); + liberator.assert(this.AT, 'Google+ Error: AT is not set. Please login and `:googleplus -setup\''); + + this.setACLEnties(aACLs); + }, + get token () { + let t = 'oz:' + this.UID + '.' + this.date.getTime().toString(16) + '.' + this.sequence.toString(16); + Object.defineProperty(this, 'token', {value: t}); + return t; + }, + get date () { + let d = new Date; + Object.defineProperty(this, 'date', {value: d}); + return d; + }, + get sequence () { + let s = PostData.sequence++; + Object.defineProperty(this, 'sequence', {value: s}); + return s; + }, + get reqid () { + let r = this.date.getHours() + 3600 + this.date.getMinutes() + 60 + this.date.getSeconds() + this.sequence * 100000; + Object.defineProperty(this, 'reqid', {value: r}); + return r; + }, + get POST_URL () { + let url = POST_URL_BASE + '?_reqid=' + this.reqid + '&rt=j'; + Object.defineProperty(this, 'POST_URL', {value: url}); + return url + }, + aclEntries: [{ + scope: { + scopeType: 'anyone', + name: 'Anyone', + id: 'anyone', + me: true, + requiresKey: false + }, + role: 20, + }, { + scope: { + scopeType: 'anyone', + name: 'Anyone', + id: 'anyone', + me: true, + requiresKey: false, + }, + role: 60 + }], + setACLEnties: function PD_setACLEnties (aACLs) { + if (!aACLs || aACLs.length == 0) + return this.aclEntries = Object.getPrototypeOf(this).aclEntries; + + let entries = []; + for (let i = 0, len = aACLs.length; i < len; ++i) { + let acl = aACLs[i]; + let scope = { + scopeType: 'focusGroup', + name: acl[0], + id: this.UID + '.' + acl[2], + me: false, + requiresKey: false, + groupType: 'p' + }; + entries.push({scope: scope, role: 60}); + entries.push({scope: scope, role: 20}); + } + return this.aclEntries = entries; + }, + getPostData: function PD_getPostData () { + let spar = [v for each(v in this.generateSpar())]; + return { + spar: JSON.stringify(spar), + at : this.AT + }; + }, + generateSpar: function PD_generateSpar() { + for (let i = 0, len = 17; i < len; ++i) { + switch (i) { + case 0: + yield this.message; + break; + case 1: + yield this.token; + break; + case 6: + if (!this.window && !this.imageURL) { + yield null; + } else { + let media = LinkDetector.get(this.window, this.imageURL); + let data = [JSON.stringify(media.generateData())]; + if (media.hasPhoto) { + data.push(JSON.stringify(media.generateData(true))); + } + yield JSON.stringify(data); + } + + break; + case 8: + yield JSON.stringify({aclEntries: this.aclEntries}); + break; + case 9: + case 11: + case 12: + yield true; + break; + case 15: + case 16: + yield false; + break; + case 10: + case 14: + yield []; + break; + default: + yield null; + break; + } + } + }, + }; + + })(); + + const LinkDetector = (function () { + let commonProto = { + init: function (win, imageURL) { + this.window = win; + this.imageURL = imageURL; + if (imageURL) { + if (win) + this.hasPhoto = true; + + this.setupImage(); + } + }, + type: { + TITLE: 3, + MEDIA_LINK: 5, + UPLOADER: 9, + TEXT: 21, + TYPE: 24, + IMAGE: 41, + PROVIDER: 47, + }, + generateData: function (isPhoto) { + let data = new Array(48); + data[this.type.TITLE] = this.getTitle(isPhoto); + data[this.type.MEDIA_LINK] = this.getMediaLink(isPhoto); + data[this.type.UPLOADER] = this.getUploader(isPhoto); + data[this.type.TEXT] = this.getContentsText(isPhoto); + data[this.type.TYPE] = this.getMediaType(isPhoto); + data[this.type.IMAGE] = this.getMediaImage(isPhoto); + data[this.type.PROVIDER] = this.getProvider(isPhoto); + return data; + }, + hasPhoto: false, + imageElement: null, + setupImage: function () { + let imgs = content.document.images; + for (let i = 0, len = imgs.length, img; img = imgs[i]; ++i) { + if (img.src == this.imageURL) { + this.imageElement = img; + } + } + }, + getMimeType: function (uri, defaultType) { + if (!(uri instanceof Ci.nsIURI)) + uri = util.createURI(uri); + + try { + return MIME.getTypeFromURI(uri); + } catch (e) {} + return defaultType; + }, + getTitle: function (isPhoto) { + return (isPhoto || !this.window) ? null : this.window.document.title; + }, + getContentsText: function (isPhoto) { + if (!this.window || isPhoto) + return null; + + let sel = this.window.getSelection(); + if (sel.isCollapsed) + return ''; + + let sels = []; + for (let i = 0, count = sel.rangeCount; i < count; ++i) { + let r = sel.getRangeAt(i), + fragment = r.cloneContents(); + sels.push(Elements.node2txt(fragment, r.commonAncestorContainer.localName)); + } + return sels.join('<br/>(snip)<br/>'); + }, + getUploader: function () [], + getMediaLink: function (isPhoto) { + if (this.window && !isPhoto) + return [null, this.window.location.href]; + + let data = [null, this.imageURL]; + if (this.imageElement) + data.push(this.imageElement.height, this.imageElement.width); + + return data; + }, + getMediaType: function (isPhoto) { + if (isPhoto) { + let type = this.getMimeType(this.imageURL, 'image/jpeg'); + let data = [null, this.imageURL, null, type, 'photo', null,null,null,null,null,null,null]; + if (this.imageElement) + data.push(this.imageElement.width, this.imageElement.height); + else + data.push(null,null); + + return data; + } + if (this.window && !isPhoto) { + type = this.window.document.contentType; + switch (type.split('/')[0]) { + case 'image': + return [null, this.window.location.href, null, type, 'image']; + case 'text': + default: + return [null, this.window.location.href, null, 'text/html', 'document']; + } + } else if (this.imageURL) { + type = this.getMimeType(this.imageURL, 'image/jpeg'); + return [null, this.imageURL, null, type, 'image']; + } + return null + }, + getMediaImage: function (isPhoto) { + let url; + if (this.window && !isPhoto) { + let type = this.window.document.contentType.split('/'); + if (type[0] != 'image') { + let host = this.window.location.host; + url = '//s2.googleusercontent.com/s2/favicons?domain=' + host; + return [ [null, url, null, null], [null, url, null, null] ]; + } else { + url = this.window.location.href; + return [ [null, url, null, null], [null, url, null, null] ]; + } + } + + let data = [null, this.imageURL]; + let w = null, h = null; + if (this.imageElement) { + w = this.imageElement.width, h = this.imageElement.height; + w = w / h * 120; + h = 120; + } + data.push(h, w); + return [ data, data ]; + }, + getProvider: function (isPhoto) { + return [ [null, (isPhoto ? 'images' : ''), 'http://google.com/profiles/media/provider'] ]; + } + }; + let classes = {}, checker = {}; + function MediaLink() { this.init.apply(this, arguments); }; + MediaLink.prototype = commonProto; + + let self = { + addType: function (name, checkFunc, proto) { + checker[name] = checkFunc; + let func = function () { this.init.apply(this, arguments); }; + proto.__super__ = proto.__proto__ = commonProto; + func.prototype = proto; + classes[name] = func; + }, + get: function (aWindow, aImageURL) { + for (let [key, checkFunc] in Iterator(checker)) { + if (checkFunc(aWindow, aImageURL)) { + return new classes[key](aWindow, aImageURL); + } + } + return new MediaLink(aWindow, aImageURL); + } + }; + + (function () { + // ------------------------------------------------------------------------- + // YouTube + // ----------------------------------------------------------------------{{{ + self.addType('youtube', + function (win) { + if (!win) return false; + + return /^https?:\/\/(?:.*\.)?youtube.com\/watch/.test(win.location.href); + }, { + get VIDEO_ID () { + let id = this.window.wrappedJSObject.yt.config_.VIDEO_ID; + Object.defineProperty(this, 'VIDEO_ID', {value: id}); + return id; + }, + getMediaLink: function () [null, 'http://www.youtube.com/v/' + this.VIDEO_ID + '&hl=en&fs=1&autoplay=1'], + getContentsText: function () this.window.document.querySelector('meta[name=description]').content, + getMediaType: function () [null, this.window.location.href, null, 'application/x-shockwave-flash', 'video'], + getMediaImage: function () { + let url = 'https://ytimg.googleusercontent.com/vi/' + this.VIDEO_ID + '/hqdefault.jpg'; + return [ [null, url, 120, 160], [null, url, 120, 160] ]; + }, + getProvider: function () [ [null, 'youtube', 'http://google.com/profiles/media/provider'] ], + }); // }}} + // ------------------------------------------------------------------------- + // Gyazo + // ----------------------------------------------------------------------{{{ + self.addType('gyazo', + function (win, image) { + let reg = /^http:\/\/gyazo\.com\/\w+(\.png)?/; + return reg.test(image); + }, { + init: function (win, imageURL) { + this.window = win; + if (imageURL.lastIndexOf('.png') != imageURL.length - 4) + imageURL += '.png'; + + this.imageURL = imageURL; + this.hasPhoto = true; + }, + }); + // }}} + })(); + return self; + })(); + + // }}} + + // Export {{{ + + __context__.command = Commands; + __context__.element = Elements; + __context__.selector = S; + __context__.rule = R; + __context__.linkDetector = LinkDetector; + + // }}} + + // Event {{{ + + // リロードしたら、ページ構成変わってるやん!!!みたいなの対応 + events.addSessionListener( + document.getElementById('appcontent'), + 'DOMContentLoaded', + function (event) { + let doc = event.originalTarget; + // XXX G+ 内の他のページへ跳ぶときは、plus.google.com を経由するが、それは除外する + if ( + doc instanceof HTMLDocument && + !doc.defaultView.frameElement && + doc.location.host === 'plus.google.com' && + doc.body && + doc.body.children.length + ) { + __context__.selector._clearCache(); + } + }, + true + ); + + // 謎のキーが効かなくなるバグへの対応 + autocommands.add( + 'LocationChange', + /https:\/\/plus\.google\.com\//, + function () { + if (!(window.content.document instanceof HTMLDocument)) + return; + + (function findFrames(frame) { + if (frame.document.body instanceof HTMLBodyElement) + frame.focus(); + Array.forEach(frame.frames, findFrames); + }(window.content)); + } + ); + + // }}} + +})(); + +// vim:sw=2 ts=2 et si fdm=marker: diff --git a/google-translator.js b/google-translator.js index 63eb1f2..cd7204f 100644 --- a/google-translator.js +++ b/google-translator.js @@ -52,7 +52,7 @@ let PLUGIN_INFO = // INFO {{{ let INFO = <plugin name="Google Translator" version="1.0.0" - href="http://svn.coderepos.org/share/lang/javascript/vimperator-plugins/trunk/google-translator.js" + href="http://github.com/vimpr/vimperator-plugins/blob/master/google-translator.js" summary="Translate with Google AJAX Language API" xmlns="http://vimperator.org/namespaces/liberator"> <author email="anekos@snca.net">anekos</author> @@ -198,11 +198,16 @@ let INFO = ); function textCompleter (context, args) { - context.completions = [ - [it, ''] - for ([, it] in Iterator(getTexts())) - if (it.length > 3 && !/^\s*</(it)) - ]; + if (!liberator.globalVariables.google_translator_text_completer) + return; + let i = 0, cs = []; + for (let [, it] in Iterator(getTexts())) { + if (++i > 100) + break; + if (it.length > 3 && !/^\s*</.test(it)) + cs.push([it, '']); + } + context.completions = cs; } function guessRequest (text, done) { @@ -280,7 +285,7 @@ let INFO = let actions = { echo: - liberator.echo, + function (text) liberator.echo('<div style="white-space:normal">' + text + '</div>', commandline.FORCE_MULTILINE), insert: // FIXME 見えない要素相手だとうまくいかない function (text) { diff --git a/happy_hacking_vimperator.js b/happy_hacking_vimperator.js index efa618b..1b0aa79 100644 --- a/happy_hacking_vimperator.js +++ b/happy_hacking_vimperator.js @@ -36,7 +36,7 @@ let PLUGIN_INFO = <VimperatorPlugin> <name>Happy Happy Vimperator</name> <description>This plugin makes you to True Vimperatorer</description> - <version>2.5.1</version> + <version>2.5.2</version> <author mail="anekos@snca.net" homepage="http://d.hatena.ne.jp/nokturnalmortum/">anekos</author> <minVersion>3.0</minVersion> <maxVersion>3.1</maxVersion> @@ -3322,7 +3322,8 @@ let PLUGIN_INFO = around(events, 'onKeyPress', function (next, [event]) { let keyStr = events.toString(event); - if (!events.feedingKeys && uncleanKeys.some(function(v) v == keyStr)) { + let fmaping = let (fmap = liberator.plugins.feedSomeKeys_3) (fmap && fmap.API.State.feeding); + if (!fmaping && !events.feedingKeys && uncleanKeys.some(function(v) v == keyStr)) { return kill('key')(event); } next(); diff --git a/hatebuWatchDog.js b/hatebuWatchDog.js index 476cbf4..0a683e4 100644 --- a/hatebuWatchDog.js +++ b/hatebuWatchDog.js @@ -119,13 +119,13 @@ let publics = plugins.hatebuWatchDog = (function() { ); let currentValue; - req.addEventListener("onSuccess", function(data) { + req.addEventListener("success", function(data) { liberator.log("XML-RPC request was succeeded."); let resXml = new XML(data.responseText.replace(/^<\?xml version[^>]+?>/, '')); currentValue = window.eval(resXml..int.toString()); onSuccess(currentValue); }); - req.addEventListener("onFailure", function(data) { + req.addEventListener("failure", function(data) { onFailure(); }); liberator.log("reauest..."); diff --git a/hatena-bookmark-search.js b/hatena-bookmark-search.js index 746843f..22fc58d 100644 --- a/hatena-bookmark-search.js +++ b/hatena-bookmark-search.js @@ -245,7 +245,7 @@ HatenaBookmark.Cache = { get store() {
if (!this._store) {
let key = 'plugins-hatena-bookmark-search-data';
- this._store = storage.newMap(key, true);
+ this._store = storage.newMap(key, {store: true});
}
return this._store;
},
diff --git a/hd-youkai-youtube.js b/hd-youkai-youtube.js deleted file mode 100644 index 792ad09..0000000 --- a/hd-youkai-youtube.js +++ /dev/null @@ -1,52 +0,0 @@ -// ==VimperatorPlugin== -// @name YouTube HD -// @description High-Quality Movie Monster YoUTuBe -// @description-ja 高画質妖怪ようつべ -// @license Creative Commons 2.1 (Attribution + Share Alike) -// @version 1.0 -// @author anekos (anekos@snca.net) -// @minVersion 2.0pre -// @maxVersion 2.0pre -// ==/VimperatorPlugin== -// -// Links: -// - -(function () { - - function monsterize (url) { - if (url.match(/&fmt=22/)) - return url; - if (url.match(/^http:\/\/(?:[^.]+\.)?youtube\.com\/watch/)) - return url + '&fmt=22'; - let m = url.match(/^http:\/\/(?:[^.]+\.)?youtube\.com\/.*\?.*v=([^&]+)/); - if (m) - return 'http://www.youtube.com/watch?v=' + m[1] + '&fmt=22'; - return url; - } - - let original = liberator.plugins.hd_youkai_youtube; - if (!original) { - liberator.plugins.youtubehd = original = { - open: liberator.open, - followLink: buffer.followLink - }; - } - - liberator.open = function (urls) { - if (typeof urls === 'string') - arguments[0] = monsterize(urls); - else - arguments[0] = urls.map(monsterize); - return original.open.apply(this, arguments); - }; - - buffer.followLink = function (elem) { - if (elem.href) - elem.href = monsterize(elem.href); - original.followLink.apply(this, arguments); - }; - -})(); - -// vim:sw=2 ts=2 et si fdm=marker: diff --git a/hints-for-embedded.js b/hints-for-embedded.js index 221e8fd..d4bc451 100644 --- a/hints-for-embedded.js +++ b/hints-for-embedded.js @@ -35,8 +35,8 @@ THE POSSIBILITY OF SUCH DAMAGE. // INFO {{{ let INFO = <> - <plugin name="HintsForEmbeded" version="1.4.1" - href="http://svn.coderepos.org/share/lang/javascript/vimperator-plugins/trunk/hints-for-embedded.js" + <plugin name="HintsForEmbeded" version="1.5.1" + href="http://github.com/vimpr/vimperator-plugins/blob/master/hints-for-embedded.js" summary="Add the hints mode for embedded objects." lang="en-US" xmlns="http://vimperator.org/namespaces/liberator"> @@ -79,8 +79,8 @@ let INFO = </description> </item> </plugin> - <plugin name="HintsForEmbeded" version="1.4.1" - href="http://svn.coderepos.org/share/lang/javascript/vimperator-plugins/trunk/hints-for-embedded.js" + <plugin name="HintsForEmbeded" version="1.5.0" + href="http://github.com/vimpr/vimperator-plugins/blob/master/hints-for-embedded.js" summary="埋め込み(embed)オブジェクト用ヒントモード" lang="ja" xmlns="http://vimperator.org/namespaces/liberator"> @@ -147,6 +147,12 @@ let INFO = value: /(?:v|wv_id)=([a-z]{2}\d{1,10})/, url: function (id) ('http://www.nicovideo.jp/watch/' + id) }, + youtube_iframe: { + site: /youtube/, + name: /^src$/, + value: /http:\/\/www\.youtube\.com\/(?:embed|v)\/([-a-zA-Z0-9_]+)/, + url: function (id) ('http://www.youtube.com/watch?v=' + id) + }, youtube: { site: /youtube/, name: /.*/, @@ -185,7 +191,7 @@ let INFO = if (elem.tagName === 'IMG' && elem.src) { if (openParent) { let p = elem.parentNode; - if (p.tagName === 'A' && /(gif|png|jpe?g)$/i(p.href)) + if (p.tagName === 'A' && /(gif|png|jpe?g)$/i.test(p.href)) return liberator.open(p.href, liberator.NEW_TAB); } return liberator.open(elem.src, liberator.NEW_TAB); @@ -201,7 +207,7 @@ let INFO = if (site) { for each (let [n, v] in info) { [n, v] = [String(n), String(v)]; - if (site.name && !site.name(n)) + if (site.name && !site.name.test(n)) continue; let m = n.match(site.value) || v.match(site.value); if (m) @@ -209,7 +215,7 @@ let INFO = } } - let urls = info.filter(function ([n, v]) /^https?:\/\//(v)); + let urls = info.filter(function ([n, v]) /^https?:\/\//.test(v)); if (!urls.length) return liberator.echoerr('Could not found URL'); @@ -233,7 +239,7 @@ let INFO = function (elem) { liberator.open(open(elem), where); }, - function () '//embed | //object | //img' + function () '//embed | //object | //img | //iframe' ); commands.addUserCommand( diff --git a/i_love_echo.js b/i_love_echo.js index 41de89b..a2f9ab1 100644 --- a/i_love_echo.js +++ b/i_love_echo.js @@ -203,6 +203,7 @@ createPrototype($s, { var s = [i < hash.length ? toHexString(hash.charCodeAt(i)) : "" for (i in hash)].join(""); return $(s); }, + get escpateRegex() $(util.escapeRegex(this.value)), s: function(from, to) $(this.value.replace(from, to)), split: function(reg) $(this.value.split(reg)), get toJSON(){ diff --git a/jquery-loader.js b/jquery-loader.js index c8ad4b6..9248e9e 100644 --- a/jquery-loader.js +++ b/jquery-loader.js @@ -58,7 +58,7 @@ let PLUGIN_INFO = let INFO = <> <plugin name="jQueryLoader" version="1.0.1" - href="http://svn.coderepos.org/share/lang/javascript/vimperator-plugins/trunk/jquery-loader.js" + href="http://github.com/vimpr/vimperator-plugins/blob/master/jquery-loader.js" summary="jQuery Loader" lang="en-US" xmlns="http://vimperator.org/namespaces/liberator"> diff --git a/ldrize_cooperation.js b/ldrize_cooperation.js index 35952e4..7a361c8 100644 --- a/ldrize_cooperation.js +++ b/ldrize_cooperation.js @@ -1,425 +1,426 @@ -// Vimperator plugin: 'Cooperation LDRize Mappings'
-// Version: 0.25
-// Last Change: 07-Jan-2011. Jan 2008
-// License: Creative Commons
-// Maintainer: Trapezoid <trapezoid.g@gmail.com> - http://unsigned.g.hatena.ne.jp/Trapezoid
-//
-// Cooperation LDRize Mappings for Vimperator
-//
-// Variables:
-// g:ldrc_captureMapping
-// Specifies keys that capture by LDRize
-// usage: let g:ldrc_captureMappings = "['j','k','p','o','?']"
-// g:ldrc_enable
-// LDRize Cooperation be Enable by default or not
-// usage: let g:ldrc_enable = "false"
-// default: true
-// g:ldrc_hints
-// Narrows "hinttags" based on Siteinfo.
-// usage: let g:ldrc_hints = "true"
-// default: false
-// g:ldrc_intelligence_bind
-// More inteligence cooperation bind
-// usage: let g:ldrc_intelligence_bind = "true"
-// default: false
-// g:ldrc_skip
-// length in which paragraph is skipped (use by inteligence bind mode)
-// usage: let g:ldrc_hints = "true"
-// default: 0.5
-// Mappings:
-// Mappings for LDRize
-// default: 'j','k','p','o'
-// Commands:
-// 'm' or 'mb' or 'minibuffer':
-// Execute args as Minibuffer Command
-// usage: :minibuffer pinned-link | open | clear-pin
-// 'pin':
-// View pinned link list
-// usage: :pin
-// 'pindownload':
-// Download View pinned link by handler function or outer promgram. please see 'handlerInfo' also
-// usage: :pindownload
-// 'ldrc' or 'toggleldrizecooperation':
-// Toggle LDRize Cooperation
-// usage: :toggleldrizecooperation
-// Hints:
-// ';l':
-// narrow down the candidates to LDRize paragraphes
-// ';L':
-// narrow down the candidates to LDRize paragraphes (in a new tab)
-// Options:
-// 'ldrc'
-// Enable LDRize Cooperation
-// usage: :set ldrc
-// 'noldrc'
-// Disable LDRize Cooperation
-// usage: :set noldrc
-//
-// 'ldrchints'
-// Enable "Narrows Hinttags based on Siteinfo"
-// usage: :set ldrchints
-// 'noldrchints'
-// Disable "Narrows Hinttags based on Siteinfo"
-// usage: :set noldrchints
-if (liberator.plugins.LDRizeCooperation == undefined) (function(){
- //pattern: wildcard
- //include: [regexp,option] or regexp
- //handler: [programPath,[args]] or programPath or function(url,title)
- var handlerInfo = [
- //{
- // pattern: "http://www.nicovideo.jp/*",
- // handler: {
- // download: ["c:\\usr\\SmileDownloader\\SmileDownloader.exe",["%URL%"]]
- // },
- // wait: 5000
- //},
- //{
- // handler: ["C:\\usr\\irvine\\irvine.exe",["%URL%"]],
- //},
- ];
- const DISABLE_ICON = "data:image/png;base64,"
- +"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAACXBIWXMAAA7E"
- +"AAAOxAGVKw4bAAACL0lEQVR4nF2Sy0tUYRjGf9+Z4/HMjJfjBUZEMM2MSDII"
- +"REjSVtVecBFZi6Bdi4RW/SFBq2oR0R8gSaUJhVJIBkEEMZOWl5kuM+fqnPN9"
- +"52sxQ4kPv837Pu+zel4xMjkz/3h5p87pbhyDw4o1mzUOkubYbvLo2kVx+4Pe"
- +"rAKMdTGQ5YgiWK/8z+QT3yyVUTFAzaBXHQ0IONPKOxepAH65dUOGSB/pM9LC"
- +"whjyy/sg4DB3TjGZbjVuVIihQhKfxGdzmzhhNBvGXhr7NDiRY+fr573ibmtC"
- +"4pN4GNJDukiXusvbIuMnh9K9YujSYKKPl6vrZu+EI5EuyheG9JEe0qPusfSR"
- +"4cGBbPA98og8LMlAPlor2ZEvVIT0kD6G9EhcEpfY58c+xbKYHBaRl4Ye432s"
- +"rqyo7pnQo/qTxEW62gy2CKoAbheu4mGGm5eHgsViOTh+5Sp37+2X4gJQC0gU"
- +"Otb0j2hhaCG06NfC0K22/radzs6uTM3ojY1SobDcdHNaCC2Mimn2YZmQggEd"
- +"kPJ0UczfyOzVWHr1xnVmrS5I0R6pgTC1mXdoUwB2Jj5QFvDsBc8fTCkpL82l"
- +"uW6rWWEPQBoL07JwCgAaywbgd8ynIrultTB3wWk73LtWdS3OXtd/fBwH2+Yg"
- +"xM4R14kqrzMZzM5pO9dcNlQrl832wTSoGiEok84eOrK0ZGB0+shTJYpyFUv7"
- +"In/s/LlbTyq+/ufZFlkTK4MhAJKUMCGs6x473rg/9xe9wS0xVA1n/AAAAABJ"
- +"RU5ErkJggg==";
- const ENABLE_ICON = "data:image/png;base64,"
- +"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAACXBIWXMAAAsT"
- +"AAALEwEAmpwYAAACI0lEQVR4nGWSzU7yQBSGp84UKalDY0MkLsSdYWtCIok3"
- +"4YKV7tx7MWy9A6/ABZDgHbhghdFqU9M0FpH57cyUcdFA8Pue3fl5T07Oe5zz"
- +"8/PhcEgpbbfbtVoN7LBer9M01VpX4f7+/t3dnfP4+JimKQDg6OgIYwz+UpZl"
- +"HMdbjbUWZVkmpQQAEEJc1wX/EYZhHMdlWQIAKKV7cgPG+PLy8uPjg/+l3+/7"
- +"vl/1KKVQURRCCABAFEVa6yAIOOeO41Tjj4+PoyiK49h1XSkl53xPbOCcz+fz"
- +"bre7WCzYhpOTk+l0GoYhhFAIIaXck1JuNc/Pz51OpyiKahkAAMb49fVVCKGU"
- +"qgTw4uKCUqq1RggZY05PT8uyTJJEa312dvby8rJcLq21y+WSUiqlhN1uN89z"
- +"xpgxJs9zQkiv1xuNRlmWXV9f39/ff39/53meZRmllBCCZrNZkiTWWowxIWQ6"
- +"nV5dXRFCGGOfn59PT0+MMWut67pa6/V6jZrNpjHGWus4TqPRsNaORqPBYCCE"
- +"GI/Hvu/7vm+trc4KAEC+71dGQggrdyaTyXA4NMbc3NxsvW82mwCAoihQrVY7"
- +"PDzctVYIEUXR29tbo9GAEO6WpJTO7e0tIQRjXK/XhRCe5ymlsiyDEAZB4Hle"
- +"lawEX19fqNVqVS/kOE6r1fI8DyHU6XT++ShjzM/Pz8HBAXx/f+/3+9X2WmvO"
- +"uVKq3GCMUUoxxlarVb1ef3h4+AWNW50eXTIBjgAAAABJRU5ErkJggg==";
-
- var Class = function() function(){this.initialize.apply(this,arguments)};
-
- var _isEnable;
-
- function replaceMap (mode,key,desc,aroundFunc,extra){
- var old = liberator.modules.mappings.getDefault(mode,key);
- var oldAction = old.action;
- old.description = desc;
- old.action = function()
- let (self = this,args = arguments)
- aroundFunc(function() oldAction.apply(self,args));
- }
-
- var LDRizeCooperation = new Class();
- LDRizeCooperation.prototype = {
- initialize: function(){
- var self = this;
- this.LDRize = {getSiteinfo: function() undefined};
- this.Minibuffer = null;
- this.handlerInfo = handlerInfo;
-
- this.LDRizeCooperationPanel = this.setupStatusbarPanel();
-
- this.isEnable = liberator.globalVariables.ldrc_enable != undefined ?
- window.eval(liberator.globalVariables.ldrc_enable) : true ;
- this.isIntelligenceBind = liberator.globalVariables.ldrc_intelligence_bind != undefined ?
- window.eval(liberator.globalVariables.ldrc_intelligence_bind) : false ;
- this.isModHints = liberator.globalVariables.ldrc_hints != undefined ?
- window.eval(liberator.globalVariables.ldrc_hints) : false ;
- this.captureMappings = window.eval(liberator.globalVariables.ldrc_captureMappings) || ["j","k","p","o"];
- this.skipHeight = liberator.globalVariables.ldrc_skip != undefined ?
- window.eval(liberator.globalVariables.ldrc_skip) : 0.5 ;
-
- this.convertHandlerInfo(this.handlerInfo);
- this.hookGreasemonkey();
- this.initLDRizeCaptureKeys(this.captureMappings);
- this.initLDRizeCooperationFuture();
-
-
- if(liberator.plugins.LDRizeCooperationPlugins != undefined){
- liberator.plugins.LDRizeCooperationPlugins.forEach(function(func){
- func.apply(self,arguments);
- });
- delete liberator.plugins.LDRizeCooperationPlugins;
- }
- },
- setupStatusbarPanel: function(){
- var self = this;
- var LDRizeCooperationPanel = document.createElement("statusbarpanel");
- LDRizeCooperationPanel.setAttribute("id","ldrizecopperation-status");
- LDRizeCooperationPanel.setAttribute("class","statusbarpanel-iconic");
- LDRizeCooperationPanel.setAttribute("src",this.isEnable ? ENABLE_ICON : DISABLE_ICON);
- LDRizeCooperationPanel.addEventListener("click",function(e){
- self.isEnable = !self.isEnable;
- },false);
- var ref = document.getElementById("security-button") ? document.getElementById("security-button").nextSibling
- : document.getElementById("status-bar").firstChild;
- document.getElementById("status-bar").insertBefore(LDRizeCooperationPanel,ref);
-
- return LDRizeCooperationPanel;
- },
- hookGreasemonkey: function(){
- var self = this;
- var GreasemonkeyService;
- try{
- GreasemonkeyService = Cc["@greasemonkey.mozdev.org/greasemonkey-service;1"].getService().wrappedJSObject;
- this.addAfter(GreasemonkeyService,"evalInSandbox",function(){
- var gmVersion = liberator.extensions.filter(function(e)e.name=='Greasemonkey')[0].version;
- var versionChecker = Cc["@mozilla.org/xpcom/version-comparator;1"]
- .getService(Ci.nsIVersionComparator);
- if (versionChecker.compare(gmVersion, "0.8.*") > 0) {
- var [code,sandbox] = arguments;
- } else {
- var [code,codebase,sandbox] = arguments;
- }
- if(sandbox.window.LDRize != undefined && sandbox.window.Minibuffer != undefined){
- sandbox.window.addEventListener("focus",function(){
- self.LDRize = liberator.eval("self",sandbox.window.LDRize.getSiteinfo);
- self.Minibuffer = liberator.eval("command",sandbox.window.Minibuffer.addCommand);
- if (typeof self.LDRize.getSiteinfo != 'function') self.LDRize = sandbox.window.LDRize;
- if (typeof self.Minibuffer.addCommand != 'function') self.Minibuffer = sandbox.window.Minibuffer.command;
- },false);
- if(window.content.wrappedJSObject == sandbox.unsafeWindow){
- self.LDRize = liberator.eval("self",sandbox.window.LDRize.getSiteinfo);
- self.Minibuffer = liberator.eval("command",sandbox.window.Minibuffer.addCommand);
- if (typeof self.LDRize.getSiteinfo != 'function') self.LDRize = sandbox.window.LDRize;
- if (typeof self.Minibuffer.addCommand != 'function') self.Minibuffer = sandbox.window.Minibuffer.command;
- }
- }
- });
- }catch(e){
- liberator.log(e);
- }
- },
- initLDRizeCaptureKeys: function(keys){
- var self = this;
- keys.forEach(function(x){
- var map = liberator.modules.mappings.get(null,x) || liberator.modules.mappings.getDefault(null,x);
- var oldAction = map.action;
- var getter = "getPrev";
- switch(x){
- case "j": getter = "getNext";
- case "k": map.action = function(){
- self.isEnableLDRizeCooperation() ?
- self.isIntelligenceBind && self.isScrollOrBind(getter) ?
- oldAction.apply(this,arguments) // scroll
- : self.sendRawKeyEvent(0,x.charCodeAt(0)) // bind
- : oldAction.apply(this,arguments);
- };
- break;
- case "J":
- case "K": map.action = function(){
- self.isEnableLDRizeCooperation()
- ? self.sendRawKeyEvent(0,x.charCodeAt(0) + 32)
- : oldAction.apply(this,arguments);
- };
- break;
- default: map.action = function(){
- self.isEnableLDRizeCooperation()
- ? self.sendRawKeyEvent(0,x.charCodeAt(0))
- : oldAction.apply(this,arguments);
- };
- break;
- }
- });
- },
- initLDRizeCooperationFuture: function(){
- var self = this;
-
- //Hints
- [
- ["l","LDRize paragraphes",liberator.CURRENT_TAB],
- ["L","LDRize paragraphes (in a new tab",liberator.NEW_TAB]
- ].forEach(function([mode,prompt,target]){
- liberator.modules.hints.addMode(mode,prompt,
- function(elem) liberator.modules.buffer.followLink(elem,target),
- function(){
- var siteinfo = self.LDRize.getSiteinfo();
- return siteinfo.paragraph + "/" + siteinfo.link;
- });
-
- });
-
- //Commands
- liberator.modules.commands.addUserCommand(["pin"],"LDRize Pinned Links",
- function(){
- var links = self.getPinnedItems();
- var showString = links.length + " Items<br/>";
- links.forEach(function(link){
- showString += link + "<br/>";
- });
- liberator.modules.commandline.echo(showString,liberator.modules.commandline.HL_NORMAL,liberator.modules.commandline.FORCE_MULTILINE);
- },{});
- liberator.modules.commands.addUserCommand(["mb","m","minibuffer"],"Execute Minibuffer",
- function(arg){ self.Minibuffer.execute(arg.string.replace(/\\+/g,"")) },
- {
- completer: function(context,arg){
- var filter = context.filter;
- var completionList = [];
- var command = self.Minibuffer.command;
- var alias = self.Minibuffer.alias_getter();
- var tokens = filter.split("|").map(function(str) str.replace(/\s+/g,""));
- var exp = new RegExp("^" + tokens.pop());
- for(let i in command) if(exp.test(i))completionList.push([tokens.concat(i).join(" | "),"MinibufferCommand"]);
- for(let i in alias) if(exp.test(i))completionList.push([i,"MinibufferAlias"]);
- context.title = ["Minibuffer Command","Description"];
- context.completions = completionList;
- }
- });
- liberator.modules.commands.addUserCommand(["pindownload"],"Download pinned links by any software",
- function(arg){ self.downloadLinksByProgram("download",self.getPinnedItems());},{});
- liberator.modules.commands.addUserCommand(["pindo"],"Do external command, with pinned links",
- function(arg){ self.downloadLinksByProgram(arg.string, self.getPinnedItems());},{});
- liberator.modules.commands.addUserCommand(["toggleldrizecooperation","toggleldrc"],"Toggle LDRize Cooperation",
- function(arg){ self.isEnable = !self.isEnable},{});
- //Options
- liberator.modules.options.add(["ldrc","ldrizecooperation"],"LDRize cooperation","boolean",this.isEnable,
- {
- setter: function(value){ return self.isEnable = value; },
- getter: function() self.isEnable
- }
- );
- liberator.modules.options.add(["ldrchints"],"mod hinttags for LDRize","boolean",this.isModHints,
- {
- setter: function(value){ return self.isModHints = value; },
- getter: function() self.isModHints
- }
- );
- },
- convertHandlerInfo: function(handlerInfoArray){
- handlerInfoArray.forEach(function(x){
- x.include = typeof x.include != "undefined"
- ? typeof x.include == "string" ? new RegExp(x.include) : new RegExp(x.include[0],x.include[1])
- : typeof x.pattern != "undefined"
- ? new RegExp("^"+String(x.pattern).replace(/\s+/g,"").replace(/[\\^$.+?|(){}\[\]]/g,"\\$&")
- .replace(/(?=\*)/g,".")+"$","i")
- : /(?:)/;
- delete x.pattern;
- });
- },
-
- get isEnable() _isEnable,
- set isEnable(value){
- this.LDRizeCooperationPanel.setAttribute("src",value ? DISABLE_ICON : ENABLE_ICON);
- _isEnable = value;
- },
- isEnableLDRize: function() this.LDRize.getSiteinfo() != undefined,
- isEnableLDRizeCooperation: function() /^https?:$/.test(content.location.protocol) && this.isEnable && this.isEnableLDRize(),
-
- //Pin
- getPinnedItems: function(){
- var linkXpath = this.LDRize.getSiteinfo()["link"];
- var viewXpath = this.LDRize.getSiteinfo()["view"] || linkXpath + "/text()";
- return this.LDRize.getPinnedItems().map(function(i){
- var linkResult = i.XPath(linkXpath),viewResult = i.XPath(viewXpath);
- return [linkResult,viewResult ? viewResult.textContent : null];
- });
- },
- downloadLinksByProgram: function(command, links){
- var self = this;
- var count = 0;
- links.forEach(function([url,title]){
- for each(let x in self.handlerInfo){
- if(x.include.test(url)){
- setTimeout(function(){
- if(typeof x.handler[command] == "object"){
- let args = x.handler[command][1].map(function(s) s.replace(/%URL%/g,url).replace(/%TITLE%/g,title));
- liberator.modules.io.run(x.handler[command][0],args,false);
- }else if(typeof x.handler[command] == "string"){
- liberator.modules.io.run(x.handler[command],[url],false);
- }else if(typeof x.handler[command] == "function"){
- x.handler[command](url.toString(),title);
- }
- },x.wait != undefined ? x.wait * count++ : 0);
- return;
- }
- }
- liberator.echoerr("LDRize Cooperation: download pattern not found!!");
- });
- },
- isScrollOrBind: function(getter){
- var self = this;
- var paragraphes,paragraph,current,next,innerHeight,scrollY,limit,p,np,cp;
- try{
- paragraphes = this.LDRize.getParagraphes();
- paragraph = paragraphes[getter]();
- current = paragraphes.current;
- next = paragraphes.getNext();
-
- innerHeight = window.content.innerHeight;
- scrollY = window.content.scrollY;
-
- limit = window.content.innerHeight * (self.skipHeight + 0.5);
-
- if(paragraph.paragraph == undefined) return true; // scroll
- if(current.paragraph == undefined) return false; // bind
- if(current.paragraph.y - window.content.scrollY == this.LDRize.getScrollHeight()
- && getter == "getPrev") return false; // bind
-
- p = this.getClientPosition(paragraph.paragraph.node);
- np = next && next.paragraph.node != undefined ?
- this.getClientPosition(next.paragraph.node) :
- {top: window.content.scrollMaxY + window.content.innerHeight,left: 0};
- cp = this.getClientPosition(current.paragraph.node);
-
- /*
- *log(p);
- *log(np);
- *log(cp);
- */
-
- //check current paragraph
- if(!(scrollY < np.top && cp.top < scrollY + innerHeight)) return false; // bind
- //check next/prev paragraph
- if(Math.abs(p.top - (scrollY + innerHeight/2)) < innerHeight * 0.5) return false; // bind
- if(Math.abs(p.top - (scrollY + innerHeight/2)) > limit) return true; // scroll
- return false; // bind
- }catch(e){
- liberator.log(e);
- }
- },
-
- //Utils
- addAfter: function(target,name,after){
- var original = target[name];
- target[name] = function(){
- var tmp = original.apply(target,arguments);
- after.apply(target,arguments);
- return tmp;
- };
- },
- getClientPosition: function(elem){
- var position;
- try{
- position = elem.getBoundingClientRect();
- }catch(e){
- position = elem.parentNode.getBoundingClientRect();
- }
- return {
- left: Math.round(window.content.scrollX+position.left),
- top: Math.round(window.content.scrollY+position.top)
- };
- },
- sendRawKeyEvent: function(keyCode,charCode){
- modes.passAllKeys = true;
- var evt = window.content.wrappedJSObject.document.createEvent("KeyEvents");
- evt.initKeyEvent("keypress",false,false,window.content.wrappedJSObject,false,false,false,false,keyCode,charCode);
- window.content.wrappedJSObject.document.dispatchEvent(evt);
- modes.passAllKeys = false;
- },
- };
-
- liberator.plugins.LDRizeCooperation = new LDRizeCooperation();
-})();
+// Vimperator plugin: 'Cooperation LDRize Mappings' +// Version: 0.25 +// Last Change: 09-Jun-2011. Jan 2008 +// License: Creative Commons +// Maintainer: Trapezoid <trapezoid.g@gmail.com> - http://unsigned.g.hatena.ne.jp/Trapezoid +// +// Cooperation LDRize Mappings for Vimperator +// +// Variables: +// g:ldrc_captureMapping +// Specifies keys that capture by LDRize +// usage: let g:ldrc_captureMappings = "['j','k','p','o','?']" +// g:ldrc_enable +// LDRize Cooperation be Enable by default or not +// usage: let g:ldrc_enable = "false" +// default: true +// g:ldrc_hints +// Narrows "hinttags" based on Siteinfo. +// usage: let g:ldrc_hints = "true" +// default: false +// g:ldrc_intelligence_bind +// More inteligence cooperation bind +// usage: let g:ldrc_intelligence_bind = "true" +// default: false +// g:ldrc_skip +// length in which paragraph is skipped (use by inteligence bind mode) +// usage: let g:ldrc_hints = "true" +// default: 0.5 +// Mappings: +// Mappings for LDRize +// default: 'j','k','p','o' +// Commands: +// 'm' or 'mb' or 'minibuffer': +// Execute args as Minibuffer Command +// usage: :minibuffer pinned-link | open | clear-pin +// 'pin': +// View pinned link list +// usage: :pin +// 'pindownload': +// Download View pinned link by handler function or outer promgram. please see 'handlerInfo' also +// usage: :pindownload +// 'ldrc' or 'toggleldrizecooperation': +// Toggle LDRize Cooperation +// usage: :toggleldrizecooperation +// Hints: +// ';l': +// narrow down the candidates to LDRize paragraphes +// ';L': +// narrow down the candidates to LDRize paragraphes (in a new tab) +// Options: +// 'ldrc' +// Enable LDRize Cooperation +// usage: :set ldrc +// 'noldrc' +// Disable LDRize Cooperation +// usage: :set noldrc +// +// 'ldrchints' +// Enable "Narrows Hinttags based on Siteinfo" +// usage: :set ldrchints +// 'noldrchints' +// Disable "Narrows Hinttags based on Siteinfo" +// usage: :set noldrchints +if (liberator.plugins.LDRizeCooperation == undefined) (function(){ + //pattern: wildcard + //include: [regexp,option] or regexp + //handler: [programPath,[args]] or programPath or function(url,title) + var handlerInfo = [ + //{ + // pattern: "http://www.nicovideo.jp/*", + // handler: { + // download: ["c:\\usr\\SmileDownloader\\SmileDownloader.exe",["%URL%"]] + // }, + // wait: 5000 + //}, + //{ + // handler: ["C:\\usr\\irvine\\irvine.exe",["%URL%"]], + //}, + ]; + const DISABLE_ICON = "data:image/png;base64," + +"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAACXBIWXMAAA7E" + +"AAAOxAGVKw4bAAACL0lEQVR4nF2Sy0tUYRjGf9+Z4/HMjJfjBUZEMM2MSDII" + +"REjSVtVecBFZi6Bdi4RW/SFBq2oR0R8gSaUJhVJIBkEEMZOWl5kuM+fqnPN9" + +"52sxQ4kPv837Pu+zel4xMjkz/3h5p87pbhyDw4o1mzUOkubYbvLo2kVx+4Pe" + +"rAKMdTGQ5YgiWK/8z+QT3yyVUTFAzaBXHQ0IONPKOxepAH65dUOGSB/pM9LC" + +"whjyy/sg4DB3TjGZbjVuVIihQhKfxGdzmzhhNBvGXhr7NDiRY+fr573ibmtC" + +"4pN4GNJDukiXusvbIuMnh9K9YujSYKKPl6vrZu+EI5EuyheG9JEe0qPusfSR" + +"4cGBbPA98og8LMlAPlor2ZEvVIT0kD6G9EhcEpfY58c+xbKYHBaRl4Ye432s" + +"rqyo7pnQo/qTxEW62gy2CKoAbheu4mGGm5eHgsViOTh+5Sp37+2X4gJQC0gU" + +"Otb0j2hhaCG06NfC0K22/radzs6uTM3ojY1SobDcdHNaCC2Mimn2YZmQggEd" + +"kPJ0UczfyOzVWHr1xnVmrS5I0R6pgTC1mXdoUwB2Jj5QFvDsBc8fTCkpL82l" + +"uW6rWWEPQBoL07JwCgAaywbgd8ynIrultTB3wWk73LtWdS3OXtd/fBwH2+Yg" + +"xM4R14kqrzMZzM5pO9dcNlQrl832wTSoGiEok84eOrK0ZGB0+shTJYpyFUv7" + +"In/s/LlbTyq+/ufZFlkTK4MhAJKUMCGs6x473rg/9xe9wS0xVA1n/AAAAABJ" + +"RU5ErkJggg=="; + const ENABLE_ICON = "data:image/png;base64," + +"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAACXBIWXMAAAsT" + +"AAALEwEAmpwYAAACI0lEQVR4nGWSzU7yQBSGp84UKalDY0MkLsSdYWtCIok3" + +"4YKV7tx7MWy9A6/ABZDgHbhghdFqU9M0FpH57cyUcdFA8Pue3fl5T07Oe5zz" + +"8/PhcEgpbbfbtVoN7LBer9M01VpX4f7+/t3dnfP4+JimKQDg6OgIYwz+UpZl" + +"HMdbjbUWZVkmpQQAEEJc1wX/EYZhHMdlWQIAKKV7cgPG+PLy8uPjg/+l3+/7" + +"vl/1KKVQURRCCABAFEVa6yAIOOeO41Tjj4+PoyiK49h1XSkl53xPbOCcz+fz" + +"bre7WCzYhpOTk+l0GoYhhFAIIaXck1JuNc/Pz51OpyiKahkAAMb49fVVCKGU" + +"qgTw4uKCUqq1RggZY05PT8uyTJJEa312dvby8rJcLq21y+WSUiqlhN1uN89z" + +"xpgxJs9zQkiv1xuNRlmWXV9f39/ff39/53meZRmllBCCZrNZkiTWWowxIWQ6" + +"nV5dXRFCGGOfn59PT0+MMWut67pa6/V6jZrNpjHGWus4TqPRsNaORqPBYCCE" + +"GI/Hvu/7vm+trc4KAEC+71dGQggrdyaTyXA4NMbc3NxsvW82mwCAoihQrVY7" + +"PDzctVYIEUXR29tbo9GAEO6WpJTO7e0tIQRjXK/XhRCe5ymlsiyDEAZB4Hle" + +"lawEX19fqNVqVS/kOE6r1fI8DyHU6XT++ShjzM/Pz8HBAXx/f+/3+9X2WmvO" + +"uVKq3GCMUUoxxlarVb1ef3h4+AWNW50eXTIBjgAAAABJRU5ErkJggg=="; + + var Class = function() function(){this.initialize.apply(this,arguments)}; + + var _isEnable; + + function replaceMap (mode,key,desc,aroundFunc,extra){ + var old = liberator.modules.mappings.getDefault(mode,key); + var oldAction = old.action; + old.description = desc; + old.action = function() + let (self = this,args = arguments) + aroundFunc(function() oldAction.apply(self,args)); + } + + var LDRizeCooperation = new Class(); + LDRizeCooperation.prototype = { + initialize: function(){ + var self = this; + this.LDRize = {getSiteinfo: function() undefined}; + this.Minibuffer = null; + this.handlerInfo = handlerInfo; + + this.LDRizeCooperationPanel = this.setupStatusbarPanel(); + + this.isEnable = liberator.globalVariables.ldrc_enable != undefined ? + window.eval(liberator.globalVariables.ldrc_enable) : true ; + this.isIntelligenceBind = liberator.globalVariables.ldrc_intelligence_bind != undefined ? + window.eval(liberator.globalVariables.ldrc_intelligence_bind) : false ; + this.isModHints = liberator.globalVariables.ldrc_hints != undefined ? + window.eval(liberator.globalVariables.ldrc_hints) : false ; + this.captureMappings = window.eval(liberator.globalVariables.ldrc_captureMappings) || ["j","k","p","o"]; + this.skipHeight = liberator.globalVariables.ldrc_skip != undefined ? + window.eval(liberator.globalVariables.ldrc_skip) : 0.5 ; + + this.convertHandlerInfo(this.handlerInfo); + this.hookGreasemonkey(); + this.initLDRizeCaptureKeys(this.captureMappings); + this.initLDRizeCooperationFuture(); + + + if(liberator.plugins.LDRizeCooperationPlugins != undefined){ + liberator.plugins.LDRizeCooperationPlugins.forEach(function(func){ + func.apply(self,arguments); + }); + delete liberator.plugins.LDRizeCooperationPlugins; + } + }, + setupStatusbarPanel: function(){ + var self = this; + var LDRizeCooperationPanel = document.createElement("statusbarpanel"); + LDRizeCooperationPanel.setAttribute("id","ldrizecopperation-status"); + LDRizeCooperationPanel.setAttribute("class","statusbarpanel-iconic"); + LDRizeCooperationPanel.setAttribute("src",this.isEnable ? ENABLE_ICON : DISABLE_ICON); + LDRizeCooperationPanel.addEventListener("click",function(e){ + self.isEnable = !self.isEnable; + },false); + var ref = document.getElementById("security-button") ? document.getElementById("security-button").nextSibling + : document.getElementById("status-bar").firstChild; + document.getElementById("status-bar").insertBefore(LDRizeCooperationPanel,ref); + + return LDRizeCooperationPanel; + }, + hookGreasemonkey: function(){ + var self = this; + var GreasemonkeyService; + try{ + GreasemonkeyService = Cc["@greasemonkey.mozdev.org/greasemonkey-service;1"].getService().wrappedJSObject; + this.addAfter(GreasemonkeyService,"evalInSandbox",function(){ + var gmVersion = liberator.extensions.filter(function(e)e.name=='Greasemonkey')[0].version; + var versionChecker = Cc["@mozilla.org/xpcom/version-comparator;1"] + .getService(Ci.nsIVersionComparator); + if (versionChecker.compare(gmVersion, "0.8.*") > 0) { + var [code,sandbox] = arguments; + } else { + var [code,codebase,sandbox] = arguments; + } + if(sandbox.window.LDRize != undefined && sandbox.window.Minibuffer != undefined){ + sandbox.window.addEventListener("focus",function(){ + self.LDRize = liberator.eval("self",sandbox.window.LDRize.getSiteinfo); + self.Minibuffer = liberator.eval("command",sandbox.window.Minibuffer.addCommand); + if (typeof self.LDRize.getSiteinfo != 'function') self.LDRize = sandbox.window.LDRize; + if (typeof self.Minibuffer.addCommand != 'function') self.Minibuffer = sandbox.window.Minibuffer.command; + },false); + if(window.content.wrappedJSObject == sandbox.unsafeWindow){ + self.LDRize = liberator.eval("self",sandbox.window.LDRize.getSiteinfo); + self.Minibuffer = liberator.eval("command",sandbox.window.Minibuffer.addCommand); + if (typeof self.LDRize.getSiteinfo != 'function') self.LDRize = sandbox.window.LDRize; + if (typeof self.Minibuffer.addCommand != 'function') self.Minibuffer = sandbox.window.Minibuffer.command; + } + } + }); + }catch(e){ + liberator.log(e); + } + }, + initLDRizeCaptureKeys: function(keys){ + var self = this; + keys.forEach(function(x){ + var map = liberator.modules.mappings.get(null,x) || liberator.modules.mappings.getDefault(null,x); + var oldAction = map.action; + var getter = "getPrev"; + switch(x){ + case "j": getter = "getNext"; + case "k": map.action = function(){ + self.isEnableLDRizeCooperation() ? + self.isIntelligenceBind && self.isScrollOrBind(getter) ? + oldAction.apply(this,arguments) // scroll + : self.sendRawKeyEvent(0,x.charCodeAt(0)) // bind + : oldAction.apply(this,arguments); + }; + break; + case "J": + case "K": map.action = function(){ + self.isEnableLDRizeCooperation() + ? self.sendRawKeyEvent(0,x.charCodeAt(0) + 32) + : oldAction.apply(this,arguments); + }; + break; + default: map.action = function(){ + self.isEnableLDRizeCooperation() + ? self.sendRawKeyEvent(0,x.charCodeAt(0)) + : oldAction.apply(this,arguments); + }; + break; + } + }); + }, + initLDRizeCooperationFuture: function(){ + var self = this; + + //Hints + [ + ["l","LDRize paragraphes",liberator.CURRENT_TAB], + ["L","LDRize paragraphes (in a new tab",liberator.NEW_TAB] + ].forEach(function([mode,prompt,target]){ + liberator.modules.hints.addMode(mode,prompt, + function(elem) liberator.modules.buffer.followLink(elem,target), + function(){ + var siteinfo = self.LDRize.getSiteinfo(); + return siteinfo.paragraph + "/" + siteinfo.link; + }); + + }); + + //Commands + liberator.modules.commands.addUserCommand(["pin"],"LDRize Pinned Links", + function(){ + var links = self.getPinnedItems(); + var showString = links.length + " Items<br/>"; + links.forEach(function(link){ + showString += link + "<br/>"; + }); + liberator.modules.commandline.echo(showString,liberator.modules.commandline.HL_NORMAL,liberator.modules.commandline.FORCE_MULTILINE); + },{}); + liberator.modules.commands.addUserCommand(["mb","m","minibuffer"],"Execute Minibuffer", + function(arg){ self.Minibuffer.execute(arg.string.replace(/\\+/g,"")) }, + { + completer: function(context,arg){ + var filter = context.filter; + var completionList = []; + var command = self.Minibuffer.command; + var alias = self.Minibuffer.alias_getter(); + var tokens = filter.split("|").map(function(str) str.replace(/\s+/g,"")); + var exp = new RegExp("^" + tokens.pop()); + for(let i in command) if(exp.test(i))completionList.push([tokens.concat(i).join(" | "),"MinibufferCommand"]); + for(let i in alias) if(exp.test(i))completionList.push([i,"MinibufferAlias"]); + context.title = ["Minibuffer Command","Description"]; + context.completions = completionList; + } + }); + liberator.modules.commands.addUserCommand(["pindownload"],"Download pinned links by any software", + function(arg){ self.downloadLinksByProgram("download",self.getPinnedItems());},{}); + liberator.modules.commands.addUserCommand(["pindo"],"Do external command, with pinned links", + function(arg){ self.downloadLinksByProgram(arg.string, self.getPinnedItems());},{}); + liberator.modules.commands.addUserCommand(["toggleldrizecooperation","toggleldrc"],"Toggle LDRize Cooperation", + function(arg){ self.isEnable = !self.isEnable},{}); + //Options + liberator.modules.options.add(["ldrc","ldrizecooperation"],"LDRize cooperation","boolean",this.isEnable, + { + setter: function(value){ return self.isEnable = value; }, + getter: function() self.isEnable + } + ); + liberator.modules.options.add(["ldrchints"],"mod hinttags for LDRize","boolean",this.isModHints, + { + setter: function(value){ return self.isModHints = value; }, + getter: function() self.isModHints + } + ); + }, + convertHandlerInfo: function(handlerInfoArray){ + handlerInfoArray.forEach(function(x){ + x.include = typeof x.include != "undefined" + ? typeof x.include == "string" ? new RegExp(x.include) : new RegExp(x.include[0],x.include[1]) + : typeof x.pattern != "undefined" + ? new RegExp("^"+String(x.pattern).replace(/\s+/g,"").replace(/[\\^$.+?|(){}\[\]]/g,"\\$&") + .replace(/(?=\*)/g,".")+"$","i") + : /(?:)/; + delete x.pattern; + }); + }, + + get isEnable() _isEnable, + set isEnable(value){ + this.LDRizeCooperationPanel.setAttribute("src",value ? DISABLE_ICON : ENABLE_ICON); + _isEnable = value; + }, + isEnableLDRize: function() this.LDRize.getSiteinfo() != undefined || + window.content.wrappedJSObject.document.getElementById("gm_ldrize") != null, + isEnableLDRizeCooperation: function() /^https?:$/.test(content.location.protocol) && this.isEnable && this.isEnableLDRize(), + + //Pin + getPinnedItems: function(){ + var linkXpath = this.LDRize.getSiteinfo()["link"]; + var viewXpath = this.LDRize.getSiteinfo()["view"] || linkXpath + "/text()"; + return this.LDRize.getPinnedItems().map(function(i){ + var linkResult = i.XPath(linkXpath),viewResult = i.XPath(viewXpath); + return [linkResult,viewResult ? viewResult.textContent : null]; + }); + }, + downloadLinksByProgram: function(command, links){ + var self = this; + var count = 0; + links.forEach(function([url,title]){ + for each(let x in self.handlerInfo){ + if(x.include.test(url)){ + setTimeout(function(){ + if(typeof x.handler[command] == "object"){ + let args = x.handler[command][1].map(function(s) s.replace(/%URL%/g,url).replace(/%TITLE%/g,title)); + liberator.modules.io.run(x.handler[command][0],args,false); + }else if(typeof x.handler[command] == "string"){ + liberator.modules.io.run(x.handler[command],[url],false); + }else if(typeof x.handler[command] == "function"){ + x.handler[command](url.toString(),title); + } + },x.wait != undefined ? x.wait * count++ : 0); + return; + } + } + liberator.echoerr("LDRize Cooperation: download pattern not found!!"); + }); + }, + isScrollOrBind: function(getter){ + var self = this; + var paragraphes,paragraph,current,next,innerHeight,scrollY,limit,p,np,cp; + try{ + paragraphes = this.LDRize.getParagraphes(); + paragraph = paragraphes[getter](); + current = paragraphes.current; + next = paragraphes.getNext(); + + innerHeight = window.content.innerHeight; + scrollY = window.content.scrollY; + + limit = window.content.innerHeight * (self.skipHeight + 0.5); + + if(paragraph.paragraph == undefined) return true; // scroll + if(current.paragraph == undefined) return false; // bind + if(current.paragraph.y - window.content.scrollY == this.LDRize.getScrollHeight() + && getter == "getPrev") return false; // bind + + p = this.getClientPosition(paragraph.paragraph.node); + np = next && next.paragraph.node != undefined ? + this.getClientPosition(next.paragraph.node) : + {top: window.content.scrollMaxY + window.content.innerHeight,left: 0}; + cp = this.getClientPosition(current.paragraph.node); + + /* + *log(p); + *log(np); + *log(cp); + */ + + //check current paragraph + if(!(scrollY < np.top && cp.top < scrollY + innerHeight)) return false; // bind + //check next/prev paragraph + if(Math.abs(p.top - (scrollY + innerHeight/2)) < innerHeight * 0.5) return false; // bind + if(Math.abs(p.top - (scrollY + innerHeight/2)) > limit) return true; // scroll + return false; // bind + }catch(e){ + liberator.log(e); + } + }, + + //Utils + addAfter: function(target,name,after){ + var original = target[name]; + target[name] = function(){ + var tmp = original.apply(target,arguments); + after.apply(target,arguments); + return tmp; + }; + }, + getClientPosition: function(elem){ + var position; + try{ + position = elem.getBoundingClientRect(); + }catch(e){ + position = elem.parentNode.getBoundingClientRect(); + } + return { + left: Math.round(window.content.scrollX+position.left), + top: Math.round(window.content.scrollY+position.top) + }; + }, + sendRawKeyEvent: function(keyCode,charCode){ + modes.passAllKeys = true; + var evt = window.content.wrappedJSObject.document.createEvent("KeyEvents"); + evt.initKeyEvent("keypress",false,false,window.content.wrappedJSObject,false,false,false,false,keyCode,charCode); + window.content.wrappedJSObject.document.dispatchEvent(evt); + modes.passAllKeys = false; + }, + }; + + liberator.plugins.LDRizeCooperation = new LDRizeCooperation(); +})();
\ No newline at end of file @@ -55,7 +55,7 @@ let PLUGIN_INFO = let INFO = <> <plugin name="link-opener" version="2.3.1" - href="http://svn.coderepos.org/share/lang/javascript/vimperator-plugins/trunk/lo.js" + href="http://github.com/vimpr/vimperator-plugins/blob/master/lo.js" summary="Link Opener" lang="en-US" xmlns="http://vimperator.org/namespaces/liberator"> @@ -104,7 +104,7 @@ let INFO = </item> </plugin> <plugin name="link-opener" version="2.3.1" - href="http://svn.coderepos.org/share/lang/javascript/vimperator-plugins/trunk/lo.js" + href="http://github.com/vimpr/vimperator-plugins/blob/master/lo.js" summary="Link Opener" lang="ja" xmlns="http://vimperator.org/namespaces/liberator"> diff --git a/loginManager.js b/loginManager.js index 106f690..3e8a80d 100644 --- a/loginManager.js +++ b/loginManager.js @@ -4,12 +4,15 @@ var PLUGIN_INFO = <name>{NAME}</name> <description>login manager</description> <author mail="konbu.komuro@gmail.com" homepage="http://d.hatena.ne.jp/hogelog/">hogelog</author> - <version>0.0.4</version> + <version>0.2.0</version> <minVersion>2.0pre</minVersion> - <maxVersion>2.2pre</maxVersion> <updateURL>https://github.com/vimpr/vimperator-plugins/raw/master/loginManger.js</updateURL> <license>public domain</license> <detail><![CDATA[ + Default login user setting: + >|| + let g:login_manager_default_user='nicovideo=mymail@addre.ss, slashdotjp=hogelogger' + ||< === TODO === @@ -54,6 +57,8 @@ var services = { }, }, hatena: { + NAME: "はてな", + URL: /^https?:\/\/\w+\.hatena\.ne.\jp/, HOST: ["https://www.hatena.ne.jp", "http://www.hatena.ne.jp"], LOGIN: "/login", LOGOUT: "/logout", @@ -62,6 +67,7 @@ var services = { logoutBeforeLogin: true, }, hatelabo: { + NAME: "はてラボ", HOST: ["https://www.hatelabo.jp", "http://www.hatelabo.jp"], LOGIN: "/login", LOGOUT: "/logout", @@ -109,17 +115,119 @@ var services = { CSRFPROTECT: tokenGetter(/CSRFPROTECT.+value="(.+?)"/), }, }, + delicious: { + HOST: ["https://secure.delicious.com"], + LOGIN: "/login", + LOGOUT: "/logout", + usernameField: "username", + passwordField: "password", + extraField: { + rememberme: "1", + }, + }, + evernote: { + HOST: ["https://www.evernote.com"], + LOGIN: "/Login.action", + LOGOUT: "/Logout.action", + usernameField: "username", + passwordField: "password", + extraField: { + rememberMe: "true", + _sourcePage: tokenGetterLoginURL(/_sourcePage.+value="(.+?)"/), + __fp: tokenGetterLoginURL(/__fp.+value="(.+?)"/), + login: "Sign In", + }, + }, + readitlater: { + HOST: ["http://readitlaterlist.com"], + LOGIN: "/login_process/", + LOGOUT: "/lo", + usernameField: "feed_id", + passwordField: "password", + }, + nicovideo: { + URL: /^https?:\/\/\w+\.nicovideo.\jp/, + NAME: "ニコニコ動画", + HOST: ["https://secure.nicovideo.jp"], + LOGIN: "/secure/login", + usernameField: "mail", + passwordField: "password", + extraField: { + site: "niconico" + } + }, + slashdotjp: { + NAME: "スラッシュドットジャパン", + HOST: ["http://slashdot.jp"], + LOGIN: "/login.pl", + usernameField: "unickname", + passwordField: "upasswd", + extraField: { + op: "userlogin", + } + }, + tumblr: { + NAME: "tumblr", + HOST: ["https://www.tumblr.com"], + URL: /^https?:\/\/(?:\w+\.)?tumblr\.com\//, + LOGIN: "/login", + LOGOUT: "/logout", + usernameField: "email", + passwordField: "password", + }, }; -for (name in services){ - services[name] = new Service(services[name]); +for (let [name, service] in Iterator(services)){ + if (!service.NAME) + service.NAME = name; + services[name] = new Service(service); +} +let (gv = liberator.globalVariables.userLoginServices || liberator.globalVariables.login_manager_services) { + if (gv) { + let userServices = gv; + for (name in userServices){ + services[name] = new Service(userServices[name]); + } + } } -if (liberator.globalVariables.userLoginServices) { - let userServices = liberator.globalVariables.userLoginServices; - for (name in userServices){ - services[name] = new Service(userServices[name]); +for (let [name, service] in Iterator(services)){ + if (!service.NAME) + service.NAME = name; +} +let (gv = liberator.globalVariables.userLoginDefaults || liberator.globalVariables.login_manager_default_user) { + if (typeof gv === 'string') { + for (let [, sn] in Iterator(gv.split(','))) { + let [s, v] = sn.split('='); + services[s.trim()].DEFAULT_USER = v.trim(); + } + } else if (typeof gv === 'object') { + for (let [n, v] in Iterator(gv)) + services[n].DEFAULT_USER = v; } } +Object.defineProperty( + services, + "auto", + { + enumerable: true, + get: function(){ + let currentURI = makeURI(buffer.URL); + for (let n in Iterator(this, true)){ + if (n === "auto") continue; + let s = this[n]; + if (s.URL && s.URL.test(buffer.URL)) + return s; + for (let [, h] in Iterator(s.HOST)){ + let sURI = makeURI(h); + if (sURI.host === currentURI.host) return s; + } + } + // XXX (補完に|エラーを)出さないためのダミー + return {getUsernames: function() ([])}; + } + } +) + // Library function Service(service) //{{{ { @@ -145,7 +253,7 @@ function Service(service) //{{{ login(); }; - self.logout = function(){ + self.logout = function(username){ let content = {}; let host = service.HOST[0]; if (service.extraField && !self.setExtraField(content)) return false; @@ -217,42 +325,71 @@ function tokenGetter(pattern) //{{{ return RegExp.$1; } }; +} +function tokenGetterLoginURL(pattern) //{{{ +{ + return function(service){ + let res = util.httpGet(service.HOST[0]+service.LOGIN); + if (pattern.test(res.responseText)){ + return RegExp.$1; + } + }; +} +function getServiceAndUsernameFromArgs(args, logout) +{ + let [servicename, username] = args; + let service = services[servicename]; + if (!service) + service = services.auto; + if (!service) + return; + if (!username) { + let names = service.getUsernames(); + if (names.length === 1) { + username = names[0]; + } else { + username = service.DEFAULT_USER; + } + } + return [service, username]; } //}}} // Commands // {{{ commands.addUserCommand(["login"], "Login", function(args){ - let [servicename, username] = args; - let service = services[servicename]; - if (!service) return false; + let [service, username] = getServiceAndUsernameFromArgs(args); + if (!service) + return liberator.echoerr("Argument required. Please supply service name."); + if (!username) + return liberator.echoerr("Argument required. Please supply user name."); service.login(username); }, { completer: function(context, args){ if (args.completeArg == 0){ context.title = ["service"]; - context.completions = [[s,""] for(s in services)]; + context.completions = [[n,s.NAME] for([n,s] in Iterator(services)) if (s.getUsernames().length)]; } else if (args.completeArg == 1){ let service = services[args[0]]; if (!service) return false; context.title = ["username"]; - context.completions = [[u,""] for each(u in service.getUsernames())]; + context.completions = [[u,] for each(u in service.getUsernames())]; } }, literal: 1, - }); + }, true); commands.addUserCommand(["logout"], "Logout", function(args){ - let [servicename, username] = args; - let service = services[servicename]; - if (!service) return false; + let [service, username] = getServiceAndUsernameFromArgs(args); + if (!service) + return liberator.echoerr("Argument required. Please supply service name."); service.logout(username); }, { completer: function(context, args){ context.title = ["service"]; - context.completions = [[s,""] for(s in services)]; + context.completions = [[n,s.NAME] for([n,s] in Iterator(services)) if (s.getUsernames().length)]; }, - }); + }, true); // }}} })(); diff --git a/lolipo-ojisan.js b/lolipo-ojisan.js index e82dd70..68f2dfb 100644 --- a/lolipo-ojisan.js +++ b/lolipo-ojisan.js @@ -64,7 +64,7 @@ let PLUGIN_INFO = let INFO = <> <plugin name="lolipo-ojisan" version="1.0.1" - href="http://svn.coderepos.org/share/lang/javascript/vimperator-plugins/trunk/lolipo-ojisan.js" + href="http://github.com/vimpr/vimperator-plugins/blob/master/lolipo-ojisan.js" summary="Chat with lolipo-ojisan." lang="en-US" xmlns="http://vimperator.org/namespaces/liberator"> @@ -82,7 +82,7 @@ let INFO = </item> </plugin> <plugin name="lolipo-ojisan" version="1.0.1" - href="http://svn.coderepos.org/share/lang/javascript/vimperator-plugins/trunk/lolipo-ojisan.js" + href="http://github.com/vimpr/vimperator-plugins/blob/master/lolipo-ojisan.js" summary="ロリポおじさんとチャットしよう" lang="ja" xmlns="http://vimperator.org/namespaces/liberator"> diff --git a/migemized_find.js b/migemized_find.js index 3e9a16b..dffb028 100644 --- a/migemized_find.js +++ b/migemized_find.js @@ -1,5 +1,5 @@ -/* {{{ -Copyright (c) 2008-2009, anekos. +/* NEW BSD LICENSE {{{ +Copyright (c) 2008-2011, anekos. All rights reserved. Redistribution and use in source and binary forms, with or without modification, @@ -32,57 +32,76 @@ THE POSSIBILITY OF SUCH DAMAGE. }}} */ -// PLUGIN_INFO {{{ -let PLUGIN_INFO = -<VimperatorPlugin> - <name>Migemized Find</name> - <name lang="ja">Migemized Find</name> - <description>Migemize default page search.</description> - <description lang="ja">デフォルトのドキュメント内検索をミゲマイズする。</description> - <version>2.10.1</version> - <author mail="anekos@snca.net" homepage="http://d.hatena.ne.jp/nokturnalmortum/">anekos</author> - <license>new BSD License (Please read the source code comments of this plugin)</license> - <license lang="ja">修正BSDライセンス (ソースコードのコメントを参照してください)</license> - <updateURL>https://github.com/vimpr/vimperator-plugins/raw/master/migemized_find.js</updateURL> - <minVersion>3.0</minVersion> - <maxVersion>3.0</maxVersion> - <detail><![CDATA[ - == Usage == - 検索ワードの一文字目が - '/' => 正規表現検索 - '?' => Migemo検索 - 以外 => Migemo検索 - - 検索ワードを指定色で強調表示する: - >|| - :ml <検索ワード> [-c <色>] - :migelight <検索ワード> [-c <色>] - ||< - - 指定の色の強調表示を消す: - >|| - :rml <色1> <色2> ... <色N> - :removemigelight <色1> <色2> ... <色N> - ||< - - 全ての強調表示を消す: - >|| - :ml! all - :migelight! all - ||< - - ミ言語設定: - >|| - let g:migemized_find_language = "cat"; - ||< - - == Link == - http://d.hatena.ne.jp/nokturnalmortum/20080805/1217941126 - ]]></detail> -</VimperatorPlugin>; +// INFO {{{ +let INFO = +<> + <plugin name="MigemizedFind" version="2.11.3" + href="http://vimpr.github.com/" + summary="Search and Highlight with Migemo." + lang="en-US" + xmlns="http://vimperator.org/namespaces/liberator"> + <author email="anekos@snca.net">anekos</author> + <license>New BSD License</license> + <project name="Vimperator" minVersion="3.0"/> + </plugin> + <plugin name="MigemizedFind" version="2.11.3" + href="http://vimpr.github.com/" + summary="Migemo で検索 & ハイライト" + lang="ja" + xmlns="http://vimperator.org/namespaces/liberator"> + <author email="anekos@snca.net">anekos</author> + <license>New BSD License</license> + <project name="Vimperator" minVersion="3.0"/> + <p> + このプラグインは _libly.js が必要です。 + </p> + <item> + <tags>migemized_find_search_word_spec</tags> + <description><p>First letter of search word: + <dl> + <dt>/</dt><dd>Regexp search</dd> + <dt>?</dt><dd>Migemo search</dd> + <dt>otherwise</dt><dd>Migemo search</dd> + </dl> + </p></description> + </item> + <item> + <tags>:migelight</tags> + <tags>:ml</tags> + <spec>:migelight <oa>-color=<a>color</a></oa> <a>word</a></spec> + <spec>:ml <oa>-color=<a>color</a></oa> <a>word</a></spec> + <description><p> + <a>word</a> をハイライトする。 + </p></description> + </item> + <item> + <tags>:removemigelight</tags> + <tags>:rml</tags> + <spec>:removemigelight <a>color1</a> <oa>color2</oa> <oa>color3</oa> ...</spec> + <spec>:rml <a>color1</a> <oa>color2</oa> <oa>color3</oa> ...</spec> + <description><p> + <a>color</a> のハイライトを削除する。 + <a>color</a> に "all" を指定すると全て削除される。 + </p></description> + </item> + <item> + <tags>g:migemized_find_language</tags> + <spec>let g:migemized_find_language = <a>lang</a></spec> + <description><p> + 検索対象言語の設定 + </p></description> + </item> + <item> + <tags>g:migemized_find_history_limit</tags> + <spec>let g:migemized_history_limit = <a>number</a></spec> + <description><p> + 検索履歴の保存される最大数 (デフォルト: 100) + </p></description> + </item> + </plugin> +</>; // }}} - (function () { let XMigemoCore = Components.classes['@piro.sakura.ne.jp/xmigemo/factory;1'] @@ -237,6 +256,8 @@ let PLUGIN_INFO = for each ([name, value] in Iterator(colors)) ]; + let store = storage.newMap(__context__.NAME, {store: true}); + function s2b (s, d) (!/^(\d+|false)$/i.test(s)|parseInt(s)|!!d*2)&1<<!s; function getPosition (elem) { @@ -266,6 +287,9 @@ let PLUGIN_INFO = MODE_REGEXP: 1, MODE_MIGEMO: 2, + // 検索履歴 + history: store.get('history', []), + // 全体で共有する変数 lastSearchText: null, lastSearchExpr: null, @@ -300,7 +324,8 @@ let PLUGIN_INFO = let result = []; (function (frame) { // ボディがない物は検索対象外なので外す - if (frame.document.body.localName.toLowerCase() == 'body') + let body = frame.document.querySelector('body'); + if (body && body.localName.toLowerCase() == 'body') result.push(frame); for (let i = 0; i < frame.frames.length; i++) arguments.callee(frame.frames[i]); @@ -308,10 +333,24 @@ let PLUGIN_INFO = return result; }, + // 履歴にアイテム追加 + pushHistory: function (s) { + let exists = false, newHistory = []; + newHistory.push(s); + for (let [i, h] in Iterator(this.history)) { + if (h === s) { + exists = true; + } else { + newHistory.push(h); + } + } + this.history = newHistory; + }, + // ボディを範囲とした Range を作る makeBodyRange: function (frame) { let range = frame.document.createRange(); - range.selectNodeContents(frame.document.body); + range.selectNodeContents(frame.document.querySelector('body')); return range; }, @@ -444,6 +483,7 @@ let PLUGIN_INFO = findAgain: function (reverse) { let backwards = !!(!this.lastDirection ^ !reverse); let last = this.storage.lastResult; + let frames = this.currentFrames; // 前回の結果がない場合、(初め|最後)のフレームを対象にする @@ -494,6 +534,8 @@ let PLUGIN_INFO = }, submit: function () { + this.pushHistory(this.currentSearchText); + this.lastSearchText = this.currentSearchText; this.lastSearchExpr = this.currentSearchExpr; this.lastColor = this.currentColor; @@ -570,6 +612,8 @@ let PLUGIN_INFO = clearTimeout(delayCallTimer); delayedFunc(); } + if (MF.currentSearchText !== command) + MF.findFirst(command, forcedBackward); if (!MF.submit()) liberator.echoerr('not found: ' + MF.currentSearchText); }, @@ -578,40 +622,63 @@ let PLUGIN_INFO = MF.cancel(); }, - onKeyPress: function (str) { - liberator.log('onKeyPress'); + onChange: function (str) { if (typeof str == 'string') { - liberator.log('findFirst'); _findFirst(str, _backwards); } else if (str === false) MF.findAgain(); }, - }; - commandline.registerCallback("change", modes.SEARCH_FORWARD, migemized.onKeyPress); - commandline.registerCallback("submit", modes.SEARCH_FORWARD, migemized.onSubmit); - commandline.registerCallback("cancel", modes.SEARCH_FORWARD, migemized.onCancel); - commandline.registerCallback("change", modes.SEARCH_BACKWARD, migemized.onKeyPress); - commandline.registerCallback("submit", modes.SEARCH_BACKWARD, migemized.onSubmit); - commandline.registerCallback("cancel", modes.SEARCH_BACKWARD, migemized.onCancel); + completer: function (context, args) { + context.compare = CompletionContext.Sort.unsorted; + context.completions = [ + [v, v] + for ([, v] in Iterator(MF.history)) + ]; + } + }; finder.findAgain = migemized.findAgain; + plugins.libly.$U.around( + finder, + 'openPrompt', + function (next, [mode]) { + let res = next(); + plugins.libly.$U.around(commandline._input, 'change', function (next, [str]) migemized.onChange(str)); + plugins.libly.$U.around(commandline._input, 'submit', function (next, [str]) migemized.onSubmit(str)); + plugins.libly.$U.around(commandline._input, 'cancel', function (next, [str]) migemized.onCancel()); + commandline._input.complete = migemized.completer; + return res; + }, + true + ); + // highlight コマンド commands.addUserCommand( ['ml', 'migelight'], 'Migelight matched words', function (args) { - let r = MF.highlightAll(args.join(' '), args['-color']); + let w = args.literalArg; + MF.pushHistory(w); + let r = MF.highlightAll(w, args['-color']); liberator.echo(r ? r.length + ' words migelighted.' : 'word not found.'); }, { - bang: true, + literal: 0, options: [ [['-color', '-c'], commands.OPTION_STRING, null, colorsCompltions], - ] - } + ], + completer: function (context, args) { + context.compare = void 0; + context.completions = [ + [v, 'History'] + for ([, v] in Iterator(MF.history)) + ]; + } + }, + true ); // remove highlight コマンド @@ -647,10 +714,17 @@ let PLUGIN_INFO = [['-backward', '-b'], commands.OPTION_NOARG], [['-color', '-c'], commands.OPTION_STRING, null, colorsCompltions], ] - } + }, + true ); // 外から使えるように liberator.plugins.migemizedFind = MF; + // 履歴の保存 + __context__.onUnload = function () { + let limit = parseInt(liberator.globalVariables.migemized_find_history_limit || 100, 10); + store.set('history', MF.history.slice(0, limit)); + }; + })(); diff --git a/modules/libDLImage.js b/modules/libDLImage.js new file mode 100644 index 0000000..63fdcb9 --- /dev/null +++ b/modules/libDLImage.js @@ -0,0 +1,72 @@ +// +// libDLImage.js +// +// libDLImage.js is code for download image data. +// libDLImage.js is ran on ChromeWorker thread. +// +// +// accept message: +// { +// 'imageUrl' :string, +// 'savePath' :string, +// 'refererUrl':string, +// 'cookie' :string +// } +// +// imageUrl : remote image URL +// savePath : full path on local strage +// refererUrl : referer string (optional) +// cookie : cookie string (optional) +// +// +// send message: +// { +// 'status' :string, +// 'message' :JSObject, +// 'savePath':string +// } +// +// status : 'normarl' or 'error' +// message : error message (string) or image data (binary) +// savePath : full path on local strage +// ( only success and same savePath on the accept message ) +// +var JSONMessage; +var xhrImg; + +function trueImage(){ + var instream=xhrImg.responseText; + self.postMessage( + {'status':'normal','message':instream,'savePath':JSONMessage.savePath} + ); + return; +}; + +function falseImage(){ + self.postMessage({'status':'error','message':'IMAGE FILE ACCEPT ERROR!!'}); + return false; +}; + +function downloadImage(){ + xhrImg=new XMLHttpRequest(); + xhrImg.addEventListener("load",trueImage,false); + xhrImg.addEventListener("error",falseImage,false); + xhrImg.open("GET",JSONMessage.imageUrl,false); + xhrImg.overrideMimeType('text/plain;charset=x-user-defined'); + if(0<JSONMessage.refererUrl.length){ + xhrImg.setRequestHeader('Referer',JSONMessage.refererUrl); + }; + if(0<JSONMessage.cookie){ + xhrImg.setRequestHeader('Cookie',JSONMessage.cookie); + }; + xhrImg.send(null); +}; + +addEventListener("message",function(event){ + JSONMessage=JSON.parse(event.data); + if(JSONMessage.imageUrl===undefined||JSONMessage.savePath===undefined){ + self.postMessage({'status':'error','message':'PARAMETA ERROR!!'}); + return false; + } + downloadImage(); +}, false); @@ -59,7 +59,7 @@ let PLUGIN_INFO = // INFO {{{ let INFO = <plugin name="Morse" version="1.2.1" - href="http://svn.coderepos.org/share/lang/javascript/vimperator-plugins/trunk/morse.js" + href="http://github.com/vimpr/vimperator-plugins/blob/master/morse.js" summary="Morse code" xmlns="http://vimperator.org/namespaces/liberator"> <author email="anekos@snca.net">anekos</author> @@ -120,7 +120,7 @@ let INFO = 9: '----.', 0: '-----', '.': '.-.-.-', - '': '--..--', + ',': '--..--', '?': '..--..', ' ': '-...-', '-': '-....-', @@ -199,9 +199,24 @@ let INFO = '\u30BB': '.---.', '\u30B9': '---.-', '\u30F3': '.-.-.', + '゛': '..', + '゜': '..--.' }; - let [short, long, interval] = [100, 400, 200]; + let codeTableIAlphabet = {}; + let codeTableIKana = {}; + + for (let [n, v] in Iterator(codeTable)) { + if (/[\w.,? \-\/@]/.test(n)) + codeTableIAlphabet[v] = n; + } + + for (let [n, v] in Iterator(codeTable)) { + if (/[^a-z]/.test(n)) + codeTableIKana[v] = n; + } + + let [defaultShort, defaultLong, defaultInterval] = [100, 400, 200]; let keybd_event = (function () { @@ -251,6 +266,18 @@ let INFO = function toCode (text) Array.slice(text).map(function (c) codeTable[c.toLowerCase()] || '').join(' '); + function fromCode (text, kana, char) { + if (char) { + let re = new RegExp(util.escapeRegex(char[0]) + '|' + util.escapeRegex(char[1]), 'g'); + text = text.replace(re, function (m) (m === char[0] ? '.' : '-')); + } + + let table = kana ? codeTableIKana : codeTableIAlphabet; + let result = ''; + text.split(/\s+/).forEach(function (c) (result += table[c])); + return result; + } + commands.addUserCommand( ['morse'], 'Mooooooooooooorse', @@ -260,11 +287,11 @@ let INFO = args['-clipboard'] && util.copyToClipboard(code); args['-echo'] && liberator.echo(code); - [short, long, interval] = + let [short, long, interval] = [ - args['-short'] || short, - args['-long'] || long, - args['-interval'] || interval + args['-short'] || defaultShort, + args['-long'] || defaultLong, + args['-interval'] || defaultInterval ]; Morse(short, long, interval)(code); @@ -277,13 +304,13 @@ let INFO = [['-short', '-s'], commands.OPTION_INT], [['-long', '-l'], commands.OPTION_INT], [['-interval', '-i'], commands.OPTION_INT], + [['-char', '-char'], commands.OPTION_STRING], [['-raw', '-r'], commands.OPTION_NOARG] ], }, true ); - })(); // vim:sw=2 ts=2 et si fdm=marker: diff --git a/multi_requester.js b/multi_requester.js index 9bfb4c4..f075779 100644 --- a/multi_requester.js +++ b/multi_requester.js @@ -123,7 +123,7 @@ var libly = liberator.plugins.libly; var $U = libly.$U; var logger = $U.getLogger("multi_requester"); var mergedSiteinfo = {}; -var store = storage.newMap('plugins-multi_requester', true); +var store = storage.newMap('plugins-multi_requester', {store: true}); //}}} // Vimperator plugin command register {{{ @@ -242,7 +242,7 @@ var DataAccess = { if (useWedata) { logger.log("use wedata"); - var wedata = new libly.Wedata("Multi%20Requester"); + var wedata = new libly.Wedata("Multi Requester"); wedata.getItems(24 * 60 * 60 * 1000, function(item) { var site = item.data; @@ -332,9 +332,9 @@ var MultiRequester = { count: count } }); - req.addEventListener("onException", $U.bind(this, this.onException)); - req.addEventListener("onSuccess", $U.bind(this, this.onSuccess)); - req.addEventListener("onFailure", $U.bind(this, this.onFailure)); + req.addEventListener("exception", $U.bind(this, this.onException)); + req.addEventListener("success", $U.bind(this, this.onSuccess)); + req.addEventListener("failure", $U.bind(this, this.onFailure)); req.get(); MultiRequester.requestCount++; } @@ -394,9 +394,9 @@ var MultiRequester = { if (!el) throw "extract link failed.: extractLink -> " + extractLink; var url = $U.pathToURL(el[0], res.req.url); var req = new libly.Request(url, null, $U.extend(res.req.options, { extractLink: true })); - req.addEventListener("onException", $U.bind(this, this.onException)); - req.addEventListener("onSuccess", $U.bind(this, this.onSuccess)); - req.addEventListener("onFailure", $U.bind(this, this.onFailure)); + req.addEventListener("exception", $U.bind(this, this.onException)); + req.addEventListener("success", $U.bind(this, this.onSuccess)); + req.addEventListener("failure", $U.bind(this, this.onFailure)); req.get(); MultiRequester.requestCount++; MultiRequester.doProcess = true; diff --git a/my-style.js b/my-style.js new file mode 100644 index 0000000..2ae6098 --- /dev/null +++ b/my-style.js @@ -0,0 +1,318 @@ +/* NEW BSD LICENSE {{{ +Copyright (c) 2011, anekos. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. + + +################################################################################### +# http://sourceforge.jp/projects/opensource/wiki/licenses%2Fnew_BSD_license # +# に参考になる日本語訳がありますが、有効なのは上記英文となります。 # +################################################################################### + +}}} */ + +// INFO {{{ +let INFO = +<> + <plugin name="MyStyle" version="1.0.0" + href="http://vimpr.github.com/" + summary="Apply my style sheet to current page." + lang="en-US" + xmlns="http://vimperator.org/namespaces/liberator"> + <author email="anekos@snca.net">anekos</author> + <license>New BSD License</license> + <project name="Vimperator" minVersion="3.0"/> + <p></p> + <item> + <tags>:mystyle-set</tags> + <spec>:mys<oa>tyle</oa> set <a>URL</a> <a>CSS</a></spec> + <description><p> + Set <a>CSS</a> to <a>URL</a>. + If <a>CSS</a> has any names of defined style, they are expanded. + </p></description> + </item> + <item> + <tags>:mystyle-unset</tags> + <spec>:mys<oa>tyle</oa> unset <a>URL</a></spec> + <description><p> + Unset style for <a>URL</a>. + </p></description> + </item> + <item> + <tags>g:my_style_define</tags> + <spec>g:my_style_define = <a>STYLES</a></spec> + <description> + <p>Define some styles and their names for completion and command.</p> + <p> + e.g. + <code><![CDATA[ +:js <<EOM +liberator.globalVariables.my_style_define = { + blackboard: '* {color: white !important; background-color: #004040 !important }', + bold: '* { font-weight: bold !important }' +}; +EOM + ]]></code> + </p> + </description> + </item> + </plugin> + <plugin name="MyStyle" version="1.0.0" + href="http://vimpr.github.com/" + summary="現在のページに自分用のスタイルを適用する" + lang="ja" + xmlns="http://vimperator.org/namespaces/liberator"> + <author email="anekos@snca.net">anekos</author> + <license>New BSD License</license> + <project name="Vimperator" minVersion="3.0"/> + <p></p> + <item> + <tags>:mystyle-set</tags> + <spec>:mys<oa>tyle</oa> set <a>URL</a> <a>CSS</a></spec> + <description><p> + <a>URL</a> に <a>CSS</a> を適用します。 + <a>CSS</a> に定義されてスタイルの名前が含まれていると、それらは展開されます。 + </p></description> + </item> + <item> + <tags>:mystyle-set</tags> + <spec>:mys<oa>tyle</oa> unset <a>URL</a></spec> + <description><p> + <a>URL</a> セットされたスタイルを解除します。 + </p></description> + </item> + <item> + <tags>g:my_style_define</tags> + <spec>g:my_style_define = <a>STYLES</a></spec> + <description> + <p>補完とコマンド用にスタイルとその名前を定義します。</p> + <p> + e.g. + <code><![CDATA[ +:js <<EOM +liberator.globalVariables.my_style_define = { + blackboard: '* {color: white !important; background-color: #004040 !important }', + bold: '* { font-weight: bold !important }' +}; +EOM + ]]></code> + </p> + </description> + </item> + </plugin> +</>; +// }}} + +(function () { + + const StyleNamePrefix = 'my-style-'; + + const DefaultDefinedStyle = { + BLACKBOARD: <><![CDATA[ + * { + color: white !important; + background-color: #004040 !important; + background-image: none !important; + } + ]]></>, + NEKOME: <><![CDATA[ + body { + background-image: url(http://snca.net/images/redeye.jpg) !important; + } + * { + background: transparent !important; + color: white !important; + } + ]]></>, + VIMPMASK: <><![CDATA[ + body { + background-image: url(http://snca.net/images/ildjarn.png) !important; + background-repeat: no-repeat !important; + background-position: right bottom !important; + background-attachment: fixed !important; + } + ]]></> + }; + + if (!__context__.DefinedStyles) { + Object.defineProperty( + __context__, + 'DefinedStyles', + { + get: function () (liberator.globalVariables.my_style_define || DefaultDefinedStyle) + } + ); + } + + let Currents = {}; + + const store = storage.newMap(__context__.NAME, {store: true}); + + function expand (css) + css.replace(/\w+/g, function (n) (DefinedStyles.hasOwnProperty(n) ? DefinedStyles[n] : n)); + + function urlCompleter (context) { + let cs = []; + let loc = content.location; + let pathname = loc.pathname; + let paths = pathname.split('/').slice(1); + + for (let i = 0; i < paths.length; i++) { + cs.push([ + [loc.protocol + '/', loc.hostname].concat(paths.slice(0, i)).join('/') + '/*', + 'Current URL' + ]); + } + + cs.push([loc.hostname, 'Current HOST']) + + context.compare = null; + context.completions = cs; + + context.fork( + 'CURRENT', + 0, + context, + function (context, args) { + context.title = ['Style set URL']; + context.completions = [ + [url, css] + for ([url, css] in Iterator(Currents)) + ]; + } + ); + } + + function styleCompleter (context, args) { + let style = Currents[args[0]]; + if (style) + context.completions = [[style, 'CURRENT']]; + + context.fork( + 'DEFINED', + 0, + context, + function (context) { + let m = args.literalArg.match(/(\s+)\S*$/); + if (m) + context.advance(m.index + m[1].length); + + context.filters = [CompletionContext.Filter.textDescription]; + context.completions = [ + [n, v] + for ([n, v] in Iterator(DefinedStyles)) + ]; + } + ); + } + + function styleNameCompleter (context) { + context.completions = [ + [url, css] + for ([url, css] in Iterator(Currents)) + ]; + } + + function set (url, css) { + Currents[url] = css; + styles.addSheet(false, StyleNamePrefix + url, url, expand(css)); + } + + function unset (url, name) { + if (name) + url = url.slice(StyleNamePrefix.length); + delete Currents[url]; + styles.removeSheet(false, StyleNamePrefix + url); + } + + const SubCommands = [ + new Command( + ['s[et]'], + 'Set style', + function (args) { + set(args[0], args.literalArg); + }, + { + literal: 1, + completer: function (context, args) { + (args.length > 1 ? styleCompleter : urlCompleter)(context, args); + } + } + ), + + new Command( + ['u[nset]'], + 'Unset style', + function (args) { + let m = args[0]; + if (m) { + unset(m); + } else { + for (let [, style] in Iterator(styles)) { + if (style.name.indexOf(StyleNamePrefix) === 0) + unset(style.name, true); + } + } + }, + { + bang: true, + literal: 0, + completer: styleNameCompleter + } + ), + + new Command( + ['p[ermanent]'], + 'Permanent current styles', + function (args) { + store.set('permanent', Currents); + store.save(); + liberator.echo('Permanent current styles'); + }, + { + argCount: '0', + } + ) + ]; + + commands.addUserCommand( + ['mys[tyle]'], + 'Set style for me', + function (args) { + /* TODO list */ + }, + { + subCommands: SubCommands + }, + true + ); + + Currents = store.get('permanent', {}); + for (let [url, css] in Iterator(Currents)) { + set(url, css); + } + +})(); + +// vim:sw=2 ts=2 et si fdm=marker: diff --git a/namakubi.js b/namakubi.js index 5621061..7d389f5 100644 --- a/namakubi.js +++ b/namakubi.js @@ -65,7 +65,7 @@ let PLUGIN_INFO = // INFO {{{ let INFO = <plugin name="生首" version="1.0.0" - href="http://svn.coderepos.org/share/lang/javascript/vimperator-plugins/trunk/namakubi.js" + href="http://github.com/vimpr/vimperator-plugins/blob/master/namakubi.js" summary="Wonderful Namakubi Talker" xmlns="http://vimperator.org/namespaces/liberator"> <author email="anekos@snca.net">anekos</author> diff --git a/nextlink.js b/nextlink.js index 00768dd..e96ea1c 100644 --- a/nextlink.js +++ b/nextlink.js @@ -112,7 +112,7 @@ var actpattern = liberator.globalVariables.nextlink_nositeinfo_act || "e"; var nositeinfoAct = nositeinfoActions[actpattern]; -var localSiteinfo = storage.newMap("nextlink-local-siteinfo", false); +var localSiteinfo = storage.newMap("nextlink-local-siteinfo", {store: false}); if (localSiteinfo) localSiteinfo = [ info for ([ i, info ] in localSiteinfo) ]; @@ -264,9 +264,9 @@ Autopager.prototype = { return true; } - req.addEventListener("onSuccess", $U.bind(this, this.onSuccess)); - req.addEventListener("onFailure", $U.bind(this, this.onFailure)); - req.addEventListener("onException", $U.bind(this, this.onFailure)); + req.addEventListener("success", $U.bind(this, this.onSuccess)); + req.addEventListener("failure", $U.bind(this, this.onFailure)); + req.addEventListener("exception", $U.bind(this, this.onFailure)); req.get(); }, onSuccess: function(res) { diff --git a/nico_related_videos.js b/nico_related_videos.js deleted file mode 100644 index 14bcc97..0000000 --- a/nico_related_videos.js +++ /dev/null @@ -1,111 +0,0 @@ -// ==VimperatorPlugin== -// @name Nico Related Videos -// @description-ja ニコニコ動画のオススメ動画のリスト -// @license Creative Commons 2.1 (Attribution + Share Alike) -// @version 1.3.1 -// ==/VimperatorPlugin== -// -// Author: -// anekos -// -// Usage: -// ニコニコ動画のオススメ動画のリストを補完で表示します。 -// -// コマンドにURL以外を指定したときの動作: -// 空 => ニコニコ動画のトップページに移動 -// 動画ID(sm.+) => 動画に移動 -// ":" タグ名 => タグ検索 -// その他文字列 => ニコニコ動画でそれを検索 -// -// "!" をつけると新しいタブで開く。 -// -// Link: -// http://d.hatena.ne.jp/nokturnalmortum/20080910#1220991278 - - -(function () { - - function getVideoId () { - let m = buffer.URL.match(/^http:\/\/(?:tw|es|de|www)\.nicovideo\.jp\/watch\/([a-z0-9]+)/); - return m && m[1]; - } - - function httpRequest (uri, onComplete) { - var xhr = new XMLHttpRequest(); - xhr.onreadystatechange = function () { - if (xhr.readyState == 4) { - if (xhr.status == 200) - onComplete(xhr.responseXML); - else - throw new Error(xhr.statusText) - } - }; - xhr.open('GET', uri, true); - xhr.send(null); - } - - function getRelatedVideos () { - let videoId = getVideoId(); - if (!videoId) - return []; - let videos = []; - let uri = 'http://www.nicovideo.jp/api/getrelation?sort=p&order=d&video=' + videoId; - let xhr = new XMLHttpRequest(); - xhr.open('GET', uri, false); - xhr.send(null); - let xml = xhr.responseXML; - let v, vs = xml.evaluate('//video', xml, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE , null); - while (v = vs.iterateNext()) { - let [cs, video] = [v.childNodes, {}]; - for each (let c in cs) - if (c.nodeName != '#text') - video[c.nodeName] = c.textContent; - videos.push(video); - } - return videos; - } - - function getRelatedTags () { - let doc = content.document; - let nodes = doc.getElementsByClassName('nicopedia'); - return [it.textContent for each (it in nodes) if (it.rel == 'tag')]; - } - - - let last = {url: null, completions: []}; - let nothing = 'No related videos'; - - commands.addUserCommand( - ['nicorelated'], - 'niconico related videos', - function (args) { - let url = args.string; - url = (function () { - if (url == nothing) - return 'http://www.nicovideo.jp/'; - if (url.match(/^[a-z]{2}\d+$/)) - return 'http://www.nicovideo.jp/watch/' + url; - if (url.match(/^[:\uff1a]/)) - return 'http://www.nicovideo.jp/tag/' + encodeURIComponent(url.substr(1)); - if (url.indexOf('http://') == -1) - return 'http://www.nicovideo.jp/search/' + encodeURIComponent(url); - })() || url; - liberator.open(url, args.bang ? liberator.NEW_TAB : liberator.CURRENT_TAB); - }, - { - bang: true, - completer: function (context, arg) { - if ((buffer.URL != last.url) || !last.completions.length) { - last.completions = []; - getRelatedVideos().forEach(function (it) last.completions.push([it.url, it.title])); - getRelatedTags().forEach(function (it) last.completions.push([":" + it, "tag"])); - last.url = buffer.URL; - } - context.title = ['Keyword']; - context.completions = last.completions; - } - }, - true - ); - -})(); diff --git a/nicolist.js b/nicolist.js new file mode 100644 index 0000000..854a869 --- /dev/null +++ b/nicolist.js @@ -0,0 +1,166 @@ +/* + * ニコニコ動画のマイリストを何かするプラグイン + * http://twitter.com/ebith + */ + +var INFO = +<plugin name="nicolist" + version="0.3" + summary="ニコニコ動画のマイリストを操作します" + xmlns="http://vimperator.org/namespaces/liberator"> + <author email="ebith.h@gmail.com">ebith</author> + <license href="http://www.opensource.org/licenses/bsd-license.php">New BSD License</license> + <project name="Vimperator" minVersion="3.2"/> + <item> + <tags>:nicolist-add</tags> + <spec>:nicolist add <a>mylist-id</a> <a>mylist-comment</a></spec> + <description><p><a>mylist-id</a>で指定したマイリストに動画を追加します。マイリストコメントの入力も可能です</p></description> + </item> + <item> + <tags>:nicolist-delete</tags> + <spec>:nicolist delete <a>mylist-id</a> <a>item-id</a></spec> + <description><p><a>mylist-id</a>のみであればマイリストを削除、<a>item-id</a>の指定もあれば動画をマイリストから削除します</p></description> + </item> + <item> + <tags>:nicolist-new</tags> + <spec>:nicolist new <oa>-p<oa>ublic</oa></oa> <a>name</a> </spec> + <description><p><a>name</a>という名前でマイリストを新規作成します。<oa>-public</oa>を付けると公開マイリストになります</p></description> + </item> + <item> + <tags>:nicolist-open</tags> + <spec>:nicolist open <a>mylist-id</a> <a>video-id</a></spec> + <description><p><a>mylist-id</a>のみであればマイリストを、<a>video-id</a>の指定もあれば動画を開きます</p></description> + </item> +</plugin>; + +commands.addUserCommand( + ['nicolist'], + 'ニコニコ動画のマイリストを操作する', + function(args) { + liberator.echoerr('nicolist : サブコマンドが足りない!'); + }, + { + subCommands: [ + new Command( + ['a[dd]'], + 'マイリストに追加する', + function (args) { + let video_id = content.window.wrappedJSObject.video_id; + if (!video_id) { + return liberator.echoerr('nicolist : watchページじゃない!'); + } + let [mylist_id, description] = args; + if (!description){ description = ''; } //undefinedが入っているとそれをマイリストコメントにしてしまうので。 + let token = content.window.wrappedJSObject.so.variables.csrfToken; + let url = 'http://www.nicovideo.jp/api/mylist/add?group_id=' + mylist_id + '&token=' + token + '&item_id=' + video_id + '&description=' + description; + liberator.echo('nicolist add : ' + JSON.parse(util.httpGet(url).responseText).status); + }, + { + literal: 1, + completer: mylistCompleter, + } + ), + new Command( + ['o[pen]'], + 'マイリストか動画を開く', + function (args) { + let [mylist_id, video_id] = args; + if (video_id) { + let url = 'http://www.nicovideo.jp/watch/' + video_id; + liberator.open(url, liberator.NEW_TAB); + } else if (mylist_id) { + let url = 'http://www.nicovideo.jp/mylist/' + mylist_id; + liberator.open(url, liberator.NEW_TAB); + } + }, + { + literal: 1, + completer: mylistCompleter, + } + ), + new Command( + ['n[ew]'], + 'マイリストを新しく作る', + function (args) { + let token = getToken(); + let url = 'http://www.nicovideo.jp/api/mylistgroup/add?name=' + args.literalArg + '&token=' + token; + if ( args['-public'] ) { url += '&public=1'; } + liberator.echo('nicolist new : ' + JSON.parse(util.httpGet(url).responseText).status); + }, + { + literal: 0, + options: [ [['-public', '-p'], commands.OPTION_NOARG] ], + } + ), + new Command( + ['d[elete]'], + 'マイリストか動画を削除する', + function (args) { + let token = getToken(); + let [mylist_id, item_id] = args; + if (item_id) { + let url = 'http://www.nicovideo.jp/api/mylist/delete?group_id=' + mylist_id + '&id_list[0][]=' + item_id + '&token=' + token; + liberator.echo('nicolist delete : ' + JSON.parse(util.httpGet(url).responseText).status); + } else if (mylist_id) { + let url = 'http://www.nicovideo.jp/api/mylistgroup/delete?group_id=' + mylist_id + '&token=' + token; + liberator.echo('nicolist delete : ' + JSON.parse(util.httpGet(url).responseText).status); + } + }, + { + literal: 1, + completer: mylistCompleter, + } + ), + ] + }, + true +); + +function mylistCompleter (context, args) { + if (args.completeArg == 0){ + context.incomplete = true; + context.title = ["id", "title"]; + context.filters = [CompletionContext.Filter.textDescription]; + context.compare = void 0; + + let url = 'http://www.nicovideo.jp/api/mylistgroup/list'; + util.httpGet(url, function (xhr) { + context.incomplete = false; + context.completions = [ + [v.id, v.name] + for ([k, v] in Iterator(JSON.parse(xhr.responseText).mylistgroup)) + ]; + }); + } else if (args.completeArg == 1 && !/add/.test(context.name)){ + context.incomplete = true; + context.title = ["id", "title"]; + context.filters = [CompletionContext.Filter.textDescription]; + context.compare = void 0; + + let url = 'http://www.nicovideo.jp/api/mylist/list?group_id=' + args[0]; + util.httpGet(url, function (xhr) { + context.incomplete = false; + + if (/open/.test(context.name)) { + context.completions = [ + [v.item_data.video_id, v.item_data.title] + for ([k, v] in Iterator(JSON.parse(xhr.responseText).mylistitem.sort(sorter))) + ]; + } else if (/delete/.test(context.name)) { + context.completions = [ + [v.item_id, v.item_data.title] + for ([k, v] in Iterator(JSON.parse(xhr.responseText).mylistitem.sort(sorter))) + ]; + } + }); + } +} + +function sorter (a, b) { + return - (a.create_time - b.create_time); +} + +function getToken () { + let url = 'http://www.nicovideo.jp/my/mylist'; + return util.httpGet(url).responseText.match(/NicoAPI\.token.+/)[0].match(/\d{5}-\d{10}-[\d\w]{40}/)[0]; +} diff --git a/notifier.js b/notifier.js index 07784eb..cb78c9d 100644 --- a/notifier.js +++ b/notifier.js @@ -255,14 +255,14 @@ function bootstrap() { this.options.headers, $U.extend({ asynchronous: true }, this.options.extra) ); - req.addEventListener("onSuccess", $U.bind(this, function(res) { + req.addEventListener("success", $U.bind(this, function(res) { logger.log("initialized"); if (typeof this.parse == "function") this.cache = this.parse(res); if (this.cache) this.initialized = true; })); - req.addEventListener("onFailure", function(res) { logger.log(res); }); - req.addEventListener("onException", function(res) { logger.log(res); }); + req.addEventListener("failure", function(res) { logger.log(res); }); + req.addEventListener("exception", function(res) { logger.log(res); }); if (this.method == "POST") req.post(); else @@ -277,7 +277,7 @@ function bootstrap() { this.options.headers, $U.extend({ asynchronous: true }, this.options.extra) ); - req.addEventListener("onSuccess", $U.bind(this, function(res) { + req.addEventListener("success", $U.bind(this, function(res) { var parsed, diff; if (typeof this.parse == "function") parsed = this.parse(res); if (parsed && typeof this.diff == "function") diff = this.diff(this.cache, parsed); @@ -0,0 +1,81 @@ +/* NEW BSD LICENSE {{{ +Copyright (c) 2011, anekos. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. + + +################################################################################### +# http://sourceforge.jp/projects/opensource/wiki/licenses%2Fnew_BSD_license # +# に参考になる日本語訳がありますが、有効なのは上記英文となります。 # +################################################################################### + +}}} */ + +// INFO {{{ +let INFO = +<> + <plugin name="nume" version="1.0.0" + href="http://vimpr.github.com/" + summary="Make numeronymized text." + lang="en-US" + xmlns="http://vimperator.org/namespaces/liberator"> + <author email="anekos@snca.net">anekos</author> + <license>New BSD License</license> + <project name="Vimperator" minVersion="3.0"/> + <p></p> + <item> + <tags>:nume</tags> + <spec>:nume</spec> + <description><p>Make numeronymized text.</p></description> + </item> + </plugin> +</>; +// }}} + + +(function () { + + commands.addUserCommand( + ['n7m', 'numeronym', 'numerorym', 'lotion', 'munemomym'], + 'Nume Nume', + function (args) { + let result = args.literalArg.replace(/(\S)(\S+)(\S)/g, function (a, l, c, r) (l + c.length + r)); + liberator.echo(result); + if (args.bang || args['-clipboard']) + util.copyToClipboard(result); + }, + { + bang: true, + literal: 0, + options: [ + [['-clipboard', '-c'], commands.OPTION_NOARG], + ] + }, + true + ); + +})(); + +// vim:sw=2 ts=2 et si fdm=marker: + diff --git a/open-frame.js b/open-frame.js index 5c3c40b..945128d 100644 --- a/open-frame.js +++ b/open-frame.js @@ -58,7 +58,7 @@ let PLUGIN_INFO = let INFO = <> <plugin name="openframe-command" version="1.2.1" - href="http://svn.coderepos.org/share/lang/javascript/vimperator-plugins/trunk/open-frame.js" + href="http://github.com/vimpr/vimperator-plugins/blob/master/open-frame.js" summary="Add openframe command." lang="en-US" xmlns="http://vimperator.org/namespaces/liberator"> @@ -77,7 +77,7 @@ let INFO = </item> </plugin> <plugin name="openframe-command" version="1.2.1" - href="http://svn.coderepos.org/share/lang/javascript/vimperator-plugins/trunk/open-frame.js" + href="http://github.com/vimpr/vimperator-plugins/blob/master/open-frame.js" summary="Add openframe command." lang="ja" xmlns="http://vimperator.org/namespaces/liberator"> @@ -23,7 +23,7 @@ let PLUGIN_INFO = let INFO = <> <plugin name="opener" version="1.0.0" - href="http://svn.coderepos.org/share/lang/javascript/vimperator-plugins/trunk/opener.js" + href="http://github.com/vimpr/vimperator-plugins/blob/master/opener.js" summary="URL 移動時にそのURLが既に開かれていたら、そのタブに移動する" xmlns="http://vimperator.org/namespaces/liberator"> <author>voidy21</author> @@ -259,10 +259,10 @@ let self = liberator.plugins.pino = (function() { } ); - request.addEventListener("onSuccess", function(data) { + request.addEventListener("success", function(data) { liberator.log("Removed pin from '" + link + "' was succeeded."); }); - request.addEventListener("onFailure", function(data) { + request.addEventListener("failure", function(data) { liberator.echoerr("Cannot remove pin"); }); request.post(); @@ -279,14 +279,14 @@ let self = liberator.plugins.pino = (function() { } ); - request.addEventListener("onSuccess", function(data) { + request.addEventListener("success", function(data) { if (isLoginPage(data)) { liberator.echoerr("Can't get pinned list. Maybe you should login to livedoor."); return; } result = unentifyObjectValues(liberator.eval(data.responseText)); }); - request.addEventListener("onFailure", function(data) { + request.addEventListener("failure", function(data) { liberator.echoerr("Can't get pinned list!!!"); }); request.post(); @@ -1,12 +1,12 @@ // INFO // var INFO = -<plugin name="pixiv.js" version="0.5" +<plugin name="pixiv.js" version="0.7.2" summary="Download image from pixiv" href="http://github.com/vimpr/vimperator-plugins/blob/master/pixiv.js" xmlns="http://vimperator.org/namespaces/liberator"> <author email="mitsugu.oyama@gmail.com">Mitsugu Oyama</author> <license href="http://opensource.org/licenses/mit-license.php">MIT</license> - <project name="Vimperator" minVersion="2.3"/> + <project name="Vimperator" minVersion="3.2"/> <p> You can save image from pixiv by this plugin. </p> @@ -15,6 +15,7 @@ var INFO = <spec>:pixiv</spec> <description> <p>You can save image from <link topic="http://www.pixiv.net/">pixiv</link> by this plugin.</p> + <p>You need libDLImage.js under of plugin/modules.</p> <p>You must login pixiv.</p> </description> </item> @@ -29,21 +30,69 @@ commands.addUserCommand( liberator.echoerr('This page is not pixiv.'); return false; } - if(contents.URL.search(/medium&illust_id=/i)==-1){ + if((contents.URL.search(/illust_id=/i)==-1)|| + (contents.URL.search(/mode=medium/i)==-1)){ liberator.echoerr("This page is not pixiv's image page."); return false; } - let Cc=Components.classes; - let Ci=Components.interfaces; + const Cc=Components.classes; + const Ci=Components.interfaces; + + let createWorker=function(fileName){ + let ret; + const resourceName="vimp-plugin"; + const ioService=Cc["@mozilla.org/network/io-service;1"] + .getService(Ci.nsIIOService); + const resProt=ioService.getProtocolHandler("resource") + .QueryInterface(Ci.nsIResProtocolHandler); + let pluginDirs=io.getRuntimeDirectories("plugin"); + if (pluginDirs.length === 0){ + return null; + } + resProt.setSubstitution(resourceName,ioService.newFileURI(pluginDirs[0])); + try { + worker=new ChromeWorker("resource://"+resourceName+"/modules/"+fileName); + }catch(e){ + return null; + } + return worker; + }; + let worker=createWorker('libDLImage.js'); + if(worker==null){ + liberator.echoerr('plugin directory is not found'); + return false; + } + worker.addEventListener('message',function(event){ + if(event.data.status=='error'){ + liberator.echoerr(event.data.message); + return false; + }; + let instream=event.data.message; + let savePath=event.data.savePath; + let aFile=Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile); + aFile.initWithPath(savePath); + let outstream=Cc["@mozilla.org/network/safe-file-output-stream;1"] + .createInstance(Ci.nsIFileOutputStream); + outstream.init(aFile,0x02|0x08|0x20,0664,0); + outstream.write(instream,instream.length); + if (outstream instanceof Ci.nsISafeOutputStream) { + outstream.finish(); + }else{ + outstream.close(); + } + },false); + worker.addEventListener('error',function(event){ + liberator.echoerr(event.data.status); + },false); let id; - if(-1==contents.URL.search(/\&from_sid=/i)){ - id=contents.URL.substr(contents.URL.lastIndexOf('=')+1); + let idTmp=contents.URL.match(/illust_id=(\d+)/i); + if(idTmp===null){ + liberator.echoerr("This page is not image page and not manga page."); + return false; }else{ - let st=contents.URL.search(/illust_id=/i)+'illust_id='.length; - let end=contents.URL.lastIndexOf('&'); - id=contents.URL.substr(st,end-st); + id=idTmp[1]; } let baseInfo; @@ -67,18 +116,13 @@ commands.addUserCommand( let fp=Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker); fp.init(window,'Select Directory',Ci.nsIFilePicker.modeGetFolder); let result=fp.show(); - switch(result){ - case Ci.nsIFilePicker.returnOK: - path=fp.file.path; - break; - default: - case Ci.nsIFilePicker.returnCancel: - return ''; + if(result==Ci.nsIFilePicker.returnOK){ + return fp.file; } - return path; + return null; }; - let savePath=directoryPicker(); - if(savePath.length<1) return; + let saveDirectory=directoryPicker(); + if(saveDirectory==null) return false; let getDOMHtmlDocument=function(str){ let doc; @@ -125,54 +169,57 @@ commands.addUserCommand( }; let imgUrl; + let destPath; - let truePixivImg=function(){ - let fileName=imgUrl.substr(imgUrl.lastIndexOf('/')); - if (-1!=fileName.indexOf('?')){ - fileName=fileName.substr(0,fileName.indexOf('?')); - } - let tmpPath=savePath+fileName; - let instream=xhrImg.responseText; - let aFile=Cc["@mozilla.org/file/local;1"] - .createInstance(Ci.nsILocalFile); - aFile.initWithPath(tmpPath); - let outstream=Cc["@mozilla.org/network/safe-file-output-stream;1"] - .createInstance(Ci.nsIFileOutputStream); - outstream.init(aFile,0x02|0x08|0x20,0664,0); - outstream.write(instream,instream.length); - if (outstream instanceof Ci.nsISafeOutputStream) { - outstream.finish(); - }else{ - outstream.close(); - } - }; - - let falsePixivImg=function(){ - liberator.echoerr("Image file accept error."); - return false; + let saveImage=function(){ + let objMessage={ + imageUrl :'', + savePath :'', + refererUrl:'', + cookie :'' + }; + objMessage.imageUrl=imgUrl; + objMessage.savePath=destPath.path; + objMessage.refererUrl=contents.URL; + objMessage.cookie=cookie; + let JSONmessage=JSON.stringify(objMessage); + worker.postMessage(JSONmessage); }; - let saveImage=function(){ - xhrImg=Cc["@mozilla.org/xmlextras/xmlhttprequest;1"] - .createInstance(); - xhrImg.QueryInterface(Ci.nsIDOMEventTarget); - xhrImg.addEventListener("load",truePixivImg,false); - xhrImg.addEventListener("error",falsePixivImg,false); - xhrImg.QueryInterface(Ci.nsIXMLHttpRequest); - xhrImg.open("GET",imgUrl,false); - xhrImg.overrideMimeType('text/plain;charset=x-user-defined'); - xhrImg.setRequestHeader('Referer',contents.URL); - xhrImg.setRequestHeader('Cookie',cookie); - xhrImg.send(null); + let getDestPath=function(url){ + let fname=url.substr(url.lastIndexOf('/')+1); + if(fname.lastIndexOf('?')!=-1){ + fname=fname.substr(0,fname.lastIndexOf('?')); + } + let path=saveDirectory.clone(); + path.append(fname); + let aFile=Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile); + let newPath=path.clone(); + aFile.initWithFile(path); + if(true===aFile.exists()){ + let value=window.prompt('すでに同じ名前のファイルがあります。デフォルトファイル名を変更してください。',fname); + if(null===value){ + return null; + }; + if(fname!=value){ + newPath=saveDirectory.clone(); + newPath.append(value); + } + } + return newPath; }; let saveImageFile=function(){ imgUrl=getImageUrl(xhrImgInfo.responseText); if(0<imgUrl.length){ + destPath=getDestPath(imgUrl); + if(destPath==null){ + return false; + }; saveImage(); }else{ liberator.echoerr("You should login pixiv :<"); - } + }; }; let getImageUrls=function(pageContents){ @@ -219,6 +266,10 @@ commands.addUserCommand( if(-1!=pnt){ imgUrl=imgUrl.substr(0,pnt); } + destPath=getDestPath(imgUrl); + if(destPath==null){ + continue; + } saveImage(); } }else{ @@ -252,6 +303,7 @@ commands.addUserCommand( xhrImgInfo.setRequestHeader('Referer',contents.URL); xhrImgInfo.setRequestHeader('Cookie',cookie); xhrImgInfo.send(null); - - } + }, + {}, + true ); diff --git a/pluginManager.js b/pluginManager.js index 629a4e7..f191b9a 100644 --- a/pluginManager.js +++ b/pluginManager.js @@ -303,7 +303,7 @@ Plugin.prototype = { // {{{ return template.table(this.name, data); }, // }}} getResourceInfo: function(){ - var store = storage.newMap('plugins-pluginManager', true); + var store = storage.newMap('plugins-pluginManager', {store: true}); var url = this.info.updateURL; var localResource = store.get(this.name) || {}; var serverResource = { @@ -325,10 +325,10 @@ Plugin.prototype = { // {{{ } }); } catch (e){} - let m = /\bPLUGIN_INFO[ \t\r\n]*=[ \t\r\n]*<VimperatorPlugin(?:[ \t\r\n][^>]*)?>([\s\S]+?)<\/VimperatorPlugin[ \t\r\n]*>/(source); + let m = /\bPLUGIN_INFO[ \t\r\n]*=[ \t\r\n]*<VimperatorPlugin(?:[ \t\r\n][^>]*)?>([\s\S]+?)<\/VimperatorPlugin[ \t\r\n]*>/.exec(source); if (m){ m = m[1].replace(/(?:<!(?:\[CDATA\[(?:[^\]]|\](?!\]>))*\]\]|--(?:[^-]|-(?!-))*--)>)+/g, ''); - m = /^[\w\W]*?<version(?:[ \t\r\n][^>]*)?>([^<]+)<\/version[ \t\r\n]*>/(m); + m = /^[\w\W]*?<version(?:[ \t\r\n][^>]*)?>([^<]+)<\/version[ \t\r\n]*>/.exec(m); if (m){ version = m[1]; } diff --git a/property-panel.js b/property-panel.js new file mode 100644 index 0000000..8a92797 --- /dev/null +++ b/property-panel.js @@ -0,0 +1,127 @@ +/* NEW BSD LICENSE {{{ +Copyright (c) 2011, anekos. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. + + +################################################################################### +# http://sourceforge.jp/projects/opensource/wiki/licenses%2Fnew_BSD_license # +# に参考になる日本語訳がありますが、有効なのは上記英文となります。 # +################################################################################### + +}}} */ + +// INFO {{{ +let INFO = +<> + <plugin name="PropertyPanel" version="1.0.0" + href="http://vimpr.github.com/" + summary="Show a object in Property Panel." + lang="en-US" + xmlns="http://vimperator.org/namespaces/liberator"> + <author email="anekos@snca.net">anekos</author> + <license>New BSD License</license> + <project name="Vimperator" minVersion="3.0"/> + <p></p> + <item> + <tags>:pp</tags> + <tags>:properypanel</tags> + <spec>:pp <a>JavaScriptExpression.</a></spec> + <description><p> + Eval <a>JavaScriptExpression</a> and show the result in Property Panel. + </p></description> + </item> + </plugin> +</>; +// }}} + + +(function () { + + let M = {}; + Cu.import('resource:///modules/PropertyPanel.jsm', M); + Cu.import('resource://gre/modules/Services.jsm', M); + + let API = { + last: {panel: null, width: 400, height: 400}, + + show: function ({object, title, recycle, panel}) { + function make () { + let doc = M.Services.wm.getMostRecentWindow("navigator:browser").document; + let popupSet = doc.getElementById('mainPopupSet'); + let output = {}; + + let pp = new M.PropertyPanel(popupSet, doc, title, output, null); + + pp.panel.setAttribute("class", "scratchpad_propertyPanel"); + + + pp.panel.setAttribute('noautofocus', 'false'); + pp.panel.setAttribute('noautohide', 'false'); + + pp.panel.openPopup(null, 'overlap', 0, 0, false, false); + + pp.panel.sizeTo(API.last.width, API.last.height); + + pp.panel.addEventListener( + 'popuphiding', + function () { + API.last.height = pp.panel.height; + API.last.width = pp.panel.width; + }, + true + ); + + return pp; + } + + let pp = panel || make(); + pp.treeView.data = object; + API.last = pp; + + return pp; + } + }; + + commands.addUserCommand( + ['properypanel', 'pp'], + 'Show property panel', + function (args) { + let expr = args.literalArg; + let result = liberator.eval(expr); + API.show({object: result, title: expr, panel: args.bang && API.last}); + }, + { + literal: 0, + //bang: true, + completer: function (context, args) { + completion.javascript(context, args); + } + }, + true + ); + +})(); + +// vim:sw=2 ts=2 et si fdm=marker: @@ -0,0 +1,81 @@ +var PLUGIN_INFO = +<VimperatorPlugin> +<name>pypi</name> +<description>Add a pypi command</description> +<author mail="gael@gawel.org" homepage="http://www.gawel.org">gawel</author> +<version>1.1</version> +<license>MPL 1.1/GPL 2.0/LGPL 2.1</license> +<minVersion>2.0pre</minVersion> +<maxVersion>2.0</maxVersion> +<updateURL>https://github.com/vimpr/vimperator-plugins/raw/master/pypi.js</updateURL> +<detail lang="en"><![CDATA[ + +== Command == + +:pypi {package or term} + +]]></detail> +</VimperatorPlugin>; + +liberator.plugins.pypi = (function(){ + +var Pypi = { + packages: [], + init_packages: function() { + var req = new XMLHttpRequest(); + req.onreadystatechange = function() { + if (req.readyState == 4) { + Pypi.packages = []; + var lines = req.responseText.split('\n'); + for (var i=0; i<lines.length; i++) { + var line = lines[i]; + if (/^<a/.exec(line)) + Pypi.packages.push(line.split('>')[1].split('<')[0]); + } + liberator.echo('Pypi packages list is up to date'); + } + } + req.open("GET", "http://pypi.python.org/simple/", false); + req.send(null); + setTimeout(Pypi.init_packages, 1000*60*60*24); + } +} + +setTimeout(Pypi.init_packages, 1000); + +commands.addUserCommand(["pypi"], "pypi search", + function(args){ + var doc = window.content.document; + if (!args.length) { + doc.location.href = 'http://pypi.python.org/pypi'; + } + var filter = args[0]; + var packages = plugins.pypi.packages; + for (var i=0; i<packages.length; i++) { + if (filter.toLowerCase() == packages[i].toLowerCase()) { + doc.location.href = 'http://pypi.python.org/pypi/'+packages[i]; + return; + } + } + doc.location.href = 'http://pypi.python.org/pypi?%3Aaction=search&submit=search&term='+filter; + }, { + completer: function(context, args){ + if (context.filter.length < 1) return; + if (!plugins.pypi.packages.length) { + plugins.pypi.init_packages(); + } + var packages = plugins.pypi.packages; + var results = []; + for (var i=0; i<packages.length; i++) { + if (new RegExp('^'+context.filter.replace('.', '\\.').toLowerCase()).exec(packages[i].toLowerCase())) { + results.push([packages[i], '']); + } + } + return {items:results, start:0}; + } + }, true); + +return Pypi; +})(); + +// vim: sw=4 ts=4 et fdm=marker: diff --git a/readitlater.js b/readitlater.js index 49a5636..8d87fb7 100644 --- a/readitlater.js +++ b/readitlater.js @@ -9,9 +9,9 @@ let PLUGIN_INFO = <VimperatorPlugin> <name>readitlater</name> <description lang="ja">Read it Later を快適に使うためのプラグインです</description> - <version>0.2.1</version> + <version>0.3.0</version> <minVersion>3.0</minVersion> - <maxVersion>3.2</maxVersion> + <maxVersion>3.3</maxVersion> <author mail="ninja.tottori@gmail.com" homepage="http://twitter.com/ninja_tottori">ninja.tottori</author> <updateURL>https://github.com/vimpr/vimperator-plugins/raw/master/readitlater.js</updateURL> <detail lang="ja"><![CDATA[ @@ -36,6 +36,8 @@ let PLUGIN_INFO = :ril add 今見ているページのurlとtitleを登録します オプションとして url , title が選べるので適当に編集して登録もできます。 + また、URL の補完も効きます。 + URL補完は let g:readitlater_complete = "slf" のようにして使用する補完を選択できます。 :ril get 登録されてるページの情報を取得してキャッシュしときます。 @@ -77,6 +79,7 @@ let PLUGIN_INFO = function (args) { addItemByArgs(args); },{ + literal: 0, options : [ [["url","u"],commands.OPTION_STRING,null, (function(){ @@ -90,6 +93,7 @@ let PLUGIN_INFO = }) ], ], + completer: function (context, args) completion.url(context, liberator.globalVariables.readitlater_complete) } ), @@ -125,7 +129,7 @@ let PLUGIN_INFO = } ), - new Command(["stats"], "Retrieve information about a user's list", + new Command(["stats","s"], "Retrieve information about a user's list", function (args) { ReadItLater.stats(); },{} @@ -211,11 +215,11 @@ let PLUGIN_INFO = ); - req.addEventListener("onSuccess",function(data){ + req.addEventListener("success",function(data){ e(data.responseText) }); - req.addEventListener("onFailure",function(data){ + req.addEventListener("failure",function(data){ liberator.echoerr(data.statusText); liberator.echoerr(data.responseText); }); @@ -238,8 +242,8 @@ let PLUGIN_INFO = postBody:getParameterMap( { apikey : this.api_key, - username : encodeURIComponent(logins[0].username), - password : encodeURIComponent(logins[0].password), + username : logins[0].username, + password : logins[0].password, format : "json", count : (liberator.globalVariables.readitlater_get_count? liberator.globalVariables.readitlater_get_count : 50 ), //state : (args["read"]) ? "read" : "unread", @@ -251,8 +255,8 @@ let PLUGIN_INFO = ); - req.addEventListener("onSuccess",function(data) callback(libly.$U.evalJson(data.responseText))); - req.addEventListener("onFailure",function(data){ + req.addEventListener("success",function(data) callback(libly.$U.evalJson(data.responseText))); + req.addEventListener("failure",function(data){ liberator.echoerr(data.statusText); liberator.echoerr(data.responseText); }); @@ -273,19 +277,18 @@ let PLUGIN_INFO = postBody:getParameterMap( { apikey : this.api_key, - username : encodeURIComponent(logins[0].username), - password : encodeURIComponent(logins[0].password), - url : encodeURIComponent(url), - title : encodeURIComponent(title), + username : logins[0].username, + password : logins[0].password, + url : url, + title : title, } ) } ); + req.addEventListener("success",callback); - req.addEventListener("onSuccess",callback); - - req.addEventListener("onFailure",function(data){ + req.addEventListener("failure",function(data){ liberator.echoerr(data.statusText); liberator.echoerr(data.responseText); }); @@ -303,7 +306,7 @@ let PLUGIN_INFO = function make_read_list(args){ let o = {}; for (let i = 0; i < args.length; i++) { - o[i] = {"url":encodeURIComponent(args[i])}; + o[i] = {"url":args[i]}; }; return JSON.stringify(o); } @@ -316,8 +319,8 @@ let PLUGIN_INFO = postBody:getParameterMap( { apikey : this.api_key, - username : encodeURIComponent(logins[0].username), - password : encodeURIComponent(logins[0].password), + username : logins[0].username, + password : logins[0].password, read : make_read_list(urls), } ) @@ -325,14 +328,13 @@ let PLUGIN_INFO = ); var ref = this; - req.addEventListener("onSuccess",callback); + req.addEventListener("success",callback); - req.addEventListener("onFailure",function(data){ + req.addEventListener("failure",function(data){ liberator.echoerr(data.statusText); liberator.echoerr(data.responseText); }); - liberator.log(urls) req.post(); @@ -350,8 +352,8 @@ let PLUGIN_INFO = postBody:getParameterMap( { apikey : this.api_key, - username : encodeURIComponent(logins[0].username), - password : encodeURIComponent(logins[0].password), + username : logins[0].username, + password : logins[0].password, format : "json", } ) @@ -359,7 +361,7 @@ let PLUGIN_INFO = ); - req.addEventListener("onSuccess",function(data){ + req.addEventListener("success",function(data){ let res = libly.$U.evalJson(data.responseText); liberator.echo( <style type="text/css"><![CDATA[ @@ -375,7 +377,7 @@ let PLUGIN_INFO = ); }); - req.addEventListener("onFailure",function(data){ + req.addEventListener("failure",function(data){ liberator.echoerr(data.statusText); liberator.echoerr(data.responseText); }); @@ -400,7 +402,7 @@ let PLUGIN_INFO = ); - req.addEventListener("onSuccess",function(data){ + req.addEventListener("success",function(data){ liberator.echo( <div> X-Limit-User-Limit : {data.transport.getResponseHeader("X-Limit-User-Limit")} <br /> @@ -414,7 +416,7 @@ let PLUGIN_INFO = ); }); - req.addEventListener("onFailure",function(data){ + req.addEventListener("failure",function(data){ liberator.echoerr(data.statusText); liberator.echoerr(data.responseText); }); @@ -443,10 +445,12 @@ let PLUGIN_INFO = } // }}} function addItemByArgs(args){ // {{{ - let url = args["url"] || buffer.URL; - let title = args["title"] || buffer.title; - ReadItLater.add(url, title,function(){ - echo("Added: " + title) + let url = args["url"] || args.literalArg; + let title = args["title"] || (url ? undefined : buffer.title); + if (!url) + url = buffer.URL; + ReadItLater.add(url, title, function(){ + echo("Added: " + (title || url)); ListCache.update(true); }); } // }}} @@ -494,12 +498,11 @@ let PLUGIN_INFO = } // }}} function getParameterMap(parameters){ // {{{ - let map = ""; - for (let key in parameters){ - if (map) map += "&"; - map += key + "=" + parameters[key]; - } - return map + return [ + key + "=" + encodeURIComponent(value) + for ([key, value] in Iterator(parameters)) + if (value) + ].join("&"); } // }}} function countObjectValues(obj){ // {{{ @@ -519,3 +522,4 @@ let PLUGIN_INFO = })(); +// vim: set noet : @@ -0,0 +1,147 @@ +/* NEW BSD LICENSE {{{ +Copyright (c) 2011, anekos. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. + + +################################################################################### +# http://sourceforge.jp/projects/opensource/wiki/licenses%2Fnew_BSD_license # +# に参考になる日本語訳がありますが、有効なのは上記英文となります。 # +################################################################################### + +}}} */ + +// INFO {{{ +let INFO = +<> + <plugin name="refe" version="1.0.0" + href="http://vimpr.github.com/" + summary="refe (Ruby reference) for vimperator" + lang="en-US" + xmlns="http://vimperator.org/namespaces/liberator"> + <author email="anekos@snca.net">anekos</author> + <license>New BSD License</license> + <project name="Vimperator" minVersion="3.0"/> + <require type="plugin">_libly.js</require> + <item> + <tags>:refe</tags> + <spec>:refe</spec> + <description><p>Seach with completer and open reference page in a current tab.</p></description> + </item> + <item> + <tags>:trefe</tags> + <spec>:trefe</spec> + <description><p>Seach with completer and open reference page in a new tab.</p></description> + </item> + </plugin> + <plugin name="refe" version="1.0.0" + href="http://vimpr.github.com/" + summary="refe (Ruby リファレンス) for Vimperator" + lang="ja" + xmlns="http://vimperator.org/namespaces/liberator"> + <author email="anekos@snca.net">anekos</author> + <license>New BSD License</license> + <project name="Vimperator" minVersion="3.0"/> + <require type="plugin">_libly.js</require> + <item> + <tags>:refe</tags> + <spec>:refe</spec> + <description><p>補完で検索し、リファレンスのページを現在のタブに開きます。</p></description> + </item> + <item> + <tags>:trefe</tags> + <spec>:trefe</spec> + <description><p>補完で検索し、リファレンスのページを新しいタブに開きます。</p></description> + </item> + </plugin> +</>; +// }}} + +(function () { + + let L = plugins.libly; + + function getList (word, callback) { + let req = new L.Request('http://gimite.net/refe-web/main/search?query=' + encodeURIComponent(word)); + req.addEventListener( + 'success', + function (res) { + callback([ + [node.getAttribute('href'), node.textContent.trim()] + for ([, node] in Iterator(Array.slice(L.$U.createHTMLDocument(res.responseText).querySelectorAll('a')))) + ]); + } + ); + req.addEventListener( + 'failure', + function (res) { + liberator.echoerr('!API Error'); + } + ); + req.get(); + } + + function addCommand (prefix, where) { + commands.addUserCommand( + [prefix + 'refe'], + 'refe - ruby reference', + function (args) { + liberator.open(args.literalArg, where); + }, + { + literal: 0, + completer: let (getter) function (context, args) { + function setCompletions (context, word) { + getList( + word, + function (list) { + context.incomplete = false; + context.completions = list; + } + ); + } + + let word = args.literalArg; + + if (word.length <= 2) + return; + + context.incomplete = true; + context.filters = [CompletionContext.Filter.textDescription]; + + if (getter) + clearTimeout(getter); + getter = setTimeout(setCompletions.bind(null, context, word), 1000); + } + }, + true + ); + } + + addCommand('', liberator.CURRENT_TAB); + addCommand('t', liberator.NEW_TAB); + +})(); + +// vim:sw=2 ts=2 et si fdm=marker: diff --git a/sbmcommentsviewer.js b/sbmcommentsviewer.js index adb64f4..650950d 100644 --- a/sbmcommentsviewer.js +++ b/sbmcommentsviewer.js @@ -3,9 +3,8 @@ var PLUGIN_INFO = <name>SBM Comments Viewer</name> <description>List show Social Bookmark Comments</description> <description lang="ja">ソーシャル・ブックマーク・コメントを表示します</description> - <version>0.2.4</version> + <version>0.2.5</version> <minVersion>2.0pre</minVersion> - <maxVersion>3.0</maxVersion> <updateURL>https://github.com/vimpr/vimperator-plugins/raw/master/sbmcommentsviewer.js</updateURL> <detail><![CDATA[ == Usage == @@ -203,9 +202,9 @@ function openSBM(url, type, format, countOnly, openToBrowser){ if (openToBrowser) manager.open(sbmContainer.toHTML(format,false)); else - liberator.echo(sbmContainer.toHTML(format,countOnly), true); + liberator.echo(sbmContainer.toHTML(format,countOnly)); } else { - liberator.echoerr(sbmURL + ' ' + xhr.status, true); + liberator.echoerr(sbmURL + ' ' + xhr.status); } } }; @@ -247,7 +246,7 @@ var SBM = { //{{{ parser: function(xhr){ var rss = xhr.responseXML; if (!rss){ - liberator.echoerr('Delicious feed is none',true); + liberator.echoerr('Delicious feed is none'); return; } var pageURL, items; @@ -525,7 +524,7 @@ commands.addUserCommand(['viewSBMComments'], 'SBM Comments Viewer', //{{{ // TODO manager.open(cacheManager.get(url,type).toHTML(format,false), liberator.forceNewTab); else - liberator.echo(cacheManager.get(url, type).toHTML(format,countOnly), true); + liberator.echo(cacheManager.get(url, type).toHTML(format,countOnly)); } else { try { openSBM(url, type, format, countOnly, openToBrowser); diff --git a/scenario-actor.js b/scenario-actor.js index 10735ec..727edb0 100644 --- a/scenario-actor.js +++ b/scenario-actor.js @@ -93,7 +93,7 @@ var $U = libly.$U; var logger = $U.getLogger('scenario-actor'); function ScenarioActor () { //{{{ - let variables = storage.newMap('scenarioactor', true); + let variables = storage.newMap('scenarioactor', {store: true}); function ScenarioContext(event) { //{{{ let triggeredEvent = event; diff --git a/session-manager.js b/session-manager.js index 0e377ba..117aae3 100644 --- a/session-manager.js +++ b/session-manager.js @@ -57,7 +57,7 @@ let PLUGIN_INFO = let INFO = <> <plugin name="session-manager" version="1.3.4" - href="http://svn.coderepos.org/share/lang/javascript/vimperator-plugins/trunk/session-manager" + href="http://github.com/vimpr/vimperator-plugins/blob/master/session-manager" summary="for Session Manager Addon" lang="en-US" xmlns="http://vimperator.org/namespaces/liberator"> @@ -77,7 +77,7 @@ let INFO = </item> </plugin> <plugin name="session-manager" version="1.3.4" - href="http://svn.coderepos.org/share/lang/javascript/vimperator-plugins/trunk/session-manager" + href="http://github.com/vimpr/vimperator-plugins/blob/master/session-manager" summary="for Session Manager Addon" lang="ja" xmlns="http://vimperator.org/namespaces/liberator"> @@ -220,7 +220,7 @@ let INFO = context.completions = [ [file.leafName.replace(/\.session$/, ''), new Date(file.lastModifiedTime)] for ([,file] in Iterator(io.File(gSessionManager.getSessionDir()).readDirectory())) - if (!ignoreAuto || args['-auto'] || !/^(backup(-\d+)?|autosave)\.session$/(file.leafName)) + if (!ignoreAuto || args['-auto'] || !/^(backup(-\d+)?|autosave)\.session$/.test(file.leafName)) ].sort(function ([, a], [, b]) (b - a)); } }, @@ -1,6 +1,6 @@ // INFO // var INFO = -<plugin name="simg.js" version="0.2" +<plugin name="simg.js" version="0.3" summary="Save image on contents area" href="http://github.com/vimpr/vimperator-plugins/blob/master/simg.js" xmlns="http://vimperator.org/namespaces/liberator"> @@ -45,19 +45,29 @@ commands.addUserCommand( return path; }; - let savePath=directoryPicker(); - if(savePath.length<1) return; + let saveDirectory=directoryPicker(); + if(saveDirectory.length<1) return; let imgURL=contents.URL; + let savePath; let trueCurrntImg=function(){ let fileName=imgURL.substr(imgURL.lastIndexOf('/')); if (-1!=fileName.indexOf('?')){ fileName=fileName.substr(0,fileName.indexOf('?')); } - savePath=savePath+fileName; + savePath=saveDirectory+fileName; let instream=xhrImg.responseText; let aFile=Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile); aFile.initWithPath(savePath); + if(true===aFile.exists()){ + let value=window.prompt('すでに同じ名前のファイルがあります。デフォルトファイル名を変更してください。',fileName.substr(1)); + if(null===value){ + return false; + } + fileName='/'+value; + savePath=saveDirectory+fileName; + aFile.initWithPath(savePath); + } let outstream=Cc["@mozilla.org/network/safe-file-output-stream;1"] .createInstance(Ci.nsIFileOutputStream); outstream.init(aFile,0x02|0x08|0x20,0664,0); @@ -255,22 +255,6 @@ SL.prototype = { // {{{ let luckyStar = (function(){ -function extend(class, obj){ - var flag; - for (let i in obj){ - flag = false; - if (obj.__lookupGetter__(i)){ - class.prototype.__defineGetter__(i, obj.__lookupGetter__(i)); - flag = true; - } - if (obj.__lookupSetter__(i)){ - class..prototype.__defineSetter__(i, obj.__lookupSetter__(i)); - flag = true; - } - if (!flag) class.prototype[i] = obj[i]; - } -} - let colors = [ ["rgba(255,215,0,alpha)", "rgba(255,255,0,alpha)"], //gold, yellow ["rgba(255,20,147,alpha)","rgba(255,0,255, alpha)"], // deeppink, magenta diff --git a/slideshare.js b/slideshare.js new file mode 100644 index 0000000..9ebc472 --- /dev/null +++ b/slideshare.js @@ -0,0 +1,227 @@ +/* NEW BSD LICENSE {{{ +Copyright (c) 2011, anekos. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. + + +################################################################################### +# http://sourceforge.jp/projects/opensource/wiki/licenses%2Fnew_BSD_license # +# に参考になる日本語訳がありますが、有効なのは上記英文となります。 # +################################################################################### + +}}} */ + +// INFO {{{ +let INFO = +<> + <plugin name="Slideshare" version="1.1.1" + href="http://vimpr.github.com/" + summary="Controll slideshare's slide." + lang="en-US" + xmlns="http://vimperator.org/namespaces/liberator"> + <author email="anekos@snca.net">anekos</author> + <license>New BSD License</license> + <project name="Vimperator" minVersion="3.0"/> + <p></p> + <item> + <tags>:slideshare-next</tags> + <spec>:Slideshare next</spec> + <description><p>Go next page.</p></description> + </item> + <item> + <tags>:slideshare-prev</tags> + <spec>:Slideshare prev</spec> + <description><p>Go previous page.</p></description> + </item> + <item> + <tags>:slideshare-fullscreen</tags> + <spec>:Slideshare fullscreen</spec> + <description><p>Toggle fullscreen.</p></description> + </item> + </plugin> + <plugin name="Slideshare" version="1.0.0" + href="http://vimpr.github.com/" + summary="Slideshare のスライドを操作する" + lang="ja" + xmlns="http://vimperator.org/namespaces/liberator"> + <author email="anekos@snca.net">anekos</author> + <license>New BSD License</license> + <project name="Vimperator" minVersion="3.0"/> + <p></p> + <item> + <tags>:slideshare-next</tags> + <spec>:Slideshare next</spec> + <description><p>次のページに移動</p></description> + </item> + <item> + <tags>:slideshare-prev</tags> + <spec>:Slideshare prev</spec> + <description><p>前のページに移動</p></description> + </item> + <item> + <tags>:slideshare-fullscreen</tags> + <spec>:Slideshare fullscreen</spec> + <description><p>フルスクリーン切り換え</p></description> + </item> + </plugin> +</>; +// }}} + + +(function () { + + function makeStyleToggler (myStyle, e) { + let originalStyle = e.getAttribute('style') || ''; + return function () { + if (e.__anekos_style_override) { + e.setAttribute('style', originalStyle); + delete e.__anekos_style_override; + } else { + e.setAttribute('style', originalStyle + myStyle.toString()); + e.__anekos_style_override = true; + } + } + } + + function makeFullscreenToggler (doc, main) { + const hiddenStyle = 'display: none !important'; + + let styleTogglers = Array.slice(doc.querySelectorAll('object')).map(makeStyleToggler.bind(null, hiddenStyle)); + + return function (callback) { + main(); + styleTogglers.forEach(function (f) f()); + setTimeout(function () { + if (callback) + callback(); + }, 1000); + }; + } + + function HTML5Slideshare (doc, callback) { + let win = doc.defaultView; + let player = win.player; + + let toggleFullscreen = + makeFullscreenToggler( + doc, + let (isFullscreen = false) + function () { + doc.querySelector(isFullscreen ? '.btnLeaveFullScreen' : '.btnFullScreen').click(); + isFullscreen ^= true; + } + ); + + toggleFullscreen( + function () { + callback({ + next: function () { + player.play(this.current + 1); + }, + + previous: function () { + if (this.current > 1) + player.play(this.current - 1); + }, + + toggleFullscreen: function () { + toggleFullscreen(); + }, + + get current () player.controller.currentPosition + }); + } + ); + } + + function FlashSlideshare (doc, callback) { + let player = doc.querySelector('#player'); + + const fullScreenStyle = <><![CDATA[ + position : fixed !important; + top : 0px !important; + left : 0px !important; + z-index : 1000 !important; + width : 100% !important; + height : 100% !important; + ]]></>; + + let toggleFullscreen = makeFullscreenToggler(doc, makeStyleToggler(fullScreenStyle, player)); + + callback({ + next: function () { + this.player.next(); + }, + + previous: function () { + this.player.previous(); + }, + + toggleFullscreen: function () { + toggleFullscreen(); + }, + + get player () doc.querySelector('#player'), + + get current () player.controller.currentPosition + }); + } + + function Slideshare (callback) { + const PN = '__anekos_slidehare'; + + if (content.document.location.host !== 'www.slideshare.net') + return liberator.echoerr('This is not slideshare!!?'); + + let doc = content.document; + let docw = doc.wrappedJSObject; + + if (doc[PN]) + return callback.call(doc[PN]); + + let func = docw.defaultView.player ? HTML5Slideshare : FlashSlideshare; + func(docw, function (instance) { + doc[PN] = instance; + callback.call(instance); + }); + } + + commands.addUserCommand( + ['slideshare'], + 'Slideshare controller', + function () { + }, + { + subCommands: [ + new Command(['n[ext]'], 'Go next page', function () Slideshare(function () this.next())), + new Command(['p[rev]'], 'Go previous page', function () Slideshare(function () this.previous())), + new Command(['f[ullscreen]'], 'Toggle fullscrenn', function () Slideshare(function () this.toggleFullscreen())), + ] + }, + true + ); + +})(); + +// vim:sw=2 ts=2 et si fdm=marker: diff --git a/spatial-navigation.js b/spatial-navigation.js index b68fdcd..74c15d1 100644 --- a/spatial-navigation.js +++ b/spatial-navigation.js @@ -60,7 +60,7 @@ let PLUGIN_INFO = let INFO = <> <plugin name="SpatialNavigation" version="1.0.0" - href="http://svn.coderepos.org/share/lang/javascript/vimperator-plugins/trunk/spatial-navigation.js" + href="http://github.com/vimpr/vimperator-plugins/blob/master/spatial-navigation.js" summary="Spatial navigation" lang="en-US" xmlns="http://vimperator.org/namespaces/liberator"> @@ -78,7 +78,7 @@ let g:spatial_navigation_mappings="<A-h> <A-j> <A-k> <A-l>" </item> </plugin> <plugin name="SpatialNavigation" version="1.0.0" - href="http://svn.coderepos.org/share/lang/javascript/vimperator-plugins/trunk/spatial-navigation.js" + href="http://github.com/vimpr/vimperator-plugins/blob/master/spatial-navigation.js" summary="空間ナビゲーション" lang="ja" xmlns="http://vimperator.org/namespaces/liberator"> diff --git a/statstat.js b/statstat.js index 170b304..6263aa3 100644 --- a/statstat.js +++ b/statstat.js @@ -57,7 +57,7 @@ let PLUGIN_INFO = // INFO {{{ let INFO = <plugin name="Stat Stat" version="1.0.4" - href="http://svn.coderepos.org/share/lang/javascript/vimperator-plugins/trunk/statstat.js" + href="http://github.com/vimpr/vimperator-plugins/blob/master/statstat.js" summary="Show information on statusline." xmlns="http://vimperator.org/namespaces/liberator"> <author email="anekos@snca.net">anekos</author> @@ -39,12 +39,11 @@ let PLUGIN_INFO = <name lang="ja">すてら</name> <description>For Niconico/YouTube/Vimeo, Add control commands and information display(on status line).</description> <description lang="ja">ニコニコ動画/YouTube/Vimeo 用。操作コマンドと情報表示(ステータスライン上に)追加します。</description> - <version>0.32.7</version> + <version>0.33.0</version> <author mail="anekos@snca.net" homepage="http://d.hatena.ne.jp/nokturnalmortum/">anekos</author> <license>new BSD License (Please read the source code comments of this plugin)</license> <license lang="ja">修正BSDライセンス (ソースコードのコメントを参照してください)</license> <minVersion>2.0</minVersion> - <maxVersion>3.0</maxVersion> <updateURL>https://github.com/vimpr/vimperator-plugins/raw/master/stella.js</updateURL> <detail><![CDATA[ == Commands == @@ -327,7 +326,7 @@ Thanks: file.appendRelativePath(U.fixFilename(title) + ext); if (file.exists()) - return U.echoerr('The file already exists! -> ' + file.path); + return U.echoError('The file already exists! -> ' + file.path); file = makeFileURI(file); @@ -381,11 +380,11 @@ Thanks: // 上手い具合に秒数に直すよ fromTimeCode: function (code, max) { var m; - if (max && (m = /^(-?\d+(?:\.\d)?)%/(code))) + if (max && (m = /^(-?\d+(?:\.\d)?)%/.exec(code))) return Math.round(max * (parseFloat(m[1]) / 100)); - if (m = /^(([-+]?)\d+):(\d+)$/(code)) + if (m = /^(([-+]?)\d+):(\d+)$/.exec(code)) return parseInt(m[1], 10) * 60 + (m[2] == '-' ? -1 : 1) * parseInt(m[3], 10); - if (m = /^([-+]?\d+\.\d+)$/(code)) + if (m = /^([-+]?\d+\.\d+)$/.exec(code)) return Math.round(parseFloat(m[1]) * 60); return parseInt(code, 10); }, @@ -827,6 +826,71 @@ Thanks: // }}} /********************************************************************************* + * VideoPlayer {{{ + *********************************************************************************/ + + function VideoPlayer () { + Player.apply(this, arguments); + } + + VideoPlayer.prototype = { + __proto__: Player.prototype, + + functions: { + currentTime: 'rw', + fetch: '', + fileURL: '', + makeURL: '', + muted: 'rw', + pageinfo: '', + pause: 'x', + play: 'x', + playEx: 'x', + playOrPause: 'x', + relations: '', + title: '', + totalTime: 'r', + volume: 'rw', + quality: '', + qualities: '' + }, + + icon: 'TODO', + + get currentTime () parseInt(this.player.currentTime), + set currentTime (value) (this.player.currentTime = value), + + get muted () this.player.muted, + set muted (value) (this.player.muted = value), + + get player () + content.document.querySelector('video'), + + get ready () !!this.player, + + get state () { + if (this.player.ended) + return Player.ST_ENDED; + if (this.player.paused) + return Player.ST_PAUSED; + return Player.ST_PLAYING; + }, + + get totalTime () parseInt(this.player.duration), + + get isValid () false, + + get volume () parseInt(this.player.volume * 100), + set volume (value) (this.player.volume = value / 100), + + play: function () this.player.play(), + + pause: function () this.player.pause() + }; + + // }}} + + /********************************************************************************* * YouTubePlayer {{{ *********************************************************************************/ @@ -835,7 +899,7 @@ Thanks: } YouTubePlayer.getIDfromURL = function (url) let ([_, r] = url.match(/[?;&]v=([-\w]+)/)) r; - YouTubePlayer.isVideoURL = function (url) /^https?:\/\/(www\.)?youtube\.com\/watch\?.+/(url); + YouTubePlayer.isVideoURL = function (url) /^https?:\/\/(www\.)?youtube\.com\/watch\?.+/.test(url); YouTubePlayer.prototype = { __proto__: Player.prototype, @@ -923,11 +987,12 @@ Thanks: let url = item.querySelector('a').href; if (!YouTubePlayer.isVideoURL(url)) continue; + let id = YouTubePlayer.getIDfromURL(url); result.push( new RelatedID( - YouTubePlayer.getIDfromURL(url), + id, item.querySelector('span.title').textContent, - item.querySelector('img').src + 'http://img.youtube.com/vi/' + id + '/1.jpg' ) ); } @@ -958,7 +1023,7 @@ Thanks: get totalTime () parseInt(this.player.getDuration()), - get isValid () U.currentURL.match(/^http:\/\/(?:[^.]+\.)?youtube\.com\/watch/), + get isValid () (this.player && U.currentURL.match(/^http:\/\/(?:[^.]+\.)?youtube\.com\/watch/)), get volume () parseInt(this.player.getVolume()), set volume (value) (this.player.setVolume(value), this.volume), @@ -967,8 +1032,12 @@ Thanks: // all(1080p,720p,480p,360p) -> 37, 22, 35, 34, 5 // FIXME 一番初めが最高画質だと期待 let cargs = content.wrappedJSObject.yt.config_.PLAYER_CONFIG.args; - let url = decodeURIComponent(cargs.fmt_url_map.split(',')[0].split('|')[1]); - U.download(url, filepath, '.flv', this.title); + cargs.url_encoded_fmt_stream_map.split(',')[0].split('&').forEach(function(x) { + let [key, val] = x.split('='); + if (key == 'url') { + U.download(decodeURIComponent(val), filepath, '.flv', this.title); + } + }, this); }, makeURL: function (value, type) { @@ -991,6 +1060,141 @@ Thanks: // }}} /********************************************************************************* + * YouTubePlayer5 {{{ + *********************************************************************************/ + + function YouTubePlayer5 (stella) { + Player.apply(this, arguments); + this._player = new VideoPlayer(stella); + } + + YouTubePlayer5.prototype = { + __proto__: YouTubePlayer.prototype, + + functions: { + currentTime: 'rw', + fetch: 'x', + fileURL: 'r', + makeURL: 'x', + muted: 'rwt', + pageinfo: 'r', + pause: 'x', + play: 'x', + playEx: 'x', + playOrPause: 'x', + relations: 'r', + title: 'r', + totalTime: 'r', + volume: 'rw', + quality: 'rw', + qualities: 'r' + }, + + icon: 'http://www.youtube.com/favicon.ico#hoge', + + get fileExtension () '.mp4', + + get fileURL () + let (as = content.document.defaultView.wrappedJSObject.swfArgs) + ('http://www.youtube.com/get_video?fmt=22&video_id=' + as.video_id + '&t=' + as.t), + + get id () + YouTubePlayer.getIDfromURL(U.currentURL), + + get pageinfo () { + let doc = content.document; + let desc = doc.querySelector('#eow-description'); + return [ + [ + 'comment', + desc ? desc.textContent.trim() : '' + ], + [ + 'tags', + XMLList([ + <span>[<a href={v.href}>{v.textContent}</a>]</span> + for ([, v] in Iterator(doc.querySelectorAll('#eow-tags > li > a'))) + ].join('')) + ], + [ + 'quality', + this.quality + ] + ]; + }, + + get player () this._player, + + get quality () this.player.getPlaybackQuality(), + set quality (value) this.player.setPlaybackQuality(value), + + get qualities () this.player.getAvailableQualityLevels(), + + get ready () !!this.player, + + get relations () { + let result = []; + let doc = content.document; + for each (let item in Array.slice(doc.querySelectorAll('#watch-tags > div > a'))) { + result.push(new RelatedTag(item.textContent)); + } + for each (let item in Array.slice(doc.querySelectorAll('.video-list-item'))) { + let url = item.querySelector('a').href; + if (!YouTubePlayer.isVideoURL(url)) + continue; + let id = YouTubePlayer.getIDfromURL(url); + result.push( + new RelatedID( + id, + item.querySelector('span.title').textContent, + 'http://img.youtube.com/vi/' + id + '/1.jpg' + ) + ); + } + return result; + }, + + get title () + content.document.title.replace(/^YouTube - /, ''), + + fetch: function (filepath) { + // all(1080p,720p,480p,360p) -> 37, 22, 35, 34, 5 + // FIXME 一番初めが最高画質だと期待 + let cargs = content.wrappedJSObject.yt.config_.PLAYER_CONFIG.args; + cargs.url_encoded_fmt_stream_map.split(',')[0].split('&').forEach(function(x) { + let [key, val] = x.split('='); + if (key == 'url') { + U.download(decodeURIComponent(val), filepath, '.flv', this.title); + } + }, this); + }, + + makeURL: function (value, type) { + switch (type) { + case Player.URL_ID: + return 'http://www.youtube.com/watch?v=' + value; + case Player.URL_TAG: + return 'http://www.youtube.com/results?search=tag&search_query=' + encodeURIComponent(value); + case Player.URL_SEARCH: + return 'http://www.youtube.com/results?search_query=' + encodeURIComponent(value); + } + return value; + }, + }; + + 'play pause currentTime muted state totalTime volume'.split(/\s+/).forEach(function (name) { + let getter = VideoPlayer.prototype.__lookupGetter__(name); + let setter = VideoPlayer.prototype.__lookupSetter__(name); + let value = !(getter || setter); + if (getter || value) + YouTubePlayer5.prototype.__defineGetter__(name, function () this.player[name]); + if (setter || value) + YouTubePlayer5.prototype.__defineSetter__(name, function (value) (this.player[name] = value)); + }); + + // }}} + + /********************************************************************************* * YouTubeUserChannelPlayer {{{ *********************************************************************************/ @@ -999,7 +1203,7 @@ Thanks: } YouTubeUserChannelPlayer.getIDfromURL = function (url) let ([_, r] = url.match(/\/([^\/]+)($|[\?]+)/)) r; - YouTubeUserChannelPlayer.isVideoURL = function (url) /^https?:\/\/(www\.)?youtube\.com\/watch\?.+/(url); + YouTubeUserChannelPlayer.isVideoURL = function (url) /^https?:\/\/(www\.)?youtube\.com\/watch\?.+/.test(url); YouTubeUserChannelPlayer.prototype = { __proto__: YouTubePlayer.prototype, @@ -1657,6 +1861,7 @@ Thanks: this.players = { niconico: new NicoPlayer(this.stella), youtube: new YouTubePlayer(this.stella), + youtube5: new YouTubePlayer5(this.stella), youtubeuc: new YouTubeUserChannelPlayer(this.stella), vimeo: new VimeoPlayer(this.stella) }; @@ -1798,7 +2003,7 @@ Thanks: return U.raiseNotSupportedPage(); let arg = args.literalArg; - let index = (/^\d+:/)(arg) && parseInt(arg, 10); + let index = /^\d+:/.test(arg) && parseInt(arg, 10); if (index > 0) arg = lastCompletions[index - 1].command; let url = self.player.has('makeURL', 'x') ? makeRelationURL(self.player, arg) : arg; diff --git a/subscldr.js b/subscldr.js index 7d8bfad..cfdb2a0 100644 --- a/subscldr.js +++ b/subscldr.js @@ -128,7 +128,7 @@ liberator.plugins.subscldr = (function() { var uri = target || endpoint + buffer.URL; var req = new libly.Request(uri, null, {asynchronous: false}); - req.addEventListener("onSuccess", function(res) { + req.addEventListener("success", function(res) { liberator.log(res.responseText); res.getHTMLDocument(); if (isLoginForm(res.doc)) throw "Please login to LDR to subscribe the feed."; @@ -160,11 +160,11 @@ liberator.plugins.subscldr = (function() { postBody: postBody } ); - req.addEventListener("onSuccess", function(data) { + req.addEventListener("success", function(data) { liberator.log("Post status: " + data.responseText); liberator.echo("Subscribe feed succeed."); }); - req.addEventListener("onFailure", function(data) { + req.addEventListener("failure", function(data) { liberator.log("POST FAILURE: " + data.responseText); liberator.echoerr("POST FAILURE: " + data.statusText); }); diff --git a/tab-history.js b/tab-history.js new file mode 100644 index 0000000..e6d61c3 --- /dev/null +++ b/tab-history.js @@ -0,0 +1,188 @@ +var INFO = +<plugin name="TabHistory" + version="0.1" + summary="Go back/forward the tab selection history" + xmlns="http://vimperator.org/namespaces/liberator"> + <author email="teramako@gmail.com">teramako</author> + <license>MPL 1.1/GPL 2.0/LGPL 2.1</license> + <project name="Vimperator" minVerion="3.1"/> + <item> + <tags>:tabhistory :tabh</tags> + <spec>:tabh<oa>istory</oa></spec> + <description> + <p>Show the selection history</p> + </description> + </item> + <item> + <spec>:tabh<oa>istory</oa> back</spec> + <description> + <p>Go back the history</p> + </description> + </item> + <item> + <spec>:tabh<oa>istory</oa> forward</spec> + <description> + <p>Go forward the history</p> + </description> + </item> +</plugin>; + +var tabHistory = (function(){ + const gBrowser = window.gBrowser, + mTabContainer = gBrowser.mTabContainer, + DISCONNECTED = Element.prototype.DOCUMENT_POSITION_DISCONNECTED; + + var history = [mTabContainer.selectedItem], + index = 0, + maxItems = -1, + dontHandle = false; + + function isDisconnected (aTab) { + return !!(mTabContainer.compareDocumentPosition(aTab) & DISCONNECTED); + } + + function handler (aEvent) { + var tab = aEvent.target; + switch (aEvent.type) { + case "TabSelect": + onTabSelect(tab); + break + case "TabClose": + onTabClose(tab); + } + } + + function onTabSelect (aTab) { + if (dontHandle) + dontHandle = false; + else + addHistory(aTab); + } + + function onTabClose (aTab) { + let i; + while ((i = history.indexOf(aTab)) !== -1) { + history.splice(i, 1); + if (i <= index) + --index; + } + + for (let i = 1; i < history.length; ++i) { + let prevTab = history[i - 1], + currentTab = history[i]; + + if (prevTab === currentTab) { + history.splice(i--, 1); + if (i <= index) + --index; + } + } + } + + function addHistory (aTab) { + if (aTab === history[index]) + return; + + var i = index + 1; + + history.splice(i, history.length - i, aTab); + if (maxItems > 0 && history.length > maxItems) + history.splice(0, history.length - maxItems); + + index = history.length - 1; + } + + function select (aTab) { + if (aTab === mTabContainer.selectedItem) + return; + + dontHandle = true; + mTabContainer.selectedItem = aTab; + } + + var TH = { + get canGoBack() index > 0, + get canGoForward() index < history.length - 1, + goBack: function TH_goBack() { + if (!this.canGoBack) + return; + + var tab = history[--index]; + if (isDisconnected(tab)) { + history.splice(index, 1); + this.goBack(); + } else + select(tab); + }, + goForward: function TH_goForward() { + if (!this.canGoForward) + return; + + var tab = history[++index]; + if (isDisconnected(tab)) { + history.splice(index, 1); + this.goForward(); + } else + select(tab); + }, + clear: function TH_clear () { + history = [mTabContainer.selectedItem]; + index = 1; + }, + set maxItemCount (val) { + val = Number(val); + if (isNaN(val)) + throw TypeError("must be Number"); + + if (val > 0) + return maxItems = val; + else + return maxItems = -1; + }, + get maxItemCount () { + return maxItems; + } + }; + + mTabContainer.addEventListener("TabSelect", handler, false); + mTabContainer.addEventListener("TabClose", handler, false); + + commands.addUserCommand(["tabh[istory]"], "Tab Selection History", + function tabSelectionHistoryAction (args) { + var arg = args[0]; + switch (arg) { + case "back": + TH.goBack(); + break; + case "forward": + TH.goForward(); + break; + default: + let xml = template.table("TabHistory", [ + [i - index, tab.label] for ([i, tab] in Iterator(history)) + ]); + liberator.echo(xml); + } + }, { + completer: function TH_completer (context, args) { + var list = []; + if (TH.canGoBack) + list.push(["back", "Back to `" + history[index - 1].label + "'"]); + + if (TH.canGoForward) + list.push(["forward", "Forward to `" + history[index + 1].label + "'"]); + + context.title = ["TabSelectionHistory", "SubCmd"]; + context.completions = list; + }, + }, true); + + + __context__.onUnload = function onUnload () { + mTabContainer.removeEventListener("TabSelect", handler, false); + mTabContainer.removeEventListener("TabClose", handler, false); + }; + + return TH; +})(); + @@ -260,7 +260,7 @@ liberator.plugins.tada = (function(){ let result = [];
var req = new libly.Request(getURI(), null, {asynchronous: false});
- req.addEventListener('onSuccess', function(data) {
+ req.addEventListener('success', function(data) {
liberator.log("success");
data.getHTMLDocument("//div[@id='Container']/div[2]/div/div/ul/li/a").forEach(function(item){
var left = $LX("../span/strong[text()]", item);
@@ -291,7 +291,7 @@ liberator.plugins.tada = (function(){ let result = [];
var req = new libly.Request(getURI() + listId.toString(), null, {asynchronous: false});
- req.addEventListener('onSuccess', function(res) {
+ req.addEventListener('success', function(res) {
liberator.log("success");
res.getHTMLDocument("//ul[@id='incomplete_items']/li/form").forEach(function(item) {
result.push({
@@ -315,12 +315,12 @@ liberator.plugins.tada = (function(){ }
);
- req.addEventListener('onSuccess', function(data) {
+ req.addEventListener('success', function(data) {
liberator.echo("Posted[" + listName + "]:" + content);
liberator.plugins.posted = data;
});
- req.addEventListener('onFailure', function(data) {
+ req.addEventListener('failure', function(data) {
liberator.echoerr("POST FAILURE: " + content);
});
@@ -332,10 +332,10 @@ liberator.plugins.tada = (function(){ liberator.log("endpoint:" + endpoint);
var req = new libly.Request(endpoint, null, {postBody: "dummy=hoge"});
- req.addEventListener('onSuccess', function(data) {
+ req.addEventListener('success', function(data) {
liberator.echo("Send Ta-Da list '" + listName + "' to your email address.");
});
- req.addEventListener('onFailure', function(data) {
+ req.addEventListener('failure', function(data) {
liberator.echoerr("EMAIL SENDING ERROR.");
liberator.log(data.responseText);
});
@@ -354,10 +354,10 @@ liberator.plugins.tada = (function(){ })
}
);
- req.addEventListener('onSuccess', function(data) {
+ req.addEventListener('success', function(data) {
liberator.echo("Done: " + itemId);
});
- req.addEventListener('onFailure', function(data) {
+ req.addEventListener('failure', function(data) {
liberator.echoerr("Done item failed.");
liberator.log(data.responseText);
});
diff --git a/tinami.js b/tinami.js deleted file mode 100644 index 644a9f9..0000000 --- a/tinami.js +++ /dev/null @@ -1,177 +0,0 @@ -// INFO // -var INFO = -<plugin name="tinami.js" version="0.1" - summary="Download image from tinami" - href="http://github.com/vimpr/vimperator-plugins/blob/master/tinami.js" - xmlns="http://vimperator.org/namespaces/liberator"> - <author email="mitsugu.oyama@gmail.com">Mitsugu Oyama</author> - <license href="http://opensource.org/licenses/mit-license.php">MIT</license> - <project name="Vimperator" minVersion="2.3"/> - <p> - You can save image from TINAMI by this plugin. - </p> - <item> - <tags>'TINAMI'</tags> - <spec>:tinami</spec> - <description> - <p>You can save image from <link topic="http://www.tinami.com/">TINAMI</link> by this plugin.</p> - <p>You must login tinami, and must display image page.(ex. http://www.tinami.com/view/??????)</p> - <p>Not supported manga page and novel page.</p> - </description> - </item> -</plugin>; - -commands.addUserCommand( - ['tinami'], - 'Save Image File from tinami', - function(){ - let Cc=Components.classes; - let Ci=Components.interfaces; - - let contents=gBrowser.selectedBrowser.contentDocument; - if(contents.domain!="www.tinami.com"){ - liberator.echoerr('This page is not tinami.'); - return false; - } - if(contents.URL.search(/view/i)==-1){ - liberator.echoerr("This page is not tinami's image page."); - return false; - } - - let directoryPicker=function() { - let path; - let fp=Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker); - fp.init(window,'Select Directory',Ci.nsIFilePicker.modeGetFolder); - let result=fp.show(); - switch(result){ - case Ci.nsIFilePicker.returnOK: - path=fp.file.path; - break; - default: - case Ci.nsIFilePicker.returnCancel: - return ''; - } - return path; - }; - - const baseInfo="http://www.tinami.com/view/original/"; - let id=contents.URL.substr(contents.URL.lastIndexOf('/')+1); - let cookie=contents.cookie; - let xhrImgInfo; - let xhrImg; - let savePath=directoryPicker(); - if(savePath.length<1) return; - let imgUrl; - - let trueTinamiImg=function(){ - let fileName=imgUrl.substr(imgUrl.lastIndexOf('/')); - savePath=savePath+fileName; - let instream=xhrImg.responseText; - let aFile=Cc["@mozilla.org/file/local;1"] - .createInstance(Ci.nsILocalFile); - aFile.initWithPath(savePath); - let outstream=Cc["@mozilla.org/network/safe-file-output-stream;1"] - .createInstance(Ci.nsIFileOutputStream); - outstream.init(aFile,0x02|0x08|0x20,0664,0); - outstream.write(instream,instream.length); - if (outstream instanceof Ci.nsISafeOutputStream) { - outstream.finish(); - }else{ - outstream.close(); - } - }; - - let falseTinamiImg=function(){ - liberator.echoerr("Image file accept error."); - return false; - }; - - let saveImag=function(){ - xhrImg=Cc["@mozilla.org/xmlextras/xmlhttprequest;1"] - .createInstance(); - xhrImg.QueryInterface(Ci.nsIDOMEventTarget); - xhrImg.addEventListener("load",trueTinamiImg,false); - xhrImg.addEventListener("error",falseTinamiImg,false); - xhrImg.QueryInterface(Ci.nsIXMLHttpRequest); - xhrImg.open("GET",imgUrl,true); - xhrImg.overrideMimeType('text/plain;charset=x-user-defined'); - xhrImg.setRequestHeader('Referer',contents.URL); - xhrImg.setRequestHeader('Cookie',cookie); - xhrImg.send(null); - }; - - let getDOMHtmlDocument=function(str){ - let doc; - let range; - try{ - if(document.implementation.createHTMLDocument){ - doc=document.implementation.createHTMLDocument(''); - range=doc.createRange(); - range.selectNodeContents(doc.documentElement); - range.deleteContents(); - doc.documentElement.appendChild(range.createContextualFragment(str)); - }else{ - let doctype=document.implementation.createDocumentType( - 'html', - '-//W3C//DTD HTML 4.01 Transitional//EN', - 'http://www.w3.org/TR/html4/loose.dtd' - ); - doc=document.implementation.createDocument(null,'html',doctype); - range=doc.createRange(); - range.selectNodeContents(doc.documentElement); - let content=doc.adoptNode(range.createContextualFragment(str)); - doc.documentElement.appendChild(content); - } - }catch(e){ - doc=null; - } - return doc; - }; - - let getImageUrl=function(pageContents){ - let url; - let htmldoc=getDOMHtmlDocument(pageContents); - if(htmldoc){ - if(0<htmldoc.getElementsByTagName('img').length) - url=htmldoc.getElementsByTagName('img').item(0).getAttribute('src'); - else - url=''; - }else{ - let s=pageContents.indexOf('src="')+5; - let e=pageContents.indexOf('"',s); - url=pageContents.substr(s,e-s); - } - return url; - }; - - let trueImgInfo=function(){ - imgUrl=getImageUrl(xhrImgInfo.responseText); - if(-1!=imgUrl.search(/http:\/\/img.tinami.com\/illust\/img\//i)){ - saveImag(); - }else{ - liberator.echoerr("You should login TINAMI :<"); - } - }; - - let falseImgInfo=function(){ - liberator.echo("Image Infomation page accept error."); - return false; - }; - - if(0<contents.getElementsByClassName('bigger').length){ - xhrImgInfo=Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(); - xhrImgInfo.QueryInterface(Ci.nsIDOMEventTarget); - xhrImgInfo.addEventListener("load",trueImgInfo,false); - xhrImgInfo.addEventListener("error",falseImgInfo,false); - xhrImgInfo.QueryInterface(Ci.nsIXMLHttpRequest); - xhrImgInfo.open("GET",baseInfo+id,true); - xhrImgInfo.setRequestHeader('Referer',contents.URL); - xhrImgInfo.setRequestHeader('Cookie',cookie); - xhrImgInfo.send(null); - }else{ - imgUrl=contents.getElementsByClassName('draggable_img').item(0) - .firstChild.getAttribute('src'); - saveImag(); - } - } -); diff --git a/twaudio.js b/twaudio.js deleted file mode 100644 index 218a365..0000000 --- a/twaudio.js +++ /dev/null @@ -1,81 +0,0 @@ -// INFO // -var INFO = -<plugin name="twaudio.js" version="0.1" - summary="twaud.io player controller" - href="http://github.com/vimpr/vimperator-plugins/blob/master/twaudio.js" - xmlns="http://vimperator.org/namespaces/liberator"> - <author email="mitsugu.oyama@gmail.com">Mitsugu Oyama</author> - <license href="http://opensource.org/licenses/mit-license.php">MIT</license> - <project name="Vimperator" minVersion="2.3"/> - <p>twaud.io player controller.</p> - <item> - <tags>'twaudio'</tags> - <spec>:twp<oa>lay</oa></spec> -<!-- - 実装するか未定。jQueryが許さないかも。 - <spec>:twv<oa>olume</oa> <oa>level</oa></spec> ---> - <description> - <p>twaud.io player controller.</p> - </description> - </item> -</plugin>; - -commands.addUserCommand( - ['twp[lay]'], - 'toggle to play button', - function(){ - let contents=gBrowser.selectedBrowser.contentDocument; - // twaud.ioのjQueryの使い方によりマウス・イベントをエミュレート - // するしかないっぽい。 - let evt=contents.createEvent("MouseEvents"); - evt.initMouseEvent( - 'click', - true, // canBubble - true, // cancelable - window, // view - 0, // detail - 0, // screenX - 0, // screenY - 0, // clientX - 0, // clientY - false, // ctrlKey - false, // altKey - false, // shiftKey - false, // metaKey - 0, // button - null //relatedTarget - ); - let btnPlay=contents.getElementById('play0'); - if(null==btnPlay.getAttribute('style')){ // for twaud.io's bug - contents.location.reload(); - }else if('display: block;'==btnPlay.getAttribute('style')){ - btnPlay.dispatchEvent(evt); - }else{ - let btnPause=contents.getElementById('pause0'); - btnPause.dispatchEvent(evt); - } - }, - { - literal: false - }, - true -); - -/* -実装するか未定。jQueryが許さないかも。 -commands.addUserCommand( - ['twv[olume]'], - 'set volume of twaud.io player.', - function(args){ - if(1!=args.length){ - liberator.echoerr('argument error'); - return false; - } - }, - { - literal: false - }, - true -); -*/ diff --git a/twittperator.js b/twittperator.js index e09a273..2d5c2d5 100644 --- a/twittperator.js +++ b/twittperator.js @@ -23,144 +23,317 @@ * THE SOFTWARE. */ -let PLUGIN_INFO = -<VimperatorPlugin> - <name>Twittperator</name> - <description>Twitter Client using OAuth and Streaming API</description> - <description lang="ja">OAuth/StreamingAPI対応Twitterクライアント</description> - <version>1.14.1</version> - <minVersion>2.3</minVersion> - <maxVersion>3.2</maxVersion> - <author mail="teramako@gmail.com" homepage="http://d.hatena.ne.jp/teramako/">teramako</author> - <author mail="anekos@snca.net" homepage="http://d.hatena.ne.jp/nokturnalmortum/">anekos</author> - <license>MIT License</license> - <updateURL>https://github.com/vimpr/vimperator-plugins/raw/master/twittperator.js</updateURL> - <detail><![CDATA[ - This is the Twitter client plugin with OAuth authentication. - == Command == - - Use completion for comfort. - :tw[ittperator] -getPIN - Opens the page to authorize Twittperator and get your PIN from Twitter. - :tw[ittperator] -setPIN {PINcode} - Allows Twittperator to access Twitter by signifying your PIN. - - :tw[ittperator] - Shows recent your timeline. (The timeline will be cashed and expired 90 seconds after Twittperator get from Twitter.) - :tw[ittperator]! - Gets recent your timeline from Twitter and shows it. - :tw[ittperator]!@ - Shows mentions to you. - :tw[ittperator]!@user - Show @user's tweets. - :tw[ittperator] {TweetText} - Tweets {TweetText}. - :tw[ittperator] @user#id {TweetText} - Tweets a reply to @user. - :tw[ittperator] RT @user#id: {refTweet} - Does official retweet. - :tw[ittperator] {TweetText} RT @user#id: {refTweet} - Does classic retweet. - :tw[ittperator]!+status_id - Adds the tweet to your favorites. - :tw[ittperator]!-status_id - Delete the tweet from your favorites. - :tw[ittperator]!?{SearchText} - Shows the result of searching {SearchText}. - :tw[ittperator]!/{URI} - Opens {URI}. - :tw[ittperator]!delete {StatusID} - Deletes the {StatusID} tweet. - == Authentication Setting == +// INFO {{{ +let INFO = +<> + <plugin name="Twittperator" version="1.16.2" + href="https://github.com/vimpr/vimperator-plugins/raw/master/twittperator.js" + summary="Twitter Client using OAuth and Streaming API"> + <author email="teramako@gmail.com" href="http://d.hatena.ne.jp/teramako/">teramako</author> + <author email="anekos@snca.net" href="http://d.hatena.ne.jp/nokturnalmortum/">anekos</author> + <license>MIT License</license> + <project name="Vimperator" minVersion="2.3"/> + <p> + This is the Twitter client plugin with OAuth authentication. + </p> + <h2>Command</h2> + - Use completion for comfort. + <item> + <spec>:tw<oa>ittperator</oa> -getPIN</spec> + <description> + <p>Opens the page to authorize Twittperator and get your PIN from Twitter.</p> + </description> + </item> + <item> + <spec>:tw<oa>ittperator</oa> -setPIN <a>PINCode</a></spec> + <description> + <p>Allows Twittperator to access Twitter by signifying your PIN.</p> + </description> + </item> + <item> + <spec>:tw<oa>ittperator</oa></spec> + <description> + <p>Shows recent your timeline. (The timeline will be cashed and expired 90 seconds after Twittperator get from Twitter.)</p> + </description> + </item> + <item> + <spec>:tw<oa>ittperator</oa>!</spec> + <description> + <p>Gets recent your timeline from Twitter and shows it.</p> + </description> + </item> + <item> + <spec>:tw<oa>ittperator</oa>!@</spec> + <description> + <p>Shows mentions to you.</p> + </description> + </item> + <item> + <spec>:tw<oa>ittperator</oa>!@user</spec> + <description> + <p>Show @user's tweets.</p> + </description> + </item> + <item> + <spec>:tw<oa>ittperator</oa> <a>TweetText</a></spec> + <description> + <p>Tweets <a>TweetText</a>.</p> + </description> + </item> + <item> + <spec>:tw<oa>ittperator</oa> @user#id <a>TweetText</a></spec> + <description> + <p>Tweets a reply to @user.</p> + </description> + </item> + <item> + <spec>:tw<oa>ittperator</oa> RT @user#id: <a>refTweet</a></spec> + <description> + <p>Does official retweet.</p> + </description> + </item> + <item> + <spec>:tw<oa>ittperator</oa> <a>TweetText</a> RT @user#id: <a>refTweet</a></spec> + <description> + <p>Does classic retweet.</p> + </description> + </item> + <item> + <spec>:tw<oa>ittperator</oa>!+status_id</spec> + <description> + <p>Adds the tweet to your favorites.</p> + </description> + </item> + <item> + <spec>:tw<oa>ittperator</oa>!-status_id</spec> + <description> + <p>Delete the tweet from your favorites.</p> + </description> + </item> + <item> + <spec>:tw<oa>ittperator</oa>!?<a>SearchText</a></spec> + <description> + <p>Shows the result of searching <a>SearchText</a>.</p> + </description> + </item> + <item> + <spec>:tw<oa>ittperator</oa>!/<a>URI</a></spec> + <description> + <p>Opens <a>URI</a>.</p> + </description> + </item> + <item> + <spec>:tw<oa>ittperator</oa>!delete <a>StatusID</a></spec> + <description> + <p>Deletes the <a>StatusID</a> tweet.</p> + </description> + </item> + <h2>Authentication Setting</h2> First of all, you have to get your PIN from Twitter and signify it to Twittperator. Type a following command: - >|| + <code> :tw -getPIN - ||< + </code> and you will get the page to authorize Twittperator to access Twitter in a new tab. If you allow and you will get the PIN (7 digit numbers), then yank it. Secondarily, authorize Twittperator with your PIN. - >|| + <code> :tw -setPIN yanked_PIN - ||< - == FAQ == + </code> + <h2>FAQ</h2> + <p> - What is this ? + </p> + <p> The plugin that just tweet with Vimperator. + </p> + <p> - My timeline is hard to see...? + </p> + <p> We are making an effort, and welcoming patches. + </p> + <p> - By the way, is it possible to show timeline automatically? + </p> + <p> Use chirpstream. Write the below line into rc file. let g:twittperator_use_chirp = 1 + </p> + <p> - It's too much of the bother to show my timeline manually!! + </p> + <p> We think implementing a wider display method and a mean of word wrapping will solve this issue. Any ideas? + </p> + <p> - Is there a plan to work together Growl GNTP? + </p> + <p> Write the plugin. - ]]></detail> - <detail lang="ja"><![CDATA[ - これはOAuth認証を用いたTwitterクライアントプラグインです。 - == Command == - - 適当に補完しましょう。 - :tw[ittperator] -getPIN - PINコード取得ページを開きます。 - :tw[ittperator] -setPIN {PINcode} - PINcodeを設定します。 - - :tw[ittperator] - 前回取得したタイムラインを表示します。 (キャッシュが90秒以上古い場合は再取得。) - :tw[ittperator]! - 強制的に取得したタイムラインを表示します。 - :tw[ittperator]!@ - あなたへの言及(mentions)表示します。 - :tw[ittperator]!@user - @user のタイムラインを表示します。 - :tw[ittperator] {TweetText} - {TweetText}をポストします。 - :tw[ittperator] @user#id {TweetText} - @user への返信になります。 - :tw[ittperator] RT @user#id: {refTweet} - 公式RTになるはずです。 - :tw[ittperator] {TweetText} RT @user#id: {refTweet} - 非公式RTになるはずです。 - :tw[ittperator]!+status_id - tweetをfavoriteします。 - :tw[ittperator]!-status_id - tweetをunfavoriteします。 - :tw[ittperator]!?{SearchText} - {SearchText}の検索結果を表示します。 - :tw[ittperator]!/{URI} - {URI}を開きます。 - :tw[ittperator]!delete {StatusID} - {StatusID}のツイートを削除します。 - == Authentication Setting == + </p> + </plugin> + <plugin name="Twittperator" version="1.16.2" + href="https://github.com/vimpr/vimperator-plugins/raw/master/twittperator.js" + lang="ja" + summary="OAuth/StreamingAPI対応Twitterクライアント"> + <author email="teramako@gmail.com" href="http://d.hatena.ne.jp/teramako/">teramako</author> + <author email="anekos@snca.net" href="http://d.hatena.ne.jp/nokturnalmortum/">anekos</author> + <license>MIT License</license> + <project name="Vimperator" minVersion="2.3"/> + <p> + これはOAuth認証を用いたTwitterクライアントプラグインです。 + </p> + <h2>Command</h2> + - 適当に補完しましょう。 + <item> + <spec>:tw<oa>ittperator</oa> -getPIN</spec> + <description> + <p>PINコード取得ページを開きます。</p> + </description> + </item> + <item> + <spec>:tw<oa>ittperator</oa> -setPIN <a>PINcode</a></spec> + <description> + <p>PINcodeを設定します。</p> + </description> + </item> + + <item> + <spec>:tw<oa>ittperator</oa></spec> + <description> + <p>前回取得したタイムラインを表示します。 (キャッシュが90秒以上古い場合は再取得。)</p> + </description> + </item> + <item> + <spec>:tw<oa>ittperator</oa>!</spec> + <description> + <p>強制的に取得したタイムラインを表示します。</p> + </description> + </item> + <item> + <spec>:tw<oa>ittperator</oa>!@</spec> + <description> + <p>あなたへの言及(mentions)表示します。</p> + </description> + </item> + <item> + <spec>:tw<oa>ittperator</oa>!@user</spec> + <description> + <p>@user のタイムラインを表示します。</p> + </description> + </item> + <item> + <spec>:tw<oa>ittperator</oa> <a>TweetText</a></spec> + <description> + <p><a>TweetText</a>をポストします。</p> + </description> + </item> + <item> + <spec>:tw<oa>ittperator</oa> @user#id <a>TweetText</a></spec> + <description> + <p>@user への返信になります。</p> + </description> + </item> + <item> + <spec>:tw<oa>ittperator</oa> RT @user#id: <a>refTweet</a></spec> + <description> + <p>公式RTになるはずです。</p> + </description> + </item> + <item> + <spec>:tw<oa>ittperator</oa> <a>TweetText</a> RT @user#id: <a>refTweet</a></spec> + <description> + <p>非公式RTになるはずです。</p> + </description> + </item> + <item> + <spec>:tw<oa>ittperator</oa>!+status_id</spec> + <description> + <p>tweetをfavoriteします。</p> + </description> + </item> + <item> + <spec>:tw<oa>ittperator</oa>!-status_id</spec> + <description> + <p>tweetをunfavoriteします。</p> + </description> + </item> + <item> + <spec>:tw<oa>ittperator</oa>!?<a>SearchText</a></spec> + <description> + <p><a>SearchText</a>の検索結果を表示します。</p> + </description> + </item> + <item> + <spec>:tw<oa>ittperator</oa>!/<a>URI</a></spec> + <description> + <p><a>URI</a>を開きます。</p> + </description> + </item> + <item> + <spec>:tw<oa>ittperator</oa>!delete <a>StatusID</a></spec> + <description> + <p><a>StatusID</a>のツイートを削除します。</p> + </description> + </item> + <h2>Authentication Setting</h2> 最初にPINコードを取得し設定する必要があります。 - >|| + <code> :tw -getPIN - ||< + </code> を実行すると新規タブに本アプリケーションを許可するかを問うページが開かれます。 許可をすると、PINコード(数値)が表示されるのでコピーしてください。 - >|| + <code> :tw -setPIN コピーしたPINコード - ||< + </code> で初期設定完了です。 - == FAQ == + <h2>FAQ</h2> + <p> - なんて読むんだ + </p> + <p> 知らん。トゥイットゥペレータと自分は勝手に読んでいる。 + </p> + <p> - 何のためのクライアント? + </p> + <p> Vimperatorを使っていて、さくっと呟きたいとき用です(ぉ + </p> + <p> - TL表示をもっと工夫しろ + </p> + <p> ごめんなさい。改良してコミットしてくれると嬉しいです。 + </p> + <p> - つーか、TLくらい自動取得しろ + </p> + <p> はい、がんばりました。 - let g:twittperator_use_chirp = 1 + <code>let g:twittperator_use_chirp = 1</code> として、chirpstream を利用してください。 + </p> + <p> - ぶっちゃけTL表示とか面倒だよね? + </p> + <p> はい、がんばります・・・ でかい表示領域と行の折り返し方法が確立できれば、もっと頑張れる気がします。 + </p> + <p> - Growl GNTP との連携しないの? + </p> + <p> プラグイン書きましょう。 - ]]></detail> -</VimperatorPlugin>; + </p> + </plugin> +</>; + +// }}} (function() { @@ -1048,7 +1221,7 @@ let PLUGIN_INFO = callback(self.accessor); } }else{ - alert(d.statusText); + window.alert(d.statusText); } }, }; @@ -1237,8 +1410,9 @@ let PLUGIN_INFO = url + '.json', query, function (text) { + let json; try { - return callback(JSON.parse(text)); + json = JSON.parse(text); } catch (e) { (onError || @@ -1248,6 +1422,7 @@ let PLUGIN_INFO = throw e; })(e); } + return callback(json); } ); }; @@ -1273,7 +1448,7 @@ let PLUGIN_INFO = } for (let [n, v] in Iterator(options)) { - if (/^on[A-Z]/(n) && (v instanceof Function)) + if (/^on[A-Z]/.test(n) && (v instanceof Function)) this.events[n.toLowerCase()] = v; } @@ -1409,7 +1584,7 @@ let PLUGIN_INFO = lines[0] = buf + lines[0]; for (let [, line] in Iterator(lines.slice(0, -1))) { try { - if (/^\s*\{/(line)) + if (/^\s*\{/.test(line)) onMsg(Utils.fixStatusObject(JSON.parse(line)), line); } catch (e) { liberator.log(e); } } @@ -1470,8 +1645,8 @@ let PLUGIN_INFO = }); }, // }}} getUserTimeline: function(target, onload) { // {{{ - let [api, query] = target ? ["statuses/user_timeline", {screen_name: target}] - : ["statuses/home_timeline", {}]; + let [api, query] = target ? ["statuses/user_timeline", {screen_name: target, count: setting.count}] + : ["statuses/home_timeline", {count: setting.count}]; tw.jsonGet( api, @@ -1492,6 +1667,9 @@ let PLUGIN_INFO = } ); }, // }}} + lookupUser: function({screenNames, ids}, callback) { // {{{ + tw.jsonGet("users/lookup", { user_id: String(ids || []), screen_name: String(screenNames || []) }, callback); + }, // }}} say: function(status, inReplyToStatusId) { // {{{ let sendData = {status: status, source: "Twittperator"}; if (inReplyToStatusId) @@ -1560,7 +1738,7 @@ let PLUGIN_INFO = } for (let [n, v] in Iterator(st)) { - if (/(^|_)id$/(n)) + if (/(^|_)id$/.test(n)) fixId(result, n); } @@ -1636,7 +1814,7 @@ let PLUGIN_INFO = function loadPluginFromDir(checkGV) { return function(dir) { dir.readDirectory().forEach(function(file) { - if (/\.tw$/(file.path) && (!checkGV || isEnabled(file))) + if (/\.tw$/.test(file.path) && (!checkGV || isEnabled(file))) Twittperator.sourceScriptFile(file); }); } @@ -1648,6 +1826,58 @@ let PLUGIN_INFO = io.getRuntimeDirectories("plugin/twittperator").forEach(loadPluginFromDir(true)); io.getRuntimeDirectories("twittperator").forEach(loadPluginFromDir(false)); }, // }}} + lookupUser: function(users) { // {{{ + function showUsersInfo(json) { // {{{ + let xml = modules.template.map(json, function(user) { + return <> + <tr> + <td class="twittperator lookup-user photo"> + <img src={user.profile_image_url} /> + </td> + <td class="twittperator lookup-user screen-name"> + <a href={"https://twitter.com/#!/" + user.screen_name}> + {user.name} + </a> + </td> + <td class="twittperator lookup-user attributes"> + {user.location} - + id {user.id_str} - + {user.following ? '' : 'not'} following - + {user.friends_count}/{user.followers_count} ee/er - + {user.statuses_count} tweets - + {user.favourites_count} favs - + {user.listed_count} listed - + from {new Date(user.created_at).toLocaleString()} + </td> + </tr> + <tr> + <td class="twittperator lookup-user description" colspan="3"> + {user.description} + </td> + </tr> + </>; + }); + liberator.echo( + <> + <style type="text/css"><![CDATA[ + .twittperator.lookup-user.photo { vertical-align: top; width: 28px; } + .twittperator.lookup-user.photo img { border: 0px; width: 24px; height: 24px; vertical-align: baseline; margin: 1px; } + .twittperator.lookup-user.attributes { white-space: normal !important; } + .twittperator.lookup-user.description { white-space: normal !important; } + .twittperator.lookup-user.description a { text-decoration: none; } + ]]></style> + <table>{xml}</table> + </> + ); + } // }}} + + let ids = [], screenNames = []; + for (let [, v] in Iterator(users)) + (/^\d+$/.test(v) ? ids : screenNames).push(v); + Twitter.lookupUser({ids: ids, screenNames: screenNames}, function(json) { + showUsersInfo(json); + }); + }, // }}} onFriends: function(msg) { // {{{ __context__.Friends = friends = msg.friends; }, // }}} @@ -1720,11 +1950,11 @@ let PLUGIN_INFO = ("window.parent.liberator.modules.plugins.twittperator.Twittperator.showStatusMenu(" + parseInt(st.id) + ")"); let html = <style type="text/css"><![CDATA[ - .twitter.user { vertical-align: top; } - .twitter.entry-content { white-space: normal !important; } - .twitter.entry-content a { text-decoration: none; } - .twitter.entry-content.rt:before { content: "RT "; color: silver; } - img.twitter.photo { border: 0px; width: 16px; height: 16px; vertical-align: baseline; margin: 1px; } + .twittperator.timeline.user { vertical-align: top; } + .twittperator.timeline.entry-content { white-space: normal !important; } + .twittperator.timeline.entry-content a { text-decoration: none; } + .twittperator.timeline.entry-content.rt:before { content: "RT "; color: silver; } + img.twittperator.timeline.photo { border: 0px; width: 24px; height: 24px; vertical-align: baseline; margin: 1px; } ]]></style>.toSource() .replace(/(?:\r\n|[\r\n])[ \t]*/g, " ") + s.reduce(function(table, status) { @@ -1732,35 +1962,35 @@ let PLUGIN_INFO = ("retweeted_status" in status) ? let (rt = status.retweeted_status) <tr> - <td class="twitter user"> + <td class="twittperator timeline user"> <a href={userURL(rt.user.screen_name)}> - <img src={rt.user.profile_image_url} alt={rt.user.screen_name} class="twitter photo"/> + <img src={rt.user.profile_image_url} alt={rt.user.screen_name} class="twittperator timeline photo"/> <strong>{rt.user.screen_name}‬</strong> </a> <a href={userURL(status.user.screen_name)}> - <img src={status.user.profile_image_url} alt={status.user.screen_name} class="twitter photo"/> + <img src={status.user.profile_image_url} alt={status.user.screen_name} class="twittperator timeline photo"/> </a> </td> - <td class="twitter entry-content rt"> + <td class="twittperator timeline entry-content rt"> {Utils.anchorLink(rt.text)} </td> - <td class="twitter menu"> + <td class="twittperator timeline menu"> <a href="javascript: void 0" onclick={menuEvent(status)}> ۞ </a> </td> </tr> : <tr> - <td class="twitter user"> + <td class="twittperator timeline user"> <a href={userURL(status.user.screen_name)}> - <img src={status.user.profile_image_url} alt={status.user.screen_name} class="twitter photo"/> + <img src={status.user.profile_image_url} alt={status.user.screen_name} class="twittperator timeline photo"/> <strong title={status.user.name}>{status.user.screen_name}‬</strong> </a> </td> - <td class="twitter entry-content"> + <td class="twittperator timeline entry-content"> {Utils.anchorLink(status.text)} </td> - <td class="twitter menu"> + <td class="twittperator timeline menu"> <a href="javascript: void 0" onclick={menuEvent(status)}> ۞ </a> @@ -1793,7 +2023,7 @@ let PLUGIN_INFO = Utils.xmlhttpRequest({ method: 'GET', - url: "http://search.twitter.com/search.json?" + tw.buildQuery({ q: word }), + url: "http://search.twitter.com/search.json?" + tw.buildQuery({ q: word, rpp: setting.count, lang: setting.lang }), onload: function(xhr) { let res = JSON.parse(xhr.responseText); if (res.results.length > 0) { @@ -1853,10 +2083,9 @@ let PLUGIN_INFO = } }, // }}} withProtectedUserConfirmation: function(check, actionName, action) { // {{{ - let protectedUserName = Twittperator.isProtected(check); - if (protectedUserName) { + if (Twittperator.isProtected(check)) { Twittperator.confirm( - protectedUserName + " is protected user! Do you really want to " + actionName + '?', + check.screenName + " is protected user! Do you really want to " + actionName + '?', action ); } else { @@ -1905,24 +2134,31 @@ let PLUGIN_INFO = context.compare = void 0; context.createRow = function(item, highlightGroup) { - let desc = item[1] || this.process[1].call(this, item, item.description); + if (highlightGroup === "CompTitle") { + return <div highlight="CompTitle" style="white-space: nowrap"> + <li highlight="CompDesc">{item} </li> + </div>; + } - if (desc && desc.user) { - return <div highlight={highlightGroup || "CompItem"} style="white-space: nowrap"> + let [value, st] = item.item; + if (st.user) { + return <div highlight="CompItem" style="white-space: nowrap"> <li highlight="CompDesc"> - <img src={desc.user.profile_image_url} style="max-width: 24px; max-height: 24px"/> -  {desc.user.screen_name}: {desc.text} + <img src={st.user.profile_image_url} style="max-width: 24px; max-height: 24px"/> +  {st.user.screen_name}: {st.text} + </li> + </div>; + } else { + return <div highlight="CompItem" style="white-space: nowrap"> + <li highlight="CompDesc"> + {st.text} </li> </div>; } - - return <div highlight={highlightGroup || "CompItem"} style="white-space: nowrap"> - <li highlight="CompDesc">{desc} </li> - </div>; }; context.filters = [statusObjectFilter]; - context.title = ["Hidden", "Entry"]; + context.title = "Entry"; } // }}} function makeTimelineCompleter(completer) { // {{{ @@ -2004,7 +2240,7 @@ let PLUGIN_INFO = "^" + this.command.map(function(c) let (r = util.escapeRegex(c)) - (/^\W$/(c) ? r : r + "( |$)") + (/^\W$/.test(c) ? r : r + "( |$)") ).join("|") ); }, @@ -2069,7 +2305,7 @@ let PLUGIN_INFO = description: "Open link", action: function(arg) Twittperator.openLink(arg), timelineCompleter: true, - completer: Completers.text(function(s) /https?:\/\//(s.text)) + completer: Completers.text(function(s) /https?:\/\//.test(s.text)) }), SubCommand({ command: ["delete"], @@ -2080,7 +2316,7 @@ let PLUGIN_INFO = Twitter.destroy(m[0]); }, timelineCompleter: true, - completer: Completers.id(seleceMine) + completer: Completers.rawid(seleceMine) }), SubCommand({ command: ["info"], @@ -2106,6 +2342,15 @@ let PLUGIN_INFO = completer: Completers.rawid(function(st) st.id) }), SubCommand({ + command: ["lookupuser"], + description: "Lookup users", + action: function(arg) { + Twittperator.lookupUser(arg.split(/\s+/)); + }, + timelineCompleter: true, + completer: Completers.screenName() + }), + SubCommand({ command: ["track"], description: "Track the specified words.", action: function(arg) { @@ -2143,18 +2388,23 @@ let PLUGIN_INFO = command: ["thread"], description: "Show tweets thread.", action: function(arg) { + function showThread () { + Twittperator.showTL(thread); + } function getStatus(id, next) { let result; - if (history.some(function (it) (it.id == id && (result = it)))) + if (history.some(function (it) (it.id == id && (result = it)))) { return next(result); - tw.jsonGet("statuses/show/" + id, null, function(res) next(res)) + } + // XXX エラーの時はなにか表示しておくべき? + tw.jsonGet("statuses/show/" + id, null, function(res) next(res), showThread); } function trace(st) { thread.push(st); if (st.in_reply_to_status_id) { getStatus(st.in_reply_to_status_id, trace); } else { - Twittperator.showTL(thread); + showThread(); } } @@ -2216,7 +2466,7 @@ let PLUGIN_INFO = function subCommandCompleter(context, args) { // {{{ if (!args.literalArg.match(/^(\W|\S+\s)/)) { - context.title = ["Sub command", "Description"]; + context.title = ["Sub Command", "Description"]; context.completions = SubCommands.map(function({ command, description }) [command[0], description]); return; } @@ -2245,7 +2495,7 @@ let PLUGIN_INFO = let arg = args.literalArg.slice(0, context.caret); let m; if (m = arg.match(/^D\s+/)) { - context.title = ["Name#ID", "Entry"]; + context.title = "Entry"; context.advance(m[0].length); Completers.name(rejectMine)(context, args); return; @@ -2261,7 +2511,7 @@ let PLUGIN_INFO = if (m) len = m.index + m[1].length; - context.title = ["Name#ID", "Entry"]; + context.title = "Entry"; context.offset += len; // XXX 本文でも検索できるように、@ はなかったことにする context.filter = context.filter.replace(/^[@#]/, ""); @@ -2342,6 +2592,8 @@ let PLUGIN_INFO = apiURLBase: "http" + (!!gv.twittperator_use_ssl_connection_for_api_ep ? "s" : "") + "://api.twitter.com/" + (gv.twittperator_twitter_api_version || 1) + "/", trackWords: gv.twittperator_track_words, + count: (gv.twittperator_count || 20), + lang: (gv.twittperator_lang || ''), }); let statusRefreshTimer; @@ -2358,7 +2610,7 @@ let PLUGIN_INFO = // ストリーム let ChirpUserStream = Stream({ name: 'chirp stream', url: "https://userstream.twitter.com/2/user.json" }); - let TrackingStream = Stream({ name: 'tracking stream', url: "http://stream.twitter.com/1/statuses/filter.json" }); + let TrackingStream = Stream({ name: 'tracking stream', url: "https://stream.twitter.com/1/statuses/filter.json" }); // 公開オブジェクト __context__.OAuth = tw; diff --git a/twittperator/add-url-completer.tw b/twittperator/add-url-completer.tw new file mode 100644 index 0000000..74cf1dd --- /dev/null +++ b/twittperator/add-url-completer.tw @@ -0,0 +1,65 @@ +/* NEW BSD LICENSE {{{ +Copyright (c) 2011, anekos. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. + + +################################################################################### +# http://sourceforge.jp/projects/opensource/wiki/licenses%2Fnew_BSD_license # +# に参考になる日本語訳がありますが、有効なのは上記英文となります。 # +################################################################################### + +}}} */ + +/* + * Please write the below line into .vimperatorrc. + * let g:twittperator_plugin_add_url_completer = 1 + * and, add "T" into "complete" option after vimperator boot. + */ + +(function () { + + completion.addUrlCompleter( + 'T', + 'Open the urls in tweets', + function (context, args) { + context.title = ['Tweeted URL', 'Tweeted text']; + context.filters = [CompletionContext.Filter.textAndDescription]; + let cs = []; + for (let [, t] in Iterator(plugins.twittperator.Tweets)) { + if (!(t.entities && t.entities.urls)) + continue; + for (let [, u] in Iterator(t.entities.urls)) { + let url = u.expanded_url || u.url; + if (url) + cs.push([url, t.text]); + } + } + context.completions = cs; + } + ); + +})(); + +// vim: sw=2 ts=2 et fdm=marker ft=javascript: diff --git a/twittperator/anti-goji.tw b/twittperator/anti-goji.tw new file mode 100644 index 0000000..bd77fda --- /dev/null +++ b/twittperator/anti-goji.tw @@ -0,0 +1,21 @@ + +/* + * 誤字のあるツイートを Fav / RT してツイートした人を切ない気分にさせないためのプラグインです。 + * 誤字のあるツイートに Fav / RT すると、エラーを出して中止してくれます。 + * (誤字のあるツイートには必ず、#誤字有り とハッシュタグをつけるように周知徹底してください。 + */ + +'favorite retweet'.split(/\s+/).forEach(function (name) { + plugins.libly.$U.around( + plugins.twittperator.Twitter, + name, + function (next, [id]) { + let goji = plugins.twittperator.Tweets.some(function (it) ((it.id == id) && /[##]誤字[アあ有][りリ]?/.test(it.text))); + if (goji) { + liberator.echoerr('The tweet has some gojis! DON NOT ' + name.toUpperCase() + '!!'); + } else { + return next(); + } + } + ); +}); diff --git a/twittperator/pong.tw b/twittperator/pong.tw index dae2e15..9f3f381 100644 --- a/twittperator/pong.tw +++ b/twittperator/pong.tw @@ -12,7 +12,7 @@ plugins.twittperator.ChirpUserStream.addListener( function onMsg (msg, raw) { function negi (pattern, reply) { - if (RegExp('^\\s*@' + screenName + '\\s+' + pattern + '\s*$')(msg.text.trim())) { + if (RegExp('^\\s*@' + screenName + '\\s+' + pattern + '\s*$').test(msg.text.trim())) { plugins.twittperator.Twitter.say('@' + msg.user.screen_name + ' ' + reply, msg.id_str); return true; } diff --git a/twittperator/twlist-win.tw b/twittperator/twlist-win.tw index 674f4f3..939e472 100644 --- a/twittperator/twlist-win.tw +++ b/twittperator/twlist-win.tw @@ -193,10 +193,10 @@ let winXML = <> function getCurrentListBox(){ return tabBox.tabpanels.selectedPanel.firstChild; } - function getParent(node, class){ + function getParent(node, klass){ let elm = node; while (elm != document.documentElement){ - if (elm instanceof class) + if (elm instanceof klass) return elm; elm = elm.parentNode; } diff --git a/twittperator/twsidebar.tw b/twittperator/twsidebar.tw new file mode 100755 index 0000000..47cae8c --- /dev/null +++ b/twittperator/twsidebar.tw @@ -0,0 +1,524 @@ +liberator.modules.TWAnekoSB = ANekoSB = (function () { + + /********************************************************************************* + * Config + *********************************************************************************/ + + let Config = liberator.globalVariables.twittperator_sidebar_config || { + // for Keyword タグ + keyword: /neko|vimp|cat|猫/i, + + // ツイート内に含まれると、表示上抹殺される (reply とか除く + vanish: /うぎぃいいい/i, + + // 自分のスクリーンネーム + screenName: 'anekos', + + // 自分のその他の名前 + myNames: /anekos|悪魔猫将軍/i, + + // ログファイル てけとーなフォーマットで保存されます + //logFile: io.File('~/.chirpstream'), + //myLogFile: io.File('~/.mychirpstream'), + + // 各イベント時に音がなる + sound: { + meow: makeAudio('file:///home/anekos/sound/my/meow.wav'), + fanfare: makeAudio('file://C:/sound-data/fanfare.wav', 0.5), + retweet: makeAudio('file:///home/anekos/sound/my/meow.wav', 0.8), + favorite: makeAudio('file:///home/anekos/sound/my/meow.wav', 0.6), + reply: makeAudio('file:///home/anekos/sound/my/meow.wav', 1.0), + debug: makeAudio('file:///home/anekos/sound/my/meow.wav', 1.0), + filter: makeAudio('file:///home/anekos/sound/my/meow.wav', 1.0), + }, + + // 文字のサイズ + fontSize: 15, + + // リストの最大保持数 + listMax: 100, + + // 日本語だけ for filter stream + jpOnly: true, + + // 地震ツイートの本文に場所をくっつける + earthquake: true, + }; + + // 日本語判定 + JP = new RegExp("[\\u4e00-\\u9fa0\\u30A1-\\u30F6\\u30FC\\u3042-\\u3093\\u3001\\u3002\\uFF01\\uFF1F]"); + + /********************************************************************************* + * Main + *********************************************************************************/ + + // util {{{ + + function className (n) + ('tw-anekos-sb-plugin-' + n); + + function px (n) + parseInt(n, 10) + 'px'; + + // }}} + + function formatText (str) { // {{{ + str = str.trim(); + let reg = /https?:\/\/[^\s]+|[#@]\w+/g; + XML.ignoreWhitespace = false; + let m, i = 0, buf = "", x = <xhtml:p class="twlist-text" xmlns:xhtml="http://www.w3.org/1999/xhtml"/>; + while((m=reg.exec(str))){ + buf = str.substring(i, m.index); + if (buf) + x.appendChild(buf); + let klass = "twlist-link", href = ""; + switch (m[0].charAt(0)){ + case "@": + klass += " twlist-user"; + href = "http://twitter.com/" + m[0].substr(1); + break; + case "#": + klass += " twlist-hash"; + href = "http://twitter.com/search?q=%23" + m[0].substr(1); + break; + default: + klass += " twlist-url"; + href = m[0]; + } + x.appendChild(<xhtml:a class={klass} href={href} + onclick="twlist.onClick(event)" xmlns:xhtml={XHTML}>{m[0]}</xhtml:a>); + i=reg.lastIndex; + } + buf = str.substr(i); + if (buf) + x.appendChild(buf); + return x; + } // }}} + + function escapeBreakers (text) // {{{ + text.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f]+/g, function(c) uneval(c)); // }}} + + function getSidebarWindow () + document.getElementById('sidebar')._contentWindow; + + let appendTweet = (function () { // {{{ + function messageToXML (t) { + XML.prettyPrinting = true; + XML.ignoreWhitespace = true; + let xml; + let sbWidth = getSidebarWindow().document.width; + xml = + <richlistitem + class={[className('tweet-panel'), className('tweet-' + t.type)].join(' ')} + style={[ + "font-size: " + px(Config.fontSize - (t.text.length > 70 ? 2 : 0)), + "width: " + px(sbWidth - 100) + ' !important' + ].join(';')} + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <hbox> + <vbox> + <image src={t.img} height="48" width="48" /> + </vbox> + <vbox style={"width: " + (sbWidth - 48 - 35) + "px !important"}> + <hbox> + <label style="font-weight: bold">{escapeBreakers(t.name)}</label> + <spacer flex="1"/> + <label>{t.sub || ''}</label> + </hbox> + <description width="100%">{escapeBreakers(t.text)}</description> + </vbox> + </hbox> + </richlistitem>; + return xml; + } + + function xmlToDom(xml, xmlns) { + XML.prettyPrinting = true; + XML.ignoreWhitespace = true; + var doc = (new DOMParser).parseFromString( + '<root xmlns="' + xmlns + '">' + xml.toXMLString() + "</root>", + "application/xml"); + var imported = document.importNode(doc.documentElement, true); + var range = document.createRange(); + range.selectNodeContents(imported); + var fragment = range.extractContents(); + range.detach(); + return fragment.childNodes.length > 1 ? fragment : fragment.firstChild; + } + + let latest = {}; + let latestNode = null; + + function append (t, tab, streamName) { + tab = tab || 'home'; + + let now = JSON.stringify({name: t.name, text: t.text, tab: tab}); + if (latest === now) { + if (latestNode) + latestNode.setAttribute( + 'class', + latestNode.getAttributeNode('class') + className('tweet-' + t.type) + ); + return; + } + latest = now; + + let cntr = getSidebarWindow().document.getElementById('tw-anekos-sb-' + tab + '-list'); + let dom = xmlToDom(messageToXML(t)); + let repDom = dom.cloneNode(true); + let len = cntr.itemCount; + cntr.appendChild(repDom); + latestNode = repDom; + + cntr.scrollToIndex(len - 1); + if (len > Config.listMax) + cntr.removeChild(cntr.firstChild); + } + + return append; + })(); // }}} + + function objectToString (obj, head) { // {{{ + if (!head) + head = ''; + + let nextHead = head + ' '; + + let result = ''; + for (let [n, v] in Iterator(obj)) { + let vstr = + (v && typeof v === 'object') ? objectToString(v, nextHead) + : (v || '').toString(); + result += head + n + ':\n' + vstr.split(/\n/).map(function(s) nextHead + s).join('\n') + '\n'; + } + + return result.trim(); + } // }}} + + function onMsg (real, msg, raw, streamName) { // {{{ + if (real) { + Tweets.unshift(msg); + if (Tweets.length > Config.listMax) + Tweets.splice(Config.listMax); + } + + let screenName = Config.screenName; + let my = (msg.retweeted_status && msg.retweeted_status.user.screen_name === screenName) + || + (msg.target_object && msg.event && ( + (msg.event === 'favorite' && msg.target_object.user.screen_name == screenName) + || + (msg.event === 'list_member_added' && msg.target.screen_name == screenName) + )) + || + (msg.user && msg.text && Config.myNames.test(msg.text)) + || + (msg.user && msg.text && msg.in_reply_to_screen_name == screenName) + || + (msg.direct_message); + + // Fav test + try { + //liberator.log(JSON.stringify(msg, null, 2)); + if (msg.event && msg.event === 'favorite' && msg.source && msg.source.screen_name === screenName) { + let t = { + name: '>' + msg.target_object.user.screen_name + '<', + img: msg.target_object.user.profile_image_url, + text: msg.target_object.text, + type: 'favorite' + }; + appendTweet(t, 'home', streamName); + appendTweet(t, 'debug', streamName); + Config.sound.debug.play(); + } + } catch (e) { + liberator.log(e); + } + + // Ignore not JP + if (!my && streamName === 'filter' && msg.text && Config.jpOnly && !JP.test(msg.text)) { + return; + } + + if (msg.text && msg.user && msg.user && msg.user.screen_name === screenName) + my = false; + + let t, dummy; + + if (msg.direct_message) { + t = { + name: msg.direct_message.sender.screen_name, + img: msg.direct_message.sender.profile_image_url, + text: msg.direct_message.text, + sub: 'DM', + type: 'DM' + }; + } else if (msg.retweeted_status) { + t = { + name: my ? msg.user.screen_name : msg.retweeted_status.user.screen_name, + img: my ? msg.user.profile_image_url : msg.retweeted_status.user.profile_image_url, + text: msg.retweeted_status.text, + sub: '\u21BB ' + msg.user.screen_name, + type: 'retweet' + }; + dummy = true; + } else if (my && msg.target && msg.event) { + if (msg.event === 'favorite' && msg.target_object && !msg.target_object.retweeted_status) { + t = { + name: msg.source.screen_name, + img: msg.source.profile_image_url, + text: msg.target_object.text, + type: 'favorite', + sub: 'fav' + }; + dummy = true; + } else if (msg.event === 'list_member_added' && msg.target) { + // 結構漏れがある? + t = { + name: msg.source.screen_name, + img: msg.source.profile_image_url, + text: + '\u3042\u306A\u305F\u3092\u30EA\u30B9\u30C8\u300C' + + msg.target_object.name + + '\u300D\u306B\u8FFD\u52A0\u3057\u307E\u3057\u305F\u3002\n' + + 'http://twitter.com' + msg.target_object.uri, + type: 'list-member-added', + sub: 'listed' + }; + dummy = true; + } + } else if (msg.event === 'follow' && msg.target && msg.source) { + t = { + name: msg.source.screen_name, + img: msg.source.profile_image_url, + text: 'follow ' + msg.target.screen_name, + type: 'follow' + }; + my = msg.target.screen_name === screenName; + dummy = true; + } else if (msg.user && msg.text && msg.in_reply_to_screen_name == screenName) { + t = { + name: msg.user.screen_name, + img: msg.user.profile_image_url, + text: msg.text, + type: 'reply' + }; + } else if (msg.user && msg.text) { + t = { + name: msg.user.screen_name, + img: msg.user.profile_image_url, + text: msg.text, + type: 'normal' + }; + } + + if (t) { + if (Config.earthquake && /\u5730\u9707/.test(t.text) && msg.text.length < 20 && msg.user && msg.user.location) { + t.text += ' [\u5730\u57DF: ' + msg.user.location + ']'; + } + + if (!t.sub && msg.created_at) { + t.sub = new Date(msg.created_at).toLocaleTimeString().replace(/:\d+$/,'');; + } + + if (real && dummy) { + if (typeof dummy != 'object') { + dummy = { + user: { + screen_name: t.name || '', + profile_image_url: t.img + }, + text: '[' + t.type + '] ' + t.text + ' - http://twitter.com/' + t.name + }; + } + plugins.twittperator.Twittperator.onMessage(dummy); + } + + if (my || !Config.vanish.test([t.name, t.text, t.sub].join(' '))) { + if (my) { + if (real) { + let sound = Config.sound[t.type] || Config.sound.fanfare; + sound.play(); + } + t.type += '-my'; + } else { + if (t.type === 'normal' && Config.keyword.test(t.text)) + t.type = 'keyword'; + } + + if (streamName === 'filter') { + if (!msg.retweeted_status) { + t.type = 'filter'; + appendTweet(t, 'home', streamName); + appendTweet(t, 'filter', streamName); + let (s = Config.sound.filter) (s && s.play()); + } + } else if (/^(keyword)$/.test(t.type)) { + appendTweet(t, 'home', streamName); + appendTweet(t, t.type, streamName); + } else if (my) { + appendTweet(t, 'home', streamName); + appendTweet(t, 'my', streamName); + } else { + appendTweet(t, 'home', streamName); + } + } + } + + if (real) { + let s = + '----------------------------------------\n' + + objectToString(msg).replace(/\x0D\x0A|\x0D|\x0A/g, '\n'); + if (Config.logFile) + Config.logFile.write(s, '>>'); + if (my && Config.myLogFile) + Config.myLogFile.write(s, '>>'); + } + } // }}} + + function makeOnMsg (real, streamName) // {{{ + function (msg, raw) + onMsg(real, msg, raw, streamName); // }}} + + function addCommands () { // {{{ + commands.addUserCommand( + ['tws[idebar'], + 'nosidebar commands', + function (args) { + }, + { + subCommands: [ + new Command( + ['v[anish]'], + 'Vanish matched tweets', + function (args) { + Config.vanish = new RegExp(args.literalArg, 'i'); + }, + { + literal: 0, + completer: function (context, args) { + context.completions = [ + [util.escapeRegex(Config.vanish.source), ''] + ]; + } + } + ), + new Command( + ['k[eyword]'], + 'Show matched tweets in keyword tab', + function (args) { + Config.keyword = new RegExp(args.literalArg, 'i'); + }, + { + literal: 0, + completer: function (context, args) { + context.completions = [ + [util.escapeRegex(Config.keyword.source), ''] + ]; + } + } + ), + new Command( + ['j[ponly]'], + 'Show only Japanese Tweet', + function (args) { + Config.jpOnly = /yes/i.test(args.literalArg); + }, + { + literal: 0, + completer: function (context, args) { + context.completions = [ + ['yes', 'yes'], + ['no', 'no'] + ]; + } + } + ), + new Command( + ['t[ab]'], + 'select tab', + function (args) { + let tabbox = getSidebarWindow().document.getElementById('tw-anekos-sb-tabbox'); + let index = parseInt(args.literalArg, 10); + tabbox.selectedIndex = index; + }, + { + literal: 0, + completer: function (context, args) { + let tabs = getSidebarWindow().document.getElementById('tw-anekos-sb-tabbox').querySelectorAll('tab'); + context.completions = [ + [i + ': ' + tab.getAttribute('label'), tab.getAttribute('label')] + for ([i, tab] in Iterator(Array.slice(tabs))) + ]; + } + } + ) + ] + }, + true + ); + } // }}} + + /********************************************************************************* + * Install + *********************************************************************************/ + + let Store = storage.newMap("twittperator-anekos-sb", {store: true}); + let started = false; + let readyToStart = false; + + let Tweets = __context__.Tweets; + if (!Tweets) + Tweets = __context__.Tweets = Store.get("history", []); + + let added = {}; + + function start () { // {{{ + if (readyToStart) + return; + if (started) + stop(); + + readyToStart = true; + started = true; + setTimeout( + function () { + readyToStart = false; + Tweets.reverse().forEach(makeOnMsg(false)); + plugins.twittperator.ChirpUserStream.addListener(added.chirp = makeOnMsg(true, 'chirp')); + plugins.twittperator.TrackingStream.addListener(added.filter = makeOnMsg(true, 'filter')); + }, + 1000 + ); + } // }}} + + function stop () { // {{{ + if (!started) + return liberator.echoerr('TWAnekoSB has not been started!'); + + plugins.twittperator.ChirpUserStream.removeListener(added.chirp); + plugins.twittperator.TrackingStream.removeListener(added.filter); + Store.set("history", Tweets); + } // }}} + + function makeAudio (path, volume) { // {{{ + let audio = new Audio(path); + // XXX 効いてない + if (volume) + audio.volume = volume; + return audio; + } // }}} + + __context__.onUnload = function() { stop(); }; + + addCommands(); + + return {start: start, stop: stop}; + +})(); + +try { + ANekoSB.start(); +} catch (e) { + window.alert(e); +} diff --git a/twittperator/twsidebar/chrome.manifest b/twittperator/twsidebar/chrome.manifest new file mode 100644 index 0000000..09edd9a --- /dev/null +++ b/twittperator/twsidebar/chrome.manifest @@ -0,0 +1,4 @@ +content twsidebar chrome/content/ +locale twsidebar en-US chrome/locale/en-US/ +skin twsidebar classic/1.0 chrome/skin/ +overlay chrome://browser/content/browser.xul chrome://twsidebar/content/overlay.xul diff --git a/twittperator/twsidebar/chrome/content/overlay.xul b/twittperator/twsidebar/chrome/content/overlay.xul new file mode 100644 index 0000000..59e7a70 --- /dev/null +++ b/twittperator/twsidebar/chrome/content/overlay.xul @@ -0,0 +1,25 @@ +<?xml version="1.0"?> +<overlay id="twitter-sidebar-overlay" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <menupopup id="viewSidebarMenu"> + <menuitem key="key_openEmptySidebar" observes="viewTwitteerSidebar" /> + </menupopup> + + <keyset id="mainKeyset"> + <key id="key_openEmptySidebar" command="viewTwitteerSidebar" + key="E" + modifiers="shift accel" /> + </keyset> + + <broadcasterset id="mainBroadcasterSet"> + <broadcaster id="viewTwitteerSidebar" + label="Twitter Sidebar" + autoCheck="false" + type="checkbox" + group="sidebar" + sidebarurl="chrome://twsidebar/content/twsidebar.xul" + sidebartitle="Twitter Sidebar" + oncommand="toggleSidebar('viewTwitteerSidebar');" /> + </broadcasterset> +</overlay> diff --git a/twittperator/twsidebar/chrome/content/twsidebar.css b/twittperator/twsidebar/chrome/content/twsidebar.css new file mode 100644 index 0000000..b835c3d --- /dev/null +++ b/twittperator/twsidebar/chrome/content/twsidebar.css @@ -0,0 +1,50 @@ +#tw-anekos-sb-tab-panels { + background-color: transparent !important; + border: none !important; + padding: 0 !important; +} + +.tw-anekos-sb-plugin-tweet-panel { + background-color: floralwhite !important; + font-size: %Config.fontSize%px; + padding-top: 2px; + padding-left: 2px; +} + +.tw-anekos-sb-plugin-tweet-DM-my { + background-color: darkred !important; + color: white !important; +} + +.tw-anekos-sb-plugin-tweet-retweet-my { + background-color: aqua !important; +} + +.tw-anekos-sb-plugin-tweet-favorite-my { + background-color: pink !important; +} + +.tw-anekos-sb-plugin-tweet-reply-my { + background-color: yellow !important; +} + +.tw-anekos-sb-plugin-tweet-normal-my { + background-color: yellow !important; +} + +.tw-anekos-sb-plugin-tweet-keyword { + background-color: lightgreen !important; +} + +.tw-anekos-sb-plugin-tweet-filter { + background-color: #66cdaa !important; +} + +.tw-anekos-sb-plugin-tweet-list-member-added-my { + background-color: lightskyblue !important; +} + +.tw-anekos-sb-plugin-tweet-follow, +.tw-anekos-sb-plugin-tweet-follow-my { + background-color: lightsalmon !important; +} diff --git a/twittperator/twsidebar/chrome/content/twsidebar.xul b/twittperator/twsidebar/chrome/content/twsidebar.xul new file mode 100644 index 0000000..69ab965 --- /dev/null +++ b/twittperator/twsidebar/chrome/content/twsidebar.xul @@ -0,0 +1,37 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> +<?xml-stylesheet href="chrome://browser/skin/browser.css" type="text/css"?> +<?xml-stylesheet href="chrome://twsidebar/content/twsidebar.css" type="text/css"?> +<page id="tw-sidebar-page" title="Twitter Sidebar" + onload="window.parent.liberator.modules.TWAnekoSB.start()" + onunload="window.parent.liberator.modules.TWAnekoSB.stop()" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" > + <vbox flex="1"> + <tabbox id="tw-anekos-sb-tabbox" flex="1"> + <tabs> + <tab label="Home"/> + <tab label="My"/> + <tab label="Keyword"/> + <tab label="Filter"/> + <tab label="Debug"/> + </tabs> + <tabpanels flex="1" id="tw-anekos-sb-tab-panels" style="background: transparent;"> + <tabpanel flex="1"> + <richlistbox id="tw-anekos-sb-home-list" contextmenu="contentAreaContextMenu" flex="1"/> + </tabpanel> + <tabpanel flex="1"> + <richlistbox id="tw-anekos-sb-my-list" contextmenu="contentAreaContextMenu" flex="1"/> + </tabpanel> + <tabpanel flex="1"> + <richlistbox id="tw-anekos-sb-keyword-list" contextmenu="contentAreaContextMenu" flex="1"/> + </tabpanel> + <tabpanel flex="1"> + <richlistbox id="tw-anekos-sb-filter-list" contextmenu="contentAreaContextMenu" flex="1"/> + </tabpanel> + <tabpanel flex="1"> + <richlistbox id="tw-anekos-sb-debug-list" contextmenu="contentAreaContextMenu" flex="1"/> + </tabpanel> + </tabpanels> + </tabbox> + </vbox> +</page> diff --git a/twittperator/twsidebar/chrome/locale/en-US/nosidebar.dtd b/twittperator/twsidebar/chrome/locale/en-US/nosidebar.dtd new file mode 100644 index 0000000..18f4ba8 --- /dev/null +++ b/twittperator/twsidebar/chrome/locale/en-US/nosidebar.dtd @@ -0,0 +1,3 @@ +<!ENTITY emptysidebar.title "EmptySidebar"> +<!ENTITY openEmptySidebar.commandkey "E"> +<!ENTITY openEmptySidebar.modifierskey "shift accel">
\ No newline at end of file diff --git a/twittperator/twsidebar/install.rdf b/twittperator/twsidebar/install.rdf new file mode 100644 index 0000000..f56b72a --- /dev/null +++ b/twittperator/twsidebar/install.rdf @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:em="http://www.mozilla.org/2004/em-rdf#"> + <Description about="urn:mozilla:install-manifest"> + <em:id>twsidebar@snca.net</em:id> + <em:name>Twitter Sidebar</em:name> + <em:version>1.0</em:version> + <em:creator>anekos</em:creator> + <em:description>Twitter Sidebar for Twittperator Plugin</em:description> + <em:targetApplication> + <Description> + <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id> <!-- firefox --> + <em:minVersion>3.6</em:minVersion> + <em:maxVersion>10.0a1</em:maxVersion> + </Description> + </em:targetApplication> + </Description> +</RDF> diff --git a/twopen.js b/twopen.js new file mode 100644 index 0000000..04637f9 --- /dev/null +++ b/twopen.js @@ -0,0 +1,190 @@ +/* NEW BSD LICENSE {{{ +Copyright (c) 2011, Jagua. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. +3. The names of the authors may not be used to endorse or promote products +derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. + + +################################################################################### +# http://sourceforge.jp/projects/opensource/wiki/licenses%2Fnew_BSD_license # +# に参考になる日本語訳がありますが、有効なのは上記英文となります。 # +################################################################################### + +}}} */ + +// PLUGIN_INFO {{{ +let PLUGIN_INFO = +<VimperatorPlugin> + <name>twopen</name> + <description>open pages relative to the twitter id</description> + <version>1.0.0</version> + <author homepage="https://github.com/Jagua">Jagua</author> + <license>new BSD License (Please read the source code comments of this plugin)</license> + <license lang="ja">修正BSDライセンス (ソースコードのコメントを参照してください)</license> + <updateURL>https://github.com/vimpr/vimperator-plugins/raw/master/twopen.js</updateURL> + <minVersion>2.3</minVersion> + <maxVersion>3.1</maxVersion> + <detail><![CDATA[ + == Command == + :twopen service[!] @twitterId + + + == Setting == + add the following setting to your ".vimperatorrc". + you type ':twopen favstar @twitterId' and enter, + then open 'http://favstar.fm/users/twitterId'. + + javascript <<EOM + liberator.globalVariables.twopen_site_definition = [{ + name: ['favstar'], + url: 'http://favstar.fm/users/%ID%', + }]; + EOM + + + ]]></detail> + <detail lang="ja"><![CDATA[ + twitter ID に紐付けされた関連サイトを開く. + 関連サイトは予め .vimperatorrc に登録しておくこと. + (twitpic のみ予め登録せずとも使える) + + (1) twitter ID が @twitterId の twitpic をカレントタブに開きたい場合 + :twopen twitpic @twitterId + + (2) 同じく新規タブに開きたい場合 + :twopen twitpic! @twitterId + + + == Command == + :twopen service[!] @twitterId + + + == Setting == + .vimperatorrc に設定書いて任意のサイトをどんどん追加できます. + + javascript <<EOM + liberator.globalVariables.twopen_site_definition = [{ + name: ['twilog'], + url: 'http://twilog.org/%ID%', + }]; + EOM + + として twilog を登録すると, + :twopen twilog @twitterId + でカレントタブに @twitterId の twilog ページをオープンする. + :twopen twilog! @twitterId + で新規タブにオープンする. + ちなみに twilog の部分は全部打たなくても Vimperator の補完で表れるようになっている. + + さらに .vimperatorrc に + cabbr -j .id if(content.document.querySelector('.tweet-user-block-screen-name')){content.document.querySelector('.tweet-user-block-screen-name').textContent.trim()}else{content.document.querySelector('.screen-name').textContent.trim()} + と書いておくと,@twitterId の ホーム or ステータス表示時に + :twopen twitpic .id<space> + で勝手に .id の部分を twitter ID に置換入力してくれて便利.<space> は C-] でも代用可. + + + == Todo == + Twittperator と連携できたら素敵でしょうか. + + + ]]></detail> +</VimperatorPlugin>; +// }}} + +(function () { + + const SITE_DEFINITION = [{ + name: ['twitpic'], + url: 'http://twitpic.com/photos/%ID%', + /* + },{ + name: ['twilog'], + url: 'http://twilog.org/%ID%', + },{ + name: ['twaudio'], + url: 'http://twaud.io/users/%ID%', + },{ + name: ['twitvideojp'], + url: 'http://twitvideo.jp/contents/lists/%ID%', + },{ + name: ['twipla'], + url: 'http://twipla.jp/users/%ID%', + },{ + name: ['favotter'], + url: 'http://favotter.net/user/%ID%', + },{ + name: ['favstar'], + url: 'http://favstar.fm/users/%ID%', + },{ + name: ['togetter'], + url: 'http://togetter.com/id/%ID%', + },{ + name: [''], + url: '', + */ + }]; + + let (siteDef = liberator.globalVariables.twopen_site_definition) { + if (siteDef) { + if (siteDef instanceof String) + siteDef = eval(siteDef); + if (siteDef.forEach instanceof Function) + siteDef.forEach(function (obj) SITE_DEFINITION.push(obj)); + else + SITE_DEFINITION.push(siteDef); + } + } + + MainSubCommands = []; + SITE_DEFINITION.forEach(function (def) { + MainSubCommands.push(new Command( + def.name, + def.name[0], + function (args) { + if (args.literalArg.trim().match(/^@([_0-9a-zA-Z]+)$/)) { + liberator.open(def.url.replace(/%ID%/, RegExp.$1), + (args.bang ? liberator.NEW_TAB : liberator.CURRENT_TAB)); + } else { + throw new Error('illegal twitter id error : ' + args.literalArg); + } + },{ + literal: 0, + bang: true + } + )); + }); + + commands.addUserCommand( + ['two[pen]'], + 'open pages relative to the twitter id', + function () { + liberator.echo('(Help) :twopen service[!] @twitterId'); + }, + { + subCommands: MainSubCommands, + }, + true + ); +})(); + +// vim:sw=2 ts=2 et si fdm=marker: @@ -1,5 +1,5 @@ /* {{{ -Copyright (c) 2008, anekos. +Copyright (c) 2008-2011, anekos. All rights reserved. Redistribution and use in source and binary forms, with or without modification, @@ -138,9 +138,9 @@ let PLUGIN_INFO = liberator.echo(<pre>{msg}</pre>); } - let resultBuffer = ''; - function kawase (value, clipboard, from, to) { + let resultBuffer = ''; + [from, to] = [from || defaultSource, to || defaultTarget].map(function (it) it.toUpperCase()); if (from == '-') from = defaultSource; @@ -171,9 +171,17 @@ let PLUGIN_INFO = req.send(null); } + function evalValue (value) { + let sandbox = new Cu.Sandbox('about:blank'); + return Cu.evalInSandbox(value, sandbox); + } + let extra = { argCount: '+', bang: true, + options: [ + [['-clipboard', '-c'], commands.OPTION_NOARG], + ], completer: function (context, args) { if (args.length == 1) { // TODO - history @@ -190,22 +198,18 @@ let PLUGIN_INFO = ['kawase'], 'Umihara Kawase Meow', function (args) { - let as = args; - resultBuffer = ''; liberator.echo('<<Results>>\n') - if (as.length == 0) - as.push('1'); - while (as.length < 3) - as.push('-'); - for (let i = 1, l = as.length - 1; i < l; i++) { - let [value, from, to] = [as[0], as[i], l == i ? '-' : as[l]]; - liberator.log({ - value: value, - from: from, - to: to - }) - value = eval(value); - kawase(value, args.bang, from, to); + + if (args.length == 0) + args.push('1'); + + while (args.length < 3) + args.push('-'); + + for (let i = 1, l = args.length - 1; i < l; i++) { + let [value, from, to] = [args[0], args[i], l == i ? '-' : args[l]]; + value = evalValue(value); + kawase(value, args['-clipboard'] || args.bang, from, to); } }, extra, diff --git a/unload-tab.js b/unload-tab.js new file mode 100644 index 0000000..951c2b9 --- /dev/null +++ b/unload-tab.js @@ -0,0 +1,66 @@ +/* + * タブを削除せずに、セッションを残しつつコンテンツをアンロードさせるエコなコマンド + * unload[tab] num + */ + +var INFO = +<plugin name="unloadTab" + version="0.2" + summary="Unload tab contents like (BarTab)" + xmlns="http://vimperator.org/namespaces/liberator"> + <author email="teramako@gmail.com">teramako</author> + <license>MPL 1.1/GPL 2.0/LGPL 2.1</license> + <project name="Vimperator" minVersion="3.1"/> + <item> + <tags>:unloadtab :unload</tags> + <spec>:unload<oa>tab</oa> <a>tabNumber</a></spec> + <description> + <p>Unload the tab contents.</p> + </description> + </item> +</plugin>; + +if (!("SS" in this)) { + XPCOMUtils.defineLazyServiceGetter(this, "SS", "@mozilla.org/browser/sessionstore;1", "nsISessionStore"); +} + +function unloadTab (aTab) { + var state = SS.getTabState(aTab); + var tab = gBrowser.addTab(null, { skipAnimation: true }); + SS.setTabState(tab, state); + if (aTab.pinned) { + gBrowser.pinTab(tab); + } else { + let objState = JSON.parse(state); + if (objState.hidden) { + gBrowser.hideTab(tab); + TabView.moveTabTo(tab, JSON.parse(objState.extData["tabview-tab"]).groupID); + } + } + gBrowser.moveTabTo(tab, aTab._tPos + 1) + gBrowser.removeTab(aTab); +} + +commands.addUserCommand(["unload[tab]"], "Unload Tabs", + function action (args) { + var str = args[0]; + var m = str.match(/^(\d+):?/); + if (!m) + return; + + var tab = gBrowser.tabs[m[1]]; + if (tab && !tab.selected && !tab.linkedBrowser.__SS_restoreState) + unloadTab(tab); + }, { + literal: 0, + completer: function (context, args) { + context.anchored = false; + context.completions = [ + [tab._tPos + ": " + tab.label, tab.linkedBrowser.currentURI.spec] + for each(tab in Array.slice(gBrowser.tabs)) + if (!tab.selected && !tab.linkedBrowser.__SS_restoreState) + ]; + } + }, true); + + @@ -35,7 +35,7 @@ THE POSSIBILITY OF SUCH DAMAGE. // INFO {{{ let INFO = <> - <plugin name="usi.js" version="1.2.2" + <plugin name="usi.js" version="1.3.1" href="http://vimpr.github.com/" summary="for Remember The Milk." lang="en-US" @@ -62,6 +62,29 @@ let INFO = // }}} + // Combo {{{ + function Combo (block) { + return function () { + let it; + let c = { + next: function (result) { + setTimeout(function () { + if (!it) + return; + try { + c.result = result; + it.send(result); + } catch (e if e instanceof StopIteration) {} + }, 0); + }, + result: void 0 + }; + it = block(c, arguments); + it.next(); + }; + } + // }}} + // Cache {{{ CacheAge = 10 * 1000 * 60; @@ -140,14 +163,17 @@ let INFO = // }}} const Utils = { // {{{ - httpGet: function (url, onComplete) { + // TODO エラー処理 + httpGet: function (url, onComplete, synchronize) { let xhr = new XMLHttpRequest(); xhr.open('GET', url, !!onComplete); xhr.onreadystatechange = function () { - if (xhr.readyState === 4 && xhr.status == 200) + if (xhr.readyState === 4 && xhr.status == 200 && !synchronize) return onComplete(xhr); }; xhr.send(); + if (synchronize) + return onComplete(xhr); }, md5: function (str) { @@ -256,7 +282,7 @@ let INFO = }; // }}} const Cow = { // {{{ - get: function (_params, {onComplete, onFail, cache, timeline, pre}) { // {{{ + get: function (_params, {onComplete, onFail, cache, timeline, pre, synchronize}) { // {{{ function toResult (text) (new XMLList(text)); @@ -304,6 +330,7 @@ let INFO = method: 'rtm.timelines.create', }, { + synchronize: synchronize, onComplete: function (result) { let timeline = result.timeline; Save.set('timeline', timeline); @@ -319,7 +346,7 @@ let INFO = let url = Cow.makeURL(params); Utils.log('Get from remote: ' + url); - Utils.httpGet( + return Utils.httpGet( url, function (xhr) { let text = xhr.responseText.replace(/^<\?[^\?]+\?>/, ''); @@ -331,7 +358,8 @@ let INFO = } else { onFail(result); } - } + }, + synchronize ); }, // }}} @@ -422,6 +450,35 @@ let INFO = } //}}} }; // }}} + const CommandOptions = { // {{{ + lists: [ + ['-lists', '-l'], + commands.OPTION_LIST, + null, + function (context, args) { + return Cow.get( + { + method: 'rtm.lists.getList', + }, + { + synchronize: true, + cache: 'lists.getList', + onComplete: function (result) { + let [, prefix] = context.filter.match(/^(.*,)[^,]*$/) || []; + if (prefix) + context.advance(prefix.length); + return [ + [v.@name, v.@id] + for ([k, v] in Iterator(result.lists.list)) + ]; + } + } + ); + } + ] + }; + // }}} + // Command maker {{{ function TaskActionOnComplete (text) { @@ -436,7 +493,7 @@ let INFO = } } - function SelectorCommand ({names, cache, description, action, onComplete, timeline, completionMethod, completionList}) { // {{{ + function SelectorCommand ({names, cache, description, action, onComplete, timeline, completionMethod, completionList, completer}) { // {{{ let ccKey = names + ':' + Utils.md5(Error().stack); return new Command( names instanceof Array ? names : [names], @@ -455,7 +512,7 @@ let INFO = }, { literal: 0, - completer: function (context, args){ + completer: completer || function (context, args){ context.incomplete = true; Cow.get( completionMethod, @@ -473,44 +530,93 @@ let INFO = } // }}} function TaskSelectorCommand ({key, method, filter, cache, names, description, onComplete}) { // {{{ - return SelectorCommand({ - key: key, - names: names, - description: description, - cache: cache || 'rtm.tasks.getList?filter=status:incomplete', - timeline: true, - action: function ([list, taskseries, task]) { - return { + let ccKey = names + ':' + Utils.md5(Error().stack); + if (!cache) + cache = 'rtm.tasks.getList?filter=status:incomplete'; + return new Command( + names instanceof Array ? names : [names], + description, + function (args) { + Cow.get( + let ([list, taskseries, task] = CompletionCache.get(ccKey, args)) ({ method: 'rtm.' + key, list_id: list.@id, taskseries_id: taskseries.@id, task_id: task.@id - }; - }, - onComplete: onComplete, - completionMethod: { - method: 'rtm.tasks.getList', - filter: filter || 'status:incomplete' - }, - completionList: function (result) { - let cs = []; - let n = new Date().getTime(); - for (let [, list] in Iterator(result.tasks.list)) { - for (let [, taskseries] in Iterator(list.taskseries)) { - for (let [, task] in Iterator(taskseries.task)) { - cs.push(let (d = Utils.toDate(task.@due)) [ - (d ? d.getTime() : Infinity), - [taskseries.@name, Utils.toSmartDateText(task.@due), [list, taskseries, task], {warn: d < n}] - ]); + }), + { + timeline: true, + onComplete: onComplete + } + ); + CompletionCache.remove(ccKey); + if (typeof cache === 'string') + Cache.remove(cache); + }, + { + literal: 0, + options: [CommandOptions.lists], + completer: Combo(function (c, [context, args]) { + context.incomplete = false; + + Cow.get( + { + method: 'rtm.lists.getList', + }, + { + cache: 'lists.getList', + onComplete: function (result) { + let table = {name2id: {}, id2name: {}}; + for (let [k, v] in Iterator(result.lists.list)) { + table.name2id[v.@name] = v.@id; + table.id2name[v.@id] = v.@name; + } + c.next(table); } } - } + ); - // 現在に近い順に並べます - Utils.timeArraySort(cs); - return cs.map(function ([a, b]) b); - } - }); + let table = yield; + + let lists = args['-lists'] || []; + + context.filter = + context.filter.replace( + /\s*#(\S+)\s*/g, + function (m, name) (table.name2id[name] ? (lists.push(name), '') : m) + ); + + Cow.get({ + method: 'rtm.tasks.getList', + filter: filter || 'status:incomplete' + }, + { + cache: cache, + onComplete: function (result) { + let cs = []; + let n = new Date().getTime(); + for (let [, list] in Iterator(result.tasks.list)) { + if (lists.length && lists.every(function (name) table.id2name[list.@id] != name)) + continue; + for (let [, taskseries] in Iterator(list.taskseries)) { + for (let [, task] in Iterator(taskseries.task)) { + cs.push(let (d = Utils.toDate(task.@due)) [ + (d ? d.getTime() : Infinity), + [taskseries.@name, Utils.toSmartDateText(task.@due), [list, taskseries, task], {warn: d < n}] + ]); + } + } + } + + // 現在に近い順に並べます + Utils.timeArraySort(cs); + CompletionCache.complete(ccKey, context, args, cs.map(function ([a, b]) b)); + } + } + ) + }) + } + ); } // }}} // }}} @@ -594,7 +700,7 @@ let INFO = // FIXME http が補完できない let left = args.string.slice(0, context.caret); - let m = /(?:^|\s)([#!@=*^]|http)([^#!@=*^]*)$/(left); + let m = /(?:^|\s)([#!@=*^]|http)([^#!@=*^]*)$/.exec(left); if (m) { let completer = SmartAddCompleter[m[1]]; if (completer) { @@ -684,7 +790,24 @@ let INFO = new Command( ['t[ask]'], 'Task control', - function (args) { + Combo(function (c, [args]) { + Cow.get( + { + method: 'rtm.lists.getList', + }, + { + cache: 'lists.getList', + onComplete: function (result) { + let table = {}; + for (let [k, v] in Iterator(result.lists.list)) + table[v.@id] = v.@name; + c.next(table); + } + } + ); + + let table = yield; + Cow.get( { method: 'rtm.tasks.getList', @@ -694,8 +817,11 @@ let INFO = cache: 'rtm.tasks.getList?filter=status:incomplete', onComplete: function (result) { let cs = []; + let lists = args['-lists']; for (let [, list] in Iterator(result.tasks.list)) { + if (lists && lists.every(function (name) table[list.@id] != name)) + continue; for (let [, taskseries] in Iterator(list.taskseries)) { for (let [, task] in Iterator(taskseries.task)) { cs.push([ @@ -710,7 +836,6 @@ let INFO = Utils.timeArraySort(cs); let contents = <></>; for (let [, [d, [a, b]]] in Iterator(cs)) { - liberator.log(d + ', ' + b); let hl = (n - d) > 0 ? 'ErrorMsg' : ''; contents += <tr highlight={hl}><td>{a}</td><td>{b}</td></tr>; } @@ -718,8 +843,9 @@ let INFO = } } ); - }, + }), { + options: [CommandOptions.lists], subCommands: TaskSubCommands } ), diff --git a/video-controller.js b/video-controller.js index 3efbc1c..8fa22c1 100644 --- a/video-controller.js +++ b/video-controller.js @@ -1,5 +1,5 @@ /* NEW BSD LICENSE {{{ -Copyright (c) 2010, anekos. +Copyright (c) 2010-2011, anekos. All rights reserved. Redistribution and use in source and binary forms, with or without modification, @@ -33,38 +33,17 @@ THE POSSIBILITY OF SUCH DAMAGE. }}} */ // PLUGIN_INFO {{{ -let PLUGIN_INFO = -<VimperatorPlugin> - <name>VideoController</name> - <name lang="ja">VideoController</name> - <description>Add :videocontrol command for HTML5 video.</description> - <description lang="ja">HTML5 Video のために :videocontrol コマンドを追加する。</description> - <version>1.1.0</version> - <author mail="anekos@snca.net" homepage="http://d.hatena.ne.jp/nokturnalmortum/">anekos</author> - <license>new BSD License (Please read the source code comments of this plugin)</license> - <license lang="ja">修正BSDライセンス (ソースコードのコメントを参照してください)</license> - <updateURL>https://github.com/vimpr/vimperator-plugins/raw/master/video-controller.js</updateURL> - <minVersion>2.3</minVersion> - <maxVersion>2.3</maxVersion> - <detail><![CDATA[ - ---- - ]]></detail> - <detail lang="ja"><![CDATA[ - ---- - ]]></detail> -</VimperatorPlugin>; -// }}} // INFO {{{ let INFO = <> - <plugin name="VideoController" version="1.0.0" - href="http://svn.coderepos.org/share/lang/javascript/vimperator-plugins/trunk/video-controller.js" + <plugin name="VideoController" version="1.1.1" + href="http://github.com/vimpr/vimperator-plugins/blob/master/video-controller.js" summary="Control HTML5 Videos" lang="en-US" xmlns="http://vimperator.org/namespaces/liberator"> <author email="anekos@snca.net">anekos</author> <license>New BSD License</license> - <project name="Vimperator" minVersion="2.3"/> + <project name="Vimperator" minVersion="3.0"/> <item> <tags>:videocontrol</tags> <spec>:videocontrol <a>command</a> <oa>arguments...</oa></spec> @@ -74,14 +53,14 @@ let INFO = </description> </item> </plugin> - <plugin name="VideoController" version="1.0.0" - href="http://svn.coderepos.org/share/lang/javascript/vimperator-plugins/trunk/video-controller.js" + <plugin name="VideoController" version="1.1.1" + href="http://github.com/vimpr/vimperator-plugins/blob/master/video-controller.js" summary="Control HTML5 Videos" lang="ja" xmlns="http://vimperator.org/namespaces/liberator"> <author email="anekos@snca.net">anekos</author> <license>New BSD License</license> - <project name="Vimperator" minVersion="2.3"/> + <project name="Vimperator" minVersion="3.0"/> <item> <tags>:videocontrol</tags> <spec>:videocontrol <a>command</a> <oa>arguments...</oa></spec> @@ -105,6 +84,7 @@ let INFO = } let lastArgs = null; + let lastCommand = null; let controlls = { __proto__: null, play: function (elem) { @@ -126,7 +106,7 @@ let INFO = HintName, 'Select video', function (elem) { - controlls[lastArgs[0]].apply(null, [elem].concat(lastArgs.slice(1))); + controlls[lastCommand].apply(null, [elem].concat(lastArgs)); }, function () '//video' ); @@ -135,15 +115,23 @@ let INFO = ['videocontrol'], 'Control HTML5 Videos', function (args) { - lastArgs = args; - hints.show(HintName); }, { - completer: function (context, args) { - const completions = [[n, n] for (n in controlls)]; - context.title = ['Command', '']; - context.completions = completions; - } + subCommands: [ + let (o = o) new Command( + [o[0] + '[' + o.slice(1) + ']'], + o + ' <video>', + function (args) { + lastCommand = o; + lastArgs = args; + hints.show(HintName); + }, + { + literal: 0 + } + ) + for (o in controlls) + ], }, true ); diff --git a/vimp_to_android_phone.js b/vimp_to_android_phone.js index 3b4821a..f428f59 100644 --- a/vimp_to_android_phone.js +++ b/vimp_to_android_phone.js @@ -32,7 +32,7 @@ var sendToPhone = function(requestURL) { 'Content-Type': 'application/x-www-form-urlencoded',
'X-Extension': 'true'
});
- req.addEventListener('onSuccess', function(res) {
+ req.addEventListener('success', function(res) {
var body = res.responseText;
if (body.substring(0, 2) == 'OK') {
liberator.echo('Send to phone successed.');
@@ -44,7 +44,7 @@ var sendToPhone = function(requestURL) { liberator.open(signOutURL, liberator.NEW_TAB);
}
});
- req.addEventListener('onFailure', function(res) {
+ req.addEventListener('failure', function(res) {
liberator.echoerr('Send to phone failed.');
});
req.get();
diff --git a/walk-input.js b/walk-input.js index f788746..80cef02 100644 --- a/walk-input.js +++ b/walk-input.js @@ -1,8 +1,8 @@ // Vimperator plugin: 'Walk Input' -// Last Change: 2009-01-25 // License: BSD -// Version: 1.1 +// Version: 1.2.3 // Maintainer: Takayama Fumihiko <tekezo@pqrs.org> +// anekos <anekos@snca.net> // ------------------------------------------------------------ // The focus walks <input> & <textarea> elements. @@ -20,11 +20,12 @@ // PLUGIN_INFO {{{ let INFO = -<plugin name="Walk-Input" version="1.2" - href="http://svn.coderepos.org/share/lang/javascript/vimperator-plugins/trunk/walk-input.js" +<plugin name="Walk-Input" version="1.2.3" + href="http://github.com/vimpr/vimperator-plugins/blob/master/walk-input.js" summary="The focus walks 'input' and 'textarea' element." xmlns="http://vimperator.org/namespaces/liberator"> <author email="tekezo@pqrs.org">Takayama Fumihiko</author> + <author email="anekos@snca.net">anekos</author> <license>BSD</license> <project name="Vimperator" minVersion="2.2"/> <p> @@ -65,9 +66,8 @@ let INFO = var types = [ "text", - "passsword", + "password", "search", - "file", "datetime", "datetime-local", "date", @@ -81,7 +81,16 @@ var types = [ "tel", "color", ].map(function(type) "@type=" + type.quote()).join(" or "); -var xpath = '//input[' + types + ' or not(@type)] | //textarea'; +var xpath = '//input[(' + types + ' or not(@type)) and not(@disabled)] | //textarea'; + +function isVisible (elem) { + while (elem && !(elem instanceof HTMLDocument)) { + if (/^none$/i.test(getComputedStyle(elem, '').display)) + return false; + elem = elem.parentNode; + } + return true; +} var walkinput = function (forward) { var focused = document.commandDispatcher.focusedElement; @@ -96,7 +105,7 @@ var walkinput = function (forward) { let r = doc.evaluate(xpath, doc, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); for (let i = 0, l = r.snapshotLength; i < l; ++i) { let e = r.snapshotItem(i); - if (/^none$/i.test(getComputedStyle(e, '').display)) + if (!isVisible(e)) continue; let ef = {element: e, frame: frame}; list.push(ef); diff --git a/win-mouse.js b/win-mouse.js index 12fa8fa..285b838 100644 --- a/win-mouse.js +++ b/win-mouse.js @@ -35,7 +35,7 @@ THE POSSIBILITY OF SUCH DAMAGE. // INFO {{{ let INFO = <> - <plugin name="Win Cursor" version="1.3.1" + <plugin name="Win Cursor" version="1.3.2" href="http://vimpr.github.com/" summary="Cursor control plugin for MS Windows" lang="en-US" @@ -109,6 +109,15 @@ let INFO = </p> </description> </item> + <item> + <tags>g:win_mouse_release_delay</tags> + <spec>g:win_mouse_release_delay = <a>msec</a></spec> + <description> + <p> + <a>msec</a> milliseconds after clicking, release modifier keys. + </p> + </description> + </item> </plugin> </>; // }}} @@ -447,7 +456,7 @@ let INFO = } SendInput(relKeys.length, ClickInput.address(), MouseInput.size) }, - 50 + liberator.globalVariables.win_mouse_release_delay || 5 ); } } @@ -59,8 +59,8 @@ let PLUGIN_INFO = // INFO {{{ let INFO = <> - <plugin name="X-Hint" version="1.1.2" - href="http://svn.coderepos.org/share/lang/javascript/vimperator-plugins/trunk/x-hint.js" + <plugin name="X-Hint" version="1.1.3" + href="http://github.com/vimpr/vimperator-plugins/blob/master/x-hint.js" summary="Show the hints with given XPath." lang="en-US" xmlns="http://vimperator.org/namespaces/liberator"> @@ -91,8 +91,8 @@ let INFO = </description> </item> </plugin> - <plugin name="X-Hint" version="1.1.2" - href="http://svn.coderepos.org/share/lang/javascript/vimperator-plugins/trunk/x-hint.js" + <plugin name="X-Hint" version="1.1.3" + href="http://github.com/vimpr/vimperator-plugins/blob/master/x-hint.js" summary="Show the hints with given XPath." lang="ja" xmlns="http://vimperator.org/namespaces/liberator"> @@ -135,6 +135,12 @@ let INFO = function xpath () (last.xpath || '//a') + function restore () { + if (last.hintMode) + last.hintMode.tags = last.hintTags; + last = {}; + } + plugins.libly.$U.around( hints, 'show', @@ -146,7 +152,13 @@ let INFO = // override last.hintMode.tags = xpath; } - return next(); + try { + return next(); + } catch (e) { + restore(); + liberator.log('x-hint: restore tags for error'); + liberator.log(e); + } }, true ); @@ -155,9 +167,7 @@ let INFO = hints, 'hide', function (next, [minor, filter, win]) { - if (last.hintMode) - last.hintMode.tags = last.hintTags; - last = {}; + restore(); return next(); }, true diff --git a/youtubeamp.js b/youtubeamp.js index 21bd18f..1ab8833 100644 --- a/youtubeamp.js +++ b/youtubeamp.js @@ -42,8 +42,7 @@ (function() { -Function.prototype.bind = function(object) { - var __method = this; +let bind = function(__method, object) { return function() { return __method.apply(object, arguments); }; @@ -56,7 +55,7 @@ function YouTubePlayerController() { } YouTubePlayerController.prototype = { initialize: function() { - this.fuller = this._changeToFull.bind(this); + this.fuller = bind(this._changeToFull, this); }, constants: { diff --git a/zip-de-download.js b/zip-de-download.js index a224011..9e0de28 100644 --- a/zip-de-download.js +++ b/zip-de-download.js @@ -1,5 +1,5 @@ let INFO = -<plugin name="zip-de-download" version="0.7.0" +<plugin name="zip-de-download" version="0.7.1" href="" summary="ZIPでダウンロードするお" xmlns="http://vimperator.org/namespaces/liberator"> @@ -176,7 +176,7 @@ let SITE_INFO = [ // 連番かもしれない id は無視する let id = elem.getAttribute('id'); - if (id && !/\d/(id)) + if (id && !/\d/.test(id)) return 'id("' + id + '")'; return getXPath(elem.parentNode) + '/' + elem.tagName.toLowerCase(); @@ -186,7 +186,7 @@ let SITE_INFO = [ let links = Array.slice( content.document.querySelectorAll('a')).filter( - function (link) (link.href && extPattern(link.href))); + function (link) (link.href && extPattern.test(link.href))); let xs = {}; for each(let link in links){ @@ -205,12 +205,12 @@ let SITE_INFO = [ return result; } function extensionValidator(vs) - vs && vs.every(function (v) /^[\da-zA-Z]+$/(v)); + vs && vs.every(function (v) /^[\da-zA-Z]+$/.test(v)); let self = { downloadZip: function(path, urls, comment, isAppend){ let zipW = new zipWriter(); - let urls = [url for each(url in urls)]; + urls = [url for each(url in urls)]; liberator.assert(urls.length > 0, "None of URLs"); if (!(/\.zip$/i).test(path)){ @@ -232,7 +232,7 @@ let SITE_INFO = [ try { let stream = ch.open(); let entryName = ("000" + ++i).slice(-3) + "-" + getEntryName(ch.URI, ch.contentType); - liberator.echomsg("zip: " + url + " to " + entryName, 3); + liberator.echomsg("zip: " + url + " to " + entryName, commandline.FORCE_SINGLELINE); zipW.addEntryStream(entryName, Date.now() * 1000, Ci.nsIZipWriter.COMPRESSION_DEFAULT, stream, false); } catch (e) { // XXX エラー分を通知すべき? @@ -332,8 +332,10 @@ let SITE_INFO = [ return; } liberator.echo("Started DownloadZip"); - let zipFile = self.download(arg[0], false, option); - liberator.echo("Completed DownloadZip: " + zipFile.path); + setTimeout(function () { + let zipFile = self.download(arg[0], false, option); + liberator.echo("Completed DownloadZip: " + zipFile.path); + }, 0); }, { argCount: "?", literal: true, diff --git a/zoom-em-all.js b/zoom-em-all.js index 923aecf..3479bcc 100644 --- a/zoom-em-all.js +++ b/zoom-em-all.js @@ -58,7 +58,7 @@ let PLUGIN_INFO = let INFO = <> <plugin name="ZoomEmAll" version="1.1.0" - href="http://svn.coderepos.org/share/lang/javascript/vimperator-plugins/trunk/zoom-em-all.js" + href="http://github.com/vimpr/vimperator-plugins/blob/master/zoom-em-all.js" summary="Zoom or pan for whole firefox." lang="en-US" xmlns="http://vimperator.org/namespaces/liberator"> @@ -77,7 +77,7 @@ let INFO = </item> </plugin> <plugin name="ZoomEmAll" version="1.1.0" - href="http://svn.coderepos.org/share/lang/javascript/vimperator-plugins/trunk/zoom-em-all.js" + href="http://github.com/vimpr/vimperator-plugins/blob/master/zoom-em-all.js" summary="ブラウザ全体をズーム" lang="ja" xmlns="http://vimperator.org/namespaces/liberator"> @@ -35,7 +35,7 @@ THE POSSIBILITY OF SUCH DAMAGE. // INFO {{{ let INFO = <> - <plugin name="跳.jp" version="1.0.1" + <plugin name="跳.jp" version="1.0.2" href="http://vimpr.github.com/" summary="跳ねます" lang="ja" @@ -84,7 +84,7 @@ let INFO = } ); req.post(); - return toResult(req.transport.responseText); + return !callback && toResult(req.transport.responseText); } let innantoka = atob('aW5zdWxpbixpbmJhZ3MsaW5wYXJhenpvLGlucHVtb25pbixpbnRlbCxpbmNhdGVpa29rdSxpbmZyYXN0cnVjdHVyZSxpbmZsdWVuemEsaW5kb2NoaW5lLGltcHJlc3MsaW5kcmEsaW52ZXJ0ZXIsaW5kaWFuYXBvbGlzLGltcGhhbCxpbnRlcnByZXRlcixpbmRvc2hpbmFoYW5udG91LHlpbmxpbmdvZmpveXRveSxpbXBlZGFuY2UsaW5nZW5tYW1lLGludGVycGhvbmUsaW5kb2xlLGludGVybixpbXBhbGE=').split(',') |