v9.2.1: re-enable ms-word on all platforms + real filenames in URLs

User clarified: with the new open-link URLs (https + no query string +
ending .docx), pressing the button DID open the file in Word 16.101+
on their Mac — only the OLD URL forms (http, query strings) were
ignored. The previous disable-on-Mac was based on terminal tests that
hit the old forms / cold-start races, so it removed working behavior.

- useVersionOpen: fireWordScheme = openInWord !== 'off' (default ''
  and 'on' fire on ALL platforms; only explicit 'off' is download-only)
- Notice updated: "กำลังเปิดไฟล์ใน Word ให้อัตโนมัติ — หากไม่เปิด
  ไฟล์อยู่ที่โฟลเดอร์ Downloads แล้ว" (covers cold-start drop)
- Docs corrected with v9.2.1 header (old forms were the broken ones;
  new form confirmed working via real usage) + correction markers on
  the v9.2 test record + CLAUDE.md rule 13 rewritten
- web 60/60 + build; QA reviewer FAIL round 1 (5 doc/comment minors,
  all fixed) → PASS round 2

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
Suphonchai Phoonsawat 2026-09-10 14:33:12 +07:00
parent f4b2ff9d28
commit 4f54af4e24
7 changed files with 30 additions and 29 deletions

View file

@ -1,16 +1,16 @@
import { onBeforeUnmount, ref } from 'vue'
import { versionDownloadPath, wordOpenUrl, type VersionFormat } from '../utils/versionDownload'
/** Shared by history and generate success — ลิงก์เปิดอายุสั้นของ backend (open-link) ใช้ทั้งดาวน์โหลดและเปิดแบบ best-effort */
/** Shared by history and generate success — ลิงก์เปิดอายุสั้นของ backend (open-link) ใช้ทั้งดาวน์โหลดและเปิดใน Word */
export function useVersionOpen() {
const { apiJson } = useApi()
// อ่านตอน setup (มี nuxt context) — จะยิง ms-word:ofe ควบคู่กับการดาวน์โหลดหรือไม่
// (v9.2): Word for Mac 16.101+ เมิน URL ทุกรูปแบบและเด้งหน้าเลือกไฟล์ทุกครั้ง — default
// จึงไม่ยิงบน Mac; override ด้วย env NUXT_PUBLIC_OPEN_IN_WORD=on|off เพื่อบังคับทุกแพลตฟอร์ม
// (v9.2.1): open-link แบบใหม่ (https + ไม่มี query string + จบ .docx) เปิดใน Word ได้จริง
// รวมถึง Word for Mac 16.101+ — ยืนยันจากการใช้งานจริง (URL แบบเก่า http/มี query ต่างหากที่ถูกเมิน)
// default จึงยิงทุกแพลตฟอร์ม; ปิดได้ด้วย env NUXT_PUBLIC_OPEN_IN_WORD=off (เช่นเครื่องที่ Word
// เด้งหน้าเลือกไฟล์รำคาญ หรือ cold-start ทำ URL event หลุดบ่อยๆ)
const openInWord = String(useRuntimeConfig().public.openInWord ?? '').toLowerCase()
const isMac = typeof navigator !== 'undefined'
&& /Mac/i.test(navigator.platform || navigator.userAgent)
const fireWordScheme = openInWord === 'on' ? true : openInWord === 'off' ? false : !isMac
const fireWordScheme = openInWord !== 'off'
const openingVersionId = ref<string | null>(null)
const downloadError = ref<string | null>(null)
@ -39,15 +39,15 @@ export function useVersionOpen() {
a.remove()
}
/** เปิดไฟล์ docx แบบ download-first (v9.2): ดาวน์โหลดไฟล์ให้ทันทีทุกครั้ง แล้วยิง ms-word:ofe
* ควบคู่แบบ best-effort เฉพาะเมื่อ fireWordScheme (default: ไม่ยิงบน Mac — ดูคอมเมนต์ด้านบน)
/** เปิดไฟล์ docx (v9.2.1): ยิง ms-word:ofe ให้ Word เปิดไฟล์เลย + ดาวน์โหลดสำรองลง Downloads ทันทีทุกครั้ง
* (ถ้า Word เปิดให้อัตโนมัติก็ใช้ได้เลย ถ้าไม่ (เช่น cold-start ครั้งแรก URL event หลุด) ไฟล์ก็อยู่ Downloads แล้ว)
* ใช้กับลิงก์ที่ถืออยู่เองได้เลย (ไฟล์ร่าง FR-16: response ของ generate-draft คือ URL แล้ว) */
function openDocxUrl(url: string) {
resetOpen()
fallbackUrl.value = url
saveToDownloads(url)
openNotice.value = fireWordScheme
? 'ดาวน์โหลดไฟล์แล้วที่โฟลเดอร์ Downloads — เปิดจากที่นั่นได้เลย (บางเครื่อง Word จะเปิดให้อัตโนมัติ)'
? 'กำลังเปิดไฟล์ใน Word ให้อัตโนมัติ — หากไม่เปิด ไฟล์อยู่ที่โฟลเดอร์ Downloads แล้วเปิดจากที่นั่นได้เลย'
: 'ดาวน์โหลดไฟล์แล้วที่โฟลเดอร์ Downloads — เปิดจากที่นั่นได้เลย'
if (fireWordScheme)
window.location.assign(wordOpenUrl(url))

View file

@ -27,8 +27,8 @@ export default defineNuxtConfig({
// Keycloak dev ใช้ https:8443 (cert ของ office-addin-dev-certs — เชื่อถือได้ในเครื่อง)
keycloakAuthority: 'https://localhost:8443/realms/word-versioning',
keycloakClientId: 'word-versioning-web',
// ยิง ms-word:ofe ควบคู่กับการดาวน์โหลดหรือไม่ — '' = auto (ไม่ยิงบน Mac เพราะ Word 16.101+
// เมิน URL ทุกรูปแบบและเด้งหน้าเลือกไฟล์รำคาญ, ยิงบน Windows/อื่นๆ), 'on'/'off' = บังคับทุกที่ (v9.2)
// ยิง ms-word:ofe ควบคู่กับการดาวน์โหลดหรือไม่ — '' / 'on' = ยิงทุกแพลตฟอร์ม (open-link แบบใหม่ https+ไม่มี query+จบ .docx
// ยืนยันแล้วว่าเปิดได้จริงแม้ Word 16.101+ บน Mac — v9.2.1), 'off' = ดาวน์โหลดอย่างเดียว
openInWord: '',
},
},

View file

@ -20,7 +20,7 @@ function mount() {
beforeEach(() => {
vi.stubGlobal('useApi', () => ({ apiJson }))
// default: auto — happy-dom ไม่ใช่ Mac UA จึงยิง ms-word ควบคู่ (แต่ละ test ที่ต้องการก็ stub ทับได้)
// default: ยิง ms-word คู่กัน (toggle ไม่มี platform logic แล้ว — แต่ละ test stub ทับได้)
vi.stubGlobal('useRuntimeConfig', () => ({ public: { openInWord: '' } }))
tab = { opener: {}, closed: false, location: { replace }, close }
vi.spyOn(window, 'open').mockReturnValue(tab as unknown as Window)
@ -116,7 +116,7 @@ describe('version open lifecycle', () => {
expect(saved).toHaveLength(1)
expect(saved[0].href).toBe('https://files.example/draft.docx?sig=a%2Fb')
expect(state.openNotice.value).toContain('Downloads')
// ยังยิง ms-word ควบคู่แบบ best-effort เผื่อเครื่องที่ scheme ยังใช้ได้ (Windows / Word รุ่นก่อน)
// ยิง ms-word คู่กัน — Word เปิดไฟล์ให้อัตโนมัติ (ถ้า cold-start หลุด ไฟล์สำรองก็อยู่ Downloads แล้ว)
expect(assign).toHaveBeenCalledWith('ms-word:ofe|u|https://files.example/draft.docx?sig=a%2Fb')
})
it('re-click downloads again with the latest URL', () => {
@ -132,28 +132,22 @@ describe('version open lifecycle', () => {
expect(saved[1].href).toBe('https://files.example/second.docx')
expect(state.fallbackUrl.value).toBe('https://files.example/second.docx')
})
it('OPEN_IN_WORD=off: ไม่ยิง ms-word เลย — ดาวน์โหลดอย่างเดียว (Word 16.101+ บน Mac เด้งหน้าเลือกไฟล์ทุกครั้ง)', () => {
it('OPEN_IN_WORD=off: ไม่ยิง ms-word เลย — ดาวน์โหลดอย่างเดียว', () => {
vi.stubGlobal('useRuntimeConfig', () => ({ public: { openInWord: 'off' } }))
const assign = vi.spyOn(window.location, 'assign').mockImplementation(() => {})
vi.spyOn(HTMLAnchorElement.prototype, 'click').mockImplementation(() => {})
const state = mount()
state.openDocxUrl('https://files.example/draft.docx')
expect(assign).not.toHaveBeenCalled()
expect(state.openNotice.value).not.toContain('อัตโนมัติ') // ข้อความไม่เกี้ยวเรื่องเปิดอัตโนมัติ
expect(state.openNotice.value).toContain('Downloads') // ข้อความชี้ไปที่ Downloads อย่างเดียว
})
it('auto บน Mac: ไม่ยิง ms-word (default), override on แล้วยิงแม้บน Mac', () => {
it('default (auto) ยิง ms-word ทุกแพลตฟอร์ม รวมถึง Mac — open-link แบบใหม่ (https + ไม่มี query + จบ .docx) เปิดได้จริงบน Word 16.101+', () => {
vi.spyOn(window.navigator, 'platform', 'get').mockReturnValue('MacIntel')
let state = mount() // default '' → auto → Mac = ไม่ยิง
const assign = vi.spyOn(window.location, 'assign').mockImplementation(() => {})
vi.spyOn(HTMLAnchorElement.prototype, 'click').mockImplementation(() => {})
state.openDocxUrl('https://files.example/draft.docx')
expect(assign).not.toHaveBeenCalled()
app.unmount()
host.remove()
vi.stubGlobal('useRuntimeConfig', () => ({ public: { openInWord: 'on' } }))
state = mount()
const state = mount()
state.openDocxUrl('https://files.example/draft.docx')
expect(assign).toHaveBeenCalledWith('ms-word:ofe|u|https://files.example/draft.docx')
expect(state.openNotice.value).toContain('อัตโนมัติ')
})
})