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 }