Tổng quan

PostDecoratorManager là thành phần trung tâm dùng để chuyển đổi hoặc “decorate” nội dung bài viết trước khi hiển thị, preview hoặc trả về qua API.
Manager không tự parse từng định dạng nội dung. Thay vào đó, nó tìm các PostDecorator đã được đăng ký trong container, lọc theo contentType, sắp xếp theo priority, rồi chạy tuần tự các decorator phù hợp. Kết quả đầu ra thường là HTML.

Khi nào sử dụng

Dùng PostDecoratorManager khi cần:
  • Chuyển EditorJS JSON sang HTML.
  • Render nội dung Thymeleaf template sang HTML.
  • Thực thi nội dung JavaScript để tạo HTML.
  • Áp dụng nhiều bước decorate liên tiếp cho cùng một loại nội dung.
  • Preview nội dung trong admin trước khi lưu hoặc xuất bản.

Luồng hoạt động

flowchart TD
    A[Client gửi nội dung cần chuyển đổi] --> B[API convert-content]
    B --> C[PostDecoratorManager.decorateContent]
    C --> D{contentType có rỗng không?}
    D -- Có --> E[Dùng HTML làm mặc định]
    D -- Không --> F[Dùng contentType từ request]
    E --> G[Lấy danh sách PostDecorator theo contentType]
    F --> G
    G --> H[Sắp xếp decorator theo priority]
    H --> I[Chạy từng decorator tuần tự]
    I --> J[Decorator gọi converter tương ứng]
    J --> K[Trả về HTML đã chuyển đổi]

API chuyển đổi nội dung

Admin cung cấp API:
POST /api/v1/convert-content
API này yêu cầu user đã đăng nhập và thuộc feature quản lý bài viết.
Request body:
{
  "contentType": "EDITOR_JS_JSON",
  "languageCode": "vi",
  "content": "{"blocks":[...]}",
  "parameters": {
    "name": "EzyArticle"
  }
}
Response:
{
  "content": "<div class="block-wrapper" type="paragraph"><p>...</p></div>"
}
Nếu content null, hệ thống xử lý như chuỗi rỗng. Nếu parameters null, hệ thống dùng map rỗng.

Cách gọi trong Java

String html = postDecoratorManager.decorateContent(
    contentType,
    content,
    languageCode,
    parameters
);
Trong đó:
  • contentType: loại nội dung đầu vào.
  • content: nội dung gốc cần chuyển đổi.
  • languageCode: mã ngôn ngữ, ví dụ vi, en.
  • parameters: dữ liệu dùng để thay thế biến trong template hoặc truyền vào converter.
Nếu contentType rỗng, manager mặc định dùng HTML.

Các contentType mặc định

Các decorator mặc định đang hỗ trợ:
contentTypeÝ nghĩaĐầu ra
EDITOR_JS_JSONChuyển JSON của EditorJS sang HTMLHTML blocks
THYMELEAFRender template Thymeleaf với biến truyền vàoHTML
JAVASCRIPTThực thi JavaScript với parameters làm dữ liệu đầu vàoChuỗi HTML hoặc chuỗi kết quả
HTMLMặc định của interface nếu decorator không overrideThường giữ nguyên hoặc decorate thêm

Cơ chế chọn decorator

PostDecoratorManager lấy toàn bộ bean implement PostDecorator từ singleton factory, sau đó:
  1. Sắp xếp theo getPriority(), số nhỏ chạy trước.
  2. Gom nhóm decorator theo getContentTypes().
  3. Khi convert, chỉ chạy decorator thuộc contentType tương ứng.
  4. Kết quả của decorator trước là input của decorator sau.
flowchart LR
    A[content ban đầu] --> B[Decorator priority 0]
    B --> C[Decorator priority 10]
    C --> D[Decorator priority 20]
    D --> E[content sau cùng]

EditorJS sang HTML

Với EDITOR_JS_JSON, converter đọc field blocks trong JSON và chuyển từng block sang HTML. Một số block đang được hỗ trợ gồm:
  • paragraph -> <p>...</p>
  • header -> <h1> đến <h6>
  • image -> <img>
  • embed -> <iframe>
  • list -> <ul> hoặc <ol>
  • quote -> <blockquote>
  • code -> <pre>
  • table -> <table>
  • raw -> HTML gốc
  • linkTool -> thẻ <a>
  • media -> audio, video, image hoặc file link
Mỗi block được bọc bởi:
<div class="block-wrapper" type="paragraph">...</div>
Nếu nội dung không giống JSON hoặc parse lỗi, converter trả lại nội dung gốc.

Thymeleaf sang HTML

Với THYMELEAF, nội dung được xem là template. Converter render template bằng ViewContext, truyền vào:
  • template là chính chuỗi content,
  • locale lấy từ languageCode,
  • content type đầu ra là HTML,
  • variables lấy từ parameters.
Ví dụ:
{
  "contentType": "THYMELEAF",
  "languageCode": "vi",
  "content": "<p>Xin chào [[]]</p>",
  "parameters": {
    "name": "EzyArticle"
  }
}
Kết quả:
<p>Xin chào EzyArticle</p>

JavaScript sang HTML

Với JAVASCRIPT, nội dung được đưa vào JavascriptService.execute(content, parameters). Kết quả thực thi được chuyển thành chuỗi. Nếu kết quả null, hệ thống trả về chuỗi rỗng.

Xử lý lỗi

Decorator nền bắt exception khi convert thất bại, ghi log, rồi trả về nội dung gốc. Vì vậy API /convert-content không tự động fail chỉ vì converter lỗi.
Điều này hữu ích cho preview/editor: người dùng vẫn nhận lại nội dung ban đầu thay vì mất toàn bộ kết quả. Tuy nhiên, nếu cần validate hoặc chặn HTML nguy hiểm, nên thực hiện ở bước validate riêng trước khi lưu hoặc trước khi public nội dung.

Mở rộng decorator mới

Để thêm loại chuyển đổi mới:
@EzySingleton
public class MarkdownPostDecorator implements PostDecorator {

    @Override
    public String decorateContent(String content, Locale locale) {
        return markdownToHtml(content);
    }

    @Override
    public String getContentType() {
        return "MARKDOWN";
    }

    @Override
    public int getPriority() {
        return 10;
    }
}
Sau khi bean được đăng ký, PostDecoratorManager sẽ tự phát hiện và đưa decorator vào nhóm MARKDOWN.

Lưu ý

  • parameters được dùng cho Thymeleaf, JavaScript và cả một số field trong EditorJS.
  • Nếu không có decorator nào khớp contentType, nội dung trả về không đổi.
  • Decorator chạy tuần tự, nên thứ tự priority ảnh hưởng trực tiếp đến HTML cuối cùng.
  • API admin chỉ convert/decorate content; việc lưu bài viết, publish, sanitize hoặc validate đầy đủ không nằm trong endpoint này.