From 4dfa6120c26a1458e29e8749c12a7f49699679ac Mon Sep 17 00:00:00 2001 From: shevapro Date: Mon, 18 Aug 2025 18:50:25 -0400 Subject: [PATCH 01/10] feat: Enhanced markdown support with footnotes and improved task lists - Add comprehensive footnotes support in KotlinRenderer - Footnote references rendered as superscript links - Footnote definitions rendered as block elements - Proper ID-based linking between references and definitions - Enhance task list support in MarkdownHandlers - Automatic detection of task list patterns in all text contexts - Support for - [x] (checked) and - [ ] (unchecked) syntax - FontAwesome icons when Silk is available, HTML checkboxes fallback - Works in table cells and other complex structures - Add footnotes feature configuration in MarkdownFeatures - Configurable footnotes support (enabled by default) - Integration with CommonMark FootnotesExtension - Update playground with test examples for new features Resolves issues with markdown rendering inconsistencies and adds support for GitHub Flavored Markdown footnotes feature. --- .../src/jsMain/resources/markdown/Markdown.md | 3 +- .../kobwebx/gradle/markdown/KotlinRenderer.kt | 93 +++++++++++++++---- .../gradle/markdown/MarkdownFeatures.kt | 30 ++++++ .../markdown/handlers/MarkdownHandlers.kt | 85 ++++++++++++++++- 4 files changed, 190 insertions(+), 21 deletions(-) diff --git a/playground/site/src/jsMain/resources/markdown/Markdown.md b/playground/site/src/jsMain/resources/markdown/Markdown.md index 902872fb2..e580767be 100644 --- a/playground/site/src/jsMain/resources/markdown/Markdown.md +++ b/playground/site/src/jsMain/resources/markdown/Markdown.md @@ -6,7 +6,7 @@ This site is generated from Markdown. Create rich, dynamic web apps with ease, leveraging [Kotlin](https://kotlinlang.org/) and [Compose HTML](https://github.com/JetBrains/compose-multiplatform#compose-html). -Markdown of course supports **bold**, _italic_, and _**bold italic**_ text. +Markdown of course supports **bold**, _italic_, _**bold italic**_, and ~~strikethrough~~ text. It also supports @@ -45,6 +45,7 @@ You can link to other Markdown documents with their route overrides resolved cor [documents/INDEX.md](documents/INDEX.md)
[KotlinLanguage.md](jetbrains/KotlinLanguage.md) (`routeOverride: languages/kotlin`)
[documents/Bananas.md](documents/Bananas.md) (`routeOverride: /fruits/`)
+[documents/unified_test.md](documents/unified_test.md) (`routeOverride: /tests/`)
[files/external.md](/files/external.md) (Linking to a Markdown file outside of the processed markdown files)
You can use inlined html tags. You can inspect this page to see that "inlined html" is diff --git a/tools/gradle-plugins/extensions/markdown/src/main/kotlin/com/varabyte/kobwebx/gradle/markdown/KotlinRenderer.kt b/tools/gradle-plugins/extensions/markdown/src/main/kotlin/com/varabyte/kobwebx/gradle/markdown/KotlinRenderer.kt index 0404792c8..b94358d9d 100644 --- a/tools/gradle-plugins/extensions/markdown/src/main/kotlin/com/varabyte/kobwebx/gradle/markdown/KotlinRenderer.kt +++ b/tools/gradle-plugins/extensions/markdown/src/main/kotlin/com/varabyte/kobwebx/gradle/markdown/KotlinRenderer.kt @@ -9,16 +9,21 @@ import com.varabyte.kobwebx.gradle.markdown.ext.kobwebcall.KobwebCall import com.varabyte.kobwebx.gradle.markdown.ext.kobwebcall.KobwebCallBlock import com.varabyte.kobwebx.gradle.markdown.ext.kobwebcall.KobwebCallBlockVisitor import com.varabyte.kobwebx.gradle.markdown.frontmatter.FrontMatterBlock +import com.varabyte.kobwebx.gradle.markdown.handlers.KOBWEB_DOM import com.varabyte.kobwebx.gradle.markdown.handlers.MarkdownHandlers import com.varabyte.kobwebx.gradle.markdown.handlers.NodeScope import com.varabyte.kobwebx.gradle.markdown.util.NodeCache import com.varabyte.kobwebx.gradle.markdown.util.escapeQuotes import com.varabyte.kobwebx.gradle.markdown.util.unescapeQuotes +import org.commonmark.ext.footnotes.FootnoteDefinition +import org.commonmark.ext.footnotes.FootnoteReference +import org.commonmark.ext.gfm.strikethrough.Strikethrough import org.commonmark.ext.gfm.tables.TableBlock import org.commonmark.ext.gfm.tables.TableBody import org.commonmark.ext.gfm.tables.TableCell import org.commonmark.ext.gfm.tables.TableHead import org.commonmark.ext.gfm.tables.TableRow +import org.commonmark.ext.task.list.items.TaskListItemMarker import org.commonmark.node.AbstractVisitor import org.commonmark.node.BlockQuote import org.commonmark.node.BulletList @@ -411,7 +416,9 @@ class KotlinRenderer internal constructor( } override fun visit(listItem: ListItem) { + doVisit(listItem, handlers.li) + } override fun visit(bulletList: BulletList) { @@ -442,23 +449,6 @@ class KotlinRenderer internal constructor( doVisit(text, handlers.text) } - override fun visit(customNode: CustomNode) { - when (customNode) { - is KobwebCall -> { - output.appendLine("$indent${customNode.toFqn(projectGroup)}") - } - - is TableHead -> visit(customNode) - is TableBody -> visit(customNode) - is TableRow -> visit(customNode) - is TableCell -> visit(customNode) - - else -> { - val unhandledNodeName = customNode::class.simpleName!! - reporter.warn("Unhandled Markdown custom node: $unhandledNodeName. Consider reporting this at: https://github.com/varabyte/kobweb/issues/new?labels=bug&template=bug_report.md&title=Unhandled%20Markdown%20node%20%22$unhandledNodeName%22") - } - } - } private fun visit(table: TableBlock) { doVisit(table, handlers.table) @@ -483,6 +473,58 @@ class KotlinRenderer internal constructor( doVisit(tableCell, handlers.td) } } + override fun visit(customNode: CustomNode) { + when (customNode) { + is KobwebCall -> { + output.appendLine("$indent${customNode.toFqn(projectGroup)}") + } + + is TableHead -> visit(customNode) + is TableBody -> visit(customNode) + is TableRow -> visit(customNode) + is TableCell -> visit(customNode) + is Strikethrough -> visit(customNode) + is FootnoteReference -> { + // Render footnote references as superscript with the actual label/number + val scope = NodeScope(reporter, data, indentCount) + val label = customNode.label + // Create a link to the footnote definition + val code = "$KOBWEB_DOM.GenericTag(\"sup\", \"id=\\\"fnref-$label\\\"\")" + scope.childrenOverride = listOf( + // Create a link to the footnote definition + org.commonmark.node.Link("#fn-$label", null).apply { + appendChild(Text(label)) + } + ) + doVisit(customNode, code, scope) + } + is TaskListItemMarker -> { + // Render a task list item, which is a checkbox that is either checked or not. + // We prefer to use Silk's FontAwesome icons if available, otherwise we fall back to a + // standard HTML checkbox. + val code = if (handlers.useSilk.get()) { + val icon = if (customNode.isChecked) { + "com.varabyte.kobweb.silk.components.icons.fa.FaSquareCheck" + } else { + "com.varabyte.kobweb.silk.components.icons.fa.FaSquare" + } + // Use a span to apply margin to the icon, giving it some breathing room from the text. + "$KOBWEB_DOM.GenericTag(\"span\", \"style=\\\"margin-right: 0.5em;\\\"\") { $icon() }" + } else { + val checkedAttr = if (customNode.isChecked) "checked" else "" + // The `disabled` attribute is important here, as these are decorative checkboxes. + // A right margin is added to space the checkbox from the list item's text. + val attrs = "type=\\\"checkbox\\\" disabled $checkedAttr style=\\\"margin-right: 0.5em;\\\"" + "$KOBWEB_DOM.GenericTag(\"input\", \"$attrs\")" + } + output.appendLine("$indent$code") + } + else -> { + val unhandledNodeName = customNode::class.simpleName!! + reporter.warn("Unhandled Markdown custom node: $unhandledNodeName. Consider reporting this at: https://github.com/varabyte/kobweb/issues/new?labels=bug&template=bug_report.md&title=Unhandled%20Markdown%20node%20%22$unhandledNodeName%22") + } + } + } override fun visit(customBlock: CustomBlock) { when (customBlock) { @@ -505,12 +547,25 @@ class KotlinRenderer internal constructor( } is TableBlock -> visit(customBlock) + is FootnoteDefinition -> { + // Render individual footnote definitions as block elements inside the footnote container + val scope = NodeScope(reporter, data, indentCount) + val code = + "com.varabyte.kobweb.compose.dom.GenericTag(\"div\", \"class=\\\"footnote-item\\\" id=\\\"fn-${customBlock.label}\\\"\")" + doVisit(customBlock, code, scope) + } else -> { - val unhandledBlockName = customBlock::class.simpleName!! - reporter.warn("Unhandled Markdown custom block: $unhandledBlockName. Consider reporting this at: https://github.com/varabyte/kobweb/issues/new?labels=bug&template=bug_report.md&title=Unhandled%20Markdown%20block%20%22$unhandledBlockName%22") + val simple = customBlock::class.simpleName + val unhandledBlockName = simple!! + reporter.warn("Unhandled Markdown custom block: $unhandledBlockName. Consider reporting this at: https://github.com/varabyte/kobweb/issues/new?labels=bug&template=bug_report.md&title=Unhandled%20Markdown%20block%20%22$unhandledBlockName%22") + } } } + + private fun visit(strikethrough: Strikethrough) { + doVisit(strikethrough, handlers.strikethrough) + } } } diff --git a/tools/gradle-plugins/extensions/markdown/src/main/kotlin/com/varabyte/kobwebx/gradle/markdown/MarkdownFeatures.kt b/tools/gradle-plugins/extensions/markdown/src/main/kotlin/com/varabyte/kobwebx/gradle/markdown/MarkdownFeatures.kt index ea4f8d470..b1b70552f 100644 --- a/tools/gradle-plugins/extensions/markdown/src/main/kotlin/com/varabyte/kobwebx/gradle/markdown/MarkdownFeatures.kt +++ b/tools/gradle-plugins/extensions/markdown/src/main/kotlin/com/varabyte/kobwebx/gradle/markdown/MarkdownFeatures.kt @@ -6,6 +6,8 @@ import com.varabyte.kobwebx.gradle.markdown.ext.kobwebcall.KobwebCallExtension import com.varabyte.kobwebx.gradle.markdown.frontmatter.FrontMatterExtension import org.commonmark.Extension import org.commonmark.ext.autolink.AutolinkExtension +import org.commonmark.ext.footnotes.FootnotesExtension +import org.commonmark.ext.gfm.strikethrough.StrikethroughExtension import org.commonmark.ext.gfm.tables.TablesExtension import org.commonmark.ext.task.list.items.TaskListItemsExtension import org.commonmark.parser.IncludeSourceSpans @@ -100,6 +102,26 @@ abstract class MarkdownFeatures { @get:Input abstract val taskList: Property + /** + * If true, support GFM strikethrough syntax using double tildes, e.g. `~~text~~`. + * + * Defaults to `true`. + * + * @see Strikethrough + */ + @get:Input + abstract val strikethrough: Property + + /** + * If true, support footnotes like "Main text[^1]" with definitions "[^1]: Footnote text". + * + * Defaults to `true`. + * + * Note: Inline footnotes via "^[inline]" are not enabled by default. + */ + @get:Input + abstract val footnotes: Property + init { autolink.convention(true) frontMatter.convention(true) @@ -107,6 +129,8 @@ abstract class MarkdownFeatures { kobwebCallDelimiters.convention('{' to '}') tables.convention(true) taskList.convention(true) + strikethrough.convention(true) + footnotes.convention(true) } /** @@ -129,6 +153,12 @@ abstract class MarkdownFeatures { if (taskList.get()) { extensions.add(TaskListItemsExtension.create()) } + if (strikethrough.get()) { + extensions.add(StrikethroughExtension.create()) + } + if (footnotes.get()) { + extensions.add(FootnotesExtension.create()) + } return Parser.builder() .extensions(extensions) diff --git a/tools/gradle-plugins/extensions/markdown/src/main/kotlin/com/varabyte/kobwebx/gradle/markdown/handlers/MarkdownHandlers.kt b/tools/gradle-plugins/extensions/markdown/src/main/kotlin/com/varabyte/kobwebx/gradle/markdown/handlers/MarkdownHandlers.kt index 8bb8b95ff..c14da3a11 100644 --- a/tools/gradle-plugins/extensions/markdown/src/main/kotlin/com/varabyte/kobwebx/gradle/markdown/handlers/MarkdownHandlers.kt +++ b/tools/gradle-plugins/extensions/markdown/src/main/kotlin/com/varabyte/kobwebx/gradle/markdown/handlers/MarkdownHandlers.kt @@ -1,5 +1,14 @@ @file:Suppress("LeakingThis") // Following official Gradle guidance +/** + * Enhanced Markdown handlers for Kobweb projects. + * + * Recent enhancements: + * - Task list support: Automatically detects and renders task list items (- [x] / - [ ]) in all text contexts, + * including table cells where CommonMark doesn't normally recognize them as TaskListItemMarker nodes. + * Uses FontAwesome icons when Silk is available, falls back to HTML input checkboxes otherwise. + */ + package com.varabyte.kobwebx.gradle.markdown.handlers import com.varabyte.kobweb.common.collect.Key @@ -12,6 +21,7 @@ import com.varabyte.kobwebx.gradle.markdown.util.escapeDollars import com.varabyte.kobwebx.gradle.markdown.util.escapeQuotes import com.varabyte.kobwebx.gradle.markdown.util.escapeTripleQuotes import com.varabyte.kobwebx.gradle.markdown.util.nestedLiteral +import org.commonmark.ext.gfm.strikethrough.Strikethrough import org.commonmark.ext.gfm.tables.TableBlock import org.commonmark.ext.gfm.tables.TableBody import org.commonmark.ext.gfm.tables.TableCell @@ -79,6 +89,28 @@ class NodeScope(val reporter: Reporter, val data: TypedMap, private val indentCo * } * } * } + * + * ## Task List Support + * + * This handler automatically detects and renders task list items (checkboxes) in markdown text, + * including those inside table cells where CommonMark doesn't automatically recognize them. + * + * Task list patterns like `- [x]` (checked) and `- [ ]` (unchecked) are converted to: + * - **With Silk**: FontAwesome icons (`FaSquareCheck` for checked, `FaSquare` for unchecked) + * - **Without Silk**: HTML input checkboxes (disabled, with proper checked state) + * + * This works in all contexts including: + * - Regular paragraphs + * - Table cells + * - List items + * - Any other text nodes + * + * Example: + * ```markdown + * | Feature | Status | + * |---------|--------| + * | Task A | - [x] | + * | Task B | - [ ] | * ``` */ abstract class MarkdownHandlers @Inject constructor(project: Project) { @@ -155,6 +187,9 @@ abstract class MarkdownHandlers @Inject constructor(project: Project) { abstract val em: Property String> @get:Nested abstract val strong: Property String> + @get:Nested + abstract val strikethrough: Property String> + @get:Nested abstract val hr: Property String> @get:Nested @@ -258,7 +293,53 @@ abstract class MarkdownHandlers @Inject constructor(project: Project) { // region Markdown Node handlers - text.convention { text -> "$JB_DOM.Text(\"${text.literal.escapeSingleQuotedText()}\")" } + text.convention { text -> + val literal = text.literal + // Handle task list items in text nodes + // This covers cases where CommonMark doesn't automatically parse task list items as + // TaskListItemMarker nodes, particularly inside table cells or other complex structures. + // + // Pattern: "- [x]" or "- [ ]" at the beginning of text (with optional whitespace) + // Renders as: + // - With Silk: FontAwesome checkbox icons with proper spacing + // - Without Silk: HTML input checkboxes (disabled) with proper checked state + val taskListPattern = Regex("""^-\s*\[([x\s])\](.*)$""") + val match = taskListPattern.find(literal.trim()) + + if (match != null) { + val isChecked = match.groupValues[1].trim() == "x" + val remainingText = match.groupValues[2].trim() + + buildString { + append("$KOBWEB_DOM.GenericTag(\"span\") { ") + + // Render checkbox/icon based on availability of Silk + if (useSilk.get()) { + // Use FontAwesome icons when Silk is available + val iconCode = if (isChecked) { + "com.varabyte.kobweb.silk.components.icons.fa.FaSquareCheck" + } else { + "com.varabyte.kobweb.silk.components.icons.fa.FaSquare" + } + append("$iconCode(); ") + } else { + // Fallback to HTML input checkbox when Silk is not available + val checkedAttr = if (isChecked) "checked" else "" + append("$KOBWEB_DOM.GenericTag(\"input\", \"type=\\\"checkbox\\\" disabled $checkedAttr\" style=\\\"margin-right: 0.5em;\\\"\"); ") + } + + // Add remaining text if present + if (remainingText.isNotEmpty()) { + append("$JB_DOM.Text(\" ${remainingText.escapeSingleQuotedText()}\") ") + } + + append("}") + } + } else { + // Standard text rendering for non-task-list text + "$JB_DOM.Text(\"${literal.escapeSingleQuotedText()}\")" + } + } img.convention { image -> processImage(image) { data -> buildString { @@ -299,6 +380,8 @@ abstract class MarkdownHandlers @Inject constructor(project: Project) { } em.convention { "$JB_DOM.Em" } strong.convention { "$JB_DOM.B" } + // Compose HTML does not expose a Del composable; use a generic tag to match HTML semantics. + strikethrough.convention { "$KOBWEB_DOM.GenericTag(\"del\")" } hr.convention { if (useSilk.get()) { "$SILK.layout.HorizontalDivider" From 0b3362b873801f7c963131bc5b22d65e89e2bbee Mon Sep 17 00:00:00 2001 From: shevapro Date: Mon, 18 Aug 2025 19:15:30 -0400 Subject: [PATCH 02/10] add support for github flavored markdown footnotes and enhance task list renderingtency and features. --- .../markdown/documents/unified_test.md | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 playground/site/src/jsMain/resources/markdown/documents/unified_test.md diff --git a/playground/site/src/jsMain/resources/markdown/documents/unified_test.md b/playground/site/src/jsMain/resources/markdown/documents/unified_test.md new file mode 100644 index 000000000..d0cea1dc2 --- /dev/null +++ b/playground/site/src/jsMain/resources/markdown/documents/unified_test.md @@ -0,0 +1,102 @@ +--- +title: Unified Markdown Processing Test +date: 'July 8, 2025' +description: Testing unified markdown processing with remark-gfm and rehype-raw plugins +tags: [ 'kobweb', 'commonmark', 'gfm', 'test', 'md', 'kotlin' ] +posted: true +routeOverride: /tests/ + +#layout: .components.layouts.EnhancedMarkdownLayout +--- + +# Unified Markdown Processing Test + +This content demonstrates markdown features on the **Kobweb Framework**, which uses +a [CommonMark](https://commonmark.org/) parser +with [GitHub Flavored Markdown (GFM) extensions](https://github.github.com/gfm/). + +## GitHub Flavored Markdown Features + +### Strikethrough + +~~This text should be struck through~~ or ~one tilde~ + +### Tables +| Feature | Status | Notes | +|------------------|--------|-------------------------------| +| Strikethrough | ✅ | Crosses out text | +| Tables | ✅ | Organizes data in a grid | +| Task Lists | ✅ | Creates checklists | +| HTML in Markdown | ✅ | Allows embedding raw HTML tags | + + + + +| Column A | Column B | Column C | Column D | +|----------|:---------|:--------:|---------:| +| Left | Left | Center | Right | +| Data 1 | Data 2 | Data 3 | Data 4 | +| Row 2 | Row 2 | Row 2 | Row 2 | + +### Task Lists + +- [x] Completed task +- [ ] Pending task +- [x] Another completed task +- [ ] Another pending task +- [x] Venus +- works + +### HTML Elements in Markdown + +This should work with underlined text and highlighted text. + +
+Note: This is HTML content inside markdown that should be preserved with rehype-raw. +
+ +### Code Blocks + +```javascript +// This is a JavaScript code block +function hello() { + console.log("Hello from unified markdown processing!"); +} +``` + +### Links and Autolinks + +Regular link: [Kobweb Documentation](https://kobweb.varabyte.com) + +Autolink: https://kobweb.varabyte.com and www.example.com. + +### Emphasis and Strong + +*Italic text* and **bold text** and ***bold italic text*** + +## Footnote + +A note can have a simple footnote.[^1] Here are some more examples: + +- A footnote with a link.[^link] +- A footnote with multiple paragraphs.[^multi] +- A footnote with a long text.[^long] + +[^1]: This is the first simple footnote. + +[^link]: This footnote contains a link to [Kobweb](https://kobweb.varabyte.com). + +[^multi]: This footnote has multiple paragraphs. +[foo]: https://example.com/foo + +This is the second paragraph of the footnote. + +[^long]: Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem +aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim +ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione +voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, +sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. + +--- + +✅ If you can see all the above features working correctly, then the CommonMark-java parser with its GFM extensions is functioning properly! \ No newline at end of file From d38614c6554cb4750301f2d626c6ec7d66d8e614 Mon Sep 17 00:00:00 2001 From: shevapro Date: Tue, 19 Aug 2025 01:11:45 -0400 Subject: [PATCH 03/10] update: Adjust unified_test.md routeOverride, location and remove unused layout property --- .../resources/markdown/{documents => tests}/unified_test.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) rename playground/site/src/jsMain/resources/markdown/{documents => tests}/unified_test.md (97%) diff --git a/playground/site/src/jsMain/resources/markdown/documents/unified_test.md b/playground/site/src/jsMain/resources/markdown/tests/unified_test.md similarity index 97% rename from playground/site/src/jsMain/resources/markdown/documents/unified_test.md rename to playground/site/src/jsMain/resources/markdown/tests/unified_test.md index d0cea1dc2..0920dbf49 100644 --- a/playground/site/src/jsMain/resources/markdown/documents/unified_test.md +++ b/playground/site/src/jsMain/resources/markdown/tests/unified_test.md @@ -4,9 +4,7 @@ date: 'July 8, 2025' description: Testing unified markdown processing with remark-gfm and rehype-raw plugins tags: [ 'kobweb', 'commonmark', 'gfm', 'test', 'md', 'kotlin' ] posted: true -routeOverride: /tests/ - -#layout: .components.layouts.EnhancedMarkdownLayout +routeOverride: /example/ --- # Unified Markdown Processing Test From 8822a62669d45f6669a5901c231e01d8167fcb09 Mon Sep 17 00:00:00 2001 From: shevapro Date: Tue, 19 Aug 2025 20:05:22 -0400 Subject: [PATCH 04/10] add support for footnotes and enhance task list rendering in markdown --- .../markdown/handlers/MarkdownHandlers.kt | 130 +++++++++--------- 1 file changed, 62 insertions(+), 68 deletions(-) diff --git a/tools/gradle-plugins/extensions/markdown/src/main/kotlin/com/varabyte/kobwebx/gradle/markdown/handlers/MarkdownHandlers.kt b/tools/gradle-plugins/extensions/markdown/src/main/kotlin/com/varabyte/kobwebx/gradle/markdown/handlers/MarkdownHandlers.kt index c14da3a11..3e6ab4161 100644 --- a/tools/gradle-plugins/extensions/markdown/src/main/kotlin/com/varabyte/kobwebx/gradle/markdown/handlers/MarkdownHandlers.kt +++ b/tools/gradle-plugins/extensions/markdown/src/main/kotlin/com/varabyte/kobwebx/gradle/markdown/handlers/MarkdownHandlers.kt @@ -21,12 +21,15 @@ import com.varabyte.kobwebx.gradle.markdown.util.escapeDollars import com.varabyte.kobwebx.gradle.markdown.util.escapeQuotes import com.varabyte.kobwebx.gradle.markdown.util.escapeTripleQuotes import com.varabyte.kobwebx.gradle.markdown.util.nestedLiteral +import org.commonmark.ext.footnotes.FootnoteDefinition +import org.commonmark.ext.footnotes.FootnoteReference import org.commonmark.ext.gfm.strikethrough.Strikethrough import org.commonmark.ext.gfm.tables.TableBlock import org.commonmark.ext.gfm.tables.TableBody import org.commonmark.ext.gfm.tables.TableCell import org.commonmark.ext.gfm.tables.TableHead import org.commonmark.ext.gfm.tables.TableRow +import org.commonmark.ext.task.list.items.TaskListItemMarker import org.commonmark.node.BlockQuote import org.commonmark.node.BulletList import org.commonmark.node.Code @@ -93,7 +96,7 @@ class NodeScope(val reporter: Reporter, val data: TypedMap, private val indentCo * ## Task List Support * * This handler automatically detects and renders task list items (checkboxes) in markdown text, - * including those inside table cells where CommonMark doesn't automatically recognize them. + * including those inside table cells where CommonMark doesn't automatically recognize them as TaskListItemMarker nodes. * * Task list patterns like `- [x]` (checked) and `- [ ]` (unchecked) are converted to: * - **With Silk**: FontAwesome icons (`FaSquareCheck` for checked, `FaSquare` for unchecked) @@ -172,60 +175,69 @@ abstract class MarkdownHandlers @Inject constructor(project: Project) { abstract val idGenerator: Property<(String) -> String> @get:Nested - abstract val text: Property String> - @get:Nested - abstract val img: Property String> - @get:Nested - abstract val heading: Property String> + abstract val a: Property String> @get:Nested - abstract val p: Property String> + abstract val blockquote: Property String> @get:Nested abstract val br: Property String> @get:Nested - abstract val a: Property String> + abstract val code: Property String> @get:Nested abstract val em: Property String> @get:Nested - abstract val strong: Property String> + abstract val footnoteDefinition: Property String> @get:Nested - abstract val strikethrough: Property String> + abstract val footnoteReference: Property String> + + @get:Nested + abstract val heading: Property String> @get:Nested abstract val hr: Property String> @get:Nested - abstract val ul: Property String> + abstract val html: Property String> @get:Nested - abstract val ol: Property String> + abstract val img: Property String> + @get:Nested + abstract val inlineCode: Property String> + + @get:Nested + abstract val inlineTag: Property String> + @get:Nested abstract val li: Property String> @get:Nested - abstract val code: Property String> + abstract val ol: Property String> @get:Nested - abstract val inlineCode: Property String> + abstract val p: Property String> @get:Nested - abstract val blockquote: Property String> + abstract val rawTag: Property String> + @get:Nested + abstract val strikethrough: Property String> + @get:Nested + abstract val strong: Property String> + @get:Nested abstract val table: Property String> @get:Nested - abstract val thead: Property String> + abstract val taskListItemMarker: Property String> @get:Nested abstract val tbody: Property String> @get:Nested - abstract val tr: Property String> - @get:Nested abstract val td: Property String> @get:Nested - abstract val th: Property String> + abstract val text: Property String> - /** Handler which is fed the raw text (name and attributes) within an opening tag, e.g. `span id="demo"` */ @get:Nested - abstract val rawTag: Property String> + abstract val th: Property String> @get:Nested - abstract val inlineTag: Property String> - + abstract val thead: Property String> @get:Nested - abstract val html: Property String> + abstract val tr: Property String> + @get:Nested + abstract val ul: Property String> + /** Handler which is fed the raw text (name and attributes) within an opening tag, e.g. `span id="demo"` */ fun String.escapeSingleQuotedText() = escapeQuotes().escapeDollars() fun String.escapeTripleQuotedText() = escapeDollars().escapeTripleQuotes() @@ -295,50 +307,8 @@ abstract class MarkdownHandlers @Inject constructor(project: Project) { text.convention { text -> val literal = text.literal - // Handle task list items in text nodes - // This covers cases where CommonMark doesn't automatically parse task list items as - // TaskListItemMarker nodes, particularly inside table cells or other complex structures. - // - // Pattern: "- [x]" or "- [ ]" at the beginning of text (with optional whitespace) - // Renders as: - // - With Silk: FontAwesome checkbox icons with proper spacing - // - Without Silk: HTML input checkboxes (disabled) with proper checked state - val taskListPattern = Regex("""^-\s*\[([x\s])\](.*)$""") - val match = taskListPattern.find(literal.trim()) - - if (match != null) { - val isChecked = match.groupValues[1].trim() == "x" - val remainingText = match.groupValues[2].trim() - - buildString { - append("$KOBWEB_DOM.GenericTag(\"span\") { ") - - // Render checkbox/icon based on availability of Silk - if (useSilk.get()) { - // Use FontAwesome icons when Silk is available - val iconCode = if (isChecked) { - "com.varabyte.kobweb.silk.components.icons.fa.FaSquareCheck" - } else { - "com.varabyte.kobweb.silk.components.icons.fa.FaSquare" - } - append("$iconCode(); ") - } else { - // Fallback to HTML input checkbox when Silk is not available - val checkedAttr = if (isChecked) "checked" else "" - append("$KOBWEB_DOM.GenericTag(\"input\", \"type=\\\"checkbox\\\" disabled $checkedAttr\" style=\\\"margin-right: 0.5em;\\\"\"); ") - } - - // Add remaining text if present - if (remainingText.isNotEmpty()) { - append("$JB_DOM.Text(\" ${remainingText.escapeSingleQuotedText()}\") ") - } - - append("}") - } - } else { - // Standard text rendering for non-task-list text - "$JB_DOM.Text(\"${literal.escapeSingleQuotedText()}\")" - } + // Standard text rendering + "$JB_DOM.Text(\"${literal.escapeSingleQuotedText()}\")" } img.convention { image -> processImage(image) { data -> @@ -519,6 +489,30 @@ abstract class MarkdownHandlers @Inject constructor(project: Project) { sb.toString() } + taskListItemMarker.convention { marker -> + val isChecked = marker.isChecked + if (useSilk.get()) { + val iconCode = if (isChecked) { + "com.varabyte.kobweb.silk.components.icons.fa.FaSquareCheck" + } else { + "com.varabyte.kobweb.silk.components.icons.fa.FaSquare" + } + "$KOBWEB_DOM.GenericTag(\"span\", \"style=\\\"margin-right: 0.5em;\\\"\") { $iconCode() }" + } else { + val checkedAttr = if (isChecked) "checked" else "" + "$KOBWEB_DOM.GenericTag(\"input\", \"type=\\\"checkbox\\\" disabled $checkedAttr style=\\\"margin-right: 0.5em;\\\"\")" + } + } + + footnoteDefinition.convention { definition -> + val label = definition.label + "$KOBWEB_DOM.GenericTag(\"div\", \"class=\\\"footnote-item\\\" id=\\\"fn-$label\\\"\")" + } + + footnoteReference.convention { reference -> + val label = reference.label + "$KOBWEB_DOM.GenericTag(\"sup\", \"id=\\\"fnref-$label\\\"\") { $KOBWEB_DOM.GenericTag(\"a\", \"href=\\\"#fn-$label\\\"\") { $JB_DOM.Text(\"$label\") } }" + } // endregion } } From 8dea60e39802171ef43ef3d2086b39b41f0304b6 Mon Sep 17 00:00:00 2001 From: shevapro Date: Tue, 19 Aug 2025 20:06:14 -0400 Subject: [PATCH 05/10] enable footnotes support and improve task list rendering in markdown --- .../varabyte/kobwebx/gradle/markdown/MarkdownFeatures.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/gradle-plugins/extensions/markdown/src/main/kotlin/com/varabyte/kobwebx/gradle/markdown/MarkdownFeatures.kt b/tools/gradle-plugins/extensions/markdown/src/main/kotlin/com/varabyte/kobwebx/gradle/markdown/MarkdownFeatures.kt index b1b70552f..c172f7b14 100644 --- a/tools/gradle-plugins/extensions/markdown/src/main/kotlin/com/varabyte/kobwebx/gradle/markdown/MarkdownFeatures.kt +++ b/tools/gradle-plugins/extensions/markdown/src/main/kotlin/com/varabyte/kobwebx/gradle/markdown/MarkdownFeatures.kt @@ -117,20 +117,20 @@ abstract class MarkdownFeatures { * * Defaults to `true`. * - * Note: Inline footnotes via "^[inline]" are not enabled by default. + * Note: Inline footnotes via `^[inline]` are not supported at this time. */ @get:Input abstract val footnotes: Property init { autolink.convention(true) + footnotes.convention(true) frontMatter.convention(true) kobwebCall.convention(true) kobwebCallDelimiters.convention('{' to '}') + strikethrough.convention(true) tables.convention(true) taskList.convention(true) - strikethrough.convention(true) - footnotes.convention(true) } /** From 5b6761b42a727b354f0eaf9ac1da7ac7e6db1b3a Mon Sep 17 00:00:00 2001 From: shevapro Date: Tue, 19 Aug 2025 20:59:41 -0400 Subject: [PATCH 06/10] Update code changes --- .../kobwebx/gradle/markdown/KotlinRenderer.kt | 151 +++++++----------- 1 file changed, 59 insertions(+), 92 deletions(-) diff --git a/tools/gradle-plugins/extensions/markdown/src/main/kotlin/com/varabyte/kobwebx/gradle/markdown/KotlinRenderer.kt b/tools/gradle-plugins/extensions/markdown/src/main/kotlin/com/varabyte/kobwebx/gradle/markdown/KotlinRenderer.kt index b94358d9d..e2b1f2b53 100644 --- a/tools/gradle-plugins/extensions/markdown/src/main/kotlin/com/varabyte/kobwebx/gradle/markdown/KotlinRenderer.kt +++ b/tools/gradle-plugins/extensions/markdown/src/main/kotlin/com/varabyte/kobwebx/gradle/markdown/KotlinRenderer.kt @@ -9,11 +9,9 @@ import com.varabyte.kobwebx.gradle.markdown.ext.kobwebcall.KobwebCall import com.varabyte.kobwebx.gradle.markdown.ext.kobwebcall.KobwebCallBlock import com.varabyte.kobwebx.gradle.markdown.ext.kobwebcall.KobwebCallBlockVisitor import com.varabyte.kobwebx.gradle.markdown.frontmatter.FrontMatterBlock -import com.varabyte.kobwebx.gradle.markdown.handlers.KOBWEB_DOM import com.varabyte.kobwebx.gradle.markdown.handlers.MarkdownHandlers import com.varabyte.kobwebx.gradle.markdown.handlers.NodeScope import com.varabyte.kobwebx.gradle.markdown.util.NodeCache -import com.varabyte.kobwebx.gradle.markdown.util.escapeQuotes import com.varabyte.kobwebx.gradle.markdown.util.unescapeQuotes import org.commonmark.ext.footnotes.FootnoteDefinition import org.commonmark.ext.footnotes.FootnoteReference @@ -344,6 +342,7 @@ class KotlinRenderer internal constructor( override fun visit(code: Code) { doVisit(code, handlers.inlineCode) } + override fun visit(emphasis: Emphasis) { doVisit(emphasis, handlers.em) @@ -402,7 +401,7 @@ class KotlinRenderer internal constructor( val route = Route( frontMatterData?.routeOverride - ?: nodeCache.metadata.getValue(destinationNode).routeWithSlug!! // Guaranteed set for a page + ?: nodeCache.metadata.getValue(destinationNode).routeWithSlug!! // Guaranteed set for a page ) if (route.isDynamic) { error("Markdown file link '${link.destination}' links to file with dynamic route override. This is not supported!") @@ -416,9 +415,7 @@ class KotlinRenderer internal constructor( } override fun visit(listItem: ListItem) { - doVisit(listItem, handlers.li) - } override fun visit(bulletList: BulletList) { @@ -441,6 +438,10 @@ class KotlinRenderer internal constructor( } } + private fun visit(strikethrough: Strikethrough) { + doVisit(strikethrough, handlers.strikethrough) + } + override fun visit(strongEmphasis: StrongEmphasis) { doVisit(strongEmphasis, handlers.strong) } @@ -448,84 +449,6 @@ class KotlinRenderer internal constructor( override fun visit(text: Text) { doVisit(text, handlers.text) } - - - private fun visit(table: TableBlock) { - doVisit(table, handlers.table) - } - - private fun visit(tableHead: TableHead) { - doVisit(tableHead, handlers.thead) - } - - private fun visit(tableBody: TableBody) { - doVisit(tableBody, handlers.tbody) - } - - private fun visit(tableRow: TableRow) { - doVisit(tableRow, handlers.tr) - } - - private fun visit(tableCell: TableCell) { - if (tableCell.isHeader) { - doVisit(tableCell, handlers.th) - } else { - doVisit(tableCell, handlers.td) - } - } - override fun visit(customNode: CustomNode) { - when (customNode) { - is KobwebCall -> { - output.appendLine("$indent${customNode.toFqn(projectGroup)}") - } - - is TableHead -> visit(customNode) - is TableBody -> visit(customNode) - is TableRow -> visit(customNode) - is TableCell -> visit(customNode) - is Strikethrough -> visit(customNode) - is FootnoteReference -> { - // Render footnote references as superscript with the actual label/number - val scope = NodeScope(reporter, data, indentCount) - val label = customNode.label - // Create a link to the footnote definition - val code = "$KOBWEB_DOM.GenericTag(\"sup\", \"id=\\\"fnref-$label\\\"\")" - scope.childrenOverride = listOf( - // Create a link to the footnote definition - org.commonmark.node.Link("#fn-$label", null).apply { - appendChild(Text(label)) - } - ) - doVisit(customNode, code, scope) - } - is TaskListItemMarker -> { - // Render a task list item, which is a checkbox that is either checked or not. - // We prefer to use Silk's FontAwesome icons if available, otherwise we fall back to a - // standard HTML checkbox. - val code = if (handlers.useSilk.get()) { - val icon = if (customNode.isChecked) { - "com.varabyte.kobweb.silk.components.icons.fa.FaSquareCheck" - } else { - "com.varabyte.kobweb.silk.components.icons.fa.FaSquare" - } - // Use a span to apply margin to the icon, giving it some breathing room from the text. - "$KOBWEB_DOM.GenericTag(\"span\", \"style=\\\"margin-right: 0.5em;\\\"\") { $icon() }" - } else { - val checkedAttr = if (customNode.isChecked) "checked" else "" - // The `disabled` attribute is important here, as these are decorative checkboxes. - // A right margin is added to space the checkbox from the list item's text. - val attrs = "type=\\\"checkbox\\\" disabled $checkedAttr style=\\\"margin-right: 0.5em;\\\"" - "$KOBWEB_DOM.GenericTag(\"input\", \"$attrs\")" - } - output.appendLine("$indent$code") - } - else -> { - val unhandledNodeName = customNode::class.simpleName!! - reporter.warn("Unhandled Markdown custom node: $unhandledNodeName. Consider reporting this at: https://github.com/varabyte/kobweb/issues/new?labels=bug&template=bug_report.md&title=Unhandled%20Markdown%20node%20%22$unhandledNodeName%22") - } - } - } - override fun visit(customBlock: CustomBlock) { when (customBlock) { is KobwebCallBlock -> { @@ -548,24 +471,68 @@ class KotlinRenderer internal constructor( is TableBlock -> visit(customBlock) is FootnoteDefinition -> { - // Render individual footnote definitions as block elements inside the footnote container - val scope = NodeScope(reporter, data, indentCount) - val code = - "com.varabyte.kobweb.compose.dom.GenericTag(\"div\", \"class=\\\"footnote-item\\\" id=\\\"fn-${customBlock.label}\\\"\")" - doVisit(customBlock, code, scope) + doVisit(customBlock, handlers.footnoteDefinition) } else -> { val simple = customBlock::class.simpleName - val unhandledBlockName = simple!! - reporter.warn("Unhandled Markdown custom block: $unhandledBlockName. Consider reporting this at: https://github.com/varabyte/kobweb/issues/new?labels=bug&template=bug_report.md&title=Unhandled%20Markdown%20block%20%22$unhandledBlockName%22") + val unhandledBlockName = simple!! + reporter.warn("Unhandled Markdown custom block: $unhandledBlockName. Consider reporting this at: https://github.com/varabyte/kobweb/issues/new?labels=bug&template=bug_report.md&title=Unhandled%20Markdown%20block%20%22$unhandledBlockName%22") } } } - private fun visit(strikethrough: Strikethrough) { - doVisit(strikethrough, handlers.strikethrough) + override fun visit(customNode: CustomNode) { + when (customNode) { + is KobwebCall -> { + output.appendLine("$indent${customNode.toFqn(projectGroup)}") + } + + is Strikethrough -> visit(customNode) + is TableHead -> visit(customNode) + is TableBody -> visit(customNode) + is TableRow -> visit(customNode) + is TableCell -> visit(customNode) + is FootnoteReference -> visit(customNode) + is TaskListItemMarker -> visit(customNode) + + else -> { + val unhandledNodeName = customNode::class.simpleName!! + reporter.warn("Unhandled Markdown custom node: $unhandledNodeName. Consider reporting this at: https://github.com/varabyte/kobweb/issues/new?labels=bug&template=bug_report.md&title=Unhandled%20Markdown%20node%20%22$unhandledNodeName%22") + } + } + } + + private fun visit(footnoteReference: FootnoteReference) { + doVisit(footnoteReference, handlers.footnoteReference) + } + + private fun visit(table: TableBlock) { + doVisit(table, handlers.table) + } + + private fun visit(tableHead: TableHead) { + doVisit(tableHead, handlers.thead) + } + + private fun visit(tableBody: TableBody) { + doVisit(tableBody, handlers.tbody) + } + + private fun visit(tableRow: TableRow) { + doVisit(tableRow, handlers.tr) + } + + private fun visit(tableCell: TableCell) { + if (tableCell.isHeader) { + doVisit(tableCell, handlers.th) + } else { + doVisit(tableCell, handlers.td) + } + } + private fun visit(taskListItemMarker: TaskListItemMarker) { + doVisit(taskListItemMarker, handlers.taskListItemMarker) } } } From 4b1f533040a987f901204fc9f394f558006f93af Mon Sep 17 00:00:00 2001 From: shevapro Date: Tue, 19 Aug 2025 21:01:14 -0400 Subject: [PATCH 07/10] bumped commonmark from 0.24.0 to 0.25.1 --- gradle/libs.versions.toml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 336bb815a..ea50c83e6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] kobweb = "0.23.2-SNAPSHOT" #------------------------ -commonmark = "0.24.0" +commonmark = "0.25.1" compose = "1.8.0" dokka = "2.0.0" gradle-publish = "1.3.0" @@ -24,8 +24,10 @@ vanniktech-publish = "0.34.0" [libraries] commonmark-core = { module = "org.commonmark:commonmark", version.ref = "commonmark" } commonmark-autolink = { module = "org.commonmark:commonmark-ext-autolink", version.ref = "commonmark" } +commonmark-strikethrough = { module = "org.commonmark:commonmark-ext-gfm-strikethrough", version.ref = "commonmark" } commonmark-tables = { module = "org.commonmark:commonmark-ext-gfm-tables", version.ref = "commonmark" } commonmark-tasklist = { module = "org.commonmark:commonmark-ext-task-list-items", version.ref = "commonmark" } +commonmark-footnotes = { module = "org.commonmark:commonmark-ext-footnotes", version.ref = "commonmark" } compose-html-core = { module = "org.jetbrains.compose.html:html-core", version.ref = "compose" } compose-compiler-plugin = { module = "org.jetbrains.kotlin:compose-compiler-gradle-plugin", version.ref = "kotlin" } compose-runtime = { module = "org.jetbrains.compose.runtime:runtime", version.ref = "compose" } @@ -62,7 +64,14 @@ truthish = { module = "com.varabyte.truthish:truthish", version.ref = "truthish" vanniktech-publish-plugin = { module = "com.vanniktech:gradle-maven-publish-plugin", version.ref = "vanniktech-publish" } [bundles] -commonmark = ["commonmark-core", "commonmark-autolink", "commonmark-tables", "commonmark-tasklist"] +commonmark = [ + "commonmark-core", + "commonmark-autolink", + "commonmark-tables", + "commonmark-tasklist", + "commonmark-strikethrough", + "commonmark-footnotes", +] ktor = [ "ktor-serialization-json", "ktor-server-auth", From 77591af3f82a28f43e71f7c777d1849a7688bc9c Mon Sep 17 00:00:00 2001 From: shevapro Date: Tue, 19 Aug 2025 21:01:35 -0400 Subject: [PATCH 08/10] location update --- .../src/jsMain/resources/markdown/Markdown.md | 2 +- .../resources/markdown/tests/unified_test.md | 27 ++++++++----------- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/playground/site/src/jsMain/resources/markdown/Markdown.md b/playground/site/src/jsMain/resources/markdown/Markdown.md index e580767be..4db7324c7 100644 --- a/playground/site/src/jsMain/resources/markdown/Markdown.md +++ b/playground/site/src/jsMain/resources/markdown/Markdown.md @@ -45,7 +45,7 @@ You can link to other Markdown documents with their route overrides resolved cor [documents/INDEX.md](documents/INDEX.md)
[KotlinLanguage.md](jetbrains/KotlinLanguage.md) (`routeOverride: languages/kotlin`)
[documents/Bananas.md](documents/Bananas.md) (`routeOverride: /fruits/`)
-[documents/unified_test.md](documents/unified_test.md) (`routeOverride: /tests/`)
+[tests/unified_test.md](tests/unified_test.md) (`routeOverride: /example/`)
[files/external.md](/files/external.md) (Linking to a Markdown file outside of the processed markdown files)
You can use inlined html tags. You can inspect this page to see that "inlined html" is diff --git a/playground/site/src/jsMain/resources/markdown/tests/unified_test.md b/playground/site/src/jsMain/resources/markdown/tests/unified_test.md index 0920dbf49..e3d06dc11 100644 --- a/playground/site/src/jsMain/resources/markdown/tests/unified_test.md +++ b/playground/site/src/jsMain/resources/markdown/tests/unified_test.md @@ -4,7 +4,6 @@ date: 'July 8, 2025' description: Testing unified markdown processing with remark-gfm and rehype-raw plugins tags: [ 'kobweb', 'commonmark', 'gfm', 'test', 'md', 'kotlin' ] posted: true -routeOverride: /example/ --- # Unified Markdown Processing Test @@ -20,21 +19,17 @@ with [GitHub Flavored Markdown (GFM) extensions](https://github.github.com/gfm/) ~~This text should be struck through~~ or ~one tilde~ ### Tables -| Feature | Status | Notes | -|------------------|--------|-------------------------------| -| Strikethrough | ✅ | Crosses out text | -| Tables | ✅ | Organizes data in a grid | -| Task Lists | ✅ | Creates checklists | -| HTML in Markdown | ✅ | Allows embedding raw HTML tags | - - - - -| Column A | Column B | Column C | Column D | -|----------|:---------|:--------:|---------:| -| Left | Left | Center | Right | -| Data 1 | Data 2 | Data 3 | Data 4 | -| Row 2 | Row 2 | Row 2 | Row 2 | +| Feature | Status | Notes | +|------------------|------------------------------------|-------------------------------| +| Strikethrough | * [x] | Crosses out text | +| Tables | [x] | Organizes data in a grid | +| Task Lists |- [x] | Creates checklists | +| HTML in Markdown | - [x] highlighted text | Allows embedding raw HTML tags | +| Column A | Column B | Column C | Column D | +|----------| :--------- |:--------:|---------:| +| Left | Left | Center | Right | +| Data 1 | Data 2 | Data 3 | Data 4 | +| Row 2 | Row 2 | Row 2 | Row 2 | ### Task Lists From ac9bb4a8e14c81e1be4b4d84d4a1201b2a493dc3 Mon Sep 17 00:00:00 2001 From: shevapro Date: Tue, 19 Aug 2025 21:07:40 -0400 Subject: [PATCH 09/10] location update --- .../site/src/jsMain/resources/markdown/tests/unified_test.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/playground/site/src/jsMain/resources/markdown/tests/unified_test.md b/playground/site/src/jsMain/resources/markdown/tests/unified_test.md index e3d06dc11..4d1d7ef4f 100644 --- a/playground/site/src/jsMain/resources/markdown/tests/unified_test.md +++ b/playground/site/src/jsMain/resources/markdown/tests/unified_test.md @@ -25,6 +25,9 @@ with [GitHub Flavored Markdown (GFM) extensions](https://github.github.com/gfm/) | Tables | [x] | Organizes data in a grid | | Task Lists |- [x] | Creates checklists | | HTML in Markdown | - [x] highlighted text | Allows embedding raw HTML tags | +| Code Blocks | - [x] | Allows embedding code blocks | +| Links | - [x] | Allows embedding links | +| Emphasis | - [x] | Allows embedding emphasis | | Column A | Column B | Column C | Column D | |----------| :--------- |:--------:|---------:| | Left | Left | Center | Right | From 42d85bdd53b1a052e4be3dc9a2882eb275659319 Mon Sep 17 00:00:00 2001 From: shevapro Date: Thu, 21 Aug 2025 21:18:10 -0400 Subject: [PATCH 10/10] refactor: Reorganize MarkdownFeatures properties and extension setup - Consolidate `footnotes` and `strikethrough` properties for clarity - Adjust initialization sequence for extensions to ensure proper loading and maintain code readability --- .../gradle/markdown/MarkdownFeatures.kt | 51 ++++++++++--------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/tools/gradle-plugins/extensions/markdown/src/main/kotlin/com/varabyte/kobwebx/gradle/markdown/MarkdownFeatures.kt b/tools/gradle-plugins/extensions/markdown/src/main/kotlin/com/varabyte/kobwebx/gradle/markdown/MarkdownFeatures.kt index c172f7b14..550d59964 100644 --- a/tools/gradle-plugins/extensions/markdown/src/main/kotlin/com/varabyte/kobwebx/gradle/markdown/MarkdownFeatures.kt +++ b/tools/gradle-plugins/extensions/markdown/src/main/kotlin/com/varabyte/kobwebx/gradle/markdown/MarkdownFeatures.kt @@ -39,6 +39,16 @@ abstract class MarkdownFeatures { @get:Input abstract val autolink: Property + + /** + * If true, support footnotes like "Main text[^1]" with definitions "[^1]: Footnote text". + * + * Defaults to `true`. + * + * Note: Inline footnotes via `^[inline]` are not supported at this time. + */ + @get:Input + abstract val footnotes: Property /** * If true, support front matter (a header YAML block at the top of your markdown file with key/value pairs). * @@ -75,6 +85,15 @@ abstract class MarkdownFeatures { */ @get:Input abstract val kobwebCallDelimiters: Property> + /** + * If true, support GFM strikethrough syntax using double tildes, e.g. `~~text~~`. + * + * Defaults to `true`. + * + * @see Strikethrough + */ + @get:Input + abstract val strikethrough: Property /** * If true, support creating tables via pipe syntax. @@ -102,25 +121,6 @@ abstract class MarkdownFeatures { @get:Input abstract val taskList: Property - /** - * If true, support GFM strikethrough syntax using double tildes, e.g. `~~text~~`. - * - * Defaults to `true`. - * - * @see Strikethrough - */ - @get:Input - abstract val strikethrough: Property - - /** - * If true, support footnotes like "Main text[^1]" with definitions "[^1]: Footnote text". - * - * Defaults to `true`. - * - * Note: Inline footnotes via `^[inline]` are not supported at this time. - */ - @get:Input - abstract val footnotes: Property init { autolink.convention(true) @@ -141,24 +141,25 @@ abstract class MarkdownFeatures { if (autolink.get()) { extensions.add(AutolinkExtension.create()) } + if (footnotes.get()) { + extensions.add(FootnotesExtension.create()) + } if (frontMatter.get()) { extensions.add(FrontMatterExtension.create()) } if (kobwebCall.get()) { extensions.add(KobwebCallExtension.create(kobwebCallDelimiters.get()) { createParser() }) } + if (strikethrough.get()) { + extensions.add(StrikethroughExtension.create()) + } if (tables.get()) { extensions.add(TablesExtension.create()) } if (taskList.get()) { extensions.add(TaskListItemsExtension.create()) } - if (strikethrough.get()) { - extensions.add(StrikethroughExtension.create()) - } - if (footnotes.get()) { - extensions.add(FootnotesExtension.create()) - } + return Parser.builder() .extensions(extensions)