├── multiplatform-markdown-renderer ├── .gitignore ├── stability_config.conf ├── src │ ├── jsMain │ │ └── kotlin │ │ │ └── com │ │ │ └── mikepenz │ │ │ └── markdown │ │ │ └── utils │ │ │ └── MarkdownLogger.js.kt │ ├── nativeMain │ │ └── kotlin │ │ │ └── com │ │ │ └── mikepenz │ │ │ └── markdown │ │ │ └── utils │ │ │ └── MarkdownLogger.native.kt │ ├── wasmJsMain │ │ └── kotlin │ │ │ └── com │ │ │ └── mikepenz │ │ │ └── markdown │ │ │ └── utils │ │ │ └── MarkdownLogger.wasmJs.kt │ ├── jvmMain │ │ └── kotlin │ │ │ └── com │ │ │ └── mikepenz │ │ │ └── markdown │ │ │ └── utils │ │ │ └── MarkdownLogger.jvm.kt │ ├── androidMain │ │ └── kotlin │ │ │ └── com │ │ │ └── mikepenz │ │ │ └── markdown │ │ │ └── utils │ │ │ └── MarkdownLogger.android.kt │ └── commonMain │ │ └── kotlin │ │ └── com │ │ └── mikepenz │ │ └── markdown │ │ ├── model │ │ ├── BulletHandler.kt │ │ ├── NoOpImageTransformerImpl.kt │ │ ├── MarkdownAnnotatorConfig.kt │ │ ├── ReferenceLinkHandler.kt │ │ ├── MarkdownColors.kt │ │ ├── MarkdownExtendedSpans.kt │ │ ├── MarkdownInlineContent.kt │ │ ├── MarkdownTypography.kt │ │ ├── MarkdownDimens.kt │ │ ├── MarkdownAnimations.kt │ │ ├── MarkdownImageState.kt │ │ ├── MarkdownAnnotator.kt │ │ ├── MarkdownPadding.kt │ │ └── ImageTransformer.kt │ │ ├── compose │ │ ├── extendedspans │ │ │ └── internal │ │ │ │ ├── TextLinkStyles.kt │ │ │ │ ├── Lists.kt │ │ │ │ └── ColorSerializers.kt │ │ ├── elements │ │ │ ├── MarkdownHeader.kt │ │ │ ├── MarkdownCheckBox.kt │ │ │ ├── MarkdownImage.kt │ │ │ ├── MarkdownParagraph.kt │ │ │ ├── MarkdownDivider.kt │ │ │ ├── MarkdownCodeTopBar.kt │ │ │ └── MarkdownBlockQuote.kt │ │ ├── LazyMarkdown.kt │ │ ├── ComposeLocal.kt │ │ └── MarkdownExtension.kt │ │ ├── utils │ │ ├── LogCompositions.kt │ │ ├── MarkdownLogger.kt │ │ └── EntityConverter.kt │ │ └── annotator │ │ └── AnnotatorSettings.kt ├── gradle.properties └── build.gradle.kts ├── multiplatform-markdown-renderer-code ├── .gitignore ├── gradle.properties ├── build.gradle.kts └── api │ ├── jvm │ └── multiplatform-markdown-renderer-code.api │ ├── android │ └── multiplatform-markdown-renderer-code.api │ └── multiplatform-markdown-renderer-code.klib.api ├── multiplatform-markdown-renderer-m2 ├── .gitignore ├── gradle.properties ├── build.gradle.kts └── src │ └── commonMain │ └── kotlin │ └── com │ └── mikepenz │ └── markdown │ └── m2 │ ├── MarkdownColors.kt │ ├── elements │ └── MarkdownCheckBox.kt │ └── MarkdownTypography.kt ├── multiplatform-markdown-renderer-m3 ├── .gitignore ├── gradle.properties ├── build.gradle.kts └── src │ └── commonMain │ └── kotlin │ └── com │ └── mikepenz │ └── markdown │ └── m3 │ ├── MarkdownColors.kt │ ├── elements │ └── MarkdownCheckBox.kt │ └── MarkdownTypography.kt ├── multiplatform-markdown-renderer-coil2 ├── .gitignore ├── api │ ├── multiplatform-markdown-renderer-coil2.klib.api │ ├── android │ │ └── multiplatform-markdown-renderer-coil2.api │ └── jvm │ │ └── multiplatform-markdown-renderer-coil2.api ├── gradle.properties ├── src │ ├── commonMain │ │ └── kotlin │ │ │ └── com │ │ │ └── mikepenz │ │ │ └── markdown │ │ │ └── coil2 │ │ │ ├── Coil2ImagePainterProvider.kt │ │ │ └── Coil2ImageTransformerImpl.kt │ ├── androidMain │ │ └── kotlin │ │ │ └── com │ │ │ └── mikepenz │ │ │ └── markdown │ │ │ └── coil2 │ │ │ └── ImagePainterProvider.kt │ └── jvmMain │ │ └── kotlin │ │ └── com │ │ └── mikepenz │ │ └── markdown │ │ └── coil2 │ │ └── ImagePainterProvider.kt └── build.gradle.kts ├── multiplatform-markdown-renderer-coil3 ├── .gitignore ├── gradle.properties ├── build.gradle.kts ├── api │ ├── jvm │ │ └── multiplatform-markdown-renderer-coil3.api │ ├── android │ │ └── multiplatform-markdown-renderer-coil3.api │ └── multiplatform-markdown-renderer-coil3.klib.api └── src │ └── commonMain │ └── kotlin │ └── com │ └── mikepenz │ └── markdown │ └── coil3 │ └── Coil3ImageTransformerImpl.kt ├── sample ├── gradle.properties ├── shared │ ├── gradle.properties │ ├── src │ │ ├── commonMain │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── mikepenz │ │ │ │ │ └── markdown │ │ │ │ │ └── sample │ │ │ │ │ ├── theme │ │ │ │ │ ├── Color.kt │ │ │ │ │ └── Theme.kt │ │ │ │ │ ├── LicensesPage.kt │ │ │ │ │ ├── App.kt │ │ │ │ │ ├── TopAppBar.kt │ │ │ │ │ ├── icon │ │ │ │ │ ├── Github.kt │ │ │ │ │ └── OpenSourceInitiative.kt │ │ │ │ │ ├── MarkDownPage.kt │ │ │ │ │ └── RecompositionPage.kt │ │ │ └── composeResources │ │ │ │ └── files │ │ │ │ └── sample.md │ │ └── iosMain │ │ │ └── kotlin │ │ │ └── com │ │ │ └── mikepenz │ │ │ └── markdown │ │ │ └── MainViewController.kt │ └── build.gradle.kts ├── android │ ├── gradle.properties │ ├── src │ │ ├── main │ │ │ ├── res │ │ │ │ ├── values │ │ │ │ │ ├── strings.xml │ │ │ │ │ └── ic_launcher_background.xml │ │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ │ └── ic_launcher.xml │ │ │ │ └── drawable │ │ │ │ │ └── ic_launcher_foreground.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── mikepenz │ │ │ │ │ └── markdown │ │ │ │ │ └── MainActivity.kt │ │ │ └── AndroidManifest.xml │ │ ├── debug │ │ │ └── screenshotTest │ │ │ │ └── reference │ │ │ │ └── com │ │ │ │ └── mikepenz │ │ │ │ └── markdown │ │ │ │ └── ui │ │ │ │ ├── m2 │ │ │ │ ├── LinkTests │ │ │ │ │ ├── LinkTest_dark_3df45f99_0.png │ │ │ │ │ └── LinkTest_light_fa4b1f98_0.png │ │ │ │ ├── SnapshotTests │ │ │ │ │ ├── ListTest_9c1ad4f7_0.png │ │ │ │ │ ├── ListTest_f33ffe9d_0.png │ │ │ │ │ ├── TableTest_3fc03e55_0.png │ │ │ │ │ ├── TableTest_5d136f40_0.png │ │ │ │ │ ├── DefaultTest_40de0fad_0.png │ │ │ │ │ ├── DefaultTest_6d087b28_0.png │ │ │ │ │ ├── RandomTest_61fad334_0.png │ │ │ │ │ ├── RandomTest_7c98d295_0.png │ │ │ │ │ ├── ListCodeBlockTest_3c07bfff_0.png │ │ │ │ │ └── ListCodeBlockTest_db9b1159_0.png │ │ │ │ ├── BlockquotesTests │ │ │ │ │ ├── BlockquoteTest_dark_3df45f99_0.png │ │ │ │ │ ├── BlockquoteTest_light_fa4b1f98_0.png │ │ │ │ │ ├── NestedBlockquoteTest_dark_3df45f99_0.png │ │ │ │ │ ├── NestedBlockquoteTest_light_fa4b1f98_0.png │ │ │ │ │ ├── BlockquoteWithMultipleLinesTest_dark_3df45f99_0.png │ │ │ │ │ ├── BlockquoteWithOtherElementsTest_dark_3df45f99_0.png │ │ │ │ │ ├── BlockquoteWithMultipleLinesTest_light_fa4b1f98_0.png │ │ │ │ │ ├── BlockquoteWithOtherElementsTest_light_fa4b1f98_0.png │ │ │ │ │ ├── BlockquoteWithMultipleParagraphsTest_dark_3df45f99_0.png │ │ │ │ │ └── BlockquoteWithMultipleParagraphsTest_light_fa4b1f98_0.png │ │ │ │ └── ElementsInListsTests │ │ │ │ │ ├── ImageInListTest_dark_3df45f99_0.png │ │ │ │ │ ├── ImageInListTest_light_fa4b1f98_0.png │ │ │ │ │ ├── CheckBoxInListTest_dark_3df45f99_0.png │ │ │ │ │ ├── CheckBoxInListTest_light_fa4b1f98_0.png │ │ │ │ │ ├── CodeBlockInListTest_dark_3df45f99_0.png │ │ │ │ │ ├── ParagraphInListTest_dark_3df45f99_0.png │ │ │ │ │ ├── BlockQuoteInListTest_dark_3df45f99_0.png │ │ │ │ │ ├── BlockQuoteInListTest_light_fa4b1f98_0.png │ │ │ │ │ ├── CodeBlockInListTest_light_fa4b1f98_0.png │ │ │ │ │ ├── ParagraphInListTest_light_fa4b1f98_0.png │ │ │ │ │ ├── UnorderedListInListTest_dark_3df45f99_0.png │ │ │ │ │ ├── UnorderedListInListTest_light_fa4b1f98_0.png │ │ │ │ │ ├── OrderedListRandomStartTest_dark_3df45f99_0.png │ │ │ │ │ └── OrderedListRandomStartTest_light_fa4b1f98_0.png │ │ │ │ └── m3 │ │ │ │ ├── LinkTests │ │ │ │ ├── LinkTest_dark_3df45f99_0.png │ │ │ │ └── LinkTest_light_fa4b1f98_0.png │ │ │ │ ├── SnapshotTests │ │ │ │ ├── ListTest_9c1ad4f7_0.png │ │ │ │ ├── ListTest_f33ffe9d_0.png │ │ │ │ ├── TableTest_3fc03e55_0.png │ │ │ │ ├── TableTest_5d136f40_0.png │ │ │ │ ├── DefaultTest_40de0fad_0.png │ │ │ │ ├── DefaultTest_6d087b28_0.png │ │ │ │ ├── RandomTest_61fad334_0.png │ │ │ │ ├── RandomTest_7c98d295_0.png │ │ │ │ ├── ListCodeBlockTest_3c07bfff_0.png │ │ │ │ └── ListCodeBlockTest_db9b1159_0.png │ │ │ │ ├── BlockquotesTests │ │ │ │ ├── BlockquoteTest_dark_3df45f99_0.png │ │ │ │ ├── BlockquoteTest_light_fa4b1f98_0.png │ │ │ │ ├── NestedBlockquoteTest_dark_3df45f99_0.png │ │ │ │ ├── NestedBlockquoteTest_light_fa4b1f98_0.png │ │ │ │ ├── BlockquoteWithMultipleLinesTest_dark_3df45f99_0.png │ │ │ │ ├── BlockquoteWithOtherElementsTest_dark_3df45f99_0.png │ │ │ │ ├── BlockquoteWithMultipleLinesTest_light_fa4b1f98_0.png │ │ │ │ ├── BlockquoteWithOtherElementsTest_light_fa4b1f98_0.png │ │ │ │ ├── BlockquoteWithMultipleParagraphsTest_dark_3df45f99_0.png │ │ │ │ └── BlockquoteWithMultipleParagraphsTest_light_fa4b1f98_0.png │ │ │ │ └── ElementsInListsTests │ │ │ │ ├── ImageInListTest_dark_3df45f99_0.png │ │ │ │ ├── ImageInListTest_light_fa4b1f98_0.png │ │ │ │ ├── CheckBoxInListTest_dark_3df45f99_0.png │ │ │ │ ├── CheckBoxInListTest_light_fa4b1f98_0.png │ │ │ │ ├── CodeBlockInListTest_dark_3df45f99_0.png │ │ │ │ ├── ParagraphInListTest_dark_3df45f99_0.png │ │ │ │ ├── BlockQuoteInListTest_dark_3df45f99_0.png │ │ │ │ ├── BlockQuoteInListTest_light_fa4b1f98_0.png │ │ │ │ ├── CodeBlockInListTest_light_fa4b1f98_0.png │ │ │ │ ├── ParagraphInListTest_light_fa4b1f98_0.png │ │ │ │ ├── UnorderedListInListTest_dark_3df45f99_0.png │ │ │ │ ├── UnorderedListInListTest_light_fa4b1f98_0.png │ │ │ │ ├── OrderedListRandomStartTest_dark_3df45f99_0.png │ │ │ │ └── OrderedListRandomStartTest_light_fa4b1f98_0.png │ │ └── screenshotTest │ │ │ └── kotlin │ │ │ └── com │ │ │ └── mikepenz │ │ │ └── markdown │ │ │ └── ui │ │ │ ├── m3 │ │ │ ├── util │ │ │ │ └── Helper.kt │ │ │ ├── theme │ │ │ │ └── M3Theme.kt │ │ │ ├── LinkTests.kt │ │ │ ├── BlockquotesTests.kt │ │ │ └── ElementsInListsTests.kt │ │ │ ├── m2 │ │ │ ├── util │ │ │ │ └── Helper.kt │ │ │ ├── theme │ │ │ │ └── M2Theme.kt │ │ │ ├── LinkTests.kt │ │ │ ├── BlockquotesTests.kt │ │ │ └── ElementsInListsTests.kt │ │ │ └── annotation │ │ │ └── Preview.kt │ └── build.gradle.kts ├── ios │ ├── iosApp │ │ ├── Assets.xcassets │ │ │ ├── Contents.json │ │ │ ├── AppIcon.appiconset │ │ │ │ ├── app-icon-1024.png │ │ │ │ └── Contents.json │ │ │ └── AccentColor.colorset │ │ │ │ └── Contents.json │ │ ├── Preview Content │ │ │ └── Preview Assets.xcassets │ │ │ │ └── Contents.json │ │ ├── iOSApp.swift │ │ ├── ContentView.swift │ │ └── Info.plist │ └── Configuration │ │ └── Config.xcconfig ├── desktop │ ├── gradle.properties │ ├── src │ │ └── jvmMain │ │ │ └── kotlin │ │ │ └── com │ │ │ └── mikepenz │ │ │ └── markdown │ │ │ └── main.kt │ └── build.gradle.kts ├── web │ ├── gradle.properties │ ├── src │ │ └── commonMain │ │ │ ├── resources │ │ │ ├── load.mjs │ │ │ ├── unsupported_browser.js │ │ │ ├── styles.css │ │ │ └── index.html │ │ │ └── kotlin │ │ │ └── com │ │ │ └── mikepenz │ │ │ └── markdown │ │ │ └── Main.kt │ └── build.gradle.kts └── README.md ├── .github ├── FUNDING.yml ├── dependabot.yml ├── ci-gradle.properties ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature_request.md │ └── bug_report.md ├── workflows │ ├── junie.yml │ ├── gradle-dependency-submission.yml │ └── static.yml ├── config │ └── configuration.json ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── CONTRIBUTORS.md ├── gradle ├── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties └── libs.versions.toml ├── CHANGELOG.md ├── Gemfile ├── .devcontainer └── devcontainer.json ├── SECURITY.md ├── .gitignore ├── settings.gradle.kts ├── Dangerfile ├── Gemfile.lock ├── gradle.properties └── gradlew.bat /multiplatform-markdown-renderer/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /multiplatform-markdown-renderer-code/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /multiplatform-markdown-renderer-m2/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /multiplatform-markdown-renderer-m3/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /multiplatform-markdown-renderer-coil2/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /multiplatform-markdown-renderer-coil3/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /sample/gradle.properties: -------------------------------------------------------------------------------- 1 | com.mikepenz.binary-compatibility-validator.enabled=false -------------------------------------------------------------------------------- /multiplatform-markdown-renderer-coil2/api/multiplatform-markdown-renderer-coil2.klib.api: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sample/shared/gradle.properties: -------------------------------------------------------------------------------- 1 | com.mikepenz.binary-compatibility-validator.enabled=false -------------------------------------------------------------------------------- /sample/android/gradle.properties: -------------------------------------------------------------------------------- 1 | com.mikepenz.binary-compatibility-validator.enabled=false -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [mikepenz] 4 | -------------------------------------------------------------------------------- /sample/ios/iosApp/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | Multiplatform Markdown Renderer contributors (sorted alphabetically) 2 | ============================================ 3 | -------------------------------------------------------------------------------- /sample/desktop/gradle.properties: -------------------------------------------------------------------------------- 1 | com.mikepenz.binary-compatibility-validator.enabled=false 2 | com.mikepenz.hotreload.enabled=true 3 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | You can find a detailed change log in the [release section](https://github.com/mikepenz/multiplatform-markdown-renderer/releases) 2 | -------------------------------------------------------------------------------- /sample/ios/Configuration/Config.xcconfig: -------------------------------------------------------------------------------- 1 | TEAM_ID= 2 | BUNDLE_ID=com.mikepenz.markdown.sample 3 | APP_NAME=Multiplatform Markdown Renderer Sample 4 | -------------------------------------------------------------------------------- /sample/ios/iosApp/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" -------------------------------------------------------------------------------- /sample/android/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Multiplatform Markdown Renderer Sample 3 | 4 | -------------------------------------------------------------------------------- /sample/web/gradle.properties: -------------------------------------------------------------------------------- 1 | com.mikepenz.multiplatform.enabled=true 2 | com.mikepenz.targets.enabled=false 3 | com.mikepenz.binary-compatibility-validator.enabled=false -------------------------------------------------------------------------------- /sample/web/src/commonMain/resources/load.mjs: -------------------------------------------------------------------------------- 1 | import { instantiate } from './markdown.uninstantiated.mjs'; 2 | 3 | await wasmSetup; 4 | 5 | instantiate({ skia: Module['asm'] }); 6 | -------------------------------------------------------------------------------- /sample/android/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #0058E6 4 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } 6 | 7 | gem 'danger' 8 | gem 'danger-android_lint' -------------------------------------------------------------------------------- /multiplatform-markdown-renderer/stability_config.conf: -------------------------------------------------------------------------------- 1 | // Consider the `ASTNode` stable 2 | org.intellij.markdown.ast.ASTNode 3 | // Consider the `IElementType` stable 4 | org.intellij.markdown.IElementType -------------------------------------------------------------------------------- /multiplatform-markdown-renderer/src/jsMain/kotlin/com/mikepenz/markdown/utils/MarkdownLogger.js.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.utils 2 | 3 | internal actual fun platformLog(tag: String, message: String) { 4 | } -------------------------------------------------------------------------------- /sample/ios/iosApp/iOSApp.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | @main 4 | struct iOSApp: App { 5 | var body: some Scene { 6 | WindowGroup { 7 | ContentView() 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /sample/ios/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/ios/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png -------------------------------------------------------------------------------- /multiplatform-markdown-renderer/src/nativeMain/kotlin/com/mikepenz/markdown/utils/MarkdownLogger.native.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.utils 2 | 3 | internal actual fun platformLog(tag: String, message: String) { 4 | } -------------------------------------------------------------------------------- /multiplatform-markdown-renderer/src/wasmJsMain/kotlin/com/mikepenz/markdown/utils/MarkdownLogger.wasmJs.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.utils 2 | 3 | internal actual fun platformLog(tag: String, message: String) { 4 | } -------------------------------------------------------------------------------- /sample/ios/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } -------------------------------------------------------------------------------- /multiplatform-markdown-renderer/src/jvmMain/kotlin/com/mikepenz/markdown/utils/MarkdownLogger.jvm.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.utils 2 | 3 | internal actual fun platformLog(tag: String, message: String) { 4 | println("$tag :: $message") 5 | } -------------------------------------------------------------------------------- /.github/ci-gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.daemon=true 2 | org.gradle.parallel=true 3 | org.gradle.workers.max=2 4 | org.gradle.jvmargs=-Xmx6G 5 | org.gradle.caching=true 6 | org.gradle.configureondemand=true 7 | # parallel kapt 8 | kapt.use.worker.api=true 9 | -------------------------------------------------------------------------------- /multiplatform-markdown-renderer/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=Multiplatform Markdown Renderer 2 | POM_DESCRIPTION=Kotlin Multiplatform Markdown Renderer. (Android, Desktop, ...) powered by Compose Multiplatform 3 | POM_ARTIFACT_ID=multiplatform-markdown-renderer -------------------------------------------------------------------------------- /multiplatform-markdown-renderer-code/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=Multiplatform Markdown Renderer - Code 2 | POM_DESCRIPTION=Kotlin Multiplatform Markdown Renderer. (Android, Desktop, ...) powered by Compose Multiplatform 3 | POM_ARTIFACT_ID=multiplatform-markdown-renderer-code -------------------------------------------------------------------------------- /multiplatform-markdown-renderer-m2/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=Multiplatform Markdown Renderer - Material 2 2 | POM_DESCRIPTION=Kotlin Multiplatform Markdown Renderer. (Android, Desktop, ...) powered by Compose Multiplatform 3 | POM_ARTIFACT_ID=multiplatform-markdown-renderer-m2 -------------------------------------------------------------------------------- /multiplatform-markdown-renderer-m3/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=Multiplatform Markdown Renderer - Material 3 2 | POM_DESCRIPTION=Kotlin Multiplatform Markdown Renderer. (Android, Desktop, ...) powered by Compose Multiplatform 3 | POM_ARTIFACT_ID=multiplatform-markdown-renderer-m3 -------------------------------------------------------------------------------- /multiplatform-markdown-renderer-coil3/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=Multiplatform Markdown Renderer - Coil 3 2 | POM_DESCRIPTION=Kotlin Multiplatform Markdown Renderer. (Android, Desktop, ...) powered by Compose Multiplatform 3 | POM_ARTIFACT_ID=multiplatform-markdown-renderer-coil3 -------------------------------------------------------------------------------- /multiplatform-markdown-renderer/src/androidMain/kotlin/com/mikepenz/markdown/utils/MarkdownLogger.android.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.utils 2 | 3 | import android.util.Log 4 | 5 | internal actual fun platformLog(tag: String, message: String) { 6 | Log.d(tag, message) 7 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Documentation 4 | url: https://github.com/mikepenz/multiplatform-markdown-renderer/blob/develop/README.md 5 | about: Check out the project documentation for answers to common questions. 6 | -------------------------------------------------------------------------------- /sample/shared/src/commonMain/kotlin/com/mikepenz/markdown/sample/theme/Color.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.sample.theme 2 | 3 | import androidx.compose.ui.graphics.Color 4 | 5 | val Primary = Color(0xff0058E6) 6 | 7 | val Red200 = Color(0xfff297a2) 8 | val Red800 = Color(0xffd00036) 9 | -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/LinkTests/LinkTest_dark_3df45f99_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/LinkTests/LinkTest_dark_3df45f99_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/SnapshotTests/ListTest_9c1ad4f7_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/SnapshotTests/ListTest_9c1ad4f7_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/SnapshotTests/ListTest_f33ffe9d_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/SnapshotTests/ListTest_f33ffe9d_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/SnapshotTests/TableTest_3fc03e55_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/SnapshotTests/TableTest_3fc03e55_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/SnapshotTests/TableTest_5d136f40_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/SnapshotTests/TableTest_5d136f40_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/LinkTests/LinkTest_dark_3df45f99_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/LinkTests/LinkTest_dark_3df45f99_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/SnapshotTests/ListTest_9c1ad4f7_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/SnapshotTests/ListTest_9c1ad4f7_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/SnapshotTests/ListTest_f33ffe9d_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/SnapshotTests/ListTest_f33ffe9d_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/SnapshotTests/TableTest_3fc03e55_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/SnapshotTests/TableTest_3fc03e55_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/SnapshotTests/TableTest_5d136f40_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/SnapshotTests/TableTest_5d136f40_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/LinkTests/LinkTest_light_fa4b1f98_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/LinkTests/LinkTest_light_fa4b1f98_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/SnapshotTests/DefaultTest_40de0fad_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/SnapshotTests/DefaultTest_40de0fad_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/SnapshotTests/DefaultTest_6d087b28_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/SnapshotTests/DefaultTest_6d087b28_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/SnapshotTests/RandomTest_61fad334_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/SnapshotTests/RandomTest_61fad334_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/SnapshotTests/RandomTest_7c98d295_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/SnapshotTests/RandomTest_7c98d295_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/LinkTests/LinkTest_light_fa4b1f98_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/LinkTests/LinkTest_light_fa4b1f98_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/SnapshotTests/DefaultTest_40de0fad_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/SnapshotTests/DefaultTest_40de0fad_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/SnapshotTests/DefaultTest_6d087b28_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/SnapshotTests/DefaultTest_6d087b28_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/SnapshotTests/RandomTest_61fad334_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/SnapshotTests/RandomTest_61fad334_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/SnapshotTests/RandomTest_7c98d295_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/SnapshotTests/RandomTest_7c98d295_0.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.0-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/SnapshotTests/ListCodeBlockTest_3c07bfff_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/SnapshotTests/ListCodeBlockTest_3c07bfff_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/SnapshotTests/ListCodeBlockTest_db9b1159_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/SnapshotTests/ListCodeBlockTest_db9b1159_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/SnapshotTests/ListCodeBlockTest_3c07bfff_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/SnapshotTests/ListCodeBlockTest_3c07bfff_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/SnapshotTests/ListCodeBlockTest_db9b1159_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/SnapshotTests/ListCodeBlockTest_db9b1159_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/BlockquotesTests/BlockquoteTest_dark_3df45f99_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/BlockquotesTests/BlockquoteTest_dark_3df45f99_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/BlockquotesTests/BlockquoteTest_light_fa4b1f98_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/BlockquotesTests/BlockquoteTest_light_fa4b1f98_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/BlockquotesTests/BlockquoteTest_dark_3df45f99_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/BlockquotesTests/BlockquoteTest_dark_3df45f99_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/BlockquotesTests/BlockquoteTest_light_fa4b1f98_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/BlockquotesTests/BlockquoteTest_light_fa4b1f98_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/BlockquotesTests/NestedBlockquoteTest_dark_3df45f99_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/BlockquotesTests/NestedBlockquoteTest_dark_3df45f99_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/BlockquotesTests/NestedBlockquoteTest_light_fa4b1f98_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/BlockquotesTests/NestedBlockquoteTest_light_fa4b1f98_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/ElementsInListsTests/ImageInListTest_dark_3df45f99_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/ElementsInListsTests/ImageInListTest_dark_3df45f99_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/ElementsInListsTests/ImageInListTest_light_fa4b1f98_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/ElementsInListsTests/ImageInListTest_light_fa4b1f98_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/BlockquotesTests/NestedBlockquoteTest_dark_3df45f99_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/BlockquotesTests/NestedBlockquoteTest_dark_3df45f99_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/BlockquotesTests/NestedBlockquoteTest_light_fa4b1f98_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/BlockquotesTests/NestedBlockquoteTest_light_fa4b1f98_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/ElementsInListsTests/ImageInListTest_dark_3df45f99_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/ElementsInListsTests/ImageInListTest_dark_3df45f99_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/ElementsInListsTests/ImageInListTest_light_fa4b1f98_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/ElementsInListsTests/ImageInListTest_light_fa4b1f98_0.png -------------------------------------------------------------------------------- /sample/android/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Java", 3 | "image": "mcr.microsoft.com/devcontainers/java:1-21", 4 | "features": { 5 | "ghcr.io/devcontainers/features/java:1": { 6 | "version": "none", 7 | "installMaven": "true", 8 | "mavenVersion": "3.8.6", 9 | "installGradle": "true" 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/ElementsInListsTests/CheckBoxInListTest_dark_3df45f99_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/ElementsInListsTests/CheckBoxInListTest_dark_3df45f99_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/ElementsInListsTests/CheckBoxInListTest_light_fa4b1f98_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/ElementsInListsTests/CheckBoxInListTest_light_fa4b1f98_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/ElementsInListsTests/CodeBlockInListTest_dark_3df45f99_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/ElementsInListsTests/CodeBlockInListTest_dark_3df45f99_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/ElementsInListsTests/ParagraphInListTest_dark_3df45f99_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/ElementsInListsTests/ParagraphInListTest_dark_3df45f99_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/ElementsInListsTests/CheckBoxInListTest_dark_3df45f99_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/ElementsInListsTests/CheckBoxInListTest_dark_3df45f99_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/ElementsInListsTests/CheckBoxInListTest_light_fa4b1f98_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/ElementsInListsTests/CheckBoxInListTest_light_fa4b1f98_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/ElementsInListsTests/CodeBlockInListTest_dark_3df45f99_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/ElementsInListsTests/CodeBlockInListTest_dark_3df45f99_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/ElementsInListsTests/ParagraphInListTest_dark_3df45f99_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/ElementsInListsTests/ParagraphInListTest_dark_3df45f99_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/ElementsInListsTests/BlockQuoteInListTest_dark_3df45f99_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/ElementsInListsTests/BlockQuoteInListTest_dark_3df45f99_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/ElementsInListsTests/BlockQuoteInListTest_light_fa4b1f98_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/ElementsInListsTests/BlockQuoteInListTest_light_fa4b1f98_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/ElementsInListsTests/CodeBlockInListTest_light_fa4b1f98_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/ElementsInListsTests/CodeBlockInListTest_light_fa4b1f98_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/ElementsInListsTests/ParagraphInListTest_light_fa4b1f98_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/ElementsInListsTests/ParagraphInListTest_light_fa4b1f98_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/ElementsInListsTests/BlockQuoteInListTest_dark_3df45f99_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/ElementsInListsTests/BlockQuoteInListTest_dark_3df45f99_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/ElementsInListsTests/BlockQuoteInListTest_light_fa4b1f98_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/ElementsInListsTests/BlockQuoteInListTest_light_fa4b1f98_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/ElementsInListsTests/CodeBlockInListTest_light_fa4b1f98_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/ElementsInListsTests/CodeBlockInListTest_light_fa4b1f98_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/ElementsInListsTests/ParagraphInListTest_light_fa4b1f98_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/ElementsInListsTests/ParagraphInListTest_light_fa4b1f98_0.png -------------------------------------------------------------------------------- /sample/ios/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "app-icon-1024.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | } 9 | ], 10 | "info" : { 11 | "author" : "xcode", 12 | "version" : 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/ElementsInListsTests/UnorderedListInListTest_dark_3df45f99_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/ElementsInListsTests/UnorderedListInListTest_dark_3df45f99_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/ElementsInListsTests/UnorderedListInListTest_light_fa4b1f98_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/ElementsInListsTests/UnorderedListInListTest_light_fa4b1f98_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/ElementsInListsTests/UnorderedListInListTest_dark_3df45f99_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/ElementsInListsTests/UnorderedListInListTest_dark_3df45f99_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/ElementsInListsTests/UnorderedListInListTest_light_fa4b1f98_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/ElementsInListsTests/UnorderedListInListTest_light_fa4b1f98_0.png -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Patches will be released to the latest major version. 6 | 7 | ## Reporting a Vulnerability 8 | 9 | Please report (suspected) security vulnerabilities to opensource-sec@mikepenz.dev. If the issue is 10 | confirmed, we will release a patch as soon as possible depending on complexity. 11 | -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/BlockquotesTests/BlockquoteWithMultipleLinesTest_dark_3df45f99_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/BlockquotesTests/BlockquoteWithMultipleLinesTest_dark_3df45f99_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/BlockquotesTests/BlockquoteWithOtherElementsTest_dark_3df45f99_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/BlockquotesTests/BlockquoteWithOtherElementsTest_dark_3df45f99_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/ElementsInListsTests/OrderedListRandomStartTest_dark_3df45f99_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/ElementsInListsTests/OrderedListRandomStartTest_dark_3df45f99_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/ElementsInListsTests/OrderedListRandomStartTest_light_fa4b1f98_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/ElementsInListsTests/OrderedListRandomStartTest_light_fa4b1f98_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/BlockquotesTests/BlockquoteWithMultipleLinesTest_dark_3df45f99_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/BlockquotesTests/BlockquoteWithMultipleLinesTest_dark_3df45f99_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/BlockquotesTests/BlockquoteWithOtherElementsTest_dark_3df45f99_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/BlockquotesTests/BlockquoteWithOtherElementsTest_dark_3df45f99_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/ElementsInListsTests/OrderedListRandomStartTest_dark_3df45f99_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/ElementsInListsTests/OrderedListRandomStartTest_dark_3df45f99_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/ElementsInListsTests/OrderedListRandomStartTest_light_fa4b1f98_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/ElementsInListsTests/OrderedListRandomStartTest_light_fa4b1f98_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/BlockquotesTests/BlockquoteWithMultipleLinesTest_light_fa4b1f98_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/BlockquotesTests/BlockquoteWithMultipleLinesTest_light_fa4b1f98_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/BlockquotesTests/BlockquoteWithOtherElementsTest_light_fa4b1f98_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/BlockquotesTests/BlockquoteWithOtherElementsTest_light_fa4b1f98_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/BlockquotesTests/BlockquoteWithMultipleLinesTest_light_fa4b1f98_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/BlockquotesTests/BlockquoteWithMultipleLinesTest_light_fa4b1f98_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/BlockquotesTests/BlockquoteWithOtherElementsTest_light_fa4b1f98_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/BlockquotesTests/BlockquoteWithOtherElementsTest_light_fa4b1f98_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/BlockquotesTests/BlockquoteWithMultipleParagraphsTest_dark_3df45f99_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/BlockquotesTests/BlockquoteWithMultipleParagraphsTest_dark_3df45f99_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/BlockquotesTests/BlockquoteWithMultipleParagraphsTest_dark_3df45f99_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/BlockquotesTests/BlockquoteWithMultipleParagraphsTest_dark_3df45f99_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/BlockquotesTests/BlockquoteWithMultipleParagraphsTest_light_fa4b1f98_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m2/BlockquotesTests/BlockquoteWithMultipleParagraphsTest_light_fa4b1f98_0.png -------------------------------------------------------------------------------- /sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/BlockquotesTests/BlockquoteWithMultipleParagraphsTest_light_fa4b1f98_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepenz/multiplatform-markdown-renderer/HEAD/sample/android/src/debug/screenshotTest/reference/com/mikepenz/markdown/ui/m3/BlockquotesTests/BlockquoteWithMultipleParagraphsTest_light_fa4b1f98_0.png -------------------------------------------------------------------------------- /multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/model/BulletHandler.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.model 2 | 3 | import org.intellij.markdown.IElementType 4 | 5 | /** An interface of providing use case specific un/ordered list handling.*/ 6 | fun interface BulletHandler { 7 | fun transform(type: IElementType, bullet: CharSequence?, index: Int, listNumber: Int, depth: Int): String 8 | } 9 | -------------------------------------------------------------------------------- /multiplatform-markdown-renderer-coil2/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=Multiplatform Markdown Renderer - Coil 2 2 | POM_DESCRIPTION=Kotlin Multiplatform Markdown Renderer. (Android, Desktop, ...) powered by Compose Multiplatform 3 | POM_ARTIFACT_ID=multiplatform-markdown-renderer-coil2 4 | 5 | com.mikepenz.android.enabled=true 6 | com.mikepenz.jvm.enabled=true 7 | com.mikepenz.wasm.enabled=false 8 | com.mikepenz.js.enabled=false 9 | com.mikepenz.composeNative.enabled=false -------------------------------------------------------------------------------- /multiplatform-markdown-renderer-coil2/src/commonMain/kotlin/com/mikepenz/markdown/coil2/Coil2ImagePainterProvider.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.coil2 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.geometry.Size 5 | import androidx.compose.ui.graphics.painter.Painter 6 | 7 | @Composable 8 | internal expect fun imagePainter(url: String): Painter? 9 | 10 | @Composable 11 | internal expect fun painterIntrinsicSize(painter: Painter): Size -------------------------------------------------------------------------------- /sample/android/src/screenshotTest/kotlin/com/mikepenz/markdown/ui/m3/util/Helper.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.ui.m3.util 2 | 3 | import androidx.compose.foundation.isSystemInDarkTheme 4 | import androidx.compose.runtime.Composable 5 | import com.mikepenz.markdown.m3.Markdown 6 | import com.mikepenz.markdown.model.rememberMarkdownState 7 | import com.mikepenz.markdown.ui.m3.theme.SampleTheme 8 | 9 | @Composable 10 | fun TestMarkdown(content: String) = SampleTheme(isSystemInDarkTheme()) { 11 | Markdown(rememberMarkdownState(content)) 12 | } -------------------------------------------------------------------------------- /sample/android/src/screenshotTest/kotlin/com/mikepenz/markdown/ui/m2/util/Helper.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.ui.m2.util 2 | 3 | import androidx.compose.foundation.isSystemInDarkTheme 4 | import androidx.compose.runtime.Composable 5 | import com.mikepenz.markdown.m2.Markdown 6 | import com.mikepenz.markdown.model.rememberMarkdownState 7 | import com.mikepenz.markdown.ui.m2.theme.SampleTheme 8 | 9 | @Composable 10 | fun TestMarkdown(content: String) = SampleTheme(isSystemInDarkTheme()) { 11 | Markdown(rememberMarkdownState(content)) 12 | } 13 | -------------------------------------------------------------------------------- /sample/android/src/screenshotTest/kotlin/com/mikepenz/markdown/ui/annotation/Preview.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.ui.annotation 2 | 3 | import android.content.res.Configuration 4 | import android.graphics.Color 5 | import androidx.compose.ui.tooling.preview.Preview 6 | 7 | @Preview(name = "light", showBackground = true, backgroundColor = Color.WHITE.toLong(), heightDp = 300) 8 | @Preview(name = "dark", showBackground = true, backgroundColor = Color.BLACK.toLong(), heightDp = 300, uiMode = Configuration.UI_MODE_NIGHT_YES) 9 | annotation class DarkLightPreview -------------------------------------------------------------------------------- /multiplatform-markdown-renderer-m2/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.mikepenz.convention.android-library") 3 | id("com.mikepenz.convention.kotlin-multiplatform") 4 | id("com.mikepenz.convention.compose") 5 | id("com.mikepenz.convention.publishing") 6 | } 7 | 8 | android { 9 | namespace = "com.mikepenz.markdown.m2" 10 | } 11 | 12 | dependencies { 13 | commonMainApi(projects.multiplatformMarkdownRenderer) 14 | commonMainApi(libs.markdown) 15 | 16 | commonMainCompileOnly(compose.runtime) 17 | commonMainCompileOnly(compose.material) 18 | } -------------------------------------------------------------------------------- /multiplatform-markdown-renderer-m3/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.mikepenz.convention.android-library") 3 | id("com.mikepenz.convention.kotlin-multiplatform") 4 | id("com.mikepenz.convention.compose") 5 | id("com.mikepenz.convention.publishing") 6 | } 7 | 8 | android { 9 | namespace = "com.mikepenz.markdown.m3" 10 | } 11 | 12 | dependencies { 13 | commonMainApi(projects.multiplatformMarkdownRenderer) 14 | commonMainApi(libs.markdown) 15 | 16 | commonMainCompileOnly(compose.runtime) 17 | commonMainCompileOnly(compose.material3) 18 | } -------------------------------------------------------------------------------- /multiplatform-markdown-renderer-coil2/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.mikepenz.convention.android-library") 3 | id("com.mikepenz.convention.kotlin-multiplatform") 4 | id("com.mikepenz.convention.compose") 5 | id("com.mikepenz.convention.publishing") 6 | } 7 | 8 | android { 9 | namespace = "com.mikepenz.markdown.coil2" 10 | } 11 | 12 | dependencies { 13 | commonMainApi(projects.multiplatformMarkdownRenderer) 14 | commonMainCompileOnly(compose.runtime) 15 | commonMainCompileOnly(compose.ui) 16 | 17 | androidMainApi(libs.coil2.core) 18 | } 19 | -------------------------------------------------------------------------------- /.github/workflows/junie.yml: -------------------------------------------------------------------------------- 1 | name: Junie 2 | run-name: Junie run ${{ inputs.run_id }} 3 | 4 | permissions: 5 | contents: write 6 | 7 | on: 8 | workflow_dispatch: 9 | inputs: 10 | run_id: 11 | description: "id of workflow process" 12 | required: true 13 | workflow_params: 14 | description: "stringified params" 15 | required: true 16 | 17 | jobs: 18 | call-workflow-passing-data: 19 | uses: jetbrains-junie/junie-workflows/.github/workflows/ej-issue.yml@main 20 | with: 21 | workflow_params: ${{ inputs.workflow_params }} 22 | -------------------------------------------------------------------------------- /sample/ios/iosApp/ContentView.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import SwiftUI 3 | import ComposeApp 4 | 5 | struct ComposeView: UIViewControllerRepresentable { 6 | func makeUIViewController(context: Context) -> UIViewController { 7 | MainViewControllerKt.MainViewController() 8 | } 9 | 10 | func updateUIViewController(_ uiViewController: UIViewController, context: Context) {} 11 | } 12 | 13 | struct ContentView: View { 14 | var body: some View { 15 | ComposeView() 16 | .ignoresSafeArea(.keyboard) // Compose has own keyboard handler 17 | } 18 | } 19 | 20 | -------------------------------------------------------------------------------- /multiplatform-markdown-renderer-coil3/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.mikepenz.convention.android-library") 3 | id("com.mikepenz.convention.kotlin-multiplatform") 4 | id("com.mikepenz.convention.compose") 5 | id("com.mikepenz.convention.publishing") 6 | } 7 | 8 | android { 9 | namespace = "com.mikepenz.markdown.coil3" 10 | } 11 | 12 | dependencies { 13 | commonMainApi(projects.multiplatformMarkdownRenderer) 14 | 15 | commonMainApi(libs.coil.core) { 16 | exclude(group = "org.jetbrains.compose.runtime") 17 | } 18 | commonMainCompileOnly(compose.runtime) 19 | } 20 | -------------------------------------------------------------------------------- /multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/model/NoOpImageTransformerImpl.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.model 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.geometry.Size 5 | import androidx.compose.ui.graphics.painter.Painter 6 | 7 | class NoOpImageTransformerImpl : ImageTransformer { 8 | 9 | @Composable 10 | override fun transform(link: String): ImageData? { 11 | return null 12 | } 13 | 14 | @Composable 15 | override fun intrinsicSize(painter: Painter): Size { 16 | return painter.intrinsicSize 17 | } 18 | } -------------------------------------------------------------------------------- /sample/shared/src/iosMain/kotlin/com/mikepenz/markdown/MainViewController.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown 2 | 3 | import androidx.compose.runtime.getValue 4 | import androidx.compose.ui.window.ComposeUIViewController 5 | import com.mikepenz.aboutlibraries.ui.compose.produceLibraries 6 | import com.mikepenz.markdown.sample.App 7 | import com.mikepenz.markdown.sample.shared.resources.Res 8 | 9 | fun MainViewController() = ComposeUIViewController { 10 | val libraries by produceLibraries { 11 | Res.readBytes("files/aboutlibraries.json").decodeToString() 12 | } 13 | App(libraries = libraries) 14 | } 15 | -------------------------------------------------------------------------------- /.github/config/configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "categories": [ 3 | { 4 | "title": "## 🚀 Features", 5 | "labels": [ 6 | "feature" 7 | ] 8 | }, 9 | { 10 | "title": "## 🐛 Fixes", 11 | "labels": [ 12 | "fix" 13 | ] 14 | }, 15 | { 16 | "title": "## 🧪 Tests", 17 | "labels": [ 18 | "test" 19 | ] 20 | }, 21 | { 22 | "title": "## 💬 Other", 23 | "labels": [ 24 | "other", 25 | "dependencies" 26 | ] 27 | } 28 | ], 29 | "template": "#{{CHANGELOG}}\n## Contributors:\n- #{{CONTRIBUTORS}}" 30 | } -------------------------------------------------------------------------------- /multiplatform-markdown-renderer-code/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.mikepenz.convention.android-library") 3 | id("com.mikepenz.convention.kotlin-multiplatform") 4 | id("com.mikepenz.convention.compose") 5 | id("com.mikepenz.convention.publishing") 6 | } 7 | 8 | android { 9 | namespace = "com.mikepenz.markdown.code" 10 | } 11 | 12 | dependencies { 13 | commonMainApi(projects.multiplatformMarkdownRenderer) 14 | commonMainCompileOnly(compose.runtime) 15 | commonMainCompileOnly(compose.ui) 16 | commonMainCompileOnly(compose.foundation) 17 | 18 | commonMainApi(libs.highlights) 19 | } -------------------------------------------------------------------------------- /multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/compose/extendedspans/internal/TextLinkStyles.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.compose.extendedspans.internal 2 | 3 | import androidx.compose.ui.text.SpanStyle 4 | import androidx.compose.ui.text.TextLinkStyles 5 | 6 | /** 7 | * Updates the [TextLinkStyles] with the provided [block]. 8 | */ 9 | fun TextLinkStyles.update(block: SpanStyle.() -> SpanStyle): TextLinkStyles { 10 | return TextLinkStyles( 11 | style = style?.run(block), 12 | focusedStyle = focusedStyle?.run(block), 13 | hoveredStyle = focusedStyle?.run(block), 14 | pressedStyle = focusedStyle?.run(block), 15 | ) 16 | } -------------------------------------------------------------------------------- /sample/web/src/commonMain/kotlin/com/mikepenz/markdown/Main.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown 2 | 3 | import androidx.compose.runtime.getValue 4 | import androidx.compose.ui.ExperimentalComposeUiApi 5 | import androidx.compose.ui.window.ComposeViewport 6 | import com.mikepenz.aboutlibraries.ui.compose.produceLibraries 7 | import com.mikepenz.markdown.sample.App 8 | import com.mikepenz.markdown.sample.web.resources.Res 9 | 10 | @OptIn(ExperimentalComposeUiApi::class) 11 | fun main() { 12 | ComposeViewport { 13 | val libraries by produceLibraries { 14 | Res.readBytes("files/aboutlibraries.json").decodeToString() 15 | } 16 | App(libraries) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /sample/desktop/src/jvmMain/kotlin/com/mikepenz/markdown/main.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown 2 | 3 | import androidx.compose.runtime.getValue 4 | import androidx.compose.ui.window.Window 5 | import androidx.compose.ui.window.application 6 | import com.mikepenz.aboutlibraries.ui.compose.produceLibraries 7 | import com.mikepenz.markdown.sample.App 8 | import com.mikepenz.markdown.sample.desktop.resources.Res 9 | 10 | fun main() = application { 11 | Window(onCloseRequest = ::exitApplication, title = "Markdown Sample") { 12 | val libraries by produceLibraries { 13 | Res.readBytes("files/aboutlibraries.json").decodeToString() 14 | } 15 | App(libraries) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /multiplatform-markdown-renderer-coil3/api/jvm/multiplatform-markdown-renderer-coil3.api: -------------------------------------------------------------------------------- 1 | public final class com/mikepenz/markdown/coil3/Coil3ImageTransformerImpl : com/mikepenz/markdown/model/ImageTransformer { 2 | public static final field $stable I 3 | public static final field INSTANCE Lcom/mikepenz/markdown/coil3/Coil3ImageTransformerImpl; 4 | public fun intrinsicSize-bSu-EZI (Landroidx/compose/ui/graphics/painter/Painter;Landroidx/compose/runtime/Composer;I)J 5 | public fun placeholderConfig-PO73lzk (Landroidx/compose/ui/unit/Density;JJ)Lcom/mikepenz/markdown/model/PlaceholderConfig; 6 | public fun transform (Ljava/lang/String;Landroidx/compose/runtime/Composer;I)Lcom/mikepenz/markdown/model/ImageData; 7 | } 8 | 9 | -------------------------------------------------------------------------------- /multiplatform-markdown-renderer-coil2/api/android/multiplatform-markdown-renderer-coil2.api: -------------------------------------------------------------------------------- 1 | public final class com/mikepenz/markdown/coil2/Coil2ImageTransformerImpl : com/mikepenz/markdown/model/ImageTransformer { 2 | public static final field $stable I 3 | public static final field INSTANCE Lcom/mikepenz/markdown/coil2/Coil2ImageTransformerImpl; 4 | public fun intrinsicSize-bSu-EZI (Landroidx/compose/ui/graphics/painter/Painter;Landroidx/compose/runtime/Composer;I)J 5 | public fun placeholderConfig-PO73lzk (Landroidx/compose/ui/unit/Density;JJ)Lcom/mikepenz/markdown/model/PlaceholderConfig; 6 | public fun transform (Ljava/lang/String;Landroidx/compose/runtime/Composer;I)Lcom/mikepenz/markdown/model/ImageData; 7 | } 8 | 9 | -------------------------------------------------------------------------------- /multiplatform-markdown-renderer-coil3/api/android/multiplatform-markdown-renderer-coil3.api: -------------------------------------------------------------------------------- 1 | public final class com/mikepenz/markdown/coil3/Coil3ImageTransformerImpl : com/mikepenz/markdown/model/ImageTransformer { 2 | public static final field $stable I 3 | public static final field INSTANCE Lcom/mikepenz/markdown/coil3/Coil3ImageTransformerImpl; 4 | public fun intrinsicSize-bSu-EZI (Landroidx/compose/ui/graphics/painter/Painter;Landroidx/compose/runtime/Composer;I)J 5 | public fun placeholderConfig-PO73lzk (Landroidx/compose/ui/unit/Density;JJ)Lcom/mikepenz/markdown/model/PlaceholderConfig; 6 | public fun transform (Ljava/lang/String;Landroidx/compose/runtime/Composer;I)Lcom/mikepenz/markdown/model/ImageData; 7 | } 8 | 9 | -------------------------------------------------------------------------------- /sample/web/src/commonMain/resources/unsupported_browser.js: -------------------------------------------------------------------------------- 1 | const unhandledError = (event, error) => { 2 | if (error instanceof WebAssembly.CompileError) { 3 | document.getElementById("warning").style.display = "initial"; 4 | 5 | // Hide a Scary Webpack Overlay which is less informative in this case. 6 | const webpackOverlay = document.getElementById("webpack-dev-server-client-overlay"); 7 | if (webpackOverlay != null) { 8 | webpackOverlay.style.display = "none"; 9 | } 10 | } 11 | }; 12 | 13 | addEventListener("error", (event) => unhandledError(event, event.error)); 14 | addEventListener("unhandledrejection", (event) => unhandledError(event, event.reason)); 15 | -------------------------------------------------------------------------------- /sample/android/src/main/kotlin/com/mikepenz/markdown/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown 2 | 3 | import android.os.Bundle 4 | import androidx.activity.ComponentActivity 5 | import androidx.activity.compose.setContent 6 | import androidx.activity.enableEdgeToEdge 7 | import com.mikepenz.aboutlibraries.Libs 8 | import com.mikepenz.aboutlibraries.util.withContext 9 | import com.mikepenz.markdown.sample.App 10 | 11 | class MainActivity : ComponentActivity() { 12 | override fun onCreate(savedInstanceState: Bundle?) { 13 | enableEdgeToEdge() 14 | super.onCreate(savedInstanceState) 15 | 16 | setContent { 17 | App(libraries = Libs.Builder().withContext(this).build()) 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /multiplatform-markdown-renderer/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.mikepenz.convention.android-library") 3 | id("com.mikepenz.convention.kotlin-multiplatform") 4 | id("com.mikepenz.convention.compose") 5 | id("com.mikepenz.convention.publishing") 6 | } 7 | 8 | android { 9 | namespace = "com.mikepenz.markdown" 10 | } 11 | 12 | composeCompiler { 13 | stabilityConfigurationFiles.add(project.layout.projectDirectory.file("stability_config.conf")) 14 | } 15 | 16 | dependencies { 17 | commonMainApi(libs.markdown) 18 | commonMainApi(baseLibs.kotlinx.collections.immutable) 19 | 20 | commonMainCompileOnly(compose.runtime) 21 | commonMainCompileOnly(compose.ui) 22 | commonMainCompileOnly(compose.foundation) 23 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | 29 | # Android Studio Navigation editor temp files 30 | .navigation/ 31 | 32 | # Android Studio captures folder 33 | captures/ 34 | 35 | # Intellij 36 | *.iml 37 | .idea/workspace.xml 38 | .idea 39 | 40 | # Keystore files 41 | *.jks 42 | 43 | # Generated podspec 44 | *.podspec 45 | /.kotlin/ 46 | 47 | ## User settings 48 | xcuserdata/ 49 | -------------------------------------------------------------------------------- /multiplatform-markdown-renderer-coil2/src/commonMain/kotlin/com/mikepenz/markdown/coil2/Coil2ImageTransformerImpl.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.coil2 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.geometry.Size 5 | import androidx.compose.ui.graphics.painter.Painter 6 | import com.mikepenz.markdown.model.ImageData 7 | import com.mikepenz.markdown.model.ImageTransformer 8 | 9 | object Coil2ImageTransformerImpl : ImageTransformer { 10 | 11 | @Composable 12 | override fun transform(link: String): ImageData? { 13 | return imagePainter(link)?.let { ImageData(it) } 14 | } 15 | 16 | @Composable 17 | override fun intrinsicSize(painter: Painter): Size { 18 | return painterIntrinsicSize(painter) 19 | } 20 | } -------------------------------------------------------------------------------- /sample/shared/src/commonMain/kotlin/com/mikepenz/markdown/sample/LicensesPage.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.sample 2 | 3 | import androidx.compose.foundation.layout.PaddingValues 4 | import androidx.compose.foundation.layout.fillMaxSize 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.ui.Modifier 7 | import com.mikepenz.aboutlibraries.Libs 8 | import com.mikepenz.aboutlibraries.ui.compose.m3.LibrariesContainer 9 | 10 | @Composable 11 | internal fun LicensesPage( 12 | libraries: Libs?, 13 | contentPadding: PaddingValues, 14 | modifier: Modifier = Modifier, 15 | ) { 16 | LibrariesContainer( 17 | libraries = libraries, 18 | modifier = modifier.fillMaxSize(), 19 | contentPadding = contentPadding, 20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/compose/extendedspans/internal/Lists.kt: -------------------------------------------------------------------------------- 1 | // Copyright 2023, Saket Narayan 2 | // SPDX-License-Identifier: Apache-2.0 3 | // https://github.com/saket/extended-spans 4 | package com.mikepenz.markdown.compose.extendedspans.internal 5 | 6 | import kotlin.contracts.ExperimentalContracts 7 | import kotlin.contracts.contract 8 | 9 | @OptIn(ExperimentalContracts::class) 10 | internal inline fun fastMapRange( 11 | start: Int, 12 | end: Int, 13 | transform: (Int) -> R, 14 | ): List { 15 | contract { callsInPlace(transform) } 16 | val destination = ArrayList(/* initialCapacity = */ end - start + 1) 17 | for (i in start..end) { 18 | destination.add(transform(i)) 19 | } 20 | return destination 21 | } -------------------------------------------------------------------------------- /sample/android/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/compose/extendedspans/internal/ColorSerializers.kt: -------------------------------------------------------------------------------- 1 | // Copyright 2023, Saket Narayan 2 | // SPDX-License-Identifier: Apache-2.0 3 | // https://github.com/saket/extended-spans 4 | package com.mikepenz.markdown.compose.extendedspans.internal 5 | 6 | import androidx.compose.ui.graphics.Color 7 | import androidx.compose.ui.graphics.isUnspecified 8 | import androidx.compose.ui.graphics.toArgb 9 | 10 | internal fun Color?.serialize(): String { 11 | return if (this == null || isUnspecified) "null" else "${toArgb()}" 12 | } 13 | 14 | internal fun String.deserializeToColor(): Color? { 15 | return if (this == "null") null else Color(this.toInt()) 16 | } 17 | 18 | internal fun Color?.colorOrNull(): Color? { 19 | return if (this == null || isUnspecified) null else this 20 | } -------------------------------------------------------------------------------- /multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/utils/LogCompositions.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.utils 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.SideEffect 5 | import androidx.compose.runtime.getValue 6 | import androidx.compose.runtime.mutableStateOf 7 | import androidx.compose.runtime.remember 8 | import androidx.compose.runtime.setValue 9 | 10 | /** 11 | * Helper to use when debugging recompositions. 12 | */ 13 | @Composable 14 | @Suppress("NOTHING_TO_INLINE") 15 | inline fun LogCompositions( 16 | crossinline message: () -> String, 17 | ) { 18 | if (MarkdownLogger.enabled) { 19 | var ref by remember { mutableStateOf(0) } 20 | SideEffect { ref++ } 21 | MarkdownLogger.d("Compositions") { 22 | "${message()} $ref" 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /.github/workflows/gradle-dependency-submission.yml: -------------------------------------------------------------------------------- 1 | name: Gradle Dependency Submission 2 | on: 3 | push: 4 | branches: 5 | - develop 6 | 7 | jobs: 8 | gradle-dependency-detection: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: write 12 | steps: 13 | - name: 'Checkout Repository' 14 | uses: actions/checkout@v6 15 | 16 | - uses: actions/setup-java@v5 17 | with: 18 | distribution: 'zulu' 19 | java-version: | 20 | 11 21 | 15 22 | 17 23 | 24 | - name: Copy CI gradle.properties 25 | run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties 26 | 27 | - name: Setup Gradle & Submit dependency graphs 28 | uses: gradle/actions/setup-gradle@v5 29 | with: 30 | dependency-graph: generate-and-submit -------------------------------------------------------------------------------- /sample/android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 12 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /sample/web/src/commonMain/resources/styles.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | width: 100%; 3 | height: 100%; 4 | margin: 0; 5 | padding: 0; 6 | background-color: white; 7 | overflow: hidden; 8 | } 9 | 10 | #warning { 11 | position: absolute; 12 | top: 100px; 13 | left: 100px; 14 | max-width: 830px; 15 | z-index: 100; 16 | background-color: white; 17 | font-size: initial; 18 | display: none; 19 | } 20 | 21 | #warning li { 22 | padding-bottom: 15px; 23 | } 24 | 25 | #warning span.code { 26 | font-family: monospace; 27 | } 28 | 29 | ul { 30 | margin-top: 0; 31 | margin-bottom: 15px; 32 | } 33 | 34 | #footer { 35 | position: fixed; 36 | bottom: 0; 37 | width: 100%; 38 | z-index: 1000; 39 | background-color: white; 40 | font-size: initial; 41 | } 42 | 43 | #close { 44 | position: absolute; 45 | top: 0; 46 | right: 10px; 47 | cursor: pointer; 48 | } 49 | -------------------------------------------------------------------------------- /multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/compose/elements/MarkdownHeader.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.compose.elements 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.Modifier 5 | import androidx.compose.ui.semantics.heading 6 | import androidx.compose.ui.semantics.semantics 7 | import androidx.compose.ui.text.TextStyle 8 | import org.intellij.markdown.IElementType 9 | import org.intellij.markdown.MarkdownTokenTypes 10 | import org.intellij.markdown.ast.ASTNode 11 | 12 | @Composable 13 | fun MarkdownHeader( 14 | content: String, 15 | node: ASTNode, 16 | style: TextStyle, 17 | contentChildType: IElementType = MarkdownTokenTypes.ATX_CONTENT, 18 | ) = MarkdownText( 19 | modifier = Modifier.semantics { 20 | heading() 21 | }, 22 | content = content, 23 | node = node, 24 | style = style, 25 | contentChildType = contentChildType, 26 | ) 27 | -------------------------------------------------------------------------------- /multiplatform-markdown-renderer-m2/src/commonMain/kotlin/com/mikepenz/markdown/m2/MarkdownColors.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.m2 2 | 3 | import androidx.compose.material.MaterialTheme 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.ui.graphics.Color 6 | import com.mikepenz.markdown.model.DefaultMarkdownColors 7 | import com.mikepenz.markdown.model.MarkdownColors 8 | 9 | @Composable 10 | fun markdownColor( 11 | text: Color = MaterialTheme.colors.onBackground, 12 | codeBackground: Color = MaterialTheme.colors.onBackground.copy(alpha = 0.1f), 13 | inlineCodeBackground: Color = codeBackground, 14 | dividerColor: Color = MaterialTheme.colors.onSurface.copy(alpha = 0.12f), 15 | tableBackground: Color = MaterialTheme.colors.onBackground.copy(alpha = 0.02f), 16 | ): MarkdownColors = DefaultMarkdownColors( 17 | text = text, 18 | codeBackground = codeBackground, 19 | inlineCodeBackground = inlineCodeBackground, 20 | dividerColor = dividerColor, 21 | tableBackground = tableBackground, 22 | ) 23 | -------------------------------------------------------------------------------- /multiplatform-markdown-renderer-coil2/api/jvm/multiplatform-markdown-renderer-coil2.api: -------------------------------------------------------------------------------- 1 | public final class com/mikepenz/markdown/coil2/Coil2ImageTransformerImpl : com/mikepenz/markdown/model/ImageTransformer { 2 | public static final field $stable I 3 | public static final field INSTANCE Lcom/mikepenz/markdown/coil2/Coil2ImageTransformerImpl; 4 | public fun intrinsicSize-bSu-EZI (Landroidx/compose/ui/graphics/painter/Painter;Landroidx/compose/runtime/Composer;I)J 5 | public fun placeholderConfig-PO73lzk (Landroidx/compose/ui/unit/Density;JJ)Lcom/mikepenz/markdown/model/PlaceholderConfig; 6 | public fun transform (Ljava/lang/String;Landroidx/compose/runtime/Composer;I)Lcom/mikepenz/markdown/model/ImageData; 7 | } 8 | 9 | public final class com/mikepenz/markdown/coil2/ImagePainterProviderKt { 10 | public static final fun fetchImage (Ljava/lang/String;Landroidx/compose/runtime/Composer;I)Landroidx/compose/ui/graphics/ImageBitmap; 11 | public static final fun loadPicture (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; 12 | } 13 | 14 | -------------------------------------------------------------------------------- /multiplatform-markdown-renderer-m3/src/commonMain/kotlin/com/mikepenz/markdown/m3/MarkdownColors.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.m3 2 | 3 | import androidx.compose.material3.MaterialTheme 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.ui.graphics.Color 6 | import com.mikepenz.markdown.model.DefaultMarkdownColors 7 | import com.mikepenz.markdown.model.MarkdownColors 8 | 9 | @Composable 10 | fun markdownColor( 11 | text: Color = MaterialTheme.colorScheme.onBackground, 12 | codeBackground: Color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.1f), 13 | inlineCodeBackground: Color = codeBackground, 14 | dividerColor: Color = MaterialTheme.colorScheme.outlineVariant, 15 | tableBackground: Color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.02f), 16 | ): MarkdownColors = DefaultMarkdownColors( 17 | text = text, 18 | codeBackground = codeBackground, 19 | inlineCodeBackground = inlineCodeBackground, 20 | dividerColor = dividerColor, 21 | tableBackground = tableBackground, 22 | ) 23 | -------------------------------------------------------------------------------- /multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/model/MarkdownAnnotatorConfig.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.model 2 | 3 | import androidx.compose.runtime.Immutable 4 | 5 | @Immutable 6 | interface MarkdownAnnotatorConfig { 7 | /** Defines if a EOL should be treated as a new line */ 8 | val eolAsNewLine: Boolean 9 | } 10 | 11 | @Immutable 12 | class DefaultMarkdownAnnotatorConfig( 13 | override val eolAsNewLine: Boolean = false, 14 | ) : MarkdownAnnotatorConfig { 15 | override fun equals(other: Any?): Boolean { 16 | if (this === other) return true 17 | if (other == null || this::class != other::class) return false 18 | 19 | other as DefaultMarkdownAnnotatorConfig 20 | 21 | return eolAsNewLine == other.eolAsNewLine 22 | } 23 | 24 | override fun hashCode(): Int { 25 | return eolAsNewLine.hashCode() 26 | } 27 | } 28 | 29 | fun markdownAnnotatorConfig( 30 | eolAsNewLine: Boolean = false, 31 | ): MarkdownAnnotatorConfig = DefaultMarkdownAnnotatorConfig( 32 | eolAsNewLine = eolAsNewLine, 33 | ) -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## About this issue 2 | 3 | - Briefly describe the issue 4 | - How can the issue be reproduced / sample code 5 | 6 | ## Details 7 | - [ ] Used library version 8 | - [ ] Used platform 9 | - [ ] Used support library version 10 | - [ ] Used gradle build tools version 11 | - [ ] Used tooling / Android Studio version 12 | - [ ] Other used libraries, potential conflicting libraries 13 | 14 | ## Checklist 15 | 16 | - [ ] Searched for [similar issues](https://github.com/mikepenz/multiplatform-markdown-renderer/issues) 17 | - [ ] Checked out the [sample application](https://github.com/mikepenz/multiplatform-markdown-renderer/tree/develop/app) 18 | - [ ] Read the [README](https://github.com/mikepenz/multiplatform-markdown-renderer/blob/develop/README.md) 19 | - [ ] Checked out the [CHANGELOG](https://github.com/mikepenz/multiplatform-markdown-renderer/releases) 20 | - [ ] Read the [FAQ](https://github.com/mikepenz/multiplatform-markdown-renderer/blob/develop/FAQ.md) 21 | - [ ] Checked out the [MIGRATION GUIDE](https://github.com/mikepenz/multiplatform-markdown-renderer/blob/develop/MIGRATION.md) 22 | -------------------------------------------------------------------------------- /multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/model/ReferenceLinkHandler.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.model 2 | 3 | /** 4 | * Interface to describe the [ReferenceLinkHandler] 5 | */ 6 | interface ReferenceLinkHandler { 7 | /** Keep the provided link */ 8 | fun store(label: String, destination: String?) 9 | 10 | /** Returns the link for the provided label if it exists */ 11 | fun find(label: String): String 12 | } 13 | 14 | /** 15 | * Implementation for [ReferenceLinkHandler] to resolve referenced link within the Markdown. 16 | * 17 | * The label is stored lowercase to allow case insensitive lookups. (https://github.com/adam-p/markdown-here/wiki/markdown-cheatsheet#links) 18 | */ 19 | class ReferenceLinkHandlerImpl : ReferenceLinkHandler { 20 | private val stored = mutableMapOf() 21 | 22 | override fun store(label: String, destination: String?) { 23 | stored[label.lowercase()] = destination 24 | } 25 | 26 | override fun find(label: String): String { 27 | return stored[label.lowercase()] ?: "" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/model/MarkdownColors.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.model 2 | 3 | import androidx.compose.runtime.Immutable 4 | import androidx.compose.ui.graphics.Color 5 | import com.mikepenz.markdown.compose.Markdown 6 | 7 | @Immutable 8 | interface MarkdownColors { 9 | /** Represents the color used for the text of this [Markdown] component. */ 10 | val text: Color 11 | 12 | /** Represents the color used for the background of code. */ 13 | val codeBackground: Color 14 | 15 | /** Represents the color used for the inline background of code. */ 16 | val inlineCodeBackground: Color 17 | 18 | /** Represents the color used for the color of dividers. */ 19 | val dividerColor: Color 20 | 21 | /** Represents the color used for the background of tables. */ 22 | val tableBackground: Color 23 | } 24 | 25 | @Immutable 26 | data class DefaultMarkdownColors( 27 | override val text: Color, 28 | override val codeBackground: Color, 29 | override val inlineCodeBackground: Color, 30 | override val dividerColor: Color, 31 | override val tableBackground: Color, 32 | ) : MarkdownColors -------------------------------------------------------------------------------- /multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/compose/elements/MarkdownCheckBox.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.compose.elements 2 | 3 | import androidx.compose.foundation.layout.Row 4 | import androidx.compose.foundation.layout.padding 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.ui.Modifier 7 | import androidx.compose.ui.text.TextStyle 8 | import androidx.compose.ui.text.font.FontFamily 9 | import androidx.compose.ui.unit.dp 10 | import org.intellij.markdown.ast.ASTNode 11 | import org.intellij.markdown.ast.getTextInNode 12 | 13 | @Composable 14 | fun MarkdownCheckBox( 15 | content: String, 16 | node: ASTNode, 17 | style: TextStyle, 18 | checkedIndicator: @Composable (Boolean, Modifier) -> Unit = { checked, modifier -> 19 | MarkdownText( 20 | content = "[${if (checked) "x" else " "}] ", 21 | modifier = modifier, 22 | style = style.copy(fontFamily = FontFamily.Monospace) 23 | ) 24 | }, 25 | ) { 26 | val checked = node.getTextInNode(content).contains("[x]") 27 | Row { 28 | checkedIndicator(checked, Modifier.padding(end = 4.dp)) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /multiplatform-markdown-renderer-m3/src/commonMain/kotlin/com/mikepenz/markdown/m3/elements/MarkdownCheckBox.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.m3.elements 2 | 3 | import androidx.compose.material3.Checkbox 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.ui.semantics.Role.Companion.Checkbox 6 | import androidx.compose.ui.semantics.role 7 | import androidx.compose.ui.semantics.semantics 8 | import androidx.compose.ui.semantics.stateDescription 9 | import androidx.compose.ui.text.TextStyle 10 | import com.mikepenz.markdown.compose.elements.MarkdownCheckBox 11 | import org.intellij.markdown.ast.ASTNode 12 | 13 | @Composable 14 | fun MarkdownCheckBox( 15 | content: String, 16 | node: ASTNode, 17 | style: TextStyle, 18 | ) = MarkdownCheckBox( 19 | content = content, 20 | node = node, 21 | style = style, 22 | checkedIndicator = { checked, modifier -> 23 | Checkbox( 24 | checked = checked, 25 | onCheckedChange = null, 26 | modifier = modifier.semantics { 27 | role = Checkbox 28 | stateDescription = if (checked) "Checked" else "Unchecked" 29 | }, 30 | ) 31 | }, 32 | ) -------------------------------------------------------------------------------- /multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/model/MarkdownExtendedSpans.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.model 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.Immutable 5 | import com.mikepenz.markdown.compose.extendedspans.ExtendedSpans 6 | 7 | @Immutable 8 | interface MarkdownExtendedSpans { 9 | val extendedSpans: (@Composable () -> ExtendedSpans)? 10 | } 11 | 12 | @Immutable 13 | class DefaultMarkdownExtendedSpans( 14 | override val extendedSpans: (@Composable () -> ExtendedSpans)?, 15 | ) : MarkdownExtendedSpans { 16 | override fun equals(other: Any?): Boolean { 17 | if (this === other) return true 18 | if (other == null || this::class != other::class) return false 19 | 20 | other as DefaultMarkdownExtendedSpans 21 | 22 | return extendedSpans == other.extendedSpans 23 | } 24 | 25 | override fun hashCode(): Int { 26 | return extendedSpans?.hashCode() ?: 0 27 | } 28 | } 29 | 30 | @Composable 31 | fun markdownExtendedSpans( 32 | extendedSpans: (@Composable () -> ExtendedSpans)? = null, 33 | ): MarkdownExtendedSpans = DefaultMarkdownExtendedSpans(extendedSpans) 34 | -------------------------------------------------------------------------------- /multiplatform-markdown-renderer-m2/src/commonMain/kotlin/com/mikepenz/markdown/m2/elements/MarkdownCheckBox.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.m2.elements 2 | 3 | import androidx.compose.material.Checkbox 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.ui.semantics.Role.Companion.Checkbox 6 | import androidx.compose.ui.semantics.role 7 | import androidx.compose.ui.semantics.semantics 8 | import androidx.compose.ui.semantics.stateDescription 9 | import androidx.compose.ui.text.TextStyle 10 | import com.mikepenz.markdown.compose.elements.MarkdownCheckBox 11 | import org.intellij.markdown.ast.ASTNode 12 | 13 | @Composable 14 | fun MarkdownCheckBox( 15 | content: String, 16 | node: ASTNode, 17 | style: TextStyle, 18 | ) = MarkdownCheckBox( 19 | content = content, 20 | node = node, 21 | style = style, 22 | checkedIndicator = { checked, modifier -> 23 | Checkbox( 24 | checked = checked, 25 | onCheckedChange = null, 26 | modifier = modifier.semantics { 27 | role = Checkbox 28 | stateDescription = if (checked) "Checked" else "Unchecked" 29 | }, 30 | ) 31 | }, 32 | ) 33 | -------------------------------------------------------------------------------- /multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/compose/elements/MarkdownImage.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.compose.elements 2 | 3 | import androidx.compose.foundation.Image 4 | import androidx.compose.runtime.Composable 5 | import com.mikepenz.markdown.compose.LocalImageTransformer 6 | import com.mikepenz.markdown.utils.findChildOfTypeRecursive 7 | import com.mikepenz.markdown.utils.getUnescapedTextInNode 8 | import org.intellij.markdown.MarkdownElementTypes 9 | import org.intellij.markdown.ast.ASTNode 10 | 11 | @Composable 12 | fun MarkdownImage(content: String, node: ASTNode) { 13 | 14 | val link = node.findChildOfTypeRecursive(MarkdownElementTypes.LINK_DESTINATION)?.getUnescapedTextInNode(content) ?: return 15 | 16 | LocalImageTransformer.current.transform(link)?.let { imageData -> 17 | Image( 18 | painter = imageData.painter, 19 | contentDescription = imageData.contentDescription, 20 | modifier = imageData.modifier, 21 | alignment = imageData.alignment, 22 | contentScale = imageData.contentScale, 23 | alpha = imageData.alpha, 24 | colorFilter = imageData.colorFilter 25 | ) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /sample/README.md: -------------------------------------------------------------------------------- 1 | ## Sample app 2 | 3 | ### Generate Dependency Information 4 | 5 | ```bash 6 | ./gradlew :sample:shared:exportLibraryDefinitions 7 | ./gradlew :sample:desktop:exportLibraryDefinitions 8 | ./gradlew :sample:web:exportLibraryDefinitions 9 | 10 | ``` 11 | 12 | ### Generate reference images for tests 13 | 14 | ```bash 15 | ./gradlew :sample:android:updateDebugScreenshotTest 16 | ``` 17 | 18 | ### Generate ScreenshotTest report 19 | 20 | ```bash 21 | ./gradlew :sample:android:validateDebugScreenshotTest 22 | ``` 23 | 24 | ### Run Android app 25 | 26 | ```bash 27 | ./gradlew :sample:installDebug 28 | ``` 29 | 30 | ### Run Desktop app 31 | 32 | ```bash 33 | ./gradlew :sample:desktop:run 34 | ``` 35 | 36 | ### Run Wasm app 37 | 38 | ```bash 39 | ./gradlew :sample:web:wasmJsBrowserDevelopmentRun 40 | ``` 41 | 42 | ### Run iOS app 43 | 44 | - Set up an 45 | environment - https://www.jetbrains.com/help/kotlin-multiplatform-dev/multiplatform-setup.html 46 | - Install Kotlin Multiplatform Mobile 47 | plugin - https://plugins.jetbrains.com/plugin/14936-kotlin-multiplatform 48 | - Open the project in IntelliJ IDEA / Android Studio 49 | - Add a new run configuration for the iOS Application 50 | - Run the iOS Application 51 | -------------------------------------------------------------------------------- /multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/model/MarkdownInlineContent.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.model 2 | 3 | import androidx.compose.foundation.text.InlineTextContent 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.runtime.Immutable 6 | 7 | @Immutable 8 | interface MarkdownInlineContent { 9 | /** Represents the map used to store tags and corresponding inline content */ 10 | val inlineContent: Map 11 | } 12 | 13 | @Immutable 14 | class DefaultMarkdownInlineContent( 15 | override val inlineContent: Map, 16 | ) : MarkdownInlineContent { 17 | override fun equals(other: Any?): Boolean { 18 | if (this === other) return true 19 | if (other == null || this::class != other::class) return false 20 | 21 | other as DefaultMarkdownInlineContent 22 | 23 | return inlineContent == other.inlineContent 24 | } 25 | 26 | override fun hashCode(): Int { 27 | return inlineContent.hashCode() 28 | } 29 | } 30 | 31 | @Composable 32 | fun markdownInlineContent( 33 | content: Map = mapOf(), 34 | ): MarkdownInlineContent = DefaultMarkdownInlineContent(content) 35 | -------------------------------------------------------------------------------- /sample/shared/src/commonMain/kotlin/com/mikepenz/markdown/sample/theme/Theme.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.sample.theme 2 | 3 | import androidx.compose.foundation.isSystemInDarkTheme 4 | import androidx.compose.material3.MaterialTheme 5 | import androidx.compose.material3.darkColorScheme 6 | import androidx.compose.material3.lightColorScheme 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.ui.graphics.Color 9 | 10 | private val DarkColorScheme = darkColorScheme( 11 | primary = Primary, 12 | onPrimary = Color.Black, 13 | secondary = Primary, 14 | onSecondary = Color.Black, 15 | error = Red200 16 | ) 17 | 18 | private val LightColorScheme = lightColorScheme( 19 | primary = Primary, 20 | onPrimary = Color.White, 21 | secondary = Primary, 22 | onSecondary = Color.White, 23 | error = Red800 24 | ) 25 | 26 | @Composable 27 | fun SampleTheme( 28 | darkTheme: Boolean = isSystemInDarkTheme(), 29 | content: @Composable () -> Unit, 30 | ) { 31 | val colorScheme = if (darkTheme) { 32 | DarkColorScheme 33 | } else { 34 | LightColorScheme 35 | } 36 | 37 | MaterialTheme( 38 | colorScheme = colorScheme, 39 | content = content 40 | ) 41 | } 42 | -------------------------------------------------------------------------------- /multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/utils/MarkdownLogger.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.utils 2 | 3 | import androidx.compose.runtime.snapshots.Snapshot 4 | 5 | object MarkdownLogger { 6 | /** 7 | * Whether to print debug log statements to the relevant system logger. Do not build release 8 | * artifacts with this enabled. It's purely for debugging purposes. 9 | */ 10 | var enabled: Boolean = false 11 | 12 | fun d(tag: String, message: () -> String) { 13 | d(tag = tag, throwable = null, message = message) 14 | } 15 | 16 | fun d(tag: String, throwable: Throwable?, message: () -> String) { 17 | if (enabled) { 18 | Snapshot.withoutReadObservation { 19 | platformLog( 20 | tag = tag, 21 | message = buildString { 22 | append(message()) 23 | if (throwable != null) { 24 | append(". Throwable: ") 25 | append(throwable) 26 | } 27 | }, 28 | ) 29 | } 30 | } 31 | } 32 | } 33 | 34 | internal expect fun platformLog(tag: String, message: String) -------------------------------------------------------------------------------- /multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/compose/elements/MarkdownParagraph.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.compose.elements 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.Modifier 5 | import androidx.compose.ui.text.TextStyle 6 | import androidx.compose.ui.text.buildAnnotatedString 7 | import com.mikepenz.markdown.annotator.AnnotatorSettings 8 | import com.mikepenz.markdown.annotator.annotatorSettings 9 | import com.mikepenz.markdown.annotator.buildMarkdownAnnotatedString 10 | import com.mikepenz.markdown.compose.LocalMarkdownTypography 11 | import org.intellij.markdown.ast.ASTNode 12 | 13 | @Composable 14 | fun MarkdownParagraph( 15 | content: String, 16 | node: ASTNode, 17 | modifier: Modifier = Modifier, 18 | style: TextStyle = LocalMarkdownTypography.current.paragraph, 19 | annotatorSettings: AnnotatorSettings = annotatorSettings(), 20 | ) { 21 | val styledText = buildAnnotatedString { 22 | pushStyle(style.toSpanStyle()) 23 | buildMarkdownAnnotatedString(content = content, node = node, annotatorSettings = annotatorSettings) 24 | pop() 25 | } 26 | 27 | MarkdownText( 28 | styledText, 29 | modifier = modifier, 30 | style = style, 31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /sample/web/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl 2 | 3 | plugins { 4 | id("com.mikepenz.convention.kotlin-multiplatform") 5 | id("com.mikepenz.convention.compose") 6 | id("com.mikepenz.aboutlibraries.plugin") 7 | } 8 | 9 | kotlin { 10 | @OptIn(ExperimentalWasmDsl::class) 11 | wasmJs { 12 | outputModuleName = "markdown" 13 | browser { 14 | commonWebpackConfig { 15 | outputFileName = "composeApp.js" 16 | } 17 | } 18 | binaries.executable() 19 | } 20 | 21 | sourceSets { 22 | commonMain.dependencies { 23 | implementation(project(":sample:shared")) 24 | implementation(compose.foundation) 25 | implementation(compose.components.resources) 26 | } 27 | } 28 | } 29 | 30 | compose.resources { 31 | packageOfResClass = "com.mikepenz.markdown.sample.web.resources" 32 | } 33 | 34 | aboutLibraries { 35 | android { 36 | registerAndroidTasks = false 37 | } 38 | export { 39 | exportVariant = "wasmJs" 40 | outputPath = file("src/commonMain/composeResources/files/aboutlibraries.json") 41 | } 42 | library { 43 | duplicationMode = com.mikepenz.aboutlibraries.plugin.DuplicateMode.MERGE 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | androidx-activityCompose = "1.10.1" 3 | coil = "3.3.0" 4 | coil2 = "2.7.0" 5 | markdown = "0.7.3" 6 | ktor = "3.2.3" 7 | highlights = "1.0.0" 8 | 9 | [libraries] 10 | androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activityCompose" } 11 | coil-core = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil" } 12 | coil-svg = { module = "io.coil-kt.coil3:coil-svg", version.ref = "coil" } 13 | coil-network-okhttp = { module = "io.coil-kt.coil3:coil-network-okhttp", version.ref = "coil" } 14 | coil-network-ktor = { module = "io.coil-kt.coil3:coil-network-ktor3", version.ref = "coil" } 15 | coil2-core = { module = "io.coil-kt:coil-compose", version.ref = "coil2" } 16 | markdown = { module = "org.jetbrains:markdown", version.ref = "markdown" } 17 | ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" } 18 | ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" } 19 | ktor-client-java = { module = "io.ktor:ktor-client-java", version.ref = "ktor" } 20 | ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" } 21 | highlights = { module = "dev.snipme:highlights", version.ref = "highlights" } 22 | 23 | [bundles] 24 | coil = [ 25 | "coil-core", 26 | "coil-network-okhttp", 27 | ] 28 | -------------------------------------------------------------------------------- /sample/android/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.mikepenz.convention.android-application") 3 | id("com.mikepenz.convention.kotlin") 4 | id("com.mikepenz.convention.compose") 5 | id("com.mikepenz.aboutlibraries.plugin") 6 | id("com.mikepenz.aboutlibraries.plugin.android") 7 | alias(baseLibs.plugins.screenshot) 8 | } 9 | 10 | android { 11 | namespace = "com.mikepenz.markdown.sample" 12 | 13 | defaultConfig { 14 | applicationId = "com.mikepenz.markdown" 15 | base.archivesName = "markdown-renderer-sample-v$versionName-c$versionCode" 16 | } 17 | 18 | @Suppress("UnstableApiUsage") 19 | experimentalProperties["android.experimental.enableScreenshotTest"] = true 20 | } 21 | 22 | dependencies { 23 | implementation(project(":sample:shared")) 24 | implementation(compose.foundation) 25 | implementation(compose.material) 26 | implementation(compose.material3) 27 | implementation(libs.androidx.activity.compose) 28 | implementation(libs.ktor.client.okhttp) 29 | debugImplementation(compose.uiTooling) 30 | "screenshotTestImplementation"(compose.uiTooling) 31 | } 32 | 33 | aboutLibraries { 34 | library { 35 | duplicationMode = com.mikepenz.aboutlibraries.plugin.DuplicateMode.MERGE 36 | } 37 | export { 38 | exportVariant = "release" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | --- 8 | 9 | ## Feature Description 10 | 11 | A clear and concise description of the feature you're requesting. 12 | 13 | ## Problem Statement 14 | 15 | A clear and concise description of what the problem is. 16 | 17 | ## Proposed Solution 18 | 19 | A clear and concise description of what you want to happen. 20 | 21 | ## Alternatives Considered 22 | 23 | A clear and concise description of any alternative solutions or features you've considered. 24 | 25 | ## Example Implementation 26 | 27 | If applicable, provide a code example or mockup of how this feature would work. 28 | 29 | ## Use Case 30 | 31 | Describe how this feature would be used and who would benefit from it. 32 | 33 | ## Additional Context 34 | 35 | Add any other context or screenshots about the feature request here. 36 | 37 | ## Checklist 38 | 39 | - [ ] I have searched for [similar feature requests](https://github.com/mikepenz/multiplatform-markdown-renderer/issues) 40 | - [ ] I have read the [README](https://github.com/mikepenz/multiplatform-markdown-renderer/blob/develop/README.md) 41 | - [ ] I have checked the [CHANGELOG](https://github.com/mikepenz/multiplatform-markdown-renderer/releases) to see if this feature is already planned 42 | -------------------------------------------------------------------------------- /sample/android/src/screenshotTest/kotlin/com/mikepenz/markdown/ui/m3/theme/M3Theme.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.ui.m3.theme 2 | 3 | import androidx.compose.foundation.isSystemInDarkTheme 4 | import androidx.compose.material3.MaterialTheme 5 | import androidx.compose.material3.darkColorScheme 6 | import androidx.compose.material3.lightColorScheme 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.ui.graphics.Color 9 | import com.mikepenz.markdown.sample.theme.Primary 10 | import com.mikepenz.markdown.sample.theme.Red200 11 | import com.mikepenz.markdown.sample.theme.Red800 12 | 13 | private val DarkColorScheme = darkColorScheme( 14 | primary = Primary, 15 | onPrimary = Color.White, 16 | secondary = Primary, 17 | onSecondary = Color.White, 18 | error = Red200 19 | ) 20 | 21 | private val LightColorScheme = lightColorScheme( 22 | primary = Primary, 23 | onPrimary = Color.White, 24 | secondary = Primary, 25 | onSecondary = Color.White, 26 | error = Red800 27 | ) 28 | 29 | @Composable 30 | fun SampleTheme( 31 | darkTheme: Boolean = isSystemInDarkTheme(), 32 | content: @Composable () -> Unit, 33 | ) { 34 | val colorScheme = if (darkTheme) { 35 | DarkColorScheme 36 | } else { 37 | LightColorScheme 38 | } 39 | 40 | MaterialTheme( 41 | colorScheme = colorScheme, 42 | content = content 43 | ) 44 | } 45 | -------------------------------------------------------------------------------- /multiplatform-markdown-renderer-coil2/src/androidMain/kotlin/com/mikepenz/markdown/coil2/ImagePainterProvider.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.coil2 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.LaunchedEffect 5 | import androidx.compose.runtime.getValue 6 | import androidx.compose.runtime.mutableStateOf 7 | import androidx.compose.runtime.remember 8 | import androidx.compose.runtime.setValue 9 | import androidx.compose.ui.graphics.painter.Painter 10 | import androidx.compose.ui.platform.LocalContext 11 | import coil.compose.AsyncImagePainter 12 | import coil.compose.rememberAsyncImagePainter 13 | import coil.request.ImageRequest 14 | import coil.size.Size 15 | 16 | @Composable 17 | internal actual fun imagePainter(url: String): Painter? { 18 | return rememberAsyncImagePainter( 19 | model = ImageRequest.Builder(LocalContext.current) 20 | .data(url) 21 | .size(Size.ORIGINAL) 22 | .build() 23 | ) 24 | } 25 | 26 | @Composable 27 | internal actual fun painterIntrinsicSize(painter: Painter): androidx.compose.ui.geometry.Size { 28 | var size by remember(painter) { mutableStateOf(painter.intrinsicSize) } 29 | 30 | if (painter is AsyncImagePainter) { 31 | LaunchedEffect(painter.state) { 32 | painter.state.painter?.let { 33 | size = it.intrinsicSize 34 | } 35 | } 36 | } 37 | 38 | return size 39 | } 40 | -------------------------------------------------------------------------------- /multiplatform-markdown-renderer-coil3/src/commonMain/kotlin/com/mikepenz/markdown/coil3/Coil3ImageTransformerImpl.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.coil3 2 | 3 | import androidx.compose.runtime.* 4 | import androidx.compose.ui.geometry.Size 5 | import androidx.compose.ui.graphics.painter.Painter 6 | import coil3.compose.AsyncImagePainter 7 | import coil3.compose.LocalPlatformContext 8 | import coil3.compose.rememberAsyncImagePainter 9 | import coil3.request.ImageRequest 10 | import com.mikepenz.markdown.model.ImageData 11 | import com.mikepenz.markdown.model.ImageTransformer 12 | 13 | object Coil3ImageTransformerImpl : ImageTransformer { 14 | 15 | @Composable 16 | override fun transform(link: String): ImageData { 17 | return rememberAsyncImagePainter( 18 | model = ImageRequest.Builder(LocalPlatformContext.current) 19 | .data(link) 20 | .size(coil3.size.Size.ORIGINAL) 21 | .build() 22 | ).let { ImageData(it) } 23 | } 24 | 25 | @Composable 26 | override fun intrinsicSize(painter: Painter): Size { 27 | var size by remember(painter) { mutableStateOf(painter.intrinsicSize) } 28 | if (painter is AsyncImagePainter) { 29 | val painterState = painter.state.collectAsState() 30 | val intrinsicSize = painterState.value.painter?.intrinsicSize 31 | intrinsicSize?.also { size = it } 32 | } 33 | return size 34 | } 35 | } -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "multiplatform-markdown-renderer-root" 2 | 3 | enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") 4 | 5 | pluginManagement { 6 | repositories { 7 | maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") 8 | google() 9 | gradlePluginPortal() 10 | mavenCentral() 11 | mavenLocal() 12 | } 13 | } 14 | 15 | plugins { 16 | id("org.gradle.toolchains.foojay-resolver") version "1.0.0" 17 | } 18 | 19 | dependencyResolutionManagement { 20 | repositories { 21 | google() 22 | mavenCentral() 23 | maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") 24 | maven("https://maven.pkg.jetbrains.space/kotlin/p/wasm/experimental") 25 | maven("https://oss.sonatype.org/content/repositories/snapshots") 26 | mavenLocal() 27 | } 28 | 29 | versionCatalogs { 30 | create("baseLibs") { 31 | from("com.mikepenz:version-catalog:0.9.0") 32 | } 33 | } 34 | } 35 | 36 | include(":multiplatform-markdown-renderer") 37 | include(":multiplatform-markdown-renderer-m2") 38 | include(":multiplatform-markdown-renderer-m3") 39 | include(":multiplatform-markdown-renderer-coil2") 40 | include(":multiplatform-markdown-renderer-coil3") 41 | include(":multiplatform-markdown-renderer-code") 42 | 43 | include(":sample:shared") 44 | include(":sample:android") 45 | include(":sample:desktop") 46 | include(":sample:web") 47 | -------------------------------------------------------------------------------- /sample/android/src/screenshotTest/kotlin/com/mikepenz/markdown/ui/m2/theme/M2Theme.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.ui.m2.theme 2 | 3 | import androidx.compose.foundation.isSystemInDarkTheme 4 | import androidx.compose.material.MaterialTheme 5 | import androidx.compose.material.darkColors 6 | import androidx.compose.material.lightColors 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.ui.graphics.Color 9 | import com.mikepenz.markdown.sample.theme.Primary 10 | import com.mikepenz.markdown.sample.theme.Red200 11 | import com.mikepenz.markdown.sample.theme.Red800 12 | 13 | private val DarkColorPalette = darkColors( 14 | primary = Primary, 15 | primaryVariant = Primary, 16 | onPrimary = Color.White, 17 | secondary = Primary, 18 | onSecondary = Color.White, 19 | error = Red200 20 | ) 21 | 22 | private val LightColorPalette = lightColors( 23 | primary = Primary, 24 | primaryVariant = Primary, 25 | onPrimary = Color.White, 26 | secondary = Primary, 27 | secondaryVariant = Primary, 28 | onSecondary = Color.White, 29 | error = Red800 30 | ) 31 | 32 | @Composable 33 | fun SampleTheme( 34 | darkTheme: Boolean = isSystemInDarkTheme(), 35 | content: @Composable () -> Unit, 36 | ) { 37 | val colors = if (darkTheme) { 38 | DarkColorPalette 39 | } else { 40 | LightColorPalette 41 | } 42 | 43 | MaterialTheme( 44 | colors = colors, 45 | content = content 46 | ) 47 | } 48 | -------------------------------------------------------------------------------- /sample/desktop/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.compose.desktop.application.dsl.TargetFormat 2 | 3 | plugins { 4 | id("com.mikepenz.convention.kotlin-multiplatform") 5 | id("com.mikepenz.convention.compose") 6 | id("com.mikepenz.aboutlibraries.plugin") 7 | } 8 | 9 | kotlin { 10 | sourceSets { 11 | commonMain.dependencies { 12 | implementation(project(":sample:shared")) 13 | implementation(compose.components.resources) 14 | } 15 | jvmMain.dependencies { 16 | implementation(compose.desktop.currentOs) 17 | implementation(libs.ktor.client.java) 18 | } 19 | } 20 | } 21 | 22 | compose.desktop { 23 | application { 24 | mainClass = "com.mikepenz.markdown.MainKt" 25 | 26 | nativeDistributions { 27 | targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb) 28 | packageName = "com.mikepenz.markdown" 29 | packageVersion = "1.0.0" 30 | } 31 | } 32 | } 33 | 34 | compose.resources { 35 | packageOfResClass = "com.mikepenz.markdown.sample.desktop.resources" 36 | } 37 | 38 | aboutLibraries { 39 | android { 40 | registerAndroidTasks = false 41 | } 42 | export { 43 | exportVariant = "jvmMain" 44 | outputPath = file("src/commonMain/composeResources/files/aboutlibraries.json") 45 | } 46 | library { 47 | duplicationMode = com.mikepenz.aboutlibraries.plugin.DuplicateMode.MERGE 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /multiplatform-markdown-renderer-code/api/jvm/multiplatform-markdown-renderer-code.api: -------------------------------------------------------------------------------- 1 | public final class com/mikepenz/markdown/compose/elements/ComposableSingletons$MarkdownHighlightedCodeKt { 2 | public static final field INSTANCE Lcom/mikepenz/markdown/compose/elements/ComposableSingletons$MarkdownHighlightedCodeKt; 3 | public fun ()V 4 | public final fun getLambda$-621386773$multiplatform_markdown_renderer_code ()Lkotlin/jvm/functions/Function3; 5 | public final fun getLambda$986930703$multiplatform_markdown_renderer_code ()Lkotlin/jvm/functions/Function3; 6 | } 7 | 8 | public final class com/mikepenz/markdown/compose/elements/MarkdownHighlightedCodeKt { 9 | public static final fun MarkdownHighlightedCode (Ljava/lang/String;Ljava/lang/String;Landroidx/compose/ui/text/TextStyle;Ldev/snipme/highlights/Highlights$Builder;ZLandroidx/compose/runtime/Composer;II)V 10 | public static final fun MarkdownHighlightedCodeBlock (Ljava/lang/String;Lorg/intellij/markdown/ast/ASTNode;Landroidx/compose/ui/text/TextStyle;Ldev/snipme/highlights/Highlights$Builder;ZLandroidx/compose/runtime/Composer;II)V 11 | public static final fun MarkdownHighlightedCodeFence (Ljava/lang/String;Lorg/intellij/markdown/ast/ASTNode;Landroidx/compose/ui/text/TextStyle;Ldev/snipme/highlights/Highlights$Builder;ZLandroidx/compose/runtime/Composer;II)V 12 | public static final fun getHighlightedCodeBlock ()Lkotlin/jvm/functions/Function3; 13 | public static final fun getHighlightedCodeFence ()Lkotlin/jvm/functions/Function3; 14 | } 15 | 16 | -------------------------------------------------------------------------------- /multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/model/MarkdownTypography.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.model 2 | 3 | import androidx.compose.runtime.Immutable 4 | import androidx.compose.ui.text.TextLinkStyles 5 | import androidx.compose.ui.text.TextStyle 6 | 7 | @Immutable 8 | interface MarkdownTypography { 9 | val text: TextStyle 10 | val code: TextStyle 11 | val inlineCode: TextStyle 12 | val h1: TextStyle 13 | val h2: TextStyle 14 | val h3: TextStyle 15 | val h4: TextStyle 16 | val h5: TextStyle 17 | val h6: TextStyle 18 | val quote: TextStyle 19 | val paragraph: TextStyle 20 | val ordered: TextStyle 21 | val bullet: TextStyle 22 | val list: TextStyle 23 | val textLink: TextLinkStyles 24 | val table: TextStyle 25 | } 26 | 27 | @Immutable 28 | data class DefaultMarkdownTypography( 29 | override val h1: TextStyle, 30 | override val h2: TextStyle, 31 | override val h3: TextStyle, 32 | override val h4: TextStyle, 33 | override val h5: TextStyle, 34 | override val h6: TextStyle, 35 | override val text: TextStyle, 36 | override val code: TextStyle, 37 | override val inlineCode: TextStyle, 38 | override val quote: TextStyle, 39 | override val paragraph: TextStyle, 40 | override val ordered: TextStyle, 41 | override val bullet: TextStyle, 42 | override val list: TextStyle, 43 | override val textLink: TextLinkStyles, 44 | override val table: TextStyle, 45 | ) : MarkdownTypography -------------------------------------------------------------------------------- /sample/web/src/commonMain/resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Markdown Sample (Kotlin/Wasm) 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | ⚠️ Please make sure that your runtime environment supports the latest version of Wasm GC and Exception-Handling proposals. 18 | For more information, see https://kotl.in/wasm-help. 19 |
20 |
21 |
    22 |
  • For Chrome and Chromium-based browsers (Edge, Brave etc.), it should just work since version 119.
  • 23 |
  • For Firefox 120 it should just work.
  • 24 |
  • For Firefox 119: 25 |
      26 |
    1. Open about:config in the browser.
    2. 27 |
    3. Enable javascript.options.wasm_gc.
    4. 28 |
    5. Refresh this page.
    6. 29 |
    30 |
  • 31 |
32 |
33 | 34 | 35 | -------------------------------------------------------------------------------- /multiplatform-markdown-renderer-code/api/android/multiplatform-markdown-renderer-code.api: -------------------------------------------------------------------------------- 1 | public final class com/mikepenz/markdown/compose/elements/ComposableSingletons$MarkdownHighlightedCodeKt { 2 | public static final field INSTANCE Lcom/mikepenz/markdown/compose/elements/ComposableSingletons$MarkdownHighlightedCodeKt; 3 | public fun ()V 4 | public final fun getLambda$-621386773$multiplatform_markdown_renderer_code_release ()Lkotlin/jvm/functions/Function3; 5 | public final fun getLambda$986930703$multiplatform_markdown_renderer_code_release ()Lkotlin/jvm/functions/Function3; 6 | } 7 | 8 | public final class com/mikepenz/markdown/compose/elements/MarkdownHighlightedCodeKt { 9 | public static final fun MarkdownHighlightedCode (Ljava/lang/String;Ljava/lang/String;Landroidx/compose/ui/text/TextStyle;Ldev/snipme/highlights/Highlights$Builder;ZLandroidx/compose/runtime/Composer;II)V 10 | public static final fun MarkdownHighlightedCodeBlock (Ljava/lang/String;Lorg/intellij/markdown/ast/ASTNode;Landroidx/compose/ui/text/TextStyle;Ldev/snipme/highlights/Highlights$Builder;ZLandroidx/compose/runtime/Composer;II)V 11 | public static final fun MarkdownHighlightedCodeFence (Ljava/lang/String;Lorg/intellij/markdown/ast/ASTNode;Landroidx/compose/ui/text/TextStyle;Ldev/snipme/highlights/Highlights$Builder;ZLandroidx/compose/runtime/Composer;II)V 12 | public static final fun getHighlightedCodeBlock ()Lkotlin/jvm/functions/Function3; 13 | public static final fun getHighlightedCodeFence ()Lkotlin/jvm/functions/Function3; 14 | } 15 | 16 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. 3 | 4 | Fixes # (issue) 5 | 6 | ## Type of change 7 | Please delete options that are not relevant. 8 | 9 | - [ ] Bug fix (non-breaking change which fixes an issue) 10 | - [ ] New feature (non-breaking change which adds functionality) 11 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 12 | - [ ] Documentation update 13 | - [ ] Code style update (formatting, renaming) 14 | - [ ] Refactoring (no functional changes, no API changes) 15 | - [ ] Build configuration change 16 | - [ ] Other (please describe): 17 | 18 | ## How Has This Been Tested? 19 | Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration. 20 | 21 | - [ ] Test A 22 | - [ ] Test B 23 | 24 | ## Checklist: 25 | - [ ] My code follows the style guidelines of this project 26 | - [ ] I have performed a self-review of my own code 27 | - [ ] I have commented my code, particularly in hard-to-understand areas 28 | - [ ] I have made corresponding changes to the documentation 29 | - [ ] My changes generate no new warnings 30 | - [ ] I have added tests that prove my fix is effective or that my feature works 31 | - [ ] New and existing unit tests pass locally with my changes 32 | - [ ] Any dependent changes have been merged and published in downstream modules 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | --- 8 | 9 | ## Bug Description 10 | 11 | A clear and concise description of what the bug is. 12 | 13 | ## Steps to Reproduce 14 | 15 | Steps to reproduce the behavior: 16 | 17 | 1. Add this code '...' 18 | 2. Run on platform '...' 19 | 3. See error 20 | 21 | ## Expected Behavior 22 | 23 | A clear and concise description of what you expected to happen. 24 | 25 | ## Screenshots 26 | 27 | If applicable, add screenshots to help explain your problem. 28 | 29 | ## Environment 30 | 31 | - Library Version: [e.g. 0.34.0] 32 | - Platform: [e.g. Android, iOS, Desktop, Web] 33 | - Device: [e.g. Pixel 9, ...] 34 | - OS Version: [e.g. Android 16, ...] 35 | - Kotlin Version: [e.g. 2.1.20] 36 | - Compose Version: [e.g. 1.8.0] 37 | 38 | ## Additional Context 39 | 40 | Add any other context about the problem here. 41 | 42 | ## Checklist 43 | 44 | - [ ] I have searched for [similar issues](https://github.com/mikepenz/multiplatform-markdown-renderer/issues) 45 | - [ ] I have checked the [sample application](https://github.com/mikepenz/multiplatform-markdown-renderer/tree/develop/sample) 46 | - [ ] I have read the [README](https://github.com/mikepenz/multiplatform-markdown-renderer/blob/develop/README.md) 47 | - [ ] I have checked the [CHANGELOG](https://github.com/mikepenz/multiplatform-markdown-renderer/releases) 48 | - [ ] I have read the [MIGRATION GUIDE](https://github.com/mikepenz/multiplatform-markdown-renderer/blob/develop/MIGRATION.md) 49 | -------------------------------------------------------------------------------- /Dangerfile: -------------------------------------------------------------------------------- 1 | github.dismiss_out_of_range_messages 2 | 3 | # Make it more obvious that a PR is a work in progress and shouldn't be merged yet. 4 | has_wip_label = github.pr_labels.any? { |label| label.include? "Engineers at work" } 5 | has_wip_title = github.pr_title.include? "[WIP]" 6 | 7 | if has_wip_label || has_wip_title 8 | warn("PR is marked as Work in Progress") 9 | end 10 | 11 | # Ensure the PR is not marked as DO NOT MERGE 12 | fail("PR specifies label DO NOT MERGE") if github.pr_labels.any? { |label| label.include? "DO NOT MERGE" } 13 | 14 | # Warn when there is a big PR 15 | warn("Big PR") if git.lines_of_code > 5000 16 | 17 | File.open("settings.gradle.kts", "r") do |file_handle| 18 | file_handle.each_line do |setting| 19 | if setting.include? "include" 20 | gradleModule = setting[10, setting.length-12] 21 | 22 | # AndroidLint 23 | androidLintFile = String.new(gradleModule + "/build/reports/lint-results.xml") 24 | androidLintDebugFile = String.new(gradleModule + "/build/reports/lint-results-debug.xml") 25 | if File.file?(androidLintFile) || File.file?(androidLintDebugFile) 26 | android_lint.skip_gradle_task = true 27 | android_lint.severity = "Warning" 28 | if File.file?(androidLintFile) 29 | android_lint.report_file = androidLintFile 30 | else 31 | android_lint.report_file = androidLintDebugFile 32 | end 33 | android_lint.filtering = true 34 | android_lint.lint(inline_mode: true) 35 | end 36 | end 37 | end 38 | end -------------------------------------------------------------------------------- /sample/android/src/screenshotTest/kotlin/com/mikepenz/markdown/ui/m2/LinkTests.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.ui.m2 2 | 3 | import androidx.compose.runtime.Composable 4 | import com.mikepenz.markdown.ui.annotation.DarkLightPreview 5 | import com.mikepenz.markdown.ui.m2.util.TestMarkdown 6 | 7 | /** 8 | * Covers links and reference behavior as documented 9 | * https://github.com/adam-p/markdown-here/wiki/markdown-cheatsheet#links 10 | */ 11 | class LinkTests { 12 | @DarkLightPreview 13 | @Composable 14 | fun LinkTest() = TestMarkdown( 15 | """ 16 | [I'm an inline-style link](https://www.google.com) 17 | 18 | [I'm an inline-style link with title](https://www.google.com "Google's Homepage") 19 | 20 | [I'm a reference-style link][Arbitrary case-insensitive reference text] 21 | 22 | [I'm a relative reference to a repository file](../blob/master/LICENSE) 23 | 24 | [You can use numbers for reference-style link definitions][1] 25 | 26 | Or leave it empty and use the [link text itself]. 27 | 28 | URLs and URLs in angle brackets will automatically get turned into links. 29 | http://www.example.com or and sometimes 30 | example.com (but not on Github, for example). 31 | 32 | Some text to show that the reference links can follow later. 33 | 34 | [arbitrary case-insensitive reference text]: https://www.mozilla.org 35 | [1]: http://slashdot.org 36 | [link text itself]: http://www.reddit.com 37 | """.trimIndent() 38 | ) 39 | } -------------------------------------------------------------------------------- /sample/android/src/screenshotTest/kotlin/com/mikepenz/markdown/ui/m3/LinkTests.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.ui.m3 2 | 3 | import androidx.compose.runtime.Composable 4 | import com.mikepenz.markdown.ui.annotation.DarkLightPreview 5 | import com.mikepenz.markdown.ui.m3.util.TestMarkdown 6 | 7 | /** 8 | * Covers links and reference behavior as documented 9 | * https://github.com/adam-p/markdown-here/wiki/markdown-cheatsheet#links 10 | */ 11 | class LinkTests { 12 | @DarkLightPreview 13 | @Composable 14 | fun LinkTest() = TestMarkdown( 15 | """ 16 | [I'm an inline-style link](https://www.google.com) 17 | 18 | [I'm an inline-style link with title](https://www.google.com "Google's Homepage") 19 | 20 | [I'm a reference-style link][Arbitrary case-insensitive reference text] 21 | 22 | [I'm a relative reference to a repository file](../blob/master/LICENSE) 23 | 24 | [You can use numbers for reference-style link definitions][1] 25 | 26 | Or leave it empty and use the [link text itself]. 27 | 28 | URLs and URLs in angle brackets will automatically get turned into links. 29 | http://www.example.com or and sometimes 30 | example.com (but not on Github, for example). 31 | 32 | Some text to show that the reference links can follow later. 33 | 34 | [arbitrary case-insensitive reference text]: https://www.mozilla.org 35 | [1]: http://slashdot.org 36 | [link text itself]: http://www.reddit.com 37 | """.trimIndent() 38 | ) 39 | } -------------------------------------------------------------------------------- /multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/model/MarkdownDimens.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.model 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.Immutable 5 | import androidx.compose.ui.unit.Dp 6 | import androidx.compose.ui.unit.dp 7 | 8 | @Immutable 9 | interface MarkdownDimens { 10 | val dividerThickness: Dp 11 | val codeBackgroundCornerSize: Dp 12 | val blockQuoteThickness: Dp 13 | val tableMaxWidth: Dp 14 | val tableCellWidth: Dp 15 | val tableCellPadding: Dp 16 | val tableCornerSize: Dp 17 | } 18 | 19 | @Immutable 20 | private data class DefaultMarkdownDimens( 21 | override val dividerThickness: Dp, 22 | override val codeBackgroundCornerSize: Dp, 23 | override val blockQuoteThickness: Dp, 24 | override val tableMaxWidth: Dp, 25 | override val tableCellWidth: Dp, 26 | override val tableCellPadding: Dp, 27 | override val tableCornerSize: Dp, 28 | ) : MarkdownDimens 29 | 30 | @Composable 31 | fun markdownDimens( 32 | dividerThickness: Dp = 1.dp, 33 | codeBackgroundCornerSize: Dp = 8.dp, 34 | blockQuoteThickness: Dp = 2.dp, 35 | tableMaxWidth: Dp = Dp.Unspecified, 36 | tableCellWidth: Dp = 160.dp, 37 | tableCellPadding: Dp = 16.dp, 38 | tableCornerSize: Dp = 8.dp, 39 | ): MarkdownDimens = DefaultMarkdownDimens( 40 | dividerThickness = dividerThickness, 41 | codeBackgroundCornerSize = codeBackgroundCornerSize, 42 | blockQuoteThickness = blockQuoteThickness, 43 | tableMaxWidth = tableMaxWidth, 44 | tableCellWidth = tableCellWidth, 45 | tableCellPadding = tableCellPadding, 46 | tableCornerSize = tableCornerSize, 47 | ) 48 | -------------------------------------------------------------------------------- /multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/model/MarkdownAnimations.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.model 2 | 3 | import androidx.compose.animation.animateContentSize 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.runtime.Immutable 6 | import androidx.compose.ui.Modifier 7 | import com.mikepenz.markdown.compose.elements.MarkdownText 8 | 9 | @Immutable 10 | interface MarkdownAnimations { 11 | /** 12 | * Modifier used to animate [MarkdownText] size changes. 13 | * This is mainly the case if inline images are loaded and their placeholder has a different size from the final image. 14 | */ 15 | val animateTextSize: Modifier.() -> Modifier 16 | } 17 | 18 | @Immutable 19 | class DefaultMarkdownAnimation( 20 | override val animateTextSize: Modifier.() -> Modifier, 21 | ) : MarkdownAnimations { 22 | override fun equals(other: Any?): Boolean { 23 | if (this === other) return true 24 | if (other == null || this::class != other::class) return false 25 | 26 | other as DefaultMarkdownAnimation 27 | 28 | return animateTextSize == other.animateTextSize 29 | } 30 | 31 | override fun hashCode(): Int { 32 | return animateTextSize.hashCode() 33 | } 34 | } 35 | 36 | @Composable 37 | fun markdownAnimations( 38 | /** 39 | * Modifier used to animate [MarkdownText] size changes. 40 | * By default, this uses [animateContentSize]. 41 | * 42 | * It's possible to modify the animation or alternatively return` {this} to not animate at all. 43 | */ 44 | animateTextSize: Modifier.() -> Modifier = { animateContentSize() }, 45 | ): MarkdownAnimations = DefaultMarkdownAnimation( 46 | animateTextSize 47 | ) 48 | -------------------------------------------------------------------------------- /multiplatform-markdown-renderer-coil3/api/multiplatform-markdown-renderer-coil3.klib.api: -------------------------------------------------------------------------------- 1 | // Klib ABI Dump 2 | // Targets: [iosArm64, iosSimulatorArm64, iosX64, js, macosArm64, macosX64, wasmJs] 3 | // Rendering settings: 4 | // - Signature version: 2 5 | // - Show manifest properties: true 6 | // - Show declarations: true 7 | 8 | // Library unique name: 9 | final object com.mikepenz.markdown.coil3/Coil3ImageTransformerImpl : com.mikepenz.markdown.model/ImageTransformer { // com.mikepenz.markdown.coil3/Coil3ImageTransformerImpl|null[0] 10 | final fun intrinsicSize(androidx.compose.ui.graphics.painter/Painter, androidx.compose.runtime/Composer?, kotlin/Int): androidx.compose.ui.geometry/Size // com.mikepenz.markdown.coil3/Coil3ImageTransformerImpl.intrinsicSize|intrinsicSize(androidx.compose.ui.graphics.painter.Painter;androidx.compose.runtime.Composer?;kotlin.Int){}[0] 11 | final fun transform(kotlin/String, androidx.compose.runtime/Composer?, kotlin/Int): com.mikepenz.markdown.model/ImageData // com.mikepenz.markdown.coil3/Coil3ImageTransformerImpl.transform|transform(kotlin.String;androidx.compose.runtime.Composer?;kotlin.Int){}[0] 12 | } 13 | 14 | final val com.mikepenz.markdown.coil3/com_mikepenz_markdown_coil3_Coil3ImageTransformerImpl$stableprop // com.mikepenz.markdown.coil3/com_mikepenz_markdown_coil3_Coil3ImageTransformerImpl$stableprop|#static{}com_mikepenz_markdown_coil3_Coil3ImageTransformerImpl$stableprop[0] 15 | 16 | final fun com.mikepenz.markdown.coil3/com_mikepenz_markdown_coil3_Coil3ImageTransformerImpl$stableprop_getter(): kotlin/Int // com.mikepenz.markdown.coil3/com_mikepenz_markdown_coil3_Coil3ImageTransformerImpl$stableprop_getter|com_mikepenz_markdown_coil3_Coil3ImageTransformerImpl$stableprop_getter(){}[0] 17 | -------------------------------------------------------------------------------- /.github/workflows/static.yml: -------------------------------------------------------------------------------- 1 | # Simple workflow for deploying static content to GitHub Pages 2 | name: Deploy static content to Pages 3 | 4 | on: 5 | push: 6 | tags: 7 | - '*' 8 | 9 | # Allows you to run this workflow manually from the Actions tab 10 | workflow_dispatch: 11 | 12 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 13 | permissions: 14 | contents: read 15 | pages: write 16 | id-token: write 17 | 18 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 19 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 20 | concurrency: 21 | group: "pages" 22 | cancel-in-progress: false 23 | 24 | jobs: 25 | # Single deploy job since we're just deploying 26 | deploy: 27 | environment: 28 | name: github-pages 29 | url: ${{ steps.deployment.outputs.page_url }} 30 | runs-on: ubuntu-latest 31 | steps: 32 | - name: Checkout 33 | uses: actions/checkout@v6 34 | - name: Setup Pages 35 | uses: actions/configure-pages@v5 36 | - uses: actions/setup-java@v5 37 | with: 38 | distribution: 'zulu' 39 | java-version: | 40 | 11 41 | 17 42 | - name: Setup Gradle 43 | uses: gradle/actions/setup-gradle@v5 44 | - name: Build Page 45 | run: | 46 | ./gradlew sample:web:wasmJsBrowserDistribution --no-configuration-cache 47 | 48 | - name: Upload artifact 49 | uses: actions/upload-pages-artifact@v4 50 | with: 51 | path: 'sample/web/build/dist/wasmJs/productionExecutable' 52 | 53 | - name: Deploy to GitHub Pages 54 | id: deployment 55 | uses: actions/deploy-pages@v4 -------------------------------------------------------------------------------- /multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/utils/EntityConverter.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.utils 2 | 3 | import org.intellij.markdown.html.entities.Entities 4 | 5 | /** 6 | * Based on: https://github.com/JetBrains/markdown/blob/master/src/commonMain/kotlin/org/intellij/markdown/html/entities/EntityConverter.kt 7 | * Removed HTML focused escaping by https://github.com/mikepenz/multiplatform-markdown-renderer/pull/222 8 | */ 9 | object EntityConverter { 10 | private const val ESCAPE_ALLOWED_STRING = """!"#\$%&'\(\)\*\+,\-.\/:;<=>\?@\[\\\]\^_`{\|}~""" 11 | private val REGEX = Regex("""&(?:([a-zA-Z0-9]+)|#([0-9]{1,8})|#[xX]([a-fA-F0-9]{1,8}));|(["&<>])""") 12 | private val REGEX_ESCAPES = Regex("${REGEX.pattern}|\\\\([$ESCAPE_ALLOWED_STRING])") 13 | 14 | fun replaceEntities( 15 | text: CharSequence, 16 | processEntities: Boolean, 17 | processEscapes: Boolean, 18 | ): String { 19 | val regex = if (processEscapes) REGEX_ESCAPES else REGEX 20 | return regex.replace(text) { match -> 21 | val g = match.groups 22 | when { 23 | g.size > 5 && g[5] != null -> g[5]!!.value[0].toString() 24 | g[4] != null -> match.value 25 | else -> { 26 | val code = when { 27 | !processEntities -> null 28 | g[1] != null -> Entities.map[match.value] 29 | g[2] != null -> g[2]!!.value.toInt() 30 | g[3] != null -> g[3]!!.value.toInt(16) 31 | else -> null 32 | } 33 | code?.toChar()?.toString() ?: "&${match.value.substring(1)}" 34 | } 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /sample/ios/iosApp/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CADisableMinimumFrameDurationOnPhone 6 | 7 | CFBundleDevelopmentRegion 8 | $(DEVELOPMENT_LANGUAGE) 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UIApplicationSceneManifest 26 | 27 | UIApplicationSupportsMultipleScenes 28 | 29 | 30 | UILaunchScreen 31 | 32 | UIRequiredDeviceCapabilities 33 | 34 | armv7 35 | 36 | UIStatusBarStyle 37 | 38 | UISupportedInterfaceOrientations 39 | 40 | UIInterfaceOrientationPortrait 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | UISupportedInterfaceOrientations~ipad 45 | 46 | UIInterfaceOrientationPortrait 47 | UIInterfaceOrientationPortraitUpsideDown 48 | UIInterfaceOrientationLandscapeLeft 49 | UIInterfaceOrientationLandscapeRight 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/model/MarkdownImageState.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.model 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.getValue 5 | import androidx.compose.runtime.mutableStateOf 6 | import androidx.compose.runtime.remember 7 | import androidx.compose.runtime.setValue 8 | import androidx.compose.ui.geometry.Size 9 | import androidx.compose.ui.platform.LocalDensity 10 | import androidx.compose.ui.unit.Density 11 | 12 | interface MarkdownImageState { 13 | val density: Density 14 | val containerSize: Size 15 | val intrinsicImageSize: Size 16 | 17 | fun updateContainerSize(size: Size) 18 | 19 | fun updateImageSize(size: Size) 20 | } 21 | 22 | internal class MarkdownImageStateImpl(override val density: Density) : MarkdownImageState { 23 | 24 | override var containerSize by mutableStateOf(Size.Unspecified) 25 | 26 | override var intrinsicImageSize by mutableStateOf(Size.Unspecified) 27 | 28 | override fun updateContainerSize(size: Size) { 29 | containerSize = size 30 | } 31 | 32 | override fun updateImageSize(size: Size) { 33 | intrinsicImageSize = size 34 | } 35 | } 36 | 37 | /** 38 | * Creates and remembers a [MarkdownImageState] instance. 39 | * 40 | * This composable function creates a new instance of [MarkdownImageState] using the current 41 | * density from [LocalDensity] and remembers it across recompositions as long as the 42 | * density doesn't change. 43 | * 44 | * It's used internally by the markdown renderer to manage image state for inline images. 45 | * 46 | * @return A remembered instance of [MarkdownImageState]. 47 | */ 48 | @Composable 49 | internal fun rememberMarkdownImageState(): MarkdownImageState { 50 | val density = LocalDensity.current 51 | return remember(density) { MarkdownImageStateImpl(density) } 52 | } 53 | -------------------------------------------------------------------------------- /multiplatform-markdown-renderer-coil2/src/jvmMain/kotlin/com/mikepenz/markdown/coil2/ImagePainterProvider.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.coil2 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.LaunchedEffect 5 | import androidx.compose.runtime.getValue 6 | import androidx.compose.runtime.mutableStateOf 7 | import androidx.compose.runtime.remember 8 | import androidx.compose.runtime.setValue 9 | import androidx.compose.ui.geometry.Size 10 | import androidx.compose.ui.graphics.ImageBitmap 11 | import androidx.compose.ui.graphics.painter.BitmapPainter 12 | import androidx.compose.ui.graphics.painter.Painter 13 | import androidx.compose.ui.graphics.toComposeImageBitmap 14 | import kotlinx.coroutines.Dispatchers 15 | import kotlinx.coroutines.withContext 16 | import org.jetbrains.skia.Image 17 | import java.io.InputStream 18 | import java.net.HttpURLConnection 19 | import java.net.URL 20 | 21 | @Composable 22 | internal actual fun imagePainter(url: String): Painter? { 23 | return fetchImage(url)?.let { BitmapPainter(it) } 24 | } 25 | 26 | 27 | @Composable 28 | internal actual fun painterIntrinsicSize(painter: Painter): Size { 29 | return painter.intrinsicSize 30 | } 31 | 32 | @Composable 33 | fun fetchImage(url: String): ImageBitmap? { 34 | var image by remember(url) { mutableStateOf(null) } 35 | LaunchedEffect(url) { 36 | image = loadPicture(url) 37 | } 38 | return image 39 | } 40 | 41 | suspend fun loadPicture(url: String): ImageBitmap? = withContext(Dispatchers.IO) { 42 | return@withContext runCatching { 43 | val connection: HttpURLConnection = URL(url).openConnection() as HttpURLConnection 44 | connection.connectTimeout = 5000 45 | connection.connect() 46 | 47 | val input: InputStream = connection.inputStream 48 | Image.makeFromEncoded(input.readBytes()).toComposeImageBitmap() 49 | }.getOrNull() 50 | } -------------------------------------------------------------------------------- /multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/compose/LazyMarkdown.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.compose 2 | 3 | import androidx.compose.foundation.layout.PaddingValues 4 | import androidx.compose.foundation.lazy.LazyColumn 5 | import androidx.compose.foundation.lazy.items 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.runtime.remember 8 | import androidx.compose.ui.Modifier 9 | import androidx.compose.ui.unit.dp 10 | import com.mikepenz.markdown.compose.components.MarkdownComponents 11 | import com.mikepenz.markdown.model.State 12 | 13 | /** 14 | * Renders the parsed markdown content in a [LazyColumn]. 15 | * 16 | * This function uses Compose's [LazyColumn] to implement virtualization, which means 17 | * only the visible portions of the document are rendered, improving performance for 18 | * large documents. 19 | * 20 | * @param state The success markdown state. 21 | * @param components The MarkdownComponents instance containing the components to use. 22 | * @param modifier The modifier to be applied to the container. 23 | * @param contentPadding The padding to be applied to the scrolling container. 24 | */ 25 | @Composable 26 | fun LazyMarkdownSuccess( 27 | state: State.Success, 28 | components: MarkdownComponents, 29 | modifier: Modifier = Modifier, 30 | contentPadding: PaddingValues = PaddingValues(0.dp), 31 | ) { 32 | // Extract nodes for rendering 33 | val nodes = remember(state.node) { state.node.children } 34 | 35 | LazyColumn( 36 | modifier = modifier, 37 | contentPadding = contentPadding, 38 | ) { 39 | items( 40 | items = nodes, 41 | // Use the node's start offset as a key for stable item identity 42 | key = { node -> node.startOffset } 43 | ) { node -> 44 | MarkdownElement(node, components, state.content) 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/compose/elements/MarkdownDivider.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.compose.elements 2 | 3 | import androidx.compose.foundation.background 4 | import androidx.compose.foundation.layout.Box 5 | import androidx.compose.foundation.layout.fillMaxHeight 6 | import androidx.compose.foundation.layout.fillMaxWidth 7 | import androidx.compose.foundation.layout.height 8 | import androidx.compose.foundation.layout.width 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.ui.Modifier 11 | import androidx.compose.ui.graphics.Color 12 | import androidx.compose.ui.platform.LocalDensity 13 | import androidx.compose.ui.unit.Dp 14 | import androidx.compose.ui.unit.dp 15 | import com.mikepenz.markdown.compose.LocalMarkdownColors 16 | import com.mikepenz.markdown.compose.LocalMarkdownDimens 17 | 18 | @Composable 19 | fun MarkdownDivider( 20 | modifier: Modifier = Modifier, 21 | color: Color = LocalMarkdownColors.current.dividerColor, 22 | thickness: Dp = LocalMarkdownDimens.current.dividerThickness, 23 | ) { 24 | val targetThickness = if (thickness == Dp.Hairline) { 25 | (1f / LocalDensity.current.density).dp 26 | } else { 27 | thickness 28 | } 29 | Box( 30 | modifier 31 | .fillMaxWidth() 32 | .height(targetThickness) 33 | .background(color = color) 34 | ) 35 | } 36 | 37 | 38 | @Composable 39 | fun VerticalMarkdownDivider( 40 | modifier: Modifier = Modifier, 41 | color: Color = LocalMarkdownColors.current.dividerColor, 42 | thickness: Dp = LocalMarkdownDimens.current.dividerThickness, 43 | ) { 44 | val targetThickness = if (thickness == Dp.Hairline) { 45 | (1f / LocalDensity.current.density).dp 46 | } else { 47 | thickness 48 | } 49 | Box( 50 | modifier 51 | .width(targetThickness) 52 | .fillMaxHeight() 53 | .background(color = color) 54 | ) 55 | } 56 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | addressable (2.8.5) 5 | public_suffix (>= 2.0.2, < 6.0) 6 | ansi (1.5.0) 7 | ast (2.4.2) 8 | claide (1.1.0) 9 | claide-plugins (0.9.2) 10 | cork 11 | nap 12 | open4 (~> 1.3) 13 | colored2 (3.1.2) 14 | cork (0.3.0) 15 | colored2 (~> 3.1) 16 | danger (9.3.1) 17 | claide (~> 1.0) 18 | claide-plugins (>= 0.9.2) 19 | colored2 (~> 3.1) 20 | cork (~> 0.1) 21 | faraday (>= 0.9.0, < 3.0) 22 | faraday-http-cache (~> 2.0) 23 | git (~> 1.13) 24 | kramdown (~> 2.3) 25 | kramdown-parser-gfm (~> 1.0) 26 | no_proxy_fix 27 | octokit (~> 6.0) 28 | terminal-table (>= 1, < 4) 29 | danger-android_lint (0.0.12) 30 | danger-plugin-api (~> 1.0) 31 | oga 32 | danger-plugin-api (1.0.0) 33 | danger (> 2.0) 34 | faraday (2.7.10) 35 | faraday-net_http (>= 2.0, < 3.1) 36 | ruby2_keywords (>= 0.0.4) 37 | faraday-http-cache (2.5.0) 38 | faraday (>= 0.8) 39 | faraday-net_http (3.0.2) 40 | git (1.18.0) 41 | addressable (~> 2.8) 42 | rchardet (~> 1.8) 43 | kramdown (2.4.0) 44 | rexml 45 | kramdown-parser-gfm (1.1.0) 46 | kramdown (~> 2.0) 47 | nap (1.1.0) 48 | no_proxy_fix (0.1.2) 49 | octokit (6.1.1) 50 | faraday (>= 1, < 3) 51 | sawyer (~> 0.9) 52 | oga (3.4) 53 | ast 54 | ruby-ll (~> 2.1) 55 | open4 (1.3.4) 56 | public_suffix (5.0.3) 57 | rchardet (1.8.0) 58 | rexml (3.3.9) 59 | ruby-ll (2.1.3) 60 | ansi 61 | ast 62 | ruby2_keywords (0.0.5) 63 | sawyer (0.9.2) 64 | addressable (>= 2.3.5) 65 | faraday (>= 0.17.3, < 3) 66 | terminal-table (3.0.2) 67 | unicode-display_width (>= 1.1.1, < 3) 68 | unicode-display_width (2.4.2) 69 | 70 | PLATFORMS 71 | ruby 72 | 73 | DEPENDENCIES 74 | danger 75 | danger-android_lint 76 | 77 | BUNDLED WITH 78 | 2.4.10 79 | -------------------------------------------------------------------------------- /multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/model/MarkdownAnnotator.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.model 2 | 3 | import androidx.compose.runtime.Immutable 4 | import androidx.compose.ui.text.AnnotatedString 5 | import org.intellij.markdown.ast.ASTNode 6 | 7 | @Immutable 8 | interface MarkdownAnnotator { 9 | 10 | /** 11 | * Use the [AnnotatedString.Builder] to build the string to display. 12 | * Return `true` to consume the child, false to allow default handling. 13 | * 14 | * @param content contains the whole content, and requires the `child` [ASTNode] to extract relevant text. 15 | */ 16 | val annotate: (AnnotatedString.Builder.(content: String, child: ASTNode) -> Boolean)? 17 | 18 | /** Defines static configuration for the [androidx.compose.ui.text.AnnotatedString] annotator */ 19 | val config: MarkdownAnnotatorConfig 20 | } 21 | 22 | @Immutable 23 | class DefaultMarkdownAnnotator( 24 | override val annotate: (AnnotatedString.Builder.(content: String, child: ASTNode) -> Boolean)?, 25 | override val config: MarkdownAnnotatorConfig, 26 | ) : MarkdownAnnotator { 27 | override fun equals(other: Any?): Boolean { 28 | if (this === other) return true 29 | if (other == null || this::class != other::class) return false 30 | 31 | other as DefaultMarkdownAnnotator 32 | 33 | if (annotate != other.annotate) return false 34 | if (config != other.config) return false 35 | 36 | return true 37 | } 38 | 39 | override fun hashCode(): Int { 40 | var result = annotate?.hashCode() ?: 0 41 | result = 31 * result + config.hashCode() 42 | return result 43 | } 44 | } 45 | 46 | fun markdownAnnotator( 47 | config: MarkdownAnnotatorConfig = markdownAnnotatorConfig(), 48 | annotate: (AnnotatedString.Builder.(content: String, child: ASTNode) -> Boolean)? = null, 49 | ): MarkdownAnnotator = DefaultMarkdownAnnotator( 50 | annotate = annotate, 51 | config = config 52 | ) 53 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Maven stuff 2 | GROUP=com.mikepenz 3 | VERSION_NAME=0.38.1 4 | VERSION_CODE=3801 5 | # 6 | POM_URL=https://github.com/mikepenz/multiplatform-markdown-renderer 7 | POM_SCM_URL=https://github.com/mikepenz/multiplatform-markdown-renderer 8 | POM_SCM_CONNECTION=scm:git@github.com:mikepenz/multiplatform-markdown-renderer.git 9 | POM_SCM_DEV_CONNECTION=scm:git@github.com:mikepenz/multiplatform-markdown-renderer.git 10 | POM_SCM_URL_ISSUES=https://github.com/mikepenz/multiplatform-markdown-renderer/issues 11 | # 12 | POM_GITHUB_REPO=mikepenz/multiplatform-markdown-renderer 13 | POM_GITHUB_README=README.md 14 | # 15 | POM_LICENCE_NAME=Apache-2.0 16 | POM_LICENCE_URL=https://www.apache.org/licenses/LICENSE-2.0.txt 17 | POM_LICENCE_DIST=repo 18 | # 19 | POM_DEVELOPER_ID=mikepenz 20 | POM_DEVELOPER_NAME=Mike Penz 21 | POM_DEVELOPER_EMAIL=opensource@mikepenz.dev 22 | # Project-wide Gradle settings. 23 | org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx2048M" 24 | org.gradle.caching=true 25 | android.useAndroidX=true 26 | # 27 | kotlin.mpp.stability.nowarn=true 28 | kotlin.mpp.androidSourceSetLayoutVersion=2 29 | kotlin.native.ignoreDisabledTargets=true 30 | kotlin.native.enableKlibsCrossCompilation=true 31 | kotlin.suppressGradlePluginWarnings=IncorrectCompileOnlyDependencyWarning 32 | # 33 | org.jetbrains.compose.experimental.wasm.enabled=true 34 | org.jetbrains.compose.experimental.uikit.enabled=true 35 | org.jetbrains.compose.experimental.jscanvas.enabled=true 36 | org.jetbrains.compose.experimental.macos.enabled=true 37 | # Screenshot test 38 | android.experimental.enableScreenshotTest=true 39 | # Dokka 40 | org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled 41 | org.jetbrains.dokka.experimental.gradle.pluginMode.noWarn=true 42 | # convention plugin 43 | com.mikepenz.binary-compatibility-validator.enabled=true 44 | com.mikepenz.version-catalog-update.enabled=true 45 | com.mikepenz.compatPatrouille.enabled=false 46 | com.mikepenz.kotlin.version=2.2 47 | com.mikepenz.kotlin.warningsAsErrors.enabled=false -------------------------------------------------------------------------------- /sample/shared/src/commonMain/kotlin/com/mikepenz/markdown/sample/App.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.sample 2 | 3 | import androidx.compose.foundation.isSystemInDarkTheme 4 | import androidx.compose.foundation.layout.padding 5 | import androidx.compose.material3.Scaffold 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.runtime.getValue 8 | import androidx.compose.runtime.mutableStateOf 9 | import androidx.compose.runtime.remember 10 | import androidx.compose.runtime.setValue 11 | import androidx.compose.ui.Modifier 12 | import com.mikepenz.aboutlibraries.Libs 13 | import com.mikepenz.markdown.sample.theme.SampleTheme 14 | 15 | @Composable 16 | fun App( 17 | libraries: Libs?, 18 | modifier: Modifier = Modifier, 19 | ) { 20 | val isSystemInDarkMode = isSystemInDarkTheme() 21 | var darkMode by remember { mutableStateOf(isSystemInDarkMode) } 22 | var showDebug by remember { mutableStateOf(false) } 23 | var showLicenses by remember { mutableStateOf(false) } 24 | SampleTheme(darkMode) { 25 | Scaffold( 26 | topBar = { 27 | TopAppBar( 28 | isDarkMode = darkMode, 29 | onThemeToggle = { darkMode = !darkMode }, 30 | debugClick = { 31 | showDebug = !showDebug 32 | showLicenses = false 33 | }, 34 | onClick = { 35 | showLicenses = !showLicenses 36 | showDebug = false 37 | } 38 | ) 39 | }, 40 | modifier = modifier 41 | ) { contentPadding -> 42 | if (showLicenses) { 43 | LicensesPage(libraries = libraries, contentPadding = contentPadding) 44 | } else if (showDebug) { 45 | RecompositionPage(modifier = Modifier.padding(contentPadding)) 46 | } else { 47 | MarkDownPage(modifier = Modifier.padding(contentPadding)) 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /sample/shared/src/commonMain/composeResources/files/sample.md: -------------------------------------------------------------------------------- 1 | # Markdown Playground 2 | 3 | --- 4 | 5 | # This is an H1 6 | 7 | ## This is an H2 8 | 9 | ### This is an H3 10 | 11 | #### This is an H4 12 | 13 | ##### This is an H5 14 | 15 | ###### This is an H6 16 | 17 | This is a paragraph with some *italic* and **bold** text. 18 | 19 | This is a paragraph with some `inline code`. 20 | 21 | This is a paragraph with a [link](https://www.jetbrains.com/). 22 | 23 | This is a code block: 24 | ```kotlin 25 | fun main() { 26 | println("Hello, world!") 27 | } 28 | ``` 29 | 30 | > This is a block quote. 31 | 32 | This is a divider 33 | 34 | --- 35 | 36 | The above was supposed to be a divider. 37 | 38 | ~~This is strikethrough with two tildes~~ 39 | 40 | ~This is strikethrough~ 41 | 42 | This is an ordered list: 43 | 1. Item 1 44 | 2. Item 2 45 | 3. Item 3 46 | 47 | This is an unordered list with dashes: 48 | - Item 1 49 | - Item 2 50 | - Item 3 51 | 52 | This is an unordered list with asterisks: 53 | * Item 1 54 | * Item 2 55 | * Item 3 56 | 57 | This is an ordered list with task list items: 58 | 1. [ ] foo 59 | 2. [x] bar 60 | 61 | This is an unordered list with task list items: 62 | - [ ] foo 63 | - [x] bar 64 | 65 | -------- 66 | 67 | # Random 68 | 69 | ### Getting Started 70 | 71 | For multiplatform projects specify this single dependency: 72 | 73 | ``` 74 | dependencies { 75 | implementation("com.mikepenz:multiplatform-markdown-renderer:{version}") 76 | } 77 | ``` 78 | 79 | You can find more information on [GitHub](https://github.com/mikepenz/multiplatform-markdown-renderer). More Text after this. 80 | 81 | ![Image](https://avatars.githubusercontent.com/u/1476232?v=4) 82 | 83 | There are many more things which can be experimented with like, inline `code`. 84 | 85 | Title 1 86 | ====== 87 | 88 | Title 2 89 | ------ 90 | 91 | [https://mikepenz.dev](https://mikepenz.dev) 92 | [https://github.com/mikepenz](https://github.com/mikepenz) 93 | [Mike Penz's Blog](https://blog.mikepenz.dev/) 94 | 95 | -------------------------------------------------------------------------------- /sample/shared/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.mikepenz.convention.android-library") 3 | id("com.mikepenz.convention.kotlin-multiplatform") 4 | id("com.mikepenz.convention.compose") 5 | id("com.mikepenz.aboutlibraries.plugin") 6 | } 7 | 8 | kotlin { 9 | androidTarget() 10 | 11 | listOf( 12 | iosX64(), 13 | iosArm64(), 14 | iosSimulatorArm64(), 15 | ).forEach { iosTarget -> 16 | iosTarget.binaries.framework { 17 | baseName = "ComposeApp" 18 | isStatic = true 19 | } 20 | } 21 | 22 | sourceSets { 23 | commonMain.dependencies { 24 | api(projects.multiplatformMarkdownRenderer) 25 | api(projects.multiplatformMarkdownRendererM2) 26 | api(projects.multiplatformMarkdownRendererM3) 27 | api(projects.multiplatformMarkdownRendererCoil3) 28 | api(projects.multiplatformMarkdownRendererCode) 29 | 30 | implementation(compose.foundation) 31 | implementation(compose.ui) 32 | implementation(compose.uiUtil) 33 | implementation(compose.components.resources) 34 | implementation(compose.material) 35 | implementation(compose.material3) 36 | 37 | // required for coil 38 | implementation(libs.ktor.client.core) 39 | implementation(libs.coil.network.ktor) 40 | implementation(libs.coil.svg) 41 | 42 | // about libs 43 | api(baseLibs.bundles.aboutlibs) 44 | } 45 | 46 | iosMain.dependencies { 47 | implementation(libs.ktor.client.darwin) 48 | } 49 | } 50 | } 51 | 52 | android { 53 | namespace = "com.mikepenz.markdown.sample.shared" 54 | } 55 | 56 | compose.resources { 57 | packageOfResClass = "com.mikepenz.markdown.sample.shared.resources" 58 | } 59 | 60 | aboutLibraries { 61 | export { 62 | outputPath = file("src/commonMain/composeResources/files/aboutlibraries.json") 63 | } 64 | library { 65 | duplicationMode = com.mikepenz.aboutlibraries.plugin.DuplicateMode.MERGE 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /sample/shared/src/commonMain/kotlin/com/mikepenz/markdown/sample/TopAppBar.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.sample 2 | 3 | import androidx.compose.foundation.layout.padding 4 | import androidx.compose.material3.ExperimentalMaterial3Api 5 | import androidx.compose.material3.Icon 6 | import androidx.compose.material3.IconButton 7 | import androidx.compose.material3.Switch 8 | import androidx.compose.material3.Text 9 | import androidx.compose.material3.TopAppBar 10 | import androidx.compose.runtime.Composable 11 | import androidx.compose.ui.Modifier 12 | import androidx.compose.ui.platform.LocalUriHandler 13 | import androidx.compose.ui.unit.dp 14 | import com.mikepenz.markdown.sample.icon.Debug 15 | import com.mikepenz.markdown.sample.icon.Github 16 | import com.mikepenz.markdown.sample.icon.OpenSourceInitiative 17 | 18 | @OptIn(ExperimentalMaterial3Api::class) 19 | @Composable 20 | internal fun TopAppBar( 21 | isDarkMode: Boolean, 22 | onThemeToggle: (Boolean) -> Unit, 23 | debugClick: () -> Unit, 24 | onClick: () -> Unit, 25 | ) { 26 | val uriHandler = LocalUriHandler.current 27 | TopAppBar( 28 | title = { Text("Markdown") }, 29 | actions = { 30 | IconButton(onClick = debugClick) { 31 | Icon( 32 | imageVector = Debug, 33 | contentDescription = "Debug Recompositions" 34 | ) 35 | } 36 | IconButton(onClick = onClick) { 37 | Icon( 38 | imageVector = OpenSourceInitiative, 39 | contentDescription = "Open Source" 40 | ) 41 | } 42 | IconButton(onClick = { 43 | uriHandler.openUri("https://github.com/mikepenz/multiplatform-markdown-renderer") 44 | }) { 45 | Icon( 46 | imageVector = Github, 47 | contentDescription = "GitHub" 48 | ) 49 | } 50 | Switch( 51 | checked = isDarkMode, 52 | onCheckedChange = onThemeToggle, 53 | modifier = Modifier.padding(end = 16.dp) 54 | ) 55 | } 56 | ) 57 | } 58 | -------------------------------------------------------------------------------- /sample/android/src/screenshotTest/kotlin/com/mikepenz/markdown/ui/m2/BlockquotesTests.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.ui.m2 2 | 3 | import androidx.compose.runtime.Composable 4 | import com.mikepenz.markdown.ui.annotation.DarkLightPreview 5 | import com.mikepenz.markdown.ui.m2.util.TestMarkdown 6 | 7 | /** 8 | * Covers blockquote behavior as documented 9 | * https://www.markdownguide.org/basic-syntax/#blockquotes-1 10 | */ 11 | class BlockquotesTests { 12 | @DarkLightPreview 13 | @Composable 14 | fun BlockquoteTest() = TestMarkdown( 15 | """ 16 | > Dorothy followed her through many of the beautiful rooms in her castle. 17 | """.trimIndent() 18 | ) 19 | 20 | @DarkLightPreview 21 | @Composable 22 | fun BlockquoteWithMultipleLinesTest() = TestMarkdown( 23 | """ 24 | > first line 25 | > 26 | > third line 27 | > fourth line 28 | """.trimIndent() 29 | ) 30 | 31 | @DarkLightPreview 32 | @Composable 33 | fun BlockquoteWithMultipleParagraphsTest() = TestMarkdown( 34 | """ 35 | > Dorothy followed her through many of the beautiful rooms in her castle. 36 | > 37 | > The Witch bade her clean the pots and kettles and sweep the floor and keep the fire fed with wood. 38 | """.trimIndent() 39 | ) 40 | 41 | @DarkLightPreview 42 | @Composable 43 | fun NestedBlockquoteTest() = TestMarkdown( 44 | """ 45 | > Dorothy followed her through many of the beautiful rooms in her castle. 46 | > 47 | >> The Witch bade her clean the pots and kettles and sweep the floor and keep the fire fed with wood. 48 | > 49 | > Dorothy followed her through many of the beautiful rooms in her castle. 50 | """.trimIndent() 51 | ) 52 | 53 | @DarkLightPreview 54 | @Composable 55 | fun BlockquoteWithOtherElementsTest() = TestMarkdown( 56 | """ 57 | > #### The quarterly results look great! 58 | > 59 | > - Revenue was off the chart. 60 | > - Profits were higher than ever. 61 | > 62 | > *Everything* is going according to **plan**. 63 | """.trimIndent() 64 | ) 65 | } -------------------------------------------------------------------------------- /sample/android/src/screenshotTest/kotlin/com/mikepenz/markdown/ui/m3/BlockquotesTests.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.ui.m3 2 | 3 | import androidx.compose.runtime.Composable 4 | import com.mikepenz.markdown.ui.annotation.DarkLightPreview 5 | import com.mikepenz.markdown.ui.m3.util.TestMarkdown 6 | 7 | /** 8 | * Covers blockquote behavior as documented 9 | * https://www.markdownguide.org/basic-syntax/#blockquotes-1 10 | */ 11 | class BlockquotesTests { 12 | @DarkLightPreview 13 | @Composable 14 | fun BlockquoteTest() = TestMarkdown( 15 | """ 16 | > Dorothy followed her through many of the beautiful rooms in her castle. 17 | """.trimIndent() 18 | ) 19 | 20 | @DarkLightPreview 21 | @Composable 22 | fun BlockquoteWithMultipleLinesTest() = TestMarkdown( 23 | """ 24 | > first line 25 | > 26 | > third line 27 | > fourth line 28 | """.trimIndent() 29 | ) 30 | 31 | @DarkLightPreview 32 | @Composable 33 | fun BlockquoteWithMultipleParagraphsTest() = TestMarkdown( 34 | """ 35 | > Dorothy followed her through many of the beautiful rooms in her castle. 36 | > 37 | > The Witch bade her clean the pots and kettles and sweep the floor and keep the fire fed with wood. 38 | """.trimIndent() 39 | ) 40 | 41 | @DarkLightPreview 42 | @Composable 43 | fun NestedBlockquoteTest() = TestMarkdown( 44 | """ 45 | > Dorothy followed her through many of the beautiful rooms in her castle. 46 | > 47 | >> The Witch bade her clean the pots and kettles and sweep the floor and keep the fire fed with wood. 48 | > 49 | > Dorothy followed her through many of the beautiful rooms in her castle. 50 | """.trimIndent() 51 | ) 52 | 53 | @DarkLightPreview 54 | @Composable 55 | fun BlockquoteWithOtherElementsTest() = TestMarkdown( 56 | """ 57 | > #### The quarterly results look great! 58 | > 59 | > - Revenue was off the chart. 60 | > - Profits were higher than ever. 61 | > 62 | > *Everything* is going according to **plan**. 63 | """.trimIndent() 64 | ) 65 | } -------------------------------------------------------------------------------- /multiplatform-markdown-renderer-m2/src/commonMain/kotlin/com/mikepenz/markdown/m2/MarkdownTypography.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.m2 2 | 3 | import androidx.compose.material.MaterialTheme 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.ui.text.SpanStyle 6 | import androidx.compose.ui.text.TextLinkStyles 7 | import androidx.compose.ui.text.TextStyle 8 | import androidx.compose.ui.text.font.FontFamily 9 | import androidx.compose.ui.text.font.FontStyle 10 | import androidx.compose.ui.text.font.FontWeight 11 | import androidx.compose.ui.text.style.TextDecoration 12 | import com.mikepenz.markdown.model.DefaultMarkdownTypography 13 | import com.mikepenz.markdown.model.MarkdownTypography 14 | 15 | @Composable 16 | fun markdownTypography( 17 | h1: TextStyle = MaterialTheme.typography.h1, 18 | h2: TextStyle = MaterialTheme.typography.h2, 19 | h3: TextStyle = MaterialTheme.typography.h3, 20 | h4: TextStyle = MaterialTheme.typography.h4, 21 | h5: TextStyle = MaterialTheme.typography.h5, 22 | h6: TextStyle = MaterialTheme.typography.h6, 23 | text: TextStyle = MaterialTheme.typography.body1, 24 | code: TextStyle = MaterialTheme.typography.body2.copy(fontFamily = FontFamily.Monospace), 25 | inlineCode: TextStyle = text.copy(fontFamily = FontFamily.Monospace), 26 | quote: TextStyle = MaterialTheme.typography.body2.plus(SpanStyle(fontStyle = FontStyle.Italic)), 27 | paragraph: TextStyle = MaterialTheme.typography.body1, 28 | ordered: TextStyle = MaterialTheme.typography.body1, 29 | bullet: TextStyle = MaterialTheme.typography.body1, 30 | list: TextStyle = MaterialTheme.typography.body1, 31 | textLink: TextLinkStyles = TextLinkStyles( 32 | style = MaterialTheme.typography.body1.copy( 33 | fontWeight = FontWeight.Bold, textDecoration = TextDecoration.Underline 34 | ).toSpanStyle() 35 | ), 36 | table: TextStyle = text, 37 | ): MarkdownTypography = DefaultMarkdownTypography( 38 | h1 = h1, 39 | h2 = h2, 40 | h3 = h3, 41 | h4 = h4, 42 | h5 = h5, 43 | h6 = h6, 44 | text = text, 45 | quote = quote, 46 | code = code, 47 | inlineCode = inlineCode, 48 | paragraph = paragraph, 49 | ordered = ordered, 50 | bullet = bullet, 51 | list = list, 52 | textLink = textLink, 53 | table = table, 54 | ) 55 | -------------------------------------------------------------------------------- /multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/model/MarkdownPadding.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.model 2 | 3 | import androidx.compose.foundation.layout.PaddingValues 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.runtime.Immutable 6 | import androidx.compose.ui.unit.Dp 7 | import androidx.compose.ui.unit.dp 8 | 9 | @Immutable 10 | interface MarkdownPadding { 11 | val block: Dp 12 | 13 | /** Padding top and bottom of a list (per layer) */ 14 | val list: Dp 15 | 16 | /** Padding top of a list item */ 17 | val listItemTop: Dp 18 | 19 | /** Padding bottom of a list item */ 20 | val listItemBottom: Dp 21 | 22 | /** The indent per list level */ 23 | val listIndent: Dp 24 | val codeBlock: PaddingValues 25 | val blockQuote: PaddingValues 26 | val blockQuoteText: PaddingValues 27 | val blockQuoteBar: PaddingValues.Absolute 28 | } 29 | 30 | @Immutable 31 | private data class DefaultMarkdownPadding( 32 | override val block: Dp, 33 | override val list: Dp, 34 | override val listItemTop: Dp, 35 | override val listItemBottom: Dp, 36 | override val listIndent: Dp, 37 | override val codeBlock: PaddingValues, 38 | override val blockQuote: PaddingValues, 39 | override val blockQuoteText: PaddingValues, 40 | override val blockQuoteBar: PaddingValues.Absolute, 41 | ) : MarkdownPadding 42 | 43 | @Composable 44 | fun markdownPadding( 45 | block: Dp = 2.dp, 46 | list: Dp = 4.dp, 47 | listItemTop: Dp = 4.dp, 48 | listItemBottom: Dp = 4.dp, 49 | /** Deprecated, please use `listIndent` instead */ 50 | indentList: Dp? = null, 51 | listIndent: Dp = 8.dp, 52 | codeBlock: PaddingValues = PaddingValues(8.dp), 53 | blockQuote: PaddingValues = PaddingValues(horizontal = 16.dp, vertical = 0.dp), 54 | blockQuoteText: PaddingValues = PaddingValues(vertical = 4.dp), 55 | blockQuoteBar: PaddingValues.Absolute = PaddingValues.Absolute(left = 4.dp, top = 2.dp, right = 4.dp, bottom = 2.dp), 56 | ): MarkdownPadding = DefaultMarkdownPadding( 57 | block = block, 58 | list = list, 59 | listItemTop = listItemTop, 60 | listItemBottom = listItemBottom, 61 | listIndent = indentList ?: listIndent, 62 | codeBlock = codeBlock, 63 | blockQuote = blockQuote, 64 | blockQuoteText = blockQuoteText, 65 | blockQuoteBar = blockQuoteBar, 66 | ) 67 | -------------------------------------------------------------------------------- /multiplatform-markdown-renderer-m3/src/commonMain/kotlin/com/mikepenz/markdown/m3/MarkdownTypography.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.m3 2 | 3 | import androidx.compose.material3.MaterialTheme 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.ui.text.SpanStyle 6 | import androidx.compose.ui.text.TextLinkStyles 7 | import androidx.compose.ui.text.TextStyle 8 | import androidx.compose.ui.text.font.FontFamily 9 | import androidx.compose.ui.text.font.FontStyle 10 | import androidx.compose.ui.text.font.FontWeight 11 | import androidx.compose.ui.text.style.TextDecoration 12 | import com.mikepenz.markdown.model.DefaultMarkdownTypography 13 | import com.mikepenz.markdown.model.MarkdownTypography 14 | 15 | @Composable 16 | fun markdownTypography( 17 | h1: TextStyle = MaterialTheme.typography.displayLarge, 18 | h2: TextStyle = MaterialTheme.typography.displayMedium, 19 | h3: TextStyle = MaterialTheme.typography.displaySmall, 20 | h4: TextStyle = MaterialTheme.typography.headlineMedium, 21 | h5: TextStyle = MaterialTheme.typography.headlineSmall, 22 | h6: TextStyle = MaterialTheme.typography.titleLarge, 23 | text: TextStyle = MaterialTheme.typography.bodyLarge, 24 | code: TextStyle = MaterialTheme.typography.bodyMedium.copy(fontFamily = FontFamily.Monospace), 25 | inlineCode: TextStyle = text.copy(fontFamily = FontFamily.Monospace), 26 | quote: TextStyle = MaterialTheme.typography.bodyMedium.plus(SpanStyle(fontStyle = FontStyle.Italic)), 27 | paragraph: TextStyle = MaterialTheme.typography.bodyLarge, 28 | ordered: TextStyle = MaterialTheme.typography.bodyLarge, 29 | bullet: TextStyle = MaterialTheme.typography.bodyLarge, 30 | list: TextStyle = MaterialTheme.typography.bodyLarge, 31 | textLink: TextLinkStyles = TextLinkStyles( 32 | style = MaterialTheme.typography.bodyLarge.copy( 33 | fontWeight = FontWeight.Bold, textDecoration = TextDecoration.Underline 34 | ).toSpanStyle() 35 | ), 36 | table: TextStyle = text, 37 | ): MarkdownTypography = DefaultMarkdownTypography( 38 | h1 = h1, 39 | h2 = h2, 40 | h3 = h3, 41 | h4 = h4, 42 | h5 = h5, 43 | h6 = h6, 44 | text = text, 45 | quote = quote, 46 | code = code, 47 | inlineCode = inlineCode, 48 | paragraph = paragraph, 49 | ordered = ordered, 50 | bullet = bullet, 51 | list = list, 52 | textLink = textLink, 53 | table = table, 54 | ) 55 | -------------------------------------------------------------------------------- /multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/compose/elements/MarkdownCodeTopBar.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.compose.elements 2 | 3 | import androidx.compose.foundation.clickable 4 | import androidx.compose.foundation.layout.Arrangement 5 | import androidx.compose.foundation.layout.Box 6 | import androidx.compose.foundation.layout.Row 7 | import androidx.compose.foundation.layout.fillMaxWidth 8 | import androidx.compose.foundation.layout.padding 9 | import androidx.compose.foundation.layout.size 10 | import androidx.compose.foundation.shape.CircleShape 11 | import androidx.compose.runtime.Composable 12 | import androidx.compose.ui.Alignment 13 | import androidx.compose.ui.Modifier 14 | import androidx.compose.ui.draw.clip 15 | import androidx.compose.ui.platform.LocalClipboardManager 16 | import androidx.compose.ui.text.AnnotatedString 17 | import androidx.compose.ui.text.font.FontFamily 18 | import androidx.compose.ui.unit.dp 19 | import androidx.compose.ui.unit.sp 20 | import com.mikepenz.markdown.compose.LocalMarkdownColors 21 | import com.mikepenz.markdown.compose.elements.material.MarkdownBasicText 22 | 23 | @Composable 24 | internal fun MarkdownCodeTopBar( 25 | language: String?, 26 | code: String, 27 | modifier: Modifier = Modifier, 28 | ) { 29 | @Suppress("DEPRECATION") val clipboardManager = LocalClipboardManager.current 30 | val textColor = LocalMarkdownColors.current.text 31 | 32 | Row( 33 | modifier = modifier 34 | .fillMaxWidth() 35 | .padding(horizontal = 8.dp, vertical = 4.dp), 36 | horizontalArrangement = Arrangement.SpaceBetween, 37 | verticalAlignment = Alignment.CenterVertically 38 | ) { 39 | MarkdownBasicText( 40 | text = language?.uppercase() ?: "CODE", 41 | style = androidx.compose.ui.text.TextStyle( 42 | fontSize = 10.sp, 43 | fontFamily = FontFamily.Monospace, 44 | color = textColor.copy(alpha = 0.6f) 45 | ) 46 | ) 47 | 48 | Box( 49 | modifier = Modifier 50 | .size(24.dp) 51 | .clip(CircleShape) 52 | .clickable { 53 | clipboardManager.setText(AnnotatedString(code)) 54 | }, 55 | contentAlignment = Alignment.Center 56 | ) { 57 | // Simple copy icon representation using text for now 58 | MarkdownBasicText( 59 | text = "⧉", 60 | style = androidx.compose.ui.text.TextStyle( 61 | fontSize = 14.sp, 62 | color = textColor.copy(alpha = 0.6f) 63 | ) 64 | ) 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/model/ImageTransformer.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.model 2 | 3 | import androidx.compose.foundation.layout.fillMaxWidth 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.runtime.Immutable 6 | import androidx.compose.ui.Alignment 7 | import androidx.compose.ui.Modifier 8 | import androidx.compose.ui.geometry.Size 9 | import androidx.compose.ui.geometry.isUnspecified 10 | import androidx.compose.ui.graphics.ColorFilter 11 | import androidx.compose.ui.graphics.DefaultAlpha 12 | import androidx.compose.ui.graphics.painter.Painter 13 | import androidx.compose.ui.layout.ContentScale 14 | import androidx.compose.ui.text.PlaceholderVerticalAlign 15 | import androidx.compose.ui.unit.Density 16 | 17 | interface ImageTransformer { 18 | /** 19 | * Will retrieve the [ImageData] from an image link/url 20 | */ 21 | @Composable 22 | fun transform(link: String): ImageData? 23 | 24 | /** 25 | * Returns the detected intrinsic size of the painter 26 | */ 27 | @Composable 28 | fun intrinsicSize(painter: Painter): Size { 29 | return painter.intrinsicSize 30 | } 31 | 32 | /** 33 | * The expected placeholderSize. Note: The same size is shared for all inline images within a single MarkdownText item. 34 | */ 35 | fun placeholderConfig(density: Density, containerSize: Size, intrinsicImageSize: Size): PlaceholderConfig { 36 | return PlaceholderConfig(with(density) { 37 | if (containerSize.isUnspecified) { 38 | Size(180f, 180f) 39 | } else if (intrinsicImageSize.isUnspecified) { 40 | Size(containerSize.width.toSp().value, 180f) 41 | } else { 42 | val width = minOf(intrinsicImageSize.width, containerSize.width) 43 | val height = if (intrinsicImageSize.width < containerSize.width) { 44 | intrinsicImageSize.height 45 | } else { 46 | (intrinsicImageSize.height * containerSize.width) / intrinsicImageSize.width 47 | } 48 | Size(width.toSp().value, height.toSp().value) 49 | } 50 | }) 51 | } 52 | } 53 | 54 | @Immutable 55 | data class PlaceholderConfig( 56 | val size: Size, 57 | val verticalAlign: PlaceholderVerticalAlign = PlaceholderVerticalAlign.Bottom, 58 | ) 59 | 60 | @Immutable 61 | data class ImageData( 62 | val painter: Painter, 63 | val modifier: Modifier = Modifier.fillMaxWidth(), 64 | val contentDescription: String? = "Image", 65 | val alignment: Alignment = Alignment.CenterStart, 66 | val contentScale: ContentScale = ContentScale.Fit, 67 | val alpha: Float = DefaultAlpha, 68 | val colorFilter: ColorFilter? = null, 69 | ) 70 | -------------------------------------------------------------------------------- /multiplatform-markdown-renderer-code/api/multiplatform-markdown-renderer-code.klib.api: -------------------------------------------------------------------------------- 1 | // Klib ABI Dump 2 | // Targets: [iosArm64, iosSimulatorArm64, iosX64, js, macosArm64, macosX64, wasmJs] 3 | // Rendering settings: 4 | // - Signature version: 2 5 | // - Show manifest properties: true 6 | // - Show declarations: true 7 | 8 | // Library unique name: 9 | final val com.mikepenz.markdown.compose.elements/highlightedCodeBlock // com.mikepenz.markdown.compose.elements/highlightedCodeBlock|{}highlightedCodeBlock[0] 10 | final fun (): kotlin/Function3 // com.mikepenz.markdown.compose.elements/highlightedCodeBlock.|(){}[0] 11 | final val com.mikepenz.markdown.compose.elements/highlightedCodeFence // com.mikepenz.markdown.compose.elements/highlightedCodeFence|{}highlightedCodeFence[0] 12 | final fun (): kotlin/Function3 // com.mikepenz.markdown.compose.elements/highlightedCodeFence.|(){}[0] 13 | 14 | final fun com.mikepenz.markdown.compose.elements/MarkdownHighlightedCode(kotlin/String, kotlin/String?, androidx.compose.ui.text/TextStyle?, dev.snipme.highlights/Highlights.Builder?, kotlin/Boolean, androidx.compose.runtime/Composer?, kotlin/Int, kotlin/Int) // com.mikepenz.markdown.compose.elements/MarkdownHighlightedCode|MarkdownHighlightedCode(kotlin.String;kotlin.String?;androidx.compose.ui.text.TextStyle?;dev.snipme.highlights.Highlights.Builder?;kotlin.Boolean;androidx.compose.runtime.Composer?;kotlin.Int;kotlin.Int){}[0] 15 | final fun com.mikepenz.markdown.compose.elements/MarkdownHighlightedCodeBlock(kotlin/String, org.intellij.markdown.ast/ASTNode, androidx.compose.ui.text/TextStyle?, dev.snipme.highlights/Highlights.Builder?, kotlin/Boolean, androidx.compose.runtime/Composer?, kotlin/Int, kotlin/Int) // com.mikepenz.markdown.compose.elements/MarkdownHighlightedCodeBlock|MarkdownHighlightedCodeBlock(kotlin.String;org.intellij.markdown.ast.ASTNode;androidx.compose.ui.text.TextStyle?;dev.snipme.highlights.Highlights.Builder?;kotlin.Boolean;androidx.compose.runtime.Composer?;kotlin.Int;kotlin.Int){}[0] 16 | final fun com.mikepenz.markdown.compose.elements/MarkdownHighlightedCodeFence(kotlin/String, org.intellij.markdown.ast/ASTNode, androidx.compose.ui.text/TextStyle?, dev.snipme.highlights/Highlights.Builder?, kotlin/Boolean, androidx.compose.runtime/Composer?, kotlin/Int, kotlin/Int) // com.mikepenz.markdown.compose.elements/MarkdownHighlightedCodeFence|MarkdownHighlightedCodeFence(kotlin.String;org.intellij.markdown.ast.ASTNode;androidx.compose.ui.text.TextStyle?;dev.snipme.highlights.Highlights.Builder?;kotlin.Boolean;androidx.compose.runtime.Composer?;kotlin.Int;kotlin.Int){}[0] 17 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH= 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /sample/shared/src/commonMain/kotlin/com/mikepenz/markdown/sample/icon/Github.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.sample.icon 2 | 3 | import androidx.compose.ui.graphics.Color 4 | import androidx.compose.ui.graphics.PathFillType 5 | import androidx.compose.ui.graphics.SolidColor 6 | import androidx.compose.ui.graphics.StrokeCap 7 | import androidx.compose.ui.graphics.StrokeJoin 8 | import androidx.compose.ui.graphics.vector.ImageVector 9 | import androidx.compose.ui.graphics.vector.path 10 | import androidx.compose.ui.unit.dp 11 | 12 | @Suppress("ObjectPropertyName") 13 | private var _Github: ImageVector? = null 14 | 15 | val Github: ImageVector 16 | get() { 17 | if (_Github != null) { 18 | return _Github!! 19 | } 20 | _Github = ImageVector.Builder( 21 | name = "Github", 22 | defaultWidth = 24.dp, 23 | defaultHeight = 24.dp, 24 | viewportWidth = 24f, 25 | viewportHeight = 24f 26 | ).apply { 27 | path( 28 | fill = SolidColor(Color(0xFF000000)), 29 | fillAlpha = 1.0f, 30 | stroke = null, 31 | strokeAlpha = 1.0f, 32 | strokeLineWidth = 1.0f, 33 | strokeLineCap = StrokeCap.Butt, 34 | strokeLineJoin = StrokeJoin.Miter, 35 | strokeLineMiter = 1.0f, 36 | pathFillType = PathFillType.NonZero 37 | ) { 38 | moveTo(12f, 2f) 39 | arcTo(10f, 10f, 0f, isMoreThanHalf = false, isPositiveArc = false, 2f, 12f) 40 | curveTo(2f, 16.42f, 4.87f, 20.17f, 8.84f, 21.5f) 41 | curveTo(9.34f, 21.58f, 9.5f, 21.27f, 9.5f, 21f) 42 | curveTo(9.5f, 20.77f, 9.5f, 20.14f, 9.5f, 19.31f) 43 | curveTo(6.73f, 19.91f, 6.14f, 17.97f, 6.14f, 17.97f) 44 | curveTo(5.68f, 16.81f, 5.03f, 16.5f, 5.03f, 16.5f) 45 | curveTo(4.12f, 15.88f, 5.1f, 15.9f, 5.1f, 15.9f) 46 | curveTo(6.1f, 15.97f, 6.63f, 16.93f, 6.63f, 16.93f) 47 | curveTo(7.5f, 18.45f, 8.97f, 18f, 9.54f, 17.76f) 48 | curveTo(9.63f, 17.11f, 9.89f, 16.67f, 10.17f, 16.42f) 49 | curveTo(7.95f, 16.17f, 5.62f, 15.31f, 5.62f, 11.5f) 50 | curveTo(5.62f, 10.39f, 6f, 9.5f, 6.65f, 8.79f) 51 | curveTo(6.55f, 8.54f, 6.2f, 7.5f, 6.75f, 6.15f) 52 | curveTo(6.75f, 6.15f, 7.59f, 5.88f, 9.5f, 7.17f) 53 | curveTo(10.29f, 6.95f, 11.15f, 6.84f, 12f, 6.84f) 54 | curveTo(12.85f, 6.84f, 13.71f, 6.95f, 14.5f, 7.17f) 55 | curveTo(16.41f, 5.88f, 17.25f, 6.15f, 17.25f, 6.15f) 56 | curveTo(17.8f, 7.5f, 17.45f, 8.54f, 17.35f, 8.79f) 57 | curveTo(18f, 9.5f, 18.38f, 10.39f, 18.38f, 11.5f) 58 | curveTo(18.38f, 15.32f, 16.04f, 16.16f, 13.81f, 16.41f) 59 | curveTo(14.17f, 16.72f, 14.5f, 17.33f, 14.5f, 18.26f) 60 | curveTo(14.5f, 19.6f, 14.5f, 20.68f, 14.5f, 21f) 61 | curveTo(14.5f, 21.27f, 14.66f, 21.59f, 15.17f, 21.5f) 62 | curveTo(19.14f, 20.16f, 22f, 16.42f, 22f, 12f) 63 | arcTo(10f, 10f, 0f, isMoreThanHalf = false, isPositiveArc = false, 12f, 2f) 64 | close() 65 | } 66 | }.build() 67 | return _Github!! 68 | } 69 | 70 | -------------------------------------------------------------------------------- /sample/android/src/screenshotTest/kotlin/com/mikepenz/markdown/ui/m2/ElementsInListsTests.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.ui.m2 2 | 3 | import androidx.compose.runtime.Composable 4 | import com.mikepenz.markdown.ui.annotation.DarkLightPreview 5 | import com.mikepenz.markdown.ui.m2.util.TestMarkdown 6 | 7 | /** 8 | * Covers elements in lists behavior as documented 9 | * https://www.markdownguide.org/basic-syntax/#adding-elements-in-lists 10 | */ 11 | class ElementsInListsTests { 12 | @DarkLightPreview 13 | @Composable 14 | fun ParagraphInListTest() = TestMarkdown( 15 | """ 16 | * This is the first list item. 17 | * Here's the second list item. 18 | 19 | I need to add another paragraph below the second list item. 20 | 21 | * And here's the third list item. 22 | """.trimIndent() 23 | ) 24 | 25 | @DarkLightPreview 26 | @Composable 27 | fun BlockQuoteInListTest() = TestMarkdown( 28 | """ 29 | * This is the first list item. 30 | * Here's the second list item. 31 | 32 | > A blockquote would look great below the second list item. 33 | 34 | * And here's the third list item. 35 | """.trimIndent() 36 | ) 37 | 38 | @DarkLightPreview 39 | @Composable 40 | fun CodeBlockInListTest() = TestMarkdown( 41 | """ 42 | 1. Open the file. 43 | 2. Find the following code block on line 21: 44 | 45 | 46 | 47 | 48 | 49 | 3. Update the title to match the name of your website. 50 | """.trimIndent() 51 | ) 52 | 53 | @DarkLightPreview 54 | @Composable 55 | fun ImageInListTest() = TestMarkdown( 56 | """ 57 | 1. Open the file containing the Linux mascot. 58 | 2. Marvel at its beauty. 59 | 60 | ![Image](https://avatars.githubusercontent.com/u/1476232?u=3db0792ad9649618b182c9e24170c9be8ad9e32f&v=4&size=80) 61 | 62 | 3. Close the file. 63 | """.trimIndent() 64 | ) 65 | 66 | @DarkLightPreview 67 | @Composable 68 | fun UnorderedListInListTest() = TestMarkdown( 69 | """ 70 | 1. First item 71 | 2. Second item 72 | 3. Third item 73 | - Indented item 74 | - Indented item 75 | 4. Fourth item 76 | """.trimIndent() 77 | ) 78 | 79 | @DarkLightPreview 80 | @Composable 81 | fun CheckBoxInListTest() = TestMarkdown( 82 | """ 83 | This is an ordered list with task list items: 84 | 1. [ ] foo 85 | 2. [x] bar 86 | 87 | This is an unordered list with task list items: 88 | - [ ] foo 89 | - [x] bar 90 | """.trimIndent() 91 | ) 92 | 93 | /** 94 | * Initial number dictates number of remaining list items. 95 | * https://spec.commonmark.org/0.31.2/#start-number 96 | */ 97 | @DarkLightPreview 98 | @Composable 99 | fun OrderedListRandomStartTest() = TestMarkdown( 100 | """ 101 | 7. First item 102 | 2. Second item 103 | 3. Third item 104 | - Indented item 105 | - Indented item 106 | 4. Fourth item 107 | """.trimIndent() 108 | ) 109 | } -------------------------------------------------------------------------------- /sample/android/src/screenshotTest/kotlin/com/mikepenz/markdown/ui/m3/ElementsInListsTests.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.ui.m3 2 | 3 | import androidx.compose.runtime.Composable 4 | import com.mikepenz.markdown.ui.annotation.DarkLightPreview 5 | import com.mikepenz.markdown.ui.m3.util.TestMarkdown 6 | 7 | /** 8 | * Covers elements in lists behavior as documented 9 | * https://www.markdownguide.org/basic-syntax/#adding-elements-in-lists 10 | */ 11 | class ElementsInListsTests { 12 | @DarkLightPreview 13 | @Composable 14 | fun ParagraphInListTest() = TestMarkdown( 15 | """ 16 | * This is the first list item. 17 | * Here's the second list item. 18 | 19 | I need to add another paragraph below the second list item. 20 | 21 | * And here's the third list item. 22 | """.trimIndent() 23 | ) 24 | 25 | @DarkLightPreview 26 | @Composable 27 | fun BlockQuoteInListTest() = TestMarkdown( 28 | """ 29 | * This is the first list item. 30 | * Here's the second list item. 31 | 32 | > A blockquote would look great below the second list item. 33 | 34 | * And here's the third list item. 35 | """.trimIndent() 36 | ) 37 | 38 | @DarkLightPreview 39 | @Composable 40 | fun CodeBlockInListTest() = TestMarkdown( 41 | """ 42 | 1. Open the file. 43 | 2. Find the following code block on line 21: 44 | 45 | 46 | 47 | 48 | 49 | 3. Update the title to match the name of your website. 50 | """.trimIndent() 51 | ) 52 | 53 | @DarkLightPreview 54 | @Composable 55 | fun ImageInListTest() = TestMarkdown( 56 | """ 57 | 1. Open the file containing the Linux mascot. 58 | 2. Marvel at its beauty. 59 | 60 | ![Image](https://avatars.githubusercontent.com/u/1476232?u=3db0792ad9649618b182c9e24170c9be8ad9e32f&v=4&size=80) 61 | 62 | 3. Close the file. 63 | """.trimIndent() 64 | ) 65 | 66 | @DarkLightPreview 67 | @Composable 68 | fun UnorderedListInListTest() = TestMarkdown( 69 | """ 70 | 1. First item 71 | 2. Second item 72 | 3. Third item 73 | - Indented item 74 | - Indented item 75 | 4. Fourth item 76 | """.trimIndent() 77 | ) 78 | 79 | @DarkLightPreview 80 | @Composable 81 | fun CheckBoxInListTest() = TestMarkdown( 82 | """ 83 | This is an ordered list with task list items: 84 | 1. [ ] foo 85 | 2. [x] bar 86 | 87 | This is an unordered list with task list items: 88 | - [ ] foo 89 | - [x] bar 90 | """.trimIndent() 91 | ) 92 | 93 | /** 94 | * Initial number dictates number of remaining list items. 95 | * https://spec.commonmark.org/0.31.2/#start-number 96 | */ 97 | @DarkLightPreview 98 | @Composable 99 | fun OrderedListRandomStartTest() = com.mikepenz.markdown.ui.m2.util.TestMarkdown( 100 | """ 101 | 7. First item 102 | 2. Second item 103 | 3. Third item 104 | - Indented item 105 | - Indented item 106 | 4. Fourth item 107 | """.trimIndent() 108 | ) 109 | } -------------------------------------------------------------------------------- /multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/annotator/AnnotatorSettings.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.annotator 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.Immutable 5 | import androidx.compose.runtime.Stable 6 | import androidx.compose.ui.platform.LocalUriHandler 7 | import androidx.compose.ui.platform.UriHandler 8 | import androidx.compose.ui.text.LinkAnnotation 9 | import androidx.compose.ui.text.LinkInteractionListener 10 | import androidx.compose.ui.text.SpanStyle 11 | import androidx.compose.ui.text.TextLinkStyles 12 | import com.mikepenz.markdown.compose.LocalMarkdownAnnotator 13 | import com.mikepenz.markdown.compose.LocalMarkdownTypography 14 | import com.mikepenz.markdown.compose.LocalReferenceLinkHandler 15 | import com.mikepenz.markdown.model.MarkdownAnnotator 16 | import com.mikepenz.markdown.model.ReferenceLinkHandler 17 | import com.mikepenz.markdown.utils.codeSpanStyle 18 | 19 | @Stable 20 | interface AnnotatorSettings { 21 | /** Represents the [TextLinkStyles] applied onto links within this annotated string */ 22 | val linkTextSpanStyle: TextLinkStyles 23 | 24 | /** Represents the [SpanStyle] applied onto code inline blocks within this annotated string */ 25 | val codeSpanStyle: SpanStyle 26 | 27 | /** Provides custom interception logic for building this annotated string */ 28 | val annotator: MarkdownAnnotator 29 | 30 | /** Represents the [ReferenceLinkHandler] used to store and find links when clicks on links occur. */ 31 | val referenceLinkHandler: ReferenceLinkHandler? 32 | 33 | /** Represents the [LinkInteractionListener] used for links in this annotated string */ 34 | val linkInteractionListener: LinkInteractionListener? 35 | } 36 | 37 | @Immutable 38 | class DefaultAnnotatorSettings( 39 | override val linkTextSpanStyle: TextLinkStyles, 40 | override val codeSpanStyle: SpanStyle, 41 | override val annotator: MarkdownAnnotator, 42 | override val referenceLinkHandler: ReferenceLinkHandler? = null, 43 | override val linkInteractionListener: LinkInteractionListener? = null, 44 | ) : AnnotatorSettings 45 | 46 | @Composable 47 | fun annotatorSettings( 48 | linkTextSpanStyle: TextLinkStyles = LocalMarkdownTypography.current.textLink, 49 | codeSpanStyle: SpanStyle = LocalMarkdownTypography.current.codeSpanStyle, 50 | annotator: MarkdownAnnotator = LocalMarkdownAnnotator.current, 51 | referenceLinkHandler: ReferenceLinkHandler? = LocalReferenceLinkHandler.current, 52 | uriHandler: UriHandler = LocalUriHandler.current, 53 | linkInteractionListener: LinkInteractionListener? = LinkInteractionListener { link -> 54 | val annotationUrl = (link as? LinkAnnotation.Url)?.url 55 | if (annotationUrl != null) { 56 | val foundReference = referenceLinkHandler?.find(annotationUrl)?.takeIf { it.isNotEmpty() } ?: annotationUrl 57 | // wait for finger up to navigate to the link 58 | try { 59 | uriHandler.openUri(foundReference) 60 | } catch (t: Throwable) { 61 | println("Could not open the provided url: $foundReference // ${t.message}") 62 | } 63 | } 64 | }, 65 | ): AnnotatorSettings = DefaultAnnotatorSettings( 66 | linkTextSpanStyle = linkTextSpanStyle, 67 | codeSpanStyle = codeSpanStyle, 68 | annotator = annotator, 69 | referenceLinkHandler = referenceLinkHandler, 70 | linkInteractionListener = linkInteractionListener, 71 | ) -------------------------------------------------------------------------------- /sample/shared/src/commonMain/kotlin/com/mikepenz/markdown/sample/icon/OpenSourceInitiative.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.sample.icon 2 | 3 | import androidx.compose.ui.graphics.Color 4 | import androidx.compose.ui.graphics.PathFillType 5 | import androidx.compose.ui.graphics.SolidColor 6 | import androidx.compose.ui.graphics.StrokeCap 7 | import androidx.compose.ui.graphics.StrokeJoin 8 | import androidx.compose.ui.graphics.vector.ImageVector 9 | import androidx.compose.ui.graphics.vector.path 10 | import androidx.compose.ui.unit.dp 11 | 12 | @Suppress("ObjectPropertyName") 13 | private var _OpenSourceInitiative: ImageVector? = null 14 | 15 | val OpenSourceInitiative: ImageVector 16 | get() { 17 | if (_OpenSourceInitiative != null) { 18 | return _OpenSourceInitiative!! 19 | } 20 | _OpenSourceInitiative = ImageVector.Builder( 21 | name = "OpenSourceInitiative", 22 | defaultWidth = 24.dp, 23 | defaultHeight = 24.dp, 24 | viewportWidth = 24f, 25 | viewportHeight = 24f 26 | ).apply { 27 | path( 28 | fill = SolidColor(Color(0xFF000000)), 29 | fillAlpha = 1.0f, 30 | stroke = null, 31 | strokeAlpha = 1.0f, 32 | strokeLineWidth = 1.0f, 33 | strokeLineCap = StrokeCap.Butt, 34 | strokeLineJoin = StrokeJoin.Miter, 35 | strokeLineMiter = 1.0f, 36 | pathFillType = PathFillType.NonZero 37 | ) { 38 | moveTo(15.41f, 22f) 39 | curveTo(15.35f, 22f, 15.28f, 22f, 15.22f, 22f) 40 | curveTo(15.1f, 21.95f, 15f, 21.85f, 14.96f, 21.73f) 41 | lineTo(12.74f, 15.93f) 42 | curveTo(12.65f, 15.69f, 12.77f, 15.42f, 13f, 15.32f) 43 | curveTo(13.71f, 15.06f, 14.28f, 14.5f, 14.58f, 13.83f) 44 | curveTo(15.22f, 12.4f, 14.58f, 10.73f, 13.15f, 10.09f) 45 | curveTo(11.72f, 9.45f, 10.05f, 10.09f, 9.41f, 11.5f) 46 | curveTo(9.11f, 12.21f, 9.09f, 13f, 9.36f, 13.69f) 47 | curveTo(9.66f, 14.43f, 10.25f, 15f, 11f, 15.28f) 48 | curveTo(11.24f, 15.37f, 11.37f, 15.64f, 11.28f, 15.89f) 49 | lineTo(9f, 21.69f) 50 | curveTo(8.96f, 21.81f, 8.87f, 21.91f, 8.75f, 21.96f) 51 | curveTo(8.63f, 22f, 8.5f, 22f, 8.39f, 21.96f) 52 | curveTo(3.24f, 19.97f, 0.67f, 14.18f, 2.66f, 9.03f) 53 | curveTo(4.65f, 3.88f, 10.44f, 1.31f, 15.59f, 3.3f) 54 | curveTo(18.06f, 4.26f, 20.05f, 6.15f, 21.13f, 8.57f) 55 | curveTo(22.22f, 11f, 22.29f, 13.75f, 21.33f, 16.22f) 56 | curveTo(20.32f, 18.88f, 18.23f, 21f, 15.58f, 22f) 57 | curveTo(15.5f, 22f, 15.47f, 22f, 15.41f, 22f) 58 | moveTo(12f, 3.59f) 59 | curveTo(7.03f, 3.46f, 2.9f, 7.39f, 2.77f, 12.36f) 60 | curveTo(2.68f, 16.08f, 4.88f, 19.47f, 8.32f, 20.9f) 61 | lineTo(10.21f, 16f) 62 | curveTo(8.38f, 15f, 7.69f, 12.72f, 8.68f, 10.89f) 63 | curveTo(9.67f, 9.06f, 11.96f, 8.38f, 13.79f, 9.36f) 64 | curveTo(15.62f, 10.35f, 16.31f, 12.64f, 15.32f, 14.47f) 65 | curveTo(14.97f, 15.12f, 14.44f, 15.65f, 13.79f, 16f) 66 | lineTo(15.68f, 20.93f) 67 | curveTo(17.86f, 19.95f, 19.57f, 18.16f, 20.44f, 15.93f) 68 | curveTo(22.28f, 11.31f, 20.04f, 6.08f, 15.42f, 4.23f) 69 | curveTo(14.33f, 3.8f, 13.17f, 3.58f, 12f, 3.59f) 70 | close() 71 | } 72 | }.build() 73 | return _OpenSourceInitiative!! 74 | } 75 | -------------------------------------------------------------------------------- /multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/compose/elements/MarkdownBlockQuote.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.compose.elements 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.foundation.layout.Spacer 5 | import androidx.compose.foundation.layout.calculateStartPadding 6 | import androidx.compose.foundation.layout.height 7 | import androidx.compose.foundation.layout.padding 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.ui.Modifier 10 | import androidx.compose.ui.draw.drawBehind 11 | import androidx.compose.ui.geometry.Offset 12 | import androidx.compose.ui.graphics.isSpecified 13 | import androidx.compose.ui.platform.LocalDensity 14 | import androidx.compose.ui.text.TextStyle 15 | import androidx.compose.ui.unit.LayoutDirection 16 | import com.mikepenz.markdown.compose.LocalMarkdownColors 17 | import com.mikepenz.markdown.compose.LocalMarkdownComponents 18 | import com.mikepenz.markdown.compose.LocalMarkdownDimens 19 | import com.mikepenz.markdown.compose.LocalMarkdownPadding 20 | import com.mikepenz.markdown.compose.LocalMarkdownTypography 21 | import com.mikepenz.markdown.compose.MarkdownElement 22 | import org.intellij.markdown.MarkdownElementTypes 23 | import org.intellij.markdown.MarkdownTokenTypes.Companion.EOL 24 | import org.intellij.markdown.ast.ASTNode 25 | 26 | @Composable 27 | fun MarkdownBlockQuote( 28 | content: String, 29 | node: ASTNode, 30 | style: TextStyle = LocalMarkdownTypography.current.quote, 31 | ) { 32 | val blockQuoteColor = if (style.color.isSpecified) { 33 | style.color 34 | } else { 35 | LocalMarkdownColors.current.text 36 | } 37 | val blockQuoteThickness = LocalMarkdownDimens.current.blockQuoteThickness 38 | val blockQuote = LocalMarkdownPadding.current.blockQuote 39 | val blockQuoteText = LocalMarkdownPadding.current.blockQuoteText 40 | val blockQuoteBar = LocalMarkdownPadding.current.blockQuoteBar 41 | val markdownComponents = LocalMarkdownComponents.current 42 | 43 | Column( 44 | modifier = Modifier 45 | .drawBehind { 46 | drawLine( 47 | color = blockQuoteColor, 48 | strokeWidth = blockQuoteThickness.toPx(), 49 | start = Offset(blockQuoteBar.calculateStartPadding(LayoutDirection.Ltr).toPx(), blockQuoteBar.calculateTopPadding().toPx()), 50 | end = Offset(blockQuoteBar.calculateStartPadding(LayoutDirection.Ltr).toPx(), size.height - blockQuoteBar.calculateBottomPadding().toPx()) 51 | ) 52 | } 53 | .padding(blockQuote) 54 | ) { 55 | val blockQuoteLineHeightInDp = with(LocalDensity.current) { LocalMarkdownTypography.current.quote.fontSize.toDp() } 56 | var priorNestedQuote = false 57 | node.children.onEachIndexed { index, child -> 58 | if (child.type == MarkdownElementTypes.BLOCK_QUOTE) { 59 | // if block quote is nested, and comes after non block quote, add padding 60 | if (!priorNestedQuote && index != 0) Spacer(Modifier.height(blockQuoteText.calculateBottomPadding())) 61 | MarkdownBlockQuote(content = content, node = child, style = style) 62 | priorNestedQuote = true 63 | } else if (child.type == EOL) { 64 | Spacer(Modifier.height(blockQuoteLineHeightInDp)) 65 | } else { 66 | // if first item either completely, or after a nested quote, add top padding 67 | if (index == 0 || priorNestedQuote) Spacer(Modifier.height(blockQuoteText.calculateTopPadding())) 68 | priorNestedQuote = false 69 | MarkdownElement( 70 | node = child, 71 | components = markdownComponents, 72 | content = content, 73 | includeSpacer = false 74 | ) 75 | // if last item, add bottom padding 76 | if (index == node.children.lastIndex) Spacer(Modifier.height(blockQuoteText.calculateBottomPadding())) 77 | } 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /sample/shared/src/commonMain/kotlin/com/mikepenz/markdown/sample/MarkDownPage.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.sample 2 | 3 | import androidx.compose.foundation.isSystemInDarkTheme 4 | import androidx.compose.foundation.layout.Box 5 | import androidx.compose.foundation.layout.fillMaxSize 6 | import androidx.compose.foundation.layout.padding 7 | import androidx.compose.foundation.rememberScrollState 8 | import androidx.compose.foundation.text.selection.SelectionContainer 9 | import androidx.compose.foundation.verticalScroll 10 | import androidx.compose.material3.CircularProgressIndicator 11 | import androidx.compose.runtime.Composable 12 | import androidx.compose.runtime.remember 13 | import androidx.compose.ui.Alignment 14 | import androidx.compose.ui.Modifier 15 | import androidx.compose.ui.unit.dp 16 | import com.mikepenz.markdown.coil3.Coil3ImageTransformerImpl 17 | import com.mikepenz.markdown.compose.components.markdownComponents 18 | import com.mikepenz.markdown.compose.elements.MarkdownHighlightedCodeBlock 19 | import com.mikepenz.markdown.compose.elements.MarkdownHighlightedCodeFence 20 | import com.mikepenz.markdown.compose.extendedspans.ExtendedSpans 21 | import com.mikepenz.markdown.compose.extendedspans.RoundedCornerSpanPainter 22 | import com.mikepenz.markdown.compose.extendedspans.SquigglyUnderlineSpanPainter 23 | import com.mikepenz.markdown.compose.extendedspans.rememberSquigglyUnderlineAnimator 24 | import com.mikepenz.markdown.m3.Markdown 25 | import com.mikepenz.markdown.m3.elements.MarkdownCheckBox 26 | import com.mikepenz.markdown.model.markdownExtendedSpans 27 | import com.mikepenz.markdown.model.rememberMarkdownState 28 | import com.mikepenz.markdown.sample.shared.resources.Res 29 | import dev.snipme.highlights.Highlights 30 | import dev.snipme.highlights.model.SyntaxThemes 31 | import org.jetbrains.compose.resources.ExperimentalResourceApi 32 | 33 | @OptIn(ExperimentalResourceApi::class) 34 | @Composable 35 | internal fun MarkDownPage(modifier: Modifier = Modifier) { 36 | val isDarkTheme = isSystemInDarkTheme() 37 | val highlightsBuilder = remember(isDarkTheme) { 38 | Highlights.Builder().theme(SyntaxThemes.atom(darkMode = isDarkTheme)) 39 | } 40 | 41 | SelectionContainer { 42 | Markdown( 43 | markdownState = rememberMarkdownState { 44 | Res.readBytes("files/sample.md").decodeToString() 45 | }, 46 | components = markdownComponents( 47 | codeBlock = { 48 | MarkdownHighlightedCodeBlock( 49 | content = it.content, 50 | node = it.node, 51 | highlightsBuilder = highlightsBuilder, 52 | showHeader = true, 53 | ) 54 | }, 55 | codeFence = { 56 | MarkdownHighlightedCodeFence( 57 | content = it.content, 58 | node = it.node, 59 | highlightsBuilder = highlightsBuilder, 60 | showHeader = true, 61 | ) 62 | }, 63 | checkbox = { MarkdownCheckBox(it.content, it.node, it.typography.text) } 64 | ), 65 | imageTransformer = Coil3ImageTransformerImpl, 66 | extendedSpans = markdownExtendedSpans { 67 | val animator = rememberSquigglyUnderlineAnimator() 68 | remember { 69 | ExtendedSpans( 70 | RoundedCornerSpanPainter(), 71 | SquigglyUnderlineSpanPainter(animator = animator) 72 | ) 73 | } 74 | }, 75 | loading = { 76 | Box( 77 | modifier = modifier.fillMaxSize(), 78 | contentAlignment = Alignment.Center 79 | ) { 80 | CircularProgressIndicator() 81 | } 82 | }, 83 | modifier = modifier 84 | .fillMaxSize() 85 | .verticalScroll(rememberScrollState()) 86 | .padding(16.dp) 87 | ) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /sample/shared/src/commonMain/kotlin/com/mikepenz/markdown/sample/RecompositionPage.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.sample 2 | 3 | import androidx.compose.foundation.layout.Arrangement 4 | import androidx.compose.foundation.layout.Column 5 | import androidx.compose.foundation.layout.Row 6 | import androidx.compose.foundation.layout.fillMaxSize 7 | import androidx.compose.foundation.layout.safeContentPadding 8 | import androidx.compose.material3.Button 9 | import androidx.compose.material3.MaterialTheme 10 | import androidx.compose.material3.Switch 11 | import androidx.compose.material3.Text 12 | import androidx.compose.runtime.Composable 13 | import androidx.compose.runtime.DisposableEffect 14 | import androidx.compose.runtime.getValue 15 | import androidx.compose.runtime.mutableIntStateOf 16 | import androidx.compose.runtime.mutableStateOf 17 | import androidx.compose.runtime.remember 18 | import androidx.compose.runtime.setValue 19 | import androidx.compose.ui.Alignment 20 | import androidx.compose.ui.Modifier 21 | import androidx.compose.ui.unit.dp 22 | import com.mikepenz.markdown.m3.Markdown 23 | import com.mikepenz.markdown.m3.markdownColor 24 | import com.mikepenz.markdown.m3.markdownTypography 25 | import com.mikepenz.markdown.model.markdownAnimations 26 | import com.mikepenz.markdown.model.markdownDimens 27 | import com.mikepenz.markdown.model.markdownExtendedSpans 28 | import com.mikepenz.markdown.model.markdownInlineContent 29 | import com.mikepenz.markdown.model.markdownPadding 30 | import com.mikepenz.markdown.model.rememberMarkdownState 31 | import com.mikepenz.markdown.utils.MarkdownLogger 32 | 33 | @Composable 34 | fun RecompositionPage( 35 | modifier: Modifier = Modifier, 36 | ) { 37 | DisposableEffect(Unit) { 38 | MarkdownLogger.enabled = true 39 | onDispose { 40 | MarkdownLogger.enabled = false 41 | } 42 | } 43 | Column( 44 | verticalArrangement = Arrangement.spacedBy(16.dp), 45 | horizontalAlignment = Alignment.CenterHorizontally, 46 | modifier = modifier.fillMaxSize().safeContentPadding() 47 | ) { 48 | var retainState by remember { 49 | mutableStateOf(true) 50 | } 51 | var includeCounter by remember { 52 | mutableStateOf(false) 53 | } 54 | var counter by remember { 55 | mutableIntStateOf(0) 56 | } 57 | 58 | Row(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically) { 59 | Text("Include Counter:") 60 | Switch(includeCounter, onCheckedChange = { includeCounter = it }) 61 | } 62 | Row(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically) { 63 | Text("Retain State:") 64 | Switch(retainState, onCheckedChange = { retainState = it }) 65 | } 66 | Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { 67 | Button(onClick = { counter++ }) { 68 | Text("Increase") 69 | } 70 | 71 | Button(onClick = { counter-- }) { 72 | Text("Decrease") 73 | } 74 | } 75 | 76 | Text("Count: $counter", color = MaterialTheme.colorScheme.onBackground) 77 | 78 | val markdownState = rememberMarkdownState( 79 | if (includeCounter) counter else 0, 80 | retainState = retainState 81 | ) { 82 | if (includeCounter) { 83 | "## My markdown text $counter" 84 | } else { 85 | "## My markdown text" 86 | } 87 | } 88 | Markdown( 89 | markdownState = markdownState, 90 | dimens = markdownDimens(), 91 | colors = markdownColor(), 92 | padding = markdownPadding(), 93 | typography = markdownTypography(), 94 | inlineContent = markdownInlineContent(), 95 | animations = markdownAnimations(), 96 | extendedSpans = markdownExtendedSpans(), 97 | modifier = Modifier, 98 | ) 99 | } 100 | } -------------------------------------------------------------------------------- /multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/compose/ComposeLocal.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.compose 2 | 3 | import androidx.compose.runtime.compositionLocalOf 4 | import androidx.compose.runtime.staticCompositionLocalOf 5 | import com.mikepenz.markdown.compose.components.MarkdownComponents 6 | import com.mikepenz.markdown.compose.components.markdownComponents 7 | import com.mikepenz.markdown.model.BulletHandler 8 | import com.mikepenz.markdown.model.DefaultMarkdownAnnotator 9 | import com.mikepenz.markdown.model.DefaultMarkdownAnnotatorConfig 10 | import com.mikepenz.markdown.model.DefaultMarkdownExtendedSpans 11 | import com.mikepenz.markdown.model.DefaultMarkdownInlineContent 12 | import com.mikepenz.markdown.model.ImageTransformer 13 | import com.mikepenz.markdown.model.MarkdownAnimations 14 | import com.mikepenz.markdown.model.MarkdownAnnotator 15 | import com.mikepenz.markdown.model.MarkdownColors 16 | import com.mikepenz.markdown.model.MarkdownDimens 17 | import com.mikepenz.markdown.model.MarkdownExtendedSpans 18 | import com.mikepenz.markdown.model.MarkdownInlineContent 19 | import com.mikepenz.markdown.model.MarkdownPadding 20 | import com.mikepenz.markdown.model.MarkdownTypography 21 | import com.mikepenz.markdown.model.ReferenceLinkHandler 22 | 23 | /** 24 | * The CompositionLocal to provide functionality related to transforming the bullet of an ordered list 25 | */ 26 | val LocalBulletListHandler = staticCompositionLocalOf { 27 | return@staticCompositionLocalOf BulletHandler { _, _, _, _, _ -> "• " } 28 | } 29 | 30 | /** 31 | * The CompositionLocal to provide functionality related to transforming the bullet of an ordered list 32 | */ 33 | val LocalOrderedListHandler = staticCompositionLocalOf { 34 | return@staticCompositionLocalOf BulletHandler { _, _, index, listNumber, _ -> "${listNumber + index}. " } 35 | } 36 | 37 | /** 38 | * Local [ReferenceLinkHandler] provider 39 | */ 40 | val LocalReferenceLinkHandler = staticCompositionLocalOf { 41 | error("CompositionLocal ReferenceLinkHandler not present") 42 | } 43 | 44 | /** 45 | * Local [MarkdownColors] provider 46 | */ 47 | val LocalMarkdownColors = compositionLocalOf { 48 | error("No local MarkdownColors") 49 | } 50 | 51 | /** 52 | * Local [MarkdownTypography] provider 53 | */ 54 | val LocalMarkdownTypography = compositionLocalOf { 55 | error("No local MarkdownTypography") 56 | } 57 | 58 | /** 59 | * Local [MarkdownPadding] provider 60 | */ 61 | val LocalMarkdownPadding = staticCompositionLocalOf { 62 | error("No local Padding") 63 | } 64 | 65 | /** 66 | * Local [MarkdownDimens] provider 67 | */ 68 | val LocalMarkdownDimens = compositionLocalOf { 69 | error("No local MarkdownDimens") 70 | } 71 | 72 | /** 73 | * Local [ImageTransformer] provider 74 | */ 75 | val LocalImageTransformer = staticCompositionLocalOf { 76 | error("No local ImageTransformer") 77 | } 78 | 79 | /** 80 | * Local [MarkdownInlineContent] provider 81 | */ 82 | val LocalMarkdownInlineContent = staticCompositionLocalOf { 83 | return@staticCompositionLocalOf DefaultMarkdownInlineContent(mapOf()) 84 | } 85 | 86 | /** 87 | * Local [MarkdownAnnotator] provider 88 | */ 89 | val LocalMarkdownAnnotator = compositionLocalOf { 90 | return@compositionLocalOf DefaultMarkdownAnnotator(null, DefaultMarkdownAnnotatorConfig()) 91 | } 92 | 93 | /** 94 | * Local [MarkdownExtendedSpans] provider 95 | */ 96 | val LocalMarkdownExtendedSpans = compositionLocalOf { 97 | return@compositionLocalOf DefaultMarkdownExtendedSpans(null) 98 | } 99 | 100 | /** 101 | * Local [MarkdownComponents] provider 102 | */ 103 | val LocalMarkdownComponents = compositionLocalOf { 104 | return@compositionLocalOf markdownComponents() 105 | } 106 | 107 | /** 108 | * Local [MarkdownAnimations] provider 109 | */ 110 | val LocalMarkdownAnimations = compositionLocalOf { 111 | error("No local MarkdownAnimations") 112 | } -------------------------------------------------------------------------------- /multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/compose/MarkdownExtension.kt: -------------------------------------------------------------------------------- 1 | package com.mikepenz.markdown.compose 2 | 3 | import androidx.compose.foundation.layout.Spacer 4 | import androidx.compose.foundation.layout.height 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.runtime.remember 7 | import androidx.compose.ui.Modifier 8 | import com.mikepenz.markdown.compose.components.MarkdownComponentModel 9 | import com.mikepenz.markdown.compose.components.MarkdownComponents 10 | import org.intellij.markdown.MarkdownElementTypes.ATX_1 11 | import org.intellij.markdown.MarkdownElementTypes.ATX_2 12 | import org.intellij.markdown.MarkdownElementTypes.ATX_3 13 | import org.intellij.markdown.MarkdownElementTypes.ATX_4 14 | import org.intellij.markdown.MarkdownElementTypes.ATX_5 15 | import org.intellij.markdown.MarkdownElementTypes.ATX_6 16 | import org.intellij.markdown.MarkdownElementTypes.BLOCK_QUOTE 17 | import org.intellij.markdown.MarkdownElementTypes.CODE_BLOCK 18 | import org.intellij.markdown.MarkdownElementTypes.CODE_FENCE 19 | import org.intellij.markdown.MarkdownElementTypes.IMAGE 20 | import org.intellij.markdown.MarkdownElementTypes.ORDERED_LIST 21 | import org.intellij.markdown.MarkdownElementTypes.PARAGRAPH 22 | import org.intellij.markdown.MarkdownElementTypes.SETEXT_1 23 | import org.intellij.markdown.MarkdownElementTypes.SETEXT_2 24 | import org.intellij.markdown.MarkdownElementTypes.UNORDERED_LIST 25 | import org.intellij.markdown.MarkdownTokenTypes.Companion.EOL 26 | import org.intellij.markdown.MarkdownTokenTypes.Companion.HORIZONTAL_RULE 27 | import org.intellij.markdown.MarkdownTokenTypes.Companion.TEXT 28 | import org.intellij.markdown.ast.ASTNode 29 | import org.intellij.markdown.flavours.gfm.GFMElementTypes.TABLE 30 | 31 | /** 32 | * Handles the rendering of a markdown element based on its [ASTNode.type]. 33 | * 34 | * This function is responsible for determining the appropriate component to use for rendering 35 | * It does handle rendering of children recursively. 36 | * 37 | * @param node The ASTNode representing the markdown element. 38 | * @param components The [MarkdownComponents] instance containing the components to use. 39 | * @param content The original markdown content string. 40 | * @param includeSpacer Whether to include a spacer before rendering the element. 41 | */ 42 | @Composable 43 | fun MarkdownElement( 44 | node: ASTNode, 45 | components: MarkdownComponents, 46 | content: String, 47 | includeSpacer: Boolean = true, 48 | ) { 49 | val typography = LocalMarkdownTypography.current 50 | val model = remember(node, content, typography) { 51 | MarkdownComponentModel( 52 | content = content, 53 | node = node, 54 | typography = typography, 55 | ) 56 | } 57 | var handled = true 58 | if (includeSpacer) Spacer(Modifier.height(LocalMarkdownPadding.current.block)) 59 | when (node.type) { 60 | TEXT -> components.text(model) 61 | EOL -> components.eol(model) 62 | CODE_FENCE -> components.codeFence(model) 63 | CODE_BLOCK -> components.codeBlock(model) 64 | ATX_1 -> components.heading1(model) 65 | ATX_2 -> components.heading2(model) 66 | ATX_3 -> components.heading3(model) 67 | ATX_4 -> components.heading4(model) 68 | ATX_5 -> components.heading5(model) 69 | ATX_6 -> components.heading6(model) 70 | SETEXT_1 -> components.setextHeading1(model) 71 | SETEXT_2 -> components.setextHeading2(model) 72 | BLOCK_QUOTE -> components.blockQuote(model) 73 | PARAGRAPH -> components.paragraph(model) 74 | ORDERED_LIST -> components.orderedList(model) 75 | UNORDERED_LIST -> components.unorderedList(model) 76 | IMAGE -> components.image(model) 77 | HORIZONTAL_RULE -> components.horizontalRule(model) 78 | TABLE -> components.table(model) 79 | else -> { 80 | handled = components.custom?.invoke(node.type, model) != null 81 | } 82 | } 83 | 84 | if (!handled) { 85 | node.children.forEach { child -> 86 | MarkdownElement(child, components, content, includeSpacer) 87 | } 88 | } 89 | } 90 | --------------------------------------------------------------------------------