Repository: gohugoio/hugo (language: go)

Issue:
Markdownify Not Closing A Paragraph
### What version of Hugo are you using (`hugo version`)?

<pre>
$ hugo version
hugo v0.120.4-f11bca5fec2ebb3a02727fb2a5cfb08da96fd9df+extended darwin/arm64 BuildDate=2023-11-08T11:18:07Z VendorInfo=brew
</pre>

### Does this issue reproduce with the latest release?

Yes

### Description

When I use markdownify on a title and paragraph, the resulting HTML leaves the paragraph unclosed.

So using `.Page.RenderString` or `markdownify` on "# My Title\nparagraph" results in:
```html
<h1 id=\"my-title\">My Title</h1>\n<p>paragraph
```

Minimal example: https://github.com/MarkRosemaker/hugo-bug-markdownify

Draft PR with failing test: https://github.com/gohugoio/hugo/pull/11699



Discussion / hints:
Workaround... the [`RenderString`](https://gohugo.io/methods/page/renderstring/) method on `Page` takes an options map, so this:

```text
{{ $s := `
## Title

Text
`}}
{{ $.Page.RenderString (dict "display" "block") $s }}
```

Is rendered to:

```html
<h2 id="title">Title</h2>
<p>Text</p>
```

The `markdownify` function is effectively aliased to `.Site.Home.RenderString`, but without the ability to specify inline vs. block. Another reason to use `RenderString` instead of `markdownify` is <https://github.com/gohugoio/hugo/issues/9692>.

This is the bit that does the `p` removal:
https://github.com/gohugoio/hugo/blob/master/helpers/content.go#L253-L267

Yeah, we should fix this. This works fine:

```
{{ $s := `
## Title

My list:

- item one
- item two
`}}
{{ .RenderString $s }}
```

So this should too:

```text
{{ $s := `
## Title

Text
`}}
{{ .RenderString $s }}
```

---

It also breaks with markdown attributes.

```text
{{ $s := `
foo
{#bar}
`}}
{{ .RenderString $s }}
```

Rendered to:

```text
<p id="bar">foo
```

Since the user specified an attribute, they must want a paragraph. So it should be:

```text
<p id="bar">foo</p>
```


Fix patch:
diff --git a/helpers/content.go b/helpers/content.go
index 510d496b9c2..c0a6d822148 100644
--- a/helpers/content.go
+++ b/helpers/content.go
@@ -251,18 +251,15 @@ func (c *ContentSpec) TruncateWordsToWholeSentence(s string) (string, bool) {
 // where said tags are the only <p> tags in the input and enclose the content
 // of the input (whitespace excluded).
 func (c *ContentSpec) TrimShortHTML(input []byte) []byte {
-	firstOpeningP := bytes.Index(input, paragraphIndicator)
-	lastOpeningP := bytes.LastIndex(input, paragraphIndicator)
-
-	lastClosingP := bytes.LastIndex(input, closingPTag)
-	lastClosing := bytes.LastIndex(input, closingIndicator)
-
-	if firstOpeningP == lastOpeningP && lastClosingP == lastClosing {
-		input = bytes.TrimSpace(input)
-		input = bytes.TrimPrefix(input, openingPTag)
-		input = bytes.TrimSuffix(input, closingPTag)
+	if bytes.Count(input, openingPTag) == 1 {
 		input = bytes.TrimSpace(input)
+		if bytes.HasPrefix(input, openingPTag) && bytes.HasSuffix(input, closingPTag) {
+			input = bytes.TrimPrefix(input, openingPTag)
+			input = bytes.TrimSuffix(input, closingPTag)
+			input = bytes.TrimSpace(input)
+		}
 	}
+
 	return input
 }
 


Test patch:
diff --git a/helpers/content_test.go b/helpers/content_test.go
index 2909c026639..72e3eeb495a 100644
--- a/helpers/content_test.go
+++ b/helpers/content_test.go
@@ -32,12 +32,15 @@ func TestTrimShortHTML(t *testing.T) {
 	}{
 		{[]byte(""), []byte("")},
 		{[]byte("Plain text"), []byte("Plain text")},
-		{[]byte("  \t\n Whitespace text\n\n"), []byte("Whitespace text")},
+		// This seems wrong. Why touch it if it doesn't have p tag?
+		// {[]byte("  \t\n Whitespace text\n\n"), []byte("Whitespace text")},
 		{[]byte("<p>Simple paragraph</p>"), []byte("Simple paragraph")},
 		{[]byte("\n  \n \t  <p> \t Whitespace\nHTML  \n\t </p>\n\t"), []byte("Whitespace\nHTML")},
 		{[]byte("<p>Multiple</p><p>paragraphs</p>"), []byte("<p>Multiple</p><p>paragraphs</p>")},
 		{[]byte("<p>Nested<p>paragraphs</p></p>"), []byte("<p>Nested<p>paragraphs</p></p>")},
 		{[]byte("<p>Hello</p>\n<ul>\n<li>list1</li>\n<li>list2</li>\n</ul>"), []byte("<p>Hello</p>\n<ul>\n<li>list1</li>\n<li>list2</li>\n</ul>")},
+		// Issue #11698
+		{[]byte("<h2 id=`a`>b</h2>\n\n<p>c</p>"), []byte("<h2 id=`a`>b</h2>\n\n<p>c</p>")},
 	}
 
 	c := newTestContentSpec(nil)
diff --git a/tpl/transform/integration_test.go b/tpl/transform/integration_test.go
new file mode 100644
index 00000000000..17348928db6
--- /dev/null
+++ b/tpl/transform/integration_test.go
@@ -0,0 +1,67 @@
+// Copyright 2023 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package transform_test
+
+import (
+	"testing"
+
+	"github.com/gohugoio/hugo/hugolib"
+)
+
+// Issue #11698
+func TestMarkdownifyIssue11698(t *testing.T) {
+	t.Parallel()
+
+	files := `
+-- config.toml --
+disableKinds = ['home','section','rss','sitemap','taxonomy','term']
+[markup.goldmark.parser.attribute]
+title = true
+block = true
+-- layouts/_default/single.html --
+_{{ markdownify .RawContent }}_
+-- content/p1.md --
+---
+title: p1
+---
+foo bar
+-- content/p2.md --
+---
+title: p2
+---
+foo
+
+**bar**
+-- content/p3.md --
+---
+title: p3
+---
+## foo
+
+bar
+-- content/p4.md --
+---
+title: p4
+---
+foo
+{#bar}
+  `
+
+	b := hugolib.Test(t, files)
+
+	b.AssertFileContent("public/p1/index.html", "_foo bar_")
+	b.AssertFileContent("public/p2/index.html", "_<p>foo</p>\n<p><strong>bar</strong></p>\n_")
+	b.AssertFileContent("public/p3/index.html", "_<h2 id=\"foo\">foo</h2>\n<p>bar</p>\n_")
+	b.AssertFileContent("public/p4/index.html", "_<p id=\"bar\">foo</p>\n_")
+}