~jan0sch/smederee
Showing details for patch 2a56d7d06bff5d7004ec3b4c6854baed1de07fa8.
diff -rN -u old-smederee/modules/hub/src/main/resources/assets/css/main.css new-smederee/modules/hub/src/main/resources/assets/css/main.css --- old-smederee/modules/hub/src/main/resources/assets/css/main.css 2025-01-31 13:57:06.982324843 +0000 +++ new-smederee/modules/hub/src/main/resources/assets/css/main.css 2025-01-31 13:57:06.982324843 +0000 @@ -345,6 +345,38 @@ border-bottom: 1px solid var(--nord4); } +.todo-default { + background-color: var(--nord15); +} + +.todo-default:hover { + background-color: transparent; +} + +.todo-error { + background-color: var(--nord11); +} + +.todo-error:hover { + background-color: transparent; +} + +.todo-info { + background-color: var(--nord14); +} + +.todo-info:hover { + background-color: transparent; +} + +.todo-warning { + background-color: var(--nord13); +} + +.todo-warning:hover { + background-color: transparent; +} + pre.latest-changes { overflow-x: auto; overflow-y: hidden; diff -rN -u old-smederee/modules/hub/src/main/scala/de/smederee/hub/MarkdownRenderer.scala new-smederee/modules/hub/src/main/scala/de/smederee/hub/MarkdownRenderer.scala --- old-smederee/modules/hub/src/main/scala/de/smederee/hub/MarkdownRenderer.scala 2025-01-31 13:57:06.982324843 +0000 +++ new-smederee/modules/hub/src/main/scala/de/smederee/hub/MarkdownRenderer.scala 2025-01-31 13:57:06.982324843 +0000 @@ -17,17 +17,21 @@ package de.smederee.hub +import java.util.Locale + import cats.syntax.all._ import org.commonmark.ext.gfm.tables.TablesExtension import org.commonmark.ext.heading.anchor.HeadingAnchorExtension import org.commonmark.ext.task.list.items.TaskListItemsExtension import org.commonmark.node._ import org.commonmark.parser.Parser +import org.commonmark.renderer.NodeRenderer import org.commonmark.renderer.html._ import org.http4s.Uri -import org.slf4j.LoggerFactory +import org.slf4j.{ Logger, LoggerFactory } import scala.jdk.CollectionConverters._ +import scala.util.matching.Regex object MarkdownRenderer { private val log = LoggerFactory.getLogger(getClass()) @@ -56,6 +60,9 @@ }) .escapeHtml(true) .extensions(MarkdownExtensions.asJava) + .nodeRendererFactory(new HtmlNodeRendererFactory { + override def create(context: HtmlNodeRendererContext): NodeRenderer = new ToDoTextRenderer(context) + }) .sanitizeUrls(true) .build() renderer.render(markdown) @@ -69,7 +76,7 @@ */ @SuppressWarnings( Array("scalafix:DisableSyntax.isInstanceOf") - ) // TODO Find a way to get rid of the isInstanceOf below. + ) // TODO: Find a way to get rid of the isInstanceOf below. class LinkHrefCorrector(private val repo: Option[VcsRepository]) extends AttributeProvider { override def setAttributes(node: Node, tagName: String, attributes: java.util.Map[String, String]): Unit = if (node.isInstanceOf[Link]) { @@ -88,4 +95,58 @@ } } } + + /** A custom text node renderer which is supposed to highlight several words which are considered "todo items". This + * is currently a very limited approach with a fixed list of matching words. + * + * @param context + * A context for an html node renderer that is needed to extract the html writer from it. + */ + class ToDoTextRenderer(context: HtmlNodeRendererContext) extends NodeRenderer { + private final val htmlWriter: HtmlWriter = context.getWriter() + /* We want to match either on specific words followed by a colon (`:`) and also on complete text nodes + * which can be created if markdown rendering is involved. + * For example a `**WORD** some text` will produce two text nodes (`WORD` and ` some text`) + * while `WORD: some text` will only produce one. + */ + private final val isToDoItem: Regex = "(?i)(.*)(DEBUG|FIXME|HACK|TODO):(.*)|^(DEBUG|FIXME|HACK|TODO)$".r + private final val log: Logger = LoggerFactory.getLogger(getClass()) + + private final val todoCssClasses: Map[String, String] = Map( + "todo" -> "todo-default", + "fixme" -> "todo-error", + "debug" -> "todo-info", + "hack" -> "todo-warning" + ).withDefaultValue("todo-default") + + override def getNodeTypes(): java.util.Set[Class[? <: Node]] = Set(classOf[Text]).asJava + + override def render(node: Node): Unit = { + val text = node.asInstanceOf[Text] // We only receive text nodes (see `getNodeTypes`). + isToDoItem.findFirstMatchIn(text.getLiteral()) match { + case Some(matchedItem) => + log.info(s"MATCH: ${text.getLiteral()} (${matchedItem.groupCount})") + if (matchedItem.group(4) === null) { + val prefix = matchedItem.group(1) + val item = matchedItem.group(2) + val suffix = matchedItem.group(3) + val cssClass = todoCssClasses(item.toLowerCase(Locale.ROOT)) + htmlWriter.text(prefix) + htmlWriter.tag("span", Map("class" -> cssClass).asJava) + htmlWriter.text(item + ":") + htmlWriter.tag("/span") + htmlWriter.text(suffix) + } else { + val item = matchedItem.group(4) + val cssClass = todoCssClasses(item.toLowerCase(Locale.ROOT)) + htmlWriter.tag("span", Map("class" -> cssClass).asJava) + htmlWriter.text(item) + htmlWriter.tag("/span") + } + case _ => + htmlWriter.text(text.getLiteral()) + } + } + + } }