Skip to content

Instantly share code, notes, and snippets.

@satyr
Created September 1, 2008 22:29
Show Gist options
  • Save satyr/8351 to your computer and use it in GitHub Desktop.
Save satyr/8351 to your computer and use it in GitHub Desktop.
はてブ
Utils.extend(feed,
{ title: 'Hatena::Bookmark Commands'
, author: {name: 'satyr', homepage: 'http://d.hatena.ne.jp/murky-satyr'}
, license: 'MIT'
})
const
Hatebu = 'http://b.hatena.ne.jp/',
Hotent = Hatebu +'hotentry',
HLogin = 'https://www.hatena.ne.jp/login',
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, input[type=button] {
font-weight:bold; font-family:monospace; margin:0 0.2em}
input[type=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:2px 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}
]]>
for each(let o in [{
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 [tag;[...;] ]comment': noun_arb_text,
alias: {
name: 'hatebu title',
label: 'title',
default: function nt_htb_ttl_default() [this._empty, this._paget],
suggest: function nt_htb_ttl_suggest(txt, htm, cb, sx)
[CmdUtils.makeSugg(txt, htm, null, .2, sx), this._paget],
_empty: {text: '', summary: ' ', score: .5},
get _paget()
let(nf = info()) CmdUtils.makeSugg(nf.title || nf.url, null, null, .4),
},
},
execute: function htbi_execute({object: {text: cmn}, alias: {text: ttl}}){
var bin = this._bin;
var {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());
}
wipe(bin);
},
preview: function htbi_preview(pb, {object, alias: {text: ttl}}){
var {comm, size, tags} = parse(object.text), req = this._req();
var lmn = this._lmn(pb), doc = pb.ownerDocument, bin = this._bin;
var [inp, cnt] = lmn.childNodes;
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.childElementCount && req === bin.preq){
let te = doc.getElementById('title-edit');
if(te) te.style.textDecoration = ttl && 'line-through';
return;
}
if(lmn.classList.contains('loading')) return;
wipe(bin);
var me = this;
ajax(lmn, {url: bin.preq = req}, function htbi_get(htm){
if(!/<form [^]+?<\/form>/.exec(htm)){
if(!~htm.indexOf(HLogin)) return ng(lmn, htm);
login(
function htbi_login_ok() ajax(lmn, {url: req}, htbi_get),
function htbi_login_ng(msg)
ng(lmn, 'Failed to auto-'+ 'login'.link(HLogin) +'. ('+ msg +')'));
return ok(lmn, '<em>Attempting auto-login ...</em>', 'loading');
}
var div = doc.createElement('div');
div.innerHTML = RegExp.lastMatch.replace(/<img src=..images[^>]+>/g, '');
var val, data = bin.data = {};
var [form] = bin.$f = (
$(div.firstChild)
.find('input, textarea').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)
Array.forEach(doc.querySelectorAll('#recommend-tags, #all-tags'),
function(n) n.addEventListener('click', tagplus, false))
if(val && doc.getElementById(me._id)){
with(box()) value = (/\S+/.exec(value) || me.name) +' '+ val;
htbi_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}
#comment, .bookmark-submit-container, .note {display:none}
]]>,
_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.querySelector(n).textContent for each(n in this)])
<><img class="favicon" src={favicon(l)}/>
<a href={l}>{t}</a></> +' '+ entimg(l)),
['title', 'link']),
me.__go);
lmn.className = '';
});
},
__go: function hoten__go(i, e){
Utils.openUrlInBrowser(e.target.parentNode.querySelector('a').href);
},
_css: <![CDATA[ img {vertical-align:middle} ]]>,
_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}){
return !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">',
[t.link(Hatebu + user +'/'+ t +'/') for each(t in tags)],
'</span><span class="comment">', comment,
'</span><span class="timestamp">', timestamp, '</span></li>');
}, '') +'</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(ls){
if(!ls.length) ls[0] = {username: ''};
var ss = [{text: n, summary: n} for each({username: n} in ls)];
return function nt_hatena_id_default() ss;
})(users()),
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, .4, 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){
pb.innerHTML =
'Preview is disabled for now, '+
'due to Firefox 4 having issue accessing <em>search.data</em>.'
return
var me = this, txt = args.object.text, usr = args.source.text;
var {dat} = this._bin, bms = dat[usr], lmn = this._lmn(pb);
if(usr) switch(bms){
case 100: return;
case 403: let msg = 'Private mode: ';
case 404: return ng(lmn, (msg || 'No such user: ') + usr.bold());
case void '':
dat[usr] = 100;
lmn.className = 'loading';
return me._get(dat, usr);
}
lmn.className = '';
if(!txt || !usr) return void this.previewDefault(pb);
var re = Utils.regexp(txt, 'i');
var {txt, num} = bms, len = num.length, cnt = 0, ls = ['<ol>'];
for(var i = offset |= 0; i < len; ++i){
let 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;
let 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">'+
'<input type="button" id="prev" accesskey="," value="&lt;,"/>'+
'<span id="pos">'+ i +'/'+ len +'</span>'+
'<input type="button" id="next" accesskey="." value=".&gt;"/>'+
'</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;
lmn.firstChild.addEventListener('click', function hbs_onfocus(e){
var b = e.target;
if(b.type !== 'button') return;
e.preventDefault();
b.disabled = true;
hbs_preview.call(me, pb, args, b.id === 'next' ? nxt : bms[offset]);
}, false);
},
previewDelay: 222,
_get: function get(dat, usr, ofs){
const Lim = 5e3;
return
var me = this, xhr = $.ajax({
url: Hatebu + usr +'/search.data',
data: {limit: Lim, offset: ofs |= 0},
error: function(x, s){
Utils.log(x, s)
if(/^40[34]$/.test(x.status)) dat[usr] = x.status;
else howl(x, s);
},
success: function(res){
if(/^\s*</.test(res)){
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;
},
}]){
o.name = 'hatebu '+ o.name;
o.icon = Icon;
let lmn = o._lmn || <div> </div>;
let id = lmn.@id = o._id = o.name.replace(/ /g, '-');
XML.prettyPrinting = XML.ignoreWhitespace = false;
let htm = (
'<style>'+ CSS + (o._css || '') +'</style>'+
<div class={id}/>.appendChild(lmn).appendChild(Logo));
delete o._css;
o._lmn = function htb_lmn(pb){
var lmn, ini;
while(!(lmn = pb.ownerDocument.getElementById(id)))
pb.innerHTML = htm, ini = 1;
lmn.ini = !!ini;
return lmn;
};
o._bin || (o._bin = {__proto__: null});
CmdUtils.CreateCommand(o);
}
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.querySelector('.content') || c).innerHTML = h;
c.className = not || '';
return c;
}
function wipe(bin){
for(var k in bin) delete bin[k];
return bin;
}
function info(){
var {defaultView: win, title, URL} = CmdUtils.getDocument(), t, u;
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)}><img src={entry(u, 'image')} border="0"/></a>);
function enc4htb(u) encodeURIComponent(u).replace(/#/g, '%23');
function parse(txt){
var [t, c] = /^\s*(?:[^;\s][^;]*;)*(?=\s*([^]*)\s*$)/.exec(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.removeAttribute('style')
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 +';');
focus();
}
}
function users(url){
url = url || 'https://www.hatena.ne.jp';
var token = (Cc['@mozilla.org/security/pk11tokendb;1']
.getService(Ci.nsIPK11TokenDB)
.getInternalKeyToken());
return (
!token.needsLogin() || token.isLoggedIn()
? (Cc['@mozilla.org/login-manager;1'].getService(Ci.nsILoginManager)
.findLogins({}, url, '', ''))
: []);
}
function login(ok, ng){
const [L] = users();
if(!L) return ng('No saved logins');
$.ajax({
url: HLogin, type: 'POST', data: {name: L.username, password: L.password},
success: ok, error: function(x) ng(x.status +' '+ x.statusText),
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment