§ 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-reactautumnnote-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. Autumn Note cùng các dự án mã nguồn mở khác của chúng tôi được liệt kê đầy đủ — phiên bản, giấy phép, cách đóng góp — ở trang Cộng đồng.

Bài viết liên quan

Development

Forge Dialog: hộp thoại dựng trên thẻ <dialog> gốc, không còn phải dò z-index

Một trang quản trị cần hỏi "bạn có chắc muốn xoá?". Hàm confirm sẵn có của trình duyệt làm được nhưng chặn cả trang và không đổi được giao diện; thư viện thay thế thì thường kéo theo cả framework. Forge Dialog là cách chúng tôi giải bài toán đó: không phụ thuộc runtime, dựng trên thẻ dialog gốc, và bộ nối cho React, Vue, Svelte nằm ngay trong cùng một gói.

Development

Forge Select: bản thay thế Select2 zero-dependency, framework-agnostic mã nguồn mở

Select2 đã phục vụ cộng đồng web nhiều năm, nhưng dựa trên jQuery — một ràng buộc ngày càng khó chấp nhận với stack hiện đại. Forge Select là component select/combobox chúng tôi tự xây: toàn bộ core chỉ 5 file, zero runtime dependency, nhưng đủ tính năng cho sản phẩm thật — virtual scroll, tree select, tags, và accessibility đầy đủ.

Development

KonexForge Themes: bộ theme VS Code với thẩm mỹ 'forge' — không phải một bản sao One Dark khác

Engineer nhìn vào editor cả ngày, nhưng thị trường theme VS Code gần như bão hòa bởi các biến thể gần giống hệt One Dark Pro. KonexForge Themes là bộ 4 theme mã nguồn mở chúng tôi tự làm — không phải để bán, mà vì bản sắc thị giác riêng cũng là một phần của văn hóa kỹ thuật.

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

Liên hệ team