github.com/mattermost/mattermost-server/v5@v5.39.3/utils/markdown.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See LICENSE.txt for license information.
     3  
     4  package utils
     5  
     6  import (
     7  	"strings"
     8  
     9  	"github.com/yuin/goldmark"
    10  	"github.com/yuin/goldmark/ast"
    11  	"github.com/yuin/goldmark/extension"
    12  	astExt "github.com/yuin/goldmark/extension/ast"
    13  	"github.com/yuin/goldmark/renderer"
    14  	"github.com/yuin/goldmark/util"
    15  )
    16  
    17  // StripMarkdown remove some markdown syntax
    18  func StripMarkdown(markdown string) (string, error) {
    19  	md := goldmark.New(
    20  		goldmark.WithExtensions(extension.Strikethrough),
    21  		goldmark.WithRenderer(
    22  			renderer.NewRenderer(renderer.WithNodeRenderers(
    23  				util.Prioritized(newNotificationRenderer(), 500),
    24  			)),
    25  		),
    26  	)
    27  
    28  	var buf strings.Builder
    29  	if err := md.Convert([]byte(markdown), &buf); err != nil {
    30  		return "", err
    31  	}
    32  
    33  	return strings.TrimSpace(buf.String()), nil
    34  }
    35  
    36  type notificationRenderer struct {
    37  }
    38  
    39  func newNotificationRenderer() *notificationRenderer {
    40  	return &notificationRenderer{}
    41  }
    42  
    43  func (r *notificationRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
    44  	// block
    45  	reg.Register(ast.KindDocument, r.renderDefault)
    46  	reg.Register(ast.KindHeading, r.renderItem)
    47  	reg.Register(ast.KindBlockquote, r.renderDefault)
    48  	reg.Register(ast.KindCodeBlock, r.renderCodeBlock)
    49  	reg.Register(ast.KindFencedCodeBlock, r.renderFencedCodeBlock)
    50  	reg.Register(ast.KindHTMLBlock, r.renderDefault)
    51  	reg.Register(ast.KindList, r.renderDefault)
    52  	reg.Register(ast.KindListItem, r.renderItem)
    53  	reg.Register(ast.KindParagraph, r.renderItem)
    54  	reg.Register(ast.KindTextBlock, r.renderTextBlock)
    55  	reg.Register(ast.KindThematicBreak, r.renderDefault)
    56  
    57  	// inlines
    58  	reg.Register(ast.KindAutoLink, r.renderDefault)
    59  	reg.Register(ast.KindCodeSpan, r.renderDefault)
    60  	reg.Register(ast.KindEmphasis, r.renderDefault)
    61  	reg.Register(ast.KindImage, r.renderDefault)
    62  	reg.Register(ast.KindLink, r.renderDefault)
    63  	reg.Register(ast.KindRawHTML, r.renderDefault)
    64  	reg.Register(ast.KindText, r.renderText)
    65  	reg.Register(ast.KindString, r.renderString)
    66  
    67  	// strikethrough
    68  	reg.Register(astExt.KindStrikethrough, r.renderDefault)
    69  }
    70  
    71  // renderDefault renderer function to renderDefault without changes
    72  func (r *notificationRenderer) renderDefault(_ util.BufWriter, _ []byte, _ ast.Node, _ bool) (ast.WalkStatus, error) {
    73  	return ast.WalkContinue, nil
    74  }
    75  
    76  func (r *notificationRenderer) renderItem(w util.BufWriter, _ []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
    77  	if !entering {
    78  		if node.NextSibling() != nil {
    79  			_ = w.WriteByte(' ')
    80  		}
    81  	}
    82  	return ast.WalkContinue, nil
    83  }
    84  
    85  func (r *notificationRenderer) renderCodeBlock(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
    86  	n := node.(*ast.CodeBlock)
    87  	if entering {
    88  		r.writeLines(w, source, n)
    89  	}
    90  
    91  	return ast.WalkContinue, nil
    92  }
    93  
    94  func (r *notificationRenderer) renderFencedCodeBlock(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
    95  	n := node.(*ast.FencedCodeBlock)
    96  	if entering {
    97  		r.writeLines(w, source, n)
    98  	}
    99  
   100  	return ast.WalkContinue, nil
   101  }
   102  
   103  func (r *notificationRenderer) renderText(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
   104  	if !entering {
   105  		return ast.WalkContinue, nil
   106  	}
   107  	n := node.(*ast.Text)
   108  	segment := n.Segment
   109  	_, _ = w.Write(segment.Value(source))
   110  	if !n.IsRaw() {
   111  		if n.HardLineBreak() || n.SoftLineBreak() {
   112  			_ = w.WriteByte('\n')
   113  		}
   114  	}
   115  	return ast.WalkContinue, nil
   116  }
   117  
   118  func (r *notificationRenderer) renderTextBlock(w util.BufWriter, _ []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
   119  	if !entering {
   120  		if _, ok := node.NextSibling().(ast.Node); ok && node.FirstChild() != nil {
   121  			_ = w.WriteByte(' ')
   122  		}
   123  	}
   124  	return ast.WalkContinue, nil
   125  }
   126  
   127  func (r *notificationRenderer) renderString(w util.BufWriter, _ []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
   128  	if !entering {
   129  		return ast.WalkContinue, nil
   130  	}
   131  	n := node.(*ast.String)
   132  	_, _ = w.Write(n.Value)
   133  	return ast.WalkContinue, nil
   134  }
   135  
   136  func (r *notificationRenderer) writeLines(w util.BufWriter, source []byte, n ast.Node) {
   137  	for i := 0; i < n.Lines().Len(); i++ {
   138  		line := n.Lines().At(i)
   139  		value := line.Value(source)
   140  		_, _ = w.Write(value)
   141  	}
   142  }