🗓️

1日がメモのカレンダーを作った

2025/03/02に公開

作ったもの

https://loosecal.com

https://github.com/my12parsecs/loose-calendar?tab=readme-ov-file

Screenshot

どういうものか

普通のカレンダーだと時間を入力したりイベント単位で入力しないといけなくて自由度がなかったので、1日1日がメモのカレンダーを作りました。

最初は既存のサービスを探したものの、大体はiOSアプリでパソコンからのアクセスが難しかったので、ウェブサービスにしました。

技術

NextjsをVercelにデプロイしていて、データベースはNeon、ORMはPrisma。認証はAuthjsを利用。

メモ帳のエディターはTipTapを初めて使い、色々なExtensionを活用したり自分でもTimestamp機能を導入するなどしていい感じにできました。

日付・時間・言語

日付・時間・言語はやっぱり面倒くさいなと改めて思いました。Clientのタイムゾーンや言語でUIの表示をしたかったので、"use client"をつけてuseEffectをごにょごにょする形。自分でやるのは大変だったので、日付を色々いじれるdayjs、ユーザーデバイスの言語を調べるget-user-locale、タイムゾーンを調べるjstimezonedetectのお世話になることに。

Clientの設定によって変わるものとしては曜日の言語("Mon","月"とか)と、そもそもClientの日付が何で、それによって適切なカレンダーにすること。

エディター

Timestamp

@3-5のように打つと@3:00~5:00のようなタイムスタンプに変換される機能をつけました。

TiptapのエディターでonUpdateで更新されるたびにregexのパターンにマッチするかチェックし、一致したら特殊なspanに変換するようにしています。少し難しかったのでClaudeにだいぶ書いてもらいました。

  const patterns = [
      {
        regex: /@(\d{2})(\d{2})-(\d{2})(\d{2})\s/,
        replace: (match, h1, m1, h2, m2) => `@${h1}:${m1}~${h2}:${m2}`,
      },
    
      // One/Two digits - One/Two digits (e.g., @1-2 => @1:00~2:00)
      {
        regex: /@(\d{1,2})-(\d{1,2})\s/,
        replace: (match, h1, h2) => `@${h1}:00~${h2}:00`,
      },
    
      // Dot separator (e.g., @1.2-3.4 => @1:20~3:40)
      {
        regex: /@(\d{1,2})\.(\d{1,2})-(\d{1,2})\.(\d{1,2})\s/,
        replace: (match, h1, m1, h2, m2) => {
          const roundedM1 = m1.length == 2 ? m1 : parseInt(m1, 10) * 10;
          const roundedM2 = m2.length == 2 ? m2 : parseInt(m2, 10) * 10;
          return `@${h1}:${roundedM1}~${h2}:${roundedM2}`;
        },
      },

      { regex: /@all\s/, replace: () => '@AllDay' }, // @all => @AllDay
// ...もっといっぱいパターンはある
]

ログインせずともローカルストレージに保存

ログインするとデータベースに書いたメモが保存されるが、ログインせずとも使えるようにしかったので、未ログイン状態で使うとLocalStorageに保存するようにしました。複数の環境から見る必要がない人はログインせずに使うことができる。

初めてサイトに来た人のためにデモのメモを表示させたかったので、「未ログイン」かつ「初めて来た」場合にはLocalStorageにデモのメモを保存して表示するようにした。


チュートリアル的な内容のメモ

「初めて来た」かどうかは、サイトに来た時にlocalStorage.setItem("isNotFirstVisit", true);(もしまだ無かったら)をすることで判別した。

ショートカットキー

初めてサイトにショートカットキーをつけてみたが、react-hotkeys-hookを使ってとても簡単に導入できた。

Bで戻る、Jで先週、Kで次週、Mで月曜、T+Uで火曜...などなど

スマホユーザーには一切便益はないけど、パソコンだととても便利。

紙のような質感

物理の手帳の雰囲気を少しでも出したかったので、Serifつきのフォントを使ったり(メモや説明文のところはInterだけど)、紙の質感を出すためにBackgroundに以下のような画像を設定した。


透明のPNGだけど、少し柄が入っている

この画像はこのGitHub Repoで見つけて、そのサイトの紙の雰囲気が良かったので拝借した。サイトの内容は難しすぎてわからなかったけど、紙みたいで良い。ただ白背景のときに若干グレー掛かるので、Light Mode用に少し画像を編集したりした。

はやくしたい

Clientの情報(日付やタイムゾーン)が前提でサイトのUIが構築されるので、若干表示が遅いと思います。たぶん今後やるべきこととしては、全てのタイムゾーンに対応できるように最初からプラスマイナス1日くらい多くメモを取得しといて、タイムゾーンがわかった時点で表示する?ぶっちゃけ何がどういう順番で処理されているのかがまだ理解しきれていないので、もっと実験してみようと思います。

Discussion