forked from tangled.org/core
this repo has no description

appview/pages/markup: insert anchor link in headings

Signed-off-by: oppiliappan <me@oppi.li>

authored by oppi.li and committed by Tangled bb4ac7ec 82f4ca08

Changed files
+63 -2
appview
pages
+39 -1
appview/pages/markup/markdown.go
··· 177 177 switch a.rctx.RendererType { 178 178 case RendererTypeRepoMarkdown: 179 179 switch n := n.(type) { 180 + case *ast.Heading: 181 + a.rctx.anchorHeadingTransformer(n) 180 182 case *ast.Link: 181 183 a.rctx.relativeLinkTransformer(n) 182 184 case *ast.Image: ··· 185 187 } 186 188 case RendererTypeDefault: 187 189 switch n := n.(type) { 190 + case *ast.Heading: 191 + a.rctx.anchorHeadingTransformer(n) 188 192 case *ast.Image: 189 193 a.rctx.imageFromKnotAstTransformer(n) 190 194 a.rctx.camoImageLinkAstTransformer(n) ··· 199 203 200 204 dst := string(link.Destination) 201 205 202 - if isAbsoluteUrl(dst) { 206 + if isAbsoluteUrl(dst) || isFragment(dst) || isMail(dst) { 203 207 return 204 208 } 205 209 ··· 240 244 img.Destination = []byte(rctx.imageFromKnotTransformer(dst)) 241 245 } 242 246 247 + func (rctx *RenderContext) anchorHeadingTransformer(h *ast.Heading) { 248 + idGeneric, exists := h.AttributeString("id") 249 + if !exists { 250 + return // no id, nothing to do 251 + } 252 + id, ok := idGeneric.([]byte) 253 + if !ok { 254 + return 255 + } 256 + 257 + // create anchor link 258 + anchor := ast.NewLink() 259 + anchor.Destination = fmt.Appendf(nil, "#%s", string(id)) 260 + anchor.SetAttribute([]byte("class"), []byte("anchor")) 261 + 262 + // create icon text 263 + iconText := ast.NewString([]byte("#")) 264 + anchor.AppendChild(anchor, iconText) 265 + 266 + // set class on heading 267 + h.SetAttribute([]byte("class"), []byte("heading")) 268 + 269 + // append anchor to heading 270 + h.AppendChild(h, anchor) 271 + } 272 + 243 273 // actualPath decides when to join the file path with the 244 274 // current repository directory (essentially only when the link 245 275 // destination is relative. if it's absolute then we assume the ··· 259 289 } 260 290 return parsed.IsAbs() 261 291 } 292 + 293 + func isFragment(link string) bool { 294 + return strings.HasPrefix(link, "#") 295 + } 296 + 297 + func isMail(link string) bool { 298 + return strings.HasPrefix(link, "mailto:") 299 + }
+1
appview/pages/markup/sanitizer.go
··· 65 65 // for code blocks 66 66 policy.AllowAttrs("class").Matching(regexp.MustCompile(`chroma`)).OnElements("pre") 67 67 policy.AllowAttrs("class").Matching(regexp.MustCompile(`anchor|footnote-ref|footnote-backref`)).OnElements("a") 68 + policy.AllowAttrs("class").Matching(regexp.MustCompile(`heading`)).OnElements("h1", "h2", "h3", "h4", "h5", "h6", "h7", "h8") 68 69 policy.AllowAttrs("class").Matching(regexp.MustCompile(strings.Join(slices.Collect(maps.Values(chroma.StandardTypes)), "|"))).OnElements("span") 69 70 70 71 // centering content
+23 -1
input.css
··· 134 134 disabled:before:bg-green-400 dark:disabled:before:bg-green-600; 135 135 } 136 136 137 + .prose hr { 138 + @apply my-2; 139 + } 140 + 137 141 .prose li:has(input) { 138 - list-style: none; 142 + @apply list-none; 143 + } 144 + 145 + .prose ul:has(input) { 146 + @apply pl-2; 147 + } 148 + 149 + .prose .heading .anchor { 150 + @apply no-underline mx-2 opacity-0; 151 + } 152 + 153 + .prose .heading:hover .anchor { 154 + @apply opacity-70; 155 + } 156 + 157 + .prose .heading .anchor:hover { 158 + @apply opacity-70; 159 + } 160 + 139 161 .prose a.footnote-backref { 140 162 @apply no-underline; 141 163 }