Skip to content

Commit 44a3cff

Browse files
waynemsmithclaude
andcommitted
Fix list spacing in display and HTML generation (v1.0.2)
htmlToText: strip <p><br></p> spacers around lists, process <ul> as block to prevent source whitespace creating extra blank lines. markdownToHtml: skip spacer paragraphs before/after list blocks. Also fix two pre-existing typecheck errors in client.ts. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent d4c42fc commit 44a3cff

6 files changed

Lines changed: 46 additions & 11 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "fizzy-cli",
3-
"version": "1.0.1",
3+
"version": "1.0.2",
44
"description": "CLI for Fizzy kanban board management",
55
"type": "module",
66
"main": "src/index.ts",

src/client.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ export class FizzyClient {
8585
let nextUrl: string | null = path.startsWith('http') ? path : `${this.config.baseUrl}${path}`;
8686

8787
while (nextUrl) {
88-
const response = await this.getPaginated<T>(nextUrl);
88+
const response: PaginatedResponse<T> = await this.getPaginated<T>(nextUrl);
8989
results.push(...response.data);
9090
nextUrl = response.nextUrl;
9191
}
@@ -197,7 +197,7 @@ export class FizzyClient {
197197
const response = await fetch(url, {
198198
method: 'PUT',
199199
headers,
200-
body: file,
200+
body: file as unknown as BodyInit,
201201
});
202202

203203
if (!response.ok) {

src/utils/html.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ export function htmlToText(html: string | null | undefined): string {
1010

1111
let text = html;
1212

13+
// Strip <p><br></p> spacers immediately before and after lists
14+
text = text.replace(/<p><br><\/p>\s*(?=<[uo]l)/gi, '');
15+
text = text.replace(/(<\/[uo]l>)\s*<p><br><\/p>/gi, '$1');
16+
1317
// Add newlines before/after block elements
1418
text = text.replace(/<h[1-6][^>]*>/gi, '\n');
1519
text = text.replace(/<\/h[1-6]>/gi, '\n');
@@ -21,17 +25,20 @@ export function htmlToText(html: string | null | undefined): string {
2125
// Handle ordered lists with numbers
2226
text = text.replace(/<ol[^>]*>([\s\S]*?)<\/ol>/gi, (match, content) => {
2327
const items = content.match(/<li[^>]*>([\s\S]*?)<\/li>/gi) || [];
24-
return '\n' + items.map((item: string, i: number) => {
28+
return items.map((item: string, i: number) => {
2529
const inner = item.replace(/<li[^>]*>/i, '').replace(/<\/li>/i, '');
2630
return ` ${i + 1}. ${inner}`;
2731
}).join('\n') + '\n';
2832
});
2933

30-
// Unordered lists
31-
text = text.replace(/<ul[^>]*>/gi, '\n');
32-
text = text.replace(/<\/ul>/gi, '');
33-
text = text.replace(/<li[^>]*>/gi, ' * ');
34-
text = text.replace(/<\/li>/gi, '\n');
34+
// Unordered lists — process as a block to avoid extra blank lines
35+
text = text.replace(/<ul[^>]*>([\s\S]*?)<\/ul>/gi, (match, content) => {
36+
const items = content.match(/<li[^>]*>([\s\S]*?)<\/li>/gi) || [];
37+
return items.map((item: string) => {
38+
const inner = item.replace(/<li[^>]*>/i, '').replace(/<\/li>/i, '');
39+
return ` * ${inner}`;
40+
}).join('\n') + '\n';
41+
});
3542

3643
// Inline formatting - keep content
3744
text = text.replace(/<strong[^>]*>([\s\S]*?)<\/strong>/gi, '$1');

src/utils/markdown.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -278,8 +278,10 @@ function convertParagraphs(text: string): string {
278278
const current = htmlParts[i];
279279
const next = htmlParts[i + 1];
280280

281-
// Skip spacing around <hr> elements
282-
if (!current.startsWith('<hr') && !next.startsWith('<hr')) {
281+
// Skip spacing around <hr> elements and before/after lists
282+
if (!current.startsWith('<hr') && !next.startsWith('<hr') &&
283+
!next.startsWith('<ul') && !next.startsWith('<ol') &&
284+
!current.startsWith('<ul') && !current.startsWith('<ol')) {
283285
result.push('<p><br></p>');
284286
}
285287
}

tests/html.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,24 @@ describe('htmlToText', () => {
3030
expect(result).toContain('* Item 2');
3131
});
3232

33+
test('no blank line between heading and list', () => {
34+
const html = '<p><b><strong>Known gaps</strong></b></p><p><br></p><ul><li>Item 1</li><li>Item 2</li></ul>';
35+
const result = htmlToText(html);
36+
expect(result).toBe('Known gaps\n * Item 1\n * Item 2');
37+
});
38+
39+
test('no blank lines between ul items with source newlines', () => {
40+
const html = '<ul>\n<li>A</li>\n<li>B</li>\n</ul>';
41+
const result = htmlToText(html);
42+
expect(result).toBe('* A\n * B');
43+
});
44+
45+
test('strips spacer after closing list tag', () => {
46+
const html = '<ul><li>X</li></ul><p><br></p><p>Next</p>';
47+
const result = htmlToText(html);
48+
expect(result).toBe('* X\n\nNext'); // one blank line between list and next paragraph
49+
});
50+
3351
test('handles ordered lists with numbers', () => {
3452
const html = '<ol><li>First</li><li>Second</li></ol>';
3553
const result = htmlToText(html);

tests/markdown.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,14 @@ describe('markdownToHtml', () => {
172172
expect(result).toContain('<p>Line two</p>');
173173
});
174174

175+
test('no spacer paragraph before or after lists', () => {
176+
const md = 'Heading:\n\n- Item 1\n- Item 2\n\nNext paragraph';
177+
const result = markdownToHtml(md);
178+
// No <p><br></p> between heading and list or between list and next para
179+
expect(result).not.toMatch(/<p><br><\/p><ul>/);
180+
expect(result).not.toMatch(/<\/ul><p><br><\/p>/);
181+
});
182+
175183
test('blank lines between list items produce one list', () => {
176184
const md = '- Item 1\n\n- Item 2\n\n- Item 3';
177185
const result = markdownToHtml(md);

0 commit comments

Comments
 (0)