| @ブログ

よく検索されているキーワード

検索フォームの下に「よく検索されているキーワード」を追加した。実はこれ完全に自己満足で Google Analytics のサイト内検索で調べる限りだと検索機能はほとんど使われていない。一方で自分はめちゃくちゃ使っている。なので自分自身の検索も含むすべての検索ログをアクセスログから抽出して集計し、検索回数が多い順に並べて表示した。

検索フォームにはインクリメンタルサーチ機能があって、検索ワード入力途中にも HTTP アクセスがあるので検索ログには不完全なキーワードも残る。それらが表示されないように 1 文字だけのキーワードや、実際に Tantiny に投げて返ってくる結果が少ないキーワードは除外している。

自己満足ではあるのだが、このサイトをもっとも閲覧していてもっとも検索している自分が便利になればそれで良い。

| @ブログ

嘉穂アルプス 屏山

最近結構走ってるから iOS のフィットネスアプリとか HealthFit でめっちゃログや統計を確認してる。

前月との比較 直近 12 ヶ月間の月ごとの走行距離 日毎の積み上げの前年比
前月との比較、直近 12 ヶ月間の月ごとの走行距離、日毎の積み上げの前年比

いま自分は月間 100km 以上走ることを目標にしている。月間 100km はガチラン勢からしたら大した距離ではないのだが、油断して 3, 4 日走るのをサボったり雨が降ったりすると達成が難しくなるので、前月の同日と比べて進捗はどうかを確認してる。

同じような感じで進捗や達成状況を確認できる機能がブログにも必要なのではないかと思って、自分のブログのアーカイブページで年を選ぶとその年の月ごとの投稿数をグラフで表示するようにした(これまでは年で絞っても年ごとのグラフしか表示されてなかった)。

これで去年の同月に比べてどうかとか、その時期にどんな内容(カテゴリー)の記事をよく書いていたのかがわかるようになった。ランニングと同じように自分の頑張り状況が可視化されるようになった。


そもそもブログを書くことの意義とは何だろうか。大まかに二つあって、以下のように整理されるのではないかと思う。

  1. バズる記事を書いてアクセスを集める
    • 集めたアクセスで自分の認知度を高める
    • 集めたアクセスをマネタイズする(広告)
    • 世の中を変える
  2. 自分の内省のために書く
    • 読書や学習で得た知識を貯めておく
    • 考えを整理する

1 の観点でいうとアクセス数が重要なのだが、いまどきブログで小銭を稼ごうとか世界を変えようとかしてる人はいないはずで、ほとんどのブログは著者自身のために書かれてる(↑の 2 の方)と思う。自分のために文章を書きたい人にとって、書いた内容と密度や頻度、また過去に書いた記事を簡単に振り返ることができることの方が重要なはずだ。

なのに、たいていのブログサービスはアクセス解析機能はあるが、過去の自分の執筆状況を統計的に確認できる機能はない(少なくとも ameblo とはてなブログと livedoor Blog にはなかった)。アクセス数は Twitter で影響力のある人にシェアされたとか外部要因で増えることもあるから、それだけで自分のブログアクティビティを正確に測るのは難しい。

同じような話を以前にもブログに書いている。

振り返りの観点でも既存のブログはあまり良くない。たいていのブログはそもそも自分で書いた過去記事を読むのも大変だったりする。時系列に全文表示されてページネーションして辿らないといけない。

検索機能もいまいちだ。自分の考えたことを貯めておく場としてはちょっと貧相すぎる。

世の中に一般的に出回ってるブログは著者自身のためというより、いかにアクセスを集めて広告でマネタイズするかに主眼が置かれているから、内省や情報の取り出しやすさはあまり考慮されていないのだろう。いかに PV を上げるか、いかに滞在時間を長くするか、いかに回遊させるかが大事になってくる。

しかし先ほども書いた通り、芸能人や著名人を除き、ブログは自分のために書いている人が多いはずで、アクセス数を集めることよりも書き手の体験にフォーカスしたブログがあってもよいのではないだろうか。

ランニングはアプリを使ってログを取ることでいろんなデータが可視化されめっちゃモチベーションが上がる。「今月 100km 走っちゃった〜」とか、「 1km のペースが 6 分切れるようになってきた〜」とか。前年同月との進捗比較も面白い。

ブログでも「今月は 10 記事書けた〜」とか「 1000 文字以上の記事を 5 記事書いた〜」みたいな達成感はあるんじゃないかと思う。つまり、 GitHub の Contribution グラフのブログ版みたいなやつが必要だと思うのだ。

GitHub Contributions Graph

ランニングだと時計だったり Strava などのサービスが走るモチベーションを喚起してくる。一方でブログは誰も書くのを促してくれない。

ブログを書く人が少なくなったのは SNS のせいという面は間違いなくあるけど、ブログ自体の進化が足りないのだと思う。もっと書き手の脳みそをハックして、著者に書きたいと思わせるような仕組みがないとブログは衰退していくばかりなんじゃないだろうか。

突き詰めると、ブログの主なマネタイズ手法が広告しかないのが根っこの問題な気がする。読み手に広告を見せることで収益化するのではなく、書き手の使い勝手や満足度を高めることで収益化できる可能性(書き手に対して機能課金する)はないのだろうか。ランニング系のサービスやアプリは実際そうやって収益化してる(うまく行っているかはわからない)。

| @ブログ

Mac の定番ランチャー、 Alfred のような検索 UI が好きだ。こういう UI で検索できるソフトウェアやウェブサイトがあると気持ちいい。自分のブログにモーダルウィンドウの検索を追加したときも Alfred みたいにしたいと思いながら作った。

見た目は Alfred っぽくできたが、キーボードで操作できない点が気になっていた。いちいち画面右上の🔍マークを押さないと検索 UI を呼び出せないし、検索しようと思って気が変わったときにはカーソルを動かしてモーダルウィンドウの外の領域をクリックしないとモーダルを閉じられない。これが地味にストレスだった。

見た目だけでなく使い勝手も Alfred のようにしようと思ってガチャガチャやってみた。 Access Key を割り当てて Alt + Control + s で検索窓を開き、おもむろに検索文字列を入力すると結果が出てきて、矢印キー上下で候補を移動でき、 Return で選択(その記事に遷移)できる。検索をやめたいと思えば Esc でモーダルウィンドウを閉じることができる。トラックパッドやマウスに持ち替えることなく、キーボードだけで検索できるようになった。めっちゃ便利。

| @ブログ

普段は Vim などのテキストエディターで文章を書いていて、ブログの投稿画面にはできあがった内容をコピペするだけだったので、投稿画面の使いやすさやは気にしたことがなかった。画像のドラッグ・アンド・ドロップ・アップロードや、オン・ザ・フライでプレビューをできるようにしたが、テキスト自体の書きやすさを改善しようとはしてこなかった。

大筋はテキストエディターで書いたとしても、最後に推敲したり、推敲していて気が付いたおかしなところの修正は投稿画面で行うことが多い。やっぱり投稿画面の書きやすさは重要だ。特に長大な内容を編集しているときにテキストエリアが狭いととても使いづらい。ある程度大きさがあり、画面内に大量の文字が表示されるテキストエリアが好みだ。

Lokka が開発されていた 10 年以上前は解像度の小さいディスプレイが主流で、 Lokka の管理画面は小さいサイズのウィンドウで閲覧することを想定したテキストエリアのサイズになっている。今日の高解像度ディスプレイで見ると不便なので画面一杯にテキストエリアが広がるようにした。

投稿画面のレイアウトを改善

iPad からも投稿しやすいように投稿画面のレイアウトも修正した。本文のテキストエリアが広がったためスクロールしないと「スラッグ」以降の入力欄にアクセスできなくなったので、ある程度横幅のあるウィンドウのときにはこれらを右側に配置するようにした。

加えて、フォームを書きかけで保存したかどうかがはっきりせず、未保存の内容があるのに保存せずページから離脱してしまうことがあったので、変更点があるときは背景色を変えてわかるようにした。これにより記入内容を保存せずページを離脱してしまい、内容が失われるという悲劇を回避できるようになった。やり方は適当に検索して出てきた Stack Overflow を参考に、ページを読み込んだ時点で JavaScript でフィールド内の要素を JSON.stringify して data attribute に格納し、その後各フィールドの input イベントを監視して変更があるかどうかをチェックしている。

class FormObserver {
  constructor() {
    this.initializeFields();
    this.observeFieldsChange();
  }

  initializeFields() {
    const fields = Array.from(document.querySelectorAll('div.field'));
    for (const field of fields) {
      const inputElement = field.querySelector('input[type="text"], textarea, input[type="datetime-local"], select option:checked');
      if (inputElement === null) {
        continue;
      }
      field.dataset.serialize = JSON.stringify(inputElement.value);
    }
  }

  observeFieldsChange() {
    const fields = Array.from(document.querySelectorAll('div.field'));
    for (const field of fields) {
      const inputElement = field.querySelector('input[type="text"], textarea, input[type="datetime-local"], select');
      if (inputElement === null) {
        continue;
      }
      inputElement.addEventListener('input', () => {
        if (field.dataset.serialize != JSON.stringify(inputElement.value)) {
          inputElement.dataset.changed = 'true';
          field.classList.add('edited');
        } else {
          inputElement.dataset.changed = 'false';
          field.classList.remove('edited');
        }
      })
    }
  }
}

input イベントを監視すると動作がもっさりするのではないかと心配したが、そんなことはないようだ。普通に使えている。

いまのところ管理画面の HTML レンダリングはサーバーサイドで Ruby で行っているが、これ以上凝ったことをやるなら React などを使って JavaScript で HTML を組み立てる方式にしていく方が効率が良さそうだ。

| @ブログ

404 ページ、昔はそもそもなくて 404 Not Found ステータスを返すだけだったり、あっても「見つかりません」というだけのものが多かったけど、最近はサイトマップ的なコンテンツや代替となるコンテンツを表示するサイトも見かける。というわけでこのサイトでもやってみることにした。

このブログの URL は /YYYY/MM/DD/slug という形式になっている。パスの /YYYY/MM/DD の部分はお飾りで、実際は slug がユニークになっているので slug で表示すべき記事を判定している。

よくあるのが記事を公開後、 slug 部分にタイポを見つけて変更するというケース。しかしすでにその時点で記事が Twitter などでバズってたりすると、 Twitter で共有されている記事を見てやってきた人が 404 Not Found ページを見ることになる(この前の「不便になるインターネット」がまさにそうだった)。それはまずいので slug のタイポを修正すると同時に Nginx の設定ファイルをいじってタイポ修正前の URL から修正後の URL へリダイレクトするようにしていた。しかしリダイレクトごときでサーバーの設定ファイルを修正して root 権限でリロードするというのはめんどい。 SSH でログインもしなければならない。大げさすぎる。

というわけで思いついたのがこの機能で、 Ruby でクラス名やメソッド名をタイポしたときに正しい候補を表示する did_you_mean.gem を利用した。存在しない slug で URL を開くと以下のように候補が表示される。

404 Not Found

コードはこんな感じ。

# Helper
def not_found_candidates
  @not_found_candidates ||=
    begin
      slugs = Entry.published.where.not('slug REGEXP ?', '^[0-9]+$').pluck(:slug)
      spell_checker = DidYouMean::SpellChecker.new(dictionary: slugs)
      current_slug = request.path_info.split('/').last
      slug_candidate = spell_checker.correct(current_slug)
      Entry.published.where(slug: slug_candidate)
    end
end

# View
- if not_found_candidates.any?
    %p Did you mean?
    - not_found_candidates.each do |candidate|
      = link_to candidate.title, candidate.link

データベースから slug 一覧を取り出して辞書とし、 DidYouMean::SpellChecker に食わせて似たページの候補を取得して表示する。タイポありのページを訪れた人はワンクリックしなければならないという手間が増えるが、これでタイポを修正したときに面倒なリダイレクトの設定をする必要がなくなった。

なお 404 ページには検索窓や最近の記事、カテゴリー一覧も表示して回遊性を高めている。

404 Not Found ページ

| @ブログ

グローバルナビゲーション(右上の白い領域)内の検索ボタンを押したら Alfred 風のモーダル検索フォームが開いて、そこにキーワードを入力するとインクリメンタルサーチが実行されて逐次検索結果の記事が表示されるようにした。

これまでだと検索すると Archives ページの絞り込み検索に飛ばすだけだったが、 Archives ページに遷移せずに検索できるようになった。また Archives ページだと時系列順でしか検索結果が表示されないが、インクリメンタルサーチではマッチ度順に関連度の高いものを表示するようにしている。ただし表示するのは上位 10 件だけにして、それ以上は Archives ページで時系列順の検索に飛ばしている。

昔ながらのブログの検索 UI には不満がある。ページネーションで何ページも辿って検索結果を見ていくのは大変だし、大抵並び順が時系列順で自分が最も用事がありそうな記事に辿り着くのに時間がかかる。自分のブログの検索はタイトルのみ表示されればよくて本文のプレビューは不要だし(著者だからタイトルを見ただけでどんな記事なのか大体わかる)、何ページもページネーションせずに一覧でガッと検索結果を見たい。それに結果は時系列順ではなく関連度が高い順に並んでいて欲しい。キーワードを一部だけかすってるような最近の記事が最も関連度が高い記事を差し置いて最上位に表示されるのはいまいちだ。

今回作った Alfred 風インクリメンタルサーチではこれらの問題が解消されていて非常に満足。自分にとって自分のブログが世の中の情報の中で一番参照頻度が高いし、そのブログで効率的に情報を取り出せるのは大切なことだと思う。

| @ブログ

ダークモードとライトモードの切り替えをグローバルナビゲーション(上の方の半透明の白い領域)から簡単に行えるようにした(これまではこのサイトについてのページで切り替える必要があった)。デフォルトは OS の設定準拠で、 OS がダークモードであればダークモードに、ライトモードであればライトモードで表示される。手動でテーマを切り替えると Cookie に設定値を保存する。機能と見た目は MDN Web Docs を参考にした。

CSS がぐちゃぐちゃなのでかなり難儀した( 3 年前にも同じことを書いている)。ダークモードとライトモードの切り替えなんて自分しかしていないと思うがかなり満足度の高い休日プログラミングだった。