Phase 11: version PDF (FR-15)
- เลข version ใหม่: เว็บ-generate = v0 ฉบับตั้งต้น (ไม่มี PDF), add-in save (Start Versioning + Save & Version) = v1+ มี PDF เสมอ - PDF ผลิตจาก Word จริงผ่าน task pane เท่านั้น (getFileAsync Pdf + runtime guard, manifest ไม่เพิ่ม requirement); capture/upload เป็น best-effort — docx ผ่านแม้ PDF ล้ม + MessageBar เตือนตาม hasPdf - Backend: DocumentVersion.PdfObjectKey (nullable, migration AddVersionPdf) + sibling .pdf ใน bucket docx-versions · multipart part pdf (ผิด type → 400) · /download + /download-link รับ ?format=pdf (ไม่มี → 404) · presigned ฝังชื่อไฟล์ไทย + _vN (RFC 5987) - Web: เปิดไฟล์แทน blob-download — PDF แท็บใหม่, docx ผ่าน ms-word:ofe|u| พร้อม heuristic fallback ลิงก์สำรอง (useVersionOpen, versionDownload) · ปุ่ม "เปิดใน Word" ที่หน้า success ของฟอร์มสร้างเอกสาร - Spec v7 + CONTEXT.md + ADR 0001 + แผน docs/plans/version-pdf.md - Automated QA: backend 68 / add-in 63 / web 55 ผ่าน · manual acceptance ผ่านบน Word for Mac (2026-09-08) รวม fidelity + track changes — ค้างเฉพาะ Windows / Word on the web Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
parent
618328ffa7
commit
39d610a781
39 changed files with 1387 additions and 212 deletions
|
|
@ -9,26 +9,24 @@ export const DOCX_CONTENT_TYPE =
|
|||
"application/vnd.openxmlformats-officedocument.wordprocessingml.document";
|
||||
|
||||
/** ขนาด slice ต่อครั้ง — 1MB กันกิน memory ยักษ์ในเครื่องรายงานน้อย */
|
||||
export const PDF_CONTENT_TYPE = "application/pdf";
|
||||
|
||||
const SLICE_SIZE = 1024 * 1024;
|
||||
|
||||
function officeError(result: Office.AsyncResult<unknown>, fallback: string): Error {
|
||||
return new Error(result.error?.message || fallback);
|
||||
}
|
||||
|
||||
function getFileAsync(): Promise<Office.File> {
|
||||
function getFileAsync(fileType: Office.FileType): Promise<Office.File> {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
Office.context.document.getFileAsync(
|
||||
Office.FileType.Compressed,
|
||||
{ sliceSize: SLICE_SIZE },
|
||||
(result) => {
|
||||
if (result.status === Office.AsyncResultStatus.Succeeded) {
|
||||
resolve(result.value);
|
||||
} else {
|
||||
reject(officeError(result, "เปิดไฟล์เอกสารไม่สำเร็จ"));
|
||||
}
|
||||
Office.context.document.getFileAsync(fileType, { sliceSize: SLICE_SIZE }, (result) => {
|
||||
if (result.status === Office.AsyncResultStatus.Succeeded) {
|
||||
resolve(result.value);
|
||||
} else {
|
||||
reject(officeError(result, "เปิดไฟล์เอกสารไม่สำเร็จ"));
|
||||
}
|
||||
);
|
||||
});
|
||||
} catch (error) {
|
||||
reject(error instanceof Error ? error : new Error("เปิดไฟล์เอกสารไม่สำเร็จ"));
|
||||
}
|
||||
|
|
@ -104,15 +102,26 @@ function closeFileQuietly(file: Office.File): Promise<void> {
|
|||
* ประกอบไฟล์ทั้งไฟล์เป็น Blob — อ่าน slice ทีละชิ้นตามลำดับ
|
||||
* เรียก closeAsync เสมอ (สำเร็จหรือ fail ก็ตาม)
|
||||
*/
|
||||
export async function getCurrentDocumentFile(): Promise<Blob> {
|
||||
const file = await getFileAsync();
|
||||
async function readFileAsBlob(fileType: Office.FileType, contentType: string): Promise<Blob> {
|
||||
const file = await getFileAsync(fileType);
|
||||
try {
|
||||
const chunks: BlobPart[] = [];
|
||||
for (let index = 0; index < file.sliceCount; index++) {
|
||||
chunks.push(await getSliceAsync(file, index));
|
||||
}
|
||||
return new Blob(chunks, { type: DOCX_CONTENT_TYPE });
|
||||
return new Blob(chunks, { type: contentType });
|
||||
} finally {
|
||||
await closeFileQuietly(file);
|
||||
}
|
||||
}
|
||||
|
||||
export async function getCurrentDocumentFile(): Promise<Blob> {
|
||||
return readFileAsBlob(Office.FileType.Compressed, DOCX_CONTENT_TYPE);
|
||||
}
|
||||
|
||||
export async function getCurrentDocumentPdf(): Promise<Blob> {
|
||||
if (!Office.context.requirements.isSetSupported("PdfFile", "1.1")) {
|
||||
throw new Error("Word นี้ไม่รองรับการสร้าง PDF");
|
||||
}
|
||||
return readFileAsBlob(Office.FileType.Pdf, PDF_CONTENT_TYPE);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue