前置き
"待"ってたぜェ!! この"瞬間"をよォ!!
と、いうことで、去る3/7にFirefox 52 が正式リリースされました。
注目すべきはコレ
async functions をサポートしました。async function、async function expression、await キーワードを追加しました (バグ 1185106)。
Chrome
ではすでに対応してましたが、Firefox
も遅ればせながら…
特に表題の機能に影響しないのですが、使ってみたり。
今回も実用度は低め。
検証環境
- Firefox 52
- Chrome 56.0.2924.87
- Greasemonkey(Tampermonkey)
デモ
コード
GistスクリプトリンクQiita_show_user_icon_to_feed.user.js
// ==UserScript==
// @name Qiita show user Icon to feed
// @namespace khsk
// @description フィードの記事アイコンをホバーしたとき、投稿者のアイコンに変更する
// @include http://qiita.com/
// @include https://qiita.com/
// @include http://qiita.com/items
// @include https://qiita.com/items
// @include http://qiita.com/stock
// @include https://qiita.com/stock
// @include http://qiita.com/mine
// @include https://qiita.com/mine
// @version 1
// @grant none
// ==/UserScript==
console.time('hover icon');
const ORIGINAL_CLASS = 'user-icon';
const CUSTOM_CLASS = 'khsk-user-icon'; // IDより面倒だけど、複数作るのでクラスだよ。うっかりIDに書き換えそうになったので。
const getUserName = elm => {
const link = elm.parentNode.parentNode.parentNode.querySelector('div.item-box-title > h1 > a');
return link.href.match(/https?:\/\/qiita.com\/([^/]+)/)[1];
};
const getUserData = (username) => {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://qiita.com/api/internal/hovercard_users/' + username);
xhr.addEventListener('load', e => {
resolve(xhr.responseText);
});
xhr.addEventListener('error', e => {
reject(xhr.responseText);
});
xhr.send();
});
};
const setPopover = (elm, data) => {
// Qiitaのhovercardはhovercard.jsではないのかもで諦め
// 最後に$(elm).tooltip() or popover()を実行するとタイトルがタグになる。謎。なので最初に設定
// datasetでは効かないものがある
$(elm).popover({
trigger : 'hover',
placement : 'top', // フィードは上から下へ見ると思うので
container : 'body',
html : true,
});
elm.dataset.toggle = 'popover';
elm.dataset.originalTitle = 'Author Info';
// hovercardっぽさのためにアイコン画像は不要か…
//elm.dataset.content = '<img src="' + data.profile_image_url + '">' + '<br>\n' + (data.name || data.url_name) + '<br>\n' + data.contribution + ' Contribution';
elm.dataset.content = (data.name || data.url_name) + '<br>\n' + data.contribution + ' Contribution';
elm.dataset.placement = 'top'; // フィードは上から下へ見ると思うので
// 改行対策にデフォルト値 + nowrap
elm.dataset.viewport = "{ selector: 'body', padding: 0 , white-space: nowrap,}";
};
const createUserIcon = elm => {
return (async () => {
const username = getUserName(elm);
const userdata = JSON.parse(await getUserData(username));
const user = elm.cloneNode();
// 識別子
user.className = user.className + ' ' + CUSTOM_CLASS;
user.src = userdata.profile_image_url;
setPopover(user, userdata);
elm.parentNode.insertBefore(user, elm);
return user;
})();
};
const showUser = elm => {
return (async elm => {
// ByClass()[0]とどっちがいいかなあ
let user = elm.parentNode.querySelector('.' + CUSTOM_CLASS);
if (!user) {
user = await createUserIcon(elm);
}
user.style.display = '';
elm.style.display = 'none';
})(elm);
};
const hideUser = user => {
return (async user => {
// たぶんツールチップ表示が邪魔するので隣接セレクタではなく一般兄弟セレクタ(関節セレクタ)で取得する
const tag = user.parentNode.querySelector('.' + CUSTOM_CLASS + ' ~ img');
tag.style.display = '';
user.style.display = 'none';
})(user);
};
// ここ非jQueryでどうするか不明
$(document).on({
'mouseenter': e => {
console.log('mouseenter');
// targetは目的のimgで、currentTargetがa
// targetのクラス名をチェックしないと、マウスでこすると想定外のelmが渡ることがあるので
if (e.target.className.indexOf(ORIGINAL_CLASS) != -1) {
showUser(e.target).catch(e => {
console.log('mouseenter catch : ' + e);
});
}
},
'mouseleave': e => {
console.info('mouseleave');
// 順調ならe.targetが作ったuser imgになるよ
if (e.target.className.indexOf(ORIGINAL_CLASS) != -1) {
hideUser(e.target).catch(e => {
console.log('mouseleave catch : ' + e);
});
}
}
}, '.action-user-icon.action > a'/* imgにイベントを設定すると、非表示時にmouseleaveが起き無限ループするので、親のaにする */);
console.timeEnd('hover icon');
元ネタ
Qiita のフィードは投稿者の存在を蔑ろにしていると思うんだ… - Qiita
マウスオーバーという手段は一覧性と噛み合わない。
文字列より画像が大切なので、充足できないのが心残り。
学びとか感想
asyncいいですね
いいですね。
使い所とかわからないけど使ってみる!で、あまり特性を活かせてないのですが、楽しい。
XMLHttpRequest
が$.ajax
風になる気分を感じる1。
await
で待てるの良い。.then()
地獄らない。
使い方簡単。
初めてPromise
を動かした時並の感動を久方ぶりに。
async
で囲まないとawait
出来ないのがちょっと不満?
どこかでawait
使うとreturnがPromise
になって呼び出し側も引きづられるようにasync/await
になっていく感じ?
あとこんな簡単な処理ではルートのasync
が返すPromise
は使わないというか、全部await
しちゃえばasync
の最後がthen()
では?って。
誤解してるんだろうと自覚してますが。もっと複雑な管理をする時に必要なんでしょう。(不定回再帰でもないし)
ユーザー情報の取得
「知り合いかも」機能を使わせてもらっています。
- (おそらく)正式公開APIでないので制限がない
- 取得できる情報がAPIとくらべて必要十分に少ない
- URLが簡単
- デフォルトでホバー時に使われているので、少しぐらい拝借してもバレへんか…
{
"articles_count": 114,
"contribution": 656,
"followed": false,
"id": 62354,
"name": "",
"profile_image_url": "https://qiita-image-store.s3.amazonaws.com/0/62354/profile-images/1473695923",
"url_name": "khsk"
}
ホバー情報
できれば、「知り合いかも」機能のホバーカードを使いたかったけど、使えなかった…
でも変わりに、今までtooltipしか知らなかったbootstrapのpopoverや、
hovercard.jsを知ることが出来た。
Greasemonkeyの@include
はあまり好みじゃないのでpopoverにしましたが。
もともとアイコン表示するだけのつもりで、ポップアップは後付だったので、すべて吹き出しに集約すれば、アイコンが見やすくなるしコードがかんたんになると思っている。
ただ、tooltipとpopoverはDOM付きでいっぱい入れるのはちょっとしんどいと感じている。
イベント登録
最初は.hover
を使おうと思っていて、いつものようにMutationObserver
で各フィードに登録しようと思った。
しかし、ちょうどタイミングよく、jQueryで動的に追加した要素はクリックイベントが発火しない?いやそんなことはないぞ - Qiitaが流れて来て、この方法でしようとした。
この方法は初見じゃないんだけど、すぐに忘れて使ったことがなかったので、フィードのタイミングがよかった。
でも、.hover
では使えなくて、.on
にはhover
イベントが無いので困った。
調べて、
jQueryの.hover()メソッドを.on()で使うためには? | THE HAM MEDIA BLOG
で解決出来たし、(mouseover
,mouseout
以外に)mouseenter
,mouseleave
を知れたし、.on
にオブジェクトで複数イベント登録できることも知ったので、良かった。
できれば非jQueryでやりたい。
今後
やっぱりアイコンを一覧で表示したい。
(アイコン画像取得リクエストが許されるならば)自分の中で許せない部分はアイコンURL取得のためのユーザー情報の取得。かなり無駄に感じる。
ただ、キャッシュすればいいので、localStorage
を使えばユーザー名とアイコンURLだけの組み合わせで10MB制限でもかなりのユーザー数がいけるはず。キャッシュ初期のリクエスト増に目をつぶって貰えれば、やってタグアイコンの隣とかに出していってもいいかもしれない。
参考
- async function - JavaScript | MDN
- jQueryで動的に追加した要素はクリックイベントが発火しない?いやそんなことはないぞ - Qiita
- jQueryの.hover()メソッドを.on()で使うためには? | THE HAM MEDIA BLOG
- Popovers · Bootstrap
- ユーザーアイコンにカーソルを合わせると、ポップアップでユーザー情報を確認できるようになりました - Qiita Blog
-
XMLHttpRequest
は同期でも書けるしAjax風って同期しているしちょっと何がいいたいかわからない。 ↩