動的ページのインデックスの問題点?
クエリパラメータ中のユーザ入力を画面内に表示するページを作ったら ユーザ入力ありで Google にインデックスされてた
なんでこんな全然関係なさそうな本文のページが自サイト内の検索結果に出てくるんだろうと思ってたらそういうことだったのか
そのページを開いたらちゃんとその誰かが入力したワードがクエリパラメータに含まれていて その内容がページ内に表示されるし 検索結果としては間違ってはないと思う

例えば 「foo bar」 という文章を打ったとして クエリパラメータは base64 形式にして 「usertext=Zm9vIGJhcg」 となるとする
Google 検索結果の候補に 「foo bar」 という内容のページがあって リンク先が 「/page.html?usertext=Zm9vIGJhcg」 という感じ
ブログみたいな投稿するサービスでもなくサーバサイド処理は一切なしの静的ファイルをおいてるだけのサイトなので 文章を書いた人は検索エンジンに載るとは思わないだろうしプライベートな内容を書いてると困りそう
でも自分でそのページを開いただけだとインデックスされないだろうし 自分でリンクをどこかのウェブページに貼ったのだろうから別にいいのかな

どっちかというと 多くの人が使うとサイト内検索のノイズが増えてサイト管理者の方が困るのかも
JavaScript の処理後の結果をインデックスしてくれるのはメリットだけどこういう問題もあったとは
App History API
Chrome の開発中の機能を見てるとこんなのがあった
https://wicg.github.io/app-history/

window.appHistory が追加されるみたい
window.history よりも SPA ページを作りやすくするものだとか
過去の履歴を取得できたり navigate メソッドでページ遷移できる
AppHistory 自体が EventTarget を継承していて navigate などのイベントを受け取れる

現状の history は history といいつつ過去の状態は見れないし pushState と replaceState で URL 書き換えるくらい
戻る/進むのイベントは window の popstate イベントだからページの更新処理は pushState や replaceState を呼び出したところと popstate イベントの複数箇所書かないといけなくて不便だった
このあたりが解決するのはよさそう
Vivaldi でタブが消せないバグ
ときどき Vivaldi でタブが消せなくなるなと思っていたら原因がわかりました
devtools 上で F5 キーを押してページをリロードすると タブが消せなくなります
タブエリアでミドルクリックしたりタブの×ボタンを押したり右クリックからタブを閉じるを選んでも反応なしです

今のところ Vivaldi を再起動する以外にタブを消せるようになる方法が見つかってません
最新版にアップデートしても修正されてないようです

以前は devtools 上で F5 キーを押してもページがロードされなくて不便だったのが解消されたと喜んでいたらこれです
どうしてもこうも Vivaldi は devtools 周りのバグが多いのでしょうね



書いた直後にもしやと思って試すと解決策が見つかりました

devtools を再度開いて console で 「close()」 を実行します
Vivaldi の UI 上からでは閉じれませんが JavaScript からだと閉じれました
一度閉じるとそれ以降は Vivaldi の UI 上からタブを閉じれるようになります
Windows 10 が終わるみたい
Windows 10 が最後の Windows だって言ってたのに
最近言われている 11 という噂も全く信用してなかったのに まさか本当だったなんて

11 という名前は発表されてないので別の名前かもしれませんが Windows 10 のメジャーアップデートごとのサポート期限のページを見てみると Home / Pro / Enterprise / Education 全てに提供終了日が 2025/10/14 と書かれています
https://docs.microsoft.com/ja-jp/lifecycle/products/windows-10-home-and-pro
https://docs.microsoft.com/ja-jp/lifecycle/products/windows-10-enterprise-and-education

急な発表なのに 2025 年って早すぎ
あとたったの 4 年しかないです
次のが来るということがわかっていて そろそろ来そうという時期なら これくらいの移行期間でも良いと思います
ですが Windows10 はこれが最後と言われていて サポート終了が来るなんて想定していなかったわけですから 影響的には出たばかりに 4 年後に終了と発表されるのと大差ないです

ただ 「提供終了日」 とあり それまでは半期チャンネルでリリースすると書かれているので 単にメジャーアップデートが出なくなるだけなのかもしれません
Windows7 の延長などを見ても セキュリティフィックスとかも提供されなくなるのはもっと先の気がします

10 ではなくなるというのだと やっぱりこれまでのように OS を買い直すことになるのでしょうか
自動で無料でのアップグレードならいいですが そうじゃなければ Windows7 と同じ未来しか見えません
ちなみに自宅は未だに Windows7 が現役です
バグか仕様かは主観的なものだと思う
これはバグだ仕様だの話を見るときによく思うこと
製作者から見て仕様であっても 使う側からみるとバグ なんてのはよくあるもの
どっちが正しいと言うのはないと思うし どっちも正解だと思う
使う側で使えない以上 その人からしてはバグなんだし
もちろん CSV パーサで JSON がパースできない みたいに明らかにおかしいのもあるけど

機能的に一貫性があればこういうときはこうなるはずだ これとこれを組み合わせたらこういう事ができるはずだ というところで 想定したとおりに動かないみたいもの
作る側が想定していなかったから動かないし直すつもりはないというのは別に構わないと思う
でもそれを仕様と呼んだところで ユーザからすれば動くべきところが動かないというだけ
製作者側が一方的にこれは仕様かバグかを決めるならその違いに対して意味はないと思う
仕様かバグかはどっちでも良くて 動くか動かないか 使えるか使えないか だし

仕様書なんてのがある世界で生きてる人は それが一つの基準かもしれない
でもそれはすべてのケースが網羅されてるの?
そこに書いてない挙動は何があっても全部仕様?
そう契約してるならそれでいいと思うけど すべてがそういうわけじゃないよね

作るときに想定してなくても 「言われてみると そのほうが正しい」 と思うなら直すこともあるし 逆にバグと認めていても直さない・直せないことだってある
バグ報告があって それが仕様かバグかの判定なんていらないから 直すか直さないか で伝えたらいいと思う
実際に どう見てもバグであっても自分の方が正しいと仕様と言い切ったり 間違ってると理解していても直せないから仕様ということにする人だっている
仕様です って返答されても 「だから使えないって言ってるんだって」 としかならないしイライラさせるだけ
まだ 直さない・直せないと言う答えのほうが納得できると思う

あと 自分の周りというかネットも含めて見かける範囲では 昔ながらの IT 系で働いてる人に 過剰と思うほど仕様かバグかをはっきりさせたがる人が多いように思う
バグと言う言葉に過敏に反応して バグ扱いされたら これはバグじゃない!とすごく熱くなってる
バグと言う言葉自体を「言ってはいけない言葉」であるかのように扱ってるようにも見える
業界的なことなのかもしれないけど 自分はユーザ寄りで気軽にバグバグ言うので 理解できない世界
自分が作ったプログラムを我が子のように思ってたりするのかな?
バグ扱い=家族を侮辱されたと考えれば あの必死さもわからなくもない……ような気もするような
mime type いらないと思う
ウェブでは Content-type に mime type が使われます
これを見て JavaScript を実行するかなどの判断が行われるので 全部 text/html 扱いの簡易サーバだと動かない部分があります
簡易サーバを自作するときでもこの当たりが面倒なので パッケージに頼ることが多いです

この mime type ですがいらないと思うんです
ちゃんとファイルの中身を見た上で ファイルの種類を判断してサーバがレスポンスを返してるならともかく ほとんどの場合は拡張子から自動変換するだけです
ファイルの中身を見てからファイルの種類を判断して mime type を返すようなものは見たことがないです

Node.js だと Express や Koa などのフレームワークはだいたいこのどちらかのパッケージを使っています
mime type と拡張子の対応が記述されている JavaScript オブジェクト形式のデータベースファイルです
https://github.com/broofa/mime/blob/master/types/standard.js
https://github.com/jshttp/mime-db/blob/master/db.json

どちらも週間ダウンロード数が 3000 万を超えています
外部パッケージは使わない方針の hapi でもこれを使っています

Python でも標準の mimetypes.guess_type で mime type を取得できます

都度 ファイルの中身を確認するのは処理が遅くなりますし ファイルごとに mime type 情報を別に保持しておくのも面倒が多いので仕方ないとは思います
ですが それなら最初から mime type じゃなくて拡張子を見ればいいと思うんです

ウェブページの URL なので HTML ファイルに .html がついてないとか .php を HTML ファイルとしたいとかあるのはわかります
そういうときだけ HTTP ヘッダーのレスポンスに情報を付加して なにもないデフォルト値は拡張子を見てほしいです

今更 変わらないとは思いますけど ちょっとしたことするだけでも mime type 変換もいるのは面倒なんですよね
ブラウザ拡張機能として localhost サーバから未指定・text/plain・text/html が来たときは拡張子から判断したものに置き換えるようにするのはありかもですね
サーバは色々変わってもブラウザは同じですし
VSCode でタブキーを押すとフォーカスが移動する
タブ文字を入力したくてタブキーを押してるのに入力されず 代わりにフォーカスが別のところに移動します
ブラウザの標準の textarea でタブを打つと次の input に移動してしまうみたいな状況です

以前もこの状態になったことがあったのですが 直し方がわからず VSCode 全体を再起動することで直ったこともあってバグかなと思ってました
今回 その原因がわかりました

ちゃんとした タブ入力でフォーカス切り替えを行うモードでした
このモードが有効になっていると右下の現在のカーソル位置などが書いているところに「Tab Moves Focus」と表示されます

そして Ctrl-M キーがこのモードを切り替えるショートカットキーになっていました
Ctrl-N のつもりで間違って押していたのでしょう

知らないうちにモードが切り替わってしまうと直し方がわからず困りますね
Vivaldi 4 で翻訳機能が使えるようになった
https://vivaldi.com/ja/whatsnew/desktopnews/vivaldi-4-0/
新機能で開いてるページを翻訳できるようになりました
翻訳機能の無さが Chrome に比べて不便なところでしたが とうとう実装されたようです

ただ 使ってみた感じでは翻訳の質は Google 翻訳に劣ります
Lingvanex というサービスを Vivaldi がホストして使ってるそうです
翻訳の質が高いと評判の DeepL とか使ってくれたらいいのに

翻訳の方法は URL バーのアイコンからです
Chrome とは違って右クリックメニューから翻訳できません
アイコンは常にあるわけではなく 自動判断で翻訳が必要そうなら出てくるようです
ページを開いても出てこないこともあれば 遅れて出てくることもあります
このあたりは改善していってほしいですね
Fedora 34 で cifs マウントできなくなった
Fedora 34 に更新して以降 Windows の共有フォルダのマウントができていません
ログを見ると エラーが出ています

libcap-ng used by "/usr/sbin/mount.cifs" failed due to not having CAP_SETPCAP in capng_apply

すでに Redhat の Bugzilla にはバグ報告があります

https://bugzilla.redhat.com/show_bug.cgi?id=1924218
https://bugzilla.redhat.com/show_bug.cgi?id=1962920

自分で最新版をビルドして解決したという報告もあるように cifs-utils 本体側では対応済みのようです
https://lists.samba.org/archive/samba-technical/2020-December/136156.html

去年末リリースの 6.12 の修正内容に CAP_SETPCAP 関係がいくつかあります
積極的にパッケージの更新を行う Fedora なら 6.12 をリリースしてくれても良いと思うのですが まだ 6.11 です

自分でビルドするのも面倒なので早く dnf upgrade で更新できるようになってほしいですね
Windows10 のユーザ作成で秘密の質問を要求される
直前の記事の通り マイクロソフトアカウントで Windows ユーザを作ると問題が出たので最初からオフラインアカウントで作り直すことにしました
相変わらず オフラインアカウントは隠し機能みたいになってるなぁ と思いながらパスワードを登録したところで 秘密の質問を登録してくださいという画面が出ました
これまでは出たことなかったですし マイクロソフトアカウントを使う場合は出ませんでした
最近の Windows ではオフラインアカウント作成時に必須になってるみたいです
スキップもできませんでした

秘密の質問を強制されるサービスがありますが すごくやめてほしいです
たいていの項目は家族や友人なら答えれる可能性があるようなものですし ターゲットが誰かを特定できれば情報は入手しやすいものです
普通のパスワードより不正ログインの難易度が落ちてます

質問内容と全く関係ない回答もできますが それはそれで覚えられないです
3 個設定が必要で 全部に同じ答えが使えなかったり 使える文字が ひらがなのみ みたいな制限もあって 使いまわしづらいですし

せめて質問自体が自由入力ならまだ有用だと思うのですけど

仮のもので登録してセットアップ処理の完了後に消そうと思って キーボードを適当にカチャカチャと押した文字列で登録したのに 設定やコントロールパネルのユーザ画面では秘密の質問の設定が見当たらなかったです
自分でもわからないくらいに適当な文字列で設定したので 悪用されることは考えにくいですし このまま放置です
Microsoft アカウントで Windows のユーザを作ったときのユーザフォルダの名前
C:\Users\【ここ】

↑の部分に入る名前は基本はユーザ名がそのままです
ですが 初回セットアップ時にマイクロソフトアカウントを使ってみたら メールアドレスの最初の 5 文字になっていました

中途半端なところですし 気持ち悪いのでローカルアカウントに切り替えました
切り替えのときにユーザ名を設定できたので ユーザフォルダ名も切り替わるものだと思ってたのですが ユーザフォルダ名はそのままでした

ただでさえ 謎の 5 文字で気持ち悪いのに ログイン時などに使うユーザ名とユーザフォルダ名が違うというさらに気持ち悪い状態になってしまいました
PHP で指定の名前空間に属する関数やクラスなどの一覧を取得
指定の名前空間内で定義されているものの一覧を確認したい
だけど そういう関数はないみたいだったので自作
すべての関数取得やすべてのクラス取得といった関数はあったので 全部取得してから名前空間でフィルタしてる
サブ名前空間も含めるかを指定可能

function get_namespace_items($namespace, $include_sub_namespaces = false) {
$namespace = trim($namespace, '\\') . '\\';

$classes = get_declared_classes();
$interfaces = get_declared_interfaces();
$traits = get_declared_traits();
$functions = get_defined_functions();
$constants = get_defined_constants();

$filter = function ($items) use ($namespace, $include_sub_namespaces) {
$matched = [];
foreach ($items as $item) {
if (strpos($item, $namespace) === 0) {
if (
$include_sub_namespaces ||
strpos(substr($item, strlen($namespace)), '\\') === false
) {
$matched[] = $item;
}
}
}
return $matched;
};

return [
'class' => $filter($classes),
'interface' => $filter($interfaces),
'trait' => $filter($traits),
'function' => $filter($functions['user']),
'constant' => $filter(array_keys($constants)),
];
}



使用例

<?php

function get_namespace_items($namespace, $include_sub_namespaces = false) {
$namespace = trim($namespace, '\\') . '\\';

$classes = get_declared_classes();
$interfaces = get_declared_interfaces();
$traits = get_declared_traits();
$functions = get_defined_functions();
$constants = get_defined_constants();

$filter = function ($items) use ($namespace, $include_sub_namespaces) {
$matched = [];
foreach ($items as $item) {
if (strpos($item, $namespace) === 0) {
if (
$include_sub_namespaces ||
strpos(substr($item, strlen($namespace)), '\\') === false
) {
$matched[] = $item;
}
}
}
return $matched;
};

return [
'class' => $filter($classes),
'interface' => $filter($interfaces),
'trait' => $filter($traits),
'function' => $filter($functions['user']),
'constant' => $filter(array_keys($constants)),
];
}

// define and declare
require_once('./def.php');

var_dump(get_namespace_items('\\foo\\bar', false));
var_dump(get_namespace_items('\\foo\\bar', true));

[def.php]
<?php

namespace foo\bar;

function f() {}

class C {}

abstract class A {}

trait T {}

interface I {}

const c = 1;
define('d', 2);

// sub namespace
namespace foo\bar\baz;

function ff() {}

結果

// sub namespace なし
array(5) {
["class"]=>
array(2) {
[0]=>
string(9) "foo\bar\C"
[1]=>
string(9) "foo\bar\A"
}
["interface"]=>
array(1) {
[0]=>
string(9) "foo\bar\I"
}
["trait"]=>
array(1) {
[0]=>
string(9) "foo\bar\T"
}
["function"]=>
array(1) {
[0]=>
string(9) "foo\bar\f"
}
["constant"]=>
array(1) {
[0]=>
string(9) "foo\bar\c"
}
}

// sub namespace あり
array(5) {
["class"]=>
array(2) {
[0]=>
string(9) "foo\bar\C"
[1]=>
string(9) "foo\bar\A"
}
["interface"]=>
array(1) {
[0]=>
string(9) "foo\bar\I"
}
["trait"]=>
array(1) {
[0]=>
string(9) "foo\bar\T"
}
["function"]=>
array(2) {
[0]=>
string(9) "foo\bar\f"
[1]=>
string(14) "foo\bar\baz\ff"
}
["constant"]=>
array(1) {
[0]=>
string(9) "foo\bar\c"
}
}
Gist のセルフフォークできなかった
Gist で 以前作ったものをベースに少し違ったものにしたいときがあります
機能追加やバグ修正みたいな更新とは違うので バージョン管理されていて以前のものに戻せるとはいえ 上書きして元のコードを置き換えてしまうのはちょっと違います
Gist は置けるファイル数が少なく フォルダ構造も作れないので 新しい Gist を作成してベースにしたい Gist からファイルを持ってくればいいのですが 面倒なのと完全に独立した別々のものになってしまうのもなぁと思います

ふと フォークすればできるんじゃないの? と閃きました
Gist は Github の機能ですし フォーク機能はあったはずです
それにフォークならフォーク元という情報が残って関連 Gist がわかるのも良いところです

早速試してみたのですが……フォークボタンがありませんでした
見覚えはあるのにと思って探すと 他人の Gist ならフォークボタンが出てきます
自分の Gist はフォークできないみたいです

このためだけにアカウントをもう一つ作って フォークするときだけ交互に使うというのはなんか違いますし 結局諦めて新しい Gist にファイルをコピーすることにしました
ShadowDOM 内の leaflet でデフォルトマーカーが使えない
leaflet を Custom Element の中で使うとデフォルトマーカーが表示されませんでした
コンソールを見ると 開いてる HTML のページと同じフォルダから探そうとしてるようです
CDN から leaflet をロードしてるのでそっちを見てほしいのですけど……

Custom Element を使わなければデフォルトマーカーが表示できているので どういうことなのか調べてみました
結果は Shadow DOM による CSS の分離が原因でした

アイコンパスを検出する関数はこれです
https://github.com/Leaflet/Leaflet/blob/v1.7.1/src/layer/marker/Icon.Default.js#L45

「leaflet-default-icon-path」 クラス付きの div を作って一時的に document.body に追加しています
その div の現在有効なスタイルの background-image に設定されたパスをもとにベースとなるパスを検出してるようです

leaflet の CSS がロードされていれば その CSS で設定された URL がもとになるので CDN からロードされるというわけです
しかし Shadow DOM を使っていて その中でのみ leaflet の CSS をロードしていると document.body に追加された要素にスタイルは適用されません

その結果 見つからず同じフォルダから探すことになっていました
document.body ではなく ShadowRoot などに置き換えられればよいのですが document.body でハードコーディングされているので 関数ごと置き換えることになります

import * as L from "https://unpkg.com/leaflet@1.7.1/dist/leaflet-src.esm.js"

L.Icon.Default.prototype._detectIconPath = () => {
return "https://unpkg.com/leaflet@1.7.1/dist/images/"
}

import のところに URL を書いてるので CSS を経由せずに直接書くことにしました

_detectIconPath の結果は IconDefault.imagePath に代入されるので

import * as L from "https://unpkg.com/leaflet@1.7.1/dist/leaflet-src.esm.js"
L.Icon.Default.imagePath = "https://unpkg.com/leaflet@1.7.1/dist/images/"

にもできます
ただ 互換性のために残されてますが IconDefault.imagePath は deprecated らしいので options.imagePath の方に設定するほうが良いかもしれません

import * as L from "https://unpkg.com/leaflet@1.7.1/dist/leaflet-src.esm.js"
L.Icon.Default.prototype.options.imagePath = "https://unpkg.com/leaflet@1.7.1/dist/images/"

CustomElement と leaflet の問題は以前もあった気がするのですが そのときはこういう対処をした覚えがないです
どうしたんだったっけ
Firefox 89 で見た目が変わった
新デザインと聞いていたのでアップデートしました
極端に変わってはないのですが 少し使った感じだと 個人的に前のほうが好きでした

タブが変に広くて縦幅取ってますし URL バーの左右には無駄な空白があります
あと Pocket やメニューなどのアイコンは小さくなって少し押しづらい感があります

何が良くなったのかはいまいちわからないのですが 統計情報を基によく使われる/使われない部分を分析して変更したらしいです
ほとんど使われてないところは非表示になったりです
「何が無くなったか」と言われると特に気づけませんでした

ちゃんと統計情報に基づいてデザイン決めてるだな~と感心した一方で 統計情報の送信って拒否してる人も多いですよね
Google とか Microsoft のサービスではよく送信するかを聞かれますが そういうこと聞かれたらイヤと言う人が多そうです
それも自由だと思いますが 統計情報を送らない以上 開発者側からはいないもの扱いなので自分はよく使う機能だったのに誰も使ってない機能と扱われて機能が消えてしまったりとかもありそうですよね
情報の送信を拒否するような人ほどマイナー機能まで使ってそうな印象ありますし
WSL の Ubuntu のアップグレードができない
WSL で使っていた Ubuntu が以前のバージョンのままだったのでアップグレードしようとしました
LTS 間でのアップグレードなので問題もないと思っていたのですが問題が起きました

「do-release-upgrade」 コマンドが途中で止まります
最後に表示されるメッセージはこれです

Reading state information... Done

何度試してもここで止まります
ログファイルを見てみてもエラーらしいものが見つかりません

以前に別の PC で Ubuntu を更新したときも同じコマンドを使ったはずで そのときは何も問題なく更新できたと思うのですけどね

ぐぐってみるとこんなページがありました
https://github.com/microsoft/WSL/discussions/3489

snapd を消すと動くという報告があります

apt remove snapd

これを実行してからもう一度 do-release-upgrade コマンドを実行してみます
すると 次に進みました!

上のページでは 18.04 へのアップグレードですが 私は 20.04 へのアップグレードでした
バージョン問わず do-release-upgrade 使う場合に起きる問題のようです

snapd を調べてみると パッケージマネージャでした
そういえば GUI インストールした Ubuntu ではこの折り紙みたいなアイコンを見たことがあります
この WSL 環境では GUI アプリを試しに使ってみたことがあり snapd が入ってたようです
以前問題なく更新できた環境だと GUI を入れてなかったから snapd も入ってなかったのかもしれません
C ドライブ全体のフォルダ共有
デフォルトで C ドライブなどドライブルートはフォルダ共有されています

\\172.23.116.130\c$\

みたいに共有名にドライブレターと $ を入れます
隠されているのでコンピュータ名だけを指定して共有一覧を見ても表示されていません

PowerShell の Get-SmbShare コマンドで共有リストを取得するとちゃんと含まれています

PS C:\Users\nexpr> Get-SmbShare

Name ScopeName Path Description
---- --------- ---- -----------
ADMIN$ * C:\WINDOWS Remote Admin
C$ * C:\ Default share
IPC$ * Remote IPC

C$ へのアクセスで使うユーザ情報は Administrator の必要があります
普段使いの管理者権限ユーザの情報ではログインできません

Administrator は無効になってることが多いので 使う場合は

コンピュータの管理 > ローカル ユーザとグループ > ユーザー

から Administrator を選んで

◯ プロパティを開いてアカウントを無効にするのチェックをはずす
◯ 右クリックからパスワードの設定

が必要です

設定すると Administrator と設定したパスワードで C$ を開けます

セキュリティ的にはあまり良くないと思いますが VM やサンドボックスのファイルすべてをホスト側のエクスプローラで操作したいときに使えます
Fedora のデフォルトエディタが nano になってた
https://fedoraproject.org/wiki/Changes/UseNanoByDefault

33 からだったようです
32 が最近 EOL を迎えてたので更新して気づきました

理由は git commit などのコマンドからエディタが起動したときに vi だと混乱する人が多いからのようです
これはすごくわかります
最近でこそ 保存と終了くらいはできるようになりましたが 私も慣れないころは vi が起動されると終了すらできなくて困りました
Ctrl-C は効かないし ググっても最初は意味不明だったのでタブごと閉じてたり……
CUI 環境だと強制再起動?

それに比べて nano は下に使い方が書いてるくらいのユーザフレンドリーさです
機能は多くないものの最低限の編集には十分です

デフォルトとするなら nano のほうが向いてると言えます
vi 使いたいような少数派の人たちは デフォルトエディタを変更するくらい苦もなくできるでしょうし

nano 使いからしては CentOS がインストール方法によってはデフォルトで nano が入ってないということに不満なのですが この変更が取り込まれる頃にはデフォルトインストールされることになりそうですね

やっぱり nano は最高なの
ページ遷移を防ぐモジュール
beforeunload イベントのリスナでページ遷移を防ぐ処理は 最近だと任意テキストを表示できないし 個別にリスナを登録する意味は特に無い
モジュールごとにリスナを登録していくと 後から強制的に移動させたい場合に解除が面倒
なので ページ遷移を防ぐ処理だけをモジュールにまとめた

let listener_added = false
const set = new Set()

const listener = (event) => event.returnValue = "preventer"

export const prevent = (key) => {
set.add(key)
if (set.size && !listener_added) {
window.addEventListener("beforeunload", listener)
listener_added = true
}
}

export const unprevent = (key) => {
set.delete(key)
if (!set.size && listener_added) {
window.removeEventListener("beforeunload", listener)
listener_added = false
}
}

export const forceUnpreventAll = () => {
for (const key of set) {
unprevent(key)
}
}

export const getReasons = () => [...set]

prevent 関数の引数にキーを入れて呼び出す
キーは任意の型で文字列とかシンボルとか
1 つ以上登録されてればページ遷移しようとするとダイアログが出る

unprevent 関数の引数にキーを入れて呼び出すと 対応する prevent を解除する

forceUnpreventAll 関数を呼び出すと全部の prevent を解除する
ただし このモジュール外で登録した beforeunload があれば ダイアログは出る

getReasons 関数でキーのリストを取得できる
現状がページ遷移がブロックされる状態かの判断や 何が原因でブロックされる状態なのかを確認できる

現状の実装だと prevent に登録されたキー数が 0, 1 の切り替わりでリスナの付け外しをしてるけど 常に付けておいて returnValue に代入するかを切り替えるでもよかったかも
IIAFE
関数即時呼び出しの IIFE の非同期版
読み方は IIFE の読み方がイッフィーだから IIAFE は イアッフィー ?
なんかテンション高そう