1
+ {{- /* Last modified: 2023-09-04T09:23:04-07:00 */}}
2
+
3
+ {{- /*
4
+ Copyright 2023 Veriphor LLC
5
+
6
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not
7
+ use this file except in compliance with the License. You may obtain a copy of
8
+ the License at
9
+
10
+ https://www.apache.org/licenses/LICENSE-2.0
11
+
12
+ Unless required by applicable law or agreed to in writing, software
13
+ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14
+ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15
+ License for the specific language governing permissions and limitations under
16
+ the License.
17
+ */}}
18
+
19
+ {{- /*
20
+ This render hook resolves internal destinations by looking for a matching:
21
+
22
+ 1. Content page
23
+ 2. Page resource (a file in the current page bundle)
24
+ 3. Section resource (a file in the current section)
25
+ 4. Global resource (a file in the assets directory)
26
+
27
+ It skips the section resource lookup if the current page is a leaf bundle.
28
+
29
+ External destinations are not modified.
30
+
31
+ You must place global resources in the assets directory. If you have placed
32
+ your resources in the static directory, and you are unable or unwilling to move
33
+ them, you must mount the static directory to the assets directory by including
34
+ both of these entries in your site configuration:
35
+
36
+ [[module.mounts]]
37
+ source = 'assets'
38
+ target = 'assets'
39
+
40
+ [[module.mounts]]
41
+ source = 'static'
42
+ target = 'assets'
43
+
44
+ By default, if this render hook is unable to resolve a destination, including a
45
+ fragment if present, it passes the destination through without modification. To
46
+ emit a warning or error, set the error level in your site configuration:
47
+
48
+ [params.render_hooks.link]
49
+ errorLevel = 'warning' # ignore (default), warning, or error (fails the build)
50
+
51
+ When you set the error level to warning, and you are in a development
52
+ environment, you can visually highlight broken internal links:
53
+
54
+ [params.render_hooks.link]
55
+ errorLevel = 'warning' # ignore (default), warning, or error (fails the build)
56
+ highlightBroken = true # true or false (default)
57
+
58
+ This will add a "broken" class to anchor elements with invalid src attributes.
59
+ Add a rule to your CSS targeting the broken links:
60
+
61
+ a.broken {
62
+ background: #ff0;
63
+ border: 2px solid #f00;
64
+ padding: 0.1em 0.2em;
65
+ }
66
+
67
+ This render hook may be unable to resolve destinations created with the ref and
68
+ relref shortcodes. Unless you set the error level to ignore you should not use
69
+ either of these shortcodes in conjunction with this render hook.
70
+
71
+ @context {string} Destination The link destination.
72
+ @context {page} Page A reference to the page containing the link.
73
+ @context {string} PlainText The link description as plain text.
74
+ @context {string} Text The link description.
75
+ @context {string} Title The link title.
76
+
77
+ @returns {template.html}
78
+ */}}
79
+
80
+ {{- /* Initialize. */}}
81
+ {{- $renderHookName := "link" }}
82
+
83
+ {{- /* Verify minimum required version. */}}
84
+ {{- $minHugoVersion := "0.114.0" }}
85
+ {{- if lt hugo.Version $minHugoVersion }}
86
+ {{- errorf "The %q render hook requires Hugo v%s or later." $renderHookName $minHugoVersion }}
87
+ {{- end }}
88
+
89
+ {{- /* Error level when unable to resolve destination: ignore, warning, or error. */}}
90
+ {{- $errorLevel := or site.Params.render_hooks.link.errorLevel "ignore" | lower }}
91
+
92
+ {{- /* If true, adds "broken" class to broken links. Applicable in development environment when errorLevel is warning. */}}
93
+ {{- $highlightBrokenLinks := or site.Params.render_hooks.link.highlightBroken false }}
94
+
95
+ {{- /* Validate error level. */}}
96
+ {{- if not (in (slice "ignore" "warning" "error") $errorLevel) }}
97
+ {{- errorf "The %q render hook is misconfigured. The errorLevel %q is invalid. Please check your site configuration." $renderHookName $errorLevel }}
98
+ {{- end }}
99
+
100
+ {{- /* Determine content path for warning and error messages. */}}
101
+ {{- $contentPath := "" }}
102
+ {{- with .Page.File }}
103
+ {{- $contentPath = .Path }}
104
+ {{- else }}
105
+ {{- $contentPath = .Path }}
106
+ {{- end }}
107
+
108
+ {{- /* Parse destination. */}}
109
+ {{- $u := urls.Parse .Destination }}
110
+
111
+ {{- /* Set common message. */}}
112
+ {{- $msg := printf "The %q render hook was unable to resolve the destination %q in %s" $renderHookName $u.String $contentPath }}
113
+
114
+ {{- /* Set attributes for anchor element. */}}
115
+ {{- $attrs := dict "href" $u.String }}
116
+ {{- if $u.IsAbs }}
117
+ {{- /* Destination is a remote resource. */}}
118
+ {{- with .Page.Site.Params.docs.extLinkNewTab | default true }}
119
+ {{- $attrs = merge $attrs (dict "rel" "external" "target" "_blank") }}
120
+ {{- else }}
121
+ {{- $attrs = merge $attrs (dict "rel" "external") }}
122
+ {{- end -}}
123
+ {{- else }}
124
+ {{- with $u.Path }}
125
+ {{- with $p := or ($.Page.GetPage .) ($.Page.GetPage (strings.TrimRight "/" .)) }}
126
+ {{- /* Destination is a page. */}}
127
+ {{- $href := .RelPermalink }}
128
+ {{- $tooltipDelay := dict "show" 550 "hide" 300 | jsonify | htmlEscape }}
129
+ {{- $pathDesc := .Description }}
130
+ {{- $pathTitle := .Title }}
131
+ {{- $pathParentSection := .Parent.Title | upper }}
132
+ {{- $pathHTML := printf "< a href ='%s '> < p > %s</ p > < strong > %s</ strong > < br > %s</ a > " $href $pathParentSection $pathTitle $pathDesc }}
133
+ {{- with $u.RawQuery }}
134
+ {{- $href = printf "%s?%s" $href . }}
135
+ {{- end }}
136
+ {{- with $u.Fragment }}
137
+ {{- $ctx := dict
138
+ "contentPath" $contentPath
139
+ "errorLevel" $errorLevel
140
+ "page" $p
141
+ "parsedURL" $u
142
+ "renderHookName" $renderHookName
143
+ }}
144
+ {{- partial "inline/h-rh-l/validate-fragment.html" $ctx }}
145
+ {{- $href = printf "%s#%s" $href . }}
146
+ {{- end }}
147
+ {{- if eq .Page.Site.Params.docs.intLinkTooltip true }}
148
+ {{- $attrs = dict "href" $href "data-bs-toggle" "tooltip" "data-bs-delay" $tooltipDelay "data-bs-html" "true" "data-bs-title" $pathHTML }}
149
+ {{- else }}
150
+ {{- $attrs = dict "href" $href }}
151
+ {{- end -}}
152
+ {{- else }}
153
+ {{- with $.Page.Resources.Get $u.Path }}
154
+ {{- /* Destination is a page resource; drop query and fragment. */}}
155
+ {{- $attrs = dict "href" .RelPermalink }}
156
+ {{- else }}
157
+ {{- with (and (ne $.Page.BundleType "leaf") ($.Page.CurrentSection.Resources.Get $u.Path)) }}
158
+ {{- /* Destination is a section resource, and current page is not a leaf bundle. */}}
159
+ {{- $attrs = dict "href" .RelPermalink }}
160
+ {{- else }}
161
+ {{- with resources.Get $u.Path }}
162
+ {{- /* Destination is a global resource; drop query and fragment. */}}
163
+ {{- $attrs = dict "href" .RelPermalink }}
164
+ {{- else }}
165
+ {{- if eq $errorLevel "warning" }}
166
+ {{- warnf $msg }}
167
+ {{- if and $highlightBrokenLinks (eq hugo.Environment "development") }}
168
+ {{- $attrs = merge $attrs (dict "class" "broken") }}
169
+ {{- end }}
170
+ {{- else if eq $errorLevel "error" }}
171
+ {{- errorf $msg }}
172
+ {{- end }}
173
+ {{- end }}
174
+ {{- end }}
175
+ {{- end }}
176
+ {{- end }}
177
+ {{- else }}
178
+ {{- with $u.Fragment }}
179
+ {{- /* Destination is on the same page; prepend relative permalink. */}}
180
+ {{- $ctx := dict
181
+ "contentPath" $contentPath
182
+ "errorLevel" $errorLevel
183
+ "page" $.Page
184
+ "parsedURL" $u
185
+ "renderHookName" $renderHookName
186
+ }}
187
+ {{- partial "inline/h-rh-l/validate-fragment.html" $ctx }}
188
+ {{- $attrs = dict "href" (printf "%s#%s" $.Page.RelPermalink .) }}
189
+ {{- else }}
190
+ {{- if eq $errorLevel "warning" }}
191
+ {{- warnf $msg }}
192
+ {{- if and $highlightBrokenLinks (eq hugo.Environment "development") }}
193
+ {{- $attrs = merge $attrs (dict "class" "broken") }}
194
+ {{- end }}
195
+ {{- else if eq $errorLevel "error" }}
196
+ {{- errorf $msg }}
197
+ {{- end }}
198
+ {{- end }}
199
+ {{- end }}
200
+ {{- end }}
201
+ {{- with .Title }}
202
+ {{- $attrs = merge $attrs (dict "title" .) }}
203
+ {{- end -}}
204
+
205
+ {{- /* Render anchor element. */ -}}
206
+ < a
207
+ {{- range $k, $v : = $attrs }}
208
+ {{- printf " %s=%q " $k $v | safeHTMLAttr }}
209
+ {{- end -}}
210
+ > {{ .Text | safeHTML }}{{- if $u.IsAbs }}{{ with .Page.Site.Params.docs.extLinkNewTab | default true }}< svg width ="16 " height ="16 " viewBox ="0 0 24 24 " xmlns ="http://www.w3.org/2000/svg "> < path fill ="currentColor " d ="M14 5c-.552 0-1-.448-1-1s.448-1 1-1h6c.552 0 1 .448 1 1v6c0 .552-.448 1-1 1s-1-.448-1-1v-3.586l-7.293 7.293c-.391.39-1.024.39-1.414 0-.391-.391-.391-1.024 0-1.414l7.293-7.293h-3.586zm-9 2c-.552 0-1 .448-1 1v11c0 .552.448 1 1 1h11c.552 0 1-.448 1-1v-4.563c0-.552.448-1 1-1s1 .448 1 1v4.563c0 1.657-1.343 3-3 3h-11c-1.657 0-3-1.343-3-3v-11c0-1.657 1.343-3 3-3h4.563c.552 0 1 .448 1 1s-.448 1-1 1h-4.563z "/> </ svg > {{ end }}{{ end }}</ a >
211
+
212
+ {{- define "partials/inline/h-rh-l/validate-fragment.html" }}
213
+ {{- /*
214
+ Validates the fragment portion of a link destination.
215
+
216
+ @context {string} contentPath The page containing the link.
217
+ @context {srting} errorLevel The error level when unable to resolve destination; ignore (default), warning, or error.
218
+ @context {page} page The page corresponding to the link destination
219
+ @context {struct} parsedURL The link destination parsed by urls.Parse.
220
+ @context {string} renderHookName The name of the render hook.
221
+ */}}
222
+
223
+ {{- /* Initialize. */}}
224
+ {{- $contentPath := .contentPath }}
225
+ {{- $errorLevel := .errorLevel }}
226
+ {{- $p := .page }}
227
+ {{- $u := .parsedURL }}
228
+ {{- $renderHookName := .renderHookName }}
229
+
230
+ {{- /* Validate. */}}
231
+ {{- with $u.Fragment }}
232
+ {{- if $p.Fragments.Identifiers.Contains . }}
233
+ {{- if gt ($p.Fragments.Identifiers.Count .) 1 }}
234
+ {{- $msg := printf "The %q render hook detected duplicate heading IDs %q in %s" $renderHookName . $contentPath }}
235
+ {{- if eq $errorLevel "warning" }}
236
+ {{- warnf $msg }}
237
+ {{- else if eq $errorLevel "error" }}
238
+ {{- errorf $msg }}
239
+ {{- end }}
240
+ {{- end }}
241
+ {{- else }}
242
+ {{- /* Determine target path for warning and error message. */}}
243
+ {{- $targetPath := "" }}
244
+ {{- with $p.File }}
245
+ {{- $targetPath = .Path }}
246
+ {{- else }}
247
+ {{- $targetPath = .Path }}
248
+ {{- end }}
249
+ {{- /* Set common message. */}}
250
+ {{- $msg := printf "The %q render hook was unable to find heading ID %q in %s. See %s" $renderHookName . $targetPath $contentPath }}
251
+ {{- if eq $targetPath $contentPath }}
252
+ {{- $msg = printf "The %q render hook was unable to find heading ID %q in %s" $renderHookName . $targetPath }}
253
+ {{- end }}
254
+ {{- /* Throw warning or error. */}}
255
+ {{- if eq $errorLevel "warning" }}
256
+ {{- warnf $msg }}
257
+ {{- else if eq $errorLevel "error" }}
258
+ {{- errorf $msg }}
259
+ {{- end }}
260
+ {{- end }}
261
+ {{- end }}
262
+ {{- end -}}
0 commit comments