Open source

เปิดไฟล์ markdown ในโปรเจกต์ Laravel เป็นเว็บเอกสารภายในได้ยังไง

laravel-ai-docs เปิดโฟลเดอร์ .ai/documents ในโปรเจกต์เป็นเว็บเอกสารที่ /docs ในแอปเรา โฟลเดอร์กลายเป็นเมนู ค้นหาด้วย ⌘K ได้ mermaid กับรูปแสดงครบ และมี gate กั้นคนที่ไม่มีสิทธิ์

เผยแพร่
เวลาอ่าน
6 นาที

เอกสารที่อยู่นอก repo มักตามโค้ดไม่ทัน

ที่ผ่านมาเราก็มักเก็บเอกสารโปรเจกต์ไว้ใน Google Docs, Notion หรือ Wiki ของ GitHub แยกจากโค้ด พอแก้โค้ดก็ไม่มีใครกลับไปแก้เอกสาร ผ่านไปไม่กี่เดือนเอกสารก็เล่าระบบเวอร์ชันเก่า

ทุกวันนี้เอกสารเริ่มย้ายกลับมาอยู่ใน repo เพราะ AI agent อย่าง Claude Code อ่านไฟล์ใน repo ได้เอง เราจึงเขียนไว้ใน CLAUDE.md หรือโฟลเดอร์อย่าง .ai/documents/ แล้วแก้ไปพร้อมโค้ดใน commit เดียวกัน

ปัญหาคือ markdown ใน repo เหมาะกับคนที่เปิด editor อยู่แล้ว ถ้าเป็นคนในทีมที่ไม่ได้เขียนโค้ด เช่น PM, ทีม support หรือลูกค้าที่อยากอ่านว่าระบบทำงานยังไง ก็ต้องมีสิทธิ์ใน GitHub ก่อน แล้วยังต้องคลิกไล่โฟลเดอร์หาไฟล์เอง

laravel-ai-docs เปิดโฟลเดอร์ markdown เป็นหน้าเว็บในแอป

แพ็กเกจนี้อ่านโฟลเดอร์ที่ชี้ไว้ (ค่าเริ่มต้นคือ .ai/documents) แล้วแสดงเป็นเว็บเอกสารที่ /docs ในแอปเดิมของเรา ไม่ต้อง build หรือ deploy เว็บแยก

  • ไฟล์ .md ทุกไฟล์เป็นหนึ่งหน้า โฟลเดอร์กลายเป็นกลุ่มในเมนูด้านซ้าย ซ้อนกันได้ลึกเท่าที่เราจัดไว้
  • ชื่อหน้าในเมนูมาจากหัวข้อ # บรรทัดแรก ไม่ต้องมีไฟล์ตั้งค่าเมนู เพิ่มไฟล์ใหม่ก็ขึ้นในเมนูเอง
  • รูปที่วางไว้ข้างไฟล์ markdown แสดงได้เลย ไม่ต้องก๊อปไปไว้ใน public/
  • ลิงก์ระหว่างไฟล์ เช่น [ดูเรื่อง webhook](../integrations/webhooks.md#retry) กลายเป็นลิงก์ไปหน้านั้นในเว็บ

หน้าเอกสารที่มีเมนูโฟลเดอร์ด้านซ้าย แผนภาพ mermaid ตรงกลาง และสารบัญหัวข้อด้านขวา

เมนูด้านซ้ายมาจากโฟลเดอร์ ด้านขวาเป็นสารบัญหัวข้อของหน้า ที่ไฮไลต์ตามตำแหน่งที่เลื่อนอ่าน

แต่ละไฟล์ใส่ front matter ได้ถ้าอยากกำหนดเอง แต่ไม่จำเป็น title ใช้เป็นชื่อหน้า nav เป็นชื่อในเมนู และ order เรียงลำดับในกลุ่ม

---
nav: Payment retries
order: 10
---

# Payment retries — ลำดับการลองตัดเงินซ้ำ

ถ้าไม่ใส่ nav แพ็กเกจตัดชื่อจาก # ที่ตัว — หรือ : ให้เอง หน้านี้จึงขึ้นในเมนูว่า Payment retries

ติดตั้ง

แพ็กเกจต้องการ PHP 8.4 ขึ้นไป Laravel 12 หรือ 13 และแอปต้องใช้ Inertia v2/v3 กับ React 18/19 เพราะหน้าเอกสารเป็นหน้า Inertia ไม่ใช่ Blade

composer require phattarachai/laravel-ai-docs
php artisan vendor:publish --tag=ai-docs-config
php artisan vendor:publish --tag=ai-docs-inertia
npm install mermaid

tag ai-docs-inertia ต้อง publish ทุกครั้ง คำสั่งนี้สร้างไฟล์เดียวคือ resources/js/pages/AiDocs.jsx เพราะ Inertia หาหน้าได้เฉพาะในโฟลเดอร์ pages ส่วนโค้ดจริงยังอยู่ใน vendor/ แล้วเรียกผ่าน alias ใน Vite แอปของเราจึงไม่มีสำเนาที่ต้องตามอัปเดต

import path from 'node:path'

export default defineConfig({
    resolve: {
        alias: {
            '@ai-docs': path.resolve(__dirname, 'vendor/phattarachai/laravel-ai-docs/resources/js/ai-docs'),
        },
    },
})

สุดท้ายเช็กแล้ว build

php artisan ai-docs:doctor
npm run build

ai-docs:doctor เช็ก route, gate, โฟลเดอร์เอกสาร, ไฟล์ที่ publish, alias ใน Vite และ mermaid แล้วบอกว่าข้อไหนยังขาด CSS ของแพ็กเกจเป็น CSS ธรรมดาที่ครอบไว้ใต้ .doc-root ไม่ต้องเพิ่ม @source ใน Tailwind และไม่ชนกับ CSS ของแอป

ยังไม่ตั้ง gate ก็ยังไม่มีใครเปิดได้

เอกสารภายในมักมีรายละเอียดที่ไม่ควรหลุดออกไป แพ็กเกจจึงปิดไว้ก่อน ถ้ายังไม่ได้เรียก AiDocs::auth() แพ็กเกจปฏิเสธทุก request รวมถึงตัวเราเองด้วย

  • คนที่ยังไม่ login จะไปหน้า login ของแอป แล้วกลับมาหน้าเอกสารที่ตั้งใจเปิดหลัง login เสร็จ
  • คนที่ login แล้วแต่ gate ไม่ให้ผ่านได้ 403
  • ตั้ง AI_DOCS_ENABLED=false แล้วแพ็กเกจไม่ลงทะเบียน route เลย /docs จะเป็น 404

แพ็กเกจต่อ middleware ที่เช็ก gate ไว้ท้ายสุดเสมอ ถึงจะแก้ ai-docs.middleware ใน config ก็ลบออกไม่ได้

ค้นหาทั้งชุดด้วย ⌘K

กด ⌘K แล้วพิมพ์คำที่หา แพ็กเกจค้นทุกหัวข้อในทุกหน้า แสดงข้อความรอบ ๆ คำที่เจอพร้อมไฮไลต์ กด Enter แล้วกระโดดไปที่หัวข้อนั้นเลย ดัชนีค้นหาสร้างฝั่งเซิร์ฟเวอร์ ไม่ต้องตั้ง Algolia หรือบริการค้นหาภายนอก

หน้าต่างค้นหาที่พิมพ์คำว่า retry แล้วแสดงหัวข้อที่เจอพร้อมไฮไลต์

mermaid, callout และโค้ดแสดงครบเหมือนบน GitHub

เขียน fence ```mermaid แล้วได้แผนภาพ ทั้งโหมดสว่างและมืดใช้สีเข้ากับหน้าเว็บ และมีสีสำเร็จรูปให้เลือกใส่ทีละ node decision, ok, bad และ actor

```mermaid
flowchart LR
    C[ตัดเงินไม่ผ่าน] --> R{ลองใหม่ได้ไหม}:::decision
    R -->|ได้| S[ตั้งเวลาลองใหม่] --> D[ชำระแล้ว]:::ok
    R -->|ไม่ได้| X[ตัดเป็นหนี้สูญ]:::bad
```

callout แบบ GitHub (> [!NOTE], > [!TIP], > [!WARNING] …) แสดงเป็นกล่องพร้อมหัวข้อ ส่วนโค้ดไฮไลต์ฝั่งเซิร์ฟเวอร์ด้วย tempest/highlight เบราว์เซอร์ไม่ต้องโหลด JavaScript สำหรับไฮไลต์

callout แบบ NOTE และ WARNING กับบล็อกโค้ดที่ไฮไลต์สี

ตาราง แผนภาพ และรูปทุกอันมีปุ่มเปิดเต็มจอ ไว้ดู sequence diagram ยาว ๆ ที่ไม่มีทางพอดีคอลัมน์

แผนภาพ mermaid เปิดเต็มจอ

หน้าเว็บมีโหมดมืด จำค่าไว้ในเบราว์เซอร์ และเก็บไว้ที่ .doc-root ไม่ไปยุ่งกับธีมของแอปเรา

หน้าเดียวกันในโหมดมืด

SVG ที่วาดเองใช้ฟอนต์ไทยตามหน้าเว็บ

mermaid เหมาะกับ flow แต่แผนผังระบบที่มีป้ายสถานะ เส้นหลายสี และป้ายภาษาไทย วาดเองเป็น SVG ง่ายกว่า วางไฟล์ .svg ไว้ข้าง markdown แล้วใส่ inline ที่ title ของรูป

![System landscape](systems.svg "inline wide")

แพ็กเกจเขียน SVG ลงในหน้าโดยตรงแทนการโหลดผ่าน <img> ข้อความใน SVG จึงใช้ฟอนต์เดียวกับหน้าเว็บ ภาษาไทยไม่เพี้ยน เปลี่ยนสีตามโหมดมืดได้ถ้าวาดด้วยตัวแปร --doc-* ลิงก์ใน SVG กดไปหน้าเอกสารอื่นได้ และข้อความใน SVG ค้นหาด้วย ⌘K เจอ ส่วน wide ให้แผนผังกว้างเต็มพื้นที่เมื่อหน้านั้นไม่มีสารบัญด้านขวา

แผนผังระบบที่วาดเป็น SVG มีป้ายภาษาไทยและลิงก์ไปหน้าเอกสาร

ก่อนใส่ลงหน้า แพ็กเกจคัด SVG ทีละ element เก็บไว้แค่ส่วนที่ใช้วาด ตัด <script>, <foreignObject>, attribute on* และลิงก์ภายนอกทิ้ง ไฟล์ที่ใครก็แก้ใน repo ได้จึงไม่กลายเป็นช่องทางรันสคริปต์ในหน้าเอกสาร

แยกเอกสารกับงานชั่วคราวเป็นคนละ panel

เอกสารระบบเราดูแลไปเรื่อย ๆ ส่วนโฟลเดอร์ task ที่จดงานแต่ละรอบเขียนเสร็จก็จบ ถ้ารวมไว้ที่เดียว task จะกลบเอกสาร แพ็กเกจจึงให้แยกเป็นหลาย panel แต่ละ panel มี URL เมนู และดัชนีค้นหาของตัวเอง สลับกันได้จากปุ่มด้านบน

'panels' => [
    'docs'  => ['root' => '.ai/documents', 'label' => 'Documents'],
    'tasks' => ['root' => '.ai/tasks',     'label' => 'Tasks'],
],

panel Tasks เปิดหน้างานที่มี checklist และปุ่มสลับ panel ด้านบน

ลิงก์ข้าม panel ได้ task ลิงก์ไปเอกสารระบบที่เกี่ยวข้อง แล้วเอกสารลิงก์กลับมาที่ task ได้

ปุ่มคัดลอก path ไว้ส่งต่อให้ AI

ทุกหน้ามีปุ่มคัดลอก path ของไฟล์ใน repo เช่น .ai/documents/billing/payment-retries.md เอาไปวางต่อท้าย @ ใน Claude Code หรือ Cursor ได้ทันที เวลาคุยงานกับทีมก็ส่งลิงก์หน้าเว็บให้คน และส่ง path ให้ AI จากหน้าเดียวกัน

อีกปุ่มคือพิมพ์หรือบันทึกเป็น PDF ผ่านเบราว์เซอร์ แพ็กเกจซ่อนเมนูกับปุ่มต่าง ๆ ตอนพิมพ์ และไม่ตัดตารางหรือแผนภาพข้ามหน้า ถ้าแผนภาพไหนใหญ่ไป ใส่ print-70 ให้ย่อเหลือราว 70% ของหน้ากระดาษ

จัดโฟลเดอร์ให้ทั้งคนและ AI อ่านง่าย

หน้าเว็บดีได้เท่าที่โฟลเดอร์จัดไว้ดี แพ็กเกจมี docs/authoring.md เล่าวิธีจัดที่ผมใช้กับโปรเจกต์ของตัวเอง สรุปสั้น ๆ คือ

  • โฟลเดอร์ต้องมีอย่างน้อยสองไฟล์ ไฟล์เดียวให้วางไว้ชั้นบนสุด เรื่องที่ใช้ทั้งระบบอย่าง auth หรือ testing ก็วางชั้นบนสุดเหมือนกัน
  • index.md คือแผนที่ มีบรรทัดละไฟล์ บอกว่าไฟล์นั้นดูแลเรื่องอะไร เพิ่ม ย้าย หรือลบไฟล์เมื่อไหร่ก็แก้ index ใน commit เดียวกัน เพราะ AI มักอ่าน index ก่อนแล้วหยุดแค่นั้น
  • หนึ่งไฟล์ไม่เกิน 500 บรรทัด ใกล้ถึงเมื่อไหร่แปลว่ามีอีกเรื่องซ่อนอยู่ ให้แยกเป็นไฟล์ใหม่ที่ตั้งชื่อตามเรื่องนั้น

ถ้าให้ Claude Code เขียนเอกสารให้ แพ็กเกจมี skill ตัวอย่างใน examples/document-skill ก๊อปไปไว้ที่ .claude/skills/document/SKILL.md แล้วสั่ง "document this" หลังทำฟีเจอร์เสร็จ

คนที่ผ่าน gate อ่านได้ทุกไฟล์ ยกเว้นที่ exclude ไว้

gate บอกได้แค่ว่าใครเข้าเว็บเอกสารได้ พอผ่านแล้วอ่านได้ทุกไฟล์ในโฟลเดอร์ ถ้าในเอกสารมีรหัสผ่านหรือ token ที่ใครเคยแปะไว้ คนที่ผ่าน gate ก็เห็นด้วย เอกสารที่เปิดให้ลูกค้าหรือคนนอกทีมอ่าน จึงควรแยกโฟลเดอร์แล้วใช้ exclude ซ่อนส่วนที่เหลือ

'exclude' => ['operations', 'internal'],

แพ็กเกจตัดโฟลเดอร์ที่ exclude ออกจากเมนู ดัชนีค้นหา URL ตรง และรูปทั้งหมด ส่วนรูปที่แสดงได้ แพ็กเกจส่งผ่าน gate เดียวกันพร้อม X-Content-Type-Options: nosniff และ SVG ได้ Content-Security-Policy ที่ไม่ให้รันสคริปต์

อีกข้อคือแพ็กเกจใช้ได้กับแอป Inertia React เท่านั้น แอปที่ทำด้วย Blade หรือ Livewire อย่างเดียวยังใช้ไม่ได้

GitHub: phattarachai/laravel-ai-docs

อ่านต่อ

ล่าสุด

ดูทั้งหมด →