Qiitaのユーザーページにナビゲーションタブを追加するUserScript作った

Note: Qiita のアップデートにより現在は動作しない。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/446383/36abb8ed-da4c-aaba-cb36-c45c4f4ebe5a.png)

現状ではユーザーの「LGTMした記事」「フォローしているユーザー」「コメント履歴」「編集リクエスト履歴」を見るためにはいちいち「...」ボタンをクリックしなければいけない。これは面倒なので、より快適に閲覧できるようにナビゲーションタブを追加するUserScriptを作成した。

インストールにはUserScript管理用の拡張機能が必要。

- Chrome: [Tampermonkey](https://chrome.google.com/webstore/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo)
- Firefox: [Tampermonkey](https://addons.mozilla.org/en-US/firefox/addon/tampermonkey/), [Greasemonkey](https://addons.mozilla.org/en-US/firefox/addon/greasemonkey/)

```js
// ==UserScript==
// @name         Qiita User Page Nav
// @namespace    https://qiita.com/righteous
// @version      0.1
// @description  Adds an navigation tab on user pages
// @author       righteous
// @match        https://qiita.com/*
// @grant        none
// ==/UserScript==

!(function () {
  const targetClassPrefixes = [
    'UserMain__ContentsContainer',
    'UserLgtms__ContentsContainer',
    'UserFollowees__ContentsContainer',
    'UserComments__ContentsContainer',
    'UserEditRequests__ContentsContainer',
  ]

  const selector = targetClassPrefixes.map(c => `[class^="${c}"],[class*=" ${c}"]`).join(',')
  const containerDOM = document.querySelector(selector)
  if (!containerDOM) return

  const [username, curPage = ''] = window.location.pathname.split('/').filter(s => s)
  if (!username) return

  const pages = [
    { path: '', label: 'マイページ' },
    { path: 'lgtms', label: 'LGTM記事' },
    { path: 'following_users', label: 'フォロー' },
    { path: 'comments', label: 'コメント' },
    { path: 'edit_requests', label: '編集リク' },
  ]

  const nav = document.createElement('div')
  nav.classList.add('qiita-user-page-nav')

  for (const page of pages) {
    const linkDOM = document.createElement('a')
    linkDOM.classList.add('ol-ItemList_tabItem')
    if (curPage === page.path) {
      linkDOM.classList.add('is-active')
    }
    linkDOM.textContent = page.label
    linkDOM.href = `/${username}/${page.path}`
    nav.appendChild(linkDOM)
  }
  containerDOM.insertBefore(nav, containerDOM.firstChild)

  const styleDOM = document.createElement('style')
  styleDOM.textContent = `
  .qiita-user-page-nav {
    background-color: white;
    display: flex;
    padding-top: 15px;
    margin-bottom: 10px;
  }

  @media (max-width: 770px) {
    .qiita-user-page-nav .ol-ItemList_tabItem {
      font-size: 8px;
    }
  }
  `
  document.head.appendChild(styleDOM)
})()
```

# 説明

## 要素を挿入するターゲット要素の取得

ユーザーページには「メインページ」「LGTMした記事」「フォローしているユーザー」「コメント履歴」「編集リクエスト履歴」の5種類あり、すべて同じような 2 column layout であるため、右の column の一番上にタブを追加することが方針である。しかし、それぞれのページでこの column の持つクラスが以下のように異なる。

- メインページ: `UserMain__ContentsContainer-sc-1ead6ck-3`
- LGTMした記事: `UserLgtms__ContentsContainer-sc-2bd1us-3`
- ...

また、見ての通りクラス名の末尾に`sc-1ead6ck-3`などとついており、これはおそらくStyled Componentsによってビルド時に生成されたハッシュ値であり、ビルドするたびに変わる可能性が高い。

よってこのUserScriptではこの column の element を取得するにあたり、次のようなセレクターを作ることにした。

- 次のいずれかを満たす要素にマッチ
    - `UserMain__ContentsContainer` から始まるクラスをもつ
    - `UserLgtms__ContentsContainer` から始まるクラスをもつ
    - ...

しかし、「〇〇から始まるクラスをもつ」という条件をきれいにセレクタで表現することはできない。なぜなら`class`属性の値は複数のクラス名がスペース区切りで連結されたものであるからである。よって次のようなセレクターを使う必要がある。

```js
// 'myclass'から始まるクラスを持つ要素を取得
document.querySelector('[class^="myclass"], [class*=" myclass"]')
```