§ blog · Development29/06/2026
← Tất cả bài viết

Autumn Note: vì sao chúng tôi tự xây một rich-text editor zero-dependency, mã nguồn mở

Hầu hết hệ thống nội bộ — CMS, admin portal, knowledge base — đều cần một ô soạn thảo văn bản có định dạng. Thay vì kéo theo jQuery hoặc một thư viện thương mại nặng, chúng tôi xây Autumn Note: editor WYSIWYG thuần ES2022, zero dependency, mã nguồn mở MIT.

DevelopmentOpen SourceWYSIWYGTypeScript7 phút đọc
By KonexForge Engineering Team
INPUT KHÔNG TIN CẬYPaste HTMLsetHTML()Typed inputDOM Sanitizerstrip script · on* · js:AUTUMN NOTE CORE · ES2022Toolbar + Bubble toolbarsticky · floating on selectContenteditable DOMvanilla JS · zero depsHistory (undo/redo)configurable depthMarkdown shortcuts# · > · - → HTMLgetHTML() · setHTML() · getMarkdown()MỞ RỘNGPlugin APIinstall() · uninstall()AutumnNote.use()đăng ký toàn cụcReact wrapperref.getHTML()Vue wrapperref.value.editorZERO DEPENDENCY · ES2022 · MIT LICENSE · OPEN SOURCEautumn.konexforge.com

Phần lớn hệ thống nội bộ mà chúng tôi xây — admin portal, CMS, knowledge base, form hỗ trợ khách hàng — đều có ít nhất một chỗ cần người dùng nhập nội dung có định dạng: in đậm, chèn ảnh, chèn bảng, không chỉ là một ô textarea thuần văn bản. Giải pháp quen thuộc cho bài toán này — Summernote, Quill, TinyMCE — đều ổn, nhưng đi kèm một chi phí thường bị bỏ qua: phụ thuộc jQuery (Summernote), bundle size lớn cho một component dùng phổ biến trên mọi trang, hoặc giấy phép thương mại khi cần tính năng nâng cao (TinyMCE, Froala). Sau vài lần phải vá lại các thư viện này cho đúng yêu cầu của khách hàng, chúng tôi quyết định xây Autumn Note — một rich-text editor zero-dependency, mã nguồn mở theo giấy phép MIT.

Vì sao zero-dependency quan trọng với một component dùng ở khắp nơi

Một rich-text editor không phải là thư viện chỉ dùng ở một trang — nó thường xuất hiện trong CMS, form hỗ trợ, trang soạn email, comment box, và nhiều nơi khác trong cùng một sản phẩm. Bất kỳ phụ thuộc nào nó kéo theo (jQuery, một framework UI cụ thể) đều nhân lên theo số lần component được dùng, hoặc buộc dự án phải cài thêm một runtime chỉ vì một ô soạn thảo. Autumn Note được viết bằng vanilla JavaScript thuần ES2022+ — không jQuery, không framework bắt buộc — build bằng Vite thành cả ES Module (tree-shakeable) và UMD (dùng trực tiếp qua thẻ `<script>` không cần bundler), với TypeScript definitions đi kèm sẵn thay vì để dự án tự khai báo type.

Bộ tính năng — đủ cho sản phẩm thực tế, không chỉ demo

Ngoài các tính năng format cơ bản (bold/italic/underline, heading, danh sách, căn lề, RTL), một số tính năng được thiết kế riêng cho nhu cầu sản phẩm thực tế:

  • **Markdown shortcuts** — gõ `# `, `> `, `- ` tự động chuyển thành heading, blockquote, danh sách — hữu ích cho người dùng quen thao tác bàn phím hơn là bấm nút toolbar
  • **Bubble toolbar** — thanh định dạng mini nổi lên ngay phía trên đoạn văn bản đang chọn, giảm số lần mắt phải di chuyển đến toolbar cố định ở trên cùng
  • **@mention autocomplete** — qua callback `onSearch` cấu hình được, dùng để tag người dùng khác hoặc liên kết đến một thực thể trong hệ thống (ticket, tài liệu) ngay trong nội dung đang soạn
  • **Find & Replace** với chế độ regex (Ctrl+F/Ctrl+H) — thường thiếu trong các editor nhẹ, nhưng quan trọng khi soạn tài liệu dài
  • **Crop ảnh inline** và resize ảnh/video bằng kéo thả ngay trong vùng soạn thảo, không cần mở công cụ ngoài
  • **Auto-save vào localStorage** kèm banner khôi phục — tránh mất nội dung khi tab bị đóng nhầm hoặc trình duyệt crash

Một quyết định thiết kế cụ thể: thay vì cố nhồi mọi tính năng vào core, các tính năng ít dùng (FontAwesome icon picker, bảng nâng cao với merge cell, syntax highlight qua Prism.js) đều có thể tắt qua cấu hình toolbar — giữ bundle nhỏ cho sản phẩm không cần đến chúng.

Sanitization — phần không thể bỏ qua của một content-editable component

Bất kỳ component nào nhận input dạng HTML từ người dùng đều là một bề mặt tấn công XSS tiềm năng — paste một đoạn HTML chứa `<script>` hoặc thuộc tính `onerror` từ nguồn ngoài là kịch bản phổ biến nhất. Autumn Note có một DOM-based sanitizer chạy ở nhiều điểm: khi paste nội dung từ ngoài vào, khi gọi `setHTML()` để nạp nội dung có sẵn, và trước khi export. Sanitizer loại bỏ thẻ `<script>`, `<object>`, `<embed>`, mọi thuộc tính `on*` (event handler), và chặn URL dạng `javascript:`/`data:` trong href/src — đồng thời vẫn giữ lại iframe từ danh sách host được tin tưởng (để nhúng video YouTube/Vimeo hợp lệ) thay vì chặn toàn bộ iframe một cách thô bạo. Đây là nguyên tắc zero-trust với nội dung đầu vào mà chúng tôi cũng áp dụng ở tầng bảo mật cho hệ thống có AI — không khác biệt giữa nội dung do người dùng paste vào editor và nội dung đưa vào context của một AI agent: cả hai đều cần được coi là chưa tin cậy cho đến khi qua một lớp validate/sanitize rõ ràng.

Plugin API — mở rộng mà không phải fork core

Một bài học khi xây editor dùng cho nhiều khách hàng khác nhau: nhu cầu tùy biến gần như luôn xuất hiện, nhưng đa số trường hợp không nên giải quyết bằng cách fork code gốc. Autumn Note có một plugin API composable:

  • `name`, `version` — định danh plugin
  • `buttons` — đăng ký toolbar button mới, dùng tên này trong cấu hình `toolbar` của editor
  • `install(ctx, options)` — chạy sau khi editor khởi tạo, có quyền truy cập toàn bộ context API của instance
  • `uninstall(ctx)` — dọn dẹp khi gỡ plugin
  • Đăng ký qua `AutumnNote.use(plugin)` toàn cục, hoặc `editor.use(plugin)` cho một instance riêng

Cấu trúc này cho phép thêm tính năng đặc thù của một khách hàng (ví dụ: chèn block trích dẫn theo định dạng riêng của họ, hoặc validate nội dung theo quy tắc nội bộ) như một plugin riêng, tách biệt khỏi core — core vẫn nhỏ và dễ audit, còn phần tùy biến sống ở lớp ứng dụng.

Tích hợp React và Vue mà không lộ chi tiết DOM

Vì Autumn Note thao tác DOM trực tiếp (contenteditable), tích hợp ẩu vào một component React/Vue dễ xung đột với virtual DOM diffing của framework. Hai wrapper chính thức — `autumnnote-react` và `autumnnote-vue` — giải quyết việc này bằng imperative handle: component expose một ref với các method (`getHTML()`, `setHTML()`, …) thay vì để framework cố quản lý nội dung bên trong vùng editor như state thông thường. Phần ứng dụng chỉ tương tác qua ref — không thao tác DOM thủ công, không xung đột re-render.

Vì sao một công ty dịch vụ phần mềm lại duy trì một thư viện mã nguồn mở

Autumn Note không phải sản phẩm thương mại — nó là công cụ nội bộ chúng tôi dùng lại trong các Pilot Build có nhu cầu soạn thảo nội dung, tương tự cách portal nội bộ hợp nhất 4 hệ thống legacy cần một lớp soạn tài liệu/knowledge base dùng chung cho 14 team. Mở mã nguồn nó mang lại hai lợi ích cụ thể: thứ nhất, một thành phần được dùng và kiểm thử qua nhiều dự án thực tế đáng tin cậy hơn một thành phần viết riêng cho một khách hàng rồi không ai động đến nữa; thứ hai, đây là cách minh bạch nhất để chứng minh năng lực kỹ thuật — code, test, và quyết định kiến trúc đều công khai trên GitHub, không chỉ là một dòng mô tả trên trang dịch vụ.

Kết luận

Autumn Note giải quyết một bài toán nhỏ nhưng lặp lại ở rất nhiều sản phẩm: cần một ô soạn thảo có định dạng, đủ tính năng cho người dùng thật, không kéo theo phụ thuộc không cần thiết, và an toàn trước nội dung không tin cậy ngay từ thiết kế. Try demo tại autumn.konexforge.com, cài qua `npm install autumnnote`, hoặc xem code đầy đủ trên GitHub — đây cũng là loại thành phần chúng tôi tái sử dụng trong lớp Development của các Pilot Build cần một lớp content editing đáng tin cậy mà không phải xây lại từ đầu.

Bài viết liên quan

Development

Hóa đơn điện tử theo từng chuyến xe: kiến trúc hệ thống real-time cho đội taxi

Thông tư 32/2025/TT-BTC bắt buộc hóa đơn điện tử phải được phát hành ngay tại thời điểm kết thúc chuyến. Bài viết phân tích ba thách thức kỹ thuật thực tế và kiến trúc event-driven để giải quyết chúng — từ đồng hồ taxi cơ học đến hệ thống ký số và đối soát tự động.

Development

GitOps và CI/CD cho ML pipeline: từ notebook experiment đến production deployment với Argo CD

ML pipeline có vòng lặp phát triển khác với software thông thường: code thay đổi ít hơn, model và data thay đổi nhiều hơn. Áp dụng GitOps trực tiếp từ software engineering sang ML mà không điều chỉnh thường dẫn đến pipeline nặng nề và khó debug. Cách thiết kế CI/CD cho ML với Argo CD: tách model artifact khỏi code deploy, trigger pipeline khi model mới train xong, và rollback tự động khi metric suy giảm.

Development

Bảo mật trong thời đại AI: rủi ro mới và nguyên tắc zero-trust cho hệ thống production

AI agent và LLM tích hợp vào hệ thống production mở ra một lớp bề mặt tấn công mới — prompt injection, data poisoning, deepfake — không thay thế mà cộng thêm vào rủi ro bảo mật truyền thống. Nguyên tắc zero-trust, secure SDLC và checklist thực tế để bảo vệ hệ thống có AI.

Có một bài toán tương tự đang cần giải?

Liên hệ team