はしがき
Qiitaの特定ユーザーの記事検索を強化して欲しいという声を見かけてはや5年以上。とくに機能追加は見られぬまま、いくつかの拡張機能やWebサービスが現れたり自作したりしてきました。
直近では2020年になっても同様の悩みからQiigleというサービスなどが生まれ続けています。
しかし今もって私が知る限り、自分や特定個人の記事を検索するには検索クエリーにuser:
を手打ちする必要があり1特定ユーザーから記事を調べることは億劫な作業です。自分の記事ですら。
一部の方はピックアップ記事に自分の記事のサイトマップや分類してまとめたようなものを作成してくれていますが、稀ですしメンテが要ります。
不便を感じ続けていますが、自力で非拡張で利便性を上げるには検索画面でのuser:user tag:tag
への到達を手軽にするぐらいしか今のところ思いつきません。
Qiita内でクリックのみでひとまずuser:user tag:tag
に到達するにはそういったリンクへの記事があればよいのではないかと考え、とりあえずそういうmdを出力するものを書きました。
作ったもの
APIを使い記事を取得してタグをまとめてmarkdownのリンクに変換します。
それをぺろっとコピペでQiitaに(限定共有)投稿すればポチポチクリックだけでリンク先に飛べます。
記事が多い方の取得結果ではタグも多すぎて冗長なため、一度しか出なかったタグを無視したりselectボックスで手動で取捨選択するオプションをつけました。
基本は自分用ですが、万一他人のリンクを取得したい場合は欲しいのはmdではなく機能するリンクなのでパーサーにかけて下部に表示する変換ボタンもつけました。
Google検索はその方が便利な場面もあるだろうということで。
Qiita上で右側の見出しリストからジャンプできるように各行は見出し#
で書いてますが、大きすぎたり下線が邪魔かもしれません。###
の方がいいかも。
ソースコード
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Qiitaユーザーのタグ検索画面へのリンク集を作成します</title>
<style type="text/css">
div {
margin-left: 3%;
}
#markdown {
width: 100%;
}
</style>
<script src="markdown-it.js"></script>
</head>
<body>
<div>
<p><a href="https://qiita.com/">Qiita</a>ユーザーの直近100件の投稿タグを利用した検索画面への個人用リンク集を作成します</p>
<p>ユーザーIDを入力して出力ボタンを押してください</p>
</div>
<div>
<input type="text" id="userid" pattern="[\w-]{3,}" placeholder="ユーザーID(ex:qiita)" required autofocus>
<input type="checkbox" id="ignore">
<label for="ignore">1件のタグを無視する</label>
</div>
<div>
<span>タグの並び:</span>
<input type="radio" name="tagsort" id='pop' checked="true">
<label for="pop">記事数順</label>
<input type="radio" name="tagsort" id='asc'>
<label for="asc">名前順</label>
</div>
<div>
<span>検索画面の並び:</span>
<input type="radio" name="searchsort" id='created' checked="true">
<label for="created">新着順</label>
<input type="radio" name="searchsort" id='rel'>
<label for="rel">関連順</label>
<input type="radio" name="searchsort" id='stock'>
<label for="stock">ストック数順</label>
<input type="radio" name="searchsort" id='like'>
<label for="like">LGTM数順</label>
</div>
<div>
<button id="getlist" type="button">タグを手動で選択する</button>
<select id="list" multiple="true"></select>
</div>
<div>
<button id="output" type="button">Markdownを出力</button>
</div>
<hr>
<div>
<button id="copy">クリップボードにコピー</button>
</div>
<div>
<textarea id="markdown" rows="15" placeholder="# [khskの記事](https://qiita.com/search?sort=like&q=user:khsk)
# [C#](https://qiita.com/search?sort=like&q=user:khsk+tag:C%23)
# [C++](https://qiita.com/search?sort=like&q=user:khsk+tag:C%2B%2B)
# [HTML](https://qiita.com/search?sort=like&q=user:khsk+tag:HTML)
# [JavaScript](https://qiita.com/search?sort=like&q=user:khsk+tag:JavaScript)
# [Node.js](https://qiita.com/search?sort=like&q=user:khsk+tag:Node.js)
# [PHP](https://qiita.com/search?sort=like&q=user:khsk+tag:PHP)
# [Ruby](https://qiita.com/search?sort=like&q=user:khsk+tag:Ruby)
# (外部検索)[Google検索](https://www.google.com/search?q=site:qiita.com/khsk)"></textarea>
</div>
<div>
<button id="html">HTMLに変換</button>
</div>
<div id="markup"></div>
<script type="text/javascript">
function validateUserId() {
return document.querySelector('#userid').reportValidity()
}
async function fetchItems(){
if (!validateUserId()) {
return
}
const url = 'https://qiita.com/api/v2/items?per_page=100&query=user:'
const res = await fetch(url + document.querySelector('#userid').value)
return res.json()
}
function extractTags(json) {
const tags = json.reduce((currentTags, item) => {
item.tags.forEach(tag => {
if(currentTags.hasOwnProperty(tag.name)) {
currentTags[tag.name].count += 1
} else {
currentTags[tag.name] = {
name: tag.name,
count: 1,
}
}
})
return currentTags
}, {})
return tags
}
function popSort(a, b) {
return b[1].count - a[1].count
}
function nameSort(a, b) {
return a[1].name.localeCompare(b[1].name)
}
function getSortedTags(tags) {
tags = Object.entries(tags)
// 同数のタグの並びをよくするため、文字列ソートは必ず通しておく
tags = tags.sort(nameSort)
if (document.querySelector('#pop').checked) {
tags = tags.sort(popSort)
}
return tags
}
function deleteSingleTags(tags) {
for (p in tags) {
if (tags[p].count == 1) {
delete tags[p]
}
}
return tags
}
document.querySelector('#getlist').addEventListener('click', () => {
if (!validateUserId()) {
return
}
const getList = document.querySelector('#getlist')
const tmpButtonText = getList.textContent
getList.textContent = '取得中…'
getList.disabled = 'true'
document.querySelector('#output').disabled = 'true'
fetchItems().then(items => {
let tags = extractTags(items)
if (document.querySelector('#ignore').checked) {
deleteSingleTags(tags)
}
tags = getSortedTags(tags)
const list = document.querySelector('#list')
list.innerHTML = ''
list.size = 5
const option = document.createElement('option')
tags.forEach(tag => {
const option = document.createElement('option')
option.textContent = tag[0]
option.value = tag[0]
list.appendChild(option)
})
getList.textContent = tmpButtonText
getList.disabled = ''
document.querySelector('#output').disabled = ''
})
})
document.querySelector('#output').addEventListener('click',async () => {
if (!validateUserId()) {
return
}
const mdButton = document.querySelector('#output')
const tmpButtonText = mdButton.textContent
mdButton.textContent = '処理…'
mdButton.disabled = 'true'
document.querySelector('#getlist').disabled = 'true'
const options = document.querySelectorAll('option:checked')
let tags
if (options.length) {
tags = Array.from(options).map((option) => option.value)
} else {
const items = await fetchItems()
tags = extractTags(items)
if (document.querySelector('#ignore').checked) {
tags = deleteSingleTags(tags)
}
tags = getSortedTags(tags).map(tag => tag[0])
}
const userid = document.querySelector('#userid').value
const baseUrl = 'https://qiita.com/search?sort=' + document.querySelector('input[name="searchsort"]:checked').id + '&q=user:' + encodeURIComponent(userid)
let mdText = ''
mdText += '# [' + userid + 'の記事](' + baseUrl + ')\n'
tags.forEach(tag => {
mdText += '# [' + tag + '](' + baseUrl + '+tag:' + encodeURIComponent(tag) + ')\n'
})
mdText += '# (外部検索)[Google検索](https://www.google.com/search?q=site:qiita.com/' + encodeURIComponent(userid) + ')'
document.querySelector('#markdown').value = mdText
mdButton.textContent = tmpButtonText
mdButton.disabled = ''
document.querySelector('#getlist').disabled = ''
document.querySelector('#copy').textContent = 'クリップボードにコピー'
})
document.querySelector('#copy').addEventListener('click',() => {
navigator.clipboard.writeText(document.querySelector('#markdown').value).then(() => {
document.querySelector('#copy').textContent = 'コピーしました'
})
})
document.querySelector('#html').addEventListener('click',() => {
document.querySelector('#markup').innerHTML = (new markdownit).render(document.querySelector('#markdown').value)
})
</script>
</body>
</html>
Qiitaのユーザー名制限の記録
Qiitaのユーザー名で使える文字種は何だったかしらんと/users
でユーザー名を見ようと思いましたが、そういえば一覧は廃止されていたので新規登録ページで見ました。
patternでは困らないであろう[\w-]{3,}
にしました。
備忘録
批判を受けたQiita運営のアップデート「50万人のコミュニティ」といかに対話するか | i:Engineer(アイエンジニア)|パーソルテクノロジースタッフのエンジニア派遣
最近、特に感心したのが、Qiitaの記事検索ができる「Qiigle(キーグル)」という仕組みを実際に作ったユーザーさんの記事でした。
▼【Qiigle】というQiitaの記事を検索するサービスを作りました
Qiitaのユーザーさんって、開発チームに新しい機能要望を出すだけじゃなくって、実際に作って提案してくれるんですよね。「こういうのがあったらいいな」を、本当に作って持ち込んでくれる。それはプログラマーのコミュニティならではなのかな、と。
うーん…2020でこの反応は公式の機能強化はまだまだなさそうかな?
検索強化関連記事(古いものもアイデアとして)
- Qiita 記事や公式ドキュメントを検索するのに便利な TIPS【検索に Google エンジンを使う】 - Qiita
- Qiitaのユーザーページにタグ検索のリンクを追加するChrome拡張機能「Qiitag Link」を作った - Qiita
ユーザーごとにどんな記事を投稿しているのかはユーザーページを見ればグラフで表示されているので一発でわかるのですが、
じゃあそのタグの記事を見たいとなると、ユーザーページから絞れないし、検索の時にオプション指定する必要があったりでめんどくさい。
- Chrome拡張 | soku_qiita - 即聞いた | Qiita のユーザー・タグ指定検索をマウスを使わずに素早く実行する #chrome_extension - Qiita
- Qiita ブックマークレット プロンプトで入力した特定キーワードでQiita内の自分の記事を検索する - Qiita
自分の昔の
-
検索オプションをクリックすれば自分の記事を絞り込めるオプションになりますが、そもそも空の検索キーワードで検索画面に飛べる導線が見つからず、適当に「a」と入力して検索画面へ飛ぶ無情感があります。 ↩