From ab7f7ee3c9eb3dbe3c14338acb31904431ef138e Mon Sep 17 00:00:00 2001 From: John McLear Date: Fri, 8 May 2026 18:29:36 +0100 Subject: [PATCH] feat: register heading tags as server-side block elements ep_headings2 already registers h1-h4 + code as block elements in the editor (aceRegisterBlockElements). It did NOT register them on the server-side content collector (ccRegisterBlockElements), so any HTML import path treated those tags as inline -- and adjacent

/

/ blocks merged into a single pad line. Fix: re-export the new ccRegisterBlockElements function from ep_plugin_helpers' lineAttribute factory (added in ether/ep_plugin_helpers#14) and wire it up via ep.json. This treats h1-h4 + code as block elements on the import side too, matching their editor behavior. Bumps the ep_plugin_helpers minimum to ^0.5.2 (the version with the new export). Test added covers the regression: a pad with adjacent

and

(no separator) survives a setHTML/getHTML round-trip with each heading on its own line and no merged 'AlphaBeta' content. Refs ether/etherpad#7568 -- this is the missing piece for the DOCX/HTML round-trip story landing in core. --- ep.json | 1 + package.json | 2 +- static/js/shared.js | 1 + static/tests/backend/specs/exportHTML.ts | 31 ++++++++++++++++++++++++ 4 files changed, 34 insertions(+), 1 deletion(-) diff --git a/ep.json b/ep.json index 76855994..98d12357 100644 --- a/ep.json +++ b/ep.json @@ -16,6 +16,7 @@ "eejsBlock_editbarMenuLeft": "ep_headings2/index", "collectContentPre": "ep_headings2/static/js/shared", "collectContentPost": "ep_headings2/static/js/shared", + "ccRegisterBlockElements": "ep_headings2/static/js/shared", "getLineHTMLForExport": "ep_headings2/index", "stylesForExport" : "ep_headings2/index" } diff --git a/package.json b/package.json index 507203fd..bcaf0850 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "url": "https://github.com/ether/ep_headings2/issues" }, "dependencies": { - "ep_plugin_helpers": "^0.3.2" + "ep_plugin_helpers": "^0.5.2" }, "devDependencies": { "eslint": "^8.57.1", diff --git a/static/js/shared.js b/static/js/shared.js index cd6f15fa..8e0b0653 100644 --- a/static/js/shared.js +++ b/static/js/shared.js @@ -8,3 +8,4 @@ const headings = lineAttribute({attr: 'heading', tags}); exports.collectContentPre = headings.collectContentPre; exports.collectContentPost = headings.collectContentPost; +exports.ccRegisterBlockElements = headings.ccRegisterBlockElements; diff --git a/static/tests/backend/specs/exportHTML.ts b/static/tests/backend/specs/exportHTML.ts index 6320db87..25b5bca9 100644 --- a/static/tests/backend/specs/exportHTML.ts +++ b/static/tests/backend/specs/exportHTML.ts @@ -121,4 +121,35 @@ describe('ep_headings2 - export headings to HTML', function () { } }); }); + + context('when pad has adjacent

and

with no separator', function () { + // Regression for ether/etherpad#7568 round-trip. Without server-side + // ccRegisterBlockElements registered for the heading tags, + // contentcollector treats

/

as inline and adjacent ones + // merge into a single pad line. + before(async function () { + html = () => buildHTML('

Alpha

Beta

'); + }); + + it('keeps each heading on its own line', function (done) { + const expected = /

\s*Alpha\s*<\/h1>[\s\S]*

\s*Beta\s*<\/h2>/; + generateJWTToken().then((token) => { + agent.get(getHTMLEndPointFor(padID)) + .set('Authorization', token) + .end((err, res) => { + if (err) return done(err); + const out = res.body.data.html; + if (out.search(expected) === -1) { + return done(new Error( + `Adjacent headings merged or missing in: ${out}`)); + } + if (out.search(/AlphaBeta/) !== -1) { + return done(new Error( + `Headings merged into one line: ${out}`)); + } + done(); + }); + }).catch(done); + }); + }); });