From 61d09421a6859906295504543e7f1c110953a23a Mon Sep 17 00:00:00 2001 From: dereklputnam Date: Mon, 12 Jan 2026 19:22:48 -0500 Subject: [PATCH] Add support for headings inside wrap blocks (#133) Modified processPostContent to find headings in two locations: 1. Direct descendants of body (existing behavior) 2. Inside .wrap blocks (new - for email filtering via [wrap=no-email]) This allows table of contents to include headings that are wrapped for email filtering purposes, which is a common use case in Discourse. The headings are combined and sorted by document order to maintain proper structure in the TOC. --- .../discourse/services/toc-processor.js | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/javascripts/discourse/services/toc-processor.js b/javascripts/discourse/services/toc-processor.js index 5c6b464..782d043 100644 --- a/javascripts/discourse/services/toc-processor.js +++ b/javascripts/discourse/services/toc-processor.js @@ -77,10 +77,21 @@ export default class TocProcessor extends Service { if (this.containsHeadings(content)) { const parsedPost = new DOMParser().parseFromString(content, "text/html"); - // direct descendants to avoid picking up headings in quotes - const headings = parsedPost.querySelectorAll( - "body > h1,body > h2,body > h3,body > h4,body > h5" - ); + // Find headings that are either: + // 1. Direct descendants of body (to avoid picking up headings in quotes) + // 2. Inside wrap blocks (which are used for email filtering with [wrap=no-email]) + const selector = + "body > :is(h1, h2, h3, h4, h5), body > :is(.wrap, .d-wrap) :is(h1, h2, h3, h4, h5)"; + + const allHeadings = parsedPost.querySelectorAll(selector); + + // Convert NodeList to Array and sort by document order + const headings = Array.from(allHeadings).sort((a, b) => { + // eslint-disable-next-line no-bitwise + return a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING + ? -1 + : 1; + }); if (headings.length < settings.TOC_min_heading) { this.setOverlayVisible(false);