Skip to content

Commit a615f42

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 ff0f67e commit a615f42

File tree

6 files changed

+319
-1
lines changed

6 files changed

+319
-1
lines changed

‎docs/data/docs.yaml‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1226,6 +1226,9 @@ config:
12261226
eastAsianLineBreaksStyle: simple
12271227
enable: false
12281228
escapedSpace: false
1229+
friendlyEmphasis: false
1230+
friendlyStrikethrough: false
1231+
friendlyEmphasisAndStrikethrough: false
12291232
definitionList: true
12301233
extras:
12311234
delete:

‎go.mod‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ require (
6868
github.com/spf13/cobra v1.9.1
6969
github.com/spf13/fsync v0.10.1
7070
github.com/spf13/pflag v1.0.7
71+
github.com/tats-u/goldmark-cjk-friendly v1.0.0
7172
github.com/tdewolff/minify/v2 v2.24.5
7273
github.com/tdewolff/parse/v2 v2.8.5-0.20251020133559-0efcf90bef1a
7374
github.com/tetratelabs/wazero v1.9.0
@@ -192,4 +193,4 @@ require (
192193
software.sslmate.com/src/go-pkcs12 v0.2.0 // indirect
193194
)
194195

195-
go 1.24.0
196+
go 1.25.1

‎go.sum‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
513513
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
514514
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
515515
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
516+
github.com/tats-u/goldmark-cjk-friendly v1.0.0 h1:CV9DuvaobrxOEII9m3MxW3rGkRQcBuWHud4tJrfoWuk=
517+
github.com/tats-u/goldmark-cjk-friendly v1.0.0/go.mod h1:gkL4MS2BRA7T5JRDLHs7PRij0/boXtbxas6DG3Yyedk=
516518
github.com/tdewolff/minify/v2 v2.24.5 h1:ytxthX3xSxrK3Xx5B38flg5moCKs/dB8VwiD/RzJViU=
517519
github.com/tdewolff/minify/v2 v2.24.5/go.mod h1:q09KtNnVai7TyEzGEZeWPAnK+c8Z+NI8prCXZW652bo=
518520
github.com/tdewolff/parse/v2 v2.8.5-0.20251020133559-0efcf90bef1a h1:Rmq+utdraciok/97XHRweYdsAo/M4LOswpCboo3yvN4=

‎markup/goldmark/convert.go‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"github.com/gohugoio/hugo/markup/goldmark/tables"
3030
"github.com/yuin/goldmark/util"
3131

32+
cjkfriendly "github.com/tats-u/goldmark-cjk-friendly"
3233
"github.com/yuin/goldmark"
3334
emoji "github.com/yuin/goldmark-emoji"
3435
"github.com/yuin/goldmark/ast"
@@ -205,6 +206,16 @@ func newMarkdown(pcfg converter.ProviderConfig) goldmark.Markdown {
205206
}
206207
c := extension.NewCJK(opts...)
207208
extensions = append(extensions, c)
209+
210+
if cfg.Extensions.CJK.FriendlyEmphasis {
211+
extensions = append(extensions, cjkfriendly.CJKFriendlyEmphasis)
212+
}
213+
if cfg.Extensions.CJK.FriendlyStrikethrough {
214+
extensions = append(extensions, cjkfriendly.CJKFriendlyStrikethrough)
215+
}
216+
if cfg.Extensions.CJK.FriendlyEmphasisAndStrikethrough {
217+
extensions = append(extensions, cjkfriendly.CJKFriendlyEmphasisAndStrikethrough)
218+
}
208219
}
209220

210221
if cfg.Extensions.Passthrough.Enable {

‎markup/goldmark/convert_test.go‎

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

749+
func TestConvertCJKWithExtensionWithFriendlyEmphasisWithoutDefaultStrikethrough(t *testing.T) {
750+
c := qt.New(t)
751+
752+
content := "Git **(ギット)**Hub\n~~(真美好)~~"
753+
754+
confStr := `
755+
[markup]
756+
[markup.goldmark]
757+
[markup.goldmark.extensions]
758+
strikethrough=false
759+
[markup.goldmark.extensions.CJK]
760+
enable=true
761+
friendlyEmphasis=true
762+
`
763+
764+
cfg := config.FromTOMLConfigString(confStr)
765+
conf := testconfig.GetTestConfig(nil, cfg)
766+
767+
b := convert(c, conf, content)
768+
got := string(b.Bytes())
769+
770+
c.Assert(got, qt.Contains, "<p>Git <strong>(ギット)</strong>Hub\n~~(真美好)~~</p>\n")
771+
}
772+
773+
func TestConvertCJKWithExtensionWithFriendlyStrikethroughWithoutDefaultStrikethrough(t *testing.T) {
774+
c := qt.New(t)
775+
776+
content := "Git **(ギット)**Hub\n~~(真美好)~~"
777+
778+
confStr := `
779+
[markup]
780+
[markup.goldmark]
781+
[markup.goldmark.extensions]
782+
strikethrough=false
783+
[markup.goldmark.extensions.CJK]
784+
enable=true
785+
friendlyStrikethrough=true
786+
`
787+
788+
cfg := config.FromTOMLConfigString(confStr)
789+
conf := testconfig.GetTestConfig(nil, cfg)
790+
791+
b := convert(c, conf, content)
792+
got := string(b.Bytes())
793+
794+
c.Assert(got, qt.Contains, "<p>Git **(ギット)**Hub\n<del>(真美好)</del></p>\n")
795+
}
796+
797+
func TestConvertCJKWithExtensionWithFriendlyEmphasisAndStrikethroughWithoutDefaultStrikethrough(t *testing.T) {
798+
c := qt.New(t)
799+
800+
content := "Git **(ギット)**Hub\n~~(真美好)~~"
801+
802+
confStr := `
803+
[markup]
804+
[markup.goldmark]
805+
[markup.goldmark.extensions]
806+
strikethrough=false
807+
[markup.goldmark.extensions.CJK]
808+
enable=true
809+
friendlyEmphasisAndStrikethrough=true
810+
`
811+
812+
cfg := config.FromTOMLConfigString(confStr)
813+
conf := testconfig.GetTestConfig(nil, cfg)
814+
815+
b := convert(c, conf, content)
816+
got := string(b.Bytes())
817+
818+
c.Assert(got, qt.Contains, "<p>Git <strong>(ギット)</strong>Hub\n<del>(真美好)</del></p>\n")
819+
}
820+
821+
func TestConvertCJKWithExtensionWithFriendlyEmphasisWithDefaultStrikethrough(t *testing.T) {
822+
c := qt.New(t)
823+
824+
content := "Git **(ギット)**Hub\n~~(真美好)~~"
825+
826+
confStr := `
827+
[markup]
828+
[markup.goldmark]
829+
[markup.goldmark.extensions]
830+
[markup.goldmark.extensions.CJK]
831+
enable=true
832+
friendlyEmphasis=true
833+
`
834+
835+
cfg := config.FromTOMLConfigString(confStr)
836+
conf := testconfig.GetTestConfig(nil, cfg)
837+
838+
b := convert(c, conf, content)
839+
got := string(b.Bytes())
840+
841+
c.Assert(got, qt.Contains, "<p>Git <strong>(ギット)</strong>Hub\n<del>(真美好)</del></p>\n")
842+
}
843+
844+
func TestConvertCJKWithExtensionWithFriendlyStrikethroughWithDefaultStrikethrough(t *testing.T) {
845+
c := qt.New(t)
846+
847+
content := "Git **(ギット)**Hub\n~~(真美好)~~"
848+
849+
confStr := `
850+
[markup]
851+
[markup.goldmark]
852+
[markup.goldmark.extensions]
853+
[markup.goldmark.extensions.CJK]
854+
enable=true
855+
friendlyStrikethrough=true
856+
`
857+
858+
cfg := config.FromTOMLConfigString(confStr)
859+
conf := testconfig.GetTestConfig(nil, cfg)
860+
861+
b := convert(c, conf, content)
862+
got := string(b.Bytes())
863+
864+
c.Assert(got, qt.Contains, "<p>Git **(ギット)**Hub\n<del>(真美好)</del></p>\n")
865+
}
866+
867+
func TestConvertCJKWithExtensionWithFriendlyEmphasisAndStrikethroughWithDefaultStrikethrough(t *testing.T) {
868+
c := qt.New(t)
869+
870+
content := "Git **(ギット)**Hub\n~~(真美好)~~"
871+
872+
confStr := `
873+
[markup]
874+
[markup.goldmark]
875+
[markup.goldmark.extensions]
876+
[markup.goldmark.extensions.CJK]
877+
enable=true
878+
friendlyEmphasisAndStrikethrough=true
879+
`
880+
881+
cfg := config.FromTOMLConfigString(confStr)
882+
conf := testconfig.GetTestConfig(nil, cfg)
883+
884+
b := convert(c, conf, content)
885+
got := string(b.Bytes())
886+
887+
c.Assert(got, qt.Contains, "<p>Git <strong>(ギット)</strong>Hub\n<del>(真美好)</del></p>\n")
888+
}
889+
890+
func TestConvertCJKWithExtensionWithFriendlyEmphasisWithoutDefaultStrikethroughWithEscapedSpace(t *testing.T) {
891+
c := qt.New(t)
892+
893+
content := "a\\ **()**\\ a𩸽**()**𩸽~~(真美好)~~"
894+
895+
confStr := `
896+
[markup]
897+
[markup.goldmark]
898+
[markup.goldmark.extensions]
899+
strikethrough=false
900+
[markup.goldmark.extensions.CJK]
901+
enable=true
902+
escapedSpace=true
903+
friendlyEmphasis=true
904+
`
905+
906+
cfg := config.FromTOMLConfigString(confStr)
907+
conf := testconfig.GetTestConfig(nil, cfg)
908+
909+
b := convert(c, conf, content)
910+
got := string(b.Bytes())
911+
912+
c.Assert(got, qt.Contains, "<p>a<strong>()</strong>a𩸽<strong>()</strong>𩸽~~(真美好)~~</p>\n")
913+
}
914+
915+
func TestConvertCJKWithExtensionWithFriendlyStrikethroughWithoutDefaultStrikethroughWithEscapedSpace(t *testing.T) {
916+
c := qt.New(t)
917+
918+
content := "a\\ **()**\\ a𩸽**()**𩸽~~(真美好)~~"
919+
920+
confStr := `
921+
[markup]
922+
[markup.goldmark]
923+
[markup.goldmark.extensions]
924+
strikethrough=false
925+
[markup.goldmark.extensions.CJK]
926+
enable=true
927+
escapedSpace=true
928+
friendlyStrikethrough=true
929+
`
930+
931+
cfg := config.FromTOMLConfigString(confStr)
932+
conf := testconfig.GetTestConfig(nil, cfg)
933+
934+
b := convert(c, conf, content)
935+
got := string(b.Bytes())
936+
937+
c.Assert(got, qt.Contains, "<p>a<strong>()</strong>a𩸽**()**𩸽<del>(真美好)</del></p>\n")
938+
}
939+
940+
func TestConvertCJKWithExtensionWithFriendlyEmphasisAndStrikethroughWithoutDefaultStrikethroughWithEscapedSpace(t *testing.T) {
941+
c := qt.New(t)
942+
943+
content := "a\\ **()**\\ a𩸽**()**𩸽~~(真美好)~~"
944+
945+
confStr := `
946+
[markup]
947+
[markup.goldmark]
948+
[markup.goldmark.extensions]
949+
strikethrough=false
950+
[markup.goldmark.extensions.CJK]
951+
enable=true
952+
escapedSpace=true
953+
friendlyEmphasisAndStrikethrough=true
954+
`
955+
956+
cfg := config.FromTOMLConfigString(confStr)
957+
conf := testconfig.GetTestConfig(nil, cfg)
958+
959+
b := convert(c, conf, content)
960+
got := string(b.Bytes())
961+
962+
c.Assert(got, qt.Contains, "<p>a<strong>()</strong>a𩸽<strong>()</strong>𩸽<del>(真美好)</del></p>\n")
963+
}
964+
965+
func TestConvertCJKWithExtensionWithFriendlyEmphasisWithDefaultStrikethroughWithEscapedSpace(t *testing.T) {
966+
c := qt.New(t)
967+
968+
content := "a\\ **()**\\ a𩸽**()**𩸽~~(真美好)~~"
969+
970+
confStr := `
971+
[markup]
972+
[markup.goldmark]
973+
[markup.goldmark.extensions]
974+
[markup.goldmark.extensions.CJK]
975+
enable=true
976+
escapedSpace=true
977+
friendlyEmphasis=true
978+
`
979+
980+
cfg := config.FromTOMLConfigString(confStr)
981+
conf := testconfig.GetTestConfig(nil, cfg)
982+
983+
b := convert(c, conf, content)
984+
got := string(b.Bytes())
985+
986+
c.Assert(got, qt.Contains, "<p>a<strong>()</strong>a𩸽<strong>()</strong>𩸽~~(真美好)~~</p>\n")
987+
}
988+
989+
func TestConvertCJKWithExtensionWithFriendlyStrikethroughWithDefaultStrikethroughWithEscapedSpace(t *testing.T) {
990+
c := qt.New(t)
991+
992+
content := "a\\ **()**\\ a𩸽**()**𩸽~~(真美好)~~"
993+
994+
confStr := `
995+
[markup]
996+
[markup.goldmark]
997+
[markup.goldmark.extensions]
998+
[markup.goldmark.extensions.CJK]
999+
enable=true
1000+
escapedSpace=true
1001+
friendlyStrikethrough=true
1002+
`
1003+
1004+
cfg := config.FromTOMLConfigString(confStr)
1005+
conf := testconfig.GetTestConfig(nil, cfg)
1006+
1007+
b := convert(c, conf, content)
1008+
got := string(b.Bytes())
1009+
1010+
c.Assert(got, qt.Contains, "<p>a<strong>()</strong>a𩸽**()**𩸽<del>(真美好)</del></p>\n")
1011+
}
1012+
1013+
func TestConvertCJKWithExtensionWithFriendlyEmphasisAndStrikethroughWithDefaultStrikethroughWithEscapedSpace(t *testing.T) {
1014+
c := qt.New(t)
1015+
1016+
content := "a\\ **()**\\ a𩸽**()**𩸽~~(真美好)~~"
1017+
1018+
confStr := `
1019+
[markup]
1020+
[markup.goldmark]
1021+
[markup.goldmark.extensions]
1022+
[markup.goldmark.extensions.CJK]
1023+
enable=true
1024+
escapedSpace=true
1025+
friendlyEmphasisAndStrikethrough=true
1026+
`
1027+
1028+
cfg := config.FromTOMLConfigString(confStr)
1029+
conf := testconfig.GetTestConfig(nil, cfg)
1030+
1031+
b := convert(c, conf, content)
1032+
got := string(b.Bytes())
1033+
1034+
c.Assert(got, qt.Contains, "<p>a<strong>()</strong>a𩸽<strong>()</strong>𩸽<del>(真美好)</del></p>\n")
1035+
}
1036+
7491037
type tableRenderer int
7501038

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

‎markup/goldmark/goldmark_config/config.go‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ var Default = Config{
5656
EastAsianLineBreaks: false,
5757
EastAsianLineBreaksStyle: "simple",
5858
EscapedSpace: false,
59+
FriendlyEmphasis: false,
60+
FriendlyStrikethrough: false,
61+
FriendlyEmphasisAndStrikethrough: false,
5962
},
6063
Extras: Extras{
6164
Delete: Delete{
@@ -268,6 +271,16 @@ type CJK struct {
268271

269272
// Whether a '\' escaped half-space(0x20) should not be rendered.
270273
EscapedSpace bool
274+
275+
// FriendlyEmphasis is a basic extension without GFM strikethrough support
276+
FriendlyEmphasis bool
277+
278+
// FriendlyStrikethrough is an extension that allow you to use strikethrough expression like '~~text~~' .
279+
FriendlyStrikethrough bool
280+
281+
// Combination of FriendlyEmphasis and FriendlyStrikethrough
282+
// It is recommended to use this extension instead of using FriendlyEmphasis and FriendlyStrikethrough separately
283+
FriendlyEmphasisAndStrikethrough bool
271284
}
272285

273286
type Renderer struct {

0 commit comments

Comments
 (0)