diff --git a/convention-plugins/src/main/kotlin/module.publication.gradle.kts b/convention-plugins/src/main/kotlin/module.publication.gradle.kts index 253681e2..be17226b 100644 --- a/convention-plugins/src/main/kotlin/module.publication.gradle.kts +++ b/convention-plugins/src/main/kotlin/module.publication.gradle.kts @@ -47,16 +47,26 @@ publishing { } } -signing { - useInMemoryPgpKeys( - System.getenv("OSSRH_GPG_SECRET_KEY_ID"), - System.getenv("OSSRH_GPG_SECRET_KEY"), - System.getenv("OSSRH_GPG_SECRET_KEY_PASSWORD"), - ) - sign(publishing.publications) +val signingKeyId = System.getenv("OSSRH_GPG_SECRET_KEY_ID") +val signingKey = System.getenv("OSSRH_GPG_SECRET_KEY") +val signingPassword = System.getenv("OSSRH_GPG_SECRET_KEY_PASSWORD") +val hasSigning = !signingKey.isNullOrBlank() && !signingPassword.isNullOrBlank() + +if (hasSigning) { + signing { + useInMemoryPgpKeys(signingKeyId, signingKey, signingPassword) + sign(publishing.publications) + } +} else { + // JitPack / CI builds usually do not have signing keys. Disable signing tasks. + tasks.withType(Sign::class.java).configureEach { + enabled = false + } } // TODO: remove after https://youtrack.jetbrains.com/issue/KT-46466 is fixed project.tasks.withType(AbstractPublishToMaven::class.java).configureEach { - dependsOn(project.tasks.withType(Sign::class.java)) -} \ No newline at end of file + if (hasSigning) { + dependsOn(project.tasks.withType(Sign::class.java)) + } +} diff --git a/gradle.properties b/gradle.properties index e7931bb0..e82728bf 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,6 +8,7 @@ org.gradle.java.installations.auto-download=true #Kotlin kotlin.code.style=official +kotlin.js.node.version=18.19.1 #Android android.useAndroidX=true diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 21d5e095..c6f00302 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/jitpack.yml b/jitpack.yml new file mode 100644 index 00000000..efde7bf2 --- /dev/null +++ b/jitpack.yml @@ -0,0 +1,2 @@ +jdk: + - openjdk17 diff --git a/richeditor-compose-coil3/build.gradle.kts b/richeditor-compose-coil3/build.gradle.kts index bd526529..89cf4e59 100644 --- a/richeditor-compose-coil3/build.gradle.kts +++ b/richeditor-compose-coil3/build.gradle.kts @@ -11,6 +11,10 @@ plugins { id("module.publication") } +// JitPack build images can ship with an older GLIBC. +// Kotlin/JS downloads a Node.js binary that may not run there, so we skip JS/WASM targets on JitPack. +val isJitPack = System.getenv("JITPACK") != null + kotlin { explicitApi() applyDefaultHierarchyTemplate() @@ -30,14 +34,16 @@ kotlin { } } - js(IR) { - browser() - } - @OptIn(ExperimentalWasmDsl::class) - wasmJs { - browser { - testTask { - enabled = false + if (!isJitPack) { + js(IR) { + browser() + } + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + browser { + testTask { + enabled = false + } } } } diff --git a/richeditor-compose/build.gradle.kts b/richeditor-compose/build.gradle.kts index ca0579c1..71b6b177 100644 --- a/richeditor-compose/build.gradle.kts +++ b/richeditor-compose/build.gradle.kts @@ -12,6 +12,11 @@ plugins { id("module.publication") } +// JitPack build images can ship with an older GLIBC. +// Kotlin/JS downloads a Node.js binary that may not run there. +// We skip JS/WASM targets on JitPack to keep the Android/Desktop publications working. +val isJitPack = System.getenv("JITPACK") != null + kotlin { explicitApi() applyDefaultHierarchyTemplate() @@ -31,12 +36,15 @@ kotlin { } } - js(IR).browser() - @OptIn(ExperimentalWasmDsl::class) - wasmJs { - browser { - testTask { - enabled = false + if (!isJitPack) { + js(IR).browser() + + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + browser { + testTask { + enabled = false + } } } } diff --git a/richeditor-compose/src/androidMain/kotlin/com/mohamedrejeb/richeditor/ui/ProvideNoSelectionToolbar.android.kt b/richeditor-compose/src/androidMain/kotlin/com/mohamedrejeb/richeditor/ui/ProvideNoSelectionToolbar.android.kt new file mode 100644 index 00000000..dd8697cb --- /dev/null +++ b/richeditor-compose/src/androidMain/kotlin/com/mohamedrejeb/richeditor/ui/ProvideNoSelectionToolbar.android.kt @@ -0,0 +1,37 @@ +package com.mohamedrejeb.richeditor.ui + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalTextToolbar +import androidx.compose.ui.platform.TextToolbar +import androidx.compose.ui.platform.TextToolbarStatus + +@Composable +internal actual fun ProvideNoSelectionToolbar( + disableSelectionToolbar: Boolean, + content: @Composable () -> Unit, +) { + if (!disableSelectionToolbar) { + content() + return + } + + val noToolbar: TextToolbar = remember { + object : TextToolbar { + override val status: TextToolbarStatus = TextToolbarStatus.Hidden + override fun hide() = Unit + override fun showMenu( + rect: androidx.compose.ui.geometry.Rect, + onCopyRequested: (() -> Unit)?, + onPasteRequested: (() -> Unit)?, + onCutRequested: (() -> Unit)?, + onSelectAllRequested: (() -> Unit)?, + ) = Unit + } + } + + CompositionLocalProvider(LocalTextToolbar provides noToolbar) { + content() + } +} diff --git a/richeditor-compose/src/commonMain/kotlin/com/mohamedrejeb/richeditor/ui/BasicRichTextEditor.kt b/richeditor-compose/src/commonMain/kotlin/com/mohamedrejeb/richeditor/ui/BasicRichTextEditor.kt index 9aac4a8c..19fe2268 100644 --- a/richeditor-compose/src/commonMain/kotlin/com/mohamedrejeb/richeditor/ui/BasicRichTextEditor.kt +++ b/richeditor-compose/src/commonMain/kotlin/com/mohamedrejeb/richeditor/ui/BasicRichTextEditor.kt @@ -98,7 +98,8 @@ public fun BasicRichTextEditor( interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, cursorBrush: Brush = SolidColor(Color.Black), decorationBox: @Composable (innerTextField: @Composable () -> Unit) -> Unit = - @Composable { innerTextField -> innerTextField() } + @Composable { innerTextField -> innerTextField() }, + disableSelectionToolbar: Boolean = false ) { BasicRichTextEditor( state = state, @@ -116,7 +117,8 @@ public fun BasicRichTextEditor( interactionSource = interactionSource, cursorBrush = cursorBrush, decorationBox = decorationBox, - contentPadding = PaddingValues() + contentPadding = PaddingValues(), + disableSelectionToolbar = disableSelectionToolbar ) } @@ -192,7 +194,8 @@ public fun BasicRichTextEditor( cursorBrush: Brush = SolidColor(Color.Black), decorationBox: @Composable (innerTextField: @Composable () -> Unit) -> Unit = @Composable { innerTextField -> innerTextField() }, - contentPadding: PaddingValues + contentPadding: PaddingValues, + disableSelectionToolbar: Boolean = false ) { val density = LocalDensity.current val layoutDirection = LocalLayoutDirection.current @@ -229,8 +232,9 @@ public fun BasicRichTextEditor( } } - CompositionLocalProvider(LocalClipboardManager provides richClipboardManager) { - BasicTextField( +CompositionLocalProvider(LocalClipboardManager provides richClipboardManager) { + ProvideNoSelectionToolbar(disableSelectionToolbar = disableSelectionToolbar) { + BasicTextField( value = state.textFieldValue, onValueChange = { if (readOnly) return@BasicTextField @@ -291,6 +295,8 @@ public fun BasicRichTextEditor( cursorBrush = cursorBrush, decorationBox = decorationBox, ) + + } } } diff --git a/richeditor-compose/src/commonMain/kotlin/com/mohamedrejeb/richeditor/ui/ProvideNoSelectionToolbar.kt b/richeditor-compose/src/commonMain/kotlin/com/mohamedrejeb/richeditor/ui/ProvideNoSelectionToolbar.kt new file mode 100644 index 00000000..ed905002 --- /dev/null +++ b/richeditor-compose/src/commonMain/kotlin/com/mohamedrejeb/richeditor/ui/ProvideNoSelectionToolbar.kt @@ -0,0 +1,15 @@ +package com.mohamedrejeb.richeditor.ui + +import androidx.compose.runtime.Composable + +/** + * Platform hook used to disable the system text selection toolbar (ActionMode: cut/copy/paste) + * when [disableSelectionToolbar] is true. + * + * Implemented per-platform to avoid referencing Android-only APIs from common code. + */ +@Composable +internal expect fun ProvideNoSelectionToolbar( + disableSelectionToolbar: Boolean, + content: @Composable () -> Unit, +) diff --git a/richeditor-compose/src/commonMain/kotlin/com/mohamedrejeb/richeditor/ui/material3/OutlinedRichTextEditor.kt b/richeditor-compose/src/commonMain/kotlin/com/mohamedrejeb/richeditor/ui/material3/OutlinedRichTextEditor.kt index fa65c8bd..76b600b7 100644 --- a/richeditor-compose/src/commonMain/kotlin/com/mohamedrejeb/richeditor/ui/material3/OutlinedRichTextEditor.kt +++ b/richeditor-compose/src/commonMain/kotlin/com/mohamedrejeb/richeditor/ui/material3/OutlinedRichTextEditor.kt @@ -110,6 +110,7 @@ public fun OutlinedRichTextEditor( interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, shape: Shape = RichTextEditorDefaults.outlinedShape, colors: RichTextEditorColors = RichTextEditorDefaults.outlinedRichTextEditorColors(), + disableSelectionToolbar: Boolean = false, contentPadding: PaddingValues = RichTextEditorDefaults.outlinedRichTextEditorPadding(), ) { // If color is not provided via the text style, use content color as a default diff --git a/richeditor-compose/src/commonMain/kotlin/com/mohamedrejeb/richeditor/ui/material3/RichTextEditor.kt b/richeditor-compose/src/commonMain/kotlin/com/mohamedrejeb/richeditor/ui/material3/RichTextEditor.kt index 79419623..330b444d 100644 --- a/richeditor-compose/src/commonMain/kotlin/com/mohamedrejeb/richeditor/ui/material3/RichTextEditor.kt +++ b/richeditor-compose/src/commonMain/kotlin/com/mohamedrejeb/richeditor/ui/material3/RichTextEditor.kt @@ -109,6 +109,7 @@ public fun RichTextEditor( interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, shape: Shape = RichTextEditorDefaults.filledShape, colors: RichTextEditorColors = RichTextEditorDefaults.richTextEditorColors(), + disableSelectionToolbar: Boolean = false, contentPadding: PaddingValues = if (label == null) { RichTextEditorDefaults.richTextEditorWithoutLabelPadding() @@ -142,6 +143,7 @@ public fun RichTextEditor( onTextLayout = onTextLayout, interactionSource = interactionSource, cursorBrush = SolidColor(colors.cursorColor(isError).value), + disableSelectionToolbar = disableSelectionToolbar, decorationBox = @Composable { innerTextField -> // places leading icon, text field with label and placeholder, trailing icon RichTextEditorDefaults.RichTextEditorDecorationBox( diff --git a/richeditor-compose/src/desktopMain/kotlin/com/mohamedrejeb/richeditor/ui/ProvideNoSelectionToolbar.desktopMain.kt b/richeditor-compose/src/desktopMain/kotlin/com/mohamedrejeb/richeditor/ui/ProvideNoSelectionToolbar.desktopMain.kt new file mode 100644 index 00000000..818855a3 --- /dev/null +++ b/richeditor-compose/src/desktopMain/kotlin/com/mohamedrejeb/richeditor/ui/ProvideNoSelectionToolbar.desktopMain.kt @@ -0,0 +1,12 @@ +package com.mohamedrejeb.richeditor.ui + +import androidx.compose.runtime.Composable + +@Composable +internal actual fun ProvideNoSelectionToolbar( + disableSelectionToolbar: Boolean, + content: @Composable () -> Unit, +) { + // No platform selection toolbar (ActionMode) available on this target. + content() +} diff --git a/richeditor-compose/src/iosMain/kotlin/com/mohamedrejeb/richeditor/ui/ProvideNoSelectionToolbar.iosMain.kt b/richeditor-compose/src/iosMain/kotlin/com/mohamedrejeb/richeditor/ui/ProvideNoSelectionToolbar.iosMain.kt new file mode 100644 index 00000000..818855a3 --- /dev/null +++ b/richeditor-compose/src/iosMain/kotlin/com/mohamedrejeb/richeditor/ui/ProvideNoSelectionToolbar.iosMain.kt @@ -0,0 +1,12 @@ +package com.mohamedrejeb.richeditor.ui + +import androidx.compose.runtime.Composable + +@Composable +internal actual fun ProvideNoSelectionToolbar( + disableSelectionToolbar: Boolean, + content: @Composable () -> Unit, +) { + // No platform selection toolbar (ActionMode) available on this target. + content() +} diff --git a/richeditor-compose/src/jsMain/kotlin/com/mohamedrejeb/richeditor/ui/ProvideNoSelectionToolbar.jsMain.kt b/richeditor-compose/src/jsMain/kotlin/com/mohamedrejeb/richeditor/ui/ProvideNoSelectionToolbar.jsMain.kt new file mode 100644 index 00000000..818855a3 --- /dev/null +++ b/richeditor-compose/src/jsMain/kotlin/com/mohamedrejeb/richeditor/ui/ProvideNoSelectionToolbar.jsMain.kt @@ -0,0 +1,12 @@ +package com.mohamedrejeb.richeditor.ui + +import androidx.compose.runtime.Composable + +@Composable +internal actual fun ProvideNoSelectionToolbar( + disableSelectionToolbar: Boolean, + content: @Composable () -> Unit, +) { + // No platform selection toolbar (ActionMode) available on this target. + content() +} diff --git a/richeditor-compose/src/wasmJsMain/kotlin/com/mohamedrejeb/richeditor/ui/ProvideNoSelectionToolbar.wasmJsMain.kt b/richeditor-compose/src/wasmJsMain/kotlin/com/mohamedrejeb/richeditor/ui/ProvideNoSelectionToolbar.wasmJsMain.kt new file mode 100644 index 00000000..818855a3 --- /dev/null +++ b/richeditor-compose/src/wasmJsMain/kotlin/com/mohamedrejeb/richeditor/ui/ProvideNoSelectionToolbar.wasmJsMain.kt @@ -0,0 +1,12 @@ +package com.mohamedrejeb.richeditor.ui + +import androidx.compose.runtime.Composable + +@Composable +internal actual fun ProvideNoSelectionToolbar( + disableSelectionToolbar: Boolean, + content: @Composable () -> Unit, +) { + // No platform selection toolbar (ActionMode) available on this target. + content() +} diff --git a/settings.gradle.kts b/settings.gradle.kts index a293e1ac..1f647589 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -25,12 +25,18 @@ plugins { id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" } +val isJitPack = System.getenv("JITPACK") != null + include( ":richeditor-compose", ":richeditor-compose-coil3", - - ":sample:android", - ":sample:desktop", - ":sample:web", - ":sample:common", ) + +if (!isJitPack) { + include( + ":sample:android", + ":sample:desktop", + ":sample:web", + ":sample:common", + ) +}