blockquotes.go - hugo - [fork] hugo port for 9front
 (HTM) git clone https://git.drkhsh.at/hugo.git
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) Submodules
 (DIR) README
 (DIR) LICENSE
       ---
       blockquotes.go (5101B)
       ---
            1 // Copyright 2024 The Hugo Authors. All rights reserved.
            2 //
            3 // Licensed under the Apache License, Version 2.0 (the "License");
            4 // you may not use this file except in compliance with the License.
            5 // You may obtain a copy of the License at
            6 // http://www.apache.org/licenses/LICENSE-2.0
            7 //
            8 // Unless required by applicable law or agreed to in writing, software
            9 // distributed under the License is distributed on an "AS IS" BASIS,
           10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
           11 // See the License for the specific language governing permissions and
           12 // limitations under the License.
           13 
           14 package blockquotes
           15 
           16 import (
           17         "regexp"
           18         "strings"
           19 
           20         "github.com/gohugoio/hugo/common/herrors"
           21         "github.com/gohugoio/hugo/common/types/hstring"
           22         "github.com/gohugoio/hugo/markup/converter/hooks"
           23         "github.com/gohugoio/hugo/markup/goldmark/internal/render"
           24         "github.com/gohugoio/hugo/markup/internal/attributes"
           25         "github.com/yuin/goldmark"
           26         "github.com/yuin/goldmark/ast"
           27         "github.com/yuin/goldmark/renderer"
           28         "github.com/yuin/goldmark/renderer/html"
           29         "github.com/yuin/goldmark/util"
           30 )
           31 
           32 type (
           33         blockquotesExtension struct{}
           34         htmlRenderer         struct{}
           35 )
           36 
           37 func New() goldmark.Extender {
           38         return &blockquotesExtension{}
           39 }
           40 
           41 func (e *blockquotesExtension) Extend(m goldmark.Markdown) {
           42         m.Renderer().AddOptions(renderer.WithNodeRenderers(
           43                 util.Prioritized(newHTMLRenderer(), 100),
           44         ))
           45 }
           46 
           47 func newHTMLRenderer() renderer.NodeRenderer {
           48         r := &htmlRenderer{}
           49         return r
           50 }
           51 
           52 func (r *htmlRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
           53         reg.Register(ast.KindBlockquote, r.renderBlockquote)
           54 }
           55 
           56 const (
           57         typeRegular = "regular"
           58         typeAlert   = "alert"
           59 )
           60 
           61 func (r *htmlRenderer) renderBlockquote(w util.BufWriter, src []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
           62         ctx := w.(*render.Context)
           63 
           64         n := node.(*ast.Blockquote)
           65 
           66         if entering {
           67                 // Store the current pos so we can capture the rendered text.
           68                 ctx.PushPos(ctx.Buffer.Len())
           69                 return ast.WalkContinue, nil
           70         }
           71 
           72         text := strings.TrimSpace(ctx.PopRenderedString())
           73 
           74         ordinal := ctx.GetAndIncrementOrdinal(ast.KindBlockquote)
           75 
           76         typ := typeRegular
           77         alert := resolveBlockQuoteAlert(text)
           78         if alert.typ != "" {
           79                 typ = typeAlert
           80         }
           81 
           82         renderer := ctx.RenderContext().GetRenderer(hooks.BlockquoteRendererType, typ)
           83         if renderer == nil {
           84                 return r.renderBlockquoteDefault(w, n, text)
           85         }
           86 
           87         if typ == typeAlert {
           88                 // Parse the blockquote content to determine the alert text. The alert
           89                 // text begins after the first newline, but we need to add an opening p
           90                 // tag if the first line of the blockquote content does not have a
           91                 // closing p tag. At some point we might want to move this to the
           92                 // parser.
           93                 before, after, found := strings.Cut(text, "\n")
           94                 if found {
           95                         if strings.HasSuffix(before, "</p>") {
           96                                 text = after
           97                         } else {
           98                                 text = "<p>" + after
           99                         }
          100                 } else {
          101                         text = ""
          102                 }
          103         }
          104 
          105         bqctx := &blockquoteContext{
          106                 BaseContext:      render.NewBaseContext(ctx, renderer, n, src, nil, ordinal),
          107                 typ:              typ,
          108                 alert:            alert,
          109                 text:             hstring.HTML(text),
          110                 AttributesHolder: attributes.New(n.Attributes(), attributes.AttributesOwnerGeneral),
          111         }
          112 
          113         cr := renderer.(hooks.BlockquoteRenderer)
          114 
          115         err := cr.RenderBlockquote(
          116                 ctx.RenderContext().Ctx,
          117                 w,
          118                 bqctx,
          119         )
          120         if err != nil {
          121                 return ast.WalkContinue, herrors.NewFileErrorFromPos(err, bqctx.Position())
          122         }
          123 
          124         return ast.WalkContinue, nil
          125 }
          126 
          127 // Code borrowed from goldmark's html renderer.
          128 func (r *htmlRenderer) renderBlockquoteDefault(
          129         w util.BufWriter, n ast.Node, text string,
          130 ) (ast.WalkStatus, error) {
          131         if n.Attributes() != nil {
          132                 _, _ = w.WriteString("<blockquote")
          133                 html.RenderAttributes(w, n, html.BlockquoteAttributeFilter)
          134                 _ = w.WriteByte('>')
          135         } else {
          136                 _, _ = w.WriteString("<blockquote>\n")
          137         }
          138 
          139         _, _ = w.WriteString(text)
          140 
          141         _, _ = w.WriteString("</blockquote>\n")
          142         return ast.WalkContinue, nil
          143 }
          144 
          145 type blockquoteContext struct {
          146         hooks.BaseContext
          147         text  hstring.HTML
          148         typ   string
          149         alert blockQuoteAlert
          150         *attributes.AttributesHolder
          151 }
          152 
          153 func (c *blockquoteContext) Type() string {
          154         return c.typ
          155 }
          156 
          157 func (c *blockquoteContext) AlertType() string {
          158         return c.alert.typ
          159 }
          160 
          161 func (c *blockquoteContext) AlertTitle() hstring.HTML {
          162         return hstring.HTML(c.alert.title)
          163 }
          164 
          165 func (c *blockquoteContext) AlertSign() string {
          166         return c.alert.sign
          167 }
          168 
          169 func (c *blockquoteContext) Text() hstring.HTML {
          170         return c.text
          171 }
          172 
          173 var blockQuoteAlertRe = regexp.MustCompile(`^<p>\[!([a-zA-Z]+)\](-|\+)?[^\S\r\n]?([^\n]*)\n?`)
          174 
          175 func resolveBlockQuoteAlert(s string) blockQuoteAlert {
          176         m := blockQuoteAlertRe.FindStringSubmatch(s)
          177         if len(m) == 4 {
          178                 title := strings.TrimSpace(m[3])
          179                 title = strings.TrimSuffix(title, "</p>")
          180                 return blockQuoteAlert{
          181                         typ:   strings.ToLower(m[1]),
          182                         sign:  m[2],
          183                         title: title,
          184                 }
          185         }
          186 
          187         return blockQuoteAlert{}
          188 }
          189 
          190 // Blockquote alert syntax was introduced by GitHub, but is also used
          191 // by Obsidian which also support some extended attributes: More types, alert titles and a +/- sign for folding.
          192 type blockQuoteAlert struct {
          193         typ   string
          194         sign  string
          195         title string
          196 }