概要
Qiitaの通知欄で各ユーザのアイコンやユーザ名をクリックすれば、そのユーザのプロフィールページへ直接移動できるようにするユーザースクリプトを作りました。
GreaseyForkで公開中です。
きっかけ
Qiitaの投稿画面の同時スクロールを改善するユーザースクリプト - Qiita
先日の記事は予想外の好評を頂きまして本当に驚きました。
Qiitaの通知欄にあんなに多くのいいね・ストックが集まるのは初めての経験でしたから嬉しい限りです。ありがとうございました。
さてところで、今から書くことは天才美少女プログラマーこと @itsminadesu 氏が某イベントで言ってたことのだいたいパクリなのですが―
Qiitaではいいね・ストックなどされたときの通知が画面右上の通知欄、あるいは通知一覧ページで確認できるようになっています。
この通知機能自体はTwitterのようなSNSでよくあるものですが、QiitaがTwitterと違うのは…
Twitterでは通知から「いいねした人」のプロフィールへリンクで移動できるようになっているのが、
Qiitaではそうではないという点です。
このせいでどんな人にいいね・ストックされたのか確認するのが面倒になっています。
まあどうしても確認したければ一応記事をいいねした人の一覧みたいなページもあるにはあるんですが、通知欄から直接移動できたほうが楽ではないでしょうか。
― @itsminadesu 氏がそんなことを言ってたときはへーそんなもんかーくらいに思ってましたが今なら確かにそう思います。
そんなふうにできないかな、しましょう。
実装
言うて前回の記事でやったことに比べればずいぶん簡単です。
単純に各通知のアイコンの要素、またはユーザ名の要素をクリックしたらそのユーザのプロフィールページに移動できるようにしましょう。
また今回はJavaScriptを使ってページを移動させます。この方法だとa
タグは使わないので、要素にマウスホバーしても下線が表示されません。
これだとクリックできるって分かりにくいので、マウスホバーで下線を表示する処理も手で書いておきましょう。
1. マウスクリックでプロフィールに移動
要素の取得
画面右上に出せる通知欄と、通知一覧ページのそれぞれで各通知のアイコンの要素とユーザ名の要素を取得したいと思います。
ここで幸いなのが、画面右上に出せる通知欄というのは基本的に通知一覧ページをiframe
要素で埋め込んでるだけということです。
ユーザースクリプトはiframe
で埋め込まれるページ内でも実行されるので、通知一覧ページのほうにだけ対応する実装をすれば自動的に画面右上に出せる通知欄でも同じ動作が実現されます。
そういうわけで実装にあたっては通知一覧ページのほうだけ見てればOK。取得には単純にCSSセレクタを使いましょう。
取得にあたって、アイコンはともかくユーザ名のほうに特別な属性が振られていないのが困りますが、ここではclass
属性にbold
が振られているspan
要素2つのうち先に出てくる方がユーザ名だと推測することにします。
そういうわけで件の要素を取得するCSSセレクタは次の通りです。
.notification .notification_actionWrapper span.bold:first-child, .notification .notification_icon img
クリック処理の登録
またしても幸いなのが、Qiitaにはスクリーンネームとか無く、プロフィールページのURLは単純にhttps://qiita.com/<ユーザ名>
だということと、
そのユーザ名は各通知のアイコンならalt
属性、ユーザ名のほうなら中身のテキストノードにそのまま書かれているので、前項で取得した要素に含まれる情報からプロフィールページのURLが簡単に生成できるということです。
URLが生成できれば後は要素がクリックされたときにそのURLに移動させる処理さえ書けば大丈夫。
手っ取り早い実装法としては単にその要素をa
タグで囲めば良いように思えますが…
Qiitaでは1つの通知全体が別のa
タグで囲われており、どこをクリックしても関連する記事に移動されるようになっています。
自由度の高いHTML5でもさすがに親要素のa
の中に子要素のa
を入れることはできませんから別の方法を考えましょう。
別の方法となればJavaScriptです。極力a
タグの挙動を再現することを目指して、
- 普通に左クリックされた場合はparent.location.hrefプロパティを設定し、現在のウィンドウでURLを開く
- 中クリックされた場合はwindow.open()メソッドを呼び出し、新しいウィンドウでURLを開く
というようにします。
Event.preventDefault()メソッドを呼び出して通常の通知クリック処理を止めるのを忘れないように、またlocation.href
でなくparent.location.href
を使ってiframe
に埋め込まれている場合は親ウィンドウでURLを開くことも忘れないようにしましょう。
ちなみに忘れるとこうなります。これはこれで面白いんですけどね。
それとJavaScriptで左クリック時に発生するイベントはclick
、中クリック時に発生するイベントはauxclick
です。
// クリックされた時にリンクを開くメソッド
const handleClick = e => {
e.preventDefault();
const username = e.target.alt ? e.target.alt : e.target.textContent;
parent.location.href = "/" + username;
};
const handleAuxclick = e => {
e.preventDefault();
const username = e.target.alt ? e.target.alt : e.target.textContent;
open("/" + username);
focus();
};
// 通知内にあるユーザ名またはアイコンの要素一覧を取得
const elements = document.querySelectorAll(".notification .notification_actionWrapper span.bold:first-child, .notification .notification_icon img");
// 要素それぞれにリンクを設定する
elements.forEach(element => {
element.addEventListener("click", handleClick);
element.addEventListener("auxclick", handleAuxclick);
});
2. マウスホバーで下線を引く
マウスホバー時に下線を引く方法としてはこの2つが考え付きます。
-
style
要素を使いCSSで:hover疑似クラスを設定して下線を表示する方法 - JavaScriptでマウスホバー時に下線を表示、ホバーが外れたときに下線を非表示にする方法
別にどっちでも良さそうですが、前項で作ったクリック処理との兼ね合いから後者の方法のほうがシンプルに実装できるので後者の方法を採ることにします。
JavaScriptでマウスホバー時に発生するイベントはmouseover
、ホバーが外れたときに発生するイベントはmouseout
です。
// マウスホバーで下線を表示するメソッド
const handleMouseover = e => {
e.target.style.textDecoration = "underline";
};
const handleMouseout = e => {
e.target.style.textDecoration = "unset";
};
// 通知内にあるユーザ名またはアイコンの要素一覧を取得
const elements = document.querySelectorAll(".notification .notification_actionWrapper span.bold:first-child, .notification .notification_icon img");
elements.forEach(element => {
element.addEventListener("mouseover", handleMouseover);
element.addEventListener("mouseout", handleMouseout);
});
最終的に出来上がったコード
GitHubとGreaseyForkで公開中です。
// ==UserScript==
// @name Qiita通知ユーザーリンク
// @version 0.1
// @description Qiitaの通知でいいねやストックしたユーザのプロフィールへのリンクを作り、どんなユーザにいいねされたのか確認しやすくする。
// @author fukuchan
// @match https://qiita.com/notifications*
// @grant none
// ==/UserScript==
// クリックされた時にリンクを開くメソッド
const handleClick = e => {
e.preventDefault();
const username = e.target.alt ? e.target.alt : e.target.textContent;
parent.location.href = "/" + username;
};
const handleAuxclick = e => {
e.preventDefault();
const username = e.target.alt ? e.target.alt : e.target.textContent;
open("/" + username);
focus();
};
// マウスホバーで下線を表示するメソッド
const handleMouseover = e => {
e.target.style.textDecoration = "underline";
};
const handleMouseout = e => {
e.target.style.textDecoration = "unset";
};
// 通知内にあるユーザ名またはアイコンの要素一覧を取得
const elements = document.querySelectorAll(".notification .notification_actionWrapper span.bold:first-child, .notification .notification_icon img");
// 要素それぞれにリンクを設定する
elements.forEach(element => {
element.addEventListener("click", handleClick);
element.addEventListener("auxclick", handleAuxclick);
element.addEventListener("mouseover", handleMouseover);
element.addEventListener("mouseout", handleMouseout);
});