4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-03-13

前置き

""ってたぜェ!! この"瞬間とき"をよォ!!

と、いうことで、去る3/7にFirefox 52 が正式リリースされました。
注目すべきはコレ

async functions をサポートしました。async function、async function expression、await キーワードを追加しました (バグ 1185106)。

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

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

検証環境

  • Firefox 52
  • Chrome 56.0.2924.87
  • Greasemonkey(Tampermonkey)

デモ

popovergif.gif

コード

GistスクリプトリンクQiita_show_user_icon_to_feed.user.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');

元ネタ

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

参考


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

4
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?