package extension import ( "fmt" "io/fs" "path/filepath" "strings" "github.com/yuin/goldmark" "github.com/yuin/goldmark/ast" "github.com/yuin/goldmark/parser" "github.com/yuin/goldmark/renderer" "github.com/yuin/goldmark/text" "github.com/yuin/goldmark/util" ) // KindCodeCopyBlock is a NodeKind of the CodeCopyBlock node. var KindCodeCopyBlock = ast.NewNodeKind("CodeCopyBlock") // CodeCopyBlock wraps a FencedCodeBlock to add a copy-to-clipboard button. type CodeCopyBlock struct { ast.BaseBlock } var _ ast.Node = (*CodeCopyBlock)(nil) func (n *CodeCopyBlock) Kind() ast.NodeKind { return KindCodeCopyBlock } func (n *CodeCopyBlock) Dump(source []byte, level int) { ast.DumpHelper(n, source, level, nil, nil) } // codeCopyTransformer wraps FencedCodeBlock nodes in CodeCopyBlock nodes. type codeCopyTransformer struct{} func (t *codeCopyTransformer) Transform(node *ast.Document, reader text.Reader, pc parser.Context) { var fencedBlocks []*ast.FencedCodeBlock _ = ast.Walk(node, func(n ast.Node, entering bool) (ast.WalkStatus, error) { if !entering { return ast.WalkContinue, nil } if fcb, ok := n.(*ast.FencedCodeBlock); ok { lang := string(fcb.Language(reader.Source())) if lang != "mermaid" { fencedBlocks = append(fencedBlocks, fcb) } } return ast.WalkContinue, nil }) for _, fcb := range fencedBlocks { wrapper := &CodeCopyBlock{} parent := fcb.Parent() parent.ReplaceChild(parent, fcb, wrapper) wrapper.AppendChild(wrapper, fcb) } } // codeCopyRenderer renders the CodeCopyBlock wrapper with a copy button. type codeCopyRenderer struct { copyIconSVG string checkIconSVG string } func (r *codeCopyRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) { reg.Register(KindCodeCopyBlock, r.renderCodeCopyBlock) } func (r *codeCopyRenderer) renderCodeCopyBlock(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { if entering { _, _ = w.WriteString(`