const
Hatebu = 'http://b.hatena.ne.jp/',
Hotent = Hatebu +'hotentry',
Icon = Hatebu +'favicon.ico',
Logo = <a class="logo" href={Hatebu} accesskey="/"/>.appendChild(
(<img width="64" height="11" src={Hatebu +'images/logo1.gif'}/>) +
(<img width="77" height="11" src={Hatebu +'images/logo2.gif'}/>)),
CSS = ''+<![CDATA[
a img {border:none}
kbd, button {font-weight:bold; font-family:monospace; margin:0 0.2em}
button {padding:0; border-width:1px}
kbd {text-decoration:underline; text-transform:uppercase}
.icon {vertical-align:middle}
.timestamp {font-size:smaller}
.logo {
background-color:#2c6ebd; display:inline-block; width:146px;
text-align:center; padding:1px 0 3px; line-height:1;
-moz-border-radius:6px;
}
.logo > img {vertical-align:bottom}
.error {font-style:oblique}
.loading {opacity:0.9}
.loading + .logo {opacity:0.4}
.error + .logo {opacity:0.7}
]]>;
(function hatebuCommand(o){
o.name = 'hatebu '+ o.name;
o.icon = Icon;
var lmn = o._lmn || <div> </div>;
var id = lmn.@id = o._id = o.name.replace(/ /g, '-');
XML.prettyPrinting = XML.ignoreWhitespace = false;
var htm = (
'<style>'+ CSS + (o._css || '') +'</style>'+
<div class={id}/>.appendChild(lmn).appendChild(Logo));
o._css = null;
o._lmn = function _lmn(pb){
var lmn, ini = false;
while(!(lmn = pb.ownerDocument.getElementById(id))){
pb.innerHTML = htm;
ini = true;
}
lmn.ini = ini;
return lmn;
};
(o._bin || (o._bin = {})).wipe = function _wipe(){
for(var k in this) delete this[k];
this.wipe = _wipe;
};
CmdUtils.CreateCommand(o);
return arguments.callee;
})({
name: 'it',
description: 'Hatena::Bookmark'.link(Hatebu) +'s the current page.',
help: ''+ (
<>Supported Services:
<ul style="list-style-image:none">
<li><a href="http://reader.google.com/">Google Reader</a></li>
<li><a href="http://reader.livedoor.com/">livedoor Reader</a></li>
<li><a href="http://fastladder.com/">Fastladder</a></li>
</ul></>),
contributor: {name: 'powchin', homepage: 'http://friendfeed.com/powchin'},
arguments: {
'object [tag0;[tag1;[...;]] ]comment': noun_arb_text,
alias: {
name: 'hatebu title',
label: 'title',
rankLast: true,
default: function nt_htb_title_default() [this._empty, this._paget],
suggest: function nt_htb_title_suggest(txt, htm, cb, sx)
[CmdUtils.makeSugg(txt, htm, null, sx), this._paget],
_empty: CmdUtils.makeSugg(''),
get _paget nt_htb_paget() CmdUtils.makeSugg(info().title, null, .5),
},
},
execute: function htb_execute({object: {text: cmn}, alias: {text: ttl}}){
var bin = this._bin, {data, $f} = bin;
if(data && data.rks){
if(ttl) data.title = ttl;
else ttl = $f.find('.title').text();
cmn = data.comment = parse(cmn).comm.replace(/[\r\n]/g, ' ');
$.ajax({
url: $f.attr('action'), data: data, type: 'POST',
error: howl,
success: function(){ say(ttl, cmn) } });
} else {
say('Not ready', 'Opening the bookmarklet page instead.');
Utils.openUrlInBrowser(this._req());
}
bin.wipe();
},
preview: function htb_preview(pb, {object: {text}, alias: {text: ttl}}){
var req = this._req(), {comm, size, tags} = parse(text);
var lmn = this._lmn(pb), [inp, cnt] = lmn.childNodes, bin = this._bin;
var doc = pb.ownerDocument;
inp.innerHTML = (
(<div class="comment">{comm}</div>).appendChild(
<span class="count"/>.appendChild(
<><sup>{size}</sup>/<sub>300</sub></>)) +
(ttl && <div class="mytitle">{ttl}</div>));
Array.forEach(cnt.getElementsByClassName('tag'), check, tags);
if(cnt.children.length && req === bin.preq){
let te = doc.getElementById('title-edit');
if(te) te.style.textDecoration = ttl && 'line-through';
return;
}
if(/\bloading\b/.test(lmn.className)) return;
bin.wipe();
var me = this;
ajax(lmn, {url: bin.preq = req}, function htb_get(htm){
if(!/<form [^]+?<\/form>/(htm)) return ng(lmn, htm);
var div = doc.createElement('div');
div.innerHTML = RegExp.lastMatch.replace(/<img src=..images[^>]+>/g, '');
var val, data = bin.data = {}, [form] = bin.$f = (
$(div.firstChild)
.find('input').each(function(){
if(this.type === 'hidden') data[this.name] = this.value;
else if(this.id === 'comment' && this.value > '')
val = (this.value.replace(/\]\[/g, ';')
.replace(/^\[([^\[]+)\]/, '$1; '));
}).end()
.find('#title-th-head, table:eq(1)').remove().end()
.find('a, img').each(abs).end()
.each(abs));
ok(lmn, form.innerHTML);
var at = doc.getElementById('all-tags');
at && at.addEventListener('click', tagplus, false);
if(val && doc.getElementById(me._id)){
with(box()) value = (/\S+/(value) || me._id) +' '+ val;
htb_preview.call(me, pb, {object: {text: val}, alias: {text: ''}});
}
});
},
previewDelay: 99,
_req: function htb__req() Hatebu +'bookmarklet?url='+ enc4htb(info().url),
_css: <![CDATA[
dl {margin:0.5em 0}
dt, .comment {font-weight:bold}
dd {margin-left:0.5em}
.comment {font-size:104%}
.mytitle {font-size:112%; text-align:center}
.count {font-size:80%; padding:0 0.5em}
.title {background:transparent none no-repeat left; padding-left:18px}
.tag {display:inline-block; margin-right:0.5em;
-moz-outline-radius:4px; cursor:pointer}
.matched {outline:1px dotted}
.selected {outline:1px solid; font-weight:bolder}
.content {font-style:normal}
.error .content {padding:0 0.2em; font-style:oblique}
]]>,
_lmn: <div><div class="input"> </div><div class="content"> </div></div>,
})({
name: 'hotentry',
description: Hotent.link(Hotent),
execute: Hotent,
preview: function hoten_preview(pb){
var me = this, lmn = me._lmn(pb);
ajax(lmn, {url: Hotent +'/diary.rss', dataType: 'xml'}, function(xml){
CmdUtils.previewList(
lmn,
Array.map(
xml.getElementsByTagName('item'),
function(item)(
let([t, l] = [item.getElementsByTagName(n)[0].textContent
for each(n in this)])
<><img class="icon" src={favicon(l)}/> <a href={l}>{t}</a></>),
['title', 'link']),
me.__go);
lmn.className = '';
});
},
__go: function hoten__go(i, e){
Utils.openUrlInBrowser(e.target.parentNode.querySelector('a').href);
},
_css: <![CDATA[ ]]>,
_lmn: <div> </div>,
})({
name: 'comments',
description: 'Shows '+ 'hatebu'.link(Hatebu) +' comments for the page.',
help: 'Supports the same services as "hatebu-it".',
execute: function hbc_execute(){ Utils.openUrlInBrowser(entry(info().url)) },
preview: function hbc_preview(pb){
var lmn = this._lmn(pb), bin = this._bin,
req = Hatebu +'entry/json/?url='+ enc4htb(info().url);
if(lmn.children.length && req === bin.preq) return;
ajax(lmn, {url: bin.preq = req, dataType: 'json'}, function(r){
if(r) ok(
lmn,
'<a class="entry_url" accesskey="e" href="'+ r.entry_url +
'"><span class="count">'+ r.count +'</span> us<u>e</u>rs</a><ol>'+
r.bookmarks.reduce(function(s, {user, tags, comment, timestamp}){
if(tags.length || comment) s = s.concat(
'<li><a class="user" href="', Hatebu, user,
'"><img src="http://www.hatena.ne.jp/users/', user.slice(0, 2),
'/', user, '/profile_s.gif"/><span class="name">', user,
'</span></a><span class="tags">',
tags.map(function(t) t.link(Hatebu + user +'/'+ t +'/')),
'</span><span class="comment">', comment,
'</span><span class="timestamp">', timestamp, '</span></li>');
return s;
}, '') +'</ol>');
else ng(lmn, 'No bookmarks.');
});
},
_css: <![CDATA[
ol {list-style:none; padding:0; margin:0.2em 0 0}
li img {vertical-align:middle}
span {margin-left:0.2em}
.entry_url {font-size:108%; float:right; margin:0 1em}
.count, .user {font-weight:bolder}
]]>,
})({
name: 'search',
description: 'Searches '+ 'hatebu'.link(Hatebu) +' for the specified user.',
arguments: {
object: noun_arb_text,
source: {
name: 'hatena id',
label: 'id',
default: (function(us){
if(!us.length) us[0] = '';
var ss = [{text: x, summary: x} for each(x in us)];
return function nt_hatena_id_default() ss;
})(users('https://www.hatena.ne.jp')),
suggest: function nt_hatena_id_suggest(txt, htm, cb, sx)(
/^[A-Za-z][\w-]{1,30}[A-Za-z\d]$/.test(txt = txt.trim())
? [CmdUtils.makeSugg(txt, htm, null, sx)]
: []),
},
},
execute: function hbs_execute({object: {text}, source: {text: user}}){
Utils.openUrlInBrowser(
Hatebu + user + (text && '/?q='+ encodeURIComponent(text)));
},
preview: function hbs_preview(pb, args, offset){
var me = this, txt = args.object.text, usr = args.source.text;
var lmn = this._lmn(pb), {dat} = this._bin, bms = dat[usr];
switch(bms){
case 100: return;
case 403: return ng(lmn, 'Private mode: '+ usr.bold());
case 404: return ng(lmn, 'No such user: '+ usr.bold());
case void '':
dat[usr] = 100;
lmn.className += ' loading';
return me._get(dat, usr);
}
lmn.className = '';
if(!txt) return void this.previewDefault(pb);
try { var re = RegExp(txt, 'i') }
catch(_){ re = RegExp(txt.replace(/\W/g, '\\$&'), 'i') }
var {txt, num} = bms, len = num.length, cnt = 0, ls = ['<ol>'];
for(var i = offset |= 0; i < len; ++i){
var i3 = i * 3, t = txt[i3], c = txt[i3+1], u = txt[i3+2];
if(!(re.test(t) || re.test(c) || re.test(u))) continue;
var k = (++cnt + 9).toString(36), [n, d] = num[i].split('\t');
ls[cnt] = '<li>'+ (
<><img class="icon" src={favicon(u)}/><a class="title" href={u}
accesskey={cnt}>{t}</a><nobr><a class="user" href={entry(u)}
accesskey={k}>{n}</a><kbd>{k}</kbd></nobr></> +
('<div class="comment">'+ tagsub(c, usr) +
'<span class="timestamp">'+
d.slice(0, 4) +'-'+ d.slice(4, 6) +'-'+ d.slice(6, 8) +
'</span></div></li>'));
if(cnt >= 9) break;
}
lmn.innerHTML = (
'<div id="navi">'+
'<button id="prev" accesskey=","><,</button>'+
'<span id="pos">'+ i +'/'+ len +'</span>'+
'<button id="next" accesskey=".">.></button></div>'+
(ls[1] ? ls.join('') +'</ol>' : 'No results.'));
var nxt = i + 1, doc = lmn.ownerDocument;
bms[nxt] = offset;
if(!offset) doc.getElementById('prev').disabled = true;
if(i >= len) doc.getElementById('next').disabled = true;
doc.getElementById('navi').addEventListener(
'focus', function hbs_onfocus(e){
var b = e.target;
if(b.nodeName !== 'BUTTON') return;
e.preventDefault();
e.stopPropagation();
b.blur();
b.disabled = true;
hbs_preview.call(me, pb, args, b.id === 'next' ? nxt : bms[offset]);
}, true);
},
previewDelay: 222,
_get: function get(dat, usr, ofs){
const Lim = 5e3;
var me = this, xhr = $.ajax({
url: Hatebu + usr +'/search.data',
data: {limit: Lim, offset: ofs |= 0},
error: function(x, s){
if(/^40[34]$/.test(x.status)) dat[usr] = x.status;
else howl(x, s);
},
success: function(res){
if(xhr.channel.URI.spec === Hatebu){
dat[usr] = 404;
return;
}
var bms = dat[usr], nfo = res.split('\n'),
len = nfo.length / 4, num = nfo.splice(len * 3);
if(bms === 100){
dat[usr] = {txt: nfo, num: num};
var U = context.chromeWindow.gUbiquity;
U.__lastValue = null;
U.__processInput(true);
} else {
var {push} = nfo;
push.apply(bms.txt, nfo);
push.apply(bms.num, num);
}
if(len === Lim) me._get(dat, usr, ofs + Lim);
},
});
},
_bin: {dat: {}},
_css: <![CDATA[
ol {margin:0; padding-left:1.8em}
#navi {text-align:center; margin-bottom:0.1em}
#pos {margin:0 1ex}
.title {font-weight:bold}
.user {font:bold 1em/1 monospace; margin-left:0.2em; padding:0 0.1em;
border:1px solid; -moz-border-radius:3px}
]]>,
})({
name: 'users',
description: ('Adds '+ entimg('http://gist.github.com/8351') +
' to each link in the page.'),
execute: function htbu_execute(){
var doc = CmdUtils.getDocument();
for each(let a in Array.slice(doc.links)){
let span = doc.createElement('span');
span.innerHTML = entimg(a.href);
a.parentNode.insertBefore(span, a.nextSibling);
}
},
preview: function htbu_preview(pb){
var lmn = this._lmn(pb);
if(lmn.ini) lmn.innerHTML = this.description;
},
});
$.extend(commands[0], {
author: {name: 'satyr', homepage: 'http://d.hatena.ne.jp/murky-satyr'},
license: 'MIT',
});
function favicon(u)(
'http://favicon.hatena.ne.jp/?url='+ encodeURIComponent(u));
function tagsub(c, user){
var ts = [], i = -1, m;
while((m = /\[.*?\]/gy(c))){
var t = m[0].slice(1, -1);
ts[++i] = t.link(Hatebu + user +'/'+ t +'/');
}
return i < 0 ? c : ts.join(',') +' '+ RegExp.rightContext;
}
function ng(c){
ok(c, Array.slice(arguments, 1).join(' '), 'error');
}
function ok(c, h, not){
(c.getElementsByClassName('content')[0] || c).innerHTML = h;
c.className = not || '';
}
function info(){
var t, u, {defaultView: win, title, URL} = CmdUtils.getDocument();
try {
(/^https?:\/\/www\.google\.[^/]+\/reader\/view\b/.test(URL)
? ({title: t, url : u}) = win.wrappedJSObject.getPermalink() :
/^https?:\/\/(?:reader\.livedoo|fastladde)r\.com\/reader\b/.test(URL)
? ({title: t, link: u}) = win.wrappedJSObject.get_active_item(1)
: 0);
} finally { return {title: t || title, url: u || URL} }
}
function entry(u, path)(
Hatebu +'entry/'+ (path ? path +'/' : '') + u.replace(/#/g, '%23'));
function entimg(u)(
<a href={entry(u)}/>.appendChild(
<img src={entry(u, 'image')} border="0"/>));
function enc4htb(u) encodeURIComponent(u).replace(/#/g, '%23');
function parse(txt){
var [t, c] = /^\s*(?:[^;\s][^;]*;)*(?=\s*([^]*)\s*$)/(txt),
tags = t.split(';').slice(0, -1);
return {
comm: tags.length ? '['+ tags.join('][') +']'+ c : c,
size: encodeURI(c.replace(/%/g, 0)).replace(/%\w/g, '').length,
tags: tags}
}
function check(tag){
var txt = tag.textContent.toLowerCase(), c = 'tag';
for each(var t in this){
if(txt === (t = t.toLowerCase())){ c += ' selected'; break }
if(~txt.indexOf(t)) c += ' matched';
}
tag.className = c;
}
function say(title){
var txt = Array.slice(arguments, 1).join(' ');
displayMessage({icon: Icon, title: title, text: txt});
}
function howl(x, s){ say(s, x.status, x.statusText) }
function keygen(n) n < 36 ? n.toString(36) : '-^@;:[],./\\<>?_'[n - 36];
function abs(){
var attr, path = (this.getAttribute(attr = 'href') ||
this.getAttribute(attr = 'src' ) ||
this.getAttribute(attr = 'action'));
if(/^(?!\w+:\/\/)\/?/.test(path)) this[attr] = Hatebu + RegExp.rightContext;
}
function xerr(it)(
function err(x, s){ ng(it, x.status, x.statusText, '('+ s +')') });
function ajax(it, op, succ){
it.className += ' loading';
op.error = xerr(it);
op.success = succ;
$.ajax(op);
}
function box()(
box.box || (box.box = context.chromeWindow.gUbiquity.textBox));
function tagplus(e){
var s = e.target;
if(s.nodeName.toLowerCase() !== 'span') return;
with(box()) value = value.replace(/^\s*(\S*)\s*(\S+;)?/,
'$1 $2'+ s.textContent +';');
}
function users(url){
var token = (Cc['@mozilla.org/security/pk11tokendb;1']
.getService(Ci.nsIPK11TokenDB)
.getInternalKeyToken());
if(token.needsLogin() && !token.isLoggedIn()) return [];
var logins = (Cc['@mozilla.org/login-manager;1']
.getService(Ci.nsILoginManager)
.findLogins({}, url, '', ''));
return [l.username for each(l in logins)];
}