Specification Draft

Nâng Cấp Hệ Thống Bullet Point

Tài liệu đặc tả chi tiết phân tích lỗi, hướng thiết kế giải pháp và chi tiết mã nguồn cần thay đổi
Người thực hiện Antigravity (AI Agent)
Ngày cập nhật 07 tháng 07, 2026
Trạng thái Chờ USER phê duyệt (Chưa sửa code)

Phân Tích 3 Vấn Đề Hiện Tại

Vấn đề 1: Phím tắt gõ nhanh - [Space] tự chuyển thành dấu tròn (Disc)

Trong Tiptap Editor mặc định, quy tắc nhập liệu (input rules) nhận diện ký tự -, *, và + để tự động kích hoạt Bullet List. Tuy nhiên, toàn bộ các phím tắt này đều chuyển đổi về bullet list mặc định với thuộc tính kiểu dáng là dấu tròn đặc (disc). USER mong muốn khi gõ cụ thể ký tự gạch ngang - thì danh sách tạo ra phải có kiểu gạch ngang (dash).

Vấn đề 2: Cần hệ thống Phân Cấp Gia Đình Bullet Point (Nesting Style Families)

Khi nhấn Tab để thụt dòng danh sách, Tiptap sinh ra thẻ <ul> con. Tuy nhiên, do thẻ con này lấy thuộc tính mặc định listStyleType: 'disc' và inline style style="list-style-type: disc" được áp trực tiếp từ node renderer, nó đè lên toàn bộ cơ chế kế thừa hoặc phân tầng CSS. Cần xây dựng 6 bộ phả hệ (families) tự động xoay chuyển dấu marker khi lồng nhau theo chuẩn Word / Google Docs (ví dụ: Level 0: ✓ -> Level 1: ○ -> Level 2: ■).

Vấn đề 3: Lỗi hiển thị Bullet Point trong Bảng (Table Cells)

Trong file CSS hiện tại có thiết lập rule quét rộng: .tiptap td > * { padding: 0; margin: 0; }. Điều này làm ghi đè và triệt tiêu padding-left: 28px mặc định của danh sách <ul>/<ol> khi nằm trực tiếp trong ô bảng. Do đó, lề trái danh sách bị đẩy về sát viền ô, và các dấu đầu dòng (marker) hiển thị tràn ra ngoài đè trực tiếp lên viền cell (như trong ảnh lỗi). Cần ghi đè đặc thù để danh sách trong bảng xem viền cell là biên lề mới của nó.

Danh Sách Mã Nguồn Cần Sửa Đổi

Giải pháp yêu cầu thay đổi 03 file nguồn phối hợp nhịp nhàng giữa frontend editor, stylesheet hiển thị và export DOCX:

  1. src/webview/extensions/CustomBulletList.ts — Điều chỉnh renderer HTML loại bỏ inline style thừa và bổ sung input rules thông minh.
  2. src/webview/styles/editor.css — Loại bỏ sự phụ thuộc inline styles; bổ sung bộ chọn CSS phân cấp đa tầng (families) cho bullet và ghi đè lề thụt dòng trong bảng.
  3. src/extension/DocxSerializer.ts — Nâng cấp bản đồ xuất bản để mỗi họ danh sách tự sinh cấu hình numbering phân tầng 9 cấp độ khác nhau khớp hoàn toàn với editor.

Chi Tiết Cần Sửa: CustomBulletList.ts

src/webview/extensions/CustomBulletList.ts

Hướng sửa:

TRƯỚC KHI SỬA
// ...
listStyleType: {
  default: 'disc',
  parseHTML: (element: HTMLElement) => {
    return element.getAttribute('data-list-style-type')
      || element.style.listStyleType
      || 'disc'
  },
  renderHTML: (attributes: Record<string, any>) => {
    const style = attributes.listStyleType || 'disc'
    return {
      'data-list-style-type': style,
      style: `list-style-type: ${style === 'checkmark' || style === 'dash' ? 'none' : style}`,
    }
  },
},
// ...
          
SAU KHI SỬA
import { wrappingInputRule } from '@tiptap/core'
// ...
listStyleType: {
  default: 'disc',
  parseHTML: (element: HTMLElement) => {
    return element.getAttribute('data-list-style-type')
      || element.style.listStyleType
      || 'disc'
  },
  renderHTML: (attributes: Record<string, any>) => {
    const style = attributes.listStyleType || 'disc'
    return {
      'data-list-style-type': style,
      // Chỉ đặt inline none cho checkmark/dash. 
      // Các loại khác để trống để CSS phân cấp hoạt động.
      style: style === 'checkmark' || style === 'dash' 
        ? 'list-style-type: none' 
        : undefined,
    }
  },
},
// ...
addInputRules() {
  return [
    wrappingInputRule({
      find: /^\s*([-*+])\s$/,
      type: this.type,
      getAttributes: match => {
        const symbol = match[1]
        if (symbol === '-') {
          return { listStyleType: 'dash' }
        }
        return { listStyleType: 'disc' }
      },
    }),
  ]
},
          

Chi Tiết Cần Sửa: editor.css

src/webview/styles/editor.css

Hướng sửa:

TRƯỚC KHI SỬA (L.3804-3823)
ul[data-list-style-type="checkmark"] > li::marker {
  content: "✓ ";
}
ul[data-list-style-type="dash"] > li::marker {
  content: "– ";
}

ul[data-list-style-type="disc"] { list-style-type: disc; }
ul[data-list-style-type="circle"] { list-style-type: circle; }
ul[data-list-style-type="square"] { list-style-type: square; }
ul[data-list-style-type="disclosure-closed"] { list-style-type: disclosure-closed; }

ul[data-list-style-type="checkmark"],
ul[data-list-style-type="dash"] {
  list-style-type: none;
}
          
SAU KHI SỬA
/* Định nghĩa cấu trúc họ Bullet Point (Family) */

/* 1. Họ Disc: ● -> ○ -> ■ */
ul[data-list-style-type="disc"] { list-style-type: disc; }
ul[data-list-style-type="disc"] ul[data-list-style-type="disc"] { list-style-type: circle; }
ul[data-list-style-type="disc"] ul[data-list-style-type="disc"] ul[data-list-style-type="disc"] { list-style-type: square; }

/* 2. Họ Circle: ○ -> ○ -> ■ */
ul[data-list-style-type="circle"] { list-style-type: circle; }
ul[data-list-style-type="circle"] ul[data-list-style-type="disc"] { list-style-type: circle; }
ul[data-list-style-type="circle"] ul[data-list-style-type="disc"] ul[data-list-style-type="disc"] { list-style-type: square; }

/* 3. Họ Square: ■ -> ■ -> ■ */
ul[data-list-style-type="square"] { list-style-type: square; }
ul[data-list-style-type="square"] ul[data-list-style-type="disc"] { list-style-type: square; }
ul[data-list-style-type="square"] ul[data-list-style-type="disc"] ul[data-list-style-type="disc"] { list-style-type: square; }

/* 4. Họ Mũi Tên: ▶ -> ○ -> ■ */
ul[data-list-style-type="disclosure-closed"] { list-style-type: disclosure-closed; }
ul[data-list-style-type="disclosure-closed"] ul[data-list-style-type="disc"] { list-style-type: circle; }
ul[data-list-style-type="disclosure-closed"] ul[data-list-style-type="disc"] ul[data-list-style-type="disc"] { list-style-type: square; }

/* 5. Họ Dấu Tích: ✓ -> ○ -> ■ */
ul[data-list-style-type="checkmark"] { list-style-type: none; }
ul[data-list-style-type="checkmark"] > li::marker { content: "✓ "; }
ul[data-list-style-type="checkmark"] ul[data-list-style-type="disc"] { list-style-type: circle; }
ul[data-list-style-type="checkmark"] ul[data-list-style-type="disc"] ul[data-list-style-type="disc"] { list-style-type: square; }

/* 6. Họ Gạch Ngang: – -> ○ -> ■ */
ul[data-list-style-type="dash"] { list-style-type: none; }
ul[data-list-style-type="dash"] > li::marker { content: "– "; }
ul[data-list-style-type="dash"] ul[data-list-style-type="disc"] { list-style-type: circle; }
ul[data-list-style-type="dash"] ul[data-list-style-type="disc"] ul[data-list-style-type="disc"] { list-style-type: square; }

/* Sửa lỗi thụt dòng & căn chỉnh lề của danh sách trong Bảng */
.tiptap td > ul, .tiptap td > ol,
.tiptap th > ul, .tiptap th > ol {
  padding-left: 24px !important;
  margin-top: 4px;
  margin-bottom: 4px;
}
          

Chi Tiết Cần Sửa: DocxSerializer.ts

src/extension/DocxSerializer.ts

Hướng sửa:

TRƯỚC KHI SỬA (L.814-821)
const BULLET_STYLE_DOCX_MAP = {
  'disc':               { symbol: '\u25CF', font: 'Symbol' },
  'circle':             { symbol: '\u25CB', font: 'Courier New' },
  'square':             { symbol: '\u25A0', font: 'Wingdings' },
  'disclosure-closed':  { symbol: '\u25BA', font: 'Symbol' },
  'checkmark':          { symbol: '\u2713', font: 'Wingdings 2' },
  'dash':               { symbol: '\u2013', font: 'Arial' },
}
          
SAU KHI SỬA
interface BulletLevelDef { symbol: string; font: string }
const BULLET_FAMILY_MAP: Record<string, BulletLevelDef[]> = {
  disc: [
    { symbol: '\u25CF', font: 'Symbol' },
    { symbol: '\u25CB', font: 'Courier New' },
    { symbol: '\u25A0', font: 'Wingdings' }
  ],
  circle: [
    { symbol: '\u25CB', font: 'Courier New' },
    { symbol: '\u25CB', font: 'Courier New' },
    { symbol: '\u25A0', font: 'Wingdings' }
  ],
  square: [
    { symbol: '\u25A0', font: 'Wingdings' },
    { symbol: '\u25A0', font: 'Wingdings' },
    { symbol: '\u25A0', font: 'Wingdings' }
  ],
  'disclosure-closed': [
    { symbol: '\u25BA', font: 'Symbol' },
    { symbol: '\u25CB', font: 'Courier New' },
    { symbol: '\u25A0', font: 'Wingdings' }
  ],
  checkmark: [
    { symbol: '\u2713', font: 'Wingdings 2' },
    { symbol: '\u25CB', font: 'Courier New' },
    { symbol: '\u25A0', font: 'Wingdings' }
  ],
  dash: [
    { symbol: '\u2013', font: 'Arial' },
    { symbol: '\u25CB', font: 'Courier New' },
    { symbol: '\u25A0', font: 'Wingdings' }
  ]
}
          

Bản Vẽ Minh Họa Trực Quan Trạng Thái Sửa Lỗi

Bản dựng kiểm thử HTML giả lập CSS mới

1. Phân cấp Gia đình (Family) trong Editor:

  • ✓ Cấp độ 0 (Checkmark)
    • Cấp độ 1 (Tự động đổi thành hình tròn rỗng ○ khi thụt lề)
      • Cấp độ 2 (Tự động đổi thành hình vuông đặc ■ khi thụt lề thêm)

2. Định dạng Bullet Point lồng gọn gàng trong Bảng:

Thông tin cơ bản

Danh sách quy định:

  • Dòng quy định thứ nhất (Thụt vào trong biên, dấu chấm nằm trọn vẹn bên trong cell)
  • Dòng quy định thứ hai
    • Quy định con cấp 2

Kế Hoạch Xác Minh (Verification Plan)

1. Xác minh chức năng gõ phím tắt

2. Xác minh cơ chế phân cấp (Tab/Shift+Tab)

3. Xác minh hiển thị trong Bảng

4. Xác minh xuất/nhập DOCX