Phase 12 v9: draft opens in Word + Thai word-breaking fix (FR-16)
แก้ตามผลใช้งานจริง 2 ประเด็น: 1. เปิดใน Word ทันที — generate-draft เปลี่ยนจากคืนไฟล์ตรงๆ เป็นเก็บ ชั่วคราวใต้ drafts/ (lifecycle ลบรายวัน, config Minio__DraftRetentionDays) + คืน presigned URL ให้เว็บเปิดผ่าน ms-word:ofe พร้อมลิงก์สำรอง (useVersionOpen.openDocxUrl แยกแกนเปิด docx URL ใช้ร่วมกับ flow v0) 2. ตัดคำไทย — T14 แตก placeholder เป็น [ / ข้อความ / ] โดย run ข้อความ ถือ w:hint="cs" + w:cs เท่านั้น; เดิมเขียนค่าลง run ปีกกาทำให้ Word จัดหน้าไทยผิด — แก้เป็นเขียนที่ run ข้อความยาวสุด + SDT เปล่าสืบทอด rPr จาก sdtPr (ใช้ร่วมทั้ง generate และ generate-draft) - ถอดของที่เลิกใช้: CORS expose Content-Disposition, downloadFile init, useApi.spec · Spec v9 + ADR 0002 Amendment + acceptance-review AC-19 - Tests: backend 84 / web 56 + build; QA reviewer PASS (2 rounds, 10 findings แก้ครบ — รวม bug จริงที่ test ใหม่จับได้: lifecycle Rules null ทำ startup ตั้ง rule ล้มแบบเงียบ) · ค้าง manual บน Word จริง Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
parent
b3c1c667f7
commit
70f71f4c5c
23 changed files with 408 additions and 241 deletions
|
|
@ -69,41 +69,5 @@ export function useApi() {
|
|||
return (await apiJson<T>(path, { method, body: form })) as T
|
||||
}
|
||||
|
||||
/**
|
||||
* ดาวน์โหลดไฟล์ผ่าน Bearer แล้วเซฟด้วย anchor download
|
||||
* (เว็บรันใน browser จริง — ไม่ใช่ webview ของ Word จึงใช้ blob download ได้ ต่างจาก add-in)
|
||||
* init ใช้ override method/body ได้ — ไฟล์ร่าง (FR-16) เป็น POST + JSON body ที่คืนไฟล์
|
||||
*/
|
||||
async function downloadFile(path: string, fallbackName: string, init: RequestInit = {}): Promise<void> {
|
||||
const res = await request(path, init)
|
||||
if (!res.ok) throw await toApiError(res)
|
||||
|
||||
const blob = await res.blob()
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = filenameFrom(res.headers.get('Content-Disposition'), fallbackName)
|
||||
document.body.append(a)
|
||||
a.click()
|
||||
a.remove()
|
||||
setTimeout(() => URL.revokeObjectURL(url), 10_000)
|
||||
}
|
||||
|
||||
return { request, apiJson, apiUpload, downloadFile }
|
||||
}
|
||||
|
||||
/** เอาชื่อไฟล์จาก Content-Disposition (รองรับ filename*=UTF-8 ที่ .NET ใส่มาสำหรับชื่อไทย) */
|
||||
function filenameFrom(header: string | null, fallback: string): string {
|
||||
if (!header) return fallback
|
||||
const utf8 = header.match(/filename\*=(?:UTF-8'')?([^;]+)/i)
|
||||
if (utf8) {
|
||||
try {
|
||||
return decodeURIComponent(utf8[1].replace(/^"|"$/g, ''))
|
||||
} catch {
|
||||
/* ผ่านต่อ */
|
||||
}
|
||||
}
|
||||
const plain = header.match(/filename=(?:"([^"]+)"|([^;]+))/i)
|
||||
if (plain) return (plain[1] ?? plain[2]).trim()
|
||||
return fallback
|
||||
return { request, apiJson, apiUpload }
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue