Skip to content

Latest commit

 

History

History

markdown

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 

Jewel Markdown Renderer

Important

The Jewel Markdown renderer is currently considered experimental. Its API and implementations may change at any time, and no guarantees are made for binary and source compatibility. It might also have bugs and missing features.

Adds the ability to render Markdown as native Compose UI.

Currently supports the CommonMark 0.31.2 specs.

Additional supported Markdown, via extensions:

On the roadmap, but not currently supported — in no particular order:

Not supported, and not on the roadmap:

  • Inline HTML rendering
  • Mermaid diagrams (GitHub Flavored Markdown)
  • LaTeX rendering, both inline and not (GitHub Flavored Markdown)
  • topoJSON/geoJSON rendering (GitHub Flavored Markdown)
  • 3D STL models (GitHub Flavored Markdown)
  • Rich rendering of embeds such as videos, YouTube, GitHub Gists/...

Add the Markdown renderer to your project

The Jewel Markdown renderer is designed to be run in a project that already has a jewel-standalone or jewel-ide-laf-bridge-* dependency. The core module doesn't contain any styling, and you're supposed to use either the jewel-markdown-int-ui-standalone-styling or jewel-markdown-ide-laf-bridge-styling instead. They will carry the necessary dependencies.

Caution

Don't use the standalone artifact in an IDE plugin, and don't use the bridge artifact in a standalone project!

If you want to use extensions, you also need to add them alongside the jewel-markdown-core:

dependencies {
    implementation(libs.jewel.standalone)
    implementation(libs.jewel.markdown.intUiStandaloneStyling)
    implementation(libs.jewel.markdown.extension.gfm.alerts) // Optional
    // Et cetera...
}

How to use Jewel's Markdown renderer

The process that leads to rendering Markdown in a native UI is two-pass.

The first pass is an upfront rendering that pre-processes blocks into MarkdownBlocks, but doesn't touch the inline Markdown. It's recommended to run this outside of the composition, since it has no dependencies on it.

// Somewhere outside of composition...
val processor = MarkdownProcessor()
val rawMarkdown = "..."
var markdownBlocks: List<MarkdownBlock> = processor.processMarkdownDocument(rawMarkdown)

Once you have your list of MarkdownBlocks, you can do the second step in the composition: render a series of MarkdownBlocks into native Jewel UI.

Here is an example:

@Composable
fun Markdown(blocks: List<MarkdownBlock>) {
    val isDark = JewelTheme.isDark
    val markdownStyling =
        remember(isDark) { if (isDark) MarkdownStyling.dark() else MarkdownStyling.light() }
    val blockRenderer = remember(markdownStyling, isDark) {
        if (isDark) MarkdownBlockRenderer.dark() else MarkdownBlockRenderer.light()
    }

    SelectionContainer(Modifier.fillMaxSize()) {
        Column(
            modifier = Modifier.verticalScroll(rememberScrollState()),
            verticalArrangement = Arrangement.spacedBy(markdownStyling.blockVerticalSpacing),
        ) {
            blockRenderer.render(blocks)
        }
    }
}

If you expect long Markdown documents, you can also use a LazyMarkdown to get better performances. You can find an example in MarkdownPreview.

Using extensions

By default, the processor will ignore any kind of Markdown it doesn't support. To support additional features, such as ones found in GitHub Flavored Markdown, you can use extensions. If you don't specify any extension, the processor will be restricted to the CommonMark specs as supported by commonmark-java.

Extensions are composed of two parts: a parsing and a rendering part. The two parts need to be passed to the MarkdownProcessor and MarkdownBlockRenderer, respectively:

// Where the parsing happens...
val parsingExtensions: List<MarkdownProcessorExtension> = listOf(/*...*/)
val processor = MarkdownProcessor(parsingExtensions)

// Where the rendering happens...
val blockRenderer = remember(markdownStyling, isDark) {
    if (isDark) {
        MarkdownBlockRenderer.dark(
            rendererExtensions = listOf(/*...*/),
            inlineRenderer = InlineMarkdownRenderer.default(parsingExtensions),
        )
    } else {
        MarkdownBlockRenderer.light(
            rendererExtensions = listOf(/*...*/),
            inlineRenderer = InlineMarkdownRenderer.default(parsingExtensions),
        )
    }
}

It is strongly recommended to use the corresponding set of rendering extensions as the ones used for parsing, otherwise the custom blocks will be parsed but not rendered.

Note that you should create an InlineMarkdownRenderer with the same list of extensions that was used to build the processor, as even though inline rendering extensions are not supported yet, they will be in the future.

Showcase

You can see this in action running the Standalone sample, and selecting Markdown from the top-left menu.

The following image shows Jewel Markdown rendering this very Jewel Markdown README. Image showing the Markdown showcase from the Jewel standalone sample