/** * 添付ファイル共通ヘルパー。 * - toBase64: File → base64(data: URL のヘッダを除去) * - uniqueAttachmentName: クリップボード画像の "image.png" 問題と名前衝突を解決。 * 添付一覧は name を React key / 削除キーに使うため一意である必要がある。 */ export async function toBase64(file: File): Promise { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => { const result = String(reader.result ?? ''); resolve(result.includes(',') ? result.split(',')[1]! : result); }; reader.onerror = () => reject(reader.error ?? new Error('file read error')); reader.readAsDataURL(file); }); } function splitExt(name: string): { stem: string; ext: string } { const dot = name.lastIndexOf('.'); if (dot <= 0) return { stem: name, ext: '' }; return { stem: name.slice(0, dot), ext: name.slice(dot) }; } export function uniqueAttachmentName( original: string, taken: Set, now: Date = new Date(), ): string { let base = original; // ブラウザのクリップボード画像は一律 "image.png" で来るため日時名に差し替える if (original === 'image.png') { const pad = (n: number, w = 2) => String(n).padStart(w, '0'); const stamp = `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`; base = `pasted-${stamp}.png`; } if (!taken.has(base)) return base; const { stem, ext } = splitExt(base); for (let i = 2; ; i++) { const candidate = `${stem}-${i}${ext}`; if (!taken.has(candidate)) return candidate; } }