diff --git a/apps/mobile/modules/t3-markdown-text/android/build.gradle b/apps/mobile/modules/t3-markdown-text/android/build.gradle index 5b7e372006f307cd554d8ca7260ea807762a4f68..13584a00be426497491bad7651ad4e7181875a6c 100644 --- a/apps/mobile/modules/t3-markdown-text/android/build.gradle +++ b/apps/mobile/modules/t3-markdown-text/android/build.gradle @@ -8,10 +8,6 @@ android { namespace 'expo.modules.t3markdowntext' compileSdk rootProject.ext.compileSdkVersion - testOptions { - unitTests.includeAndroidResources = true - } - defaultConfig { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion @@ -21,12 +17,4 @@ android { dependencies { implementation project(':expo-modules-core') implementation 'com.facebook.react:react-android' - testImplementation 'junit:junit:4.13.2' - testImplementation 'org.robolectric:robolectric:4.16.1' -} - -tasks.withType(Test).configureEach { - javaLauncher = javaToolchains.launcherFor { - languageVersion = JavaLanguageVersion.of(21) - } } diff --git a/apps/mobile/modules/t3-markdown-text/android/src/main/java/expo/modules/t3markdowntext/T3MarkdownTextSelectionModule.kt b/apps/mobile/modules/t3-markdown-text/android/src/main/java/expo/modules/t3markdowntext/T3MarkdownTextSelectionModule.kt index c68b8e641aab45426e22a3d3a69f30ab21cdc3aa..af8675831f2d2753660b924f7b1840d36e574ab5 100644 --- a/apps/mobile/modules/t3-markdown-text/android/src/main/java/expo/modules/t3markdowntext/T3MarkdownTextSelectionModule.kt +++ b/apps/mobile/modules/t3-markdown-text/android/src/main/java/expo/modules/t3markdowntext/T3MarkdownTextSelectionModule.kt @@ -18,23 +18,19 @@ import kotlin.math.min private const val OBJECT_REPLACEMENT_CHARACTER = "\uFFFC" -internal fun copyTextWithoutInlineImages( +private fun copyTextWithoutInlineImages( text: CharSequence, start: Int, end: Int ): String { if (text !is Spanned) return text.subSequence(start, end).toString() - fun isInlineImage(index: Int): Boolean = - index >= 0 && text[index].toString() == OBJECT_REPLACEMENT_CHARACTER && - text.getSpans(index, index + 1, ReplacementSpan::class.java).isNotEmpty() - return buildString { for (index in start until end) { - // The renderer inserts one NBSP after each image to keep its label on the same line. - // Inspect the original text even when selection starts after the image. - val isIconSpacer = text[index] == '\u00A0' && isInlineImage(index - 1) - if (!isInlineImage(index) && !isIconSpacer) append(text[index]) + val isInlineImage = + text[index].toString() == OBJECT_REPLACEMENT_CHARACTER && + text.getSpans(index, index + 1, ReplacementSpan::class.java).isNotEmpty() + if (!isInlineImage) append(text[index]) } } } diff --git a/apps/mobile/modules/t3-markdown-text/android/src/test/java/expo/modules/t3markdowntext/MarkdownSelectionCopyTest.kt b/apps/mobile/modules/t3-markdown-text/android/src/test/java/expo/modules/t3markdowntext/MarkdownSelectionCopyTest.kt deleted file mode 100644 index da9012ee166581cf049515e2b6f5ad19600eb77d..0000000000000000000000000000000000000000 --- a/apps/mobile/modules/t3-markdown-text/android/src/test/java/expo/modules/t3markdowntext/MarkdownSelectionCopyTest.kt +++ /dev/null @@ -1,48 +0,0 @@ -package expo.modules.t3markdowntext - -import android.graphics.drawable.ColorDrawable -import android.text.SpannableString -import android.text.Spanned -import android.text.style.ImageSpan -import org.junit.Assert.assertEquals -import org.junit.Test -import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner -import org.robolectric.annotation.Config - -@RunWith(RobolectricTestRunner::class) -@Config(sdk = [36], manifest = Config.NONE) -class MarkdownSelectionCopyTest { - private fun withIcon(value: String): SpannableString = SpannableString(value).apply { - val index = value.indexOf('\uFFFC') - setSpan(ImageSpan(ColorDrawable()), index, index + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) - } - - @Test - fun removesIconAndInjectedSpacer() { - val text = withIcon("\uFFFC\u00A0main.go:12 starts the server.") - assertEquals("main.go:12 starts the server.", copyTextWithoutInlineImages(text, 0, text.length)) - } - - @Test - fun removesSpacerWhenSelectionStartsAfterIcon() { - val text = withIcon("\uFFFC\u00A0main.go:12 starts the server.") - assertEquals("main.go:12", copyTextWithoutInlineImages(text, 1, 12)) - } - - @Test - fun preservesAuthoredWhitespaceAndLiteralObjectCharacters() { - val text = withIcon("before\u00A0 \uFFFC\u00A0\u00A0 main.go after\u00A0\uFFFC\u00A0") - assertEquals( - "before\u00A0 \u00A0 main.go after\u00A0\uFFFC\u00A0", - copyTextWithoutInlineImages(text, 0, text.length) - ) - } - - @Test - fun preservesTextWithoutImageSpans() { - val text = "\uFFFC\u00A0main.go" - assertEquals(text, copyTextWithoutInlineImages(text, 0, text.length)) - assertEquals(text, copyTextWithoutInlineImages(SpannableString(text), 0, text.length)) - } -} diff --git a/apps/mobile/modules/t3-markdown-text/src/NativeMarkdownBlock.ios.tsx b/apps/mobile/modules/t3-markdown-text/src/NativeMarkdownBlock.ios.tsx index 3aa7e1cf4e468868e883b3ca3ec774a0ec5e9a7c..348a3c489a2c4458d04446ba8a078c6d1d88ef70 100644 --- a/apps/mobile/modules/t3-markdown-text/src/NativeMarkdownBlock.ios.tsx +++ b/apps/mobile/modules/t3-markdown-text/src/NativeMarkdownBlock.ios.tsx @@ -4,11 +4,7 @@ import type { MarkdownNode } from "react-native-nitro-markdown/headless"; import { CopyTextButton } from "./CopyTextButton"; import { MarkdownTextPrimitive } from "./MarkdownTextPrimitive"; -import { - nativeMarkdownBlockSpacing, - nativeMarkdownDocumentRuns, - nativeMarkdownListItemBlocks, -} from "./nativeMarkdownText"; +import { nativeMarkdownDocumentRuns, nativeMarkdownListItemBlocks } from "./nativeMarkdownText"; import { NativeMarkdownSelectableText } from "./NativeMarkdownSelectableText.ios"; import type { MarkdownCodeHighlighter, @@ -599,28 +595,17 @@ export function NativeMarkdownBlock(props: { switch (props.node.type) { case "document": return ( - + {(props.node.children ?? []).map((child, index) => ( - 0 - ? 8 - : 0, - }} - > - - + node={child} + skills={props.skills} + textStyle={props.textStyle} + highlightCode={props.highlightCode} + onLinkPress={props.onLinkPress} + depth={depth} + /> ))} ); diff --git a/apps/mobile/modules/t3-markdown-text/src/NativeMarkdownSelectableText.ios.tsx b/apps/mobile/modules/t3-markdown-text/src/NativeMarkdownSelectableText.ios.tsx index 7e1eaf88f546200ac55d8aa4f7709b17dcda194f..a5c6cf540f1cde16d35d40ef8638dba8f60bb2fd 100644 --- a/apps/mobile/modules/t3-markdown-text/src/NativeMarkdownSelectableText.ios.tsx +++ b/apps/mobile/modules/t3-markdown-text/src/NativeMarkdownSelectableText.ios.tsx @@ -222,10 +222,6 @@ export function NativeMarkdownSelectableText(props: { } } - if (Platform.OS === "android" && (run.fileIcon || linkIcon)) { - text = `\u00A0${text}`; - } - return { key: `${signature}:${occurrence}`, run, text, linkIcon }; }); // T3MarkdownText only rebuilds its attributed string during native layout. A diff --git a/apps/mobile/modules/t3-markdown-text/src/SelectableMarkdownText.ios.tsx b/apps/mobile/modules/t3-markdown-text/src/SelectableMarkdownText.ios.tsx index b7768ffbae03b7852273b79be348b0a8dfb60f55..2a231c6035840a458aa90ce714b5cf333e10f03f 100644 --- a/apps/mobile/modules/t3-markdown-text/src/SelectableMarkdownText.ios.tsx +++ b/apps/mobile/modules/t3-markdown-text/src/SelectableMarkdownText.ios.tsx @@ -1,5 +1,5 @@ import { useMemo } from "react"; -import { Platform, View } from "react-native"; +import { View } from "react-native"; import { parseMarkdownWithOptions } from "react-native-nitro-markdown/headless"; import { @@ -84,10 +84,8 @@ export function SelectableMarkdownText({ the resulting single-line width instead of reflowing it. */} {chunks.map((chunk, index) => { - // Android inline images drift when one Text mixes paragraph and list - // spacer line heights. Keep those layouts in separate native blocks. const content = - chunk.kind === "rich" || Platform.OS === "android" ? ( + chunk.kind === "rich" ? ( 0) { - appendSpacer(runs, nativeMarkdownBlockSpacing(children[index - 1], child)); + const previous = children[index - 1]; + appendSpacer( + runs, + child.type === "heading" ? 20 : previous?.type === "heading" ? 10 : 12, + ); } appendDocumentBlock(runs, child, depth); } diff --git a/apps/mobile/src/lib/nativeMarkdownText.test.ts b/apps/mobile/src/lib/nativeMarkdownText.test.ts index 1091aeb65a6c1bd8ee1ceb4c2c1ebc69d6cc9109..c3951c8d81f05d517cfac77637e19dbbcf3fde75 100644 --- a/apps/mobile/src/lib/nativeMarkdownText.test.ts +++ b/apps/mobile/src/lib/nativeMarkdownText.test.ts @@ -2,7 +2,6 @@ import { describe, expect, it } from "vite-plus/test"; import type { MarkdownNode } from "react-native-nitro-markdown/headless"; import { - nativeMarkdownBlockSpacing, nativeMarkdownChunkSpacing, nativeMarkdownDocumentChunks, nativeMarkdownDocumentRuns, @@ -382,11 +381,6 @@ describe("nativeMarkdownDocumentRuns", () => { .filter((run) => run.role === "spacer") .map((run) => run.spacing), ).toEqual([20, 10, 12]); - expect( - node.children?.map((child, index) => - nativeMarkdownBlockSpacing(node.children?.[index - 1], child), - ), - ).toEqual([0, 20, 10, 12]); }); it("renders tight list items whose inline nodes are direct children", () => { diff --git a/docs/user/composer.md b/docs/user/composer.md index 7e388d4eb34fb3c71cf8f3b11ba5fa30345d6fe8..4a8df5333664e30aa347972256d365fd6b303b91 100644 --- a/docs/user/composer.md +++ b/docs/user/composer.md @@ -6,9 +6,6 @@ include a skill when the task needs more context. Messages can contain up to 120,000 characters. Longer drafts stay in the composer so you can shorten them or split them into several messages. -On Android, long-press message text to select within a paragraph or list item. -Use the message’s copy button to copy the whole message. - ## Attach files Attach up to eight files per message. Images can be up to 10 MB; other files can