- [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).
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: ■).
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ó.
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:
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.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.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.Hướng sửa:
renderHTML: Chỉ xuất thuộc tính data-list-style-type. Không xuất inline style list-style-type cho các bullet chuẩn để CSS có thể tự do gán hình dáng xoay vòng theo cấp lồng. Chỉ xuất list-style-type: none riêng cho checkmark và dash để hiển thị ký tự đặc biệt qua ::marker.addInputRules: Sử dụng wrappingInputRule để bắt ký tự đầu dòng. Nếu người dùng gõ - sẽ tự động gán thuộc tính listStyleType: 'dash'.
// ...
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}`,
}
},
},
// ...
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' } }, }), ] },
Hướng sửa:
ul[data-list-style-type="X"] ul[data-list-style-type="disc"] để chỉ áp dụng phân cấp thông minh cho các thẻ lồng nhau có giá trị mặc định (disc), giữ nguyên các thẻ lồng nhau được USER gán kiểu dáng riêng biệt một cách cố ý..tiptap td > ul, etc.) với padding-left: 24px !important.
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;
}
/* Đị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; }
Hướng sửa:
BULLET_STYLE_DOCX_MAP từ cấu trúc ký tự phẳng thành bộ định nghĩa phân cấp 3 cấp độ BULLET_FAMILY_MAP.docOptions.numbering.config để lấy ký tự dấu đầu dòng động theo phép chia dư cấp lồng levels[i % 3].createList nhận thêm tham số parentBulletStyle để truyền thừa kế kiểu dáng cha xuống các danh sách con lồng sâu khi xuất file Word.
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' },
}
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' } ] }
1. Phân cấp Gia đình (Family) trong Editor:
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ấu trừ và khoảng trắng). Xác nhận danh sách dấu đầu dòng được tạo ra là dạng gạch ngang (–) chứ không phải hình tròn đặc (●).* (dấu sao và khoảng trắng). Xác nhận danh sách dạng tròn đặc (●) được kích hoạt.Tab để thụt dòng danh sách con.[Marker chính] -> ○ -> ■ tương ứng với họ phân cấp được khai báo.Shift + Tab để nhô dòng ra ngoài, xác nhận dấu đầu dòng quay trở về ký tự cấp cao hơn.24px và dấu bullet point nằm hoàn toàn bên trong đường viền của ô.