Qiitaのフィードのタグアイコンをホバー時ユーザーアイコンに切り替えついでに名前とかをポップアップするユーザースクリプト

# <i class="fa fa-cubes"></i> 前置き

**"<ruby>待<rt>ま</rt></ruby>"ってたぜェ!! この"<ruby>瞬間<rt>とき</rt></ruby>"をよォ!!**

と、いうことで、去る3/7に[Firefox 52 が正式リリースされました。]
注目すべきは[コレ]
>async functions をサポートしました。async function、async function expression、await キーワードを追加しました (バグ 1185106)。


`Chrome`ではすでに対応してましたが、`Firefox`も遅ればせながら…

特に表題の機能に影響しないのですが、使ってみたり。
今回も実用度は低め。

# <i class="fa fa-cogs"></i> 検証環境

* Firefox 52
* Chrome 56.0.2924.87
* Greasemonkey(Tampermonkey)

# <i class="fa fa-desktop"></i> デモ

![popovergif.gif](https://qiita-image-store.s3.amazonaws.com/0/62354/1d20e2ae-997c-c0de-8978-b0d56c59c852.gif)



# <i class="fa fa-code"></i> コード

Gistスクリプトリンク<i class="fa fa-external-link"></i>[Qiita_show_user_icon_to_feed.user.js]


```js: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');
```


# <i class="fa fa-info-circle" aria-hidden="true"></i> 元ネタ

[Qiita のフィードは投稿者の存在を蔑ろにしていると思うんだ… - Qiita]

マウスオーバーという手段は一覧性と噛み合わない。
文字列より画像が大切なので、充足できないのが心残り。

# <i class="fa fa-leanpub" aria-hidden="true"></i> 学びとか感想


## asyncいいですね

いいですね。
使い所とかわからないけど使ってみる！で、あまり特性を活かせてないのですが、楽しい。
`XMLHttpRequest`が`$.ajax`風になる気分を感じる[^1]。
`await`で待てるの良い。`.then()`地獄らない。
使い方簡単。
初めて`Promise`を動かした時並の感動を久方ぶりに。

`async`で囲まないと`await`出来ないのがちょっと不満？
どこかで`await`使うとreturnが`Promise`になって呼び出し側も引きづられるように`async/await`になっていく感じ？
あとこんな簡単な処理ではルートの`async`が返す`Promise`は使わないというか、全部`await`しちゃえば`async`の最後が`then()`では？って。
誤解してるんだろうと自覚してますが。もっと複雑な管理をする時に必要なんでしょう。(不定回再帰でもないし)

## ユーザー情報の取得

「知り合いかも」機能を使わせてもらっています。

* (おそらく)正式公開APIでないので制限がない
* 取得できる情報がAPIとくらべて必要十分に少ない
* URLが簡単
* デフォルトでホバー時に使われているので、少しぐらい拝借してもバレへんか…

http://qiita.com/api/internal/hovercard_users/khsk

```json

{
    "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制限でもかなりのユーザー数がいけるはず。キャッシュ初期のリクエスト増に目をつぶって貰えれば、やってタグアイコンの隣とかに出していってもいいかもしれない。


# <i class="fa fa-book"></i> 参考

* [async function - JavaScript | MDN]
* [jQueryで動的に追加した要素はクリックイベントが発火しない？いやそんなことはないぞ - Qiita]
* [jQueryの.hover()メソッドを.on()で使うためには？ | THE HAM MEDIA BLOG]
* [Popovers · Bootstrap]
* [ユーザーアイコンにカーソルを合わせると、ポップアップでユーザー情報を確認できるようになりました - Qiita Blog]

***

* [Qiitaのフィードに記事投稿者の名前を表示するユーザースクリプト - Qiita]
* [Qiitaのコードにコピーボタンを追加するユーザースクリプト - Qiita]







[Qiita_show_user_icon_to_feed.user.js]:https://gist.github.com/khsk/bca459a16e214c4e26c353618249a7bd/raw/9ba737592fbb28a95b761eec7afff7b74883829f/Qiita_show_user_icon_to_feed.user.js

[Firefox 52 が正式リリースされました。]:https://www.mozilla.jp/firefox/52.0/releasenotes/
[コレ]:https://developer.mozilla.org/ja/Firefox/Releases/52#New_features_2
[Qiita のフィードは投稿者の存在を蔑ろにしていると思うんだ… - Qiita]:http://qiita.com/kobake@github/items/9f3406bed89c08052107
[hovercard.js]:http://designwithpc.com/Plugins/Hovercard
[jQueryで動的に追加した要素はクリックイベントが発火しない？いやそんなことはないぞ - Qiita]:http://qiita.com/ss_watanabe/items/5d044bc08b9308767f4c
[jQueryの.hover()メソッドを.on()で使うためには？ | THE HAM MEDIA BLOG]:http://h2ham.seesaa.net/article/242553044.html
[async function - JavaScript | MDN]:https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/async_function
[Qiitaのフィードに記事投稿者の名前を表示するユーザースクリプト - Qiita]:http://qiita.com/khsk/items/e93df0adef897fea8fe2
[Qiitaのコードにコピーボタンを追加するユーザースクリプト - Qiita]:http://qiita.com/khsk/items/27a80755d883521ecd73
[Popovers · Bootstrap]:https://v4-alpha.getbootstrap.com/components/popovers/
[ユーザーアイコンにカーソルを合わせると、ポップアップでユーザー情報を確認できるようになりました - Qiita Blog]:http://blog.qiita.com/post/150810837689/hovercard

[^1]: `XMLHttpRequest`は同期でも書けるしAjax風って同期しているしちょっと何がいいたいかわからない。