Skip to content

Tài liệu đọc trong lesson ​

Một lesson (course_concepts) từ 08.09.2026 có thể mang một hoặc nhiều tài liệu đọc giàu định dạng: ảnh, video nhúng, khung nhấn, khối gập, câu hỏi chấm tại chỗ. Người soạn viết chúng trong admin bằng trình soạn thảo TipTap; người học đọc chúng ở chặng Tìm hiểu của trang bài.

Nó KHÔNG thay ba bài viết của máy sinh ​

course_concept_articlescourse_lesson_docs
Ai viếtmáy sinh (authoring.ts)người soạn, trong admin
Dạngchữ thuần (body_vi)TipTap JSON (doc_json)
Lớp kiểmcontent-rules, quality-audit, assert văn phongkhông có, người soạn tự chịu
Số lượngđúng ba (what / confusion / apply)0..n, có thứ tự

Ba bài viết phải giữ dạng chữ thuần để các lớp kiểm còn đọc được. Tài liệu đọc là thứ khác: bài đọc dài kiểu Substack, dựng bối cảnh cho ba lát cắt ngắn phía sau, nên nó hiện trước ba bài viết trong chặng Tìm hiểu.

Ba chỗ phải sửa cùng lúc khi thêm một loại khối ​

Đây là bẫy chính của tính năng này, và cả ba lần thiếu đều không báo lỗi gì:

ChỗFileThiếu thì
Whitelist ở máy chủapi.conan.school/src/shared/rich-doc.tskhối bị bỏ khi lưu, người soạn mất công
Trình soạn thảoadmin.conan.school/legacy/admin-course-content/src/components/lesson-doc/nodes.tskhông chèn được khối
Cách vẽpackages/rich-doc/RichDoc.tsxkhối biến mất khi đọc

Tên node và tên attr phải trùng từng chữ ở cả ba.

An toàn: whitelist ở máy chủ, không phải ở editor ​

sanitizeDoc() chạy trong đường ghi của mọi endpoint admin. Nó là danh sách cho phép, không bao giờ là danh sách cấm: node lạ bị bỏ, attr không khai bị cắt, href chỉ nhận https: và mailto:, src của ảnh chỉ nhận https:, video chỉ nhận YouTube / Vimeo / Cloudflare Stream và được đổi sang đúng URL nhúng.

Trình soạn thảo không phải hàng rào, nó là JavaScript trong trình duyệt người soạn, và một lượt curl tới PATCH /api/admin/lesson-docs/:id bỏ qua nó hoàn toàn.

Phía đọc, RichDoc.tsx dựng React element, không bao giờ dangerouslySetInnerHTML. Hai lớp, không lớp nào dựng HTML từ chuỗi, đúng chỗ đã hỏng với SVG cover lấy từ D1.

Lớp kiểm ​

auditCourse không đọc course_lesson_docs. Lớp kiểm của tài liệu đọc là một gác riêng:

bash
npm run check:lesson-docs

Nó dựng lại các migration bằng SQLite, rồi soi từng tài liệu bằng mười luật đếm được, kèm mười mẫu lỗi phải bị bắt và hai mẫu hợp lệ phải được cho qua. Chi tiết và ba quyết định thiết kế đáng giữ: skill lesson-docs, luật 8.

Nháp và xuất bản ​

Hai cột, không phải một:

  • draft_json, bản đang soạn. Mọi lượt lưu (kể cả tự lưu sau 2 giây ngừng gõ) chỉ chạm cột này.
  • doc_json, bản người học đọc. Chỉ POST /:id/publish ghi vào đây.

learn-overview chỉ trả tài liệu status='published', và câu SELECT của nó không códraft_json trong danh sách cột. Xuất bản một tài liệu rỗng bị chặn ở API (422): một mục có tiêu đề mà bấm vào không có gì là đúng loại hỏng mà hệ vẫn trông như đang chạy.

Ba trạng thái người soạn phải phân biệt được trên màn hình:

NhãnNghĩa
Đang hiển thịngười học đang đọc đúng bản này
Có nháp chưa xuất bảnngười học vẫn đang đọc bản cũ
Nháp, người học chưa thấychưa từng xuất bản, ngoài kia không có gì

Ảnh ​

Tải lên qua POST /api/admin/lesson-docs/concepts/:conceptId/assets: nén WebP xuống dưới 400 KB (cùng hàm với media_library.ts), đẩy vào R2 EVENT_MEDIA ở lesson-docs/<conceptId>/<id>.webp, ghi sổ course_lesson_doc_assets, tài liệu chỉ giữ URL.

Không bao giờ nhúng base64 vào doc_json. Một tấm ảnh 2 MB thành ~2,7 MB chữ, nằm cùng hàng D1 với nội dung và đi kèm mọi lượt tải trang học, đúng khuôn "loader trả cả cục" đã làm trang chủ www nặng 1,99 MB.

Sổ asset tồn tại vì R2 không trả lời được câu "ảnh này còn ai dùng không". Không có sổ thì mỗi lần xoá tài liệu là để lại rác vĩnh viễn trong bucket.

Hai đường ảnh ​

Ảnh của aiĐi đâuSổ
Người soạn tải lên trong adminR2 (lesson-docs/<conceptId>/<id>.webp)course_lesson_doc_assets
Hình của tài liệu do agent soạncom.conan.school/public/lesson-docs/*.svg trong gitgit

Đường thứ hai tồn tại vì không có đường nào đưa ảnh vào R2 qua CI. Tài liệu do agent soạn sống trong git, nên hình của nó phải sống cùng chỗ, để trên R2 thì chạy lại repo ở môi trường khác là tài liệu mất hình. Đừng dùng đường git cho ảnh của người soạn: nó bắt người ta mở PR để thay một tấm ảnh.

Hình trong git vẽ bằng scripts/build-lesson-doc-figures.mjs: màu đọc thẳng từ theme.css lúc chạy, và không chữ trong hình, nghĩa nằm ở alt, thứ RichDoc.tsx in ra thành caption.

bash
npm run figures:build     # vẽ lại sau khi đổi token hoặc đổi hình
npm run check:figures     # gác trong job `guards` của CI

Gác bắt bốn kiểu hỏng, cả bốn đều im lặng: đổi token màu mà quên vẽ lại (SVG nằm trong <img> nên biến CSS của trang không với tới nó), sửa tay file SVG, hình mồ côi còn trong git mà bộ vẽ không sinh ra nữa, và URL trong migration trỏ tới hình không tồn tại.

Khối "Hỏi nhanh" gửi kèm đáp án: và đó là cố ý ​

quickCheck chấm ngay trong trình duyệt, không ghi điểm, không chặn đường ai. Hai cổng chặn thật, quiz chốt của bài và quiz cuối unit, vẫn chấm ở máy chủ và không bao giờ gửi đáp án đi trước. Đừng chép khuôn của quickCheck sang hai cổng đó.

Đường dẫn ​

ViệcChỗ
Soạnadmin.conan.school → Content & Learning → Courses & Docs → Lesson Documents
API/api/admin/lesson-docs/* (miền quyền content)
Người học đọc/learn/lesson/:lessonId, chặng Tìm hiểu
Bảngcourse_lesson_docs, course_lesson_doc_assets (họ không tiền tố)
Migrationapi.conan.school/migrations/20260916a_lesson_docs.sql

Xem thêm: skill lesson-docs, content-ops, known-risks, design-system.

Tài liệu nội bộ nền tảng Conan School.