Qiitaの通知欄から各ユーザのプロフィールに移動できるユーザースクリプト

# 概要

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/507909/be170259-c4dd-afef-b21d-3e4ec37937cd.png)

Qiitaの通知欄で各ユーザのアイコンやユーザ名をクリックすれば、そのユーザのプロフィールページへ直接移動できるようにするユーザースクリプトを作りました。

[GreaseyFork](https://greasyfork.org/ja/scripts/397416-qiita%E9%80%9A%E7%9F%A5%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E3%83%AA%E3%83%B3%E3%82%AF)で公開中です。

# きっかけ

[Qiitaの投稿画面の同時スクロールを改善するユーザースクリプト - Qiita](https://qiita.com/m_fukuchan/items/2a0c1f7b45894449ff56)

先日の記事は予想外の好評を頂きまして本当に驚きました。
Qiitaの通知欄にあんなに多くのいいね・ストックが集まるのは初めての経験でしたから嬉しい限りです。ありがとうございました。

さてところで、今から書くことは天才美少女プログラマーこと @itsminadesu 氏が某イベントで言ってたことのだいたいパクリなのですが―

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/507909/299de400-863c-96a5-3dfd-c5cb194a56f4.png)

Qiitaではいいね・ストックなどされたときの通知が画面右上の通知欄、あるいは[通知一覧ページ](https://qiita.com/notifications)で確認できるようになっています。

この通知機能自体はTwitterのようなSNSでよくあるものですが、QiitaがTwitterと違うのは…

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/507909/138c77a2-81b6-c93a-4e7e-0eeff6dfe926.png)

Twitterでは通知から「いいねした人」のプロフィールへリンクで移動できるようになっているのが、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/507909/f6322348-5af1-f838-108c-cf0696577775.png)

Qiitaではそうではないという点です。

このせいでどんな人にいいね・ストックされたのか確認するのが面倒になっています。

まあどうしても確認したければ一応[記事をいいねした人の一覧](https://qiita.com/m_fukuchan/items/cd9f75ea5a2f78d3f821/likers)みたいなページもあるにはあるんですが、通知欄から直接移動できたほうが楽ではないでしょうか。

― @itsminadesu 氏がそんなことを言ってたときはへーそんなもんかーくらいに思ってましたが今なら確かにそう思います。

そんなふうにできないかな、しましょう。

# 実装

言うて前回の記事でやったことに比べればずいぶん簡単です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/507909/5dd1c533-8592-58cd-aaa7-d35a00bcd41e.png)

単純に各通知のアイコンの要素、またはユーザ名の要素をクリックしたらそのユーザのプロフィールページに移動できるようにしましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/507909/8a161140-2c12-3c8a-bd64-047969c30bcb.png)

また今回はJavaScriptを使ってページを移動させます。この方法だと`a`タグは使わないので、要素にマウスホバーしても下線が表示されません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/507909/b3623d62-46fc-6ee7-180f-85bead2ca399.png)

これだとクリックできるって分かりにくいので、マウスホバーで下線を表示する処理も手で書いておきましょう。

## 1. マウスクリックでプロフィールに移動

### 要素の取得

画面右上に出せる通知欄と、[通知一覧ページ](https://qiita.com/notifications)のそれぞれで各通知のアイコンの要素とユーザ名の要素を取得したいと思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/507909/7af3a22c-5b78-c733-62a5-b53b5ea9fa6a.png)

ここで幸いなのが、画面右上に出せる通知欄というのは基本的に通知一覧ページを`iframe`要素で埋め込んでるだけということです。

ユーザースクリプトは`iframe`で埋め込まれるページ内でも実行されるので、通知一覧ページのほうにだけ対応する実装をすれば自動的に画面右上に出せる通知欄でも同じ動作が実現されます。

そういうわけで実装にあたっては通知一覧ページのほうだけ見てればOK。取得には単純にCSSセレクタを使いましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/507909/9d25cc5c-a8eb-62b6-7843-2f466a81a962.png)

取得にあたって、アイコンはともかくユーザ名のほうに特別な属性が振られていないのが困りますが、ここでは`class`属性に`bold`が振られている`span`要素2つのうち先に出てくる方がユーザ名だと推測することにします。

そういうわけで件の要素を取得するCSSセレクタは次の通りです。

```css:document.querySelectorAllに渡すCSSセレクタ
.notification .notification_actionWrapper span.bold:first-child, .notification .notification_icon img
```

### クリック処理の登録

またしても幸いなのが、Qiitaにはスクリーンネームとか無く、プロフィールページのURLは単純に`https://qiita.com/<ユーザ名>`だということと、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/507909/8cfb8239-d03d-cbf0-7f6a-e0b6a0bcd0bf.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/507909/f49b0f5b-443a-aab4-f40b-a4544240f884.png)

そのユーザ名は各通知のアイコンなら`alt`属性、ユーザ名のほうなら中身のテキストノードにそのまま書かれているので、前項で取得した要素に含まれる情報からプロフィールページのURLが簡単に生成できるということです。

URLが生成できれば後は要素がクリックされたときにそのURLに移動させる処理さえ書けば大丈夫。
手っ取り早い実装法としては単にその要素を`a`タグで囲めば良いように思えますが…

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/507909/9ebcb3fc-d743-d9e4-d451-ff89d9701d06.png)

Qiitaでは1つの通知全体が別の`a`タグで囲われており、どこをクリックしても関連する記事に移動されるようになっています。
自由度の高いHTML5でもさすがに親要素の`a`の中に子要素の`a`を入れることはできませんから別の方法を考えましょう。

別の方法となればJavaScriptです。極力`a`タグの挙動を再現することを目指して、

* 普通に左クリックされた場合は[parent.location.href](https://developer.mozilla.org/ja/docs/Web/API/Location)プロパティを設定し、**現在のウィンドウ**でURLを開く
* 中クリックされた場合は[window.open()](https://developer.mozilla.org/ja/docs/Web/API/Window/open)メソッドを呼び出し、**新しいウィンドウ**でURLを開く

というようにします。
[Event.preventDefault()](https://developer.mozilla.org/ja/docs/Web/API/Event/preventDefault)メソッドを呼び出して通常の通知クリック処理を止めるのを忘れないように、また`location.href`でなく`parent.location.href`を使って`iframe`に埋め込まれている場合は親ウィンドウでURLを開くことも忘れないようにしましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/507909/1584a124-007d-7add-4450-63b50381c1af.png)

ちなみに忘れるとこうなります。これはこれで面白いんですけどね。

それとJavaScriptで左クリック時に発生するイベントは`click`、中クリック時に発生するイベントは`auxclick`です。

```javascript
// クリックされた時にリンクを開くメソッド
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](https://developer.mozilla.org/ja/docs/Web/CSS/:hover)疑似クラスを設定して下線を表示する方法
* JavaScriptでマウスホバー時に下線を表示、ホバーが外れたときに下線を非表示にする方法

別にどっちでも良さそうですが、前項で作ったクリック処理との兼ね合いから後者の方法のほうがシンプルに実装できるので後者の方法を採ることにします。

JavaScriptでマウスホバー時に発生するイベントは`mouseover`、ホバーが外れたときに発生するイベントは`mouseout`です。

```javascript
// マウスホバーで下線を表示するメソッド
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](https://github.com/ia15076/qiita-notification-user-link)と[GreaseyFork](https://greasyfork.org/ja/scripts/397416-qiita%E9%80%9A%E7%9F%A5%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E3%83%AA%E3%83%B3%E3%82%AF)で公開中です。

```javascript
// ==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);
});
```

![Qiita - Mozilla Firefox 2020-03-06 20-01-57.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/507909/a22fa8f9-db2d-8f21-9c9b-be2f0b24ecbd.gif)

# 参考

* [CSS値の「initial」「inherit」「unset」「revert」の違い - Qiita](https://qiita.com/h-naito/items/3027f92dde68899159c7)
* [html - Is it ok to have an `<a>` inside another `<a>`? - Stack Overflow](https://stackoverflow.com/questions/7863554/is-it-ok-to-have-an-a-inside-another-a)