Odoc plugins for jon.recoil.org
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add tag system, atom.xml test rule, and markdown generation

- Add @page-tags metadata to 39 blog posts across 10 tags (ai, docs-ci,
meta, notebooks, ocaml, odoc, plugins, teaching, tessera, weeknotes)
- Generate tag pages with post lists via gen_blog_index.exe
- @page-tags plugin now uses resolved odoc references for chip links
instead of hardcoded URLs
- gen_atom.ml: deterministic timestamps, CLI args, UTF-8-safe truncation
- gen_rules.ml: add markdown generation phase (@markdown alias), atom.xml
and tag page diff rules under @runtest
- Include weeknotes-2026-15 in blog indexes
- Skip dotfiles in blog scanner (fixes Emacs lockfile crash)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+72 -49
+72 -49
src/odoc_jons_plugins.ml
··· 434 434 padding: 0.15em 0.65em; 435 435 font-size: 0.8rem; 436 436 line-height: 1.4; 437 - color: var(--text-muted, #666); 438 437 background: var(--surface-alt, #f3f3f3); 439 438 border: 1px solid var(--border-color, #e0e0e0); 440 439 border-radius: 999px; 440 + transition: background 0.15s ease, color 0.15s ease; 441 + } 442 + .page-tags .tag-chip a { 443 + color: var(--text-muted, #666); 441 444 text-decoration: none; 442 - transition: background 0.15s ease, color 0.15s ease; 443 445 } 444 446 .page-tags .tag-chip:hover { 445 - color: var(--accent-color, #b44e2d); 446 447 background: var(--bg-hover, #eee); 447 448 } 449 + .page-tags .tag-chip:hover a { 450 + color: var(--accent-color, #b44e2d); 451 + } 448 452 @media (prefers-color-scheme: dark) { 449 453 .page-tags .tag-chip { 450 - color: var(--text-muted, #aaa); 451 454 background: rgba(255,255,255,0.04); 452 455 border-color: rgba(255,255,255,0.1); 456 + } 457 + .page-tags .tag-chip a { 458 + color: var(--text-muted, #aaa); 453 459 } 454 460 } 455 461 |} ··· 492 498 let raw_block html = 493 499 Odoc_document.Types.Block.{ attr = []; desc = Raw_markup ("html", html) } 494 500 495 - (* HTML-escape a tag (tags are already constrained to safe chars, but 496 - be defensive). *) 497 - let escape_attr s = 498 - let b = Buffer.create (Stdlib.String.length s) in 499 - Stdlib.String.iter (fun c -> 500 - match c with 501 - | '&' -> Buffer.add_string b "&amp;" 502 - | '<' -> Buffer.add_string b "&lt;" 503 - | '>' -> Buffer.add_string b "&gt;" 504 - | '"' -> Buffer.add_string b "&quot;" 505 - | c -> Buffer.add_char b c 506 - ) s; 507 - Buffer.contents b 508 - 509 - let render_chips tags = 510 - let buf = Buffer.create 256 in 511 - Buffer.add_string buf {|<div class="page-tags">|}; 512 - List.iter (fun tag -> 513 - let t = escape_attr tag in 514 - Buffer.add_string buf 515 - (Printf.sprintf {|<a class="tag-chip" href="/tags/%s">%s</a>|} t t) 516 - ) tags; 517 - Buffer.add_string buf "</div>"; 518 - Buffer.contents buf 519 - 520 501 let to_document ~tag:_ content = 521 - let tags = extract_tags content in 522 - let content = 523 - if tags = [] then [] 524 - else [ raw_block (render_chips tags) ] 525 - in 526 - { 527 - Api.content; 528 - overrides = []; 529 - resources = [ Api.Css_inline page_tags_css ]; 530 - assets = []; 531 - } 502 + let blocks = Api.blocks_of_nestable_elements content in 503 + let open Odoc_document.Types in 504 + (* After link phase, the content is a paragraph of resolved references. 505 + Extract the Link elements and render them as chips. *) 506 + let links = List.concat_map (fun (b : Block.one) -> 507 + match b.desc with 508 + | Paragraph inlines | Inline inlines -> 509 + List.filter_map (fun (i : Inline.one) -> 510 + match i.desc with 511 + | Link _ -> Some i 512 + | _ -> None 513 + ) inlines 514 + | _ -> [] 515 + ) blocks in 516 + if links = [] then 517 + (* Fallback: just render the plain text tags *) 518 + Api.simple_output blocks 519 + else 520 + let result = ref [] in 521 + let add b = result := b :: !result in 522 + add (raw_block {|<div class="page-tags">|}); 523 + List.iter (fun (link : Inline.one) -> 524 + add (raw_block {|<span class="tag-chip">|}); 525 + add Block.{ attr = []; desc = Inline [link] }; 526 + add (raw_block {|</span>|}) 527 + ) links; 528 + add (raw_block {|</div>|}); 529 + { 530 + content = List.rev !result; 531 + overrides = []; 532 + resources = [ Api.Css_inline page_tags_css ]; 533 + assets = []; 534 + } 532 535 536 + (* Link phase: replace tag text with resolved Reference elements 537 + pointing to each tag's page. By constructing resolved references, 538 + odoc generates correct links without hardcoded URLs. *) 533 539 let link ~tag:_ env content = 534 540 let tags = extract_tags content in 535 - List.iter (fun tag -> 541 + let dummy_loc = { 542 + Api.Location_.file = ""; start = { line = 0; column = 0 }; 543 + end_ = { line = 0; column = 0 } 544 + } in 545 + let loc v = { Api.Location_.value = v; location = dummy_loc } in 546 + let refs = List.filter_map (fun tag -> 536 547 let hierarchy : Odoc_model.Paths.Reference.Hierarchy.t = 537 548 (`TCurrentPackage, [ "tags"; tag ]) 538 549 in 539 550 match Api.Env.lookup_page_by_path hierarchy env with 540 - | Ok _ -> () 551 + | Ok page -> 552 + let resolved_ref : Odoc_model.Paths.Reference.Resolved.t = 553 + `Identifier (page.name :> Odoc_model.Paths.Identifier.t) 554 + in 555 + Some (loc (`Reference (`Resolved resolved_ref, [ loc (`Word tag) ]))) 541 556 | Error _ -> 542 - failwith 543 - (Printf.sprintf 544 - "@page-tags: no page found for tag '%s'. Create \ 545 - site/tags/%s.mld before using this tag." 546 - tag tag) 547 - ) tags; 548 - content 557 + Format.eprintf 558 + "@page-tags: no page found for tag '%s'. Create \ 559 + site/tags/%s.mld before using this tag.@." tag tag; 560 + None 561 + ) tags in 562 + if refs = [] then content 563 + else 564 + let spaced = 565 + List.concat_map (fun r -> [loc `Space; r]) refs 566 + |> List.tl (* drop leading space *) 567 + in 568 + [ loc (`Paragraph spaced) ] 549 569 end 550 570 551 571 let () = 552 572 Api.Registry.register_with_link (module Page_tags) 573 + 574 + let () = 575 + hidden_tag_extension "tagged-pages" 553 576 554 577 (* Whitespace-separated tokeniser with support for double-quoted 555 578 values. Used by @figure and the image / linked-image inlines. *)