at backyard

Color my life with the chaos of trouble.

Lexicalを使って画面が二分割されているタイプのMarkdown エディターを作ってみた

下書きに入れていたまま塩漬けになっていた生地をまたしても見つけた。というか割とたくさんの塩漬けがある。。

このまま塩漬けにしていてもしょうがないので公開する。

lexicalを触っていた時のものだが、絶賛開発中のプロジェクトだと思うのでこの記事に関するソースなども古くなっているかもしれないことは一応書いておく。

以下本文。

——————————————————————-

最近Lexicalに触れる機会があり、個人的にも興味があるプロダクトだったため、Lexicalを使って試しにMarkdown エディターを作ってみた。

ソースコードは下記。

github.com

最近はVSCodeで使うときぐらいしか見ることはなかったが、少し前は画面が二分割されているタイプのMarkdownエディターをMac上で使っていたこともあり、そんなやつをReactを使って作ってみることにした。

なお、Lexicalはまだ絶賛開発中という感じで、破壊的変更が途中で入っていたりもするので、人が書いたブログを見るよりは Lexical の公式ドキュメントやソースコードを見るほうが確実だと感じた。 また、Lexical自体のドキュメントもまだ充実しておらず、READMEに書かれているサンプルコードも簡素化されており実際のインターフェースとは異なるものなどもあったりするため、そういう意味ではソースコードやそれに近い部分(ソースコード上に書かれているコメントなど)を見るほうが良いかもしれないと思ったりもした。

というわけで、このブログでも上に載せたソースコードの解説を入れようかと思ったが、書いても役に立たなくなる可能性ありなので文章はこれぐらいに留めておく。

ヨーグルトを毎日欠かさず食べていたら尿酸値が下がり、コレステロール値が改善した話

最初に

ここに書いた文章はあくまで私の個人的な経験による文章となり、ヨーグルトを食べれば必ず効果が出るものではありません

あくまで個人的な日記としてこの文章を書いています。
また以上のような性格を持つ文章であるため、健康診断の細かな数値などについても載せません。
よって〇〇したらこうなった、実際の値はこちら、みたいなある程度信頼できる値などは提示しておりませんのであしからず。

あくまで個人の日記です。

毎日ヨーグルト生活

私はビールや酒のつまみに合いそうなものがとても大好きなため、尿酸値がいつも悩みのタネとなっていた。

ここ数年間は病院の人間ドックや健康診断などで尿酸値が高めの値を出しており、常に気をつける対象となっていた。

またコレステロール値もここ数年は常によろしくない値を叩き出しており、こちらも同様に気をつける対象となっていた。

そんなことを以前実家に帰った際に父親に話してみたら、「明治ブルガリアヨーグルトを毎日欠かさず食べていたら体質が変わって、花粉症が治ったし尿酸値の値も良くなったから試してみろ」と言われ、せっかくだから試してみると思い、およそ半年以上1年未満、毎日欠かさずヨーグルトを食べている。

食べている量はいつもこのサイズのものを一日半量ずつ食べているようにしている。
食べる時間帯は大抵は朝食の時間帯が多い。私は朝食は食べないことのほうが多いのだが、食べないと午前10時ぐらいには空腹を感じる。その空腹を埋めるためにヨーグルトを半量流し込んでいるという形である。

いつも明治ブルガリアヨーグルトだと飽きるので、時々違うタイプのヨーグルトを間に挟んでいる。

いずれにしても無糖タイプのものを選ぶようにしており、あとはなるべく白湯もよく飲むようにしている。

白湯はもともと好きだが、この時期になるとより美味しく飲めるようになるので消費量は増えている。

そんなこんなでヨーグルト代がかかることには目をつぶり(と言ってもビール代に比べれば安い)律儀にヨーグルト生活を続けていたのだが、先週たまたまちょっとしたことで病院に行き、採血を行い検査をする機会があった。

そのときに尿酸値やコレステロール値も計測されたのだが、なんとあれほど下がりにくかった尿酸値が基準値に近いぐらいまで下がり、おまけにコレステロール値も改善されていた!

これは当然ながら非常に嬉しかった。
尿酸値のためにと毎日欠かさず食べていたヨーグルト生活が報われように思え、自身の努力が実を結んだ、という言いようのない達成感があった。

というわけで、ここ最近うれしかったことベスト5にある出来事でした。

先日友人とあったときにも話したのだが、この年になると『健康』が関心ワードになるので、どうしてもこういう出来事は印象深くなるなーと思った冬の日であった。

カゼインは尿酸値排出に良い?

ちなみにヨーグルト生活を続け始めた頃にインターネットで見た内容なのでソースも記憶も曖昧だったりするが、どうやら乳製品に含まれるカゼインというタンパク質が尿酸値を排出する働きを持っているというのを見た。
カゼインが胃腸で分解されてアラニンという物質に変わり、このアラニンが尿酸排出を促進する作用を持っているらしい)

カゼインで検索するとあまり体に良くないらしい?ような記述も見かけたが、私としては尿酸を外に追いやってほしい一心でカゼインを取得するため、ヨーグルト以外にも時折牛乳なども飲むようにしていた。
(と言っても常識的な量しか飲んでいない。牛乳に限らず飲みすぎ食べ過ぎは良くない)

このカゼインが今回の結果に大きく関与しているかは定かではないが、とりあえず尿酸値(とコレステロール値)が改善されたのは事実なので、カゼインの力もあったのかもしれないなと思ったりしている。

花粉症は治るのか?

そんなわけで尿酸値とコレステロール値の改善が見られたわけだが、父親はヨーグルトを食べていたら花粉症も治ったらしい。
たしかに以前は辛そうだったので最近は楽そうだ。

私もここ数年花粉症に悩まされているので、今度の春にどうなっているかこの体を持ってして確認をしてみようと思う。

create-react-appで作成したアプリに別のエンドポイント(html)を生やす方法

はじめに

自分用の備忘録として書いているので説明が簡素なのと、細かなところまで確認していないので確認が漏れている箇所がある。
よって間違いが含まれている可能性がある。

また、この修正自体はちょっとした検証を行う際に必要だったという理由で行ったものであり、この設定を施したアプリをProduction環境にデプロイして確認したわけではないので、その点は注意する必要がある。

特に後半の rewrite の設定などはアプリの環境やデプロイするホスティングサービスによって設定を行う必要も出てくると思うので、参考程度にとどめてほしい。

目次

Create react appで作成したアプリに別のエンドポイントを生やす

Create react appで作成したアプリではエンドポイントが / のみとなる。

今回 /about.html というエンドポイントを生やし、about.html にアクセスした場合はそちらはそちらで別のSPAとしてアプリを機能させたいと考えた。

このポストはそれを実現するまでのメモとなる。

基本的にgit の diff を貼り付けてメモを残す。

ejectする

まず eject する。
ejectしないで済む方法はわからない。

yarn eject

一旦 eject した状態で問題なくアプリが動くかを確認し、一旦コミットする。

以降の diff はこのコミットした状態からの diff となる。

別のエンドポイントを追加するまでの修正内容

まずは config/paths.js を以下のように変更する。

--- a/config/paths.js
+++ b/config/paths.js
@@ -57,7 +57,9 @@ module.exports = {
   appBuild: resolveApp(buildPath),
   appPublic: resolveApp('public'),
   appHtml: resolveApp('public/index.html'),
+  appAboutHtml: resolveApp('public/about.html'),
   appIndexJs: resolveModule(resolveApp, 'src/index'),
+  appAboutJs: resolveModule(resolveApp, 'src/about'),
   appPackageJson: resolveApp('package.json'),
   appSrc: resolveApp('src'),
   appTsConfig: resolveApp('tsconfig.json'),

次に config/webpack.config.js を以下のように変更する。

--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -200,7 +200,10 @@ module.exports = function (webpackEnv) {
       : isEnvDevelopment && 'cheap-module-source-map',
     // These are the "entry points" to our application.
     // This means they will be the "root" imports that are included in JS bundle.
-    entry: paths.appIndexJs,
+    entry: {
+      index:[paths.appIndexJs],
+      about:[paths.appAboutJs],
+    },
     output: {
       // The build folder.
       path: paths.appBuild,
@@ -210,7 +213,7 @@ module.exports = function (webpackEnv) {
       // In development, it does not produce real files.
       filename: isEnvProduction
         ? 'static/js/[name].[contenthash:8].js'
-        : isEnvDevelopment && 'static/js/bundle.js',
+        : isEnvDevelopment && 'static/js/[name].bundle.js',
       // There are also additional JS chunk files if you use code splitting.
       chunkFilename: isEnvProduction
         ? 'static/js/[name].[contenthash:8].chunk.js'
@@ -569,7 +572,36 @@ module.exports = function (webpackEnv) {
           {},
           {
             inject: true,
+            chunks: ["index"],
             template: paths.appHtml,
+            filename: "index.html"
+          },
+          isEnvProduction
+            ? {
+                minify: {
+                  removeComments: true,
+                  collapseWhitespace: true,
+                  removeRedundantAttributes: true,
+                  useShortDoctype: true,
+                  removeEmptyAttributes: true,
+                  removeStyleLinkTypeAttributes: true,
+                  keepClosingSlash: true,
+                  minifyJS: true,
+                  minifyCSS: true,
+                  minifyURLs: true,
+                },
+              }
+            : undefined
+        )
+      ),
+      new HtmlWebpackPlugin(
+        Object.assign(
+          {},
+          {
+            inject: true,
+            chunks: ["about"],
+            template: paths.appAboutHtml,
+            filename: "about.html"
           },
           isEnvProduction
             ? {
@@ -642,9 +674,12 @@ module.exports = function (webpackEnv) {
             manifest[file.name] = file.path;
             return manifest;
           }, seed);
-          const entrypointFiles = entrypoints.main.filter(
-            fileName => !fileName.endsWith('.map')
-          );
+
+          let entrypointFiles = [];
+          for(let [entryFile, fileName] of Object.entries(entrypoints)) {
+            let notMapFiles = fileName.filter(fieldName => !fieldName.endsWith(".map"))
+            entrypointFiles = entrypointFiles.concat(notMapFiles);
+          }
 
           return {
             files: manifestFiles,

この時点で /about.html にアクセスして意図したページが表示されることを確認。

rewriteの設定

ただし、/about.html/foo みたいなURLをReact Routerなどでアクセスできるようにしている場合、/about.html/foo の状態でリロードすると意図した画面が表示されない。

ここでは config/webpackDevServer.config.js を編集し、下記のようなリダイレクト処理を挟むことにする。

ちなみに verbose: true にすると、 Rewriting 時のログをコンソール上で確認できる。

--- a/config/webpackDevServer.config.js
+++ b/config/webpackDevServer.config.js
@@ -98,6 +98,10 @@ module.exports = function (proxy, allowedHost) {
       // See https://github.com/facebook/create-react-app/issues/387.
       disableDotRule: true,
       index: paths.publicUrlOrPath,
+      verbose: true,
+      rewrites: [
+        { from : /^\/about/, to: "/about.html" }
+      ]
     },
     // `proxy` is run between `before` and `after` `webpack-dev-server` hooks
     proxy,

これで /about.html/foo の状態でリロードしても、意図したページの状態で描画されることが確認できた。

【Chrome拡張機能開発の備忘録】設定したショートカットを押してもポップアップが表示されないとき

Chrome拡張機能を開発していて、設定した manifest.json 内の suggested_key にショートカットキーを設定したのに、設定した通りのショートカットキーを押しても拡張機能のポップアップが表示されないケースが時折ある。

"commands": {
    "_execute_action": {
      "suggested_key": {
        "default": "Ctrl+Shift+J",
        "mac": "MacCtrl+Shift+J"
      },
    }
  },

例えば上記のように manifest.json を設定してmac上で Ctrl + Shift + J を押せばポップアップが表示されると思いきや、何故かうまくポップアップは表示されない...といった状態でかなりストレスになる。

なぜそうなるのか?という原因究明までは至っていないが、ひとまず問題を(暫定的かもしれないが)解決させる方法まではわかったので、勢いでこのポストを書いている。

Chrome拡張機能ページから割り当てられているショートカットキーを確認する

Chrome拡張機能ページ内にショートカットキーの割当を確認できるページがあるので、まずはそのページに遷移する。

遷移方法はChrome右上のメニューから その他のツール を選択し、更にその下の 拡張機能 を選択。
すると拡張機能ページに遷移するので、今度は画面左側のメニューから キーボード ショートカット を選択すると、拡張機能に割り当てられているショートカットを確認できるページに飛べる。

私の場合、うまく動いていない拡張機能のショートカットキーを確認してみると、manifest.json 内で設定しているショートカットキーとは異なるショートカットキーが割り当てられていたので、このページ内でそのまま意図したショートカットキーに修正した。

こういう直し方で抜本的な解決になるかは怪しいところだが、これでひとまず動くようになったので備忘録としてこの情報をこちらのブログに残しておこうと思う。

AirPods(第3世代)を購入したので率直な感想を書く

AirPods(第1世代)をずっと使っていたが、このほど新たにAirPods(第3世代)を購入した。

今回はAirPods(第3世代)を使ってみた率直な感想を書いていこうと思う。

目次

比較対象

上にも書いたが、内容としてはAirPods(第1世代)と比べてみてどうだったか?という視点で書いていく。

左が第3世代、右が第1世代

※こうやって改めて見返してみると、ケースの見た目的には第3世代のほうがスマートになった印象を受ける。

持ちやすさ

左が第1世代、右が第3世代。ご覧の通り、うどんの長さが違う

まず持ちやすさについてだが、AirPods(第3世代)はうどんの長さが短くなった。
私はよくうどんの部分をもって耳にかけるため、個人的にはうどんの部分が長い第1世代のほうが持ちやすく感じる。

うどんが短いと、ただでさえ落としやすいAirPodsがより落としやすくなる。

というわけで、持ちやすさは第1世代に軍配が上がる。

見た目はスマートかも

ただ、うどんが短くなったことで見た目的なスマートさは上がったかと思う。
まあデザインを取るか実用性を取るかという感じかなと思います(個人的な感想です)

感圧センサーで操作するように変わった

第1世代のほうはタップを2回することで好みの操作(私は 再生・一時停止 を設定していた)を行うことが出来たが、AirPods第3世代は感圧センサーでこれらの操作をするように変わっていた。

support.apple.com

再生・一時停止は感圧センサー部分を一度押すことで実現できる。

おすと「ピッ」という若干安っぽい音がする。

Bluetooth接続について

まだ利用して一月は経っていない状態であり、たまたまなのかもしれないが、Bluetooth接続がうまく行かないときが時々ある。 挙動としてはペアリングがうまく行っていないのか、対象のデバイスを見つけられない時があるという感じ。

大抵は中央のボタンを長押しすることですぐに見つけられるのだが、購入して最初の方はこのボタンを長押ししてもうまく動かない(ランプが白く点滅してデバイスから検知できる状態にならない)ことが何度かあり、返品を考えた。

「返品するか」と重い腰を上げようとしたらちゃんと動き始めて、それ以降は問題なく動作している、という謎な(そしてちょっと嫌な)AirPods第3世代生活のスタートを切っているので、ここらへんについてはまだ注視している。

私の個体がたまたまなのかもしれないが、そういう経緯もありBluetooth接続については第1世代のほうが安定している印象を持った。

音質

第一世代に比べてAirPods(第3世代)は低音が強調されている。
普段聴き慣れている音楽で聴き比べるとその差が歴然としている。

楽曲制作時などにモニターヘッドホンとは別に、一般的に使われているAirPodsなどのイヤホンでもモニターするが、低音が回る回る...低音の処理を誤ると低音が回ってしまいそうだなという印象。

AirPods(第3世代)のほうが音質が良いか?と聴かれると特にそうは思わない。

第一世代の聴き心地も悪くないと思うので、どっちが良いとかは特にないという感じ。

ここらへんは利用者の好みによる部分になるかと思われる。

空間オーディオ

空間オーディオの自然さには驚いた。

クオリティは高いが、すごい!で終わり。

人と話しているときとかにこれを使うとまた変わるのだろうか?別に使ってみたいとは思わないが。

結論(コスパ重視で第2世代でもあり)

AirPods(第3世代)になって全体的に良くなった、という感想は特に抱かなかった。

コスト的には今購入できる第2世代のほうが安いので、今だったらそちらを購入しても良いかもと思うぐらい。

www.apple.com

別にAirPods(第3世代)が悪いというわけではないが、特別良くなっているという感想は抱かなかったという感じ。

以上、率直な感想でした。

Wailsでアプリをビルドするときにアイコンを変更する方法

Wailsでアプリをビルドする際にアイコンを変更する方法についてのメモ。

buildディレクトリ配下に存在する appicon.png を差し替えて wails build を実行することで、差し替えたアイコンに変更されたことを確認した。
差し替え方法については

それぞれで確認している。

Windowsを利用していて、アプリのアイコンが反映されないケースがあった

上に書いたようにWindowsの場合も同じなのだが、ビルドしたのにエクスプローラー上から見えるアイコンが変更されていない場合がある。
(なお、この際にアプリウィンドウ左上のアイコンやタスクバーに表示されるアイコンは反映されている状況となっている)

どうやらWindowsにはアイコンキャッシュというものがあり、いわゆるキャッシュが残っている状態にとなっているらしい。
下記のissueに従い、プロパティを見てみるとアイコンが反映されていることを確認した。

github.com

PocketからExportしたHTMLデータをCSVに変換するツールをGoで書いて、Notionにimportした

私は普段からPocketを利用している。後でチェックしたいと思ったものはなんでもPocketに放り込んでおり、Pocket経由で見返すということをしている。
ちなみに読んだ記事をアーカイブする、とかそういうことは面倒なのでしていない。 Pocketに放り込んだら、あとは放置という非常に怠惰な管理の仕方をしている。そもそも管理と言えないようレベルかもしれない。

最近Pocketの中身をNotion側で検索したいと思うようになった。

普段Notionをメインのメモアプリとして利用しているのだが、「あ、あのときメモったやつ、Notionに記載していたか、それともPocketに入れていたか、どっちだったっけ?」と思うようなことが時折発生していたので、Pocket側で記録したものは全てNotion側の検索に引っかかるようにしたいと考えた。

そこでPocketのエクスポート機能を用いて吐き出したHTMLファイルをCSV形式に変換し、Notionにimportすることを思いついた。
※ちなみにPocketのエクスポート機能については以下を参照されたし

help.getpocket.com

エクスポートしたHTMLファイルをそのままNotion側にインポートすることも可能だったが、Pocketでエクスポートしたデータにはページタイトルが入っていないものが相当数あった。

Notionの検索インデックスには当然ページタイトルを載せたいと考えていたため、これではせっかくエクスポートしたデータは使い物にならない状態となっていた。

というわけで、エクスポートしたHTMLデータ内でページタイトルが入っていないものについてはページタイトルを取得してから、エクスポートデータ自体をCSV形式に変換するツールをGoで書いた。
(ちなみにとりあえず動けば良し!的なノリで書いているので、後日コードはもう少し整理するかもしれない)

github.com

これでページタイトルも込みでCSV形式のエクスポートデータが作成されるので、あとはこれをNotionにインポートすれば、ページタイトルで検索ができるようになる。

ちなみにCSV形式でimportしたデータはNotion側で自動的にテーブル構造化されるので見やすいのも良い。

吐き出したPocketデータは全てアーカイブか、削除すれば完璧...と思ったら、Pocket内のデータをすべて一度に削除する方法はないようだ...

help.getpocket.com

Pocketに保存したデータが8,000~9,000ぐらいあるので、手作業で全て削除は諦めた。まあ、そこらへんは今後も適当に管理していくか
(結局Pocket自体はちゃんと整理しきれていない)

追記:コメントでPocketに保存したデータをすべて消す方法をお教えいただいた

大変ありがたいことにコメントでPocketに保存したデータをすべて消す方法についてお教えいただいたので、追記する。

原文についてはこの記事のコメント欄をご覧いただくとして、ご説明いただいた下記の方法でPocketデータをすべて削除することができた。

Pocketに保存したリストを全部消していいのであれば以下で出来るかもしれません。
Web版で Account→Manage Account→Privacy「Clear Data」

またこちらは試していないが、API経由でも削除ができるようだ。

https://getpocket.com/developer/docs/v3/modify#action_delete

ありがとうございます!