はてなキーワード: T-CONとは
気にしてるの私だけかもしれないけど、インプレス系(*.watch.impress.co.jp)の記事だけ、はてなブックマークの記事一覧で概要欄が空欄になってしまってるのが昔から気になっていたので、両社に要望してみた。
例:
(画面右上に3つ並んだビュー選択ボタンから、真ん中の概要付きビューを選ぶと影響が顕著)
(私はこのビューは使ってないけど、独自に当ててるスタイルシートで概要欄の内容を活用している)
要望内容:
はてなブックマーク - はてなブックマークへのフィードバック https://b.hatena.ne.jp/-/feedback/hatena_bookmark インプレス系のメディアにだけ、はてブホットエントリ一覧などのページで p.entrylist-contents-description が空欄になってしまうのが気になっています。インプレスの記事のヘッダに meta[property="og:description"] meta[name="twitter:description"] などは存在するのに、単純な meta[name="description"] が存在していないことが原因でしょうか? 存在しない場合は、meta[property="og:description"] などの妥当な候補を順次探しに行ってもいいのではないでしょうか。 (別途、インプレスにも meta[name="description"] を用意するよう要望してみます) (そもそも、p.entrylist-contents-description が空欄になってしまう理由がこれなのかどうかも自信がありません)
webサービスのほうでブロック機能を用意してほしい所ですが、実際のところ期待できないというのが正直なところです。
また、サービス毎にブラウザ拡張を用意するのも、それはそれで面倒だし複雑なので、uBlock Originのマイフィルターでブロックできるようにしてみました。
! 2025/00/00 https://news.yahoo.co.jp
! 2025/00/00 https://b.hatena.ne.jp/
b.hatena.ne.jp##.entrylist-contents:has-text(/堀江貴文|ホリエモン|西村博之|ひろゆき/)
b.hatena.ne.jp##.entrylist-contents:has(a:is([href*="anond.hatelabo.jp/"], [href*="togetter.com/"]))
! 2025/00/00 https://www.youtube.com/
! [YouTube] Title Keyword Filter
youtube.com##:is(ytd-rich-item-renderer, ytd-video-renderer, ytd-compact-video-renderer, ytd-grid-video-renderer, ytd-playlist-panel-video-renderer):has-text(/堀江貴文|ホリエモン|西村博之|ひろゆき/)
youtube.com##:is(ytd-rich-item-renderer, ytd-video-renderer, ytd-compact-video-renderer, ytd-grid-video-renderer):has(#channel-name:has-text(/堀江貴文|ホリエモン|西村博之|ひろゆき/))
”⼆年少々前に、イギリス政府はアダム・スミス・コナーを起訴しました。”
ちなみに22年前のunborn son.を「生まれぬ息子」と訳すのはいかがなものか。
英国の退役軍人であるアダム・スミスコナー氏(51歳)は、2022年11月、イングランド南部ボーンマスの中絶クリニック近くで数分間黙祷を捧げた際、公共空間保護命令( Public Spaces Protection Order PSPO)に違反したとして起訴されました。
ttps://adfinternational.org/en-gb/news/guilty-army-vet-convicted-for-praying-silently-near-abortion-facility
このPSPOは、2022年10月にボーンマス・クライストチャーチ・プール(BCP)評議会によって導入され、中絶サービスに関連する問題について、抗議や賛否の表明を禁止するものでした。”
PSPOの全文はこちら ttps://www.bcpcouncil.gov.uk/Assets/Crime-safety-and-emergencies/PSPOs/Ophir-Road-and-surrounding-area-Public-Spaces-Protection-Order-PSPO.pdf
スミスコナー氏は、22年前に自身が関与した中絶で失った息子のために祈っていたと述べています。
ttps://www.standard.co.uk/news/crime/bournemouth-christchurch-uk-parliament-army-british-b1188699.html
ttps://www.independent.co.uk/news/uk/crime/christian-bournemouth-christchurch-uk-parliament-army-b2631603.html
2024年10月16日、プール治安判事裁判所は彼に有罪判決を下し、執行猶予付きの判決と9,000ポンド(約170万円)の裁判費用の支払いを命じました。
クリスチャントゥデイ(日本)2024年10月23日
”スミスコナーさんを弁護したキリスト教法曹団体「ADFインターナショナル」英国支部(英語)のジェレマイア・イグヌボル上級法律顧問は、「非常に大きな影響を持つ法的転換点」だとして、次のように述べた。
「今日、ある男性が有罪判決を受けましたが、それはイングランドの公の通りで彼が考えたこと、つまり神への祈りの内容を理由としたものでした。言論や思想の自由という基本的自由をないがしろにするという点において、英国はこれ以上ないほど落ちぶれてしまいました。私たちは判決をよく検討し、控訴するかどうかを検討しています。人権は全ての人に与えられているものであり、中絶に対する考え方とは関係ありません」”
”Human rights are for all people – no matter their view on abortion.”
【前置き】
はてな匿名で書く内容なのかというのはともかく、私が美味しいと思うケーキ屋・パティシエを書き出して、順に並べてみた。私は製菓や料理について専門的な教育を受けたこともないし、生業としてフードライターをやっているわけでもない。仕事柄、堅い文章を書くことにはある程度慣れているけれど、エッセーのような柔らかい文章を書いた経験はほとんどない。
このランキングを作ってみようと思ったのは、怪我をして外を歩く気になれず暇だったからというのが一番の動機だけれど、ランキング作成の過程で「あの時のあのケーキは本当においしかったな」と幸せな思い出を振り返ることができたし、「自分はこういうケーキを美味しいと思っているのか」という傾向を何となく知ることができて面白かった。
このランキングは、私の個人的な好みに基づいて、偏差値表のような形で作成している(もちろん偏差値そのものは表していない。)。偏差値50を超えるケーキは基本的にどれも文句なく美味しいし、そこまで確固たる優劣があるとは思わない。特に、偏差値60-65のレンジはその日の気分によって順番が変わるだろうと思う。比較的趣向が近い友人に意見を聞いても、結構な食い違いがあったので、本当にその人の好み次第ということなのだと思う。また、日頃からメモを取っているわけでもなく、思い出した順に書きだしただけなので、ランキングに載っていて当然なのに書き落としているものもたくさんあると思う。
そんな感じで免責みたいなものを書き連ねたところで、ランキングをご覧ください。
80: Maxime Frédéric (Le Tout-Paris, Maxime Frédéric chez Louis Vuitton) (Paris)
79: Matthieu Carlin (Butterfly Pâtisserie at Hôtel de Crillon) (Paris), François Perret (Ritz Paris Le Comptoir) (Paris)
78:
77:
76:
75: Mori Yoshida (Paris), Cedric Grolet (Paris)
74:
73: [xxxxxxxxxxxx] (京橋)
71:
69: ASSEMBLAGES KAKIMOTO (京都), pàtisserie Tendresse (京都)
68:
67:
66: FOBS (蔵前), Les Alternatives (東小金井), Pâtisserie K-Vincent (神楽坂)
65: La Pâtisserie Cyril Lignac (Paris), Equal (幡ヶ谷), LE CAFE DU BONBON (代々木八幡)
64: Libertable(赤坂), grains de vanille (京都), Ryoura (用賀), Dining 33 Pâtisserie à la maison (麻布台), おかしやうっちー (北参道)
63: Yann Couvreur Pâtisserie (Paris), La Pâtisserie Ryoco (高輪), Fleurs d’été (代々木上原), Harmonika Kyoto (京都), sweets garden YUJI AJIKI (神奈川), Sweet Rehab (NY)
62: PIERRE HERMÉ PARIS, AU BON VIEUX TEMPS (尾山台), Paris S'éveille (自由が丘), AIGRE DOUCE (目白), CONFECT-CONCEPT (稲荷町), Paris S'éveille (自由が丘), AIGRE DOUCE (目白),
61: équilibre (不動前), Pâtisserie ease (日本橋), 銀座和光 (銀座), Palace Hotel Tokyo (大手町),その他美味しいホテル系, Asterisque (代々木上原), a tes souhaits! (吉祥寺), N'importe quoi (京都)
60: Avranches Guesnay (春日), Taisuke Endo (学芸大学), Préférence (新中野), feuquiage (調布), PÂTISSERIE ASAKO IWAYANAGI (等々力)
[...]
55: 千疋屋総本店, Henri Charpentier, その他デパ地下系・工場系
[...]
[...]
45: Lysee (NY)
[...]
【いくつか補足】
パリには複数回行ったことがあるが、パリのケーキは本当に美味しく感じた。もちろん遠路はるばる補正・思い出補正もあるのだと思うけれど、単純に値段がとても高いので、惜しむことなく良い材料を使えているのではないかと思う。パティシエの社会的地位が高いらしいので、その結果パティシエが制約なしで好きなようにケーキを作れる、ということもあるのかもしれない。
Maxime Frédéricは有名人だし、本当にどのケーキ(あと、ややテーマ外だけれど、レストランのデザートも)も美味しい。いつかホテルのシグネチャーレストランにも行ってみたい。Matthieu CarlinやFrançois Perretは、少なくとも私のアンテナでは情報がたくさん入ってくる感じではないけれど、複数のケーキで感動した。
Cedric GroletはMaxime Frédéricよりも有名人で、逆張りしてみたくはなるのだけれど、フルーツの使い方が独創的で、まあ確かに美味しい。あの値段であれだけの人を集められるのは、SNS映えだけでは説明できないと思う。
[xxxxxxxxxxxx]は、フルーツに真摯に向き合っていることや高い洋菓子基礎力に裏打ちされていることが素人の私にも伝わってくるような気がする。接客は独特で毎回緊張するけれど、敬意をもって謙虚に臨めば、丁寧に遇してくれるし、嫌な思いをしたことはない。職人ってそういうものなのかな、と思っている。
LESSは、以前はnomaやINUAみたいな雰囲気の実験的なケーキが多くて毎回面白かったのだけれど、最近はそれよりは分かりやすいケーキが多くなった。もちろん、それらもとても美味しい。たまに昔のLESSを食べてみたくなるときもある。
タンドレスは、もともと酒やムースとかのイデミスギノ的なものがあまり得意ではない私でも、これは凄いと感じる何かがある。私では理解が及ばないというか、置いて行かれている感じもする。
FOBSは一般的にはすごく高く評価されている感じはしないものの(ゴーフレットはとても有名)、ここのショートケーキは特に美味しいと思う。卵や牛乳などの素材がシンプルによくまとまっている感じがする。判官贔屓的なものが入っているかもしれないけれど。
Alternativeは夏の焼き菓子イベントのタルト類が全て美味しい。Equalのチーズケーキは瑞々しくてチーズの香りがよく整っていて、これより美味しいチーズケーキはなかなかないと思う。「フランス菓子道」の真ん中・正当を通ってきたかはよく分からないけれど、個人的な好みに合っている。
Ryouraは街のケーキの範疇で、傑出して美味しい。うっちーのシグネチャーのショートケーキの生クリームのミルク感は特徴的。
リョーコは(伝統的という意味ではなく)ちょっと古い感じがするけれど、分かりやすく美味しいと思う。セブンイレブンとのコラボレーションは。。カーヴァンソンやリベルターブルも私の中では同じような枠に入っているのだけれど、こちらの方が個人的な好みに合っている(何でだろう)。
étéをどう位置づけるかは難しかった。テンションの上がる見た目だし確かに美味しいのだけれど、洋菓子としての工夫・洗練を感じるかというと、そういうことを目指して作られたものでもないような気がする。
ピエールエルメのイスパハンは、なんだかんだといってやっぱり名作だと思う。ホテル系のちゃんと作られたケーキは、やや割高な感じはするものの、総じてしっかり美味しい。
easeは個人的な好みにはあまりハマらないけれど、美味しいし評価されていることもわかる。アステリスクやアテスウェイは家族で食べるのにとてもよいと思う。ナンポルトクワのリンゴのタルトは一つの発明だと思う(別途元祖があるのかな)。パリセヴェイユはフランス菓子の正当という感じで間違いなく美味しいのだけれど、個人的に好きなケーキとは少し違う(パンや焼き菓子はすごく好み)。
アメリカでウケているケーキは、個人的にはそこまでヒットしなかった。Sweet Rehabは見た目もきれいですごく高く評価されているけれど、高額すぎる点を措いても、評価に見合うほどではない気がする。
店名が大文字だったり小文字だったり「pàtisserie」が付いたり付かなかったりするのは、適当にgoogle検索した結果をそのまま引き写しているだけなので、正確ではないかもしれない。
こういう番付モノは、それ自体を批評の対象として「それは違う、こっちの方が上だ」等々とケチをつけることに楽しさがあると思う。コメントをいただけたら嬉しい。
【追記1209】
記事を公開した後、思ってもいなかった程たくさんの方に見ていただいて、色々なコメントをいただいた。一つ一つどれもありがたいと思っている。
書き忘れていたお店を何個か思い出したので、適宜ランキングに追加してみた(Asako Iwayanagi, AU BON VIEUX TEMPS, CONFECT-CONCEPT, Dining 33 Pâtisserie à la maison, équilibre, Libertable, sweets garden YUJI AJIKI, Patisserie Porte Bonheur)。また、説明部分にもいくつか加筆をした。加筆するにあたっても、既に順番を書き換えた方がいいのではないかという気がしてくるから、やっぱりその時の気分次第の順に過ぎないんだなと分かった。
「偏差値表のような形」というのが分かりにくいというコメントがあった。イメージしていた偏差値表は、例えばこんな感じのものだった。
https://www.syutoken-mosi.co.jp/application/hensachi/upload/dansi202412.pdf
もちろん、今回は母集団が正規分布かもよく分からないし平均や分散を求めて何かしているわけではない。あくまで偏差値「風」ということでご容赦いただきたい。今回は、偏差値50の重責をローソンに担ってもらった。個人的に偏差値(風)60以下かなと思ったものは、角が立つので基本的には取り上げなかった(NYでいくら角が立っても大丈夫。)。
神奈川・千葉・埼玉などの郊外や大阪・神戸のような別の都市圏はあまり開拓できていないので、いつか掘り下げてみたい。
中野のMORI YOSHIDAについては、どう取り上げるか迷った。例えば、パリで食べたババトロピックはミルクが入ったスポイトのようなものが刺さっていて、それを注入して食べるのがとても美味しかった記憶がある。中野の方はそうはなっていなくて、少なくとも何らかの「違い」はあるのだと思う(見た目に明らかに分かるもの以外も多分。)。ただ、だからといって劣るとかいうわけでもないし、でもそうすると今度は、中野のMORI YOSHIDAとLESSに大きな差があるかのようになるけれど、本当にそうなのかというとよく分からない。突き詰めていくと、遠路はるばる補正・思い出補正を数値化することにもつながりそうで、それは楽しい思い出のためにも止めておいた方がよいかなと思い、取り上げないことにしていた。
FANZAの検索結果から熟女を除外するブックマークレット がうまく動いたので、気を良くしてはてなブックマークのコメント欄をスター数順にソートするブックマークレットを作りました。
作った動機は、「注目コメントに入りきれなかったちょっといいコメント」をサクサク探したいから。結果として建設的コメント順位付けモデルを無効化していますが、あのアルゴリズムには特に不満は特にありません。
javascript: (async () => {
const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
document.querySelector('.js-bookmarks-sort-tab[data-sort="recent"]').click();
window.scrollTo(0, document.body.scrollHeight);
await wait(1000);
window.scrollTo(0, 0);
await wait(1000);
const p = document.querySelector('.js-bookmarks-recent');
let l = Array.from(p.querySelectorAll('.entry-comment-contents'));
const g = e => {
let n = e.querySelectorAll('.hatena-star-star').length;
const c = e.querySelector('.hatena-star-inner-count');
return c ? n + Number(c.textContent) : n
};
l = l.filter(e => g(e) > 0);
l.sort((a, b) => g(b) - g(a));
p.replaceChildren(...l);
})();
ミニファイしたもの ※コードに一部誤りがありましたので訂正しました(2024-08-16 11:47)
javascript:(async()=>{const wait=ms=>new Promise(resolve=>setTimeout(resolve,ms));document.querySelector('.js-bookmarks-sort-tab[data-sort="recent"]').click();window.scrollTo(0,document.body.scrollHeight);await wait(1000);window.scrollTo(0,0);await wait(1000);const p=document.querySelector('.js-bookmarks-recent');let l=Array.from(p.querySelectorAll('.entry-comment-contents'));const g=e=>{let n=e.querySelectorAll('.hatena-star-star').length;const c=e.querySelector('.hatena-star-inner-count');return c?n+Number(c.textContent):n};l=l.filter(e=>g(e)>0);l.sort((a,b)=>g(b)-g(a));p.replaceChildren(...l)})()
FANZAの検索結果から熟女を除外するブックマークレット 参照
javascript:
ブックマークレットに必要な、URLの種類を示すスキーム名です。
(async () => {
// 処理
})();ページに元々ある変数たちとバッティングしないように、まず無名関数でラップします。処理の中で await を使いたいので async 宣言しています。
const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
document.querySelector('.js-bookmarks-sort-tab[data-sort="recent"]').click();
window.scrollTo(0, document.body.scrollHeight);
await wait(1000);「新着コメント」タブをクリックし、ページの一番下までスクロールダウンしてから少し待つ動作です。新着コメントの後半部分(スクロールきっかけの遅延読み込みになっているところ)の読み込みをうながしています。
window.scrollTo(0, 0);
await wait(1000);
ページの先頭に戻ってまた少し待ちます。合計2秒の待ち時間は雰囲気で決めていますので、これでなければならない・これで過不足ないという値ではありません。単にコメントの読み込み完了を判定する処理を書くのがめんどうだっただけです。
const p = document.querySelector('.js-bookmarks-recent');
新着ブコメの親要素です。繰り返し呼び出すので名前をつけています。
let l = Array.from(p.querySelectorAll('.entry-comment-contents'));
const g = e => {
let n = e.querySelectorAll('.hatena-star-star').length;
const c = e.querySelector('.hatena-star-inner-count');
return c ? n + Number(c.textContent) : n
};
コメントのはてなスター数をカウントして返す関数です。たくさんスターがついてる ★256★ みたいなやつの数字も足します。
l = l.filter(e => g(e) > 0);
ソートする前に、無スターのコメントを消去しています。してもしなくてもいいことですが。
l.sort((a, b) => g(b) - g(a));
残ったコメントをスター数で降順ソートします。.querySelectorAll() で収集した要素を配列に入れ直したのは、この .sort() メソッドを使いたいからです(.querySelectorAll() が返す配列風の NodeList オブジェクトは、配列と共通のメソッドもいくつかあるものの、大半は使えないのです)。
p.replaceChildren(...l);
親要素の内容を、並び替えの終わったコメントでそっくり入れ替えて、処理完了です。画面を見ると新着コメントの中身が「スターのついたコメントのみ・スターの多い順」に並んでいます。元に戻す方法はないので、原状回復にはリロードします。ソート状態を示すフラグを立てておいてスター数ソート⇔日付ソートをかわりばんこに行うようにすればできそうだなと思ったけど実装はしません。連打スターを省く処理を追加してUU数でソートできればもっと厳正なランキングになるなーと今思いつきましたがそれも実装はしません。
パソコン画面右上のアイコンで選ぶ表示スタイルを一番右の「ヘッドライン」表示にしといてな
/* ヘッドライン表示を切り詰める */ /* #container 指定でCSS優先度を上げる必要がある */ body[data-entrylist-layout="headline"] #container .entrylist-main{ padding-right: 0 !important; } body[data-entrylist-layout="headline"] #container .entrylist-contents{ padding-left: 0 !important; } body[data-entrylist-layout="headline"] #container .entrylist-contents-users{ position: static !important; } body[data-entrylist-layout="headline"] #container .entrylist-contents-users{ top: 14px !important; } /* ヘッドライン表示にサムネイルを追加 */ body[data-entrylist-layout="headline"] #container .entrylist-contents-main{ display: grid; grid-template: "users body title" 28px "bookmark body domain" 20px / 60px 120px 1fr; } body[data-entrylist-layout="headline"] #container .entrylist-contents-users{ grid-area: users; } body[data-entrylist-layout="headline"] #container .entrylist-contents-users a span{ margin-right: 0; } body[data-entrylist-layout="headline"] #container .following-bookmarks-container{ grid-area: bookmark; position: absolute; left: 20px; bottom: 2.5px; } body[data-entrylist-layout="headline"] #container .entrylist-contents-body{ grid-area: body; } body[data-entrylist-layout="headline"] #container .entrylist-contents-title{ grid-area: title; z-index: 99; } body[data-entrylist-layout="headline"] #container .entrylist-contents-title > a{ margin-left: -120px; padding-left: 120px; margin-bottom: -28px; padding-bottom: 28px; width: 890px; white-space: nowrap; display: block; } body[data-entrylist-layout="headline"] #container .entrylist-contents-body{ display: block !important; } body[data-entrylist-layout="headline"] #container .entrylist-contents-thumb{ position: static; } body[data-entrylist-layout="headline"] #container .entrylist-contents-thumb span{ width: 100px; height: 50px; } body[data-entrylist-layout="headline"] #container .entrylist-contents-thumb{ background: #f0f0f0; width: 100px; height: 50px; background-position: 50%; background-size: cover; border-radius: 4px; } /* 2行目に、総合ではドメイン(domain), サイト内一覧ではカテゴリと時刻(meta), マウスホバー時はいずれも概要文(description) */ body[data-entrylist-layout="headline"] #container .entrylist-contents-domain, body[data-entrylist-layout="headline"] #container .entrylist-contents-meta, body[data-entrylist-layout="headline"] #container .entrylist-contents-description{ grid-area: domain; display: block; opacity: 0; padding: 0 !important; } body[data-entrylist-layout="headline"] #container .entrylist-contents-meta > li{ vertical-align: top; } html[data-stable-request-url^="https://b.hatena.ne.jp/entrylist/"] body[data-entrylist-layout="headline"] #container .entrylist-contents-domain, html[data-stable-request-url^="https://b.hatena.ne.jp/site/"] body[data-entrylist-layout="headline"] #container .entrylist-contents-meta{ opacity: 1; } body[data-entrylist-layout="headline"] #container .entrylist-contents:hover .entrylist-contents-domain img.favicon + span, body[data-entrylist-layout="headline"] #container .entrylist-contents:hover .entrylist-contents-meta{ opacity: 0; } body[data-entrylist-layout="headline"] #container .entrylist-contents-description{ opacity: 0; position: absolute; top: calc(40px - 3px); left: calc(180px + 16px + .5em); height: 20px; line-height: 20px; color: #999; min-height: auto !important; padding-right: 0 !important; width: 890px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } html[data-stable-request-url^="https://b.hatena.ne.jp/site/"] body[data-entrylist-layout="headline"] #container .entrylist-contents:hover .entrylist-contents-domain, body[data-entrylist-layout="headline"] #container .entrylist-contents:hover .entrylist-contents-description{ opacity: 1; } /* 増田調整 */ body[data-entrylist-layout="headline"] #container a[href^="/entry/s/anond.hatelabo.jp/"] .entrylist-contents-thumb{ background-image: url('https://cdn-ak-scissors.b.st-hatena.com/image/square/b1638cdb5807a4788e4ba3c1109a984166e095fc/height=288;version=1;width=512/https%3A%2F%2Fanond.hatelabo.jp%2Fimages%2Fog-image-1500.gif'); } /* マウスホバー時にサムネも反応させる見た目調整 */ .entrylist-contents-title:hover ~ .entrylist-contents-body .entrylist-contents-thumb{ opacity: .90; }
松本人志以外も消える
// ==UserScript== // @name hatebu-matsumoto // @version 1 // @grant none // @include https://b.hatena.ne.jp/ // ==/UserScript== var startTime = new Date().getTime(); var t = setInterval( () => { const list = document.querySelectorAll(".entrylist-contents-thumb,.entrylist-issue-thumb"); list.forEach(x => x.style.opacity = "0"); if (new Date().getTime() - startTime > 10000) { clearInterval(t); } }, 10 );
document.cssRulesを加工するかMutationObserverを使えよ。 これだと一瞬松本の顔が映るだろ
https://b.hatena.ne.jp/site/anond.hatelabo.jp
で動くスクリプトでたとえば投稿後10分以内にブクマされページに乗ったら「1 user」が「1 user セルクマ 1とか5(何分後にブクマされたか)」になる。もしマイナスなら誤判定なので無視して。
時間を置いたセルクマには効かないし普通のファーストブクマカがどれぐらいの頻度で確認してるかしらないけど5分以内や1分以内もポロポロあるのでまあ目安に。
.forEach(div => {
('.entrylist-contents-title > a')
とかの
を
<>
に変えてね
他にも見落としあるかも
誤判定が減るから非公開ファーストブクマを判定できたらいいんだけどね。
// ==UserScript== // @name hatebu masuda selkmark // @namespace http://tampermonkey.net/ // @version 0.1 // @description 特定時間以内にブクマされた増田を強調する // @author You // @match https://b.hatena.ne.jp/site/anond.hatelabo.jp* // @grant none // ==/UserScript== (function() { 'use strict'; const threshold = 60 * 10 // 何秒以内か const domain = 'https://anond.hatelabo.jp/' const dateTemplate = '202301020304' // 時分まで urlの時刻表記 const dateTest = new RegExp('\\d{' + dateTemplate.length + '}') document.querySelectorAll('div.entrylist-contents').forEach(div => { const masuda = div.querySelector('.entrylist-contents-title > a') const dateStr = masuda.href.substring(domain.length + dateTemplate.length, domain.length) if (!dateTest.test(dateStr)) { // キーワードとか console.log('not diary', dateStr) return } // new Dateできるように変換 // https://amateur-engineer.com/javascript-date-yyyymmddhhmm/ const year = parseInt(dateStr.substring(0, 4)) const month = parseInt(dateStr.substring(4, 6)) const day = parseInt(dateStr.substring(6, 8)) const hour = parseInt(dateStr.substring(8, 10)) const min = parseInt(dateStr.substring(10, 12)) const date = new Date(year, month - 1, day, hour, min) const bukumaDate = new Date(div.querySelector('.entrylist-contents-date').textContent) // 2023/01/23 00:00 const diffSec = (bukumaDate - date) / 1000 // ms to sec if (diffSec > threshold) { return } // ブクマ数 user const user = div.querySelector('span.entrylist-contents-users a').lastChild user.textContent += ' セルクマ ' + (diffSec / 60) // 古い記事がマイナスになる でも2015年ぐらいの記事までかな?新着はセーフ臭い /* if(diffSec < 0) { user.textContent += ' 異常差分:' + diffSec } */ }) })();
非表示機能で完全に無視したいって程でもないんだよな〜っていう。
タイプ別色分け機能とかこういうののはてなスター版とかも欲しい。面倒だから誰か書いて。
// ==UserScript== // @name hatebu_comment_usuku_hyouji // @version 1 // @grant none // @include https://b.hatena.ne.jp/entry/* // ==/UserScript== const userIdList = [ 'hoge', 'piyo', ... ]; setInterval(() => { document.querySelectorAll('.entry-comment-contents').forEach((element, i) => { if (userIdList.includes(element.dataset.userName)) { element.style.opacity = '0.3'; } }); }, 3000);
正直動けばいいと思うので、現状でも問題ないと思うけれどちょっとだけ気になった点。
$$使ってる時点で、デベロッパーツール前提なのは分かるけど、 javascriptで動かしたいのか、デベロッパーツールで動かしたいのか、どっちつかずな書き方になっている。
$$('.hatena-star-inner-count').map(a => a.click());
let data = {};
Array.from($$('.entry-comment-contents')).forEach(x => {const userName = x.querySelector('.entry-comment-username a').href.split('/')[3];const comment = x.querySelector('.entry-comment-text').innerText;const stars = Array.from(x.querySelectorAll('.list-star-container a.hatena-star-star')).map(y => y.href.split('/')[3]);data[userName] = [comment, stars];});
console.info(Object.entries(data).map(x => { return ['|', `b:id:${x[0]}`, '|', x[1][0], '|', x[1][1].join(','), '|'].join(' '); }).join("92;n"));
デペロッパーツールだけで動くだけでいいなら、3行目のArray.fromがなくても動く。多分、forEachをmapに書き換えても動く。個人的にはforEachに統一したいけど、そこは好みレベルの問題だと思う。
$$('.hatena-star-inner-count').map(a => a.click());
let data = {};
$$('.entry-comment-contents').forEach(x => {const userName = x.querySelector('.entry-comment-username a').href.split('/')[3];const comment = x.querySelector('.entry-comment-text').innerText;const stars = Array.from(x.querySelectorAll('.list-star-container a.hatena-star-star')).map(y => y.href.split('/')[3]);data[userName] = [comment, stars];});
console.info(Object.entries(data).map(x => { return ['|', `b:id:${x[0]}`, '|', x[1][0], '|', x[1][1].join(','), '|'].join(' '); }).join("92;n"));
逆に、javascriptで動かしたいなら1行目と3行目はquerySelectorAllに書き換えれる。
document.querySelectorAll('.hatena-star-inner-count').forEach((a)=>{a.click()});
let data = {};
document.querySelectorAll('.entry-comment-contents').forEach(x => {const userName = x.querySelector('.entry-comment-username a').href.split('/')[3];const comment = x.querySelector('.entry-comment-text').innerText;const stars = Array.from(x.querySelectorAll('.list-star-container a.hatena-star-star')).map(y => y.href.split('/')[3]);data[userName] = [comment, stars];});
console.info(Object.entries(data).map(x => { return ['|', `b:id:${x[0]}`, '|', x[1][0], '|', x[1][1].join(','), '|'].join(' '); }).join("92;n"));
これも好みの問題だけど、途中のconstの宣言は一回しか使ってないので、宣言せずにそのまんま入れてもいいんじゃないかと思った。
document.querySelectorAll('.hatena-star-inner-count').forEach((a)=>{a.click()});
let data = {};
document.querySelectorAll('.entry-comment-contents').forEach(x => {data[x.querySelector('.entry-comment-username a').href.split('/')[3]] = [x.querySelector('.entry-comment-text').innerText, Array.from(x.querySelectorAll('.list-star-container a.hatena-star-star')).map(y => y.href.split('/')[3])];});
console.info(Object.entries(data).map(x => { return ['|', `b:id:${x[0]}`, '|', x[1][0], '|', x[1][1].join(','), '|'].join(' '); }).join("&#92;n"));
|<<
javascript:document.querySelectorAll(".entry-comment-contents").forEach(function(e){if(e.innerText.indexOf('🐻')!=-1||e.innerText.indexOf('ʕ•̫͡•ʔ')!=-1){e.remove()}})
ブックマークレットのクリックが面倒な人は、GreasemonkeyかTampermonkeyで。
他のパターンのコメント削除したい人は『||e.innerText.indexOf('削除したいコメントに含まれる文字列')!=-1』を修正なり、追加なりでどうぞ。
何か要望があれば、はてブコメント(ただし熊系は消してます)か言及でどうぞ。気が向いたら対応します。
なんか漏れてるのがある。文字列の方は、もっと短くした方が良さそう。ただ、これでも漏れてるのがありそうで、もし多様されそうなら対策の調査か、はてブユーザーの非表示機能を使おう。一応、サンプルの提供感謝。
javascript:document.querySelectorAll(".entry-comment-contents").forEach(function(e){if(e.innerText.indexOf('🐻')!=-1||e.innerText.indexOf('•̫͡')!=-1){e.remove()}})
>id:kako-jun クマで後半を埋めてるコメントで、前半は残したいので、正規表現にして後置のクマだけ消してほしい
こんなんでどうだろう。『正規表現にして後置のクマ』が面倒なので、『1文字目に熊がある場合は削除対象外』で。あと『クマだけ消す』も不十分だと思うので、『replace(/a|b|c|d/g, '')』のabcdの箇所を必要に応じて修正して。
javascript:document.querySelectorAll(".entry-comment-text").forEach(function(e){if(2<e.innerText.indexOf('🐻')||2<e.innerText.indexOf('•̫͡')){e.innerHTML=e.innerHTML.replace(/🐻|ʕ|•|̫͡|̫•|ʔ/g, '');}})
ただ、増田の仕様で『<>(大なり小なり)の半角』が使えないので、上記の<は<の半角に適宜変換してください。
id:kako-jun 氏が、自身で作られてました。
https://b.hatena.ne.jp/entry/4706344345181168386/comment/kako-jun
実行すると、各記事を
{
users:ブクマ数,
tags:[タグ]
}
の形式に変換し、500ブクマ以上でフィルタし、ブクマ数降順で返す。
#一行版
curl -s https://b.hatena.ne.jp/hotentry/it | pup --charset utf-8 'div.entrylist-contents-main json{}' | jq -r '[.[] | {title: (.. | select(.class? == "entrylist-contents-title") | .children[].title), url: (.. | select(.class? == "entrylist-contents-title")) | .children[].href, users: (.. | select(.class? == "entrylist-contents-users") | .children[].children[].text | tonumber), tags: ([.. | select(.class? == "entrylist-contents-tags") | .children[]?.children[]?.text])}] | unique | map(select(.users >= 500)) | sort_by(.users) | reverse'
#変数版
title='title: (.. | select(.class? == "entrylist-contents-title") | .children[].title)' users='users: (.. | select(.class? == "entrylist-contents-users") | .children[].children[].text | tonumber)' url='url: (.. | select(.class? == "entrylist-contents-title")) | .children[].href' tags='tags: ([.. | select(.class? == "entrylist-contents-tags") | .children[]?.children[]?.text])' target='https://b.hatena.ne.jp/' hotentry='hotentry/it' curl -s $target$hotentry | ¥ pup --charset utf-8 'div.entrylist-contents-main json{}' | ¥ jq -r "[.[] | {${title}, ${url}, ${users}, ${tags}}] | unique | map(select(.users >= 500)) | sort_by(.users) | reverse"
anond:20180523215832 のついで。
同じくuBlock OriginのMy Filtersを使う。
コメントやタグに「死ねばいいのに」という文章が含まれるブコメを隠したい場合
「Dummy_ID」によるブコメをブロックしたい場合、同じくMy Filtersで、
hatena.ne.jp##.entry-comment-contents:has(a[href*="Dummy_ID"])
を追加すれば消えるのだけど、これだとDummy_IDにスターを付けられたブコメまで消えてしまう。もうちょっと調べればなんとかなりそう。
ここのところ思い出したかのように時々リニューアルが行われてるはてなブックマークのデザインだけどさ。
少し前にリニューアルされたお気に入りページは、#left-container #center-container #right-container って、要するにdivタグにidを振り分けて3ペインのデザインを実現しているんだけれども、先日リニューアルされたユーザーのブックマーク一覧ページは .left-container .center-container .right-container って、divタグにclassを振り分けて3ペインのデザインを実現しているのね。
こういう全体で1度しか使わないデザイン上の区切りを指定するのにidを振るかclassを振るかってのは正直好みの問題でもあって、header,footer的なものはidだから3ペインもidだろとか、優先順位とか管理しやすいからclassでもいいだろとか宗教問題にもなりそうではあるんだけれど、同じ会社が作っている同じサービスのほとんど同じページ表示に別の形式での記述がされているのはデザイナー間での事前定義と言うか意思疎通がされていないってことでもあってかなりマズいことじゃないのかな。
何かリニューアルするたびに「まともに仕事できないのかはてなのデザイナーは」って揶揄されるけど、実際はてなのデザイナーチーム、チーム内で大変なことになってるんじゃないか。経営陣その辺ちゃんと把握してるか?
ずっと旧ページ使ってて、どうにも窮屈な感じがしたので。
.wrapper-container-inner { box-sizing: border-box; width: 100%; padding: 20px 20px 0; background-image: none; } #right-container { display: none; } #center-container { box-sizing: border-box; padding: 0 0 0 20px; width: calc(100% - 180px); }
右カラムは消した。
あくまで広くしただけ。
幅が広すぎる!って場合は最後の width: calc(100% - 180px); にある100%の値を調整すればいい。
なんで PC 画面がワイド化してんのに、ブコメ表示部がこんなに幅狭いんだよ……。というわけで、お気に入りページのサイドバーとサムネイル消して表示幅を広げるユーザースタイルシート。
.wrapper-container-inner { width: 90%; } #center-container { width: 100%; } #left-container, #right-container, .entry-image-block { display: none; } .wrapper-container-inner.left-column-line { background-image: none; } .entry-title, .entry-block blockquote, .entry-data, .user-comment-meta, .starContainer { display: inline; } .profile-image { width: 1.2em !important; height: 1.2em !important; }
縦線の消し方をブコメで教わりました。ありがとうございます! > id:ikihaji_kun
要約文を消してさらに縦を圧縮するなら、以下を追加すればよいですね。
.entry-block blockquote { display: none; }
.entry-summary { display: none }
http://kago.in/portal/index.php?url=<>
Warning: file_get_contents(http://api.pathtraq.com/pages?url=<>&m=popular) [function.file-get-contents]: failed to open stream: HTTP request failed! HTTP/1.1 400 Bad Request in /virtual/kago/public_html/portal/index.php on line 25 Warning: file_get_contents(http://api.search.yahoo.co.jp/ImageSearchService/V1/imageSearch?appid=_DG85xqxg66_I.XOwI8HXsFIoVCrj7wuyv4HstVVzMq.Eq5NFShOxteUK6FlXyPB9BFCYw--&query=<>&results=50) [function.file-get-contents]: failed to open stream: HTTP request failed! HTTP/1.1 400 Bad Request in /virtual/kago/public_html/portal/index.php on line 32 Warning: file_get_contents(http://www.2chsearch.net/api/list?q=<>&mode=html) [function.file-get-contents]: failed to open stream: HTTP request failed! HTTP/1.1 500 Internal Server Error in /virtual/kago/public_html/portal/index.php on line 42
インスパイヤ元 - http://anond.hatelabo.jp/20080219145538
@-moz-document domain("b.hatena.ne.jp") { ul#bookmarked_user{ font-size: 105%; } #bookmarked_user li{ list-style-type: decimal; } #bookmarked_user li:before{ content: '\FF1A'; } #bookmarked_user .timestamp:before{ content: '\756A\7D44\306E\9014\4E2D\3067\3059\304C\306F\3066\306A\3067\3059\FF1A '; font-size: 110%; font-weight: bold; color: #008000; } #bookmarked_user img.hatena-id-icon, #bookmarked_user .user-tag, #bookmarked_user .hatena-star-comment-container, #bookmarked_user .hatena-star-star-container{ display: none; } #bookmarked_user a[href*='bookmark-']:before{ content: 'ID:'; } #bookmarked_user a[href*='bookmark-']:link, #bookmarked_user a[href*='bookmark-']:hover{ color: black; text-decoration: none; } #bookmarked_user a[href*='bookmark-']:hover{ color: blue; text-decoration: underline; } #bookmarked_user span.comment{ display: block; padding: 8px 1em; margin-left: 1.5em; } }
曜日の表記と「2008年02月19日」を「2008/02/19」にしたかったが、方法が分からなかった。あ、あと投稿時間も。Greasemonkey じゃないと無理か?
転載・改変は自由なので、再利用したいとかここを変えた方がより良い、という方は好きに使って結構です。
→ はてブちゃんねる
/* Ten */
if (typeof(Ten) == 'undefined') {
Ten = {};
}
Ten.NAME = 'Ten';
Ten.VERSION = 0.06;
/* Ten.Class */
Ten.Class = function(klass, prototype) {
if (klass && klass.initialize) {
var c = klass.initialize;
} else if(klass && klass.base) {
var c = function() { return klass.base[0].apply(this, arguments) };
} else {
var c = function() {};
}
c.prototype = prototype || {};
c.prototype.constructor = c;
Ten.Class.inherit(c, klass);
if (klass && klass.base) {
for (var i = 0; i < klass.base.length; i++) {
var parent = klass.base[i];
if (i == 0) {
c.SUPER = parent;
c.prototype.SUPER = parent.prototype;
}
Ten.Class.inherit(c, parent);
Ten.Class.inherit(c.prototype, parent.prototype);
}
}
return c;
}
Ten.Class.inherit = function(child,parent) {
for (var prop in parent) {
if (typeof(child[prop]) != 'undefined' || prop == 'initialize') continue;
child[prop] = parent[prop];
}
}
/*
// Basic Ten Classes
**/
/* Ten.JSONP */
Ten.JSONP = new Ten.Class({
initialize: function(uri,obj,method) {
if (Ten.JSONP.Callbacks.length) {
setTimeout(function() {new Ten.JSONP(uri,obj,method)}, 500);
return;
}
var del = uri.match(/\?/) ? '&' : '?';
uri += del + 'callback=Ten.JSONP.callback';
if (!uri.match(/timestamp=/)) {
uri += '&' + encodeURI(new Date());
}
if (obj && method) Ten.JSONP.addCallback(obj,method);
this.script = document.createElement('script');
this.script.src = uri;
this.script.type = 'text/javascript';
document.getElementsByTagName('head')[0].appendChild(this.script);
},
addCallback: function(obj,method) {
Ten.JSONP.Callbacks.push({object: obj, method: method});
},
callback: function(args) {
// alert('callback called');
var cbs = Ten.JSONP.Callbacks;
for (var i = 0; i < cbs.length; i++) {
var cb = cbs[i];
cb.object[cb.method].call(cb.object, args);
}
Ten.JSONP.Callbacks = [];
},
MaxBytes: 8000,
Callbacks: []
});
/* Ten.XHR */
Ten.XHR = new Ten.Class({
initialize: function(uri,opts,obj,method) {
if (!uri) return;
this.request = Ten.XHR.getXMLHttpRequest();
this.callback = {object: obj, method: method};
var xhr = this;
var prc = this.processReqChange;
this.request.onreadystatechange = function() {
prc.apply(xhr, arguments);
}
var method = opts.method || 'GET';
this.request.open(method, uri, true);
if (method == 'POST') {
this.request.setRequestHeader('Content-Type',
'application/x-www-form-urlencoded');
}
var data = opts.data ? Ten.XHR.makePostData(opts.data) : null;
this.request.send(data);
},
getXMLHttpRequest: function() {
var xhr;
var tryThese = [
function () { return new XMLHttpRequest(); },
function () { return new ActiveXObject('Msxml2.XMLHTTP'); },
function () { return new ActiveXObject('Microsoft.XMLHTTP'); },
function () { return new ActiveXObject('Msxml2.XMLHTTP.4.0'); },
];
for (var i = 0; i < tryThese.length; i++) {
var func = tryThese[i];
try {
xhr = func;
return func();
} catch (e) {
//alert(e);
}
}
return xhr;
},
makePostData: function(data) {
var pairs = [];
var regexp = /%20/g;
for (var k in data) {
var v = data[k].toString();
var pair = encodeURIComponent(k).replace(regexp,'+') + '=' +
encodeURIComponent(v).replace(regexp,'+');
pairs.push(pair);
}
return pairs.join('&');
}
},{
processReqChange: function() {
var req = this.request;
if (req.readyState == 4) {
if (req.status == 200) {
var cb = this.callback;
cb.object[cb.method].call(cb.object, req);
} else {
alert("There was a problem retrieving the XML data:\n" +
req.statusText);
}
}
}
});
/* Ten.Observer */
Ten.Observer = new Ten.Class({
initialize: function(element,event,obj,method) {
var func = obj;
if (typeof(method) == 'string') {
func = obj[method];
}
this.element = element;
this.event = event;
this.listener = function(event) {
return func.call(obj, new Ten.Event(event || window.event));
}
if (this.element.addEventListener) {
if (this.event.match(/^on(.+)$/)) {
this.event = RegExp.$1;
}
this.element.addEventListener(this.event, this.listener, false);
} else if (this.element.attachEvent) {
this.element.attachEvent(this.event, this.listener);
}
}
},{
stop: function() {
if (this.element.removeEventListener) {
this.element.removeEventListener(this.event,this.listener,false);
} else if (this.element.detachEvent) {
this.element.detachEvent(this.event,this.listener);
}
}
});
/* Ten.Event */
Ten.Event = new Ten.Class({
initialize: function(event) {
this.event = event;
},
keyMap: {
8:"backspace", 9:"tab", 13:"enter", 19:"pause", 27:"escape", 32:"space",
33:"pageup", 34:"pagedown", 35:"end", 36:"home", 37:"left", 38:"up",
39:"right", 40:"down", 44:"printscreen", 45:"insert", 46:"delete",
112:"f1", 113:"f2", 114:"f3", 115:"f4", 116:"f5", 117:"f6", 118:"f7",
119:"f8", 120:"f9", 121:"f10", 122:"f11", 123:"f12",
144:"numlock", 145:"scrolllock"
}
},{
mousePosition: function() {
if (!this.event.clientX) return;
return Ten.Geometry.getMousePosition(this.event);
},
isKey: function(name) {
var ecode = this.event.keyCode;
if (!ecode) return;
var ename = Ten.Event.keyMap[ecode];
if (!ename) return;
return (ename == name);
},
targetIsFormElements: function() {
var target = this.event.target;
if (!target) return;
var T = (target.tagName || '').toUpperCase();
return (T == 'INPUT' || T == 'SELECT' || T == 'OPTION' ||
T == 'BUTTON' || T == 'TEXTAREA');
},
stop: function() {
var e = this.event;
if (e.stopPropagation) {
e.stopPropagation();
e.preventDefault();
} else {
e.cancelBubble = true;
e.returnValue = false;
}
}
});
/* Ten.DOM */
Ten.DOM = new Ten.Class({
getElementsByTagAndClassName: function(tagName, className, parent) {
if (typeof(parent) == 'undefined') {
parent = document;
}
var children = parent.getElementsByTagName(tagName);
if (className) {
var elements = [];
for (var i = 0; i < children.length; i++) {
var child = children[i];
var cls = child.className;
if (!cls) {
continue;
}
var classNames = cls.split(' ');
for (var j = 0; j < classNames.length; j++) {
if (classNames[j] == className) {
elements.push(child);
break;
}
}
}
return elements;
} else {
return children;
}
},
removeEmptyTextNodes: function(element) {
var nodes = element.childNodes;
for (var i = 0; i < nodes.length; i++) {
var node = nodes[i];
if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) {
node.parentNode.removeChild(node);
}
}
},
nextElement: function(elem) {
do {
elem = elem.nextSibling;
} while (elem && elem.nodeType != 1);
return elem;
},
prevElement: function(elem) {
do {
elem = elem.previousSibling;
} while (elem && elem.nodeType != 1);
return elem;
},
scrapeText: function(node) {
var rval = [];
(function (node) {
var cn = node.childNodes;
if (cn) {
for (var i = 0; i < cn.length; i++) {
arguments.callee.call(this, cn[i]);
}
}
var nodeValue = node.nodeValue;
if (typeof(nodeValue) == 'string') {
rval.push(nodeValue);
}
})(node);
return rval.join('');
},
onLoadFunctions: [],
loaded: false,
timer: null,
addEventListener: function(event,func) {
if (event != 'load') return;
Ten.DOM.onLoadFunctions.push(func);
Ten.DOM.checkLoaded();
},
checkLoaded: function() {
var c = Ten.DOM;
if (c.loaded) return true;
if (document && document.getElementsByTagName &&
document.getElementById && document.body) {
if (c.timer) {
clearInterval(c.timer);
c.timer = null;
}
for (var i = 0; i < c.onLoadFunctions.length; i++) {
c.onLoadFunctions[i]();
}
c.onLoadFunctions = [];
c.loaded = true;
} else {
c.timer = setInterval(c.checkLoaded, 13);
}
}
});
/* Ten.Style */
Ten.Style = new Ten.Class({
applyStyle: function(elem, style) {
for (prop in style) {
elem.style[prop] = style[prop];
}
}
});
/* Ten.Geometry */
Ten.Geometry = new Ten.Class({
initialize: function() {
if (Ten.Geometry._initialized) return;
var func = Ten.Geometry._functions;
var de = document.documentElement;
if (window.innerWidth) {
func.getWindowWidth = function() { return window.innerWidth; }
func.getWindowHeight = function() { return window.innerHeight; }
func.getXScroll = function() { return window.pageXOffset; }
func.getYScroll = function() { return window.pageYOffset; }
} else if (de && de.clientWidth) {
func.getWindowWidth = function() { return de.clientWidth; }
func.getWindowHeight = function() { return de.clientHeight; }
func.getXScroll = function() { return de.scrollLeft; }
func.getYScroll = function() { return de.scrollTop; }
} else if (document.body.clientWidth) {
func.getWindowWidth = function() { return document.body.clientWidth; }
func.getWindowHeight = function() { return document.body.clientHeight; }
func.getXScroll = function() { return document.body.scrollLeft; }
func.getYScroll = function() { return document.body.scrollTop; }
}
Ten.Geometry._initialized = true;
},
_initialized: false,
_functions: {},
getScroll: function() {
if (!Ten.Geometry._initialized) new Ten.Geometry;
return {
x: Ten.Geometry._functions.getXScroll(),
y: Ten.Geometry._functions.getYScroll()
};
},
getMousePosition: function(pos) {
// pos should have clientX, clientY same as mouse event
if ((navigator.userAgent.indexOf('Safari') > -1) &&
(navigator.userAgent.indexOf('Version/') < 0)) {
return {
x: pos.clientX,
y: pos.clientY
};
} else {
var scroll = Ten.Geometry.getScroll();
return {
x: pos.clientX + scroll.x,
y: pos.clientY + scroll.y
};
}
},
getElementPosition: function(e) {
return {
x: e.offsetLeft,
y: e.offsetTop
};
},
getWindowSize: function() {
if (!Ten.Geometry._initialized) new Ten.Geometry;
return {
w: Ten.Geometry._functions.getWindowWidth(),
h: Ten.Geometry._functions.getWindowHeight()
};
}
});
/* Ten.Position */
Ten.Position = new Ten.Class({
initialize: function(x,y) {
this.x = x;
this.y = y;
},
subtract: function(a,b) {
return new Ten.Position(a.x - b.x, a.y - b.y);
}
});
/*
// require Ten.js
**/
/* Ten.SubWindow */
Ten.SubWindow = new Ten.Class({
initialize: function() {
var c = this.constructor;
if (c.singleton && c._cache) {
return c._cache;
}
var div = document.createElement('div');
Ten.Style.applyStyle(div, Ten.SubWindow._baseStyle);
Ten.Style.applyStyle(div, c.style);
this.window = div;
this.addContainerAndCloseButton();
document.body.appendChild(div);
if (c.draggable) {
this._draggable = new Ten.Draggable(div, this.handle);
}
if (c.singleton) c._cache = this;
return this;
},
_baseStyle: {
color: '#000',
position: 'absolute',
display: 'none',
zIndex: 2,
left: 0,
top: 0,
backgroundColor: '#fff',
border: '1px solid #bbb'
},
style: {
padding: '2px',
textAlign: 'center',
borderRadius: '6px',
MozBorderRadius: '6px',
width: '100px',
height: '100px'
},
handleStyle: {
position: 'absolute',
top: '0px',
left: '0px',
backgroundColor: '#f3f3f3',
borderBottom: '1px solid #bbb',
width: '100%',
height: '30px'
},
containerStyle: {
margin: '32px 0 0 0',
padding: '0 10px'
},
// closeButton: 'close.gif',
closeButton: 'http://s.hatena.com/images/close.gif',
closeButtonStyle: {
position: 'absolute',
top: '8px',
right: '10px',
cursor: 'pointer'
},
_baseScreenStyle: {
position: 'absolute',
top: '0px',
left: '0px',
display: 'none',
zIndex: 1,
overflow: 'hidden',
width: '100%',
height: '100%'
},
screenStyle: {},
showScreen: true,
singleton: true,
draggable: true,
_cache: null
},{
screen: null,
windowObserver: null,
visible: false,
addContainerAndCloseButton: function() {
var win = this.window;
var c = this.constructor;
var div = document.createElement('div');
win.appendChild(div);
Ten.Style.applyStyle(div, c.containerStyle);
this.container = div;
if (c.handleStyle) {
var handle = document.createElement('div');
Ten.Style.applyStyle(handle, c.handleStyle);
win.appendChild(handle);
this.handle = handle;
}
if (c.closeButton) {
var btn = document.createElement('img');
btn.src = c.closeButton;
btn.alt = 'close';
Ten.Style.applyStyle(btn, c.closeButtonStyle);
win.appendChild(btn);
new Ten.Observer(btn, 'onclick', this, 'hide');
this.closeButton = btn;
}
if (c.showScreen) {
var screen = document.createElement('div');
Ten.Style.applyStyle(screen, Ten.SubWindow._baseScreenStyle);
Ten.Style.applyStyle(screen, c.screenStyle);
document.body.appendChild(screen);
this.screen = screen;
new Ten.Observer(screen, 'onclick', this, 'hide');
}
},
show: function(pos) {
pos = (pos.x && pos.y) ? pos : {x:0, y:0};
with (this.window.style) {
display = 'block';
left = pos.x + 'px';
top = pos.y + 'px';
}
if (this.screen) {
with (this.screen.style) {
display = 'block';
left = Ten.Geometry.getScroll().x + 'px';
top = Ten.Geometry.getScroll().y + 'px';
}
}
this.windowObserver = new Ten.Observer(document.body, 'onkeypress', this, 'handleEscape');
this.visible = true;
},
handleEscape: function(e) {
if (!e.isKey('escape')) return;
this.hide();
},
hide: function() {
if (this._draggable) this._draggable.endDrag();
this.window.style.display = 'none';
if (this.screen) this.screen.style.display = 'none';
if (this.windowObserver) this.windowObserver.stop();
this.visible = false;
}
});
/* Ten.Draggable */
Ten.Draggable = new Ten.Class({
initialize: function(element,handle) {
this.element = element;
this.handle = handle || element;
this.startObserver = new Ten.Observer(this.handle, 'onmousedown', this, 'startDrag');
this.handlers = [];
}
},{
startDrag: function(e) {
if (e.targetIsFormElements()) return;
this.delta = Ten.Position.subtract(
e.mousePosition(),
Ten.Geometry.getElementPosition(this.element)
);
this.handlers = [
new Ten.Observer(document, 'onmousemove', this, 'drag'),
new Ten.Observer(document, 'onmouseup', this, 'endDrag'),
new Ten.Observer(this.element, 'onlosecapture', this, 'endDrag')
];
e.stop();
},
drag: function(e) {
var pos = Ten.Position.subtract(e.mousePosition(), this.delta);
Ten.Style.applyStyle(this.element, {
left: pos.x + 'px',
top: pos.y + 'px'
});
e.stop();
},
endDrag: function(e) {
for (var i = 0; i < this.handlers.length; i++) {
this.handlers[i].stop();
}
if(e) e.stop();
}
});
/* Hatena */
if (typeof(Hatena) == 'undefined') {
Hatena = {};
}
/* Hatena.User */
Hatena.User = new Ten.Class({
initialize: function(name) {
this.name = name;
},
getProfileIcon: function(name) {
if (!name) name = 'user';
var pre = name.match(/^[\w-]{2}/)[0];
var img = document.createElement('img');
img.src = 'http://www.hatena.ne.jp/users/' + pre + '/' + name + '/profile_s.gif';
img.alt = name;
img.setAttribute('class', 'profile-icon');
img.setAttribute('width','16px');
img.setAttribute('height','16px');
with (img.style) {
margin = '0 3px';
border = 'none';
verticalAlign = 'middle';
}
return img;
}
}, {
profileIcon: function() {
return Hatena.User.getProfileIcon(this.name);
}
});
/* Hatena.Star */
if (typeof(Hatena.Star) == 'undefined') {
Hatena.Star = {};
}
/*
// Hatena.Star.* classes //
**/
if (window.location && window.location.host.match(/hatena\.com/)) {
Hatena.Star.BaseURL = 'http://s.hatena.com/';
} else {
Hatena.Star.BaseURL = 'http://s.hatena.ne.jp/';
}
Hatena.Star.Token = null;
/* Hatena.Star.User */
Hatena.Star.User = new Ten.Class({
base: [Hatena.User],
initialize: function(name) {
if (Hatena.Star.User._cache[name]) {
return Hatena.Star.User._cache[name];
} else {
this.name = name;
Hatena.Star.User._cache[name] = this;
return this;
}
},
_cache: {}
},{
userPage: function() {
return Hatena.Star.BaseURL + this.name + '/';
}
});
/* Hatena.Star.Entry */
Hatena.Star.Entry = new Ten.Class({
initialize: function(e) {
this.entry = e;
this.uri = e.uri;
this.title = e.title;
this.star_container = e.star_container;
this.comment_container = e.comment_container;
this.stars = [];
this.comments = [];
},
maxStarCount: 11
},{
flushStars: function() {
this.stars = [];
this.star_container.innerHTML = '';
},
bindStarEntry: function(se) {
this.starEntry = se;
for (var i = 0; i < se.stars.length; i++) {
if (typeof(se.stars[i]) == 'number') {
this.stars.push(new Hatena.Star.InnerCount(se.stars[i],this));
} else {
this.stars.push(new Hatena.Star.Star(se.stars[i]));
}
}
if (se.comments && !this.comments.length) {
for (var i = 0; i < se.comments.length; i++) {
this.comments.push(new Hatena.Star.Comment(se.comments[i]));
}
}
this.can_comment = se.can_comment;
},
setCanComment: function(v) {
this.can_comment = v;
},
showButtons: function() {
this.addAddButton();
this.addCommentButton();
},
addAddButton: function() {
if (this.star_container) {
this.addButton = new Hatena.Star.AddButton(this);
this.star_container.appendChild(this.addButton);
}
},
addCommentButton: function() {
if (this.comment_container) {
this.commentButton = new Hatena.Star.CommentButton(this);
this.comment_container.appendChild(this.commentButton.img);
}
},
showStars: function() {
var klass = this.constructor;
// if (this.stars.length > klass.maxStarCount) {
// var ic = new Hatena.Star.InnerCount(this.stars.slice(1,this.stars.length));
// this.star_container.appendChild(this.stars[0]);
// this.star_container.appendChild(ic);
// this.star_container.appendChild(this.stars[this.stars.length - 1]);
// } else {
for (var i = 0; i < this.stars.length; i++) {
this.star_container.appendChild(this.stars[i]);
}
},
showCommentButton: function() {
if (this.can_comment) {
this.commentButton.show();
if (this.comments.length) this.commentButton.activate();
} else {
// this.commentButton.hide();
}
},
addStar: function(star) {
this.stars.push(star);
this.star_container.appendChild(star);
},
addComment: function(com) {
if (!this.comments) this.comments = [];
if (this.comments.length == 0) {
this.commentButton.activate();
}
this.comments.push(com);
},
showCommentCount: function() {
this.comment_container.innerHTML += this.comments.length;
}
});
/* Hatena.Star.Button */
Hatena.Star.Button = new Ten.Class({
createButton: function(args) {
var img = document.createElement('img');
img.src = args.src;
img.alt = img.title = args.alt;
with (img.style) {
cursor = 'pointer';
margin = '0 3px';
padding = '0';
border = 'none';
verticalAlign = 'middle';
}
return img;
}
});
/* Hatena.Star.AddButton */
Hatena.Star.AddButton = new Ten.Class({
base: ['Hatena.Star.Button'],
initialize: function(entry) {
this.entry = entry;
this.lastPosition = null;
var img = Hatena.Star.Button.createButton({
src: Hatena.Star.AddButton.ImgSrc,
alt: 'Add Star'
});
this.observer = new Ten.Observer(img,'onclick',this,'addStar');
this.img = img;
return img;
},
ImgSrc: Hatena.Star.BaseURL + 'images/add.gif'
},{
addStar: function(e) {
this.lastPosition = e.mousePosition();
var uri = Hatena.Star.BaseURL + 'star.add.json?uri=' + encodeURIComponent(this.entry.uri) +
'&title=' + encodeURIComponent(this.entry.title);
if (Hatena.Star.Token) {
uri += '&token=' + Hatena.Star.Token;
}
new Ten.JSONP(uri, this, 'receiveResult');
},
receiveResult: function(args) {
var name = args ? args.name : null;
if (name) {
this.entry.addStar(new Hatena.Star.Star({name: name}));
//alert('Succeeded in Adding Star ' + args);
} else if (args.errors) {
var pos = this.lastPosition;
pos.x -= 10;
pos.y += 25;
var scroll = Ten.Geometry.getScroll();
var scr = new Hatena.Star.AlertScreen();
var alert = args.errors[0];
scr.showAlert(alert, pos);
}
}
});
/* Hatena.Star.CommentButton */
Hatena.Star.CommentButton = new Ten.Class({
base: ['Hatena.Star.Button'],
initialize: function(entry) {
this.entry = entry;
this.lastPosition = null;
var img = Hatena.Star.Button.createButton({
src: Hatena.Star.CommentButton.ImgSrc,
alt: 'Comments'
});
img.style.display = 'none';
this.observer = new Ten.Observer(img,'onclick',this,'showComments');
this.img = img;
},
ImgSrc: Hatena.Star.BaseURL + 'images/comment.gif',
ImgSrcActive: Hatena.Star.BaseURL + 'images/comment_active.gif'
},{
showComments: function(e) {
if (!this.screen) this.screen = new Hatena.Star.CommentScreen();
this.screen.bindEntry(this.entry);
var pos = e.mousePosition();
pos.y += 25;
this.screen.showComments(this.entry, pos);
},
hide: function() {
this.img.style.display = 'none';
},
show: function() {
this.img.style.display = 'inline';
},
activate: function() {
this.show();
this.img.src = Hatena.Star.CommentButton.ImgSrcActive;
}
});
/* Hatena.Star.Star */
Hatena.Star.Star = new Ten.Class({
initialize: function(args) {
if (args.img) {
this.img = args.img;
this.name = this.img.getAttribute('alt');
} else {
this.name = args.name;
var img = document.createElement('img');
img.src = Hatena.Star.Star.ImgSrc;
img.alt = this.name;
with (img.style) {
padding = '0';
border = 'none';
}
this.img = img;
}
new Ten.Observer(this.img,'onmouseover',this,'showName');
new Ten.Observer(this.img,'onmouseout',this,'hideName');
if (this.name) {
this.user = new Hatena.Star.User(this.name);
this.img.style.cursor = 'pointer';
new Ten.Observer(this.img,'onclick',this,'goToUserPage');
}
if (args.count && args.count > 1) {
var c = document.createElement('span');
c.setAttribute('class', 'hatena-star-inner-count');
Ten.Style.applyStyle(c, Hatena.Star.InnerCount.style);
c.innerHTML = args.count;
var s = document.createElement('span');
s.appendChild(img);
s.appendChild(c);
return s;
} else {
return this.img;
}
},
ImgSrc: Hatena.Star.BaseURL + 'images/star.gif'
},{
showName: function(e) {
if (!this.screen) this.screen = new Hatena.Star.NameScreen();
var pos = e.mousePosition();
pos.x += 10;
pos.y += 25;
this.screen.showName(this.name, pos);
},
hideName: function() {
if (!this.screen) return;
this.screen.hide();
},
goToUserPage: function() {
window.location = this.user.userPage();
}
});
/* Hatena.Star.InnerCount */
Hatena.Star.InnerCount = new Ten.Class({
initialize: function(count, e) {
this.count = count;
this.entry = e;
var c = document.createElement('span');
c.setAttribute('class', 'hatena-star-inner-count');
Ten.Style.applyStyle(c, Hatena.Star.InnerCount.style);
c.style.cursor = 'pointer';
c.innerHTML = count;
new Ten.Observer(c,'onclick',this,'showInnerStars');
this.container = c;
return c;
},
style: {
color: '#f4b128',
fontWeight: 'bold',
fontSize: '80%',
fontFamily: '"arial", sans-serif',
margin: '0 2px'
}
},{
showInnerStars: function() {
var url = Hatena.Star.BaseURL + 'entry.json?uri=' +
encodeURIComponent(this.entry.uri);
new Ten.JSONP(url, this, 'receiveStarEntry');
},
receiveStarEntry: function(res) {
var se = res.entries[0];
var e = this.entry;
if (encodeURIComponent(se.uri) != encodeURIComponent(e.uri)) return;
e.flushStars();
e.bindStarEntry(se);
e.addAddButton();
e.showStars();
}
});
/* Hatena.Star.Comment */
Hatena.Star.Comment = new Ten.Class({
initialize: function(args) {
this.name = args.name;
this.body = args.body;
}
},{
asElement: function() {
var div = document.createElement('div');
with (div.style) {
margin = '0px 0';
padding = '5px 0';
borderBottom = '1px solid #ddd';
}
var ico = Hatena.User.getProfileIcon(this.name);
div.appendChild(ico);
var span = document.createElement('span');
with(span.style) {
fontSize = '90%';
}
span.innerHTML = this.body;
div.appendChild(span);
return div;
}
});
/* Hatena.Star.NameScreen */
Hatena.Star.NameScreen = new Ten.Class({
base: [Ten.SubWindow],
style: {
padding: '2px',
textAlign: 'center'
},
containerStyle: {
margin: 0,
padding: 0
},
handleStyle: null,
showScreen: false,
closeButton: null,
draggable: false
},{
showName: function(name, pos) {
this.container.innerHTML = '';
this.container.appendChild(Hatena.User.getProfileIcon(name));
this.container.appendChild(document.createTextNode(name));
this.show(pos);
}
});
/* Hatena.Star.AlertScreen */
Hatena.Star.AlertScreen = new Ten.Class({
base: [Ten.SubWindow],
style: {
padding: '2px',
textAlign: 'center',
borderRadius: '6px',
MozBorderRadius: '6px',
width: '240px',
height: '120px'
},
handleStyle: {
position: 'absolute',
top: '0px',
left: '0px',
backgroundColor: '#f3f3f3',
borderBottom: '1px solid #bbb',
width: '100%',
height: '30px',
borderRadius: '6px 6px 0 0',
MozBorderRadius: '6px 6px 0 0'
}
},{
showAlert: function(msg, pos) {
this.container.innerHTML = msg;
var win = Ten.Geometry.getWindowSize();
var scr = Ten.Geometry.getScroll();
var w = parseInt(this.constructor.style.width) + 20;
if (pos.x + w > scr.x + win.w) pos.x = win.w + scr.x - w;
this.show(pos);
}
});
/* Hatena.Star.CommentScreen */
Hatena.Star.CommentScreen = new Ten.Class({
base: [Ten.SubWindow],
initialize: function() {
var self = this.constructor.SUPER.call(this);
if (!self.commentsContainer) self.addCommentsContainer();
return self;
},
style: {
width: '280px',
height: '280px',
overflowY: 'auto',
padding: '2px',
textAlign: 'center',
borderRadius: '6px',
MozBorderRadius: '6px'
},
handleStyle: {
position: 'absolute',
top: '0px',
left: '0px',
backgroundColor: '#f3f3f3',
borderBottom: '1px solid #bbb',
width: '100%',
height: '30px',
borderRadius: '6px 6px 0 0',
MozBorderRadius: '6px 6px 0 0'
},
containerStyle: {
margin: '32px 0 0 0',
textAlign: 'left',
padding: '0 10px'
},
getLoadImage: function() {
var img = document.createElement('img');
img.src = Hatena.Star.BaseURL + 'images/load.gif';
img.setAttribute('alt', 'Loading');
with (img.style) {
verticalAlign = 'middle';
margin = '0 2px';
}
return img;
}
},{
addCommentsContainer: function() {
var div = document.createElement('div');
with (div.style) {
marginTop = '-3px';
}
this.container.appendChild(div);
this.commentsContainer = div;
},
showComments: function(e, pos) {
var comments = e.comments;
if (!comments) comments = [];
this.commentsContainer.innerHTML = '';
for (var i=0; i<comments.length; i++) {
this.commentsContainer.appendChild(comments[i].asElement());
}
if (e.starEntry && !e.can_comment) {
this.hideCommentForm();
} else {
this.addCommentForm();
}
var win = Ten.Geometry.getWindowSize();
var scr = Ten.Geometry.getScroll();
var w = parseInt(this.constructor.style.width) + 20;
if (pos.x + w > scr.x + win.w) pos.x = win.w + scr.x - w;
this.show(pos);
},
bindEntry: function(e) {
this.entry = e;
},
sendComment: function(e) {
if (!e.isKey('enter')) return;
var body = this.commentInput.value;
if (!body) return;
this.commentInput.disabled = 'true';
this.showLoadImage();
var url = Hatena.Star.BaseURL + 'comment.add.json?body=' + encodeURIComponent(body) +
'&uri=' + encodeURIComponent(this.entry.uri) +
'&title=' + encodeURIComponent(this.entry.title);
new Ten.JSONP(url, this, 'receiveResult');
},
receiveResult: function(args) {
if (!args.name || !args.body) return;
this.commentInput.value = '';
this.commentInput.disabled = '';
this.hideLoadImage();
var com = new Hatena.Star.Comment(args);
this.entry.addComment(com);
this.commentsContainer.appendChild(com.asElement());
},
showLoadImage: function() {
if (!this.loadImage) return;
this.loadImage.style.display = 'inline';
},
hideLoadImage: function() {
if (!this.loadImage) return;
this.loadImage.style.display = 'none';
},
hideCommentForm: function() {
if (!this.commentForm) return;
this.commentForm.style.display = 'none';
},
addCommentForm: function() {
if (this.commentForm) {
this.commentForm.style.display = 'block';
return;
}
var form = document.createElement('div');
this.container.appendChild(form);
this.commentForm = form;
with (form.style) {
margin = '0px 0';
padding = '5px 0';
// borderTop = '1px solid #ddd';
}
//if (Hatena.Visitor) {
// form.appendChild(Hatena.Visitor.profileIcon());
//} else {
// form.appendChild(Hatena.User.getProfileIcon());
//}
var input = document.createElement('input');
input.type = 'text';
with (input.style) {
width = '215px';
border = '1px solid #bbb';
padding = '3px';
}
form.appendChild(input);
this.commentInput = input;
var img = this.constructor.getLoadImage();
this.loadImage = img;
this.hideLoadImage();
form.appendChild(img);
new Ten.Observer(input,'onkeypress',this,'sendComment');
}
});
/* Hatena.Star.EntryLoader */
Hatena.Star.EntryLoader = new Ten.Class({
initialize: function() {
var entries = Hatena.Star.EntryLoader.loadEntries();
this.entries = [];
for (var i = 0; i < entries.length; i++) {
var e = new Hatena.Star.Entry(entries[i]);
e.showButtons();
this.entries.push(e);
}
this.getStarEntries();
},
createStarContainer: function() {
var sc = document.createElement('span');
sc.setAttribute('class', 'hatena-star-star-container');
sc.style.marginLeft = '1px';
return sc;
},
createCommentContainer: function() {
var cc = document.createElement('span');
cc.setAttribute('class', 'hatena-star-comment-container');
cc.style.marginLeft = '1px';
return cc;
},
scrapeTitle: function(node) {
var rval = [];
(function (node) {
if (node.tagName == 'SPAN' &&
(node.className == 'sanchor' ||
node.className == 'timestamp')) {
return;
} else if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) {
return;
}
var cn = node.childNodes;
if (cn) {
for (var i = 0; i < cn.length; i++) {
arguments.callee.call(this, cn[i]);
}
}
var nodeValue = node.nodeValue;
if (typeof(nodeValue) == 'string') {
rval.push(nodeValue);
}
})(node);
return rval.join('');
},
headerTagAndClassName: ['h3',null],
getHeaders: function() {
var t = Hatena.Star.EntryLoader.headerTagAndClassName;
return Ten.DOM.getElementsByTagAndClassName(t[0],t[1],document);
},
loadEntries: function() {
var entries = [];
//var headers = document.getElementsByTagName('h3');
var c = Hatena.Star.EntryLoader;
var headers = c.getHeaders();
for (var i = 0; i < headers.length; i++) {
var header = headers[i];
var a = header.getElementsByTagName('a')[0];
if (!a) continue;
var uri = a.href;
var title = '';
// Ten.DOM.removeEmptyTextNodes(header);
var cns = header.childNodes;
title = c.scrapeTitle(header);
var cc = c.createCommentContainer();
header.appendChild(cc);
var sc = c.createStarContainer();
header.appendChild(sc);
entries.push({
uri: uri,
title: title,
star_container: sc,
comment_container: cc
});
}
return entries;
}
},{
getStarEntries: function() {
var url = Hatena.Star.BaseURL + 'entries.json?';
for (var i = 0; i < this.entries.length; i++) {
if (url.length > Ten.JSONP.MaxBytes) {
new Ten.JSONP(url, this, 'receiveStarEntries');
url = Hatena.Star.BaseURL + 'entries.json?';
}
url += 'uri=' + encodeURIComponent(this.entries[i].uri) + '&';
}
new Ten.JSONP(url, this, 'receiveStarEntries');
},
receiveStarEntries: function(res) {
var entries = res.entries;
if (!entries) entries = [];
for (var i = 0; i < this.entries.length; i++) {
var e = this.entries[i];
for (var j = 0; j < entries.length; j++) {
var se = entries[j];
if (!se.uri) continue;
if (encodeURIComponent(se.uri) == encodeURIComponent(e.uri)) {
e.bindStarEntry(se);
entries.splice(j,1);
break;
}
}
if (typeof(e.can_comment) == 'undefined') {
e.setCanComment(res.can_comment);
}
e.showStars();
e.showCommentButton();
}
}
});
/* Hatena.Star.WindowObserver */
Hatena.Star.WindowObserver = new Ten.Class({
initialize: funct