Skip to content

Commit 48c8ec0

Browse files
committed
markup/goldmark: Add goldmark-cjk-friendly extension
Add `goldmark-cjk-friendly` extension which enhances handling of CJK emphasis and strikethrough. Fixes #14114
1 parent 079f3eb commit 48c8ec0

File tree

8 files changed

+290
-1
lines changed

8 files changed

+290
-1
lines changed

‎docs/content/en/configuration/markup.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ The extensions below, excluding Extras and Passthrough, are enabled by default.
4646
Extension|Documentation|Enabled
4747
:--|:--|:-:
4848
`cjk`|[Goldmark Extensions: CJK]|:heavy_check_mark:
49+
`cjkFriendly`|[Goldmark Extensions: CJK Friendly]| 
4950
`definitionList`|[PHP Markdown Extra: Definition lists]|:heavy_check_mark:
5051
`extras`|[Hugo Goldmark Extensions: Extras]| 
5152
`footnote`|[PHP Markdown Extra: Footnotes]|:heavy_check_mark:
@@ -337,6 +338,7 @@ ordered
337338
[GitHub Flavored Markdown: Task list items]: https://github.github.com/gfm/#task-list-items-extension-
338339
[GitHub Flavored Markdown]: https://github.github.com/gfm/
339340
[Goldmark Extensions: CJK]: https://github.com/yuin/goldmark?tab=readme-ov-file#cjk-extension
341+
[Goldmark Extensions: CJK Friendly]: https://github.com/tats-u/goldmark-cjk-friendly
340342
[Goldmark Extensions: Typographer]: https://github.com/yuin/goldmark?tab=readme-ov-file#typographer-extension
341343
[Goldmark]: https://github.com/yuin/goldmark/
342344
[Hugo Goldmark Extensions: Extras]: https://github.com/gohugoio/hugo-goldmark-extensions?tab=readme-ov-file#extras-extension

‎docs/data/docs.yaml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1232,6 +1232,8 @@ config:
12321232
eastAsianLineBreaksStyle: simple
12331233
enable: false
12341234
escapedSpace: false
1235+
cjkFriendly:
1236+
emphasis: false
12351237
definitionList: true
12361238
extras:
12371239
delete:

‎go.mod‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ require (
6767
github.com/spf13/cobra v1.10.2
6868
github.com/spf13/fsync v0.10.1
6969
github.com/spf13/pflag v1.0.9
70+
github.com/tats-u/goldmark-cjk-friendly/v2 v2.0.2
7071
github.com/tdewolff/minify/v2 v2.24.8
7172
github.com/tdewolff/parse/v2 v2.8.5
7273
github.com/tetratelabs/wazero v1.10.1

‎go.sum‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
505505
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
506506
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
507507
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
508+
github.com/tats-u/goldmark-cjk-friendly/v2 v2.0.2 h1:+N9clTED51Tt55ljeXYXufmqS8wUXpLJW771aIGIRxo=
509+
github.com/tats-u/goldmark-cjk-friendly/v2 v2.0.2/go.mod h1:1Wm+kJwLMq/sr22CCei+eN593nanPDVn1eH91QdVPEI=
508510
github.com/tdewolff/minify/v2 v2.24.8 h1:58/VjsbevI4d5FGV0ZSuBrHMSSkH4MCH0sIz/eKIauE=
509511
github.com/tdewolff/minify/v2 v2.24.8/go.mod h1:0Ukj0CRpo/sW/nd8uZ4ccXaV1rEVIWA3dj8U7+Shhfw=
510512
github.com/tdewolff/parse/v2 v2.8.5 h1:ZmBiA/8Do5Rpk7bDye0jbbDUpXXbCdc3iah4VeUvwYU=

‎markup/goldmark/convert.go‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"github.com/gohugoio/hugo/markup/goldmark/internal/render"
2828
"github.com/gohugoio/hugo/markup/goldmark/passthrough"
2929
"github.com/gohugoio/hugo/markup/goldmark/tables"
30+
cjkFriendly "github.com/tats-u/goldmark-cjk-friendly/v2"
3031
"github.com/yuin/goldmark/util"
3132

3233
"github.com/yuin/goldmark"
@@ -153,7 +154,7 @@ func newMarkdown(pcfg converter.ProviderConfig) goldmark.Markdown {
153154
extensions = append(extensions, tables.New())
154155
}
155156

156-
if cfg.Extensions.Strikethrough {
157+
if cfg.Extensions.Strikethrough && !cfg.Extensions.CJKFriendly.Emphasis {
157158
extensions = append(extensions, extension.Strikethrough)
158159
}
159160

@@ -207,6 +208,14 @@ func newMarkdown(pcfg converter.ProviderConfig) goldmark.Markdown {
207208
extensions = append(extensions, c)
208209
}
209210

211+
if cfg.Extensions.CJKFriendly.Emphasis {
212+
if cfg.Extensions.Strikethrough {
213+
extensions = append(extensions, cjkFriendly.CJKFriendlyEmphasisAndStrikethrough)
214+
} else {
215+
extensions = append(extensions, cjkFriendly.CJKFriendlyEmphasis)
216+
}
217+
}
218+
210219
if cfg.Extensions.Passthrough.Enable {
211220
extensions = append(extensions, passthrough.New(cfg.Extensions.Passthrough))
212221
}

‎markup/goldmark/convert_test.go‎

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -746,6 +746,140 @@ escapedSpace=true
746746
c.Assert(got, qt.Contains, "<p>私は太郎です。\nプログラミングが好きです。運動が苦手です。</p>\n")
747747
}
748748

749+
func TestConvertCJKFriendlyWithExtensionEmphasisWithExtensionStrikethrough(t *testing.T) {
750+
c := qt.New(t)
751+
752+
content := "Git **(ギット)**Hub\n~~(真美好)~~"
753+
754+
tests := []struct {
755+
name string
756+
strikethrough bool
757+
cjkFriendlyEmphasis bool
758+
expect string
759+
}{
760+
{"noFriendly_noStrike", false, false, "<p>Git **(ギット)**Hub\n~~(真美好)~~</p>\n"},
761+
{"friendly_noStrike", false, true, "<p>Git <strong>(ギット)</strong>Hub\n~~(真美好)~~</p>\n"},
762+
{"noFriendly_strike", true, false, "<p>Git **(ギット)**Hub\n<del>(真美好)</del></p>\n"},
763+
{"friendly_strike", true, true, "<p>Git <strong>(ギット)</strong>Hub\n<del>(真美好)</del></p>\n"},
764+
}
765+
766+
for _, tt := range tests {
767+
c.Run(tt.name, func(c *qt.C) {
768+
confStr := fmt.Sprintf(`
769+
[markup]
770+
[markup.goldmark]
771+
[markup.goldmark.extensions]
772+
strikethrough=%v
773+
[markup.goldmark.extensions.CJKFriendly]
774+
emphasis=%v
775+
`, tt.strikethrough, tt.cjkFriendlyEmphasis)
776+
777+
cfg := config.FromTOMLConfigString(confStr)
778+
conf := testconfig.GetTestConfig(nil, cfg)
779+
780+
b := convert(c, conf, content)
781+
got := string(b.Bytes())
782+
783+
c.Assert(got, qt.Contains, tt.expect)
784+
})
785+
}
786+
}
787+
788+
func TestConvertCJKFriendlyWithExtensionEmphasisWithExtensionStrikethroughWithCJKEscapedSpace(t *testing.T) {
789+
c := qt.New(t)
790+
791+
content := "a\\ **()**\\ a𩸽**()**𩸽~~(真美好)~~"
792+
793+
tests := []struct {
794+
name string
795+
strikethrough bool
796+
escapedSpace bool
797+
cjkFriendlyEmphasis bool
798+
expect string
799+
}{
800+
{
801+
name: "noFriendly_noStrike_noEscaped",
802+
strikethrough: false,
803+
escapedSpace: false,
804+
cjkFriendlyEmphasis: false,
805+
expect: "<p>a\\ <strong>()</strong>\\ a𩸽**()**𩸽~~(真美好)~~</p>\n",
806+
},
807+
{
808+
name: "noFriendly_noStrike_escaped",
809+
strikethrough: false,
810+
escapedSpace: true,
811+
cjkFriendlyEmphasis: false,
812+
expect: "<p>a<strong>()</strong>a𩸽**()**𩸽~~(真美好)~~</p>\n",
813+
},
814+
{
815+
name: "noFriendly_strike_noEscaped",
816+
strikethrough: true,
817+
escapedSpace: false,
818+
cjkFriendlyEmphasis: false,
819+
expect: "<p>a\\ <strong>()</strong>\\ a𩸽**()**𩸽~~(真美好)~~</p>\n",
820+
},
821+
{
822+
name: "noFriendly_strike_escaped",
823+
strikethrough: true,
824+
escapedSpace: true,
825+
cjkFriendlyEmphasis: false,
826+
expect: "<p>a<strong>()</strong>a𩸽**()**𩸽~~(真美好)~~</p>\n",
827+
},
828+
{
829+
name: "friendly_noStrike_noEscaped",
830+
strikethrough: false,
831+
escapedSpace: false,
832+
cjkFriendlyEmphasis: true,
833+
expect: "<p>a\\ <strong>()</strong>\\ a𩸽<strong>()</strong>𩸽~~(真美好)~~</p>\n",
834+
},
835+
{
836+
name: "friendly_noStrike_escaped",
837+
strikethrough: false,
838+
escapedSpace: true,
839+
cjkFriendlyEmphasis: true,
840+
expect: "<p>a<strong>()</strong>a𩸽<strong>()</strong>𩸽~~(真美好)~~</p>\n",
841+
},
842+
{
843+
name: "friendly_strike_noEscaped",
844+
strikethrough: true,
845+
escapedSpace: false,
846+
cjkFriendlyEmphasis: true,
847+
expect: "<p>a\\ <strong>()</strong>\\ a𩸽<strong>()</strong>𩸽<del>(真美好)</del></p>\n",
848+
},
849+
{
850+
name: "friendly_strike_escaped",
851+
strikethrough: true,
852+
escapedSpace: true,
853+
cjkFriendlyEmphasis: true,
854+
expect: "<p>a<strong>()</strong>a𩸽<strong>()</strong>𩸽<del>(真美好)</del></p>\n",
855+
},
856+
}
857+
858+
for _, tt := range tests {
859+
c.Run(tt.name, func(c *qt.C) {
860+
confStr := fmt.Sprintf(`
861+
[markup]
862+
[markup.goldmark]
863+
[markup.goldmark.extensions]
864+
strikethrough=%v
865+
[markup.goldmark.extensions.CJK]
866+
enable=true
867+
escapedSpace=%v
868+
[markup.goldmark.extensions.CJKFriendly]
869+
emphasis=%v
870+
`, tt.strikethrough, tt.escapedSpace, tt.cjkFriendlyEmphasis)
871+
872+
cfg := config.FromTOMLConfigString(confStr)
873+
conf := testconfig.GetTestConfig(nil, cfg)
874+
875+
b := convert(c, conf, content)
876+
got := string(b.Bytes())
877+
878+
c.Assert(got, qt.Contains, tt.expect)
879+
})
880+
}
881+
}
882+
749883
type tableRenderer int
750884

751885
func (hr tableRenderer) RenderTable(cctx context.Context, w hugio.FlexiWriter, ctx hooks.TableContext) error {

‎markup/goldmark/goldmark_config/config.go‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ var Default = Config{
5757
EastAsianLineBreaksStyle: "simple",
5858
EscapedSpace: false,
5959
},
60+
CJKFriendly: CJKFriendly{
61+
Emphasis: false,
62+
},
6063
Extras: Extras{
6164
Delete: Delete{
6265
Enable: false,
@@ -168,6 +171,7 @@ type Extensions struct {
168171
LinkifyProtocol string
169172
TaskList bool
170173
CJK CJK
174+
CJKFriendly CJKFriendly `json:"cjkFriendly"`
171175
}
172176

173177
// Footnote holds footnote configuration.
@@ -270,6 +274,11 @@ type CJK struct {
270274
EscapedSpace bool
271275
}
272276

277+
type CJKFriendly struct {
278+
// Emphasis adds support for CJK-friendly 'emphasis'. If "strikethrough" goldmark extension is enabled as well, CJK-friendly 'emphasis and strikethrough' will be used.
279+
Emphasis bool
280+
}
281+
273282
type Renderer struct {
274283
// Whether softline breaks should be rendered as '<br>'
275284
HardWraps bool

‎markup/goldmark/goldmark_integration_test.go‎

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -828,6 +828,136 @@ H~2~0
828828
)
829829
}
830830

831+
func TestExtrasExtensionWithCJKFriendlyEmphasis(t *testing.T) {
832+
t.Parallel()
833+
834+
files := `
835+
-- hugo.toml --
836+
disableKinds = ['page','rss','section','sitemap','taxonomy','term']
837+
[markup.goldmark.extensions]
838+
strikethrough = false
839+
[markup.goldmark.extensions.extras.delete]
840+
enable = false
841+
[markup.goldmark.extensions.extras.insert]
842+
enable = false
843+
[markup.goldmark.extensions.extras.mark]
844+
enable = false
845+
[markup.goldmark.extensions.extras.subscript]
846+
enable = false
847+
[markup.goldmark.extensions.extras.superscript]
848+
enable = false
849+
[markup.goldmark.extensions.CJKFriendly]
850+
emphasis = false
851+
-- layouts/index.html --
852+
{{ .Content }}
853+
-- content/_index.md --
854+
---
855+
title: home
856+
---
857+
~~削除~~
858+
859+
++挿入++
860+
861+
==マーク==
862+
863+
(H~2~O)
864+
865+
面積は1^乗^です
866+
867+
(~~削除~~)
868+
869+
~~(挿入)~~
870+
871+
混合 **(強)**~~削除~~
872+
873+
空 ~~ ~~
874+
875+
++CJK**「ハロー」**Test++
876+
877+
Mark**==マーク==**Test
878+
879+
MarkParen**(==マーク==)**Test
880+
`
881+
882+
b := hugolib.Test(t, files)
883+
884+
// CJKFriendlyEmphasis disabled, Extras disabled.
885+
b.AssertFileContent("public/index.html",
886+
"<p>~~削除~~</p>",
887+
"<p>++挿入++</p>",
888+
"<p>==マーク==</p>",
889+
"<p>(H~2~O)</p>",
890+
"<p>面積は1^乗^です</p>",
891+
"<p>(~~削除~~)</p>",
892+
"<p>~~(挿入)~~</p>",
893+
"<p>混合 <strong>(強)</strong>~~削除~~</p>",
894+
"<p>空 ~~ ~~</p>",
895+
"<p>++CJK**「ハロー」**Test++</p>",
896+
"<p>Mark**==マーク==**Test</p>",
897+
"<p>MarkParen**(==マーク==)**Test</p>",
898+
)
899+
900+
files = strings.ReplaceAll(files, "enable = false", "enable = true")
901+
902+
b = hugolib.Test(t, files)
903+
904+
// CJKFriendlyEmphasis disabled, Extras enabled.
905+
b.AssertFileContent("public/index.html",
906+
"<p><del>削除</del></p>",
907+
"<p><ins>挿入</ins></p>",
908+
"<p><mark>マーク</mark></p>",
909+
"<p>(H<sub>2</sub>O)</p>",
910+
"<p>面積は1<sup>乗</sup>です</p>",
911+
"<p>(<del>削除</del>)</p>",
912+
"<p><del>(挿入)</del></p>",
913+
"<p>混合 <strong>(強)</strong><del>削除</del></p>",
914+
"<p>空 ~~ ~~</p>",
915+
"<p><ins>CJK**「ハロー」**Test</ins></p>",
916+
"<p>Mark**<mark>マーク</mark>**Test</p>",
917+
"<p>MarkParen**(<mark>マーク</mark>)**Test</p>",
918+
)
919+
920+
filesCJKEnabled := strings.Replace(files, "emphasis = false", "emphasis = true", 1)
921+
922+
b = hugolib.Test(t, filesCJKEnabled)
923+
924+
// CJKFriendlyEmphasis enabled, Extras enabled.
925+
b.AssertFileContent("public/index.html",
926+
"<p><del>削除</del></p>",
927+
"<p><ins>挿入</ins></p>",
928+
"<p><mark>マーク</mark></p>",
929+
"<p>(H<sub>2</sub>O)</p>",
930+
"<p>面積は1<sup>乗</sup>です</p>",
931+
"<p>(<del>削除</del>)</p>",
932+
"<p><del>(挿入)</del></p>",
933+
"<p>混合 <strong>(強)</strong><del>削除</del></p>",
934+
"<p>空 ~~ ~~</p>",
935+
"<p><ins>CJK<strong>「ハロー」</strong>Test</ins></p>",
936+
"<p>Mark**<mark>マーク</mark>**Test</p>",
937+
"<p>MarkParen<strong>(<mark>マーク</mark>)</strong>Test</p>",
938+
)
939+
940+
filesCJKEnabledExtrasDisabled := strings.ReplaceAll(filesCJKEnabled, "enable = true", "enable = false")
941+
942+
b = hugolib.Test(t, filesCJKEnabledExtrasDisabled)
943+
944+
// CJKFriendlyEmphasis enabled, Extras disabled.
945+
b.AssertFileContent("public/index.html",
946+
"<p>~~削除~~</p>",
947+
"<p>++挿入++</p>",
948+
"<p>==マーク==</p>",
949+
"<p>(H~2~O)</p>",
950+
"<p>面積は1^乗^です</p>",
951+
"<p>(~~削除~~)</p>",
952+
"<p>~~(挿入)~~</p>",
953+
"<p>混合 <strong>(強)</strong>~~削除~~</p>",
954+
"<p>空 ~~ ~~</p>",
955+
"<p>++CJK<strong>「ハロー」</strong>Test++</p>",
956+
"<p>Mark**==マーク==**Test</p>",
957+
"<p>MarkParen<strong>(==マーク==)</strong>Test</p>",
958+
)
959+
}
960+
831961
// Issue 12997.
832962
func TestGoldmarkRawHTMLWarningBlocks(t *testing.T) {
833963
files := `

0 commit comments

Comments
 (0)