fix: stop tripling thread titles in exports; convert non-markdown node data
Three fixes to thread/namespace export:
1. Nodes authored in non-markdown source_format (rst, mediawiki, latex, ...)
had their raw source dumped directly into the markdown renderer. A RST
thread exported as .md therefore yielded RST, not markdown. New
_node_data_as_markdown helper converts node.data through pandoc when
source_format != markdown, with a raw-source fallback on pandoc failure.
2. node_tree_to_markdown prepended '# {root_node.title}' on top of data
that already carries its own H1 (either native markdown or a converted
RST underline heading). Drop the prepend — the data owns the title.
3. convert() passed --metadata title=X which makes pandoc render a visible
title-block in HTML/PDF above the body. Combined with (2) and the data's
own H1 this showed the title three times in exported HTML/PDF. For
html-family outputs (and pdf via wkhtmltopdf) switch to -V pagetitle=X
so only the <title> tag gets populated; other formats still use the
proper --metadata title=X for real document metadata.
This commit is contained in:
parent
2fd71e9029
commit
8b6dac4dd7
3 changed files with 48 additions and 12 deletions
|
|
@ -206,7 +206,15 @@ def convert(source, from_format="markdown", to_format="html5", title=None, stand
|
|||
cmd.append("--standalone")
|
||||
|
||||
if title:
|
||||
cmd.extend(["--metadata", "title={}".format(title)])
|
||||
# For HTML-family outputs (and PDF, which we render through HTML via
|
||||
# wkhtmltopdf), set the template's pagetitle so we get <title> without
|
||||
# an extra title-block in the body — the rendered document already
|
||||
# carries its own H1 from the thread data. For every other format,
|
||||
# set the proper document metadata.
|
||||
if to_format in ("html", "html5", "html4", "chunkedhtml", "pdf"):
|
||||
cmd.extend(["-V", "pagetitle={}".format(title)])
|
||||
else:
|
||||
cmd.extend(["--metadata", "title={}".format(title)])
|
||||
|
||||
# PDF needs explicit engine since no pdflatex.
|
||||
if to_format == "pdf":
|
||||
|
|
@ -240,6 +248,33 @@ def convert(source, from_format="markdown", to_format="html5", title=None, stand
|
|||
return result.stdout
|
||||
|
||||
|
||||
def _node_data_as_markdown(node):
|
||||
"""Return node.data rendered as markdown.
|
||||
|
||||
Nodes authored in non-markdown source_formats (rst, mediawiki, latex, html,
|
||||
...) keep their raw source in .data. For export we need real markdown, so
|
||||
convert through pandoc when needed. A pandoc failure falls back to the raw
|
||||
source — better a rough dump than an empty export.
|
||||
"""
|
||||
if not node.data:
|
||||
return ""
|
||||
src_fmt = (getattr(node, "source_format", None) or "markdown").lower()
|
||||
if src_fmt in ("markdown", "gfm", "commonmark", "commonmark_x"):
|
||||
return node.data
|
||||
try:
|
||||
return convert(
|
||||
node.data, from_format=src_fmt, to_format="markdown",
|
||||
standalone=False,
|
||||
).rstrip()
|
||||
except Exception:
|
||||
log.exception(
|
||||
"Pandoc failed converting node %s from %s to markdown; "
|
||||
"falling back to raw source",
|
||||
node.id, src_fmt,
|
||||
)
|
||||
return node.data
|
||||
|
||||
|
||||
def node_tree_to_markdown(root_node, nodes, include_root=True):
|
||||
"""Render a node tree as a nested markdown document.
|
||||
|
||||
|
|
@ -271,12 +306,11 @@ def node_tree_to_markdown(root_node, nodes, include_root=True):
|
|||
|
||||
lines = []
|
||||
|
||||
if include_root and root_node.title:
|
||||
lines.append("# {}".format(root_node.title))
|
||||
lines.append("")
|
||||
|
||||
# Thread bodies already carry their own H1 (or RST ==== underline that
|
||||
# becomes H1 after conversion). Don't prepend another `# {title}` — it
|
||||
# triples the title when pandoc then also adds a title-block in HTML/PDF.
|
||||
if include_root and root_node.data:
|
||||
lines.append(root_node.data)
|
||||
lines.append(_node_data_as_markdown(root_node))
|
||||
lines.append("")
|
||||
|
||||
def _render_children(parent_id, depth):
|
||||
|
|
@ -290,7 +324,7 @@ def node_tree_to_markdown(root_node, nodes, include_root=True):
|
|||
lines.append("{} {} — {}".format("#" * heading_level, author, date))
|
||||
lines.append("")
|
||||
if child.data:
|
||||
lines.append(child.data)
|
||||
lines.append(_node_data_as_markdown(child))
|
||||
lines.append("")
|
||||
_render_children(child.id, depth + 1)
|
||||
|
||||
|
|
@ -325,7 +359,7 @@ def namespace_to_markdown(namespace, roots, node_fetcher):
|
|||
lines.append("## {}".format(chapter_title))
|
||||
lines.append("")
|
||||
if root.data:
|
||||
lines.append(root.data)
|
||||
lines.append(_node_data_as_markdown(root))
|
||||
lines.append("")
|
||||
|
||||
# Fetch all nodes in this thread
|
||||
|
|
@ -354,7 +388,7 @@ def namespace_to_markdown(namespace, roots, node_fetcher):
|
|||
))
|
||||
lines.append("")
|
||||
if child.data:
|
||||
lines.append(child.data)
|
||||
lines.append(_node_data_as_markdown(child))
|
||||
lines.append("")
|
||||
_render(child.id, depth + 1)
|
||||
|
||||
|
|
|
|||
|
|
@ -173,7 +173,9 @@ class TestNodeTreeToMarkdown(unittest.TestCase):
|
|||
def test_single_root_node(self):
|
||||
root = MockNode(1, title="Thread Title", data="Root content.")
|
||||
md = node_tree_to_markdown(root, [])
|
||||
self.assertIn("# Thread Title", md)
|
||||
# We no longer prepend `# {title}`; the thread data carries its own
|
||||
# heading. Duplicating it produced three title copies in HTML/PDF.
|
||||
self.assertNotIn("# Thread Title", md)
|
||||
self.assertIn("Root content.", md)
|
||||
|
||||
def test_root_with_children(self):
|
||||
|
|
@ -181,7 +183,6 @@ class TestNodeTreeToMarkdown(unittest.TestCase):
|
|||
child = MockNode(2, data="Reply text.", parent_id=1, created=1,
|
||||
user=MockUser("alice"))
|
||||
md = node_tree_to_markdown(root, [child])
|
||||
self.assertIn("# Thread", md)
|
||||
self.assertIn("Root.", md)
|
||||
self.assertIn("alice", md)
|
||||
self.assertIn("Reply text.", md)
|
||||
|
|
|
|||
|
|
@ -123,7 +123,8 @@ class TestExportThread(UndiggFunctionalTests):
|
|||
)
|
||||
self.assertEqual(res.status_int, 200)
|
||||
self.assertIn("text/markdown", res.content_type)
|
||||
self.assertIn(b"Test Thread", res.body)
|
||||
# Markdown export is a short-circuit — no pandoc, no title block;
|
||||
# we render the thread data exactly so it carries its own heading.
|
||||
self.assertIn(b"Thread content.", res.body)
|
||||
|
||||
def test_export_thread_html(self):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue