11// @flow
22//-----------------------------------------------------------------------------
33// Remove empty blocks functionality for Tidy plugin
4- // Last updated 2025-09-24 for v1.0 .0 by @jgclark
4+ // Last updated 2025-11-13 for v1.16 .0 by @jgclark
55//-----------------------------------------------------------------------------
66
77import pluginJson from '../plugin.json'
@@ -19,28 +19,25 @@ import { showMessage } from '@helpers/userInput'
1919 *
2020 * @param {TNote } note - The note to process
2121 * @param {boolean } preserveHeadings - Whether to preserve heading structure (skip removing empty headings)
22- * @returns {boolean } - Whether any changes were made
22+ * @returns {number } - Number of changes made
2323 *
2424 * @example
2525 * When preserveHeadings = false (default):
2626 * - Removes: "- " (empty list item)
2727 * - Removes: "> " (empty quote)
2828 * - Removes: "# " (empty heading)
29+ * - Removes: "* " (empty task)
30+ * - Removes: "+ " (empty checklist)
2931 * - Preserves: "- Some content" (list with content)
3032 * - Preserves: "> Some quote" (quote with content)
3133 * - Preserves: "# Some heading" (heading with content)
3234 *
3335 * When preserveHeadings = true:
34- * - Removes: "- " (empty list item)
35- * - Removes: "> " (empty quote)
3636 * - Preserves: "# " (empty heading - structure preserved)
37- * - Preserves: "- Some content" (list with content)
38- * - Preserves: "> Some quote" (quote with content)
39- * - Preserves: "# Some heading" (heading with content)
4037 */
41- function removeEmptyListItemsAndHeadings ( note : TNote , preserveHeadings : boolean = false ) : boolean {
38+ function removeEmptyParagraphs ( note : TNote , preserveHeadings : boolean = false ) : number {
4239 const paragraphs = note . paragraphs
43- let changesMade = false
40+ let numChangesMade = 0
4441
4542 for ( const para of paragraphs ) {
4643 const trimmedContent = para . content . trim ( )
@@ -56,18 +53,17 @@ function removeEmptyListItemsAndHeadings(note: TNote, preserveHeadings: boolean
5653 // Remove empty headings when not preserving structure
5754 shouldRemove = isEmptyContent
5855 } else {
59- // For list and quote items , remove if empty
60- shouldRemove = isEmptyContent && [ 'list' , 'quote' ] . includes ( para . type )
56+ // For most other paragraph types , remove if empty
57+ shouldRemove = isEmptyContent && [ 'open' , 'checklist' , 'scheduled' , 'checklistScheduled' , ' list', 'quote' ] . includes ( para . type )
6158 }
62-
6359 if ( shouldRemove ) {
6460 logDebug ( 'removeEmptyElements' , `Removing empty ${ para . type } para on line ${ String ( para . lineIndex ) } ` )
6561 note . removeParagraph ( para )
66- changesMade = true
62+ numChangesMade ++
6763 }
6864 }
69-
70- return changesMade
65+ logInfo ( 'removeEmptyElements' , `Removed ${ String ( numChangesMade ) } empty paras` )
66+ return numChangesMade
7167}
7268
7369/**
@@ -85,7 +81,7 @@ function removeEmptyListItemsAndHeadings(note: TNote, preserveHeadings: boolean
8581 * - It has no content in its section AND no subheadings with content
8682 *
8783 * @param {TNote } note - The note to process
88- * @returns {boolean } - Whether any changes were made
84+ * @returns {number } - Number of changes made
8985 *
9086 * @example
9187 * // This heading is PRESERVED because its subheading has content:
@@ -98,10 +94,10 @@ function removeEmptyListItemsAndHeadings(note: TNote, preserveHeadings: boolean
9894 * // ## Empty Subsection
9995 * // (no content)
10096 */
101- function removeEmptySections ( note : TNote ) : boolean {
97+ function removeEmptySections ( note : TNote ) : number {
10298 const paragraphs = note . paragraphs
10399 const titleParas = paragraphs . filter ( ( para ) => para . type === 'title' )
104- let changesMade = false
100+ let numChangesMade = 0
105101
106102 // First, mark which headings have content (including subheadings)
107103 const headingHasContent = new Map < number , boolean > ( )
@@ -151,11 +147,11 @@ function removeEmptySections(note: TNote): boolean {
151147 if ( ! hasContent ) {
152148 logDebug ( 'removeEmptyElements' , `Removing heading para on line ${ String ( para . lineIndex ) } (no content and no subheadings with content)` )
153149 note . removeParagraph ( para )
154- changesMade = true
150+ numChangesMade ++
155151 }
156152 }
157-
158- return changesMade
153+ logInfo ( 'removeEmptyElements' , `Removed ${ String ( numChangesMade ) } empty headings` )
154+ return numChangesMade
159155}
160156
161157/**
@@ -168,7 +164,7 @@ function removeEmptySections(note: TNote): boolean {
168164 * @param {TNote } note - The note to process
169165 * @param {boolean } stripAllEmptyLines - Whether to remove all empty lines or just consecutive ones
170166 * @param {boolean } preserveHeadings - Whether to preserve heading structure (skip removing empty headings)
171- * @returns {boolean } - Whether any changes were made
167+ * @returns {number } - Number of changes made
172168 *
173169 * @example
174170 * // When stripAllEmptyLines = false (default):
@@ -179,9 +175,9 @@ function removeEmptySections(note: TNote): boolean {
179175 * // Before: "Line 1\n\n\n\nLine 2"
180176 * // After: "Line 1\nLine 2" (removes all empty lines)
181177 */
182- function removeConsecutiveEmptyLines ( note : TNote , stripAllEmptyLines : boolean , preserveHeadings : boolean = false ) : boolean {
178+ function removeConsecutiveEmptyLines ( note : TNote , stripAllEmptyLines : boolean , preserveHeadings : boolean = false ) : number {
183179 const paragraphs = note . paragraphs
184- let changesMade = false
180+ let numChangesMade = 0
185181
186182 if ( stripAllEmptyLines ) {
187183 // Delete *all* empty paras, but preserve headings if preserveHeadings is true
@@ -193,7 +189,7 @@ function removeConsecutiveEmptyLines(note: TNote, stripAllEmptyLines: boolean, p
193189 for ( const para of emptyParasToRemove ) {
194190 logDebug ( 'removeEmptyElements' , `Removing empty para on line ${ String ( para . lineIndex ) } ` )
195191 note . removeParagraph ( para )
196- changesMade = true
192+ numChangesMade ++
197193 }
198194 } else {
199195 // Delete multiple consecutive empty paras, leaving only one empty line
@@ -223,15 +219,17 @@ function removeConsecutiveEmptyLines(note: TNote, stripAllEmptyLines: boolean, p
223219 }
224220
225221 // Remove the marked paragraphs in reverse order to avoid index shifting
226- for ( let i = parasToRemove . length - 1 ; i >= 0 ; i -- ) {
227- const para = parasToRemove [ i ]
228- logDebug ( 'removeEmptyElements' , `Removing empty para on line ${ String ( para . lineIndex ) } ` )
229- note . removeParagraph ( para )
230- changesMade = true
222+ if ( parasToRemove . length > 0 ) {
223+ for ( let i = parasToRemove . length - 1 ; i >= 0 ; i -- ) {
224+ const para = parasToRemove [ i ]
225+ logDebug ( 'removeEmptyElements' , `Removing empty para on line ${ String ( para . lineIndex ) } ` )
226+ note . removeParagraph ( para )
227+ numChangesMade ++
228+ }
231229 }
232230 }
233-
234- return changesMade
231+ logInfo ( 'removeEmptyElements' , `Removed ${ String ( numChangesMade ) } empty paras` )
232+ return numChangesMade
235233}
236234
237235/**
@@ -300,14 +298,14 @@ export async function removeEmptyElements(filenameIn: string = 'Editor', stripAl
300298 logDebug ( pluginJson , `preserveHeadingStructure: ${ String ( preserveHeadingStructure ) } typeof=${ typeof preserveHeadingStructure } / preserveHeadings: ${ String ( preserveHeadings ) } ` )
301299
302300 // Execute the phases of cleanup
303- const changes1 = removeEmptyListItemsAndHeadings ( note , preserveHeadings )
301+ const changes1 = removeEmptyParagraphs ( note , preserveHeadings )
304302 const changes2 = preserveHeadings ? false : removeEmptySections ( note )
305303 const changes3 = removeConsecutiveEmptyLines ( note , Boolean ( stripAllEmptyLines ) , preserveHeadings )
306304
307- const changesMade = changes1 || changes2 || changes3
305+ const numChangesMade = changes1 + changes2 + changes3
308306
309- if ( changesMade ) {
310- logInfo ( 'removeEmptyElements' , `Removed empty elements from note '${ displayTitle ( note ) } '` )
307+ if ( numChangesMade > 0 ) {
308+ logInfo ( 'removeEmptyElements' , `Removed ${ String ( numChangesMade ) } empty elements from note '${ displayTitle ( note ) } '` )
311309 // Save Editor (if that's where we're working)
312310 if ( workingInEditor ) {
313311 await Editor . save ( )
0 commit comments