作業効率化用のサイトを作る際、「プロンプト(指示内容)の末尾に、とりあえず後ろに貼り付ける」だけで作るサイトの利便性を上げる事ができる可能性の高い追記文を紹介します。
こういった場合に、恐ろしく便利です。
ユーザーが必要な操作に関して、「サイトを開いてインポートボタンからファイルを選ぶ」というのが通常の手法ですが、このプロンプトはこの手法に加え「エクスポート操作でダウンロードしたHTMLファイルをダブルクリックなどで開くだけで、ブラウザが起動し、元サイトへ移動して作業状態を復元できる」手法も採用しています。
- エクスポート・インポート機能を付与できる
- 特にPC操作ではCtrl+Sによるエクスポートが可能(こまめにエクスポートするのが簡単になる)
- 再開するのはダウンロードしたエクスポートファイルを開くだけ
- リアルタイムセーブにより、ブラウザを閉じても作業が再開できる
という仕組みを組み込めるため、操作性が上がる事が多いです。
下記のプロンプトを、プロンプト末尾に追記して下さい。(1行目のリアルタイム保存部分は、サイトによって向き不向きがありますので、省くかどうかの検討が必要です)
各内容は作業の度にindexeddbに保存して、次に開いた際に、その状態から再開されるようにして下さい。
エクスポート・インポートボタンを作成して下さい。
Ctrl+Sでエクスポートが実行されるようにして下さい。
エクスポートの仕様は以下の通りです。
## HTMLエクスポート機能追加プロンプト
添付HTMLに、作業状態を持ち運ぶHTMLエクスポート機能を追加してください。既存の画面・構成を保ち、必要箇所だけ変更します。
### 要件
- 保存対象:`[入力値・設定・チェック状態などを指定]`
- 保存HTML名:`[ファイル名.html]`
- エクスポート時に開いているURLへ移動するHTMLを出力する。開くとそのURLへ**実際に遷移**し、データを復元する。URLのクエリは維持し、復元後はフラグメントを消す。
- アプリ本体は保存HTMLに含めず、遷移処理と圧縮データだけを含める。
- 対応可能な複数の圧縮形式を試し、圧縮後のデータが最小の形式を採用する。フラグメントのkeyに形式を記録し、keyに応じて復元形式を選ぶ。例:`#gz=...`、`#dr=...`。
- 「エクスポート」ボタンとCtrl+S/Command+Sを用意する。ファイル選択からのインポートにも対応し、HTML全体は実行せず、許可した遷移URLと形式だけを検証して読み取る。
- 保存データにバージョンを付け、破損・未対応データでは既存入力を壊さずエラー表示する。実装後、保存・ファイルを開く・インポート・ショートカット・破損データを確認して結果を報告する。
添付HTMLを調査し、実際の状態取得・復元処理に合わせて実装してください。以下は要点を示す例です。状態関数と保存名は既存サイトに合わせてください。
```js
const formats = [
{ key: "gz", format: "gzip" },
{ key: "df", format: "deflate" },
{ key: "dr", format: "deflate-raw" }
];
async function transform(bytes, format, decompress = false) {
const Ctor = decompress ? DecompressionStream : CompressionStream;
const stream = new Blob([bytes]).stream().pipeThrough(new Ctor(format));
return new Uint8Array(await new Response(stream).arrayBuffer());
}
function toB64Url(bytes) {
let s = "";
for (const b of bytes) s += String.fromCharCode(b);
return btoa(s).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
}
function fromB64Url(s) {
const b = atob(s.replace(/-/g, "+").replace(/_/g, "/") + "===".slice((s.length + 3) % 4));
return Uint8Array.from(b, c => c.charCodeAt(0));
}
async function makePayload(state) {
const raw = new TextEncoder().encode(JSON.stringify({ v: 1, state }));
const candidates = await Promise.all(formats.map(async item => {
try {
const compressed = await transform(raw, item.format);
return { ...item, data: toB64Url(compressed) };
} catch { return null; }
}));
const best = candidates.filter(Boolean).sort((a, b) =>
(a.key.length + a.data.length) - (b.key.length + b.data.length)
)[0];
if (!best) throw new Error("利用できる圧縮形式がありません。");
return `${best.key}=${best.data}`;
}
async function exportHtml() {
const hash = await makePayload(collectAppState());
const target = new URL(location.href);
target.hash = hash; // 現在のURLとクエリを保ち、保存データを付加
const safe = JSON.stringify(target.href).replace(/</g, "\\u003c");
const html = `<!doctype html><meta charset="utf-8"><title>読み込み中</title><script>location.replace(${safe})</script>`;
const a = document.createElement("a");
a.href = URL.createObjectURL(new Blob([html], { type: "text/html;charset=utf-8" }));
a.download = "[ファイル名.html]";
a.click();
setTimeout(() => URL.revokeObjectURL(a.href), 1000);
}
async function restoreFromHash() {
const match = location.hash.match(/^#(gz|df|dr)=([A-Za-z0-9_-]+)$/);
if (!match) return;
const item = formats.find(x => x.key === match[1]);
const raw = await transform(fromB64Url(match[2]), item.format, true);
const saved = JSON.parse(new TextDecoder().decode(raw));
if (saved.v !== 1) throw new Error("未対応の保存データです。");
applyAppState(saved.state);
history.replaceState(null, "", location.pathname + location.search);
}
document.addEventListener("keydown", e => {
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "s") {
e.preventDefault();
exportHtml();
}
});
```
`CompressionStream` の標準対応形式を確認し、使えない形式は候補から除外してください。Brotliは標準APIで利用できない環境があるため、ライブラリが既にある場合だけ候補に含めます。CSSを変更する場合は `@layer` を使い、`!important` は使わないでください。