50 |
50 |
}
|
51 |
51 |
|
52 |
52 |
class CommonMarkListFormatter {
|
53 |
|
format(line) {
|
54 |
|
// Match list items in CommonMark syntax.
|
55 |
|
// Captures either an ordered list (e.g., '1. ' or '2) ') or an unordered list (e.g., '* ', '- ', '+ ').
|
56 |
|
// The regex structure:
|
57 |
|
// ^(\s*) → leading whitespace
|
58 |
|
// (?:(\d+)([.)]) → an ordered list marker: number followed by '.' or ')'
|
59 |
|
// |([*+\-]) → OR an unordered list marker: '*', '+', or '-'
|
60 |
|
// (.*)$ → the actual list item content
|
61 |
|
//
|
62 |
|
// Examples:
|
63 |
|
// '2. ordered text' → indent='', number='2', delimiter='.', bullet=undefined, content='ordered text'
|
64 |
|
// ' 3) nested ordered text' → indent=' ', number='3', delimiter=')', bullet=undefined, content='nested ordered text'
|
65 |
|
// '* unordered text' → indent='', number=undefined, delimiter=undefined, bullet='*', content='unordered text'
|
66 |
|
// '+ unordered text' → indent='', number=undefined, delimiter=undefined, bullet='+', content='unordered text'
|
67 |
|
// ' - nested unordered text' → indent=' ', number=undefined, delimiter=undefined, bullet='-', content='nested unordered text'
|
68 |
|
const match = line.match(/^(\s*)(?:(\d+)([.)])|([*+\-])) (.*)$/)
|
69 |
|
if (!match) return null
|
|
53 |
// Example: ' * text' → indent=' ', bullet='*', content='text' (or '+' or '-')
|
|
54 |
#bulletItemPattern = /^(?<indent>\s*)(?<bullet>[*+\-]) (?<content>.*)$/;
|
|
55 |
// Example: ' 1. text' → indent=' ', num='1', delimiter='.', content='text' (or ')')
|
|
56 |
#orderedItemPattern = /^(?<indent>\s*)(?<num>\d+)(?<delimiter>[.)]) (?<content>.*)$/;
|
|
57 |
// Example: '[ ] Task' → taskContent='Task'
|
|
58 |
// '[x] Task' → taskContent='Task'
|
|
59 |
#taskAtStartPattern = /^\[[ x]\] (?<taskContent>.*)$/;
|
70 |
60 |
|
71 |
|
const indent = match[1]
|
72 |
|
const number = match[2]
|
73 |
|
const delimiter = match[3]
|
74 |
|
const bullet = match[4]
|
75 |
|
const content = match[5]
|
76 |
|
|
77 |
|
if (content === '') {
|
78 |
|
return { action: 'remove' }
|
|
61 |
format(line) {
|
|
62 |
const bulletMatch = line.match(this.#bulletItemPattern);
|
|
63 |
if (bulletMatch) {
|
|
64 |
return (
|
|
65 |
this.#formatBulletTask(bulletMatch.groups) ||
|
|
66 |
this.#formatBulletList(bulletMatch.groups)
|
|
67 |
);
|
79 |
68 |
}
|
80 |
69 |
|
81 |
|
if (number) {
|
82 |
|
const nextNumber = parseInt(number, 10) + 1
|
83 |
|
return { action: 'insert', text: `${indent}${nextNumber}${delimiter} ` }
|
84 |
|
} else {
|
85 |
|
return { action: 'insert', text: `${indent}${bullet} ` }
|
|
70 |
const orderedMatch = line.match(this.#orderedItemPattern);
|
|
71 |
if (orderedMatch) {
|
|
72 |
return (
|
|
73 |
this.#formatOrderedTask(orderedMatch.groups) ||
|
|
74 |
this.#formatOrderedList(orderedMatch.groups)
|
|
75 |
);
|
86 |
76 |
}
|
87 |
77 |
}
|
|
78 |
|
|
79 |
// '- [ ] Task' or '* [ ] Task' or '+ [ ] Task'
|
|
80 |
#formatBulletTask({ indent, bullet, content }) {
|
|
81 |
const m = content.match(this.#taskAtStartPattern);
|
|
82 |
if (!m) return null;
|
|
83 |
const taskContent = m.groups.taskContent;
|
|
84 |
|
|
85 |
return taskContent === ''
|
|
86 |
? { action: 'remove' }
|
|
87 |
: { action: 'insert', text: `${indent}${bullet} [ ] ` };
|
|
88 |
}
|
|
89 |
|
|
90 |
// '- Item' or '* Item' or '+ Item'
|
|
91 |
#formatBulletList({ indent, bullet, content }) {
|
|
92 |
return content === ''
|
|
93 |
? { action: 'remove' }
|
|
94 |
: { action: 'insert', text: `${indent}${bullet} ` };
|
|
95 |
}
|
|
96 |
|
|
97 |
// '1. [ ] Task' or '1) [ ] Task'
|
|
98 |
#formatOrderedTask({ indent, num, delimiter, content }) {
|
|
99 |
const m = content.match(this.#taskAtStartPattern);
|
|
100 |
if (!m) return null;
|
|
101 |
const taskContent = m.groups.taskContent;
|
|
102 |
|
|
103 |
const next = `${Number(num) + 1}${delimiter}`;
|
|
104 |
return taskContent === ''
|
|
105 |
? { action: 'remove' }
|
|
106 |
: { action: 'insert', text: `${indent}${next} [ ] ` };
|
|
107 |
}
|
|
108 |
|
|
109 |
// '1. Item' or '1) Item'
|
|
110 |
#formatOrderedList({ indent, num, delimiter, content }) {
|
|
111 |
const next = `${Number(num) + 1}${delimiter}`;
|
|
112 |
return content === ''
|
|
113 |
? { action: 'remove' }
|
|
114 |
: { action: 'insert', text: `${indent}${next} ` };
|
|
115 |
}
|
88 |
116 |
}
|
89 |
117 |
|
90 |
118 |
class TextileListFormatter {
|
91 |
119 |
format(line) {
|
92 |
|
// Match list items in Textile syntax.
|
93 |
|
// Captures either an ordered list (using '#') or an unordered list (using '*').
|
94 |
|
// The regex structure:
|
95 |
|
// ^([*#]+) → one or more list markers: '*' for unordered, '#' for ordered
|
96 |
|
// (.*)$ → the actual list item content
|
97 |
|
//
|
98 |
120 |
// Examples:
|
99 |
121 |
// '# ordered text' → marker='#', content='ordered text'
|
100 |
122 |
// '## nested ordered text' → marker='##', content='nested ordered text'
|
101 |
123 |
// '* unordered text' → marker='*', content='unordered text'
|
102 |
124 |
// '** nested unordered text' → marker='**', content='nested unordered text'
|
103 |
|
const match = line.match(/^([*#]+) (.*)$/)
|
|
125 |
const match = line.match(/^(?<marker>[*#]+) (?<content>.*)$/);
|
104 |
126 |
if (!match) return null
|
105 |
127 |
|
106 |
|
const marker = match[1]
|
107 |
|
const content = match[2]
|
108 |
|
|
109 |
|
if (content === '') {
|
110 |
|
return { action: 'remove' }
|
111 |
|
}
|
112 |
|
|
113 |
|
return { action: 'insert', text: `${marker} ` }
|
|
128 |
const { marker, content } = match.groups;
|
|
129 |
return content === ''
|
|
130 |
? { action: 'remove' }
|
|
131 |
: { action: 'insert', text: `${marker} ` };
|
114 |
132 |
}
|
115 |
133 |
}
|
116 |
134 |
|