From dce5ba0f83ef592b377fde983ce275fee96bd674 Mon Sep 17 00:00:00 2001 From: pmario Date: Sun, 12 Jul 2026 23:37:57 +0200 Subject: [PATCH] Let block terminators claim the preceding newline Consistency fix: every block terminator now claims the newline before its closing marker, so paragraph text is identical with and without a blank line, and quoteblock, styleblock, html and conditional behave like codeblock, typedblock and macrodef. Fixes #9907. * Anchor the quoteblock and styleblock terminators from the newline * Prefix the html and conditional terminators with an optional newline; a mandatory anchor would break legal mid-line close tags * Leave inline paths unchanged: a newline in an inline run is real content * Correct the (:?^| typo to (?:^| in the fnprocdef end marker regexp Compatibility: these paragraph text nodes no longer end with an invisible newline and their end positions shift. Rendered output is unchanged; practical risk is low. --- .../parsers/wikiparser/rules/conditional.js | 6 +- .../parsers/wikiparser/rules/fnprocdef.js | 2 +- core/modules/parsers/wikiparser/rules/html.js | 4 +- .../parsers/wikiparser/rules/quoteblock.js | 4 +- .../parsers/wikiparser/rules/styleblock.js | 4 +- .../tests/test-wikitext-block-terminators.js | 56 +++++++++++++++++++ .../5.5.0/#9907-block-terminator-newline.tid | 13 +++++ .../5.5.0/#9907-impact-text-end-positions.tid | 9 +++ 8 files changed, 92 insertions(+), 6 deletions(-) create mode 100644 editions/test/tiddlers/tests/test-wikitext-block-terminators.js create mode 100644 editions/tw5.com/tiddlers/releasenotes/5.5.0/#9907-block-terminator-newline.tid create mode 100644 editions/tw5.com/tiddlers/releasenotes/5.5.0/#9907-impact-text-end-positions.tid diff --git a/core/modules/parsers/wikiparser/rules/conditional.js b/core/modules/parsers/wikiparser/rules/conditional.js index b3de3fb5305..ec2fe2c41a3 100644 --- a/core/modules/parsers/wikiparser/rules/conditional.js +++ b/core/modules/parsers/wikiparser/rules/conditional.js @@ -79,7 +79,9 @@ exports.parseIfClause = function(filterCondition) { var reEndString = "\\<\\%\\s*(endif)\\s*\\%\\>|\\<\\%\\s*(else)\\s*\\%\\>|\\<\\%\\s*(elseif)\\s+([\\s\\S]+?)\\%\\>", ex; if(hasLineBreak) { - ex = this.parser.parseBlocksTerminatedExtended(reEndString); + // Let the terminator claim the newline before the marker, or it is + // swallowed into the preceding paragraph text node + ex = this.parser.parseBlocksTerminatedExtended("(?:\\r?\\n)?(?:" + reEndString + ")"); } else { var reEnd = new RegExp(reEndString,"mg"); ex = this.parser.parseInlineRunTerminatedExtended(reEnd,{eatTerminator: true}); @@ -97,7 +99,7 @@ exports.parseIfClause = function(filterCondition) { var reEndString = "\\<\\%\\s*(endif)\\s*\\%\\>", ex; if(hasLineBreak) { - ex = this.parser.parseBlocksTerminatedExtended(reEndString); + ex = this.parser.parseBlocksTerminatedExtended("(?:\\r?\\n)?(?:" + reEndString + ")"); } else { var reEnd = new RegExp(reEndString,"mg"); ex = this.parser.parseInlineRunTerminatedExtended(reEnd,{eatTerminator: true}); diff --git a/core/modules/parsers/wikiparser/rules/fnprocdef.js b/core/modules/parsers/wikiparser/rules/fnprocdef.js index 01184497a33..ca039491a4e 100644 --- a/core/modules/parsers/wikiparser/rules/fnprocdef.js +++ b/core/modules/parsers/wikiparser/rules/fnprocdef.js @@ -50,7 +50,7 @@ exports.parse = function() { var reEnd; if(this.match[5]) { // If so, it is a multiline definition and the end of the body is marked with \end - reEnd = new RegExp("((:?^|\\r?\\n)[^\\S\\n\\r]*\\\\end[^\\S\\n\\r]*(?:" + $tw.utils.escapeRegExp(this.match[2]) + ")?\\s*?(?:$|\\r?\\n))","mg"); + reEnd = new RegExp("((?:^|\\r?\\n)[^\\S\\n\\r]*\\\\end[^\\S\\n\\r]*(?:" + $tw.utils.escapeRegExp(this.match[2]) + ")?\\s*?(?:$|\\r?\\n))","mg"); } else { // Otherwise, the end of the definition is marked by the end of the line reEnd = /($|\r?\n)/mg; diff --git a/core/modules/parsers/wikiparser/rules/html.js b/core/modules/parsers/wikiparser/rules/html.js index 2d1a42a0b11..340070cb6a7 100644 --- a/core/modules/parsers/wikiparser/rules/html.js +++ b/core/modules/parsers/wikiparser/rules/html.js @@ -56,7 +56,9 @@ exports.parse = function() { if(!tag.isSelfClosing && $tw.config.htmlVoidElements.indexOf(tag.tag) === -1) { var reEndString = ""; if(hasLineBreak) { - tag.children = this.parser.parseBlocks(reEndString); + // Let the terminator claim the newline before the close tag, or it + // is swallowed into the preceding paragraph text node + tag.children = this.parser.parseBlocks("(?:\\r?\\n)?" + reEndString); } else { var reEnd = new RegExp("(" + reEndString + ")","mg"); tag.children = this.parser.parseInlineRun(reEnd,{eatTerminator: true}); diff --git a/core/modules/parsers/wikiparser/rules/quoteblock.js b/core/modules/parsers/wikiparser/rules/quoteblock.js index be8746de425..058732369fe 100644 --- a/core/modules/parsers/wikiparser/rules/quoteblock.js +++ b/core/modules/parsers/wikiparser/rules/quoteblock.js @@ -21,7 +21,9 @@ exports.init = function(parser) { exports.parse = function() { var classes = ["tc-quote"]; // Get all the details of the match - var reEndString = "^\\s*" + this.match[1] + "(?!<)"; + // Anchor from the newline, not the line start: with "^\s*" the newline + // before the marker stays inside the preceding paragraph text node + var reEndString = "(?:^|\\r?\\n)\\s*" + this.match[1] + "(?!<)"; // Move past the \n\nfoo\n")[0].children[0].children[0].text).toBe("foo"); + }); + + it("should not swallow the newline before a conditional terminator", function() { + // the block body sits in the synthesized $list-template + expect(parse("<%if [[x]]%>\n\nfoo\n<%endif%>")[0].children[0].children[0].children[0].text).toBe("foo"); + }); + +}); diff --git a/editions/tw5.com/tiddlers/releasenotes/5.5.0/#9907-block-terminator-newline.tid b/editions/tw5.com/tiddlers/releasenotes/5.5.0/#9907-block-terminator-newline.tid new file mode 100644 index 00000000000..bfd8454d026 --- /dev/null +++ b/editions/tw5.com/tiddlers/releasenotes/5.5.0/#9907-block-terminator-newline.tid @@ -0,0 +1,13 @@ +change-category: developer +change-type: bugfix +created: 20260712232208000 +description: Paragraph text before a block closing marker no longer carries a trailing newline +github-contributors: pmario +github-links: https://github.com/TiddlyWiki/TiddlyWiki5/issues/9907 +modified: 20260712232208000 +release: 5.5.0 +tags: $:/tags/ChangeNote +title: $:/changenotes/5.5.0/#9907 +type: text/vnd.tiddlywiki + +Block terminators now claim the single newline before their closing marker, so the preceding paragraph text node ends without it, exactly as it does with a blank line before the marker. For consistency, `quoteblock`, `styleblock`, HTML block bodies and conditional block clauses now behave like `codeblock`, `typedblock` and `\procedure` definitions. Rendered output is unchanged. diff --git a/editions/tw5.com/tiddlers/releasenotes/5.5.0/#9907-impact-text-end-positions.tid b/editions/tw5.com/tiddlers/releasenotes/5.5.0/#9907-impact-text-end-positions.tid new file mode 100644 index 00000000000..4251e931fb9 --- /dev/null +++ b/editions/tw5.com/tiddlers/releasenotes/5.5.0/#9907-impact-text-end-positions.tid @@ -0,0 +1,9 @@ +title: $:/changenotes/5.5.0/#9907/impacts/text-end-positions +changenote: $:/changenotes/5.5.0/#9907 +created: 20260712232208000 +modified: 20260712232208000 +tags: $:/tags/ImpactNote +description: Text and end positions of paragraph nodes before block terminators have changed +impact-type: compatibility-break + +Paragraph text nodes inside quote, style, HTML and conditional blocks no longer end with an invisible newline; their `end` positions shrink accordingly. All block constructs now yield the same paragraph text, as the blank-line form and `codeblock`, `typedblock` and `\procedure` already did. Rendered output is unchanged.