russell.ballestrini.net/pelican-upstream-patch.diff

142 lines
5.3 KiB
Diff

diff --git a/pelican/contents.py b/pelican/contents.py
index a06a1a6a..82da413a 100644
--- a/pelican/contents.py
+++ b/pelican/contents.py
@@ -27,6 +27,7 @@ from pelican.utils import (
sanitised_join,
set_date_tzinfo,
slugify,
+ strip_toc_elements_from_html,
truncate_html_paragraphs,
truncate_html_words,
)
@@ -446,14 +447,20 @@ class Content:
content = truncate_html_paragraphs(self.content, max_paragraphs)
if self.settings["SUMMARY_MAX_LENGTH"] is None:
- return content
-
- return truncate_html_words(
+ summary = content
+ else:
+ summary = truncate_html_words(
content,
self.settings["SUMMARY_MAX_LENGTH"],
self.settings["SUMMARY_END_SUFFIX"],
)
+ # Strip TOC elements that would contain broken links in summary context
+ # TOC anchors only work in full article view, not in summaries/excerpts
+ summary = strip_toc_elements_from_html(summary)
+
+ return summary
+
@property
def summary(self) -> str:
return self.get_summary(self.get_siteurl())
diff --git a/pelican/tests/test_utils.py b/pelican/tests/test_utils.py
index 95bf197a..f0b9c32b 100644
--- a/pelican/tests/test_utils.py
+++ b/pelican/tests/test_utils.py
@@ -420,6 +420,60 @@ class TestUtils(LoggedTestCase):
self.assertEqual(utils.truncate_html_paragraphs(three, 3), three)
self.assertEqual(utils.truncate_html_paragraphs(three, 4), three)
+ def test_strip_toc_elements_from_html(self):
+ # Test removing TOC div with various class names
+ html_with_toc = (
+ '<div class="contents topic" id="table-of-contents">'
+ '<p class="topic-title">Table of Contents</p>'
+ '<ul><li><a href="#section1">Section 1</a></li></ul>'
+ '</div>'
+ '<p>Some content here</p>'
+ )
+ result = utils.strip_toc_elements_from_html(html_with_toc)
+ self.assertNotIn('<div class="contents', result)
+ self.assertIn('<p>Some content here</p>', result)
+
+ # Test removing toc-backref anchors while preserving heading text
+ html_with_backref = (
+ '<h2><a class="toc-backref" href="#id1">Section Heading</a></h2>'
+ '<p>Some content</p>'
+ )
+ result = utils.strip_toc_elements_from_html(html_with_backref)
+ self.assertNotIn('toc-backref', result)
+ self.assertNotIn('<a class=', result)
+ self.assertIn('Section Heading', result)
+ self.assertIn('<h2>Section Heading</h2>', result)
+
+ # Test combined - remove both TOC div and backrefs
+ html_combined = (
+ '<div class="contents">'
+ '<p>TOC here</p>'
+ '</div>'
+ '<h2><a class="toc-backref" href="#id1">the design</a></h2>'
+ '<p>Article content</p>'
+ '<h2><a class="toc-backref" href="#id2">key features</a></h2>'
+ '<p>More content</p>'
+ )
+ result = utils.strip_toc_elements_from_html(html_combined)
+ self.assertNotIn('<div class="contents', result)
+ self.assertNotIn('toc-backref', result)
+ self.assertIn('the design', result)
+ self.assertIn('key features', result)
+ self.assertIn('<p>Article content</p>', result)
+
+ # Test empty input
+ self.assertEqual(utils.strip_toc_elements_from_html(''), '')
+
+ # Test HTML without TOC elements (should be unchanged)
+ plain_html = '<p>Just some plain content</p>'
+ self.assertEqual(utils.strip_toc_elements_from_html(plain_html), plain_html)
+
+ # Test case-insensitive matching
+ html_mixed_case = '<div CLASS="CONTENTS"><p>TOC</p></div><p>Content</p>'
+ result = utils.strip_toc_elements_from_html(html_mixed_case)
+ self.assertNotIn('CONTENTS', result)
+ self.assertIn('<p>Content</p>', result)
+
def test_process_translations(self):
fr_articles = []
en_articles = []
diff --git a/pelican/utils.py b/pelican/utils.py
index eb281ce9..50213bc0 100644
--- a/pelican/utils.py
+++ b/pelican/utils.py
@@ -643,6 +643,36 @@ def truncate_html_paragraphs(s, count):
return "".join(paragraphs)
+def strip_toc_elements_from_html(html: str) -> str:
+ """Strip table of contents elements from HTML summaries.
+
+ Removes TOC divs and toc-backref anchor links that would be broken
+ when article summaries are displayed outside of the full article context
+ (e.g., on homepage, in RSS feeds).
+
+ :param html: HTML content to process
+ :return: Cleaned HTML with TOC elements removed
+ """
+ # Remove the entire <div class="contents"> ... </div> block
+ html = re.sub(
+ r'<div\s+class="contents[^"]*"[^>]*>.*?</div>',
+ '',
+ html,
+ flags=re.DOTALL | re.IGNORECASE
+ )
+
+ # Remove anchor links from headings (e.g., <a class="toc-backref" href="#id1">text</a>)
+ # These links point to anchors that don't exist in summary context
+ html = re.sub(
+ r'<a[^>]*class="[^"]*toc-backref[^"]*"[^>]*>(.*?)</a>',
+ r'\1',
+ html,
+ flags=re.DOTALL | re.IGNORECASE
+ )
+
+ return html
+
+
def process_translations(
content_list: list[Content],
translation_id: str | Collection[str] | None = None,