diff options
-rw-r--r-- | _libly.js | 307 | ||||
-rw-r--r-- | notifier.js | 236 | ||||
-rw-r--r-- | notifier/jquery.jgrowl.css | 87 | ||||
-rw-r--r-- | notifier/observer_growl.js | 120 | ||||
-rw-r--r-- | notifier/subject_test.js | 52 |
5 files changed, 802 insertions, 0 deletions
diff --git a/_libly.js b/_libly.js new file mode 100644 index 0000000..13dc034 --- /dev/null +++ b/_libly.js @@ -0,0 +1,307 @@ +/** + * ==VimperatorPlugin== + * @name _libly.js + * @description suvene's library + * @description-ja 適当なライブラリっぽいものたち + * @author suVene suvene@zeromemory.info + * @version 0.1.0 + * @minVersion 1.2 + * @maxVersion 2.0pre + * Last Change: 07-Dec-2008. + * ==/VimperatorPlugin== + * + * HEAD COMMENT {{{ + * }}} + */ +if (!liberator.plugins.libly) { + +liberator.plugins.libly = {}; +var lib = liberator.plugins.libly; + +lib.$U = {//{{{ + getLogger: function(prefix) { + return new function() { + this.log = function(msg, level) { + if (typeof msg == 'object') msg = util.objectToString(msg); + liberator.log(lib.$U.dateFormat(new Date()) + ': ' + (prefix || '') + ': ' + msg, (level || 0)); + }; + this.echo = function(msg, flg) { + flg = flg || commandline.FORCE_MULTILINE; + this.log(msg); + liberator.echo(msg, flg); + }; + this.echoerr = function(msg) { + this.log('error: ' + msg); + liberator.echoerr(msg); + }; + } + }, + extend: function(dst, src) { + for (let prop in src) + dst[prop] = src[prop]; + return dst; + }, + A: function(hash, iter) { + var ret = []; + for each (let item in hash) ret.push(item); + return ret; + }, + bind: function(obj, func) { + return function() { + return func.apply(obj, arguments); + } + }, + stripTags: function(str, tags) { + var ignoreTags = [].concat(tags); + ignoreTags = '(?:' + ignoreTags.join('|') + ')'; + return str.replace(new RegExp('<' + ignoreTags + '(?:[ \\t\\n\\r][^>]*|/)?>([\\S\\s]*?)<\/' + ignoreTags + '[ \\t\\r\\n]*>', 'ig'), ''); + }, + eval: function(text) { + var fnc = window.eval; + var sandbox; + try { + sandbox = new Components.utils.Sandbox(window); + if (Components.utils.evalInSandbox('true', sandbox) === true) { + fnc = function(text) { return Components.utils.evalInSandbox(text, sandbox); }; + } + } catch (e) { $U.log('warning: multi_requester.js is working with unsafe sandbox.'); } + + return fnc(text); + }, + evalJson: function(str, toRemove) { + var json; + try { + json = Components.classes['@mozilla.org/dom/json;1'].getService(Components.interfaces.nsIJSON); + if (toRemove) str = str.substring(1, str.length - 1); + return json.decode(str); + } catch (e) { return null; } + }, + getSelectedString: function() { + return (new XPCNativeWrapper(window.content.window)).getSelection().toString(); + }, + pathToURL: function(path) { + if (/^https?:\/\//.test(path)) return path; + var link = document.createElement('a'); + link.href= path; + return link.href; + }, + dateFormat: function(dtm, fmt) { + var y = dtm.getFullYear(); + var M = dtm.getMonth() + 1; + var d = dtm.getDay(); + var h = dtm.getHours(); + var m = dtm.getMinutes(); + var s = dtm.getSeconds(); + + if (M < 10) M = '0' + M; + if (d < 10) d = '0' + d; + if (h < 10) h = '0' + h; + if (m < 10) m = '0' + m; + if (s < 10) s = '0' + s; + return y + '/' + M + '/' + d + ' ' + h + ':' + m + ':' + s; + }, + readDirectory: function(path, filter, func) { + var d = io.getFile(path); + if (d.exists() && d.isDirectory()) { + let enm = d.directoryEntries; + let flg = false; + while (enm.hasMoreElements()) { + let item = enm.getNext(); + item.QueryInterface(Components.interfaces.nsIFile); + flg = false; + if (typeof filter == 'string') { + if ((new RegExp(filter)).test(item.leafName)) flg = true; + } else if (typeof filter == 'function') { + flg = filter(item); + } + if (flg) func(item); + } + } + }, +}; +//}}} + +lib.Request = function() {//{{{ + this.initialize.apply(this, arguments); +}; +lib.Request.EVENTS = ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; +lib.Request.requestCount = 0; +lib.Request.prototype = { + initialize: function(url, headers, options) { + this.url = url; + this.headers = headers || {}; + this.options = lib.$U.extend({ + asynchronous: true, + encoding: 'UTF-8' + }, options || {}); + this.observers = {}; + }, + addEventListener: function(name, func) { + try { + if (typeof this.observers[name] == 'undefined') this.observers[name] = []; + this.observers[name].push(func); + } catch (e) { + if (!this.fireEvent('onException', e)) throw e; + } + }, + fireEvent: function(name, args, asynchronous) { + if (!(this.observers[name] instanceof Array)) return false; + this.observers[name].forEach(function(event) { + if (asynchronous) { + setTimeout(event, 10, args); + } else { + event(args); + } + }); + return true; + }, + _complete: false, + _request: function(method) { + + try { + lib.Request.requestCount++; + + this.transport = new XMLHttpRequest(); + this.transport.open(method, this.url, this.options.asynchronous); + + this.transport.onreadystatechange = lib.$U.bind(this, this._onStateChange); + this.setRequestHeaders(); + this.transport.overrideMimeType('text/html; charset=' + this.options.encoding); + + this.body = this.method == 'POST' ? this.options.postBody : null; + + this.transport.send(this.body); + + // Force Firefox to handle ready state 4 for synchronous requests + if (!this.options.asynchronous && this.transport.overrideMimeType) + this._onStateChange(); + + } catch (e) { + if (!this.fireEvent('onException', e, this.options.asynchronous)) throw e; + } + }, + _onStateChange: function() { + var readyState = this.transport.readyState; + if (readyState > 1 && !(readyState == 4 && this._complete)) + this.respondToReadyState(this.transport.readyState); + }, + getStatus: function() { + try { + return this.transport.status || 0; + } catch (e) { return 0; } + }, + isSuccess: function() { + return !status || (status >= 200 && status < 300); + }, + respondToReadyState: function(readyState) { + var state = lib.Request.EVENTS[readyState]; + var res = new lib.Response(this); + + if (state == 'Complete') { + lib.Request.requestCount--; + try { + this._complete = true; + this.fireEvent('on' + (this.isSuccess() ? 'Success' : 'Failure'), res, this.options.asynchronous); + } catch (e) { + if (!this.fireEvent('onException', e, this.options.asynchronous)) throw e; + } + } + }, + setRequestHeaders: function() { + var headers = { + 'Accept': 'text/javascript, application/javascript, text/html, application/xhtml+xml, application/xml, text/xml, */*;q=0.1' + }; + + if (this.method == 'POST') { + headers['Content-type'] = 'application/x-www-form-urlencoded' + + (this.options.encoding ? '; charset=' + this.options.encoding : ''); + + if (this.transport.overrideMimeType) { + let year = parseInt((navigator.userAgent.match(/\bGecko\/(\d{4})/) || [0, 2005])[1], 10); + if (0 < year && year < 2005) + headers['Connection'] = 'close'; + } + } + + for (let key in this.headers) + if (this.headers.hasOwnProperty(key)) headers[key] = this.headers[key]; + + for (let name in headers) + this.transport.setRequestHeader(name, headers[name]); + + }, + get: function() { + this._request('GET'); + }, + post: function() { + this._request('POST'); + } +};//}}} + +lib.Response = function() {//{{{ + this.initialize.apply(this, arguments); +}; +lib.Response.prototype = { + initialize: function(req) { + this.req = req; + this.transport = req.transport; + this.isSuccess = req.isSuccess(); + this.readyState = this.transport.readyState; + + if (this.readyState == 4) { + this.status = this.getStatus(); + this.statusText = this.getStatusText(); + this.responseText = (this.transport.responseText == null) ? '' : this.transport.responseText; + } + + this.doc = null; + this.htmlFragmentstr = ''; + }, + status: 0, + statusText: '', + getStatus: lib.Request.prototype.getStatus, + getStatusText: function() { + try { + return this.transport.statusText || ''; + } catch (e) { return ''; } + }, + getHTMLDocument: function(xpath, xmlns) { + if (!this.doc) { + this.htmlFragmentstr = this.responseText.replace(/^[\s\S]*?<html(?:[ \t\n\r][^>]*)?>|<\/html[ \t\r\n]*>[\S\s]*$/ig, '').replace(/[\r\n]+/g, ' '); + let ignoreTags = ['script']; + if (this.req.options.siteinfo.ignoreTags) { + ignoreTags.concat(this.req.options.siteinfo.ignoreTags.split(',')); + } + this.htmlStripScriptFragmentstr = lib.$U.stripTags(this.htmlFragmentstr, 'script'); + this.doc = this._createHTMLDocument(this.htmlStripScriptFragmentstr, xmlns); + } + + var ret = this.doc; + if (xpath) { + ret = this.getNodeFromXPath(xpath, this.doc); + } + return ret; + }, + _createHTMLDocument: function(str) { + var htmlFragment = document.implementation.createDocument(null, 'html', null); + var range = document.createRange(); + range.setStartAfter(window.content.document.body); + htmlFragment.documentElement.appendChild(htmlFragment.importNode(range.createContextualFragment(str), true)); + return htmlFragment; + }, + getNodeFromXPath: function(xpath, doc, parentNode) { + if (!xpath || !doc) return doc; + var node = doc || document; + var nodesSnapshot = (node.ownerDocument || node).evaluate(xpath, node, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); + + if (nodesSnapshot.snapshotLength == 0) return parentNode; + parentNode = parentNode || document.createElementNS(null, 'div'); + for (let i = 0, l = nodesSnapshot.snapshotLength; i < l; parentNode.appendChild(nodesSnapshot.snapshotItem(i++))); + return parentNode; + } +}; +//}}} + +} +// vim: set fdm=marker sw=4 ts=4 sts=0 et: + diff --git a/notifier.js b/notifier.js new file mode 100644 index 0000000..9269ed4 --- /dev/null +++ b/notifier.js @@ -0,0 +1,236 @@ +/** + * ==VimperatorPlugin== + * @name notifier.js + * @description notice of change framework. + * @description-ja 変更通知フレームワーク。 + * @author suVene suvene@zeromemory.info + * @version 0.1.0 + * @minVersion 2.0pre + * @maxVersion 2.0pre + * Last Change: 07-Dec-2008. + * ==/VimperatorPlugin== + * + * HEAD COMMENT {{{ + * }}} + */ +(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 lib = liberator.plugins.libly; + var $U = lib.$U; + var logger = $U.getLogger('notifier'); + + var Loader = function() {//{{{ + this.initialize.apply(this, arguments); + }; + Loader.prototype = { + initialize: function(name, registerHook) { + liberator.plugins.notifier[name] = this; + this.name = name; + this.plugins = []; + this.registerHook = registerHook; + 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(plugin) { + this.plugins.push(this.registerHook(plugin)); + }, + 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, 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.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(); + } + };//}}} + + var Subject = function() {//{{{ + this.__initialize__.apply(this, arguments); + }; + Subject.prototype = { + __initialize__: function(args) { + this.isActive = false; + this.observers = []; + this.interval = 0; + //if (this.interval < 60) 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.' } + };//}}} + + 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('bussy.' + 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.lib = lib; + 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)); + })); + + this.isBusy = false; + },//}}} + start: function() {//{{{ + + if (this.timer) { + logger.log('already running.' + this.id); + return; + } + + if (this.isBusy) { + logger.log('bussy.' + 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) { //{{{ + liberator.dump('window:' + window.content.window); + 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) { + setTimeout(function() { + let start = (new Date()).getTime(); + s.isActive = true; + 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; + }, 10); + } + }); + liberator.sleep(3 * 1000); + }//}}} + if (typeof this.finallycallback == 'function') this.finallycallback(); + logger.echo('notifier[' + this.id + '] stoped.(' + start + ')', commandline.force_singleline); + }), 10); + + logger.log('start end'); + },//}}} + stop: function(finallycallback) {//{{{ + if (!this.timer) { + logger.log('not running.'); + if (typeof finallycallback == 'function') finallycallback(); + return; + } + this.finallycallback = finallycallback; + this.timer = false; + }//}}} + };//}}} + + var instance = new Notifier(); + instance.setup(); + instance.start(); + return instance; +}; + +})(); +// vim: set fdm=marker sw=4 ts=4 sts=0 et: + diff --git a/notifier/jquery.jgrowl.css b/notifier/jquery.jgrowl.css new file mode 100644 index 0000000..5cbd9ed --- /dev/null +++ b/notifier/jquery.jgrowl.css @@ -0,0 +1,87 @@ + +div.observer_growl { + padding: 10px; + z-index: 9999; +} + +/** Normal Style Positions **/ +body > div.observer_growl { + position: fixed; +} + +body > div.observer_growl.top-left { + left: 0px; + top: 0px; +} + +body > div.observer_growl.top-right { + right: 0px; + top: 0px; +} + +body > div.observer_growl.bottom-left { + left: 0px; + bottom: 0px; +} + +body > div.observer_growl.bottom-right { + right: 0px; + bottom: 0px; +} + +body > div.observer_growl.center { + top: 0px; + width: 50%; + left: 25%; +} + +/** Cross Browser Styling **/ +div.center div.observer_growl_notification, div.center div.observer_growl-closer { + margin-left: auto; + margin-right: auto; +} + +div.observer_growl div.observer_growl_notification, div.observer_growl div.observer_growl-closer { + background-color: #000; + color: #fff; + opacity: .85; + filter: alpha(opacity = 85); + zoom: 1; + width: 235px; + padding: 10px; + margin-top: 5px; + margin-bottom: 5px; + font-family: Tahoma, Arial, Helvetica, sans-serif; + font-size: 12px; + text-align: left; + display: none; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; +} + +div.observer_growl div.observer_growl_notification { + min-height: 40px; +} + +div.observer_growl div.observer_growl_notification div.header { + font-weight: bold; + font-size: 10px; +} + +div.observer_growl div.observer_growl_notification div.close { + float: right; + font-weight: bold; + font-size: 12px; + cursor: pointer; +} + +div.observer_growl div.observer_growl-closer { + height: 15px; + padding-top: 4px; + padding-bottom: 4px; + cursor: pointer; + font-size: 11px; + font-weight: bold; + text-align: center; +} + diff --git a/notifier/observer_growl.js b/notifier/observer_growl.js new file mode 100644 index 0000000..b6f1d96 --- /dev/null +++ b/notifier/observer_growl.js @@ -0,0 +1,120 @@ +/** + * notifier.js plugin observer + * @name observer_growl.js + * @description growl when notified. + * @description-ja Growl風通知。 + * @author suVene suvene@zeromemory.info + * @version 0.1.0 + * Last Change: 07-Dec-2008. + * + * use JQuery + * http://jquery.com/ + * use JGrowl + * http://stanlemon.net/projects/jgrowl.html + */ +(function() { + +var notifier = liberator.plugins.notifier; +if (!notifier) return; + +var lib = notifier.lib; +var $U = lib.$U; +var logger = $U.getLogger('observer_growl'); + +var Growl = function() {//{{{ + this.initialize.apply(this, arguments); +} +Growl.prototype = { + defaults: { + life: 3000 + }, + initialize: function(dom, container) { + this.dom = dom; + this.container = container; + this.created = new Date(); + this.life = this.defaults.life; + dom.childNodes[0].addEventListener("click", $U.bind(this, this.remove), false); + }, + remove: function() { + this.container.removeChild(this.dom); + }, + +}//}}} + +notifier.observer.register({ + initialize: function () { + logger.log('initialize'); + this.count = 1; + + io.getRuntimeDirectories('').forEach(function(dir) { + let path = io.expandPath(dir.path + '/plugin/notifier'); + $U.readDirectory(path, '^jquery' , function(f) { + try { + io.source(f.path, true) + logger.log('load success: ' + f.leafName); + } catch (e) { + logger.log('load failed: ' + f.leafName); + } + }); + }); + }, + update: function(message) { + logger.log('update:' + this.count); + + var doc = window.content.document; + var container = doc.getElementById("observer_growl"); + if (!container) { + doc.body.appendChild(util.xmlToDom(<div id="observer_growl" class="observer_growl top-right" />, doc)); + container = doc.getElementById("observer_growl"); + } + + this.createPopup(doc, message, container); + container.appendChild(this.createPopup(doc, message, container)); + + if (container.childNodes.length == 1) { + var interval = setInterval($U.bind(this, this.checkStatus), 1000); + container.__interval__ = interval; + } + + this.count++; + }, + createPopup: function(doc, message, nodes) { + var dom; + var html = + <div class="observer_growl_notification" style="display: block;"> + <div class="close">×</div> + <div class="header">{util.escapeHTML(this.count + ': ' + message.title)}</div> + <div class="message">{util.escapeHTML(message.message)}</div> + </div>; + dom = util.xmlToDom(html, doc, nodes); + dom.__data__ = new Growl(dom, nodes); + + let count = this.count; + return dom; + }, + checkStatus: function() { + + var doc = window.content.document; + var container = doc.getElementById("observer_growl"); + if (!container) return; + + var removeNodes = []; + for (let i = 0, len = container.childNodes.length; i < len; i++) { + let item = container.childNodes[i]; + let growl = item.__data__; + if (growl && growl.created && + growl.created.getTime() + growl.life < (new Date()).getTime()) { + removeNodes.push(item); + } + } + removeNodes.forEach(function(n) container.removeChild(n)); + + if (container.childNodes.length == 0) + clearInterval(container.__interval__); + + } +}); + +})(); +// vim: set fdm=marker sw=4 ts=4 sts=0 et: + diff --git a/notifier/subject_test.js b/notifier/subject_test.js new file mode 100644 index 0000000..b5689c6 --- /dev/null +++ b/notifier/subject_test.js @@ -0,0 +1,52 @@ +/** + * notifier.js plugin subject + * @name subject_test.js + * @description notify if ... + * @description-ja ... の時ポップアップ通知。 + * @author suVene suvene@zeromemory.info + * @version 0.1.0 + * Last Change: 07-Dec-2008. + */ +(function() { + +var notifier = liberator.plugins.notifier; +if (!notifier) return; + +var lib = notifier.lib; +var $U = lib.$U; +var logger = $U.getLogger('subject_test'); + +notifier.subject.register({ + interval: 3, + initialize: function() { + logger.log('initialize'); + this.count = 0; + }, + check: function() { + this.count++; + logger.log('check'); + var req = new lib.Request( + 'http://localhost:8080/index.html', + null, // headers {} + { + encode: 'shift_jis' + } + ); + req.addEventListener('onSuccess', $U.bind(this, function(res) { + var text = res.responseText; + logger.log('success!! '); + var message = new notifier.Message('TEST', text); + this.notify(message); + + if (this.count == 5) { + notifier.subject.unregister(this); + this.count = 0; + } + })); + req.get(); + } +}); + +})(); +// vim: set fdm=marker sw=4 ts=4 sts=0 et: + |