■
[Greasemonkey] HaikuのReplyを全部展開するGreasemonkeyのパッチ
Rev.16803で既に対応されていたみたいですね。突っ込み、ありがとうございます m(_ _)m
HaikuのReplyの仕様が微妙に変わったお蔭で、id:cho45さんが作って下さったグリモンスクリプトが動かなくなりましたので、そのパッチ。
と言っても、[@src='/images/icon-replylink.gif]を消しただけなんだけどね・・・
CodeRepos、のアカウントを持ってないので、パッチとソースコードをここにさらします。
パッチ
67c67 < $X("./div[@class='list-body']/div/a[img[@src='/images/icon-replylink.gif']]", e)[0].style.opacity = "0.2"; --- > $X("./div[@class='list-body']/div/a[img]", e)[0].style.opacity = "0.2"; 90c90 < var a = $X("./div[@class='list-body']/div/a[img[@src='/images/icon-replylink.gif']]", entry)[0]; --- > var a = $X("./div[@class='list-body']/div/a[img]", entry)[0];
ソース
// ==UserScript== // @name Expand replies tree // @namespace http://lowreal.net/ // @include http://h.hatena.ne.jp/* // @include http://h.hatena.com/* // ==/UserScript== (function (unsafeWindow) { with (D()) { const WAIT = 1; const ICON = [ "data:image/png;base64,", "iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAMAAAAolt3jAAAAD1BMVEX45u/y", "zt////+za4X///8WHaQkAAAABXRSTlP/////APu2DlMAAAAZdEVYdFNvZnR3", "YXJlAEFkb2JlIEltYWdlUmVhZHlxyWU8AAAARElEQVQI103OORIAQAgCQWD4", "/5s3cC+yLgtV5aVVyckmkW0PG8YBNK3IDj0k+vlNX3fzbkb8d++q+WrYVLZd", "1JaeDF8WzSMCBiKqHq0AAAAASUVORK5CYII=" ].join(""); GM_addStyle([ "div.info .expand a {", "background: transparent url('",ICON,"') no-repeat scroll 0 65%;", "padding: 6px 0 2px 16px;", "margin: 0 0 0 3px", "}", ].join("")); function applyJavaScript (entry) { entry = entry.parentNode.removeChild(entry); // remove event handlers entry.innerHTML = entry.innerHTML; // append to temporary element var t = h(); t.appendChild(entry); // remove duplicated star elements $X("../*/div/*[@class='title']/span[@class='hatena-star-comment-container' or @class='hatena-star-star-container']", entry).forEach(function (e) { e.parentNode.removeChild(e); }); unsafeWindow.Hatena.Haiku.Pager.dispatchEvent('loadedEntries', entry.parentNode.wrappedJSObject || entry.parentNode); return entry; } function expandChildReplies (entry) { var body = $X("./div[@class='list-body']", entry)[0]; return parallel( $X("./div[@class='info']/span[@class='replies']/a[@href]", body).map(function (a) { a.style.opacity = "0.6"; // don't show if this was showed entry var raw_href = $X("string(@href)", a); if ($X("boolean(.//span[@class='timestamp']/a[@href = '"+raw_href+"'])", entry)) { a.style.opacity = "0.2"; return next(); } // don't load already showed entry var es = $X("//div[@class='entry' and .//span[@class='timestamp']/a[@href = '"+raw_href+"']]"); if (es.length) return appendAndLoadNext(es[0]); log(a.href); return xhttp.get(a.href).next(function (data) { // remove unnecessary tags var t = h(data.responseText.replace(/<\/?(?:script|i?frame)[^>]*>/g, "")); return parallel($X(".//div[@class='entries']/div[@class='entry']", t).map(appendAndLoadNext)); }); function appendAndLoadNext (e) { a.style.opacity = "0.2"; $X("./div[@class='list-body']/div/a[img]", e)[0].style.opacity = "0.2"; body.appendChild(applyJavaScript(e)); return wait(WAIT).next(function () { return call(expandChildReplies, e); }); } }) );//.next(function () { // // sort by timestamp // $X("li[@class='entry']", body).map(function (i) { // return { // time: $X("string(div/div/span[@class='timestamp']/a/@href)", i).match(/\d+$/)[0], // elem: i // }; // }).sort(function (a, b) { // return b.time - a.time; // }).forEach(function (i) { // body.appendChild(i.elem); // }); // }); } function expandReplies (entry) { var a = $X("./div[@class='list-body']/div/a[img]", entry)[0]; var raw_href = $X("string(@href)", a); var es = $X("//div[@class='entry' and .//span[@class='timestamp']/a[@href = '"+raw_href+"']]"); if (a) a.style.opacity = "0.6"; $X(".//span[@class='expand']", entry).forEach(function (e) { e.innerHTML = "Loading..."; }); return parallel([ expandChildReplies(entry), !a ? next() : es.length ? appendAndLoadNext(es[0]) : xhttp.get(a.href).next(function (data) { var t = h(data.responseText.replace(/<\/?(?:script|i?frame)[^>]*>/g, "")); return parallel($X(".//div[@class='entries']/div[@class='entry']", t).map(appendAndLoadNext)); }), ]). next(function () { $X(".//span[@class='expand']", entry).forEach(function (e) { e.parentNode.removeChild(e); }); }); function appendAndLoadNext (e) { if (a) a.style.opacity = "0.2"; e = applyJavaScript(e); var body = $X("./div[@class='list-body']", e)[0]; entry.parentNode.insertBefore(e, entry); body.appendChild(entry); return wait(WAIT).next(function () { return call(expandReplies, e); }); } } // observe pagerize var num = 0; next(function () { var n = $X("count(//div[@class='entries']/div[@class='entry'])"); if (num < n) { var entries = $X("//div[@class='entries']/div[@class='entry'][.//img[starts-with(@src, '/images/icon-repl')]]"); loop(entries.length, function (n) { var e = entries[n].wrappedJSObject || entries[n]; if (e._gm_expandreplies_applied) return; e._gm_expandreplies_applied = true; var handler = function () { if (window.getSelection().toString()) return; if (e._loading) return; e._loading = true; log("Loading..."); expandReplies(e).next(function () { if (log.element) { log.element.parentNode.removeChild(log.element); log.element = null; } }).error(function (e) { alert("Error"+e); }); }; // $X("div/div[@class='body']", e)[0].addEventListener("click", handler, false); var info = $X("./div/div[@class='info']", e)[0]; info.appendChild(h("<span class='expand'><a href='javascript:void(156)'>Expand</a></span>").firstChild); info.addEventListener("click", handler, false); }); num = n; } return wait(WAIT).next(arguments.callee); }). error(function (e) { alert(e); }); } // end with /* template functions */ function h (s) { var d = document.createElement("div"); d.innerHTML = s; return d; } function log (m) { // var c = unsafeWindow.console; // if (c) c.log.apply(c, arguments); var o = Array.prototype.concat.apply([], arguments); if (window.console) { window.console.log(o.join(", ")); } else if (GM_log) { GM_log(o); } else { location.href = "javascript:(function () { if (window.console) console.log.apply(console.log, "+o.toSource()+") })();"; } if (!arguments.callee.element) { arguments.callee.element = h("<div style='position:fixed;top:0;left:0;background:#000;color:#fff;padding:0 0.5em;'/>").firstChild; document.body.appendChild(arguments.callee.element); } arguments.callee.element.innerHTML = escapeHTML(m); } function escapeHTML (str) { var str = String(str); var map = { "&" : "&", "<" : "<" , ">" : ">"}; return str.replace(/[&<>]/g, function (m) { return map[m]; }); }; // extend version of $X // $X(exp); // $X(exp, context); // $X(exp, type); // $X(exp, context, type); function $X (exp, context, type /* want type */) { // console.log(String(exp)); if (typeof context == "function") { type = context; context = null; } if (!context) context = document; var exp = (context.ownerDocument || context).createExpression(exp, function (prefix) { var ns = { "atom" : "http://purl.org/atom/ns#", "hatena" : "http://www.hatena.ne.jp/info/xmlns#" }; return document.createNSResolver((context.ownerDocument == null ? context : context.ownerDocument).documentElement) .lookupNamespaceURI(prefix) || ns[prefix] || document.documentElement.namespaceURI; }); switch (type) { case String: return exp.evaluate( context, XPathResult.STRING_TYPE, null ).stringValue; case Number: return exp.evaluate( context, XPathResult.NUMBER_TYPE, null ).numberValue; case Boolean: return exp.evaluate( context, XPathResult.BOOLEAN_TYPE, null ).booleanValue; case Array: var result = exp.evaluate( context, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null ); var ret = []; for (var i = 0, len = result.snapshotLength; i < len; ret.push(result.snapshotItem(i++))); return ret; case undefined: var result = exp.evaluate(context, XPathResult.ANY_TYPE, null); switch (result.resultType) { case XPathResult.STRING_TYPE : return result.stringValue; case XPathResult.NUMBER_TYPE : return result.numberValue; case XPathResult.BOOLEAN_TYPE: return result.booleanValue; case XPathResult.UNORDERED_NODE_ITERATOR_TYPE: { // not ensure the order. var ret = []; var i = null; while (i = result.iterateNext()) { ret.push(i); } return ret; } } return null; default: throw(TypeError("$X: specified type is not valid type.")); } } // Usage:: with (D()) { your code } // JSDefeered 0.2.0 (c) Copyright (c) 2007 cho45 ( www.lowreal.net ) // See http://coderepos.org/share/wiki/JSDeferred function D () { function Deferred () { return (this instanceof Deferred) ? this.init(this) : new Deferred() } Deferred.prototype = { init : function () { this._next = null; this.callback = { ok: function (x) { return x }, ng: function (x) { throw x } }; return this; }, next : function (fun) { return this._post("ok", fun) }, error : function (fun) { return this._post("ng", fun) }, call : function (val) { return this._fire("ok", val) }, fail : function (err) { return this._fire("ng", err) }, cancel : function () { (this.canceller || function () {})(); return this.init(); }, _post : function (okng, fun) { this._next = new Deferred(); this._next.callback[okng] = fun; return this._next; }, _fire : function (okng, value) { var self = this, next = "ok"; try { value = self.callback[okng].call(self, value); } catch (e) { alert(e); next = "ng"; value = e; } if (value instanceof Deferred) { value._next = self._next; } else { if (self._next) self._next._fire(next, value); } return this; } }; Deferred.parallel = function (dl) { var ret = new Deferred(), values = {}, num = 0; for (var i in dl) { if (!dl.hasOwnProperty(i)) continue; (function (d, i) { d.next(function (v) { values[i] = v; if (--num <= 0) { if (dl instanceof Array) { values.length = dl.length; values = Array.prototype.slice.call(values, 0); } ret.call(values); } }).error(function (e) { ret.fail(e); }); num++; })(dl[i], i); } if (!num) Deferred.next(function () { ret.call() }); return ret; }; Deferred.wait = function (n) { var d = new Deferred(), t = new Date(); var id = setTimeout(function () { clearTimeout(id); d.call((new Date).getTime() - t.getTime()); }, n * 1000) d.canceller = function () { try { clearTimeout(id) } catch (e) {} }; return d; }; Deferred.next = function (fun) { var d = new Deferred(); var id = setTimeout(function () { clearTimeout(id); d.call() }, 0); if (fun) d.callback.ok = fun; d.canceller = function () { try { clearTimeout(id) } catch (e) {} }; return d; }; Deferred.call = function (f, args) { args = Array.prototype.slice.call(arguments); f = args.shift(); return Deferred.next(function () { return f.apply(this, args); }); }; Deferred.loop = function (n, fun) { var o = { begin : n.begin || 0, end : n.end || (n - 1), step : n.step || 1, last : false, prev : null }; var ret, step = o.step; return Deferred.next(function () { function _loop (i) { if (i <= o.end) { if ((i + step) > o.end) { o.last = true; o.step = o.end - i + 1; } o.prev = ret; ret = fun.call(this, i, o); if (ret instanceof Deferred) { return ret.next(function (r) { ret = r; return Deferred.call(_loop, i + step); }); } else { return Deferred.call(_loop, i + step); } } else { return ret; } } return Deferred.call(_loop, o.begin); }); }; Deferred.register = function (name, fun) { this.prototype[name] = function () { return this.next(Deferred.wrap(fun).apply(null, arguments)); }; }; Deferred.wrap = function (dfun) { return function () { var a = arguments; return function () { return dfun.apply(null, a); }; }; }; Deferred.register("loop", Deferred.loop); Deferred.register("wait", Deferred.wait); Deferred.define = function (obj, list) { if (!list) list = ["parallel", "wait", "next", "call", "loop"]; if (!obj) obj = (function () { return this })(); for (var i = 0; i < list.length; i++) { var n = list[i]; obj[n] = Deferred[n]; } return Deferred; }; function xhttp (opts) { var d = Deferred(); if (opts.onload) d = d.next(opts.onload); if (opts.onerror) d = d.error(opts.onerror); opts.onload = function (res) { d.call(res); }; opts.onerror = function (res) { d.fail(res); }; GM_xmlhttpRequest(opts); return d; } xhttp.get = function (url) { return xhttp({method:"get", url:url}) } xhttp.post = function (url, data) { return xhttp({method:"post", url:url, data:data, headers:{"Content-Type":"application/x-www-form-urlencoded"}}) } function http (opts) { var d = Deferred(); var req = new XMLHttpRequest(); req.open(opts.method, opts.url, true); req.onreadystatechange = function () { if (req.readyState == 4) d.call(req); }; req.send(opts.data || null); return d; } http.get = function (url) { return http({method:"get", url:url}) } http.post = function (url, data) { return http({method:"post", url:url, data:data}) } Deferred.Deferred = Deferred; Deferred.http = http; Deferred.xhttp = xhttp; return Deferred; }// End of JSDeferred })(this.unsafeWindow || this);