aboutsummaryrefslogtreecommitdiffstats
path: root/hatenaStar.js
blob: 1d07ac15d901c5be50e2810f1740ac3035f056d3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
var PLUGIN_INFO =
<VimperatorPlugin>
	<name>{NAME}</name>
	<description>Add Hatena Star.</description>
	<description lang="ja">はてなスターをつける</description>
	<author mail="mattn.jp@gmail.com">mattn</author>
	<version>0.1.3</version>
	<minVersion>2.3pre</minVersion>
	<maxVersion>2.3pre</maxVersion>
	<updateURL>http://svn.coderepos.org/share/lang/javascript/vimperator-plugins/trunk/hatenaStar.js</updateURL>
</VimperatorPlugin>;
(function() {

const Cc = Components.classes;
const Ci = Components.interfaces;
const StarXPath = './/img[contains(concat(" ", @class, " "), " hatena-star-add-button ")]';
var flasher = null;

var nmap = (liberator.globalVariables.hatena_star_mappings || ',?s').split(/\s+/);
var hmap = liberator.globalVariables.hatena_star_hint_mapping || 'h';
var hmax = function () parseInt(liberator.globalVariables.hatena_star_hint_max || '10', 10);
var hinterval = function () parseInt(liberator.globalVariables.hatena_star_interval || '100', 10);

function getFlasher() {
	if (!flasher) {
		flasher = Cc['@mozilla.org/inspector/flasher;1'].createInstance(Ci.inIFlasher);
		flasher.color = '#FF0000';
		flasher.thickness = 2;
	}
	return flasher;
}

function blink(aNode) {
	if (!aNode) {
		liberator.echoerr('Hatena Star not found');
		return;
	}
	if (aNode.nodeType == 3) aNode = aNode.parentNode;
	var toggle = true;
	var flasher = getFlasher();
	for (let i=1; i<7; ++i) {
		setTimeout(function() {
			if (toggle) flasher.drawElementOutline(aNode);
			else        flasher.repaintElement(aNode);
			toggle = !toggle;
		}, i * 100);
	}
}

function addHatenaStar (elem) {
	var e = document.createEvent('MouseEvents');
	// "Hatena.Star.AddButton.selectedText" is initialized on mouseover.
	e.initMouseEvent('mouseover', true, true, window, 1, 10, 50, 10, 50, 0, 0, 0, 0, 1, elem);
	elem.dispatchEvent(e);
	e.initMouseEvent('click', true, true, window, 1, 10, 50, 10, 50, 0, 0, 0, 0, 1, elem);
	elem.dispatchEvent(e);
}

liberator.modules.commands.addUserCommand(['hatenastar', 'hatenas'], 'add Hatena Star',
	function (args) {
		try {
			var arg = args.string;
			let result = util.evaluateXPath(StarXPath);
			let m      = arg.match(/^(\d+)\?$/);
			if (m) {
				blink(result.snapshotItem(Number(m[1])-1));
				return;
			}
			for (let i = 0, l = result.snapshotLength; i < l; i++) {
				if (arg == '' || arg == 'all' || arg == (i+1)) {
					addHatenaStar(result.snapshotItem(i));
				}
			}
		} catch (e) { liberator.echoerr('hatenaStar: ' + e); }
	}
);

liberator.modules.mappings.addUserMap([liberator.modules.modes.NORMAL], nmap, 'add Hatena Star',
	function (count) {
		try {
			for (let n = 0; n++ < count; liberator.modules.commands.get('hatenastar').execute("all", false, count));
		} catch (e) { liberator.echoerr('hatenaStar: ' + e); }
	}, {
		noremap: true,
		count: true
	}
);

liberator.modules.hints.addMode(hmap, 'Add Hatena star',
	function (elem, _, count) {
		count = Math.min(hmax(), Math.max(count, 1)) - 1;
		addHatenaStar(elem);
		let handle = setInterval(function () {
			if (count-- > 0)
				addHatenaStar(elem);
			else
				clearInterval(handle);
		}, hinterval());
	},
	function () StarXPath
);

})();

// vim: set noet :
n class="p">) === 命名規約 === prefix "subject_" を付け"rumtimepath/plugin/notifier" の下にインストールして下さい e.g.)"${rumtimepath}/notifier/subject_XXX.js" === 登録方法 === liberator.plugins.notifier.subject.register(baseClass, extendsMethods) baseClass: 基底クラスとなります現在以下の基底クラスが存在します - liberator.plugins.notifier.Subject - liberator.plugins.notifier.SubjectHttp extendsMethosd: 基底クラスへの拡張をハッシュ形式で渡します === 基底クラスの説明 === ==== librator.plugins.notifier.Subject ==== Subject の基本クラスです interval: 秒で変更チェックするインターバルを指定しますデフォルトは 60 です initialize(): 必要の無い場合実装しなくても OK です インスタンス生成時に1度だけフレームワークによって呼び出されます 初期化処理など必要な処理を実装して下さい check(): 必ず実装して下さい 指定したインターバルごとにフレームワークによって呼び出されます 変更を検知した場合liberator.plugins.notifier.Message のインスタンスを引数に this.notify(message) を呼び出してください shutdown(): 必要の無い場合実装しなくても OK です 変更通知フレームワークの終了時に呼ばれます ==== librator.plugins.notifier.SubjectHttp ==== Httpを利用した変更検知の基底クラスです リクエスト内容をキャッシュします interval: 秒で変更チェックするインターバルを指定しますデフォルトは 60 です options{}: url: URL を指定します headers{}: リクエストに header が必要な場合ハッシュで指定します extra{}: リクエストのオプションですハッシュで指定します 以下の key が有効です asynchronous (false), encoding(default utf-8) parse(liberator.pluginsnotifier.Request): 必ず実装して下さい リクエストを解析した結果を返却して下さい diff(cache, parsed): 必要の無い場合実装しなくても OK です デフォルトの実装は cache を返却します this.parse() による解析結果とそのキャッシュとの差分を抽出して返却して下さい buildMessages(diff): 必ず実装して下さい this.diff() により抽出されたオブジェクトを元にliberator.plugins.notifier.Message のインスタンス またはその配列を返却して下さい ]]></detail> </VimperatorPlugin>; //}}} (function() { if (!liberator.plugins.libly) { liberator.log("notifier: needs _libly.js"); return; } if (liberator.plugins.notifier && liberator.plugins.notifier._self) { liberator.plugins.notifier._self.stop(function() { liberator.plugins.notifier._self = bootstrap(); }); } else { liberator.plugins.notifier = {}; liberator.plugins.notifier._self = bootstrap(); } function bootstrap() { var libly = liberator.plugins.libly; var $U = libly.$U; var logger = $U.getLogger("notifier"); var Loader = function() {//{{{ this.initialize.apply(this, arguments); }; Loader.prototype = { initialize: function(name) { liberator.plugins.notifier[name] = this; this.name = name; this.plugins = []; this.load(name); }, load: function(name) { io.getRuntimeDirectories("plugin/notifier").forEach(function(dir) { $U.readDirectory(io.expandPath(dir.path), "^" + name + "_", function(f) { try { io.source(f.path, true) logger.log("plugin load success: " + f.leafName); } catch (e) { logger.log("plugin load failed: " + f.leafName); } }); }); }, register: function(baseClass, pluginExtends) { this.plugins.push(new baseClass(pluginExtends)); }, unregister: function(plugin) { var ret = []; this.plugins.forEach(function(p) { if (p != plugin) ret.push(p); }); this.plugins = ret; }, getPlugins: function() { return this.plugins; } }; //}}} var Message = function() {//{{{ this.initialize.apply(this, arguments); }; Message.prototype = { initialize: function(title, message, link, options) { try { if (typeof title == "undefined" || title == null) throw "title is undefined."; if (typeof message == "undefined" || message == null) throw "message is undefined."; this.title = title; this.message = message; this.link = link; this.options = options; } catch (e) { logger.log("Message.initialize error: " + e); throw e; } } };//}}} var Observer = function() {//{{{ this.__initialize__.apply(this, arguments); }; Observer.prototype = { __initialize__: function(args) { $U.extend(this, args); if (typeof this.initialize == "function") this.initialize(); }, shutdown: function() {} };//}}} var Subject = function() {//{{{ this.__initialize__.apply(this, arguments); }; Subject.prototype = { __initialize__: function(args) { this.isActive = false; this.observers = []; this.interval = 60; this.__nextTime = new Date(); $U.extend(this, args); if (typeof this.initialize == "function") this.initialize(); }, attach: function(observer) { this.observers.push(observer); }, notify: function(message) { if (!message) return; this.observers.forEach(function(o) { if (!o || typeof o.update != "function") return; try { o.update(message); } catch (e) { logger.log(e); } }); }, check: function() { throw "needs override." }, shutdown: function() {} };//}}} var SubjectHttp = Subject;//{{{ $U.extend(SubjectHttp.prototype, { initialize: function() { this.preInitialized = true; this.initialized = false; this.count = 0; this.cache; if (typeof this.preInitialize == "function") this.preInitialized = this.preInitialize(); if (!this.preInitialized) return; var req = new libly.Request( this.options.url, this.options.headers, $U.extend({ asynchronous: true }, this.options.extra) ); req.addEventListener("onSuccess", $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); }); if (this.method == "POST") req.post(); else req.get(); }, check: function() { if (!this.initialized) return; this.count++; var req = new libly.Request( this.options.url, this.options.headers, $U.extend({ asynchronous: true }, this.options.extra) ); req.addEventListener("onSuccess", $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); if (diff && (typeof diff.length == "undefined" || diff.length > 0)) { this.cache = parsed; if (typeof this.buildMessages == "function") { let messages = this.buildMessages(diff); [].concat(messages).forEach(function(m) { this.notify(m); liberator.sleep(1500); }, this); } } })); if (this.method == "POST") req.post(); else req.get(); }, diff: function(cache, parsed) parsed });//}}} var Notifier = function() {//{{{ this.initialize.apply(this, arguments); }; Notifier.prototype = { initialize: function(args) {//{{{ this.id = (new Date()).getTime(); this.observers; this.subjects; this.timer = false; this.finallycallback; this.isBusy = false; },//}}} setup: function() {//{{{ if (this.isBusy) { logger.log("busy." + this.id); return; } this.isBusy = true; commands.addUserCommand([ "notifierstart" ], "start the notifier", $U.bind(this, function(args) this.start()), null, true ); commands.addUserCommand([ "notifierrestart" ], "restart the notifier", $U.bind(this, function(args) this.stop($U.bind(this, function() { this.start() }))), null, true ); commands.addUserCommand([ "notifierstop" ], "stop the notifier", $U.bind(this, function(args) this.stop()), null, true ); liberator.plugins.notifier.libly = libly; liberator.plugins.notifier.Observer = Observer; liberator.plugins.notifier.Subject = Subject; liberator.plugins.notifier.SubjectHttp = SubjectHttp; liberator.plugins.notifier.Message = Message; this.observers = new Loader("observer", function(args) new Observer(args)); this.subjects = new Loader("subject", function(args) new Subject(args)); this.observers.getPlugins().forEach($U.bind(this, function(o) { this.subjects.getPlugins().forEach(function(s) s.attach(o)); })); liberator.registerObserver("shutdown", $U.bind(this, function() this.stop())); this.isBusy = false; },//}}} start: function() {//{{{ if (this.timer) { logger.log("already running." + this.id); return; } if (this.isBusy) { logger.log("busy." + this.id); return; } var start = $U.dateFormat(new Date()); setTimeout($U.bind(this, function() { logger.echo("notifier[" + this.id + "] running at " + start, commandline.force_singleline); this.timer = true; while (this.timer) { //{{{ this.subjects.getPlugins().forEach(function(s) { let now = new Date(); if (!s.__nextTime) s.__nextTime = now; if (s.interval > 0 && !s.isActive && s.__nextTime <= now) { s.isActive = true; setTimeout(function() { let start = (new Date()).getTime(); if (typeof s.check == "function") try { s.check(); } catch (e) { logger.log("subject.check error: " + e) } let stop = (new Date()).getTime(); let elapse = (stop - start); s.isActive = false; s.__nextTime = new Date(s.__nextTime.getTime() + s.interval * 1000); if (s.__nextTime < now) s.__nextTime = now; }, 1000); } }); liberator.sleep(3 * 1000); }//}}} if (typeof this.finallycallback == "function") this.finallycallback(); logger.echo("notifier[" + this.id + "] stopped.(" + start + ")", commandline.force_singleline); }), 10); },//}}} stop: function(finallycallback) {//{{{ if (!this.timer) { logger.log("not running."); if (typeof finallycallback == "function") finallycallback(); return; } this.subjects.getPlugins().forEach(function(s) s.shutdown()); this.observers.getPlugins().forEach(function(o) o.shutdown()); this.finallycallback = finallycallback; this.timer = false; }//}}} };//}}} var instance = new Notifier(); instance.setup(); instance.start(); return instance; }; })(); // vim: set fdm=marker sw=2 ts=2 sts=0 et: