[JEWEL-742] Load markdown images using Coil3

It supports every image as an inline node;
Using built-in coroutine library and ktor3 from the platform;
Added SVG support using a coil dependency.

Moved images into an extension so it can be loaded
on demand without pushin coil3 dependencies to everyone.
Add Coil3ImagesRendererExtension to rendering extensions to
render images.

Also expose `imageLoader` as a parameter to give fine
controls to apps wanting to pass in their global loader.

(cherry picked from commit f7b51b8239622c39297aeb8dcfd8fad3435111ec)


(cherry picked from commit 59f8cf9d80d1a53267e902f78976c97d24a898b3)

IJ-MR-168786

GitOrigin-RevId: 880feae68b283c1fce907557522a0d4e78a8cdd2
This commit is contained in:
Oleg Baskakov
2025-01-21 16:44:56 +00:00
committed by intellij-monorepo-bot
parent 61d1f2fe74
commit 9b0bb9f169
39 changed files with 634 additions and 9 deletions

1
.idea/modules.xml generated
View File

@@ -834,6 +834,7 @@
<module fileurl="file://$PROJECT_DIR$/platform/jewel/markdown/extensions/gfm-alerts/intellij.platform.jewel.markdown.extensions.gfmAlerts.iml" filepath="$PROJECT_DIR$/platform/jewel/markdown/extensions/gfm-alerts/intellij.platform.jewel.markdown.extensions.gfmAlerts.iml" />
<module fileurl="file://$PROJECT_DIR$/platform/jewel/markdown/extensions/gfm-strikethrough/intellij.platform.jewel.markdown.extensions.gfmStrikethrough.iml" filepath="$PROJECT_DIR$/platform/jewel/markdown/extensions/gfm-strikethrough/intellij.platform.jewel.markdown.extensions.gfmStrikethrough.iml" />
<module fileurl="file://$PROJECT_DIR$/platform/jewel/markdown/extensions/gfm-tables/intellij.platform.jewel.markdown.extensions.gfmTables.iml" filepath="$PROJECT_DIR$/platform/jewel/markdown/extensions/gfm-tables/intellij.platform.jewel.markdown.extensions.gfmTables.iml" />
<module fileurl="file://$PROJECT_DIR$/platform/jewel/markdown/extensions/images/intellij.platform.jewel.markdown.extensions.images.iml" filepath="$PROJECT_DIR$/platform/jewel/markdown/extensions/images/intellij.platform.jewel.markdown.extensions.images.iml" />
<module fileurl="file://$PROJECT_DIR$/platform/jewel/markdown/ide-laf-bridge-styling/intellij.platform.jewel.markdown.ideLafBridgeStyling.iml" filepath="$PROJECT_DIR$/platform/jewel/markdown/ide-laf-bridge-styling/intellij.platform.jewel.markdown.ideLafBridgeStyling.iml" />
<module fileurl="file://$PROJECT_DIR$/platform/jewel/markdown/int-ui-standalone-styling/intellij.platform.jewel.markdown.intUiStandaloneStyling.iml" filepath="$PROJECT_DIR$/platform/jewel/markdown/int-ui-standalone-styling/intellij.platform.jewel.markdown.intUiStandaloneStyling.iml" />
<module fileurl="file://$PROJECT_DIR$/platform/jewel/samples/ide-plugin/intellij.platform.jewel.samples.idePlugin.iml" filepath="$PROJECT_DIR$/platform/jewel/samples/ide-plugin/intellij.platform.jewel.samples.idePlugin.iml" />

View File

@@ -25,6 +25,7 @@ internal object JewelMavenArtifacts {
"intellij.platform.jewel.markdown.extensions.gfmStrikethrough" to "jewel-markdown-extensions-gfm-strikethrough",
"intellij.platform.jewel.markdown.extensions.autolink" to "jewel-markdown-extensions-autolink",
"intellij.platform.jewel.markdown.extensions.gfmAlerts" to "jewel-markdown-extensions-gfm-alerts",
"intellij.platform.jewel.markdown.extensions.images" to "jewel-markdown-extensions-images",
)
internal val STANDALONE: Map<String, String> = mapOf(
"intellij.platform.jewel.markdown.intUiStandaloneStyling" to "jewel-markdown-int-ui-standalone-styling",
@@ -46,6 +47,7 @@ internal object JewelMavenArtifacts {
"jewel-markdown-extensions-gfm-alerts" to setOf("jewel-foundation", "jewel-ui"),
"jewel-markdown-extensions-gfm-strikethrough" to setOf("jewel-foundation", "jewel-ui"),
"jewel-markdown-extensions-gfm-tables" to setOf("jewel-foundation", "jewel-ui"),
"jewel-markdown-extensions-images" to setOf("jewel-foundation", "jewel-ui"),
"jewel-int-ui-standalone" to setOf("jewel-foundation"),
"jewel-int-ui-decorated-window" to setOf("jewel-foundation", "jewel-ui", "jewel-int-ui-standalone"),
"jewel-markdown-int-ui-standalone-styling" to setOf("jewel-foundation", "jewel-ui"),

View File

@@ -8208,6 +8208,48 @@ jvm_import(
visibility = ["//visibility:public"]
)
jvm_import(
name = "platform-jewel-markdown-extensions-images-io-coil-kt-coil3-compose-core-jvm",
jar = "@coil-compose-core-jvm-3_1_0_http//file",
source_jar = "@coil-compose-core-jvm-3_1_0-sources_http//file",
visibility = ["//visibility:public"]
)
jvm_import(
name = "platform-jewel-markdown-extensions-images-io-coil-kt-coil3-compose-jvm",
jar = "@coil-compose-jvm-3_1_0_http//file",
source_jar = "@coil-compose-jvm-3_1_0-sources_http//file",
visibility = ["//visibility:public"]
)
jvm_import(
name = "platform-jewel-markdown-extensions-images-io-coil-kt-coil3-core-jvm",
jar = "@coil-core-jvm-3_1_0_http//file",
source_jar = "@coil-core-jvm-3_1_0-sources_http//file",
visibility = ["//visibility:public"]
)
jvm_import(
name = "platform-jewel-markdown-extensions-images-io-coil-kt-coil3-network-core-jvm",
jar = "@coil-network-core-jvm-3_1_0_http//file",
source_jar = "@coil-network-core-jvm-3_1_0-sources_http//file",
visibility = ["//visibility:public"]
)
jvm_import(
name = "platform-jewel-markdown-extensions-images-io-coil-kt-coil3-network-ktor3-jvm",
jar = "@coil-network-ktor3-jvm-3_1_0_http//file",
source_jar = "@coil-network-ktor3-jvm-3_1_0-sources_http//file",
visibility = ["//visibility:public"]
)
jvm_import(
name = "platform-jewel-markdown-extensions-images-io-coil-kt-coil3-svg-jvm",
jar = "@coil-svg-jvm-3_1_0_http//file",
source_jar = "@coil-svg-jvm-3_1_0-sources_http//file",
visibility = ["//visibility:public"]
)
jvm_import(
name = "platform-jewel-samples-standalone-com-darkrockstudios-mpfilepicker",
jar = "@mpfilepicker-3_1_0_http//file",

View File

@@ -11660,6 +11660,90 @@ http_file(
downloaded_file_path = "commonmark-ext-gfm-tables-0.24.0-sources.jar"
)
http_file(
name = "coil-compose-core-jvm-3_1_0_http",
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/io/coil-kt/coil3/coil-compose-core-jvm/3.1.0/coil-compose-core-jvm-3.1.0.jar",
sha256 = "8aa1d7ae1d11f969e8cdcc8fee42b7ee6a036e21f70567e3c3486edd9d7dc594",
downloaded_file_path = "coil-compose-core-jvm-3.1.0.jar"
)
http_file(
name = "coil-compose-core-jvm-3_1_0-sources_http",
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/io/coil-kt/coil3/coil-compose-core-jvm/3.1.0/coil-compose-core-jvm-3.1.0-sources.jar",
sha256 = "70ae99718a081cb53c353f8f60100960ab27c743465781b96fa2b237aa0f5ed2",
downloaded_file_path = "coil-compose-core-jvm-3.1.0-sources.jar"
)
http_file(
name = "coil-compose-jvm-3_1_0_http",
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/io/coil-kt/coil3/coil-compose-jvm/3.1.0/coil-compose-jvm-3.1.0.jar",
sha256 = "d43e0ed4566d30f8ce8e023539240b494be6fa69a1b8c81b4c32f5f465a7cc06",
downloaded_file_path = "coil-compose-jvm-3.1.0.jar"
)
http_file(
name = "coil-compose-jvm-3_1_0-sources_http",
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/io/coil-kt/coil3/coil-compose-jvm/3.1.0/coil-compose-jvm-3.1.0-sources.jar",
sha256 = "f9f84a9feaff1a4299ce2d4f4ba6d4de9cd36865f10676cdc9e798a58088002f",
downloaded_file_path = "coil-compose-jvm-3.1.0-sources.jar"
)
http_file(
name = "coil-core-jvm-3_1_0_http",
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/io/coil-kt/coil3/coil-core-jvm/3.1.0/coil-core-jvm-3.1.0.jar",
sha256 = "6d44a11188c53f4eea2e87ebadc6aad90069132c9b029fc1b35aeb0added5ef7",
downloaded_file_path = "coil-core-jvm-3.1.0.jar"
)
http_file(
name = "coil-core-jvm-3_1_0-sources_http",
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/io/coil-kt/coil3/coil-core-jvm/3.1.0/coil-core-jvm-3.1.0-sources.jar",
sha256 = "3a8039b9e8de0755d0889d53816f02d567bd16e6a5548d3a79ead69d9e1e4b50",
downloaded_file_path = "coil-core-jvm-3.1.0-sources.jar"
)
http_file(
name = "coil-network-core-jvm-3_1_0_http",
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/io/coil-kt/coil3/coil-network-core-jvm/3.1.0/coil-network-core-jvm-3.1.0.jar",
sha256 = "8332e45cf792cd24d9814744db84b0e6d33ec6eaf0724bd07ddc1fce7c55591f",
downloaded_file_path = "coil-network-core-jvm-3.1.0.jar"
)
http_file(
name = "coil-network-core-jvm-3_1_0-sources_http",
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/io/coil-kt/coil3/coil-network-core-jvm/3.1.0/coil-network-core-jvm-3.1.0-sources.jar",
sha256 = "b83d0d40b5f9074240a337fd33b42853c2a92049290623ce3b2c1d84fd1499a9",
downloaded_file_path = "coil-network-core-jvm-3.1.0-sources.jar"
)
http_file(
name = "coil-network-ktor3-jvm-3_1_0_http",
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/io/coil-kt/coil3/coil-network-ktor3-jvm/3.1.0/coil-network-ktor3-jvm-3.1.0.jar",
sha256 = "cc4f9f8d6d447e7559cb9717c3a45dfbd351ddd06a34724001322512160b0215",
downloaded_file_path = "coil-network-ktor3-jvm-3.1.0.jar"
)
http_file(
name = "coil-network-ktor3-jvm-3_1_0-sources_http",
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/io/coil-kt/coil3/coil-network-ktor3-jvm/3.1.0/coil-network-ktor3-jvm-3.1.0-sources.jar",
sha256 = "8f1a55a9ed282fe77df261aa7e4d57172508a70843951af422cfcbf97e0ab349",
downloaded_file_path = "coil-network-ktor3-jvm-3.1.0-sources.jar"
)
http_file(
name = "coil-svg-jvm-3_1_0_http",
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/io/coil-kt/coil3/coil-svg-jvm/3.1.0/coil-svg-jvm-3.1.0.jar",
sha256 = "413a53b4b6e0a40b851d1a99a01a4cd91ac0dc68facefddd7adfdb2d5456588f",
downloaded_file_path = "coil-svg-jvm-3.1.0.jar"
)
http_file(
name = "coil-svg-jvm-3_1_0-sources_http",
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/io/coil-kt/coil3/coil-svg-jvm/3.1.0/coil-svg-jvm-3.1.0-sources.jar",
sha256 = "b12fe3352673d00d5b10f6585a456e3bb0a2f19aadf5d9efcb42bf97dbeb856f",
downloaded_file_path = "coil-svg-jvm-3.1.0-sources.jar"
)
http_file(
name = "mpfilepicker-3_1_0_http",
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/com/darkrockstudios/mpfilepicker/3.1.0/mpfilepicker-3.1.0.jar",

View File

@@ -233,6 +233,18 @@ object CommunityLibraryLicenses {
LibraryLicense("CMake For VisualStudio Code", version = "0.0.17", attachedTo = "intellij.textmate", url = "https://github.com/twxs/vs.language.cmake")
.mit("https://github.com/twxs/vs.language.cmake/blob/master/LICENSE"),
// For loading images in Compose (used in Markdown preview, for example)
LibraryLicense("Coil", libraryName = "io.coil.kt.coil3.core.jvm", url = "https://github.com/coil-kt/coil")
.additionalLibraryNames(
"io.coil.kt.coil3.svg.jvm",
"io.coil.kt.coil3.network.core.jvm",
"io.coil.kt.coil3.network.ktor3.jvm",
"io.coil.kt.coil3.compose.jvm",
"io.coil.kt.coil3.compose.core.jvm",
)
.apache("https://github.com/coil-kt/coil/blob/main/README.md#license")
.copyrightText("Copyright 2025 Coil Contributors"),
LibraryLicense("Command Line Interface Parser for Java", libraryName = "cli-parser", url = "https://github.com/spullara/cli-parser?tab=readme-ov-file")
.apache("https://github.com/spullara/cli-parser/blob/95edeb2d1a21fb13760b4f96f976a7f3108e0942/README.md?plain=1#L65")
.copyrightText("Copyright 2012 Sam Pullara"),

View File

@@ -18,6 +18,7 @@ java_library(
"//platform/jewel/markdown/extensions/gfm-alerts",
"//platform/jewel/markdown/extensions/gfm-strikethrough",
"//platform/jewel/markdown/extensions/gfm-tables",
"//platform/jewel/markdown/extensions/images",
"//platform/jewel/markdown/ide-laf-bridge-styling",
],
runtime_deps = [
@@ -28,6 +29,7 @@ java_library(
"//platform/jewel/markdown/extensions/gfm-alerts",
"//platform/jewel/markdown/extensions/gfm-strikethrough",
"//platform/jewel/markdown/extensions/gfm-tables",
"//platform/jewel/markdown/extensions/images",
"//platform/jewel/markdown/ide-laf-bridge-styling",
]
)

View File

@@ -34,6 +34,7 @@
<orderEntry type="module" module-name="intellij.platform.jewel.markdown.extensions.gfmAlerts" exported="" />
<orderEntry type="module" module-name="intellij.platform.jewel.markdown.extensions.gfmStrikethrough" exported="" />
<orderEntry type="module" module-name="intellij.platform.jewel.markdown.extensions.gfmTables" exported="" />
<orderEntry type="module" module-name="intellij.platform.jewel.markdown.extensions.images" exported="" />
<orderEntry type="module" module-name="intellij.platform.jewel.markdown.ideLafBridgeStyling" exported="" />
</component>
</module>

View File

@@ -17,6 +17,7 @@ dependencies {
sarif(projects.markdown.extensions.gfmAlerts)
sarif(projects.markdown.extensions.gfmStrikethrough)
sarif(projects.markdown.extensions.gfmTables)
sarif(projects.markdown.extensions.images)
sarif(projects.markdown.ideLafBridgeStyling)
sarif(projects.markdown.intUiStandaloneStyling)
sarif(projects.samples.idePlugin)

View File

@@ -1,4 +1,5 @@
[versions]
coil = "3.1.0"
commonmark = "0.24.0"
composeDesktop = "1.8.1"
detekt = "1.23.6"
@@ -17,6 +18,11 @@ kotlinxBinaryCompat = "0.17.0"
ktfmtGradlePlugin = "0.22.0"
[libraries]
coil-compose-core = { module = "io.coil-kt.coil3:coil-compose-core", version.ref = "coil" }
# network is only needed in a standalone non-ide version
coil-network-ktor3 = { module = "io.coil-kt.coil3:coil-network-ktor3", version.ref = "coil" }
coil-svg = { module = "io.coil-kt.coil3:coil-svg", version.ref = "coil" }
commonmark-core = { module = "org.commonmark:commonmark", version.ref = "commonmark" }
commonmark-ext-autolink = { module = "org.commonmark:commonmark-ext-autolink", version.ref = "commonmark" }
commonmark-ext-gfm-strikethrough = { module = "org.commonmark:commonmark-ext-gfm-strikethrough", version.ref = "commonmark" }
@@ -27,6 +33,7 @@ filePicker = { module = "com.darkrockstudios:mpfilepicker", version.ref = "filep
kotlinSarif = { module = "io.github.detekt.sarif4k:sarif4k", version.ref = "kotlinSarif" }
kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerialization" }
ktor-client-java = { module = "io.ktor:ktor-client-java", version = "3.0.3" }
jna-core = { module = "net.java.dev.jna:jna", version.ref = "jna" }

View File

@@ -2,6 +2,10 @@ f:org.jetbrains.jewel.markdown.MarkdownBlock$ListItem
- <init>(java.util.List,I):V
- <init>(org.jetbrains.jewel.markdown.MarkdownBlock[],I):V
- f:getLevel():I
org.jetbrains.jewel.markdown.extensions.ImageRendererExtension
- a:renderImagesContent(org.jetbrains.jewel.markdown.InlineMarkdown$Image,androidx.compose.runtime.Composer,I):androidx.compose.foundation.text.InlineTextContent
org.jetbrains.jewel.markdown.extensions.MarkdownRendererExtension
- getImageRendererExtension():org.jetbrains.jewel.markdown.extensions.ImageRendererExtension
f:org.jetbrains.jewel.markdown.processing.MarkdownProcessor
- sf:$stable:I
- <init>():V

View File

@@ -260,6 +260,10 @@ public abstract interface class org/jetbrains/jewel/markdown/WithTextContent {
public abstract fun getContent ()Ljava/lang/String;
}
public abstract interface class org/jetbrains/jewel/markdown/extensions/ImageRendererExtension {
public abstract fun renderImagesContent (Lorg/jetbrains/jewel/markdown/InlineMarkdown$Image;Landroidx/compose/runtime/Composer;I)Landroidx/compose/foundation/text/InlineTextContent;
}
public abstract interface class org/jetbrains/jewel/markdown/extensions/MarkdownBlockProcessorExtension {
public abstract fun canProcess (Lorg/commonmark/node/CustomBlock;)Z
public abstract fun processMarkdownBlock (Lorg/commonmark/node/CustomBlock;Lorg/jetbrains/jewel/markdown/processing/MarkdownProcessor;)Lorg/jetbrains/jewel/markdown/MarkdownBlock$CustomBlock;
@@ -308,11 +312,13 @@ public final class org/jetbrains/jewel/markdown/extensions/MarkdownProcessorExte
public abstract interface class org/jetbrains/jewel/markdown/extensions/MarkdownRendererExtension {
public abstract fun getBlockRenderer ()Lorg/jetbrains/jewel/markdown/extensions/MarkdownBlockRendererExtension;
public abstract fun getDelimitedInlineRenderer ()Lorg/jetbrains/jewel/markdown/extensions/MarkdownDelimitedInlineRendererExtension;
public abstract fun getImageRendererExtension ()Lorg/jetbrains/jewel/markdown/extensions/ImageRendererExtension;
}
public final class org/jetbrains/jewel/markdown/extensions/MarkdownRendererExtension$DefaultImpls {
public static fun getBlockRenderer (Lorg/jetbrains/jewel/markdown/extensions/MarkdownRendererExtension;)Lorg/jetbrains/jewel/markdown/extensions/MarkdownBlockRendererExtension;
public static fun getDelimitedInlineRenderer (Lorg/jetbrains/jewel/markdown/extensions/MarkdownRendererExtension;)Lorg/jetbrains/jewel/markdown/extensions/MarkdownDelimitedInlineRendererExtension;
public static fun getImageRendererExtension (Lorg/jetbrains/jewel/markdown/extensions/MarkdownRendererExtension;)Lorg/jetbrains/jewel/markdown/extensions/ImageRendererExtension;
}
public final class org/jetbrains/jewel/markdown/processing/MarkdownParserFactory {

View File

@@ -0,0 +1,12 @@
package org.jetbrains.jewel.markdown.extensions
import androidx.compose.foundation.text.InlineTextContent
import androidx.compose.runtime.Composable
import org.jetbrains.jewel.foundation.ExperimentalJewelApi
import org.jetbrains.jewel.markdown.InlineMarkdown
/** An extension for the Jewel images rendering engine. */
@ExperimentalJewelApi
public interface ImageRendererExtension {
@Composable public fun renderImagesContent(image: InlineMarkdown.Image): InlineTextContent
}

View File

@@ -24,4 +24,10 @@ public interface MarkdownRendererExtension {
*/
public val delimitedInlineRenderer: MarkdownDelimitedInlineRendererExtension?
get() = null
/**
* An extension for handling the rendering of image elements. Can be null if no custom image rendering is provided.
*/
public val imageRendererExtension: ImageRendererExtension?
get() = null
}

View File

@@ -1,5 +1,6 @@
package org.jetbrains.jewel.markdown.rendering
import androidx.compose.foundation.text.appendInlineContent
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.takeOrElse
import androidx.compose.ui.text.AnnotatedString
@@ -166,7 +167,8 @@ public open class DefaultInlineMarkdownRenderer(rendererExtensions: List<Markdow
enabled: Boolean,
currentTextStyle: TextStyle,
) {
// Not supported yet — see JEWEL-746
// Each image source corresponds to one rendered image.
appendInlineContent(node.source, "![${node.title}](${node.source})")
}
// The T type parameter is needed to avoid issues with capturing lambdas

View File

@@ -14,6 +14,7 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.text.InlineTextContent
import androidx.compose.foundation.text.selection.DisableSelection
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
@@ -42,6 +43,7 @@ import org.jetbrains.jewel.foundation.code.MimeType
import org.jetbrains.jewel.foundation.code.highlighting.LocalCodeHighlighter
import org.jetbrains.jewel.foundation.modifier.thenIf
import org.jetbrains.jewel.foundation.theme.LocalContentColor
import org.jetbrains.jewel.markdown.InlineMarkdown
import org.jetbrains.jewel.markdown.MarkdownBlock
import org.jetbrains.jewel.markdown.MarkdownBlock.BlockQuote
import org.jetbrains.jewel.markdown.MarkdownBlock.CodeBlock
@@ -150,6 +152,7 @@ public open class DefaultMarkdownBlockRenderer(
.clickable(interactionSource = interactionSource, indication = null, onClick = onTextClick),
text = renderedContent,
style = mergedStyle,
inlineContent = renderedImages(block),
)
}
@@ -523,6 +526,17 @@ public open class DefaultMarkdownBlockRenderer(
inlineRenderer.renderAsAnnotatedString(block.inlineContent, styling, enabled, onUrlClick)
}
@Composable
private fun renderedImages(blockInlineContent: WithInlineMarkdown): Map<String, InlineTextContent> {
return rendererExtensions
.firstNotNullOfOrNull { it.imageRendererExtension }
?.let { imagesRenderer ->
getImages(blockInlineContent).associate { image ->
image.source to imagesRenderer.renderImagesContent(image)
}
} ?: emptyMap()
}
@Composable
protected fun MaybeScrollingContainer(
isScrollable: Boolean,
@@ -551,3 +565,21 @@ public open class DefaultMarkdownBlockRenderer(
override operator fun plus(extension: MarkdownRendererExtension): MarkdownBlockRenderer =
DefaultMarkdownBlockRenderer(rootStyling, rendererExtensions = rendererExtensions + extension, inlineRenderer)
}
private fun getImages(input: WithInlineMarkdown): List<InlineMarkdown.Image> = buildList {
fun collectImagesRecursively(items: List<InlineMarkdown>) {
for (item in items) {
when (item) {
is InlineMarkdown.Image -> {
if (item.source.isNotBlank()) add(item)
}
is WithInlineMarkdown -> {
collectImagesRecursively(item.inlineContent)
}
else -> {}
}
}
}
collectImagesRecursively(input.inlineContent)
}

View File

@@ -143,6 +143,7 @@ public final class org/jetbrains/jewel/markdown/extensions/github/alerts/GitHubA
public fun <init> (Lorg/jetbrains/jewel/markdown/extensions/github/alerts/AlertStyling;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling;)V
public fun getBlockRenderer ()Lorg/jetbrains/jewel/markdown/extensions/MarkdownBlockRendererExtension;
public fun getDelimitedInlineRenderer ()Lorg/jetbrains/jewel/markdown/extensions/MarkdownDelimitedInlineRendererExtension;
public fun getImageRendererExtension ()Lorg/jetbrains/jewel/markdown/extensions/ImageRendererExtension;
}
public final class org/jetbrains/jewel/markdown/extensions/github/alerts/ImportantAlertStyling : org/jetbrains/jewel/markdown/extensions/github/alerts/BaseAlertStyling {

View File

@@ -44,5 +44,6 @@ public final class org/jetbrains/jewel/markdown/extensions/github/strikethrough/
public static final field INSTANCE Lorg/jetbrains/jewel/markdown/extensions/github/strikethrough/GitHubStrikethroughRendererExtension;
public fun getBlockRenderer ()Lorg/jetbrains/jewel/markdown/extensions/MarkdownBlockRendererExtension;
public fun getDelimitedInlineRenderer ()Lorg/jetbrains/jewel/markdown/extensions/MarkdownDelimitedInlineRendererExtension;
public fun getImageRendererExtension ()Lorg/jetbrains/jewel/markdown/extensions/ImageRendererExtension;
}

View File

@@ -67,6 +67,7 @@ public final class org/jetbrains/jewel/markdown/extensions/github/tables/GitHubT
public fun <init> (Lorg/jetbrains/jewel/markdown/extensions/github/tables/GfmTableStyling;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling;)V
public fun getBlockRenderer ()Lorg/jetbrains/jewel/markdown/extensions/MarkdownBlockRendererExtension;
public fun getDelimitedInlineRenderer ()Lorg/jetbrains/jewel/markdown/extensions/MarkdownDelimitedInlineRendererExtension;
public fun getImageRendererExtension ()Lorg/jetbrains/jewel/markdown/extensions/ImageRendererExtension;
}
public final class org/jetbrains/jewel/markdown/extensions/github/tables/RowBackgroundStyle : java/lang/Enum {

View File

@@ -0,0 +1,51 @@
### auto-generated section `build intellij.platform.jewel.markdown.extensions.images` start
load("//build:compiler-options.bzl", "create_kotlinc_options")
load("@rules_jvm//:jvm.bzl", "jvm_library", "jvm_resources")
create_kotlinc_options(
name = "custom",
context_receivers = True,
opt_in = [
"androidx.compose.ui.ExperimentalComposeUiApi",
"androidx.compose.foundation.ExperimentalFoundationApi",
"org.jetbrains.jewel.foundation.ExperimentalJewelApi",
"org.jetbrains.jewel.foundation.InternalJewelApi",
]
)
jvm_resources(
name = "images_resources",
files = glob(["src/main/resources/**/*"]),
strip_prefix = "src/main/resources"
)
jvm_library(
name = "images",
module_name = "intellij.platform.jewel.markdown.extensions.images",
visibility = ["//visibility:public"],
srcs = glob(["src/main/kotlin/**/*.kt", "src/main/kotlin/**/*.java"], allow_empty = True),
kotlinc_opts = ":custom",
deps = [
"@lib//:kotlin-stdlib",
"@lib//:kotlinx-coroutines-core",
"@lib//:jetbrains-annotations",
"//platform/jewel/markdown/core",
"//platform/jewel/ui",
"//platform/jewel/foundation",
"//libraries/compose-foundation-desktop",
"@lib//:platform-jewel-markdown-extensions-images-io-coil-kt-coil3-core-jvm",
"@lib//:platform-jewel-markdown-extensions-images-io-coil-kt-coil3-compose-core-jvm",
"@lib//:platform-jewel-markdown-extensions-images-io-coil-kt-coil3-compose-jvm",
"@lib//:platform-jewel-markdown-extensions-images-io-coil-kt-coil3-network-core-jvm",
"@lib//:platform-jewel-markdown-extensions-images-io-coil-kt-coil3-network-ktor3-jvm",
"@lib//:platform-jewel-markdown-extensions-images-io-coil-kt-coil3-svg-jvm",
],
exports = [
"@lib//:platform-jewel-markdown-extensions-images-io-coil-kt-coil3-compose-core-jvm",
"@lib//:platform-jewel-markdown-extensions-images-io-coil-kt-coil3-compose-jvm",
"@lib//:platform-jewel-markdown-extensions-images-io-coil-kt-coil3-svg-jvm",
],
runtime_deps = [":images_resources"],
plugins = ["@lib//:compose-plugin"]
)
### auto-generated section `build intellij.platform.jewel.markdown.extensions.images` end

View File

@@ -0,0 +1,14 @@
# Images module
This module adds support for inline images, typically rendered from `![alt](url)` syntax.
It's based on core CommonMark but allows users to not depend on Coil if they don't need images support
and avoiding runtime image loading dependencies.
* No Coil or UI image loading logic is included in the image parser module.
* Keeps the structure modular, consistent with how other extensions are handled.
## Usage
To use the image module, add the `Coil3ImagesRendererExtension` to your `rendererExtensions`.
If you don't want to use library-specific image loader, you can pass it as a parameter,
but you have to handle its initialization as well as making sure it's the same instance.

View File

@@ -0,0 +1,13 @@
f:org.jetbrains.jewel.markdown.extensions.images.Coil3ImagesRendererExtension
- org.jetbrains.jewel.markdown.extensions.MarkdownRendererExtension
- sf:$stable:I
- sf:Companion:org.jetbrains.jewel.markdown.extensions.images.Coil3ImagesRendererExtension$Companion
- <init>(coil3.ImageLoader):V
- getImageRendererExtension():org.jetbrains.jewel.markdown.extensions.ImageRendererExtension
f:org.jetbrains.jewel.markdown.extensions.images.Coil3ImagesRendererExtension$Companion
- f:withDefaultLoader(androidx.compose.runtime.Composer,I):org.jetbrains.jewel.markdown.extensions.images.Coil3ImagesRendererExtension
f:org.jetbrains.jewel.markdown.extensions.images.Coil3ImagesRendererExtensionImpl
- org.jetbrains.jewel.markdown.extensions.ImageRendererExtension
- sf:$stable:I
- <init>(coil3.ImageLoader):V
- renderImagesContent(org.jetbrains.jewel.markdown.InlineMarkdown$Image,androidx.compose.runtime.Composer,I):androidx.compose.foundation.text.InlineTextContent

View File

@@ -0,0 +1,19 @@
public final class org/jetbrains/jewel/markdown/extensions/images/Coil3ImagesRendererExtension : org/jetbrains/jewel/markdown/extensions/MarkdownRendererExtension {
public static final field $stable I
public static final field Companion Lorg/jetbrains/jewel/markdown/extensions/images/Coil3ImagesRendererExtension$Companion;
public fun <init> (Lcoil3/ImageLoader;)V
public fun getBlockRenderer ()Lorg/jetbrains/jewel/markdown/extensions/MarkdownBlockRendererExtension;
public fun getDelimitedInlineRenderer ()Lorg/jetbrains/jewel/markdown/extensions/MarkdownDelimitedInlineRendererExtension;
public fun getImageRendererExtension ()Lorg/jetbrains/jewel/markdown/extensions/ImageRendererExtension;
}
public final class org/jetbrains/jewel/markdown/extensions/images/Coil3ImagesRendererExtension$Companion {
public final fun withDefaultLoader (Landroidx/compose/runtime/Composer;I)Lorg/jetbrains/jewel/markdown/extensions/images/Coil3ImagesRendererExtension;
}
public final class org/jetbrains/jewel/markdown/extensions/images/Coil3ImagesRendererExtensionImpl : org/jetbrains/jewel/markdown/extensions/ImageRendererExtension {
public static final field $stable I
public fun <init> (Lcoil3/ImageLoader;)V
public fun renderImagesContent (Lorg/jetbrains/jewel/markdown/InlineMarkdown$Image;Landroidx/compose/runtime/Composer;I)Landroidx/compose/foundation/text/InlineTextContent;
}

View File

@@ -0,0 +1,23 @@
plugins {
jewel
`jewel-publish`
`jewel-check-public-api`
alias(libs.plugins.composeDesktop)
alias(libs.plugins.compose.compiler)
}
dependencies {
implementation(projects.markdown.core)
runtimeOnly(libs.ktor.client.java)
implementation(libs.coil.compose.core)
implementation(libs.coil.network.ktor3)
implementation(libs.coil.svg)
testImplementation(compose.desktop.uiTestJUnit4)
}
publishing.publications.named<MavenPublication>("main") {
val ijpTarget = project.property("ijp.target") as String
artifactId = "jewel-markdown-extension-${project.name}-$ijpTarget"
}
publicApiValidation { excludedClassRegexes = setOf("org.jetbrains.jewel.markdown.extensions.images.*") }

View File

@@ -0,0 +1,2 @@
androidx/compose/**
coil3/ImageLoader

View File

@@ -0,0 +1,149 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="kotlin-language" name="Kotlin">
<configuration version="5" platform="JVM 17" allPlatforms="JVM [17]" useProjectSettings="false">
<compilerSettings>
<option name="additionalArguments" value="-Xjvm-default=all -opt-in=androidx.compose.ui.ExperimentalComposeUiApi -Xcontext-receivers -opt-in=androidx.compose.foundation.ExperimentalFoundationApi -opt-in=org.jetbrains.jewel.foundation.ExperimentalJewelApi -opt-in=org.jetbrains.jewel.foundation.InternalJewelApi -Xexplicit-api=strict" />
</compilerSettings>
<compilerArguments>
<stringArguments>
<stringArg name="jvmTarget" arg="17" />
<stringArg name="apiVersion" arg="2.2" />
<stringArg name="languageVersion" arg="2.2" />
</stringArguments>
<arrayArguments>
<arrayArg name="pluginClasspaths">
<args>$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-compose-compiler-plugin/2.2.0/kotlin-compose-compiler-plugin-2.2.0.jar</args>
</arrayArg>
</arrayArguments>
</compilerArguments>
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/kotlin" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="library" name="kotlin-stdlib" level="project" />
<orderEntry type="library" name="kotlinx-coroutines-core" level="project" />
<orderEntry type="library" name="jetbrains-annotations" level="project" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module" module-name="intellij.platform.jewel.markdown.core" />
<orderEntry type="module" module-name="intellij.platform.jewel.ui" />
<orderEntry type="module" module-name="intellij.platform.jewel.foundation" />
<orderEntry type="module" module-name="intellij.libraries.compose.foundation.desktop" />
<orderEntry type="module-library">
<library name="io.coil.kt.coil3.core.jvm" type="repository">
<properties include-transitive-deps="false" maven-id="io.coil-kt.coil3:coil-core-jvm:3.1.0">
<verification>
<artifact url="file://$MAVEN_REPOSITORY$/io/coil-kt/coil3/coil-core-jvm/3.1.0/coil-core-jvm-3.1.0.jar">
<sha256sum>6d44a11188c53f4eea2e87ebadc6aad90069132c9b029fc1b35aeb0added5ef7</sha256sum>
</artifact>
</verification>
</properties>
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/io/coil-kt/coil3/coil-core-jvm/3.1.0/coil-core-jvm-3.1.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/io/coil-kt/coil3/coil-core-jvm/3.1.0/coil-core-jvm-3.1.0-sources.jar!/" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module-library" exported="">
<library name="io.coil.kt.coil3.compose.core.jvm" type="repository">
<properties include-transitive-deps="false" maven-id="io.coil-kt.coil3:coil-compose-core-jvm:3.1.0">
<verification>
<artifact url="file://$MAVEN_REPOSITORY$/io/coil-kt/coil3/coil-compose-core-jvm/3.1.0/coil-compose-core-jvm-3.1.0.jar">
<sha256sum>8aa1d7ae1d11f969e8cdcc8fee42b7ee6a036e21f70567e3c3486edd9d7dc594</sha256sum>
</artifact>
</verification>
</properties>
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/io/coil-kt/coil3/coil-compose-core-jvm/3.1.0/coil-compose-core-jvm-3.1.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/io/coil-kt/coil3/coil-compose-core-jvm/3.1.0/coil-compose-core-jvm-3.1.0-sources.jar!/" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module-library" exported="">
<library name="io.coil.kt.coil3.compose.jvm" type="repository">
<properties include-transitive-deps="false" maven-id="io.coil-kt.coil3:coil-compose-jvm:3.1.0">
<verification>
<artifact url="file://$MAVEN_REPOSITORY$/io/coil-kt/coil3/coil-compose-jvm/3.1.0/coil-compose-jvm-3.1.0.jar">
<sha256sum>d43e0ed4566d30f8ce8e023539240b494be6fa69a1b8c81b4c32f5f465a7cc06</sha256sum>
</artifact>
</verification>
</properties>
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/io/coil-kt/coil3/coil-compose-jvm/3.1.0/coil-compose-jvm-3.1.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/io/coil-kt/coil3/coil-compose-jvm/3.1.0/coil-compose-jvm-3.1.0-sources.jar!/" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module-library">
<library name="io.coil.kt.coil3.network.core.jvm" type="repository">
<properties include-transitive-deps="false" maven-id="io.coil-kt.coil3:coil-network-core-jvm:3.1.0">
<verification>
<artifact url="file://$MAVEN_REPOSITORY$/io/coil-kt/coil3/coil-network-core-jvm/3.1.0/coil-network-core-jvm-3.1.0.jar">
<sha256sum>8332e45cf792cd24d9814744db84b0e6d33ec6eaf0724bd07ddc1fce7c55591f</sha256sum>
</artifact>
</verification>
</properties>
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/io/coil-kt/coil3/coil-network-core-jvm/3.1.0/coil-network-core-jvm-3.1.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/io/coil-kt/coil3/coil-network-core-jvm/3.1.0/coil-network-core-jvm-3.1.0-sources.jar!/" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module-library">
<library name="io.coil.kt.coil3.network.ktor3.jvm" type="repository">
<properties include-transitive-deps="false" maven-id="io.coil-kt.coil3:coil-network-ktor3-jvm:3.1.0">
<verification>
<artifact url="file://$MAVEN_REPOSITORY$/io/coil-kt/coil3/coil-network-ktor3-jvm/3.1.0/coil-network-ktor3-jvm-3.1.0.jar">
<sha256sum>cc4f9f8d6d447e7559cb9717c3a45dfbd351ddd06a34724001322512160b0215</sha256sum>
</artifact>
</verification>
</properties>
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/io/coil-kt/coil3/coil-network-ktor3-jvm/3.1.0/coil-network-ktor3-jvm-3.1.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/io/coil-kt/coil3/coil-network-ktor3-jvm/3.1.0/coil-network-ktor3-jvm-3.1.0-sources.jar!/" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module-library" exported="">
<library name="io.coil.kt.coil3.svg.jvm" type="repository">
<properties include-transitive-deps="false" maven-id="io.coil-kt.coil3:coil-svg-jvm:3.1.0">
<verification>
<artifact url="file://$MAVEN_REPOSITORY$/io/coil-kt/coil3/coil-svg-jvm/3.1.0/coil-svg-jvm-3.1.0.jar">
<sha256sum>413a53b4b6e0a40b851d1a99a01a4cd91ac0dc68facefddd7adfdb2d5456588f</sha256sum>
</artifact>
</verification>
</properties>
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/io/coil-kt/coil3/coil-svg-jvm/3.1.0/coil-svg-jvm-3.1.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/io/coil-kt/coil3/coil-svg-jvm/3.1.0/coil-svg-jvm-3.1.0-sources.jar!/" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module" module-name="intellij.libraries.compose.foundation.desktop.junit" scope="TEST" />
</component>
</module>

View File

@@ -0,0 +1,40 @@
package org.jetbrains.jewel.markdown.extensions.images
import androidx.compose.runtime.Composable
import coil3.ImageLoader
import coil3.compose.LocalPlatformContext
import coil3.memory.MemoryCache
import org.jetbrains.jewel.markdown.extensions.ImageRendererExtension
import org.jetbrains.jewel.markdown.extensions.MarkdownRendererExtension
private const val DEFAULT_MEMORY_CACHE_SIZE: Long = 20 * 1024 * 1024 // 20 MB
/**
* A [MarkdownRendererExtension] for rendering images using the Coil3 library.
*
* This extension enables the display of images specified in Markdown, for example: `![An
* image](https://example.com/image.png)`.
*
* It requires an [ImageLoader] to handle fetching and displaying images. For optimal performance and resource
* management (e.g., shared memory and disk caches), it is recommended to provide a single, app-wide [ImageLoader]
* instance via the constructor.
*/
public class Coil3ImagesRendererExtension(private val imageLoader: ImageLoader) : MarkdownRendererExtension {
override val imageRendererExtension: ImageRendererExtension
get() = Coil3ImagesRendererExtensionImpl(imageLoader)
public companion object {
/**
* A default images loader with limited in-memory cache is to decouple clients from depending directly on coil
* internals. This shouldn't be used if there is an app-wide image loader already availble; Which could be set
* via a constructor.
*/
@Composable
public fun withDefaultLoader(): Coil3ImagesRendererExtension =
Coil3ImagesRendererExtension(
ImageLoader.Builder(LocalPlatformContext.current)
.memoryCache { MemoryCache.Builder().maxSizeBytes { DEFAULT_MEMORY_CACHE_SIZE }.build() }
.build()
)
}
}

View File

@@ -0,0 +1,75 @@
package org.jetbrains.jewel.markdown.extensions.images
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.text.InlineTextContent
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.Placeholder
import androidx.compose.ui.text.PlaceholderVerticalAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import coil3.ImageLoader
import coil3.compose.AsyncImage
import coil3.compose.LocalPlatformContext
import coil3.request.ImageRequest
import coil3.size.Size
import org.jetbrains.jewel.foundation.util.JewelLogger
import org.jetbrains.jewel.markdown.InlineMarkdown
import org.jetbrains.jewel.markdown.extensions.ImageRendererExtension
/**
* Renders Markdown images using Coil3.
*
* This implementation uses [AsyncImage] to load and display images. It reserves space for the image with a
* [Placeholder] and updates its size upon successful loading to prevent layout shifts.
*
* @param imageLoader A custom [ImageLoader] for Coil3 image requests.
*/
public class Coil3ImagesRendererExtensionImpl(private val imageLoader: ImageLoader) : ImageRendererExtension {
@Composable
public override fun renderImagesContent(image: InlineMarkdown.Image): InlineTextContent {
val knownSize = remember(image.source) { mutableStateOf<ImageSize?>(null) }
return InlineTextContent(
with(LocalDensity.current) {
// `toSp` ensures that the placeholder size matches the original image size in
// pixels.
// This approach doesn't allow images from appearing larger with different screen
// scaling,
// but simply maintains behavior consistent with standalone AsyncImage rendering.
Placeholder(
width = knownSize.value?.width?.dp?.toSp() ?: 0.sp,
height = knownSize.value?.height?.dp?.toSp() ?: 1.sp,
placeholderVerticalAlign = PlaceholderVerticalAlign.Bottom,
)
}
) {
AsyncImage(
model =
ImageRequest.Builder(LocalPlatformContext.current)
.data(image.source)
// make sure image doesn't get downscaled to the placeholder size
.size(Size.ORIGINAL)
.build(),
imageLoader = imageLoader,
contentDescription = image.title,
onSuccess = { state ->
// onSuccess should only be called once, but adding additional protection from
// unnecessary rerender
if (knownSize.value == null) {
knownSize.value = state.result.image.let { ImageSize(it.width, it.height) }
}
},
onError = { error ->
JewelLogger.getInstance("Jewel").warn("AsyncImage loading: ${error.result.throwable}")
},
modifier = knownSize.value?.let { Modifier.height(it.height.dp).width(it.width.dp) } ?: Modifier,
)
}
}
private data class ImageSize(val width: Int, val height: Int)
}

View File

@@ -0,0 +1,7 @@
<idea-plugin>
<dependencies>
<module name="intellij.platform.jewel.markdown.core" />
<module name="intellij.platform.jewel.ui" />
<module name="intellij.platform.jewel.foundation" />
</dependencies>
</idea-plugin>

View File

@@ -8,5 +8,6 @@
<module name="intellij.platform.jewel.markdown.extensions.gfmAlerts" />
<module name="intellij.platform.jewel.markdown.extensions.gfmTables" />
<module name="intellij.platform.jewel.markdown.extensions.gfmStrikethrough" />
<module name="intellij.platform.jewel.markdown.extensions.images" />
</dependencies>
</idea-plugin>

View File

@@ -42,6 +42,7 @@ jvm_library(
"//platform/jewel/markdown/extensions/gfm-alerts",
"//platform/jewel/markdown/extensions/gfm-tables",
"//platform/jewel/markdown/extensions/gfm-strikethrough",
"//platform/jewel/markdown/extensions/images",
"//platform/jewel/int-ui/int-ui-standalone:jewel-intUi-standalone",
"//libraries/compose-foundation-desktop",
"//platform/jewel/samples/showcase",

View File

@@ -12,10 +12,11 @@ dependencies {
implementation(projects.intUi.intUiDecoratedWindow)
implementation(projects.intUi.intUiStandalone)
implementation(projects.markdown.core)
implementation(projects.markdown.extensions.autolink)
implementation(projects.markdown.extensions.gfmAlerts)
implementation(projects.markdown.extensions.gfmStrikethrough)
implementation(projects.markdown.extensions.gfmTables)
implementation(projects.markdown.extensions.autolink)
implementation(projects.markdown.extensions.images)
implementation(projects.markdown.intUiStandaloneStyling)
implementation(projects.samples.showcase)

View File

@@ -79,6 +79,7 @@
<orderEntry type="module" module-name="intellij.platform.jewel.markdown.extensions.gfmAlerts" />
<orderEntry type="module" module-name="intellij.platform.jewel.markdown.extensions.gfmTables" />
<orderEntry type="module" module-name="intellij.platform.jewel.markdown.extensions.gfmStrikethrough" />
<orderEntry type="module" module-name="intellij.platform.jewel.markdown.extensions.images" />
<orderEntry type="module-library" scope="RUNTIME">
<library name="org.lwjgl.lwjgl.tinyfd" type="repository">
<properties include-transitive-deps="false" maven-id="org.lwjgl:lwjgl-tinyfd:3.3.1">

View File

@@ -21,6 +21,8 @@ desktop-optimized theme and set of components.
>
> Use at your own risk!
![logo](https://avatars.githubusercontent.com/u/878437?s=48)
Jewel provides an implementation of the IntelliJ Platform themes that can be used in any Compose for Desktop
application. Additionally, it has a Swing LaF Bridge that only works in the IntelliJ Platform (i.e., used to create IDE
plugins), but automatically mirrors the current Swing LaF into Compose for a native-looking, consistent UI.

View File

@@ -38,6 +38,7 @@ import org.jetbrains.jewel.markdown.extensions.github.strikethrough.GitHubStrike
import org.jetbrains.jewel.markdown.extensions.github.tables.GfmTableStyling
import org.jetbrains.jewel.markdown.extensions.github.tables.GitHubTableProcessorExtension
import org.jetbrains.jewel.markdown.extensions.github.tables.GitHubTableRendererExtension
import org.jetbrains.jewel.markdown.extensions.images.Coil3ImagesRendererExtension
import org.jetbrains.jewel.markdown.processing.MarkdownProcessor
import org.jetbrains.jewel.markdown.rendering.MarkdownBlockRenderer
import org.jetbrains.jewel.markdown.rendering.MarkdownStyling
@@ -65,6 +66,7 @@ public fun MarkdownPreview(modifier: Modifier = Modifier, rawMarkdown: CharSeque
)
)
}
val coil3ImagesRendererExtension = Coil3ImagesRendererExtension.withDefaultLoader()
LaunchedEffect(rawMarkdown) {
// TODO you may want to debounce or drop on backpressure, in real usages. You should also
@@ -81,6 +83,7 @@ public fun MarkdownPreview(modifier: Modifier = Modifier, rawMarkdown: CharSeque
styling = markdownStyling,
rendererExtensions =
listOf(
coil3ImagesRendererExtension,
GitHubAlertRendererExtension(AlertStyling.dark(), markdownStyling),
GitHubStrikethroughRendererExtension,
GitHubTableRendererExtension(GfmTableStyling.dark(), markdownStyling),
@@ -91,6 +94,7 @@ public fun MarkdownPreview(modifier: Modifier = Modifier, rawMarkdown: CharSeque
styling = markdownStyling,
rendererExtensions =
listOf(
coil3ImagesRendererExtension,
GitHubAlertRendererExtension(AlertStyling.light(), markdownStyling),
GitHubStrikethroughRendererExtension,
GitHubTableRendererExtension(GfmTableStyling.light(), markdownStyling),

View File

@@ -11,5 +11,6 @@
<module name="intellij.platform.jewel.markdown.extensions.gfmStrikethrough" />
<module name="intellij.platform.jewel.markdown.extensions.gfmTables" />
<module name="intellij.platform.jewel.markdown.extensions.autolink" />
<module name="intellij.platform.jewel.markdown.extensions.images" />
</dependencies>
</idea-plugin>

View File

@@ -43,6 +43,7 @@ include(
":markdown:extensions:gfm-alerts",
":markdown:extensions:gfm-strikethrough",
":markdown:extensions:gfm-tables",
":markdown:extensions:images",
":markdown:int-ui-standalone-styling",
":markdown:ide-laf-bridge-styling",
":samples:ide-plugin",

View File

@@ -24,6 +24,7 @@
<module name="intellij.platform.jewel.markdown.extensions.gfmAlerts"/>
<module name="intellij.platform.jewel.markdown.extensions.gfmTables"/>
<module name="intellij.platform.jewel.markdown.extensions.gfmStrikethrough"/>
<module name="intellij.platform.jewel.markdown.extensions.images"/>
<module name="intellij.platform.jewel.markdown.core"/>
<module name="intellij.platform.diagnostic.freezeAnalyzer"/>
<module name="intellij.platform.diagnostic.freezes"/>

View File

@@ -46,6 +46,7 @@ import org.jetbrains.jewel.markdown.extensions.github.strikethrough.GitHubStrike
import org.jetbrains.jewel.markdown.extensions.github.tables.GfmTableStyling
import org.jetbrains.jewel.markdown.extensions.github.tables.GitHubTableProcessorExtension
import org.jetbrains.jewel.markdown.extensions.github.tables.GitHubTableRendererExtension
import org.jetbrains.jewel.markdown.extensions.images.Coil3ImagesRendererExtension
import org.jetbrains.jewel.markdown.processing.MarkdownProcessor
import org.jetbrains.jewel.markdown.rendering.DefaultInlineMarkdownRenderer
import org.jetbrains.jewel.markdown.scrolling.ScrollSyncMarkdownBlockRenderer
@@ -57,7 +58,7 @@ import kotlin.time.Duration.Companion.milliseconds
internal class MarkdownComposePanel(
private val project: Project?,
private val virtualFile: VirtualFile?,
private val updateHandler: MarkdownUpdateHandler = MarkdownUpdateHandler.Debounced()
private val updateHandler: MarkdownUpdateHandler = MarkdownUpdateHandler.Debounced(),
) : MarkdownHtmlPanelEx, UserDataHolder by UserDataHolderBase() {
constructor() : this(null, null)
@@ -95,7 +96,7 @@ internal class MarkdownComposePanel(
val tableRenderer = remember(markdownStyling) {
GitHubTableRendererExtension(GfmTableStyling.create(), markdownStyling)
}
val allRenderingExtensions = listOf(tableRenderer, GitHubStrikethroughRendererExtension)
val allRenderingExtensions = listOf(tableRenderer, GitHubStrikethroughRendererExtension, Coil3ImagesRendererExtension.withDefaultLoader())
val blockRenderer = remember(markdownStyling) {
ScrollSyncMarkdownBlockRenderer(
markdownStyling,
@@ -132,10 +133,11 @@ internal class MarkdownComposePanel(
@OptIn(FlowPreview::class)
@Suppress("FunctionName")
@Composable
private fun MarkdownPreviewPanel(scrollState: ScrollState,
scrollingSynchronizer: ScrollingSynchronizer?,
blockRenderer: ScrollSyncMarkdownBlockRenderer,
animationSpec: AnimationSpec<Float> = TweenSpec(easing = LinearEasing)
private fun MarkdownPreviewPanel(
scrollState: ScrollState,
scrollingSynchronizer: ScrollingSynchronizer?,
blockRenderer: ScrollSyncMarkdownBlockRenderer,
animationSpec: AnimationSpec<Float> = TweenSpec(easing = LinearEasing),
) {
val request by updateHandler.requests.collectAsState(null)
(request as? PreviewRequest.Update)?.let {
@@ -170,7 +172,7 @@ internal class MarkdownComposePanel(
MarkdownLinkOpener.getInstance().openLink(project, url)
else
MarkdownLinkOpener.getInstance().openLink(project, url, virtualFile)
},
},
blockRenderer = blockRenderer,
)
}