# Universal Document Viewer — Vue 2 + Vue 3 (satu paket)

**Satu source komponen, dua entry point JavaScript browser siap pakai.** Pilih versi Vue yang dipakai aplikasi. Komponen `<document-viewer>` dan seluruh propertinya sama untuk keduanya. Tidak perlu TypeScript, SFC (`.vue`), Vite, npm, atau proses build untuk menggunakannya.

Dibuat dengan pendekatan seperti [VueFilesPreview](https://github.com/VueFilesPreview/vue-files-preview), tetapi versi ini secara khusus memprioritaskan PDF.js, preview berjenjang **Microsoft Office → Google Docs Viewer → lokal** untuk DOCX/Excel.

**📘 Panduan pemakaian lengkap (termasuk `viewer-default`, DOCX lokal, radio Microsoft/GDocs/Local):** baca [`PANDUAN_PENGGUNAAN.md`](PANDUAN_PENGGUNAAN.md) untuk cara memasang, contoh Vue 2 / Vue 3, PDF locked, konfigurasi, dan troubleshooting.

**Demo publik:** [https://file-preview.inovasiapp.id/](https://file-preview.inovasiapp.id/) · [Vue 2](https://file-preview.inovasiapp.id/examples/vue2.html) · [Vue 3](https://file-preview.inovasiapp.id/examples/vue3.html)

## Pilih file

| File | Fungsi |
| --- | --- |
| `dist/document-viewer.vue2.js` | **Vue 2**: auto-register global dengan `Vue.component('document-viewer', ...)`. |
| `dist/document-viewer.vue3.js` | **Vue 3**: gunakan `app.use(window.DocumentViewer)` atau `app.component(...)`. |
| `dist/document-viewer.css` | Satu stylesheet yang digunakan **kedua versi**. |
| `examples/vue2.html` | **Demo Vue 2 interaktif:** 6 file sampel, upload, URL, pengaturan online/locked, event log, dan salin kode. |
| `examples/DEMO_VUE2.md` | Panduan menjalankan demo Vue 2, termasuk daftar file uji. |
| `examples/vue3.html` | Demo Vue 3, upload file dan URL. |
| `src/viewer.common.js` | Source tunggal yang sama untuk dua versi. |
| `tools/build.py` | Opsional jika ingin mengedit source dan membuat kedua file `dist/` ulang. |

Jalankan demo (static + API Node):

```bash
npm start
# PORT=8080 npm start
# lokal: http://localhost:8080/examples/vue2.html
# publik: https://file-preview.inovasiapp.id/examples/vue2.html
```

File sampel untuk demo Vue 2: **DOCX, PDF dua halaman, XLSX dua sheet, PNG, CSV, dan JSON** (semuanya sudah di dalam ZIP). Lihat [panduan demo Vue 2](examples/DEMO_VUE2.md). Jalankan lewat HTTP, bukan `file://` agar `fetch()` sampel bekerja.

Browser harus bisa mengakses CDN Vue untuk demo. Dependency preview khusus PDF/DOCX/Excel diambil dari CDN **saat format tersebut dibuka**; tidak semuanya dimuat di awal.

## Vue 2 — cukup include

```html
<link rel="stylesheet" href="/vendor/document-viewer/dist/document-viewer.css">
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.min.js"></script>
<script src="/vendor/document-viewer/dist/document-viewer.vue2.js"></script>

<div id="app">
  <document-viewer
    :src="fileUrl"
    filename="surat.pdf"
    :locked="true"
    :allow-download="false"
    height="700px"
  ></document-viewer>
</div>
<script>
new Vue({
  el: '#app',
  data: function () { return { fileUrl: '/api/documents/123/preview' }; }
});
</script>
```

`Vue.component('document-viewer', ...)` **sudah otomatis** dijalankan oleh file versi Vue 2. Jika perlu registrasi lokal: `components: { DocumentViewer: window.DocumentViewer }`.

## Vue 3 — cukup include

```html
<link rel="stylesheet" href="/vendor/document-viewer/dist/document-viewer.css">
<script src="https://cdn.jsdelivr.net/npm/vue@3/dist/vue.global.prod.js"></script>
<script src="/vendor/document-viewer/dist/document-viewer.vue3.js"></script>

<div id="app">
  <document-viewer
    :src="fileUrl"
    filename="laporan.docx"
    viewer-default="local"
    :allow-download="false"
    height="700px"
  ></document-viewer>
</div>
<script>
const app = Vue.createApp({
  data() { return { fileUrl: 'https://example.com/laporan.docx' }; }
});
app.use(window.DocumentViewer);
app.mount('#app');
</script>
```

> Gunakan **full build** Vue: Vue 2 `vue.min.js`/`vue.js`, Vue 3 `vue.global.prod.js`/`vue.global.js`, karena komponen menggunakan template string yang dikompilasi pada runtime. Build Vue *runtime-only* tanpa template compiler tidak bisa langsung menjalankan komponen ini.

**Jangan muat kedua entry point sekaligus** dalam satu halaman. Cukup pilih versi aplikasi Anda. Jangan campur Vue 2 dan Vue 3 di satu instance.

## Contoh Vue 2 + PHP

```html
<!-- komponen ada di DOM yang dikendalikan Vue -->
<div id="app">
  <document-viewer
    :src="documentUrl"
    :request-headers="{ Authorization: 'Bearer ' + token }"
    filename="kontrak.pdf"
    :locked="true"
    :allow-download="false"
  ></document-viewer>
</div>
<script>
new Vue({
  el: '#app',
  data: () => ({ documentUrl: '/api/arsip/preview/123', token: 'ACCESS_TOKEN' })
});
</script>
```

Untuk request berotorisasi, Microsoft Office Online **tidak** digunakan (Microsoft tidak bisa mengirim header Authorization aplikasi). PDF / DOCX / XLSX dipreview lokal dari hasil `fetch()`.

## Props

| Prop | Tipe | Default | Keterangan |
| --- | --- | --- | --- |
| `src` | `String / File / Blob` | `null` | URL berkas, `File` dari input, atau `Blob`. |
| `filename` | `String` | `''` | Nama file; wajib diberikan jika URL API tidak memiliki ekstensi. |
| `mime-type` | `String` | `''` | MIME type alternatif untuk PDF, gambar, teks, dll. |
| `width` | `String` | `100%` | Lebar viewer. |
| `height` | `String` | `650px` | Tinggi viewer. |
| `locked` | `Boolean` | `false` | Menyembunyikan download dan memblok menu klik-kanan PDF di area viewer. |
| `allow-download` | `Boolean` | `true` | Kontrol tombol download; `locked=true` selalu menang. |
| `viewer-default` | `String` | `local` | Engine Office: `local` \| `microsoft` \| `google`. Radio di header; tanpa fallback otomatis. |
| `max-rows` | `Number` | `250` | Batas baris saat preview Excel lokal. |
| `max-columns` | `Number` | `30` | Batas kolom saat preview Excel lokal. |
| `request-headers` | `Object` | `{}` | Header untuk `fetch()` URL yang memerlukan autentikasi. |
| `request-credentials` | `String` | `same-origin` | Fetch credentials (`same-origin`, `include`, `omit`). |
| `watermark` | `String` | `''` | Teks watermark overlay. Bukan proteksi hak cipta permanen. |
| `show-toolbar` | `Boolean` | `true` | Sembunyikan/tampilkan toolbar viewer. |

**Events:** `@loaded`, `@viewer-change`, `@update:viewerDefault`, `@provider-change`, `@provider-status`, `@error`, `@download`.

**Methods** (via `$refs.viewer`): `open()`, `selectViewer(mode)`, `useLocal()`, `goPage(delta)`, `changeZoom(delta)`, `toggleFullscreen()`, `download()`.

## Format dan perilaku

| Ekstensi | Preview | Catatan |
| --- | --- | --- |
| PDF | PDF.js canvas | Page/zoom, tanpa iframe browser PDF; dapat disembunyikan tombol unduh. |
| JPG, JPEG, PNG, GIF, WEBP, BMP, AVIF, SVG | Native image | zoom. |
| DOCX | Local (default) / Microsoft / GDocs | Pilih lewat `viewer-default` atau radio header. Online hanya untuk URL HTTPS publik. |
| XLSX, XLS, XLSM | Local (default) / Microsoft / GDocs | Tab per sheet untuk renderer lokal. |
| PPT, PPTX, DOC | Microsoft / GDocs | **Tidak ada** renderer lokal; konversi ke PDF/DOCX/XLSX bila perlu. |
| CSV, ODS | SheetJS lokal | Tidak perlu Microsoft. |
| TXT, LOG, JSON, XML, MD, YAML, SQL, JS, CSS | Text | JSON pretty print. Markdown tampil sebagai teks, bukan HTML. |
| MP3, WAV, OGG, M4A, FLAC, AAC | `<audio>` | Native browser. |
| MP4, WEBM, OGV, MOV | `<video>` | Codec harus didukung browser. |

### Pilih viewer Office (tanpa fallback otomatis)

Untuk dokumen Office, gunakan prop **`viewer-default`** atau radio di header toolbar:

1. **Local** (default) — `docx-preview` / SheetJS.
2. **Microsoft** — `view.officeapps.live.com` (URL HTTPS publik).
3. **GDocs** — `docs.google.com/gview` (URL HTTPS publik).

Tidak ada rantai timeout Microsoft → Google → lokal. Jika iframe kosong/error, pilih viewer lain lewat radio.

`iframe.onload` memicu `@provider-status` `unverified` (bukan `@loaded`). `@loaded` hanya untuk renderer lokal.

**Sumber privat:** File/Blob, localhost, header Authorization, atau `request-credentials="include"` → Microsoft/GDocs dinonaktifkan; pakai Local.

**Privasi:** dua layanan eksternal perlu mengakses URL dan isi dokumen. Hindari keduanya untuk dokumen internal/rahasia. Endpoint harus menerima direct file, bukan halaman login atau tautan halaman Drive. URL bertanda token/signed juga akan dikirim ke penyedia.

### PDF locked dan keamanan

`locked=true` atau `allow-download=false` hanya menghapus akses unduh lewat UI komponen (dan tidak bisa menonaktifkan kontrol bawaan iframe layanan eksternal). Tidak mungkin membuat berkas **100% anti-download** di browser, karena byte PDF dikirim ke klien untuk dirender. Untuk arsip sensitif, gunakan autentikasi & otorisasi backend setiap request, URL singkat, audit akses, watermark user, dan pembatasan cache.

### Konfigurasi CDN / on-premise

Atur URL library **sebelum** instance komponen dimount:

```js
window.DocumentViewer.configure({
  pdf: '/assets/pdfjs/pdf.min.js',
  pdfWorker: '/assets/pdfjs/pdf.worker.min.js',
  jszip: '/assets/jszip.min.js',
  docx: '/assets/docx-preview.min.js',
  xlsx: '/assets/xlsx.full.min.js'
});
```

Default yang tercantum dalam source menggunakan PDF.js 3.11.174, JSZip 3.10.1, docx-preview 0.4.1, dan SheetJS 0.20.3 dari CDN. Aplikasi dengan CSP ketat/offline perlu melakukan hosting sendiri atas file dependensi sesuai kebijakan lisensi vendor.

## Pengembangan source

Jika perlu ubah fitur satu kali untuk kedua versi:

```bash
# edit src/viewer.common.js
python3 tools/build.py
node --test tests/smoke.test.cjs
```

Dua file `dist/` akan dibangun ulang dari source tunggal. `tools/build.py` hanya memakai Python standard library. Tidak ada paket npm yang perlu diinstal untuk menjalankan tes JavaScript dasar.

### Batas pengujian

Kedua JS lulus pemeriksaan sintaks dan smoke test komponen menggunakan runtime stub. Pengujian end-to-end dengan Vue asli dan CDN belum dilakukan dalam lingkungan pembuatan karena akses jaringan/CDN tidak tersedia. Gunakan `examples/vue2.html` dan `examples/vue3.html` untuk QA di browser dengan jaringan yang mengizinkan dependensi.

## Lisensi

MIT untuk kode komponen dalam paket ini. Library PDF.js, JSZip, docx-preview, dan SheetJS adalah dependensi eksternal dengan ketentuan lisensi masing-masing; periksa sebelum distribusi komersial.

### Memantau status Office dari Vue

```html
<document-viewer
  :src="fileUrl" filename="laporan.docx"
  @provider-status="onProviderStatus"
  @loaded="onLoaded"
></document-viewer>
```

```js
methods: {
  onProviderStatus(info) {
    console.log(info.engine, info.status, info.reason);
    // status: loading | unverified | failed | skipped
    // verified selalu false untuk provider online.
  },
  onLoaded(info) {
    console.log('Renderer lokal selesai:', info.engine);
  }
}
```

Lihat [PANDUAN_PENGGUNAAN.md](PANDUAN_PENGGUNAAN.md) untuk status lengkap dan penanganan fallback.

## Baru v1.3 — online/offline dan rendering DOCX lokal

`online` adalah saklar utama untuk **layanan viewer eksternal** (Microsoft/Google). Default `true`.

```html
<!-- Vue 2 dan Vue 3 memiliki nama prop yang sama -->
<document-viewer
  :src="fileUrl"
  filename="surat.docx"
  :online="false"
  :docx-fit-width="true"
  :docx-options="{ renderHeaders: true, renderFooters: true }"
  height="750px"
></document-viewer>
```

`online=false` memilih fallback lokal dan tidak mengirim URL ke Microsoft/Google. **Ini tidak sama dengan mode tanpa koneksi Internet**: library DOCX masih akan diambil dari CDN saat pertama kali digunakan, kecuali Anda mengarahkan JSZip dan docx-preview ke file lokal lewat `DocumentViewer.configure(...)`.

DOCX lokal kini menjaga gaya bawaan dokumen (margin/ukuran kertas, header/footer, tabel, gambar, font), menyediakan zoom/pas-lebar, deteksi dan navigasi section halaman, serta style prefix unik pada tiap instance. Sejak v1.3.4+ logo floating di header (kop surat) ikut diperbaiki agar tidak hilang / tidak menutupi shadow lembar. Jumlah halaman tidak selalu 100% sama seperti Word karena keterbatasan DOCX-to-HTML. Contoh: `examples/contoh-docx-kop-surat.docx` (demo Vue 2 → **Kop surat**) dan `examples/contoh-docx-lokal.docx`.

Lihat rincian API dan batasannya di **[PANDUAN_PENGGUNAAN.md](PANDUAN_PENGGUNAAN.md)**.
