laporan-generator 2.7.0 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/.agents/skills/laporan-generator/SKILL.md +30 -15
  2. package/.agents/skills/laporan-generator/references/campus-guide.md +8 -2
  3. package/.agents/skills/laporan-generator/references/metadata-schema.md +39 -11
  4. package/.agents/skills/laporan-generator/references/template-guide.md +20 -14
  5. package/.agents/skills/laporan-generator/resources/cover.md +18 -14
  6. package/.agents/skills/laporan-generator/resources/docx.lua +208 -5
  7. package/.agents/skills/laporan-generator/resources/laporan +2 -2
  8. package/.agents/skills/laporan-generator/resources/laporan.ps1 +55 -25
  9. package/.agents/skills/laporan-generator/resources/metadata.yml +13 -16
  10. package/.agents/skills/laporan-generator/resources/presets/ipb-ppki.yml +27 -0
  11. package/.agents/skills/laporan-generator/resources/presets/its-skripsi.yml +6 -6
  12. package/.agents/skills/laporan-generator/resources/presets/telkom-ta.yml +26 -0
  13. package/.agents/skills/laporan-generator/resources/presets/ub-skripsi.yml +26 -0
  14. package/.agents/skills/laporan-generator/resources/presets/ui-skripsi.yml +6 -6
  15. package/.agents/skills/laporan-generator/resources/presets/unair-skripsi.yml +26 -0
  16. package/.agents/skills/laporan-generator/resources/presets/undip-skripsi.yml +26 -0
  17. package/.agents/skills/laporan-generator/resources/scripts/bundle.py +7 -1
  18. package/.agents/skills/laporan-generator/resources/scripts/docx-pagenum.py +6 -2
  19. package/.agents/skills/laporan-generator/resources/scripts/finalize-docx.py +13 -0
  20. package/.agents/skills/laporan-generator/resources/scripts/setup-deps.sh +9 -3
  21. package/.agents/skills/laporan-generator/resources/scripts/test_scripts.py +130 -0
  22. package/.agents/skills/laporan-generator/resources/template.typ +3 -0
  23. package/.agents/skills/laporan-generator/scripts/build-docx.sh +122 -0
  24. package/.agents/skills/laporan-generator/scripts/build.sh +32 -8
  25. package/.agents/skills/laporan-generator/scripts/bundle.py +7 -1
  26. package/.agents/skills/laporan-generator/scripts/docx-pagenum.py +6 -2
  27. package/.agents/skills/laporan-generator/scripts/finalize-docx.py +13 -0
  28. package/.agents/skills/laporan-generator/scripts/laporan +55 -5
  29. package/.agents/skills/laporan-generator/scripts/laporan.ps1 +628 -529
  30. package/.agents/skills/laporan-generator/scripts/setup-deps.sh +9 -3
  31. package/.agents/skills/laporan-generator/scripts/test_scripts.py +130 -0
  32. package/README.md +29 -19
  33. package/bin/laporan-generator.js +34 -6
  34. package/cover.md +18 -14
  35. package/docs/campus-guide.md +8 -2
  36. package/docs/metadata-schema.md +39 -11
  37. package/docs/template-guide.md +20 -14
  38. package/docx.lua +208 -5
  39. package/laporan +2 -2
  40. package/laporan.ps1 +55 -25
  41. package/lib/init.js +28 -3
  42. package/lib/mcp.js +21 -9
  43. package/lib/sync-hosts.js +26 -2
  44. package/metadata.yml +13 -16
  45. package/package.json +3 -2
  46. package/presets/ipb-ppki.yml +27 -0
  47. package/presets/its-skripsi.yml +6 -6
  48. package/presets/telkom-ta.yml +26 -0
  49. package/presets/ub-skripsi.yml +26 -0
  50. package/presets/ui-skripsi.yml +6 -6
  51. package/presets/unair-skripsi.yml +26 -0
  52. package/presets/undip-skripsi.yml +26 -0
  53. package/scripts/bundle.py +7 -1
  54. package/scripts/docx-pagenum.py +6 -2
  55. package/scripts/finalize-docx.py +13 -0
  56. package/scripts/setup-deps.sh +9 -3
  57. package/scripts/test_scripts.py +130 -0
  58. package/template.typ +3 -0
@@ -31,12 +31,12 @@ When available in your environment, **always prefer calling the native MCP tools
31
31
 
32
32
  | Action | Native MCP Tool | CLI Fallback |
33
33
  | :--- | :--- | :--- |
34
- | **Initialize Project** | `laporan_init({ preset, title, author_name, institution })` | `npx laporan-generator init` |
34
+ | **Initialize Project** | `laporan_init({ preset, title, author_name, author_id, institution, daftar_gambar, daftar_tabel })` | `npx laporan-generator init` |
35
35
  | **Audit & Health Check** | `laporan_doctor({ target_dir })` | `./laporan doctor` or `npx laporan-generator doctor --json` |
36
36
  | **Document Stats & Metrics** | `laporan_stats({ target_dir })` | `./laporan stats` or `npx laporan-generator stats --json` |
37
37
  | **Inspect Campus Presets** | `laporan_presets({ action: "list" \| "get", preset_name })` | `./laporan preset list` |
38
- | **Search & Add Citations** | `laporan_citations({ action: "search_doi" \| "add_citation", query, doi })` | Manual edit `references.bib` |
39
- | **Compile PDF & DOCX** | `laporan_build({ format: "all" \| "pdf" \| "docx" })` | `./laporan build` or `bash build.sh && make docx` |
38
+ | **Search & Add Citations** | `laporan_citations({ action: "search_doi" \| "get_bibtex" \| "add_citation" \| "validate", query, doi, bibtex })` | Manual edit `references.bib` |
39
+ | **Compile PDF & DOCX** | `laporan_build({ target_dir, format: "all" \| "pdf" \| "docx" })` | `./laporan build` or `npx laporan-generator build` |
40
40
 
41
41
  --------------------------------------------------------------------------------
42
42
 
@@ -51,22 +51,37 @@ Identify the document type and target university:
51
51
  3. **Makalah Kuliah / Paper Ilmiah**: Focus on research problem, methodology, findings, and discussion. (In Indonesia, course papers use standard Roman Bab headings: `BAB I`, `BAB II`, etc.).
52
52
  4. **Target University Preset**:
53
53
  - `skripsi-4433`: National Standard (Top 4cm, Left 4cm, Bottom 3cm, Right 3cm).
54
- - `ui-skripsi`: Universitas Indonesia (Buku Panduan UI).
55
- - `itb-ta`: Institut Teknologi Bandung (Pedoman TA ITB).
56
- - `ugm-skripsi`: Universitas Gadjah Mada (Format Skripsi UGM).
57
- - `its-skripsi`: Institut Teknologi Sepuluh Nopember.
58
- - `unpad-skripsi`: Universitas Padjadjaran.
59
- - `standard`: General University (2.5cm / 2cm).
60
-
61
- ### Stage 2: Scaffolding & Metadata Configuration
62
- 1. Initialize the workspace:
63
- - Call MCP tool `laporan_init` or run `npx laporan-generator init`.
64
- 2. Configure `metadata.yml`:
54
+ - `ui-skripsi`: Universitas Indonesia (Left 4cm, Top/Right/Bottom 3cm).
55
+ - `itb-ta`: Institut Teknologi Bandung (Pedoman TA ITB 4-4-3-3, Minimalist Cover).
56
+ - `ugm-skripsi`: Universitas Gadjah Mada (Format Skripsi UGM 4-4-3-3).
57
+ - `its-skripsi`: Institut Teknologi Sepuluh Nopember (Top 3cm, Left 3cm, Bottom 2.5cm, Right 2cm).
58
+ - `unpad-skripsi`: Universitas Padjadjaran (4-4-3-3).
59
+ - `ub-skripsi`: Universitas Brawijaya (4-4-3-3).
60
+ - `undip-skripsi`: Universitas Diponegoro (4-4-3-3).
61
+ - `unair-skripsi`: Universitas Airlangga (4-4-3-3).
62
+ - `ipb-ppki`: IPB University - Pedoman PPKI (Left 4cm, Top/Right/Bottom 3cm, 1cm indent).
63
+ - `telkom-ta`: Telkom University (Left 4cm, Top/Right/Bottom 3cm).
64
+ - `standard`: General University (Top 2cm, Bottom 3cm, Left 2.5cm, Right 2.5cm).
65
+
66
+ ### Stage 2: Document Structure Confirmation & Scaffolding
67
+ 1. **Confirm Document Structure**:
68
+ - The universal default structure is minimal: **Cover -> Kata Pengantar -> Daftar Isi -> Batang Tubuh (BAB I..n) -> Daftar Pustaka**.
69
+ - **MANDATORY INQUIRY**: Ask the user before scaffolding if they want to activate optional components:
70
+ - Abstrak / Abstract (`abstract_id` / `abstract_en`)
71
+ - Lembar Pengesahan (`approval`)
72
+ - Daftar Gambar (`daftar_gambar: true`)
73
+ - Daftar Tabel (`daftar_tabel: true`)
74
+ - Lampiran
75
+ 2. **Initialize the workspace**:
76
+ - Call MCP tool `laporan_init` (e.g. `laporan_init({ preset, title, daftar_gambar, daftar_tabel })`) or run `npx laporan-generator init`.
77
+ 3. **Configure `metadata.yml`**:
65
78
  - `title`: Clear, concise, capitalized (rendered as inverted pyramid).
66
79
  - `author`: Author array with `name` and `nim` (Student ID).
67
80
  - `preset`: Selected university preset (e.g. `skripsi-4433`, `itb-ta`).
81
+ - `daftar_gambar: false`, `daftar_tabel: false` (set to `true` only if opted-in).
82
+ - `abstract_id`: Kept empty or commented out unless user requested Abstrak.
68
83
  - `institution`, `faculty`, `department`, `year`.
69
- 3. Configure `cover.md`: Set formal title, institutional affiliations, and kata pengantar.
84
+ 4. **Configure `cover.md`**: Set formal title, institutional affiliations, and kata pengantar.
70
85
 
71
86
  ### Stage 3: Rigorous Academic Writing Standard
72
87
  When authoring content into `chapters/bab*.md`, strictly enforce **Indonesian Standard Academic Tone**:
@@ -29,10 +29,16 @@ Atau gunakan bantuan CLI interaktif:
29
29
  |---|---|---|---|
30
30
  | `standard` | Standar Modern | 2.0 cm - 3.0 cm - 2.5 cm - 2.5 cm | Format default tugas akhir, magang, praktikum |
31
31
  | `skripsi-4433` | Skripsi Tradisional | 4.0 cm - 3.0 cm - 4.0 cm - 3.0 cm | Hardcover jilid tebal margin kiri 4cm |
32
- | `ui-skripsi` | Universitas Indonesia (UI) | 2.5 cm - 2.5 cm - 2.5 cm - 2.5 cm | Sesuai Pedoman Karya Ilmiah UI |
32
+ | `ui-skripsi` | Universitas Indonesia (UI) | 3.0 cm - 3.0 cm - 4.0 cm - 3.0 cm | Sesuai Pedoman Teknis Tugas Akhir UI |
33
33
  | `itb-ta` | Institut Teknologi Bandung (ITB) | 4.0 cm - 3.0 cm - 4.0 cm - 3.0 cm | Cover minimalis (tanpa dosen pembimbing di cover luar) |
34
34
  | `ugm-skripsi` | Universitas Gadjah Mada (UGM) | 4.0 cm - 3.0 cm - 4.0 cm - 3.0 cm | Full academic cover |
35
- | `its-skripsi` | Institut Teknologi Sepuluh Nopember (ITS) | 4.0 cm - 3.0 cm - 4.0 cm - 3.0 cm | Standard Tugas Akhir & Tesis ITS |
35
+ | `its-skripsi` | Institut Teknologi Sepuluh Nopember (ITS) | 3.0 cm - 2.5 cm - 3.0 cm - 2.0 cm | Sesuai Buku Pedoman TA & Tesis ITS |
36
+ | `unpad-skripsi` | Universitas Padjadjaran (UNPAD) | 4.0 cm - 3.0 cm - 4.0 cm - 3.0 cm | Format skripsi & karya ilmiah UNPAD |
37
+ | `ub-skripsi` | Universitas Brawijaya (UB) | 4.0 cm - 3.0 cm - 4.0 cm - 3.0 cm | Pedoman penulisan skripsi UB |
38
+ | `undip-skripsi` | Universitas Diponegoro (UNDIP) | 4.0 cm - 3.0 cm - 4.0 cm - 3.0 cm | Pedoman akademik skripsi UNDIP |
39
+ | `unair-skripsi` | Universitas Airlangga (UNAIR) | 4.0 cm - 3.0 cm - 4.0 cm - 3.0 cm | Pedoman penulisan karya ilmiah UNAIR |
40
+ | `ipb-ppki` | IPB University (PPKI) | 3.0 cm - 3.0 cm - 4.0 cm - 3.0 cm | Pedoman PPKI IPB (Indent 1cm, sub-bab tanpa titik akhir) |
41
+ | `telkom-ta` | Telkom University (Tel-U) | 3.0 cm - 3.0 cm - 4.0 cm - 3.0 cm | Pedoman Buku Tugas Akhir Telkom University |
36
42
 
37
43
  ---
38
44
 
@@ -20,14 +20,33 @@ lecturer_label: "Dosen Pengampu:" # Opsional: "Dosen Pembimbing:", "Pembimbing
20
20
  course: "Pengolahan Citra Digital dan Pembelajaran Mesin"
21
21
  institution: "Universitas Gadjah Mada"
22
22
  faculty: "Departemen Teknik Elektro dan Teknologi Informasi"
23
+ department: "Teknologi Informasi"
24
+ city: "Yogyakarta"
23
25
  year: "2025/2026"
24
26
  date: "Juli 2026"
25
- abstract: |
26
- Laporan ini membahas pipeline otomatisasi dokumen akademik
27
- menggunakan Markdown, Pandoc, Typst, dan Bash. Pipeline ini
28
- memungkinkan penulisan konten laporan dalam format Markdown
29
- yang kemudian dikonversi menjadi PDF berkualitas percetakan
30
- melalui Pandoc dan Typst serta berkas Microsoft Word (.docx).
27
+ preset: "ugm-skripsi"
28
+ daftar_gambar: false
29
+ daftar_tabel: false
30
+ # Komponen opsional (aktifkan sesuai kebutuhan):
31
+ # approval:
32
+ # city: "Yogyakarta"
33
+ # date: "20 Juli 2026"
34
+ # supervisors:
35
+ # - name: "Dr. Ir. Hendra Wijaya, M.T."
36
+ # nip: "197501012000031001"
37
+ # role: "Pembimbing Utama"
38
+ # dean:
39
+ # name: "Prof. Dr. Eng. Dekan Fakultas, M.T."
40
+ # nip: "196803031992031003"
41
+ # role: "Dekan Fakultas Teknik"
42
+ # abstract_id: |
43
+ # Laporan ini membahas pipeline otomatisasi dokumen akademik
44
+ # menggunakan Markdown, Pandoc, Typst, dan Bash.
45
+ # keywords_id: ["Pandoc", "Typst", "OpenXML", "Otomatisasi Dokumen"]
46
+ # abstract_en: |
47
+ # This report discusses an automated academic document pipeline
48
+ # built with Markdown, Pandoc, Typst, and Bash.
49
+ # keywords_en: ["Pandoc", "Typst", "OpenXML", "Document Automation"]
31
50
  ...
32
51
  ```
33
52
 
@@ -37,19 +56,28 @@ abstract: |
37
56
 
38
57
  | Field | Tipe Data | Wajib? | Keterangan |
39
58
  |---|---|---|---|
40
- | `title` | String | **Ya** | Judul utama laporan (tampil tebal di halaman sampul). |
59
+ | `title` | String | **Ya** | Judul utama laporan (tampil tebal piramida terbalik di halaman sampul). |
41
60
  | `subtitle` | String | Opsional | Sub-judul atau keterangan tambahan laporan. |
42
61
  | `author` | List of Objects | **Ya** | Daftar penulis. Setiap entri memiliki `name` dan `nim`. |
43
62
  | `author[].name` | String | **Ya** | Nama lengkap mahasiswa / penyusun. |
44
63
  | `author[].nim` | String | **Ya** | Nomor Induk Mahasiswa (NIM / NPM / NISN). |
45
64
  | `lecturer` | String | **Ya** | Nama lengkap dan gelar Dosen / Guru Pengampu / Pembimbing. |
46
65
  | `lecturer_label` | String | Opsional | Label peran dosen (default: `"Dosen Pengampu:"`, bisa `"Dosen Pembimbing:"`). |
47
- | `course` | String | **Ya** | Nama Mata Kuliah atau Mata Pelajaran. |
66
+ | `course` | String | **Ya** | Nama Mata Kuliah atau Jenis Karya Tulis. |
48
67
  | `institution` | String | **Ya** | Nama Universitas, Institut, Politeknik, atau Sekolah. |
49
- | `faculty` | String | **Ya** | Nama Fakultas, Departemen, atau Program Studi. |
50
- | `year` | String | **Ya** | Tahun Ajaran (contoh: `2025/2026`). |
68
+ | `faculty` | String | **Ya** | Nama Fakultas, Sekolah, atau Departemen. |
69
+ | `department` | String | Opsional | Nama Program Studi atau Jurusan. |
70
+ | `city` | String | Opsional | Kota tempat institusi berada (digunakan pada lembar pengesahan). |
71
+ | `year` | String | **Ya** | Tahun Ajaran atau Tahun Kelulusan (contoh: `2026` atau `2025/2026`). |
51
72
  | `date` | String | Opsional | Bulan dan Tahun pembuatan (contoh: `Juli 2026`). |
52
- | `abstract` | Multiline String | Opsional | Ringkasan/Abstrak laporan dalam Bahasa Indonesia. |
73
+ | `preset` | String | Opsional | ID preset format kampus (`standard`, `skripsi-4433`, `ui-skripsi`, `itb-ta`, `ugm-skripsi`, `its-skripsi`, `unpad-skripsi`, `ub-skripsi`, `undip-skripsi`, `unair-skripsi`, `ipb-ppki`, `telkom-ta`). |
74
+ | `daftar_gambar` | Boolean | Opsional | Aktifkan halaman DAFTAR GAMBAR (`true`/`false`, default: `false`). |
75
+ | `daftar_tabel` | Boolean | Opsional | Aktifkan halaman DAFTAR TABEL (`true`/`false`, default: `false`). |
76
+ | `approval` | Object | Opsional | Konfigurasi Lembar Pengesahan (`city`, `date`, `supervisors[]`, `dean`). |
77
+ | `abstract_id` | Multiline String | Opsional | Ringkasan/Abstrak laporan dalam Bahasa Indonesia (Opt-In). |
78
+ | `keywords_id` | List of Strings | Opsional | Daftar kata kunci Bahasa Indonesia. |
79
+ | `abstract_en` | Multiline String | Opsional | Ringkasan/Abstract laporan dalam Bahasa Inggris (Opt-In). |
80
+ | `keywords_en` | List of Strings | Opsional | Daftar kata kunci Bahasa Inggris (Keywords). |
53
81
 
54
82
  ---
55
83
 
@@ -17,16 +17,15 @@ File `template.typ` merupakan jantung dari pemformatan visual PDF pada **Laporan
17
17
  ```typst
18
18
  #set page(
19
19
  paper: "a4",
20
- margin: (top: 2cm, bottom: 3cm, left: 2.5cm, right: 2.5cm),
20
+ margin: doc-margin,
21
+ numbering: none,
21
22
  )
22
23
  #set text(font: "Libertinus Serif", size: 12pt)
23
- #set par(justify: true, leading: 1.5em)
24
+ #set par(justify: true, leading: 0.75em, first-line-indent: 1.25cm)
24
25
  ```
25
- * Margin Kiri & Kanan: 2.5 cm
26
- * Margin Atas: 2.0 cm
27
- * Margin Bawah: 3.0 cm
28
- * Spasi Antar Baris: 1.5 spasi (*one half spacing*)
29
- * Paragraf rata kanan-kiri (*justify*)
26
+ * Margin Default (`standard`): Atas 2.0 cm, Bawah 3.0 cm, Kiri 2.5 cm, Kanan 2.5 cm (atau mengikuti `presets/*.yml` aktif).
27
+ * Spasi Antar Baris: `0.75em` (setara 1.5 spasi di Typst).
28
+ * Paragraf rata kanan-kiri (*justify*) dengan indentasi baris pertama `1.25cm`.
30
29
 
31
30
  ---
32
31
 
@@ -36,28 +35,35 @@ File `template.typ` merupakan jantung dari pemformatan visual PDF pada **Laporan
36
35
  ```typst
37
36
  #set heading(numbering: (..ns) => {
38
37
  if ns.len() == 1 {
39
- "BAB " + numbering("I", ns.at(0)) + linebreak()
38
+ if chapter-prefix != "" {
39
+ chapter-prefix + format-chapter-num(ns.at(0))
40
+ } else {
41
+ format-chapter-num(ns.at(0))
42
+ }
40
43
  } else if ns.len() == 2 {
41
- numbering("1.1", ..ns)
44
+ numbering("1.1.", ..ns)
42
45
  } else {
43
46
  numbering("1.1.1", ..ns)
44
47
  }
45
48
  })
46
49
  ```
47
- * **Bab**: Diformat dengan Angka Romawi (`BAB I`, `BAB II`, `BAB III`, dst). Angka Romawi dan judul dipisahkan oleh *line break*.
48
- * **Sub-bab**: Diformat dengan Angka Arab (`1.1`, `1.2`, `2.1`).
50
+ * **Bab**: Diformat dengan Angka Romawi (`BAB I`, `BAB II`, `BAB III`, dst) atau Angka Arab sesuai preset (`heading_chapter_num_format`). Nomor bab dan judul dipisahkan oleh *line break* pada `#show heading.where(level: 1)`.
51
+ * **Sub-bab**: Diformat dengan Angka Arab (`1.1.`, `1.2.`, `2.1.`) sesuai flag `heading_sub_dot`.
49
52
  * **Sub-sub-bab**: Diformat dengan Angka Arab 3 tingkat (`1.1.1`, `1.1.2`).
50
53
  * **Heading tanpa nomor** (Kata Pengantar, Daftar Isi, Daftar Pustaka): menggunakan `{-}` di Markdown yang diterjemahkan Pandoc menjadi `#heading(level: 1, numbering: none)`.
51
54
 
52
55
  > **Catatan**: Jangan menulis pola `"BAB I"` langsung sebagai format string `numbering(...)`. Typst menginterpretasikan rangkaian huruf `BAB` sebagai pola huruf dan memotong angka Romawinya. Selalu concat literal secara manual: `"BAB " + numbering("I", n)`.
53
56
 
54
57
  ### 2. Penomoran Halaman (frontmatter vs mainmatter)
55
- * **Bagian Depan** (Kata Pengantar, Daftar Isi): Menggunakan penomoran Romawi kapital (`I`, `II`, `III`) di bagian tengah bawah. Diatur di `cover.md`:
58
+ * **Bagian Depan** (Lembar Pengesahan, Abstrak, Kata Pengantar, Daftar Isi): Menggunakan penomoran Romawi kecil (`i`, `ii`, `iii`) di bagian tengah bawah. Diatur di `template.typ` tepat setelah sampul depan:
56
59
  ```typst
57
- #set page(numbering: "I")
60
+ #pagebreak()
61
+ #set page(numbering: "i")
62
+ #counter(page).update(1)
58
63
  ```
59
- * **Bagian Utama** (BAB I hingga BAB V): Menggunakan penomoran Angka Arab (`1`, `2`, `3`) berurutan, di-reset ulang:
64
+ * **Bagian Utama** (BAB I hingga BAB V & Daftar Pustaka): Menggunakan penomoran Angka Arab (`1`, `2`, `3`) berurutan, di-reset ulang di akhir `cover.md`:
60
65
  ```typst
66
+ #pagebreak()
61
67
  #set page(numbering: "1")
62
68
  #counter(page).update(1)
63
69
  ```
@@ -22,21 +22,25 @@ Penulis menyadari bahwa laporan ini masih jauh dari sempurna. Oleh karena itu, k
22
22
  depth: 3,
23
23
  )
24
24
  #context {
25
- let imgs = query(figure.where(kind: image))
26
- if imgs.len() > 0 {
27
- pagebreak()
28
- outline(
29
- title: align(center)[#text(size: 14pt, weight: "bold")[DAFTAR GAMBAR]],
30
- target: figure.where(kind: image),
31
- )
25
+ if opt-daftar-gambar {
26
+ let imgs = query(figure.where(kind: image))
27
+ if imgs.len() > 0 {
28
+ pagebreak()
29
+ outline(
30
+ title: align(center)[#text(size: 14pt, weight: "bold")[DAFTAR GAMBAR]],
31
+ target: figure.where(kind: image),
32
+ )
33
+ }
32
34
  }
33
- let tbls = query(figure.where(kind: table))
34
- if tbls.len() > 0 {
35
- pagebreak()
36
- outline(
37
- title: align(center)[#text(size: 14pt, weight: "bold")[DAFTAR TABEL]],
38
- target: figure.where(kind: table),
39
- )
35
+ if opt-daftar-tabel {
36
+ let tbls = query(figure.where(kind: table))
37
+ if tbls.len() > 0 {
38
+ pagebreak()
39
+ outline(
40
+ title: align(center)[#text(size: 14pt, weight: "bold")[DAFTAR TABEL]],
41
+ target: figure.where(kind: table),
42
+ )
43
+ }
40
44
  }
41
45
  }
42
46
  #pagebreak()
@@ -14,6 +14,28 @@ local subsub = 0
14
14
  local toc_entries = {}
15
15
  local chapter_prefix = "BAB "
16
16
  local chapter_num_fmt = "roman"
17
+ local sub_dot = true
18
+ local subsub_dot = false
19
+ local doc_margins = { top = 1134, right = 1417, bottom = 1701, left = 1417 }
20
+
21
+ local function dim_to_twips(val, fallback)
22
+ if not val or val == "" then return fallback end
23
+ local num, unit = val:match("^%s*([0-9]+%.?[0-9]*)%s*([a-zA-Z]+)%s*$")
24
+ if not num then return fallback end
25
+ local n = tonumber(num)
26
+ if not n then return fallback end
27
+ unit = unit:lower()
28
+ if unit == "cm" then
29
+ return math.floor(n * 566.929133858 + 0.5)
30
+ elseif unit == "mm" then
31
+ return math.floor(n * 56.6929133858 + 0.5)
32
+ elseif unit == "in" then
33
+ return math.floor(n * 1440 + 0.5)
34
+ elseif unit == "pt" then
35
+ return math.floor(n * 20 + 0.5)
36
+ end
37
+ return fallback
38
+ end
17
39
 
18
40
  local function is_unnumbered(el)
19
41
  for _, cls in ipairs(el.attr.classes or {}) do
@@ -138,9 +160,24 @@ local function meta_str(meta, key)
138
160
  if v == nil then
139
161
  return ""
140
162
  end
163
+ if type(v) == "boolean" then
164
+ return tostring(v)
165
+ end
141
166
  return pandoc.utils.stringify(v)
142
167
  end
143
168
 
169
+ local function meta_bool(meta, key)
170
+ local v = meta[key]
171
+ if v == nil then
172
+ return false
173
+ end
174
+ if type(v) == "boolean" then
175
+ return v
176
+ end
177
+ local s = meta_str(meta, key):lower():gsub("%s+", "")
178
+ return s == "true" or s == "1" or s == "yes"
179
+ end
180
+
144
181
  local function format_chapter_number(n)
145
182
  if chapter_num_fmt == "arabic" then
146
183
  return tostring(n)
@@ -165,6 +202,20 @@ local function Meta(meta)
165
202
  if fmt ~= "" then
166
203
  chapter_num_fmt = fmt
167
204
  end
205
+ if meta["heading_sub_dot"] ~= nil then
206
+ sub_dot = meta_bool(meta, "heading_sub_dot")
207
+ end
208
+ if meta["heading_subsub_dot"] ~= nil then
209
+ subsub_dot = meta_bool(meta, "heading_subsub_dot")
210
+ end
211
+ local m_preset = meta_str(meta, "margin_preset")
212
+ if m_preset == "skripsi-4433" or m_preset == "4-4-3-3" then
213
+ doc_margins = { top = 2268, right = 1701, bottom = 1701, left = 2268 }
214
+ end
215
+ doc_margins.top = dim_to_twips(meta_str(meta, "margin_top"), doc_margins.top)
216
+ doc_margins.bottom = dim_to_twips(meta_str(meta, "margin_bottom"), doc_margins.bottom)
217
+ doc_margins.left = dim_to_twips(meta_str(meta, "margin_left"), doc_margins.left)
218
+ doc_margins.right = dim_to_twips(meta_str(meta, "margin_right"), doc_margins.right)
168
219
  end
169
220
 
170
221
  function Header(el)
@@ -213,20 +264,22 @@ function Header(el)
213
264
  elseif el.level == 2 then
214
265
  sub = sub + 1
215
266
  subsub = 0
267
+ local sep2 = sub_dot and ". " or " "
216
268
  toc_entries[#toc_entries + 1] = {
217
269
  level = 2,
218
270
  id = el.identifier,
219
- text = bab .. "." .. sub .. ". " .. title,
271
+ text = bab .. "." .. sub .. sep2 .. title,
220
272
  }
221
- el.content = pandoc.Str(bab .. "." .. sub .. ". " .. title)
273
+ el.content = pandoc.Str(bab .. "." .. sub .. sep2 .. title)
222
274
  elseif el.level == 3 then
223
275
  subsub = subsub + 1
276
+ local sep3 = subsub_dot and ". " or " "
224
277
  toc_entries[#toc_entries + 1] = {
225
278
  level = 3,
226
279
  id = el.identifier,
227
- text = bab .. "." .. sub .. "." .. subsub .. " " .. title,
280
+ text = bab .. "." .. sub .. "." .. subsub .. sep3 .. title,
228
281
  }
229
- el.content = pandoc.Str(bab .. "." .. sub .. "." .. subsub .. " " .. title)
282
+ el.content = pandoc.Str(bab .. "." .. sub .. "." .. subsub .. sep3 .. title)
230
283
  end
231
284
  return el
232
285
  end
@@ -280,6 +333,44 @@ local function daftar_isi()
280
333
  return out
281
334
  end
282
335
 
336
+ local function daftar_gambar()
337
+ local out = pandoc.List{}
338
+ out[#out + 1] = pagebreak()
339
+ out[#out + 1] = pandoc.RawBlock(
340
+ "openxml",
341
+ '<w:p><w:pPr><w:pStyle w:val="TOCHeading"/><w:jc w:val="center"/><w:outlineLvl w:val="9"/></w:pPr>' ..
342
+ '<w:r><w:rPr><w:b/><w:sz w:val="28"/><w:szCs w:val="28"/></w:rPr><w:t>DAFTAR GAMBAR</w:t></w:r></w:p>'
343
+ )
344
+ out[#out + 1] = pandoc.RawBlock(
345
+ "openxml",
346
+ '<w:p><w:pPr><w:pStyle w:val="TOC1"/></w:pPr>' ..
347
+ '<w:r><w:fldChar w:fldCharType="begin" w:dirty="true"/></w:r>' ..
348
+ '<w:r><w:instrText xml:space="preserve"> TOC \\c "Gambar" \\h \\z </w:instrText></w:r>' ..
349
+ '<w:r><w:fldChar w:fldCharType="separate"/></w:r>' ..
350
+ '<w:r><w:fldChar w:fldCharType="end"/></w:r></w:p>'
351
+ )
352
+ return out
353
+ end
354
+
355
+ local function daftar_tabel()
356
+ local out = pandoc.List{}
357
+ out[#out + 1] = pagebreak()
358
+ out[#out + 1] = pandoc.RawBlock(
359
+ "openxml",
360
+ '<w:p><w:pPr><w:pStyle w:val="TOCHeading"/><w:jc w:val="center"/><w:outlineLvl w:val="9"/></w:pPr>' ..
361
+ '<w:r><w:rPr><w:b/><w:sz w:val="28"/><w:szCs w:val="28"/></w:rPr><w:t>DAFTAR TABEL</w:t></w:r></w:p>'
362
+ )
363
+ out[#out + 1] = pandoc.RawBlock(
364
+ "openxml",
365
+ '<w:p><w:pPr><w:pStyle w:val="TOC1"/></w:pPr>' ..
366
+ '<w:r><w:fldChar w:fldCharType="begin" w:dirty="true"/></w:r>' ..
367
+ '<w:r><w:instrText xml:space="preserve"> TOC \\c "Tabel" \\h \\z </w:instrText></w:r>' ..
368
+ '<w:r><w:fldChar w:fldCharType="separate"/></w:r>' ..
369
+ '<w:r><w:fldChar w:fldCharType="end"/></w:r></w:p>'
370
+ )
371
+ return out
372
+ end
373
+
283
374
  function Pandoc(doc)
284
375
  if FORMAT ~= "docx" then
285
376
  return doc
@@ -292,6 +383,8 @@ function Pandoc(doc)
292
383
  local faculty = meta_str(meta, "faculty")
293
384
  local institution = meta_str(meta, "institution")
294
385
  local year = meta_str(meta, "year")
386
+ local show_dg = (meta_bool(meta, "daftar_gambar") or meta_bool(meta, "list_of_figures"))
387
+ local show_dt = (meta_bool(meta, "daftar_tabel") or meta_bool(meta, "list_of_tables"))
295
388
 
296
389
  local blocks = pandoc.List{}
297
390
 
@@ -379,9 +472,109 @@ function Pandoc(doc)
379
472
  -- Section break (Next Page) penutup cover: tanpa footer/nomor halaman
380
473
  blocks[#blocks + 1] = pandoc.RawBlock(
381
474
  "openxml",
382
- '<w:p><w:pPr><w:sectPr><w:footnotePr><w:numRestart w:val="eachSect"/></w:footnotePr><w:pgSz w:w="11906" w:h="16838"/><w:pgMar w:top="1134" w:right="1417" w:bottom="1701" w:left="1417" w:header="720" w:footer="720" w:gutter="0"/></w:sectPr></w:pPr></w:p>'
475
+ string.format(
476
+ '<w:p><w:pPr><w:sectPr><w:footnotePr><w:numRestart w:val="eachSect"/></w:footnotePr><w:pgSz w:w="11906" w:h="16838"/><w:pgMar w:top="%d" w:right="%d" w:bottom="%d" w:left="%d" w:header="720" w:footer="720" w:gutter="0"/></w:sectPr></w:pPr></w:p>',
477
+ doc_margins.top, doc_margins.right, doc_margins.bottom, doc_margins.left
478
+ )
383
479
  )
384
480
 
481
+ -- Optional Lembar Pengesahan (approval)
482
+ if meta["approval"] ~= nil and type(meta["approval"]) == "table" then
483
+ local app = meta["approval"]
484
+ local app_city = meta_str(app, "city")
485
+ local app_date = meta_str(app, "date")
486
+ if app_date == "" then app_date = meta_str(meta, "date") end
487
+ blocks[#blocks + 1] = pandoc.Header(1, { pandoc.Str("LEMBAR PENGESAHAN") }, pandoc.Attr("lembar-pengesahan", { "unnumbered" }, {}))
488
+ if title ~= "" then
489
+ blocks[#blocks + 1] = para({ pandoc.Strong(pandoc.Str(string.upper(title))) }, "CoverLine")
490
+ end
491
+ local date_line = (app_city ~= "" and (app_city .. ", ") or "") .. app_date
492
+ if date_line ~= "" then
493
+ blocks[#blocks + 1] = para({ pandoc.Str("Telah diperiksa dan disetujui pada: " .. date_line) }, "CoverLine")
494
+ end
495
+ if app["supervisors"] ~= nil and type(app["supervisors"]) == "table" then
496
+ for _, sup in ipairs(app["supervisors"]) do
497
+ local s_role = pandoc.utils.stringify(sup["role"] or "Pembimbing")
498
+ local s_name = pandoc.utils.stringify(sup["name"] or "")
499
+ local s_nip = pandoc.utils.stringify(sup["nip"] or "")
500
+ blocks[#blocks + 1] = spacer(360)
501
+ blocks[#blocks + 1] = para({ pandoc.Str(s_role) }, "CoverLine")
502
+ blocks[#blocks + 1] = spacer(480)
503
+ blocks[#blocks + 1] = para({ pandoc.Strong(pandoc.Str(s_name)) }, "CoverName")
504
+ if s_nip ~= "" then
505
+ blocks[#blocks + 1] = para({ pandoc.Str("NIP. " .. s_nip) }, "CoverLine")
506
+ end
507
+ end
508
+ end
509
+ if app["dean"] ~= nil and type(app["dean"]) == "table" then
510
+ local d_role = pandoc.utils.stringify(app["dean"]["role"] or "Dekan")
511
+ local d_name = pandoc.utils.stringify(app["dean"]["name"] or "")
512
+ local d_nip = pandoc.utils.stringify(app["dean"]["nip"] or "")
513
+ blocks[#blocks + 1] = spacer(360)
514
+ blocks[#blocks + 1] = para({ pandoc.Str("Mengetahui, " .. d_role) }, "CoverLine")
515
+ blocks[#blocks + 1] = spacer(480)
516
+ blocks[#blocks + 1] = para({ pandoc.Strong(pandoc.Str(d_name)) }, "CoverName")
517
+ if d_nip ~= "" then
518
+ blocks[#blocks + 1] = para({ pandoc.Str("NIP. " .. d_nip) }, "CoverLine")
519
+ end
520
+ end
521
+ blocks[#blocks + 1] = pagebreak()
522
+ end
523
+
524
+ -- Optional Abstrak Bahasa Indonesia (abstract_id)
525
+ local abs_id = meta_str(meta, "abstract_id")
526
+ if abs_id ~= "" then
527
+ blocks[#blocks + 1] = pandoc.Header(1, { pandoc.Str("ABSTRAK") }, pandoc.Attr("abstrak", { "unnumbered" }, {}))
528
+ if title ~= "" then
529
+ blocks[#blocks + 1] = para({ pandoc.Strong(pandoc.Str(string.upper(title))) }, "CoverLine")
530
+ end
531
+ blocks[#blocks + 1] = pandoc.Para({ pandoc.Str(abs_id) })
532
+ if meta["keywords_id"] ~= nil then
533
+ local kw_list = {}
534
+ if type(meta["keywords_id"]) == "table" and meta["keywords_id"][1] ~= nil then
535
+ for _, k in ipairs(meta["keywords_id"]) do
536
+ kw_list[#kw_list + 1] = pandoc.utils.stringify(k)
537
+ end
538
+ else
539
+ kw_list[#kw_list + 1] = meta_str(meta, "keywords_id")
540
+ end
541
+ if #kw_list > 0 then
542
+ blocks[#blocks + 1] = pandoc.Para({
543
+ pandoc.Strong(pandoc.Str("Kata Kunci: ")),
544
+ pandoc.Emph(pandoc.Str(table.concat(kw_list, ", ")))
545
+ })
546
+ end
547
+ end
548
+ blocks[#blocks + 1] = pagebreak()
549
+ end
550
+
551
+ -- Optional Abstract English (abstract_en)
552
+ local abs_en = meta_str(meta, "abstract_en")
553
+ if abs_en ~= "" then
554
+ blocks[#blocks + 1] = pandoc.Header(1, { pandoc.Str("ABSTRACT") }, pandoc.Attr("abstract", { "unnumbered" }, {}))
555
+ if title ~= "" then
556
+ blocks[#blocks + 1] = para({ pandoc.Strong(pandoc.Emph(pandoc.Str(string.upper(title)))) }, "CoverLine")
557
+ end
558
+ blocks[#blocks + 1] = pandoc.Para({ pandoc.Emph(pandoc.Str(abs_en)) })
559
+ if meta["keywords_en"] ~= nil then
560
+ local kw_en = {}
561
+ if type(meta["keywords_en"]) == "table" and meta["keywords_en"][1] ~= nil then
562
+ for _, k in ipairs(meta["keywords_en"]) do
563
+ kw_en[#kw_en + 1] = pandoc.utils.stringify(k)
564
+ end
565
+ else
566
+ kw_en[#kw_en + 1] = meta_str(meta, "keywords_en")
567
+ end
568
+ if #kw_en > 0 then
569
+ blocks[#blocks + 1] = pandoc.Para({
570
+ pandoc.Strong(pandoc.Str("Keywords: ")),
571
+ pandoc.Emph(pandoc.Str(table.concat(kw_en, ", ")))
572
+ })
573
+ end
574
+ end
575
+ blocks[#blocks + 1] = pagebreak()
576
+ end
577
+
385
578
  meta.title = nil
386
579
  meta.subtitle = nil
387
580
  meta.date = nil
@@ -401,6 +594,16 @@ function Pandoc(doc)
401
594
  for _, b in ipairs(daftar_isi()) do
402
595
  body[#body + 1] = b
403
596
  end
597
+ if show_dg then
598
+ for _, b in ipairs(daftar_gambar()) do
599
+ body[#body + 1] = b
600
+ end
601
+ end
602
+ if show_dt then
603
+ for _, b in ipairs(daftar_tabel()) do
604
+ body[#body + 1] = b
605
+ end
606
+ end
404
607
  inserted = true
405
608
  end
406
609
  body[#body + 1] = blk
@@ -27,7 +27,7 @@ export PYTHONUTF8=1
27
27
  show_banner() {
28
28
  echo -e "${CYAN}${BOLD}"
29
29
  echo " ========================================================"
30
- echo " LAPORAN GENERATOR CLI v2.7.0 "
30
+ echo " LAPORAN GENERATOR CLI v2.9.0 "
31
31
  echo " Otomatisasi Dokumen Akademik (Typst + DOCX Engine) "
32
32
  echo " ========================================================"
33
33
  echo -e "${NC}"
@@ -345,7 +345,7 @@ cmd_build() {
345
345
  ./build.sh
346
346
  echo ""
347
347
  echo -e "${BLUE}${BOLD}2. Membangun Dokumen Microsoft Word (DOCX Multi-Pass)...${NC}"
348
- make docx
348
+ ./build-docx.sh
349
349
  echo ""
350
350
  echo -e "${GREEN}${BOLD}Dokumen PDF dan DOCX berhasil dibuat!${NC}"
351
351
  echo -e " - ${CYAN}Laporan.pdf${NC}"