From de5d16f4dfa444f958af55a1319b4893663451a1 Mon Sep 17 00:00:00 2001 From: Andrew Valencik Date: Sun, 29 Mar 2026 09:27:59 -0400 Subject: [PATCH 1/4] Better error messages for bad SVG lookups Previously if a svg directive didn't find an icon the build would fail with a `java.util.NoSuchElementException` but without any indication of what file the bad lookup happen in. By using an Either and Laika's Directive's `evalMap` we get nicer build errors with file names and locations. --- build.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/build.scala b/build.scala index fc9d13c9..87943df3 100644 --- a/build.scala +++ b/build.scala @@ -272,10 +272,10 @@ object LaikaCustomizations { }, TemplateDirectives.create("svg") { import TemplateDirectives.dsl.* - attribute(0).as[String].map { icon => - TemplateElement( - RawContent(NonEmptySet.of("html", "rss"), Icons(icon)) - ) + attribute(0).as[String].evalMap { icon => + Icons.get(icon).toRight(s"Unknown SVG icon '$icon'").map { svg => + TemplateElement(RawContent(NonEmptySet.of("html", "rss"), svg)) + } } } ) From ecf6f6008ec54a7a49551c5bdda1dd6b620b19c1 Mon Sep 17 00:00:00 2001 From: Andrew Valencik Date: Sun, 29 Mar 2026 10:08:31 -0400 Subject: [PATCH 2/4] Handle KaTeX parse errors --- build.scala | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/build.scala b/build.scala index 87943df3..ba02c70b 100644 --- a/build.scala +++ b/build.scala @@ -284,10 +284,9 @@ object LaikaCustomizations { val spanDirectives = Seq( SpanDirectives.create("math") { import SpanDirectives.dsl.* - rawBody.map { body => - RawContent( - NonEmptySet.of("html", "rss"), - KaTeX(body, false) + rawBody.evalMap { body => + KaTeX(body, false).map(katexStr => + RawContent(NonEmptySet.of("html", "rss"), katexStr) ) } } @@ -295,11 +294,9 @@ object LaikaCustomizations { val blockDirectives = Seq( BlockDirectives.create("math") { import BlockDirectives.dsl.* - rawBody.map { body => - RawContent( - NonEmptySet.of("html", "rss"), - KaTeX(body, true), - Styles("bulma-has-text-centered") + rawBody.evalMap { body => + KaTeX(body, true).map(katexStr => + RawContent(NonEmptySet.of("html", "rss"), katexStr, Styles("bulma-has-text-centered")) ) } }, @@ -438,14 +435,18 @@ object KaTeX { ctx.getBindings("js").getMember("katex") } - def apply(latex: String, displayMode: Boolean = false): String = + def apply(latex: String, displayMode: Boolean = false): Either[String, String] = synchronized { val options = Map( "throwOnError" -> true, "strict" -> true, "displayMode" -> displayMode - ) - katex.invokeMember("renderToString", latex, options.asJava).asString + ).asJava + try { + Right(katex.invokeMember("renderToString", latex, options).asString) + } catch { + case ex: Exception => Left(ex.getMessage) + } } } From 0bac71d6d4071e1bf549d5d35b684c940d2bdac2 Mon Sep 17 00:00:00 2001 From: Andrew Valencik Date: Sun, 29 Mar 2026 10:18:08 -0400 Subject: [PATCH 3/4] Fix formatting --- build.scala | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/build.scala b/build.scala index ba02c70b..9d827d5a 100644 --- a/build.scala +++ b/build.scala @@ -296,7 +296,11 @@ object LaikaCustomizations { import BlockDirectives.dsl.* rawBody.evalMap { body => KaTeX(body, true).map(katexStr => - RawContent(NonEmptySet.of("html", "rss"), katexStr, Styles("bulma-has-text-centered")) + RawContent( + NonEmptySet.of("html", "rss"), + katexStr, + Styles("bulma-has-text-centered") + ) ) } }, @@ -435,7 +439,10 @@ object KaTeX { ctx.getBindings("js").getMember("katex") } - def apply(latex: String, displayMode: Boolean = false): Either[String, String] = + def apply( + latex: String, + displayMode: Boolean = false + ): Either[String, String] = synchronized { val options = Map( "throwOnError" -> true, From 72cbc57ee969788101a5e7284660e70b1d36469f Mon Sep 17 00:00:00 2001 From: Andrew Valencik Date: Sun, 29 Mar 2026 12:20:40 -0400 Subject: [PATCH 4/4] Rename KaTeX.apply to KaTeX.render --- build.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build.scala b/build.scala index ab78f3ea..736f6610 100644 --- a/build.scala +++ b/build.scala @@ -285,7 +285,7 @@ object LaikaCustomizations { SpanDirectives.create("math") { import SpanDirectives.dsl.* rawBody.evalMap { body => - (KaTeX(body, false), KaTeX(body, false, "mathml")).mapN( + (KaTeX.render(body, false), KaTeX.render(body, false, "mathml")).mapN( (katexStr, mathmlStr) => SpanSequence( RawContent(NonEmptySet.of("html"), katexStr), @@ -299,7 +299,7 @@ object LaikaCustomizations { BlockDirectives.create("math") { import BlockDirectives.dsl.* rawBody.evalMap { body => - (KaTeX(body, true), KaTeX(body, true, "mathml")).mapN( + (KaTeX.render(body, true), KaTeX.render(body, true, "mathml")).mapN( (katexStr, mathmlStr) => BlockSequence( RawContent( @@ -454,7 +454,7 @@ object KaTeX { ctx.getBindings("js").getMember("katex") } - def apply( + def render( latex: String, displayMode: Boolean = false, output: String = "htmlAndMathml"